#include "stdafx.h"
#include "Loaders.h"
#include "WAVTools.h"
#include "Tagging.h"
#include "../common/version.h"
#ifndef MODPLUG_NO_FILESAVE
#include "../common/mptFileIO.h"
#endif
OPENMPT_NAMESPACE_BEGIN
WAVReader::WAVReader(FileReader &inputFile) : file(inputFile)
{
file.Rewind();
RIFFHeader fileHeader;
codePage = 28591; isDLS = false;
subFormat = 0;
mayBeCoolEdit16_8 = false;
if(!file.ReadStruct(fileHeader)
|| (fileHeader.magic != RIFFHeader::idRIFF && fileHeader.magic != RIFFHeader::idLIST)
|| (fileHeader.type != RIFFHeader::idWAVE && fileHeader.type != RIFFHeader::idwave))
{
return;
}
isDLS = (fileHeader.magic == RIFFHeader::idLIST);
auto chunks = file.ReadChunks<RIFFChunk>(2);
if(chunks.size() >= 4
&& chunks[1].GetHeader().GetID() == RIFFChunk::iddata
&& chunks[1].GetHeader().GetLength() % 2u != 0
&& chunks[2].GetHeader().GetLength() == 0
&& chunks[3].GetHeader().GetID() == RIFFChunk::id____)
{
file.Seek(sizeof(RIFFHeader));
chunks = file.ReadChunks<RIFFChunk>(1);
}
FileReader formatChunk = chunks.GetChunk(RIFFChunk::idfmt_);
if(!formatChunk.ReadStruct(formatInfo))
{
return;
}
if(formatInfo.format == WAVFormatChunk::fmtPCM && formatChunk.BytesLeft() == 4)
{
uint16 size = formatChunk.ReadIntLE<uint16>();
uint16 value = formatChunk.ReadIntLE<uint16>();
if(size == 2 && value == 1)
{
mayBeCoolEdit16_8 = true;
}
} else if(formatInfo.format == WAVFormatChunk::fmtExtensible)
{
WAVFormatChunkExtension extFormat;
if(!formatChunk.ReadStruct(extFormat))
{
return;
}
subFormat = static_cast<uint16>(mpt::UUID(extFormat.subFormat).GetData1());
}
sampleData = chunks.GetChunk(RIFFChunk::iddata);
if(!sampleData.IsValid())
{
sampleData = chunks.GetChunk(RIFFChunk::idpcm_);
}
sampleLength = chunks.GetChunk(RIFFChunk::idfact).ReadUint32LE();
if((formatInfo.format != WAVFormatChunk::fmtIMA_ADPCM || sampleLength == 0) && GetSampleSize() != 0)
{
if((GetBlockAlign() == 0) || (GetBlockAlign() / GetNumChannels() >= 2 * GetSampleSize()))
{
sampleLength = sampleData.GetLength() / GetSampleSize();
} else
{
sampleLength = sampleData.GetLength() / GetBlockAlign();
}
}
codePage = GetFileCodePage(chunks);
FindMetadataChunks(chunks);
wsmpChunk = chunks.GetChunk(RIFFChunk::idwsmp);
}
void WAVReader::FindMetadataChunks(ChunkReader::ChunkList<RIFFChunk> &chunks)
{
smplChunk = chunks.GetChunk(RIFFChunk::idsmpl);
instChunk = chunks.GetChunk(RIFFChunk::idinst);
cueChunk = chunks.GetChunk(RIFFChunk::idcue_);
ChunkReader listChunk = chunks.GetChunk(RIFFChunk::idLIST);
if(listChunk.ReadMagic("INFO"))
{
infoChunk = listChunk.ReadChunks<RIFFChunk>(2);
}
xtraChunk = chunks.GetChunk(RIFFChunk::idxtra);
}
uint16 WAVReader::GetFileCodePage(ChunkReader::ChunkList<RIFFChunk> &chunks)
{
FileReader csetChunk = chunks.GetChunk(RIFFChunk::idCSET);
if(!csetChunk.IsValid())
{
FileReader iSFT = infoChunk.GetChunk(RIFFChunk::idISFT);
if(iSFT.ReadMagic("OpenMPT"))
{
std::string versionString;
iSFT.ReadString<mpt::String::maybeNullTerminated>(versionString, iSFT.BytesLeft());
versionString = mpt::String::Trim(versionString);
Version version = Version::Parse(mpt::ToUnicode(mpt::CharsetISO8859_1, versionString));
if(version && version < MAKE_VERSION_NUMERIC(1,28,00,02))
{
return 1252; } else
{
return 28591; }
} else
{
return 28591; }
}
if(!csetChunk.CanRead(2))
{
return 28591; }
uint16 codepage = csetChunk.ReadUint16LE();
return codepage;
}
void WAVReader::ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharset, char (&sampleName)[MAX_SAMPLENAME])
{
FileReader textChunk = infoChunk.GetChunk(RIFFChunk::idINAM);
if(textChunk.IsValid())
{
std::string sampleNameEncoded;
textChunk.ReadString<mpt::String::nullTerminated>(sampleNameEncoded, textChunk.GetLength());
mpt::String::Copy(sampleName, mpt::ToCharset(sampleCharset, mpt::ToUnicode(codePage, mpt::CharsetWindows1252, sampleNameEncoded)));
}
if(isDLS)
{
mpt::String::Copy(sample.filename, sampleName);
}
const bool isOldMPT = infoChunk.GetChunk(RIFFChunk::idISFT).ReadMagic("Modplug Tracker");
WAVSampleInfoChunk sampleInfo;
smplChunk.Rewind();
if(smplChunk.ReadStruct(sampleInfo))
{
WAVSampleLoop loopData;
if(sampleInfo.numLoops > 1 && smplChunk.ReadStruct(loopData))
{
loopData.ApplyToSample(sample.nSustainStart, sample.nSustainEnd, sample.nLength, sample.uFlags, CHN_SUSTAINLOOP, CHN_PINGPONGSUSTAIN, isOldMPT);
}
if(smplChunk.ReadStruct(loopData))
{
loopData.ApplyToSample(sample.nLoopStart, sample.nLoopEnd, sample.nLength, sample.uFlags, CHN_LOOP, CHN_PINGPONGLOOP, isOldMPT);
}
sample.rootNote = static_cast<uint8>(sampleInfo.baseNote);
if(sample.rootNote < 128)
sample.rootNote += NOTE_MIN;
else
sample.rootNote = NOTE_NONE;
sample.SanitizeLoops();
}
if(sample.rootNote == NOTE_NONE && instChunk.LengthIsAtLeast(sizeof(WAVInstrumentChunk)))
{
WAVInstrumentChunk inst;
instChunk.Rewind();
if(instChunk.ReadStruct(inst))
{
sample.rootNote = inst.unshiftedNote;
if(sample.rootNote < 128)
sample.rootNote += NOTE_MIN;
else
sample.rootNote = NOTE_NONE;
}
}
if(cueChunk.IsValid())
{
uint32 numPoints = cueChunk.ReadUint32LE();
LimitMax(numPoints, mpt::saturate_cast<uint32>(MPT_ARRAY_COUNT(sample.cues)));
for(uint32 i = 0; i < numPoints; i++)
{
WAVCuePoint cuePoint;
cueChunk.ReadStruct(cuePoint);
sample.cues[i] = cuePoint.position;
}
}
WAVExtraChunk mptInfo;
xtraChunk.Rewind();
if(xtraChunk.ReadStruct(mptInfo))
{
if(mptInfo.flags & WAVExtraChunk::setPanning) sample.uFlags.set(CHN_PANNING);
sample.nPan = std::min<uint16>(mptInfo.defaultPan, 256);
sample.nVolume = std::min<uint16>(mptInfo.defaultVolume, 256);
sample.nGlobalVol = std::min<uint16>(mptInfo.globalVolume, 64);
sample.nVibType = static_cast<VibratoType>(mptInfo.vibratoType.get());
sample.nVibSweep = mptInfo.vibratoSweep;
sample.nVibDepth = mptInfo.vibratoDepth;
sample.nVibRate = mptInfo.vibratoRate;
if(xtraChunk.CanRead(MAX_SAMPLENAME))
{
xtraChunk.ReadString<mpt::String::nullTerminated>(sampleName, MAX_SAMPLENAME);
xtraChunk.ReadString<mpt::String::nullTerminated>(sample.filename, xtraChunk.BytesLeft());
}
}
}
void WAVSampleLoop::ApplyToSample(SmpLength &start, SmpLength &end, SmpLength sampleLength, SampleFlags &flags, ChannelFlags enableFlag, ChannelFlags bidiFlag, bool mptLoopFix) const
{
if(loopEnd == 0)
{
return;
}
start = std::min(static_cast<SmpLength>(loopStart), sampleLength);
end = Clamp(static_cast<SmpLength>(loopEnd), start, sampleLength);
if(!mptLoopFix && end < sampleLength)
{
end++;
}
flags.set(enableFlag);
if(loopType == loopBidi)
{
flags.set(bidiFlag);
}
}
void WAVSampleLoop::ConvertToWAV(SmpLength start, SmpLength end, bool bidi)
{
identifier = 0;
loopType = bidi ? loopBidi : loopForward;
loopStart = mpt::saturate_cast<uint32>(start);
if(end > start)
{
loopEnd = mpt::saturate_cast<uint32>(end - 1);
} else
{
loopEnd = loopStart;
}
fraction = 0;
playCount = 0;
}
#ifndef MODPLUG_NO_FILESAVE
WAVWriter::WAVWriter(std::ostream *stream) : s(stream)
{
Seek(sizeof(RIFFHeader));
}
WAVWriter::WAVWriter(mpt::byte_span data) : memory(data)
{
Seek(sizeof(RIFFHeader));
}
WAVWriter::~WAVWriter() noexcept(false)
{
if(!s || s->good())
{
Finalize();
}
}
size_t WAVWriter::Finalize()
{
FinalizeChunk();
RIFFHeader fileHeader;
fileHeader.magic = RIFFHeader::idRIFF;
fileHeader.length = static_cast<uint32>(totalSize - 8);
fileHeader.type = RIFFHeader::idWAVE;
Seek(0);
Write(fileHeader);
s = nullptr;
memory = {};
return totalSize;
}
void WAVWriter::StartChunk(RIFFChunk::ChunkIdentifiers id)
{
FinalizeChunk();
chunkStartPos = position;
chunkHeader.id = id;
Skip(sizeof(chunkHeader));
}
void WAVWriter::FinalizeChunk()
{
if(chunkStartPos != 0)
{
const size_t chunkSize = position - (chunkStartPos + sizeof(RIFFChunk));
chunkHeader.length = mpt::saturate_cast<uint32>(chunkSize);
size_t curPos = position;
Seek(chunkStartPos);
Write(chunkHeader);
Seek(curPos);
if((chunkSize % 2u) != 0)
{
uint8 padding = 0;
Write(padding);
}
chunkStartPos = 0;
}
}
void WAVWriter::Seek(size_t pos)
{
position = pos;
totalSize = std::max(totalSize, position);
if(s != nullptr)
{
s->seekp(position);
}
}
void WAVWriter::Write(const void *data, size_t numBytes)
{
if(s != nullptr)
{
s->write(static_cast<const char*>(data), numBytes);
} else if(!memory.empty())
{
if(position <= memory.size() && numBytes <= memory.size() - position)
{
memcpy(memory.data() + position, data, numBytes);
} else
{
MPT_ASSERT_NOTREACHED();
}
}
position += numBytes;
totalSize = std::max(totalSize, position);
}
void WAVWriter::WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding)
{
StartChunk(RIFFChunk::idfmt_);
WAVFormatChunk wavFormat;
bool extensible = (numChannels > 2);
wavFormat.format = static_cast<uint16>(extensible ? WAVFormatChunk::fmtExtensible : encoding);
wavFormat.numChannels = numChannels;
wavFormat.sampleRate = sampleRate;
wavFormat.blockAlign = (bitDepth * numChannels + 7) / 8;
wavFormat.byteRate = wavFormat.sampleRate * wavFormat.blockAlign;
wavFormat.bitsPerSample = bitDepth;
Write(wavFormat);
if(extensible)
{
WAVFormatChunkExtension extFormat;
extFormat.size = sizeof(WAVFormatChunkExtension) - sizeof(uint16);
extFormat.validBitsPerSample = bitDepth;
switch(numChannels)
{
case 1:
extFormat.channelMask = 0x0004; break;
case 2:
extFormat.channelMask = 0x0003; break;
case 3:
extFormat.channelMask = 0x0103; break;
case 4:
extFormat.channelMask = 0x0033; break;
default:
extFormat.channelMask = 0;
break;
}
extFormat.subFormat = mpt::UUID(static_cast<uint16>(encoding), 0x0000, 0x0010, 0x800000AA00389B71ull);
Write(extFormat);
}
}
void WAVWriter::WriteMetatags(const FileTags &tags)
{
StartChunk(RIFFChunk::idCSET);
Write(mpt::as_le(uint16(65001))); Write(mpt::as_le(uint16(0))); Write(mpt::as_le(uint16(0))); Write(mpt::as_le(uint16(0)));
StartChunk(RIFFChunk::idLIST);
const char info[] = { 'I', 'N', 'F', 'O' };
WriteArray(info);
WriteTag(RIFFChunk::idINAM, tags.title);
WriteTag(RIFFChunk::idIART, tags.artist);
WriteTag(RIFFChunk::idIPRD, tags.album);
WriteTag(RIFFChunk::idICRD, tags.year);
WriteTag(RIFFChunk::idICMT, tags.comments);
WriteTag(RIFFChunk::idIGNR, tags.genre);
WriteTag(RIFFChunk::idTURL, tags.url);
WriteTag(RIFFChunk::idISFT, tags.encoder);
WriteTag(RIFFChunk::idTRCK, tags.trackno);
}
void WAVWriter::WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext)
{
std::string text = mpt::ToCharset(mpt::CharsetUTF8, utext);
text = text.substr(0, uint32_max - 1u);
if(!text.empty())
{
const uint32 length = mpt::saturate_cast<uint32>(text.length() + 1);
RIFFChunk chunk;
chunk.id = static_cast<uint32>(id);
chunk.length = length;
Write(chunk);
Write(text.c_str(), length);
if((length % 2u) != 0)
{
uint8 padding = 0;
Write(padding);
}
}
}
void WAVWriter::WriteLoopInformation(const ModSample &sample)
{
if(!sample.uFlags[CHN_LOOP | CHN_SUSTAINLOOP] && !ModCommand::IsNote(sample.rootNote))
{
return;
}
StartChunk(RIFFChunk::idsmpl);
WAVSampleInfoChunk info;
uint32 sampleRate = sample.nC5Speed;
if(sampleRate == 0)
{
sampleRate = ModSample::TransposeToFrequency(sample.RelativeTone, sample.nFineTune);
}
info.ConvertToWAV(sampleRate, sample.rootNote);
WAVSampleLoop loops[2];
if(sample.uFlags[CHN_SUSTAINLOOP])
{
loops[info.numLoops++].ConvertToWAV(sample.nSustainStart, sample.nSustainEnd, sample.uFlags[CHN_PINGPONGSUSTAIN]);
}
if(sample.uFlags[CHN_LOOP])
{
loops[info.numLoops++].ConvertToWAV(sample.nLoopStart, sample.nLoopEnd, sample.uFlags[CHN_PINGPONGLOOP]);
} else if(sample.uFlags[CHN_SUSTAINLOOP])
{
loops[info.numLoops++].ConvertToWAV(0, 0, false);
}
Write(info);
for(uint32 i = 0; i < info.numLoops; i++)
{
Write(loops[i]);
}
}
void WAVWriter::WriteCueInformation(const ModSample &sample)
{
StartChunk(RIFFChunk::idcue_);
{
Write(mpt::as_le(static_cast<uint32>(CountOf(sample.cues))));
}
for(uint32 i = 0; i < CountOf(sample.cues); i++)
{
WAVCuePoint cuePoint;
cuePoint.ConvertToWAV(i, sample.cues[i]);
Write(cuePoint);
}
}
void WAVWriter::WriteExtraInformation(const ModSample &sample, MODTYPE modType, const char *sampleName)
{
StartChunk(RIFFChunk::idxtra);
WAVExtraChunk mptInfo;
mptInfo.ConvertToWAV(sample, modType);
Write(mptInfo);
if(sampleName != nullptr)
{
char name[MAX_SAMPLENAME];
mpt::String::Write<mpt::String::nullTerminated>(name, sampleName, MAX_SAMPLENAME);
WriteArray(name);
char filename[MAX_SAMPLEFILENAME];
mpt::String::Write<mpt::String::nullTerminated>(filename, sample.filename);
WriteArray(filename);
}
}
#endif
OPENMPT_NAMESPACE_END