123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652 |
- /*
- * SampleFormats.cpp
- * -----------------
- * Purpose: Code for loading various more or less common sample and instrument formats.
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Sndfile.h"
- #include "mod_specifications.h"
- #ifdef MODPLUG_TRACKER
- #include "../mptrack/Moddoc.h"
- #include "Dlsbank.h"
- #endif // MODPLUG_TRACKER
- #include "../soundlib/AudioCriticalSection.h"
- #ifndef MODPLUG_NO_FILESAVE
- #include "mpt/io/base.hpp"
- #include "mpt/io/io.hpp"
- #include "mpt/io/io_stdstream.hpp"
- #include "../common/mptFileIO.h"
- #endif // !MODPLUG_NO_FILESAVE
- #include "../common/misc_util.h"
- #include "openmpt/base/Endian.hpp"
- #include "Tagging.h"
- #include "ITTools.h"
- #include "XMTools.h"
- #include "S3MTools.h"
- #include "WAVTools.h"
- #include "../common/version.h"
- #include "Loaders.h"
- #include "../common/FileReader.h"
- #include "../soundlib/ModSampleCopy.h"
- #include <functional>
- #include <map>
- OPENMPT_NAMESPACE_BEGIN
- using namespace mpt::uuid_literals;
- bool CSoundFile::ReadSampleFromFile(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize, bool includeInstrumentFormats)
- {
- if(!nSample || nSample >= MAX_SAMPLES) return false;
- if(!ReadWAVSample(nSample, file, mayNormalize)
- && !(includeInstrumentFormats && ReadXISample(nSample, file))
- && !(includeInstrumentFormats && ReadITISample(nSample, file))
- && !ReadW64Sample(nSample, file)
- && !ReadCAFSample(nSample, file)
- && !ReadAIFFSample(nSample, file, mayNormalize)
- && !ReadITSSample(nSample, file)
- && !(includeInstrumentFormats && ReadPATSample(nSample, file))
- && !ReadIFFSample(nSample, file)
- && !ReadS3ISample(nSample, file)
- && !ReadSBISample(nSample, file)
- && !ReadAUSample(nSample, file, mayNormalize)
- && !ReadBRRSample(nSample, file)
- && !ReadFLACSample(nSample, file)
- && !ReadOpusSample(nSample, file)
- && !ReadVorbisSample(nSample, file)
- && !ReadMP3Sample(nSample, file, false)
- && !ReadMediaFoundationSample(nSample, file)
- )
- {
- return false;
- }
- if(nSample > GetNumSamples())
- {
- m_nSamples = nSample;
- }
- if(Samples[nSample].uFlags[CHN_ADLIB])
- {
- InitOPL();
- }
- return true;
- }
- bool CSoundFile::ReadInstrumentFromFile(INSTRUMENTINDEX nInstr, FileReader &file, bool mayNormalize)
- {
- if ((!nInstr) || (nInstr >= MAX_INSTRUMENTS)) return false;
- if(!ReadITIInstrument(nInstr, file)
- && !ReadXIInstrument(nInstr, file)
- && !ReadPATInstrument(nInstr, file)
- && !ReadSFZInstrument(nInstr, file)
- // Generic read
- && !ReadSampleAsInstrument(nInstr, file, mayNormalize))
- {
- bool ok = false;
- #ifdef MODPLUG_TRACKER
- CDLSBank bank;
- if(bank.Open(file))
- {
- ok = bank.ExtractInstrument(*this, nInstr, 0, 0);
- }
- #endif // MODPLUG_TRACKER
- if(!ok) return false;
- }
- if(nInstr > GetNumInstruments()) m_nInstruments = nInstr;
- return true;
- }
- bool CSoundFile::ReadSampleAsInstrument(INSTRUMENTINDEX nInstr, FileReader &file, bool mayNormalize)
- {
- // Scanning free sample
- SAMPLEINDEX nSample = GetNextFreeSample(nInstr); // may also return samples which are only referenced by the current instrument
- if(nSample == SAMPLEINDEX_INVALID)
- {
- return false;
- }
- // Loading Instrument
- ModInstrument *pIns = new (std::nothrow) ModInstrument(nSample);
- if(pIns == nullptr)
- {
- return false;
- }
- if(!ReadSampleFromFile(nSample, file, mayNormalize, false))
- {
- delete pIns;
- return false;
- }
- // Remove all samples which are only referenced by the old instrument, except for the one we just loaded our new sample into.
- RemoveInstrumentSamples(nInstr, nSample);
- // Replace the instrument
- DestroyInstrument(nInstr, doNoDeleteAssociatedSamples);
- Instruments[nInstr] = pIns;
- #if defined(MPT_ENABLE_FILEIO) && defined(MPT_EXTERNAL_SAMPLES)
- SetSamplePath(nSample, file.GetOptionalFileName().value_or(P_("")));
- #endif
- return true;
- }
- bool CSoundFile::DestroyInstrument(INSTRUMENTINDEX nInstr, deleteInstrumentSamples removeSamples)
- {
- if(nInstr == 0 || nInstr >= MAX_INSTRUMENTS || !Instruments[nInstr]) return true;
- if(removeSamples == deleteAssociatedSamples)
- {
- RemoveInstrumentSamples(nInstr);
- }
- CriticalSection cs;
- ModInstrument *pIns = Instruments[nInstr];
- Instruments[nInstr] = nullptr;
- for(auto &chn : m_PlayState.Chn)
- {
- if(chn.pModInstrument == pIns)
- chn.pModInstrument = nullptr;
- }
- delete pIns;
- return true;
- }
- // Remove all unused samples from the given nInstr and keep keepSample if provided
- bool CSoundFile::RemoveInstrumentSamples(INSTRUMENTINDEX nInstr, SAMPLEINDEX keepSample)
- {
- if(Instruments[nInstr] == nullptr)
- {
- return false;
- }
- std::vector<bool> keepSamples(GetNumSamples() + 1, true);
- // Check which samples are used by the instrument we are going to nuke.
- auto referencedSamples = Instruments[nInstr]->GetSamples();
- for(auto sample : referencedSamples)
- {
- if(sample <= GetNumSamples())
- {
- keepSamples[sample] = false;
- }
- }
- // If we want to keep a specific sample, do so.
- if(keepSample != SAMPLEINDEX_INVALID)
- {
- if(keepSample <= GetNumSamples())
- {
- keepSamples[keepSample] = true;
- }
- }
- // Check if any of those samples are referenced by other instruments as well, in which case we want to keep them of course.
- for(INSTRUMENTINDEX nIns = 1; nIns <= GetNumInstruments(); nIns++) if (Instruments[nIns] != nullptr && nIns != nInstr)
- {
- Instruments[nIns]->GetSamples(keepSamples);
- }
- // Now nuke the selected samples.
- RemoveSelectedSamples(keepSamples);
- return true;
- }
- ////////////////////////////////////////////////////////////////////////////////
- //
- // I/O From another song
- //
- bool CSoundFile::ReadInstrumentFromSong(INSTRUMENTINDEX targetInstr, const CSoundFile &srcSong, INSTRUMENTINDEX sourceInstr)
- {
- if ((!sourceInstr) || (sourceInstr > srcSong.GetNumInstruments())
- || (targetInstr >= MAX_INSTRUMENTS) || (!srcSong.Instruments[sourceInstr]))
- {
- return false;
- }
- if (m_nInstruments < targetInstr) m_nInstruments = targetInstr;
- ModInstrument *pIns = new (std::nothrow) ModInstrument();
- if(pIns == nullptr)
- {
- return false;
- }
- DestroyInstrument(targetInstr, deleteAssociatedSamples);
- Instruments[targetInstr] = pIns;
- *pIns = *srcSong.Instruments[sourceInstr];
- std::vector<SAMPLEINDEX> sourceSample; // Sample index in source song
- std::vector<SAMPLEINDEX> targetSample; // Sample index in target song
- SAMPLEINDEX targetIndex = 0; // Next index for inserting sample
- for(auto &sample : pIns->Keyboard)
- {
- const SAMPLEINDEX sourceIndex = sample;
- if(sourceIndex > 0 && sourceIndex <= srcSong.GetNumSamples())
- {
- const auto entry = std::find(sourceSample.cbegin(), sourceSample.cend(), sourceIndex);
- if(entry == sourceSample.end())
- {
- // Didn't consider this sample yet, so add it to our map.
- targetIndex = GetNextFreeSample(targetInstr, targetIndex + 1);
- if(targetIndex <= GetModSpecifications().samplesMax)
- {
- sourceSample.push_back(sourceIndex);
- targetSample.push_back(targetIndex);
- sample = targetIndex;
- } else
- {
- sample = 0;
- }
- } else
- {
- // Sample reference has already been created, so only need to update the sample map.
- sample = *(entry - sourceSample.begin() + targetSample.begin());
- }
- } else
- {
- // Invalid or no source sample
- sample = 0;
- }
- }
- #ifdef MODPLUG_TRACKER
- if(pIns->filename.empty() && srcSong.GetpModDoc() != nullptr && &srcSong != this)
- {
- pIns->filename = srcSong.GetpModDoc()->GetPathNameMpt().GetFullFileName().ToLocale();
- }
- #endif
- pIns->Convert(srcSong.GetType(), GetType());
- // Copy all referenced samples over
- for(size_t i = 0; i < targetSample.size(); i++)
- {
- ReadSampleFromSong(targetSample[i], srcSong, sourceSample[i]);
- }
- return true;
- }
- bool CSoundFile::ReadSampleFromSong(SAMPLEINDEX targetSample, const CSoundFile &srcSong, SAMPLEINDEX sourceSample)
- {
- if(!sourceSample
- || sourceSample > srcSong.GetNumSamples()
- || (targetSample >= GetModSpecifications().samplesMax && targetSample > GetNumSamples()))
- {
- return false;
- }
- DestroySampleThreadsafe(targetSample);
- const ModSample &sourceSmp = srcSong.GetSample(sourceSample);
- ModSample &targetSmp = GetSample(targetSample);
- if(GetNumSamples() < targetSample) m_nSamples = targetSample;
- targetSmp = sourceSmp;
- m_szNames[targetSample] = srcSong.m_szNames[sourceSample];
- if(sourceSmp.HasSampleData())
- {
- if(targetSmp.CopyWaveform(sourceSmp))
- targetSmp.PrecomputeLoops(*this, false);
- // Remember on-disk path (for MPTM files), but don't implicitely enable on-disk storage
- // (we really don't want this for e.g. duplicating samples or splitting stereo samples)
- #ifdef MPT_EXTERNAL_SAMPLES
- SetSamplePath(targetSample, srcSong.GetSamplePath(sourceSample));
- #endif
- targetSmp.uFlags.reset(SMP_KEEPONDISK);
- }
- #ifdef MODPLUG_TRACKER
- if((targetSmp.filename.empty()) && srcSong.GetpModDoc() != nullptr && &srcSong != this)
- {
- targetSmp.filename = mpt::ToCharset(GetCharsetInternal(), srcSong.GetpModDoc()->GetTitle());
- }
- #endif
- if(targetSmp.uFlags[CHN_ADLIB] && !SupportsOPL())
- {
- AddToLog(LogInformation, U_("OPL instruments are not supported by this format."));
- }
- targetSmp.Convert(srcSong.GetType(), GetType());
- if(targetSmp.uFlags[CHN_ADLIB])
- {
- InitOPL();
- }
- return true;
- }
- ////////////////////////////////////////////////////////////////////////
- // IMA ADPCM Support for WAV files
- static bool IMAADPCMUnpack16(int16 *target, SmpLength sampleLen, FileReader file, uint16 blockAlign, uint32 numChannels)
- {
- static constexpr int8 IMAIndexTab[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
- static constexpr int16 IMAUnpackTable[90] =
- {
- 7, 8, 9, 10, 11, 12, 13, 14,
- 16, 17, 19, 21, 23, 25, 28, 31,
- 34, 37, 41, 45, 50, 55, 60, 66,
- 73, 80, 88, 97, 107, 118, 130, 143,
- 157, 173, 190, 209, 230, 253, 279, 307,
- 337, 371, 408, 449, 494, 544, 598, 658,
- 724, 796, 876, 963, 1060, 1166, 1282, 1411,
- 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
- 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
- 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
- 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
- 32767, 0
- };
- if(target == nullptr || blockAlign < 4u * numChannels)
- return false;
- SmpLength samplePos = 0;
- sampleLen *= numChannels;
- while(file.CanRead(4u * numChannels) && samplePos < sampleLen)
- {
- FileReader block = file.ReadChunk(blockAlign);
- FileReader::PinnedView blockView = block.GetPinnedView();
- const std::byte *data = blockView.data();
- const uint32 blockSize = static_cast<uint32>(blockView.size());
- for(uint32 chn = 0; chn < numChannels; chn++)
- {
- // Block header
- int32 value = block.ReadInt16LE();
- int32 nIndex = block.ReadUint8();
- Limit(nIndex, 0, 89);
- block.Skip(1);
- SmpLength smpPos = samplePos + chn;
- uint32 dataPos = (numChannels + chn) * 4;
- // Block data
- while(smpPos <= (sampleLen - 8) && dataPos <= (blockSize - 4))
- {
- for(uint32 i = 0; i < 8; i++)
- {
- uint8 delta = mpt::byte_cast<uint8>(data[dataPos]);
- if(i & 1)
- {
- delta >>= 4;
- dataPos++;
- } else
- {
- delta &= 0x0F;
- }
- int32 v = IMAUnpackTable[nIndex] >> 3;
- if (delta & 1) v += IMAUnpackTable[nIndex] >> 2;
- if (delta & 2) v += IMAUnpackTable[nIndex] >> 1;
- if (delta & 4) v += IMAUnpackTable[nIndex];
- if (delta & 8) value -= v; else value += v;
- nIndex += IMAIndexTab[delta & 7];
- Limit(nIndex, 0, 88);
- Limit(value, -32768, 32767);
- target[smpPos] = static_cast<int16>(value);
- smpPos += numChannels;
- }
- dataPos += (numChannels - 1) * 4u;
- }
- }
- samplePos += ((blockSize - (numChannels * 4u)) * 2u);
- }
- return true;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // WAV Open
- bool CSoundFile::ReadWAVSample(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize, FileReader *wsmpChunk)
- {
- WAVReader wavFile(file);
- static constexpr WAVFormatChunk::SampleFormats SupportedFormats[] = {WAVFormatChunk::fmtPCM, WAVFormatChunk::fmtFloat, WAVFormatChunk::fmtIMA_ADPCM, WAVFormatChunk::fmtMP3, WAVFormatChunk::fmtALaw, WAVFormatChunk::fmtULaw};
- if(!wavFile.IsValid()
- || wavFile.GetNumChannels() == 0
- || wavFile.GetNumChannels() > 2
- || (wavFile.GetBitsPerSample() == 0 && wavFile.GetSampleFormat() != WAVFormatChunk::fmtMP3)
- || (wavFile.GetBitsPerSample() < 32 && wavFile.GetSampleFormat() == WAVFormatChunk::fmtFloat)
- || (wavFile.GetBitsPerSample() > 64)
- || !mpt::contains(SupportedFormats, wavFile.GetSampleFormat()))
- {
- return false;
- }
- DestroySampleThreadsafe(nSample);
- m_szNames[nSample] = "";
- ModSample &sample = Samples[nSample];
- sample.Initialize();
- sample.nLength = wavFile.GetSampleLength();
- sample.nC5Speed = wavFile.GetSampleRate();
- wavFile.ApplySampleSettings(sample, GetCharsetInternal(), m_szNames[nSample]);
- FileReader sampleChunk = wavFile.GetSampleData();
- SampleIO sampleIO(
- SampleIO::_8bit,
- (wavFile.GetNumChannels() > 1) ? SampleIO::stereoInterleaved : SampleIO::mono,
- SampleIO::littleEndian,
- SampleIO::signedPCM);
- if(wavFile.GetSampleFormat() == WAVFormatChunk::fmtIMA_ADPCM && wavFile.GetNumChannels() <= 2)
- {
- // IMA ADPCM 4:1
- LimitMax(sample.nLength, MAX_SAMPLE_LENGTH);
- sample.uFlags.set(CHN_16BIT);
- sample.uFlags.set(CHN_STEREO, wavFile.GetNumChannels() == 2);
- if(!sample.AllocateSample())
- {
- return false;
- }
- IMAADPCMUnpack16(sample.sample16(), sample.nLength, sampleChunk, wavFile.GetBlockAlign(), wavFile.GetNumChannels());
- sample.PrecomputeLoops(*this, false);
- } else if(wavFile.GetSampleFormat() == WAVFormatChunk::fmtMP3)
- {
- // MP3 in WAV
- bool loadedMP3 = ReadMP3Sample(nSample, sampleChunk, false, true) || ReadMediaFoundationSample(nSample, sampleChunk, true);
- if(!loadedMP3)
- {
- return false;
- }
- } else if(!wavFile.IsExtensibleFormat() && wavFile.MayBeCoolEdit16_8() && wavFile.GetSampleFormat() == WAVFormatChunk::fmtPCM && wavFile.GetBitsPerSample() == 32 && wavFile.GetBlockAlign() == wavFile.GetNumChannels() * 4)
- {
- // Syntrillium Cool Edit hack to store IEEE 32bit floating point
- // Format is described as 32bit integer PCM contained in 32bit blocks and an WAVEFORMATEX extension size of 2 which contains a single 16 bit little endian value of 1.
- // (This is parsed in WAVTools.cpp and returned via MayBeCoolEdit16_8()).
- // The data actually stored in this case is little endian 32bit floating point PCM with 2**15 full scale.
- // Cool Edit calls this format "16.8 float".
- sampleIO |= SampleIO::_32bit;
- sampleIO |= SampleIO::floatPCM15;
- sampleIO.ReadSample(sample, sampleChunk);
- } else if(!wavFile.IsExtensibleFormat() && wavFile.GetSampleFormat() == WAVFormatChunk::fmtPCM && wavFile.GetBitsPerSample() == 24 && wavFile.GetBlockAlign() == wavFile.GetNumChannels() * 4)
- {
- // Syntrillium Cool Edit hack to store IEEE 32bit floating point
- // Format is described as 24bit integer PCM contained in 32bit blocks.
- // The data actually stored in this case is little endian 32bit floating point PCM with 2**23 full scale.
- // Cool Edit calls this format "24.0 float".
- sampleIO |= SampleIO::_32bit;
- sampleIO |= SampleIO::floatPCM23;
- sampleIO.ReadSample(sample, sampleChunk);
- } else if(wavFile.GetSampleFormat() == WAVFormatChunk::fmtALaw || wavFile.GetSampleFormat() == WAVFormatChunk::fmtULaw)
- {
- // a-law / u-law
- sampleIO |= SampleIO::_16bit;
- sampleIO |= wavFile.GetSampleFormat() == WAVFormatChunk::fmtALaw ? SampleIO::aLaw : SampleIO::uLaw;
- sampleIO.ReadSample(sample, sampleChunk);
- } else
- {
- // PCM / Float
- SampleIO::Bitdepth bitDepth;
- switch((wavFile.GetBitsPerSample() - 1) / 8u)
- {
- default:
- case 0: bitDepth = SampleIO::_8bit; break;
- case 1: bitDepth = SampleIO::_16bit; break;
- case 2: bitDepth = SampleIO::_24bit; break;
- case 3: bitDepth = SampleIO::_32bit; break;
- case 7: bitDepth = SampleIO::_64bit; break;
- }
- sampleIO |= bitDepth;
- if(wavFile.GetBitsPerSample() <= 8)
- sampleIO |= SampleIO::unsignedPCM;
- if(wavFile.GetSampleFormat() == WAVFormatChunk::fmtFloat)
- sampleIO |= SampleIO::floatPCM;
- if(mayNormalize)
- sampleIO.MayNormalize();
- sampleIO.ReadSample(sample, sampleChunk);
- }
- if(wsmpChunk != nullptr)
- {
- // DLS WSMP chunk
- *wsmpChunk = wavFile.GetWsmpChunk();
- }
- sample.Convert(MOD_TYPE_IT, GetType());
- sample.PrecomputeLoops(*this, false);
- return true;
- }
- ///////////////////////////////////////////////////////////////
- // Save WAV
- #ifndef MODPLUG_NO_FILESAVE
- bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, std::ostream &f) const
- {
- const ModSample &sample = Samples[nSample];
- if(sample.uFlags[CHN_ADLIB])
- return false;
- mpt::IO::OFile<std::ostream> ff(f);
- WAVWriter file(ff);
- file.WriteFormat(sample.GetSampleRate(GetType()), sample.GetElementarySampleSize() * 8, sample.GetNumChannels(), WAVFormatChunk::fmtPCM);
- // Write sample data
- file.StartChunk(RIFFChunk::iddata);
- file.Skip(SampleIO(
- sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
- sample.uFlags[CHN_STEREO] ? SampleIO::stereoInterleaved : SampleIO::mono,
- SampleIO::littleEndian,
- sample.uFlags[CHN_16BIT] ? SampleIO::signedPCM : SampleIO::unsignedPCM)
- .WriteSample(f, sample));
- file.WriteLoopInformation(sample);
- file.WriteExtraInformation(sample, GetType());
- if(sample.HasCustomCuePoints())
- {
- file.WriteCueInformation(sample);
- }
- FileTags tags;
- tags.SetEncoder();
- tags.title = mpt::ToUnicode(GetCharsetInternal(), m_szNames[nSample]);
- file.WriteMetatags(tags);
- file.Finalize();
- return true;
- }
- #endif // MODPLUG_NO_FILESAVE
- /////////////////
- // Sony Wave64 //
- struct Wave64FileHeader
- {
- mpt::GUIDms GuidRIFF;
- uint64le FileSize;
- mpt::GUIDms GuidWAVE;
- };
- MPT_BINARY_STRUCT(Wave64FileHeader, 40)
- struct Wave64ChunkHeader
- {
- mpt::GUIDms GuidChunk;
- uint64le Size;
- };
- MPT_BINARY_STRUCT(Wave64ChunkHeader, 24)
- struct Wave64Chunk
- {
- Wave64ChunkHeader header;
- FileReader::off_t GetLength() const
- {
- uint64 length = header.Size;
- if(length < sizeof(Wave64ChunkHeader))
- {
- length = 0;
- } else
- {
- length -= sizeof(Wave64ChunkHeader);
- }
- return mpt::saturate_cast<FileReader::off_t>(length);
- }
- mpt::UUID GetID() const
- {
- return mpt::UUID(header.GuidChunk);
- }
- };
- MPT_BINARY_STRUCT(Wave64Chunk, 24)
- static void Wave64TagFromLISTINFO(mpt::ustring & dst, uint16 codePage, const FileReader::ChunkList<RIFFChunk> & infoChunk, RIFFChunk::ChunkIdentifiers id)
- {
- if(!infoChunk.ChunkExists(id))
- {
- return;
- }
- FileReader textChunk = infoChunk.GetChunk(id);
- if(!textChunk.IsValid())
- {
- return;
- }
- std::string str;
- textChunk.ReadString<mpt::String::maybeNullTerminated>(str, textChunk.GetLength());
- str = mpt::replace(str, std::string("\r\n"), std::string("\n"));
- str = mpt::replace(str, std::string("\r"), std::string("\n"));
- dst = mpt::ToUnicode(codePage, mpt::Charset::Windows1252, str);
- }
- bool CSoundFile::ReadW64Sample(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize)
- {
- file.Rewind();
- constexpr mpt::UUID guidRIFF = "66666972-912E-11CF-A5D6-28DB04C10000"_uuid;
- constexpr mpt::UUID guidWAVE = "65766177-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- constexpr mpt::UUID guidLIST = "7473696C-912F-11CF-A5D6-28DB04C10000"_uuid;
- constexpr mpt::UUID guidFMT = "20746D66-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- //constexpr mpt::UUID guidFACT = "74636166-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- constexpr mpt::UUID guidDATA = "61746164-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- //constexpr mpt::UUID guidLEVL = "6C76656C-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- //constexpr mpt::UUID guidJUNK = "6b6E756A-ACF3-11D3-8CD1-00C04f8EDB8A"_uuid;
- //constexpr mpt::UUID guidBEXT = "74786562-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- //constexpr mpt::UUID guiMARKER = "ABF76256-392D-11D2-86C7-00C04F8EDB8A"_uuid;
- //constexpr mpt::UUID guiSUMMARYLIST = "925F94BC-525A-11D2-86DC-00C04F8EDB8A"_uuid;
- constexpr mpt::UUID guidCSET = "54455343-ACF3-11D3-8CD1-00C04F8EDB8A"_uuid;
- Wave64FileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(mpt::UUID(fileHeader.GuidRIFF) != guidRIFF)
- {
- return false;
- }
- if(mpt::UUID(fileHeader.GuidWAVE) != guidWAVE)
- {
- return false;
- }
- if(fileHeader.FileSize != file.GetLength())
- {
- return false;
- }
- FileReader chunkFile = file;
- auto chunkList = chunkFile.ReadChunks<Wave64Chunk>(8);
- if(!chunkList.ChunkExists(guidFMT))
- {
- return false;
- }
- FileReader formatChunk = chunkList.GetChunk(guidFMT);
- WAVFormatChunk format;
- if(!formatChunk.ReadStruct(format))
- {
- return false;
- }
- uint16 sampleFormat = format.format;
- if(format.format == WAVFormatChunk::fmtExtensible)
- {
- WAVFormatChunkExtension formatExt;
- if(!formatChunk.ReadStruct(formatExt))
- {
- return false;
- }
- sampleFormat = static_cast<uint16>(mpt::UUID(formatExt.subFormat).GetData1());
- }
- if(format.sampleRate == 0)
- {
- return false;
- }
- if(format.numChannels == 0)
- {
- return false;
- }
- if(format.numChannels > 2)
- {
- return false;
- }
- if(sampleFormat != WAVFormatChunk::fmtPCM && sampleFormat != WAVFormatChunk::fmtFloat)
- {
- return false;
- }
- if(sampleFormat == WAVFormatChunk::fmtFloat && format.bitsPerSample != 32 && format.bitsPerSample != 64)
- {
- return false;
- }
- if(sampleFormat == WAVFormatChunk::fmtPCM && format.bitsPerSample > 64)
- {
- return false;
- }
- SampleIO::Bitdepth bitDepth;
- switch((format.bitsPerSample - 1) / 8u)
- {
- default:
- case 0: bitDepth = SampleIO::_8bit ; break;
- case 1: bitDepth = SampleIO::_16bit; break;
- case 2: bitDepth = SampleIO::_24bit; break;
- case 3: bitDepth = SampleIO::_32bit; break;
- case 7: bitDepth = SampleIO::_64bit; break;
- }
- SampleIO sampleIO(
- bitDepth,
- (format.numChannels > 1) ? SampleIO::stereoInterleaved : SampleIO::mono,
- SampleIO::littleEndian,
- (sampleFormat == WAVFormatChunk::fmtFloat) ? SampleIO::floatPCM : SampleIO::signedPCM);
- if(format.bitsPerSample <= 8)
- {
- sampleIO |= SampleIO::unsignedPCM;
- }
- if(mayNormalize)
- {
- sampleIO.MayNormalize();
- }
- FileTags tags;
- uint16 codePage = 28591; // mpt::Charset::ISO8859_1
- FileReader csetChunk = chunkList.GetChunk(guidCSET);
- if(csetChunk.IsValid())
- {
- if(csetChunk.CanRead(2))
- {
- codePage = csetChunk.ReadUint16LE();
- }
- }
- if(chunkList.ChunkExists(guidLIST))
- {
- FileReader listChunk = chunkList.GetChunk(guidLIST);
- if(listChunk.ReadMagic("INFO"))
- {
- auto infoChunk = listChunk.ReadChunks<RIFFChunk>(2);
- Wave64TagFromLISTINFO(tags.title, codePage, infoChunk, RIFFChunk::idINAM);
- Wave64TagFromLISTINFO(tags.encoder, codePage, infoChunk, RIFFChunk::idISFT);
- //Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idICOP);
- Wave64TagFromLISTINFO(tags.artist, codePage, infoChunk, RIFFChunk::idIART);
- Wave64TagFromLISTINFO(tags.album, codePage, infoChunk, RIFFChunk::idIPRD);
- Wave64TagFromLISTINFO(tags.comments, codePage, infoChunk, RIFFChunk::idICMT);
- //Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idIENG);
- //Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idISBJ);
- Wave64TagFromLISTINFO(tags.genre, codePage, infoChunk, RIFFChunk::idIGNR);
- //Wave64TagFromLISTINFO(void, codePage, infoChunk, RIFFChunk::idICRD);
- Wave64TagFromLISTINFO(tags.year, codePage, infoChunk, RIFFChunk::idYEAR);
- Wave64TagFromLISTINFO(tags.trackno, codePage, infoChunk, RIFFChunk::idTRCK);
- Wave64TagFromLISTINFO(tags.url, codePage, infoChunk, RIFFChunk::idTURL);
- //Wave64TagFromLISTINFO(tags.bpm, codePage, infoChunk, void);
- }
- }
- if(!chunkList.ChunkExists(guidDATA))
- {
- return false;
- }
- FileReader audioData = chunkList.GetChunk(guidDATA);
-
- SmpLength length = mpt::saturate_cast<SmpLength>(audioData.GetLength() / (sampleIO.GetEncodedBitsPerSample()/8));
- ModSample &mptSample = Samples[nSample];
- DestroySampleThreadsafe(nSample);
- mptSample.Initialize();
- mptSample.nLength = length;
- mptSample.nC5Speed = format.sampleRate;
- sampleIO.ReadSample(mptSample, audioData);
- m_szNames[nSample] = mpt::ToCharset(GetCharsetInternal(), GetSampleNameFromTags(tags));
- mptSample.Convert(MOD_TYPE_IT, GetType());
- mptSample.PrecomputeLoops(*this, false);
- return true;
- }
- #ifndef MODPLUG_NO_FILESAVE
- ///////////////////////////////////////////////////////////////
- // Save RAW
- bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, std::ostream &f) const
- {
- const ModSample &sample = Samples[nSample];
- SampleIO(
- sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
- sample.uFlags[CHN_STEREO] ? SampleIO::stereoInterleaved : SampleIO::mono,
- SampleIO::littleEndian,
- SampleIO::signedPCM)
- .WriteSample(f, sample);
- return true;
- }
- #endif // MODPLUG_NO_FILESAVE
- /////////////////////////////////////////////////////////////
- // GUS Patches
- struct GF1PatchFileHeader
- {
- char magic[8]; // "GF1PATCH"
- char version[4]; // "100", or "110"
- char id[10]; // "ID#000002"
- char copyright[60]; // Copyright
- uint8le numInstr; // Number of instruments in patch
- uint8le voices; // Number of voices, usually 14
- uint8le channels; // Number of wav channels that can be played concurently to the patch
- uint16le numSamples; // Total number of waveforms for all the .PAT
- uint16le volume; // Master volume
- uint32le dataSize;
- char reserved2[36];
- };
- MPT_BINARY_STRUCT(GF1PatchFileHeader, 129)
- struct GF1Instrument
- {
- uint16le id; // Instrument id: 0-65535
- char name[16]; // Name of instrument. Gravis doesn't seem to use it
- uint32le size; // Number of bytes for the instrument with header. (To skip to next instrument)
- uint8 layers; // Number of layers in instrument: 1-4
- char reserved[40];
- };
- MPT_BINARY_STRUCT(GF1Instrument, 63)
- struct GF1SampleHeader
- {
- char name[7]; // null terminated string. name of the wave.
- uint8le fractions; // Start loop point fraction in 4 bits + End loop point fraction in the 4 other bits.
- uint32le length; // total size of wavesample. limited to 65535 now by the drivers, not the card.
- uint32le loopstart; // start loop position in the wavesample
- uint32le loopend; // end loop position in the wavesample
- uint16le freq; // Rate at which the wavesample has been sampled
- uint32le low_freq; // check note.h for the correspondance.
- uint32le high_freq; // check note.h for the correspondance.
- uint32le root_freq; // check note.h for the correspondance.
- int16le finetune; // fine tune. -512 to +512, EXCLUDING 0 cause it is a multiplier. 512 is one octave off, and 1 is a neutral value
- uint8le balance; // Balance: 0-15. 0=full left, 15 = full right
- uint8le env_rate[6]; // attack rates
- uint8le env_volume[6]; // attack volumes
- uint8le tremolo_sweep, tremolo_rate, tremolo_depth;
- uint8le vibrato_sweep, vibrato_rate, vibrato_depth;
- uint8le flags;
- int16le scale_frequency; // Note
- uint16le scale_factor; // 0...2048 (1024 is normal) or 0...2
- char reserved[36];
- };
- MPT_BINARY_STRUCT(GF1SampleHeader, 96)
- // -- GF1 Envelopes --
- //
- // It can be represented like this (the envelope is totally bogus, it is
- // just to show the concept):
- //
- // |
- // | /----` | |
- // | /------/ `\ | | | | |
- // | / \ | | | | |
- // | / \ | | | | |
- // |/ \ | | | | |
- // ---------------------------- | | | | | |
- // <---> attack rate 0 0 1 2 3 4 5 amplitudes
- // <----> attack rate 1
- // <> attack rate 2
- // <--> attack rate 3
- // <> attack rate 4
- // <-----> attack rate 5
- //
- // -- GF1 Flags --
- //
- // bit 0: 8/16 bit
- // bit 1: Signed/Unsigned
- // bit 2: off/on looping
- // bit 3: off/on bidirectionnal looping
- // bit 4: off/on backward looping
- // bit 5: off/on sustaining (3rd point in env.)
- // bit 6: off/on envelopes
- // bit 7: off/on clamped release (6th point, env)
- struct GF1Layer
- {
- uint8le previous; // If !=0 the wavesample to use is from the previous layer. The waveheader is still needed
- uint8le id; // Layer id: 0-3
- uint32le size; // data size in bytes in the layer, without the header. to skip to next layer for example:
- uint8le samples; // number of wavesamples
- char reserved[40];
- };
- MPT_BINARY_STRUCT(GF1Layer, 47)
- static double PatchFreqToNote(uint32 nFreq)
- {
- return std::log(nFreq / 2044.0) * (12.0 * 1.44269504088896340736); // 1.0/std::log(2.0)
- }
- static int32 PatchFreqToNoteInt(uint32 nFreq)
- {
- return mpt::saturate_round<int32>(PatchFreqToNote(nFreq));
- }
- static void PatchToSample(CSoundFile *that, SAMPLEINDEX nSample, GF1SampleHeader &sampleHeader, FileReader &file)
- {
- ModSample &sample = that->GetSample(nSample);
- file.ReadStruct(sampleHeader);
- sample.Initialize();
- if(sampleHeader.flags & 4) sample.uFlags.set(CHN_LOOP);
- if(sampleHeader.flags & 8) sample.uFlags.set(CHN_PINGPONGLOOP);
- if(sampleHeader.flags & 16) sample.uFlags.set(CHN_REVERSE);
- sample.nLength = sampleHeader.length;
- sample.nLoopStart = sampleHeader.loopstart;
- sample.nLoopEnd = sampleHeader.loopend;
- sample.nC5Speed = sampleHeader.freq;
- sample.nPan = (sampleHeader.balance * 256 + 8) / 15;
- if(sample.nPan > 256) sample.nPan = 128;
- else sample.uFlags.set(CHN_PANNING);
- sample.nVibType = VIB_SINE;
- sample.nVibSweep = sampleHeader.vibrato_sweep;
- sample.nVibDepth = sampleHeader.vibrato_depth;
- sample.nVibRate = sampleHeader.vibrato_rate / 4;
- if(sampleHeader.scale_factor)
- {
- sample.Transpose((84.0 - PatchFreqToNote(sampleHeader.root_freq)) / 12.0);
- }
- SampleIO sampleIO(
- SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::littleEndian,
- (sampleHeader.flags & 2) ? SampleIO::unsignedPCM : SampleIO::signedPCM);
- if(sampleHeader.flags & 1)
- {
- sampleIO |= SampleIO::_16bit;
- sample.nLength /= 2;
- sample.nLoopStart /= 2;
- sample.nLoopEnd /= 2;
- }
- sampleIO.ReadSample(sample, file);
- sample.Convert(MOD_TYPE_IT, that->GetType());
- sample.PrecomputeLoops(*that, false);
- that->m_szNames[nSample] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name);
- }
- bool CSoundFile::ReadPATSample(SAMPLEINDEX nSample, FileReader &file)
- {
- file.Rewind();
- GF1PatchFileHeader fileHeader;
- GF1Instrument instrHeader; // We only support one instrument
- GF1Layer layerHeader;
- if(!file.ReadStruct(fileHeader)
- || memcmp(fileHeader.magic, "GF1PATCH", 8)
- || (memcmp(fileHeader.version, "110\0", 4) && memcmp(fileHeader.version, "100\0", 4))
- || memcmp(fileHeader.id, "ID#000002\0", 10)
- || !fileHeader.numInstr || !fileHeader.numSamples
- || !file.ReadStruct(instrHeader)
- //|| !instrHeader.layers // DOO.PAT has 0 layers
- || !file.ReadStruct(layerHeader)
- || !layerHeader.samples)
- {
- return false;
- }
- DestroySampleThreadsafe(nSample);
- GF1SampleHeader sampleHeader;
- PatchToSample(this, nSample, sampleHeader, file);
- if(instrHeader.name[0] > ' ')
- {
- m_szNames[nSample] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrHeader.name);
- }
- return true;
- }
- // PAT Instrument
- bool CSoundFile::ReadPATInstrument(INSTRUMENTINDEX nInstr, FileReader &file)
- {
- file.Rewind();
- GF1PatchFileHeader fileHeader;
- GF1Instrument instrHeader; // We only support one instrument
- GF1Layer layerHeader;
- if(!file.ReadStruct(fileHeader)
- || memcmp(fileHeader.magic, "GF1PATCH", 8)
- || (memcmp(fileHeader.version, "110\0", 4) && memcmp(fileHeader.version, "100\0", 4))
- || memcmp(fileHeader.id, "ID#000002\0", 10)
- || !fileHeader.numInstr || !fileHeader.numSamples
- || !file.ReadStruct(instrHeader)
- //|| !instrHeader.layers // DOO.PAT has 0 layers
- || !file.ReadStruct(layerHeader)
- || !layerHeader.samples)
- {
- return false;
- }
- ModInstrument *pIns = new (std::nothrow) ModInstrument();
- if(pIns == nullptr)
- {
- return false;
- }
- DestroyInstrument(nInstr, deleteAssociatedSamples);
- if (nInstr > m_nInstruments) m_nInstruments = nInstr;
- Instruments[nInstr] = pIns;
- pIns->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrHeader.name);
- pIns->nFadeOut = 2048;
- if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
- {
- pIns->nNNA = NewNoteAction::NoteOff;
- pIns->nDNA = DuplicateNoteAction::NoteFade;
- }
- SAMPLEINDEX nextSample = 0;
- int32 nMinSmpNote = 0xFF;
- SAMPLEINDEX nMinSmp = 0;
- for(uint8 smp = 0; smp < layerHeader.samples; smp++)
- {
- // Find a free sample
- nextSample = GetNextFreeSample(nInstr, nextSample + 1);
- if(nextSample == SAMPLEINDEX_INVALID) break;
- if(m_nSamples < nextSample) m_nSamples = nextSample;
- if(!nMinSmp) nMinSmp = nextSample;
- // Load it
- GF1SampleHeader sampleHeader;
- PatchToSample(this, nextSample, sampleHeader, file);
- int32 nMinNote = (sampleHeader.low_freq > 100) ? PatchFreqToNoteInt(sampleHeader.low_freq) : 0;
- int32 nMaxNote = (sampleHeader.high_freq > 100) ? PatchFreqToNoteInt(sampleHeader.high_freq) : static_cast<uint8>(NOTE_MAX);
- int32 nBaseNote = (sampleHeader.root_freq > 100) ? PatchFreqToNoteInt(sampleHeader.root_freq) : -1;
- if(!sampleHeader.scale_factor && layerHeader.samples == 1) { nMinNote = 0; nMaxNote = NOTE_MAX; }
- // Fill Note Map
- for(int32 k = 0; k < NOTE_MAX; k++)
- {
- if(k == nBaseNote || (!pIns->Keyboard[k] && k >= nMinNote && k <= nMaxNote))
- {
- if(!sampleHeader.scale_factor)
- pIns->NoteMap[k] = NOTE_MIDDLEC;
- pIns->Keyboard[k] = nextSample;
- if(k < nMinSmpNote)
- {
- nMinSmpNote = k;
- nMinSmp = nextSample;
- }
- }
- }
- }
- if(nMinSmp)
- {
- // Fill note map and missing samples
- for(uint8 k = 0; k < NOTE_MAX; k++)
- {
- if(!pIns->NoteMap[k]) pIns->NoteMap[k] = k + 1;
- if(!pIns->Keyboard[k])
- {
- pIns->Keyboard[k] = nMinSmp;
- } else
- {
- nMinSmp = pIns->Keyboard[k];
- }
- }
- }
- pIns->Sanitize(MOD_TYPE_IT);
- pIns->Convert(MOD_TYPE_IT, GetType());
- return true;
- }
- /////////////////////////////////////////////////////////////
- // S3I Samples
- bool CSoundFile::ReadS3ISample(SAMPLEINDEX nSample, FileReader &file)
- {
- file.Rewind();
- S3MSampleHeader sampleHeader;
- if(!file.ReadStruct(sampleHeader)
- || (sampleHeader.sampleType != S3MSampleHeader::typePCM && sampleHeader.sampleType != S3MSampleHeader::typeAdMel)
- || (memcmp(sampleHeader.magic, "SCRS", 4) && memcmp(sampleHeader.magic, "SCRI", 4))
- || !file.Seek(sampleHeader.GetSampleOffset()))
- {
- return false;
- }
- DestroySampleThreadsafe(nSample);
- ModSample &sample = Samples[nSample];
- sampleHeader.ConvertToMPT(sample);
- m_szNames[nSample] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name);
- if(sampleHeader.sampleType < S3MSampleHeader::typeAdMel)
- sampleHeader.GetSampleFormat(false).ReadSample(sample, file);
- else if(SupportsOPL())
- InitOPL();
- else
- AddToLog(LogInformation, U_("OPL instruments are not supported by this format."));
- sample.Convert(MOD_TYPE_S3M, GetType());
- sample.PrecomputeLoops(*this, false);
- return true;
- }
- #ifndef MODPLUG_NO_FILESAVE
- bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, std::ostream &f) const
- {
- const ModSample &sample = Samples[smp];
- S3MSampleHeader sampleHeader{};
- SmpLength length = sampleHeader.ConvertToS3M(sample);
- mpt::String::WriteBuf(mpt::String::nullTerminated, sampleHeader.name) = m_szNames[smp];
- mpt::String::WriteBuf(mpt::String::maybeNullTerminated, sampleHeader.reserved2) = mpt::ToCharset(mpt::Charset::UTF8, Version::Current().GetOpenMPTVersionString());
- if(length)
- sampleHeader.dataPointer[1] = sizeof(S3MSampleHeader) >> 4;
- mpt::IO::Write(f, sampleHeader);
- if(length)
- sampleHeader.GetSampleFormat(false).WriteSample(f, sample, length);
- return true;
- }
- #endif // MODPLUG_NO_FILESAVE
- /////////////////////////////////////////////////////////////
- // SBI OPL patch files
- bool CSoundFile::ReadSBISample(SAMPLEINDEX sample, FileReader &file)
- {
- file.Rewind();
- const auto magic = file.ReadArray<char, 4>();
- if((memcmp(magic.data(), "SBI\x1A", 4) && memcmp(magic.data(), "SBI\x1D", 4)) // 1D = broken JuceOPLVSTi files
- || !file.CanRead(32 + sizeof(OPLPatch))
- || file.CanRead(64)) // Arbitrary threshold to reject files that are unlikely to be SBI files
- return false;
- if(!SupportsOPL())
- {
- AddToLog(LogInformation, U_("OPL instruments are not supported by this format."));
- return true;
- }
- DestroySampleThreadsafe(sample);
- InitOPL();
- ModSample &mptSmp = Samples[sample];
- mptSmp.Initialize(MOD_TYPE_S3M);
- file.ReadString<mpt::String::nullTerminated>(m_szNames[sample], 32);
- OPLPatch patch;
- file.ReadArray(patch);
- mptSmp.SetAdlib(true, patch);
- mptSmp.Convert(MOD_TYPE_S3M, GetType());
- return true;
- }
- /////////////////////////////////////////////////////////////
- // XI Instruments
- bool CSoundFile::ReadXIInstrument(INSTRUMENTINDEX nInstr, FileReader &file)
- {
- file.Rewind();
- XIInstrumentHeader fileHeader;
- if(!file.ReadStruct(fileHeader)
- || memcmp(fileHeader.signature, "Extended Instrument: ", 21)
- || fileHeader.version != XIInstrumentHeader::fileVersion
- || fileHeader.eof != 0x1A)
- {
- return false;
- }
- ModInstrument *pIns = new (std::nothrow) ModInstrument();
- if(pIns == nullptr)
- {
- return false;
- }
- DestroyInstrument(nInstr, deleteAssociatedSamples);
- if(nInstr > m_nInstruments)
- {
- m_nInstruments = nInstr;
- }
- Instruments[nInstr] = pIns;
- fileHeader.ConvertToMPT(*pIns);
- // Translate sample map and find available sample slots
- std::vector<SAMPLEINDEX> sampleMap(fileHeader.numSamples);
- SAMPLEINDEX maxSmp = 0;
- for(size_t i = 0 + 12; i < 96 + 12; i++)
- {
- if(pIns->Keyboard[i] >= fileHeader.numSamples)
- {
- continue;
- }
- if(sampleMap[pIns->Keyboard[i]] == 0)
- {
- // Find slot for this sample
- maxSmp = GetNextFreeSample(nInstr, maxSmp + 1);
- if(maxSmp != SAMPLEINDEX_INVALID)
- {
- sampleMap[pIns->Keyboard[i]] = maxSmp;
- }
- }
- pIns->Keyboard[i] = sampleMap[pIns->Keyboard[i]];
- }
- if(m_nSamples < maxSmp)
- {
- m_nSamples = maxSmp;
- }
- std::vector<SampleIO> sampleFlags(fileHeader.numSamples);
- // Read sample headers
- for(SAMPLEINDEX i = 0; i < fileHeader.numSamples; i++)
- {
- XMSample sampleHeader;
- if(!file.ReadStruct(sampleHeader)
- || !sampleMap[i])
- {
- continue;
- }
- ModSample &mptSample = Samples[sampleMap[i]];
- sampleHeader.ConvertToMPT(mptSample);
- fileHeader.instrument.ApplyAutoVibratoToMPT(mptSample);
- mptSample.Convert(MOD_TYPE_XM, GetType());
- if(GetType() != MOD_TYPE_XM && fileHeader.numSamples == 1)
- {
- // No need to pan that single sample, thank you...
- mptSample.uFlags &= ~CHN_PANNING;
- }
- mptSample.filename = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
- m_szNames[sampleMap[i]] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
- sampleFlags[i] = sampleHeader.GetSampleFormat();
- }
- // Read sample data
- for(SAMPLEINDEX i = 0; i < fileHeader.numSamples; i++)
- {
- if(sampleMap[i])
- {
- sampleFlags[i].ReadSample(Samples[sampleMap[i]], file);
- Samples[sampleMap[i]].PrecomputeLoops(*this, false);
- }
- }
- // Read MPT crap
- ReadExtendedInstrumentProperties(pIns, file);
- pIns->Convert(MOD_TYPE_XM, GetType());
- pIns->Sanitize(GetType());
- return true;
- }
- #ifndef MODPLUG_NO_FILESAVE
- bool CSoundFile::SaveXIInstrument(INSTRUMENTINDEX nInstr, std::ostream &f) const
- {
- ModInstrument *pIns = Instruments[nInstr];
- if(pIns == nullptr)
- {
- return false;
- }
- // Create file header
- XIInstrumentHeader header;
- header.ConvertToXM(*pIns, false);
- const std::vector<SAMPLEINDEX> samples = header.instrument.GetSampleList(*pIns, false);
- if(samples.size() > 0 && samples[0] <= GetNumSamples())
- {
- // Copy over auto-vibrato settings of first sample
- header.instrument.ApplyAutoVibratoToXM(Samples[samples[0]], GetType());
- }
- mpt::IO::Write(f, header);
- std::vector<SampleIO> sampleFlags(samples.size());
- // XI Sample Headers
- for(SAMPLEINDEX i = 0; i < samples.size(); i++)
- {
- XMSample xmSample;
- if(samples[i] <= GetNumSamples())
- {
- xmSample.ConvertToXM(Samples[samples[i]], GetType(), false);
- } else
- {
- MemsetZero(xmSample);
- }
- sampleFlags[i] = xmSample.GetSampleFormat();
- mpt::String::WriteBuf(mpt::String::spacePadded, xmSample.name) = m_szNames[samples[i]];
- mpt::IO::Write(f, xmSample);
- }
- // XI Sample Data
- for(SAMPLEINDEX i = 0; i < samples.size(); i++)
- {
- if(samples[i] <= GetNumSamples())
- {
- sampleFlags[i].WriteSample(f, Samples[samples[i]]);
- }
- }
- // Write 'MPTX' extension tag
- mpt::IO::WriteText(f, "XTPM");
- WriteInstrumentHeaderStructOrField(pIns, f); // Write full extended header.
- return true;
- }
- #endif // MODPLUG_NO_FILESAVE
- // Read first sample from XI file into a sample slot
- bool CSoundFile::ReadXISample(SAMPLEINDEX nSample, FileReader &file)
- {
- file.Rewind();
- XIInstrumentHeader fileHeader;
- if(!file.ReadStruct(fileHeader)
- || !file.CanRead(sizeof(XMSample))
- || memcmp(fileHeader.signature, "Extended Instrument: ", 21)
- || fileHeader.version != XIInstrumentHeader::fileVersion
- || fileHeader.eof != 0x1A
- || fileHeader.numSamples == 0)
- {
- return false;
- }
- if(m_nSamples < nSample)
- {
- m_nSamples = nSample;
- }
- uint16 numSamples = fileHeader.numSamples;
- FileReader::off_t samplePos = sizeof(XIInstrumentHeader) + numSamples * sizeof(XMSample);
- // Preferrably read the middle-C sample
- auto sample = fileHeader.instrument.sampleMap[48];
- if(sample >= fileHeader.numSamples)
- sample = 0;
- XMSample sampleHeader;
- while(sample--)
- {
- file.ReadStruct(sampleHeader);
- samplePos += sampleHeader.length;
- }
- file.ReadStruct(sampleHeader);
- // Gotta skip 'em all!
- file.Seek(samplePos);
- DestroySampleThreadsafe(nSample);
- ModSample &mptSample = Samples[nSample];
- sampleHeader.ConvertToMPT(mptSample);
- if(GetType() != MOD_TYPE_XM)
- {
- // No need to pan that single sample, thank you...
- mptSample.uFlags.reset(CHN_PANNING);
- }
- fileHeader.instrument.ApplyAutoVibratoToMPT(mptSample);
- mptSample.Convert(MOD_TYPE_XM, GetType());
- mptSample.filename = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
- m_szNames[nSample] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
- // Read sample data
- sampleHeader.GetSampleFormat().ReadSample(Samples[nSample], file);
- Samples[nSample].PrecomputeLoops(*this, false);
- return true;
- }
- ///////////////
- // Apple CAF //
- struct CAFFileHeader
- {
- uint32be mFileType;
- uint16be mFileVersion;
- uint16be mFileFlags;
- };
- MPT_BINARY_STRUCT(CAFFileHeader, 8)
- struct CAFChunkHeader
- {
- uint32be mChunkType;
- int64be mChunkSize;
- };
- MPT_BINARY_STRUCT(CAFChunkHeader, 12)
- struct CAFChunk
- {
- enum ChunkIdentifiers
- {
- iddesc = MagicBE("desc"),
- iddata = MagicBE("data"),
- idstrg = MagicBE("strg"),
- idinfo = MagicBE("info")
- };
- CAFChunkHeader header;
- FileReader::off_t GetLength() const
- {
- int64 length = header.mChunkSize;
- if(length == -1)
- {
- length = std::numeric_limits<int64>::max(); // spec
- }
- if(length < 0)
- {
- length = std::numeric_limits<int64>::max(); // heuristic
- }
- return mpt::saturate_cast<FileReader::off_t>(length);
- }
- ChunkIdentifiers GetID() const
- {
- return static_cast<ChunkIdentifiers>(header.mChunkType.get());
- }
- };
- MPT_BINARY_STRUCT(CAFChunk, 12)
- enum {
- CAFkAudioFormatLinearPCM = MagicBE("lpcm"),
- CAFkAudioFormatAppleIMA4 = MagicBE("ima4"),
- CAFkAudioFormatMPEG4AAC = MagicBE("aac "),
- CAFkAudioFormatMACE3 = MagicBE("MAC3"),
- CAFkAudioFormatMACE6 = MagicBE("MAC6"),
- CAFkAudioFormatULaw = MagicBE("ulaw"),
- CAFkAudioFormatALaw = MagicBE("alaw"),
- CAFkAudioFormatMPEGLayer1 = MagicBE(".mp1"),
- CAFkAudioFormatMPEGLayer2 = MagicBE(".mp2"),
- CAFkAudioFormatMPEGLayer3 = MagicBE(".mp3"),
- CAFkAudioFormatAppleLossless = MagicBE("alac")
- };
- enum {
- CAFkCAFLinearPCMFormatFlagIsFloat = (1L << 0),
- CAFkCAFLinearPCMFormatFlagIsLittleEndian = (1L << 1)
- };
- struct CAFAudioFormat
- {
- float64be mSampleRate;
- uint32be mFormatID;
- uint32be mFormatFlags;
- uint32be mBytesPerPacket;
- uint32be mFramesPerPacket;
- uint32be mChannelsPerFrame;
- uint32be mBitsPerChannel;
- };
- MPT_BINARY_STRUCT(CAFAudioFormat, 32)
- static void CAFSetTagFromInfoKey(mpt::ustring & dst, const std::map<std::string,std::string> & infoMap, const std::string & key)
- {
- auto item = infoMap.find(key);
- if(item == infoMap.end())
- {
- return;
- }
- if(item->second.empty())
- {
- return;
- }
- dst = mpt::ToUnicode(mpt::Charset::UTF8, item->second);
- }
- bool CSoundFile::ReadCAFSample(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize)
- {
- file.Rewind();
- CAFFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(fileHeader.mFileType != MagicBE("caff"))
- {
- return false;
- }
- if(fileHeader.mFileVersion != 1)
- {
- return false;
- }
- auto chunkList = file.ReadChunks<CAFChunk>(0);
- CAFAudioFormat audioFormat;
- if(!chunkList.GetChunk(CAFChunk::iddesc).ReadStruct(audioFormat))
- {
- return false;
- }
- if(audioFormat.mSampleRate <= 0.0)
- {
- return false;
- }
- if(audioFormat.mChannelsPerFrame == 0)
- {
- return false;
- }
- if(audioFormat.mChannelsPerFrame > 2)
- {
- return false;
- }
- if(!mpt::in_range<uint32>(mpt::saturate_round<int64>(audioFormat.mSampleRate)))
- {
- return false;
- }
- uint32 sampleRate = static_cast<uint32>(mpt::saturate_round<int64>(audioFormat.mSampleRate));
- if(sampleRate <= 0)
- {
- return false;
- }
- SampleIO sampleIO;
- if(audioFormat.mFormatID == CAFkAudioFormatLinearPCM)
- {
- if(audioFormat.mFramesPerPacket != 1)
- {
- return false;
- }
- if(audioFormat.mBytesPerPacket == 0)
- {
- return false;
- }
- if(audioFormat.mBitsPerChannel == 0)
- {
- return false;
- }
- if(audioFormat.mFormatFlags & CAFkCAFLinearPCMFormatFlagIsFloat)
- {
- if(audioFormat.mBitsPerChannel != 32 && audioFormat.mBitsPerChannel != 64)
- {
- return false;
- }
- if(audioFormat.mBytesPerPacket != audioFormat.mChannelsPerFrame * audioFormat.mBitsPerChannel/8)
- {
- return false;
- }
- }
- if(audioFormat.mBytesPerPacket % audioFormat.mChannelsPerFrame != 0)
- {
- return false;
- }
- if(audioFormat.mBytesPerPacket / audioFormat.mChannelsPerFrame != 1
- && audioFormat.mBytesPerPacket / audioFormat.mChannelsPerFrame != 2
- && audioFormat.mBytesPerPacket / audioFormat.mChannelsPerFrame != 3
- && audioFormat.mBytesPerPacket / audioFormat.mChannelsPerFrame != 4
- && audioFormat.mBytesPerPacket / audioFormat.mChannelsPerFrame != 8
- )
- {
- return false;
- }
- SampleIO::Channels channels = (audioFormat.mChannelsPerFrame == 2) ? SampleIO::stereoInterleaved : SampleIO::mono;
- SampleIO::Endianness endianness = (audioFormat.mFormatFlags & CAFkCAFLinearPCMFormatFlagIsLittleEndian) ? SampleIO::littleEndian : SampleIO::bigEndian;
- SampleIO::Encoding encoding = (audioFormat.mFormatFlags & CAFkCAFLinearPCMFormatFlagIsFloat) ? SampleIO::floatPCM : SampleIO::signedPCM;
- SampleIO::Bitdepth bitdepth = static_cast<SampleIO::Bitdepth>((audioFormat.mBytesPerPacket / audioFormat.mChannelsPerFrame) * 8);
- sampleIO = SampleIO(bitdepth, channels, endianness, encoding);
- } else
- {
- return false;
- }
- if(mayNormalize)
- {
- sampleIO.MayNormalize();
- }
- /*
- std::map<uint32, std::string> stringMap; // UTF-8
- if(chunkList.ChunkExists(CAFChunk::idstrg))
- {
- FileReader stringsChunk = chunkList.GetChunk(CAFChunk::idstrg);
- uint32 numEntries = stringsChunk.ReadUint32BE();
- if(stringsChunk.Skip(12 * numEntries))
- {
- FileReader stringData = stringsChunk.ReadChunk(stringsChunk.BytesLeft());
- stringsChunk.Seek(4);
- for(uint32 entry = 0; entry < numEntries && stringsChunk.CanRead(12); entry++)
- {
- uint32 stringID = stringsChunk.ReadUint32BE();
- int64 offset = stringsChunk.ReadIntBE<int64>();
- if(offset >= 0 && mpt::in_range<FileReader::off_t>(offset))
- {
- stringData.Seek(mpt::saturate_cast<FileReader::off_t>(offset));
- std::string str;
- if(stringData.ReadNullString(str))
- {
- stringMap[stringID] = str;
- }
- }
- }
- }
- }
- */
- std::map<std::string, std::string> infoMap; // UTF-8
- if(chunkList.ChunkExists(CAFChunk::idinfo))
- {
- FileReader informationChunk = chunkList.GetChunk(CAFChunk::idinfo);
- uint32 numEntries = informationChunk.ReadUint32BE();
- for(uint32 entry = 0; entry < numEntries && informationChunk.CanRead(2); entry++)
- {
- std::string key;
- std::string value;
- if(!informationChunk.ReadNullString(key))
- {
- break;
- }
- if(!informationChunk.ReadNullString(value))
- {
- break;
- }
- if(!key.empty() && !value.empty())
- {
- infoMap[key] = value;
- }
- }
- }
- FileTags tags;
- CAFSetTagFromInfoKey(tags.bpm, infoMap, "tempo");
- //CAFSetTagFromInfoKey(void, infoMap, "key signature");
- //CAFSetTagFromInfoKey(void, infoMap, "time signature");
- CAFSetTagFromInfoKey(tags.artist, infoMap, "artist");
- CAFSetTagFromInfoKey(tags.album, infoMap, "album");
- CAFSetTagFromInfoKey(tags.trackno, infoMap, "track number");
- CAFSetTagFromInfoKey(tags.year, infoMap, "year");
- //CAFSetTagFromInfoKey(void, infoMap, "composer");
- //CAFSetTagFromInfoKey(void, infoMap, "lyricist");
- CAFSetTagFromInfoKey(tags.genre, infoMap, "genre");
- CAFSetTagFromInfoKey(tags.title, infoMap, "title");
- //CAFSetTagFromInfoKey(void, infoMap, "recorded date");
- CAFSetTagFromInfoKey(tags.comments, infoMap, "comments");
- //CAFSetTagFromInfoKey(void, infoMap, "copyright");
- //CAFSetTagFromInfoKey(void, infoMap, "source encoder");
- CAFSetTagFromInfoKey(tags.encoder, infoMap, "encoding application");
- //CAFSetTagFromInfoKey(void, infoMap, "nominal bit rate");
- //CAFSetTagFromInfoKey(void, infoMap, "channel layout");
- //CAFSetTagFromInfoKey(tags.url, infoMap, void);
- if(!chunkList.ChunkExists(CAFChunk::iddata))
- {
- return false;
- }
- FileReader dataChunk = chunkList.GetChunk(CAFChunk::iddata);
- dataChunk.Skip(4); // edit count
- FileReader audioData = dataChunk.ReadChunk(dataChunk.BytesLeft());
-
- SmpLength length = mpt::saturate_cast<SmpLength>((audioData.GetLength() / audioFormat.mBytesPerPacket) * audioFormat.mFramesPerPacket);
- ModSample &mptSample = Samples[nSample];
- DestroySampleThreadsafe(nSample);
- mptSample.Initialize();
- mptSample.nLength = length;
- mptSample.nC5Speed = sampleRate;
- sampleIO.ReadSample(mptSample, audioData);
- m_szNames[nSample] = mpt::ToCharset(GetCharsetInternal(), GetSampleNameFromTags(tags));
- mptSample.Convert(MOD_TYPE_IT, GetType());
- mptSample.PrecomputeLoops(*this, false);
- return true;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- // AIFF File I/O
- // AIFF header
- struct AIFFHeader
- {
- char magic[4]; // FORM
- uint32be length; // Size of the file, not including magic and length
- char type[4]; // AIFF or AIFC
- };
- MPT_BINARY_STRUCT(AIFFHeader, 12)
- // General IFF Chunk header
- struct AIFFChunk
- {
- // 32-Bit chunk identifiers
- enum ChunkIdentifiers
- {
- idCOMM = MagicBE("COMM"),
- idSSND = MagicBE("SSND"),
- idINST = MagicBE("INST"),
- idMARK = MagicBE("MARK"),
- idNAME = MagicBE("NAME"),
- };
- uint32be id; // See ChunkIdentifiers
- uint32be length; // Chunk size without header
- size_t GetLength() const
- {
- return length;
- }
- ChunkIdentifiers GetID() const
- {
- return static_cast<ChunkIdentifiers>(id.get());
- }
- };
- MPT_BINARY_STRUCT(AIFFChunk, 8)
- // "Common" chunk (in AIFC, a compression ID and compression name follows this header, but apart from that it's identical)
- struct AIFFCommonChunk
- {
- uint16be numChannels;
- uint32be numSampleFrames;
- uint16be sampleSize;
- uint8be sampleRate[10]; // Sample rate in 80-Bit floating point
- // Convert sample rate to integer
- uint32 GetSampleRate() const
- {
- uint32 mantissa = (sampleRate[2] << 24) | (sampleRate[3] << 16) | (sampleRate[4] << 8) | (sampleRate[5] << 0);
- uint32 last = 0;
- uint8 exp = 30 - sampleRate[1];
- while(exp--)
- {
- last = mantissa;
- mantissa >>= 1;
- }
- if(last & 1) mantissa++;
- return mantissa;
- }
- };
- MPT_BINARY_STRUCT(AIFFCommonChunk, 18)
- // Sound chunk
- struct AIFFSoundChunk
- {
- uint32be offset;
- uint32be blockSize;
- };
- MPT_BINARY_STRUCT(AIFFSoundChunk, 8)
- // Marker
- struct AIFFMarker
- {
- uint16be id;
- uint32be position; // Position in sample
- uint8be nameLength; // Not counting eventually existing padding byte in name string
- };
- MPT_BINARY_STRUCT(AIFFMarker, 7)
- // Instrument loop
- struct AIFFInstrumentLoop
- {
- enum PlayModes
- {
- noLoop = 0,
- loopNormal = 1,
- loopBidi = 2,
- };
- uint16be playMode;
- uint16be beginLoop; // Marker index
- uint16be endLoop; // Marker index
- };
- MPT_BINARY_STRUCT(AIFFInstrumentLoop, 6)
- struct AIFFInstrumentChunk
- {
- uint8be baseNote;
- uint8be detune;
- uint8be lowNote;
- uint8be highNote;
- uint8be lowVelocity;
- uint8be highVelocity;
- uint16be gain;
- AIFFInstrumentLoop sustainLoop;
- AIFFInstrumentLoop releaseLoop;
- };
- MPT_BINARY_STRUCT(AIFFInstrumentChunk, 20)
- bool CSoundFile::ReadAIFFSample(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize)
- {
- file.Rewind();
- // Verify header
- AIFFHeader fileHeader;
- if(!file.ReadStruct(fileHeader)
- || memcmp(fileHeader.magic, "FORM", 4)
- || (memcmp(fileHeader.type, "AIFF", 4) && memcmp(fileHeader.type, "AIFC", 4)))
- {
- return false;
- }
- auto chunks = file.ReadChunks<AIFFChunk>(2);
- // Read COMM chunk
- FileReader commChunk(chunks.GetChunk(AIFFChunk::idCOMM));
- AIFFCommonChunk sampleInfo;
- if(!commChunk.ReadStruct(sampleInfo))
- {
- return false;
- }
- // Is this a proper sample?
- if(sampleInfo.numSampleFrames == 0
- || sampleInfo.numChannels < 1 || sampleInfo.numChannels > 2
- || sampleInfo.sampleSize < 1 || sampleInfo.sampleSize > 64)
- {
- return false;
- }
- // Read compression type in AIFF-C files.
- uint8 compression[4] = { 'N', 'O', 'N', 'E' };
- SampleIO::Endianness endian = SampleIO::bigEndian;
- if(!memcmp(fileHeader.type, "AIFC", 4))
- {
- if(!commChunk.ReadArray(compression))
- {
- return false;
- }
- if(!memcmp(compression, "twos", 4))
- {
- endian = SampleIO::littleEndian;
- }
- }
- // Read SSND chunk
- FileReader soundChunk(chunks.GetChunk(AIFFChunk::idSSND));
- AIFFSoundChunk sampleHeader;
- if(!soundChunk.ReadStruct(sampleHeader))
- {
- return false;
- }
- SampleIO::Bitdepth bitDepth;
- switch((sampleInfo.sampleSize - 1) / 8)
- {
- default:
- case 0: bitDepth = SampleIO::_8bit; break;
- case 1: bitDepth = SampleIO::_16bit; break;
- case 2: bitDepth = SampleIO::_24bit; break;
- case 3: bitDepth = SampleIO::_32bit; break;
- case 7: bitDepth = SampleIO::_64bit; break;
- }
- SampleIO sampleIO(bitDepth,
- (sampleInfo.numChannels == 2) ? SampleIO::stereoInterleaved : SampleIO::mono,
- endian,
- SampleIO::signedPCM);
- if(!memcmp(compression, "fl32", 4) || !memcmp(compression, "FL32", 4) || !memcmp(compression, "fl64", 4) || !memcmp(compression, "FL64", 4))
- {
- sampleIO |= SampleIO::floatPCM;
- } else if(!memcmp(compression, "alaw", 4) || !memcmp(compression, "ALAW", 4))
- {
- sampleIO |= SampleIO::aLaw;
- sampleIO |= SampleIO::_16bit;
- } else if(!memcmp(compression, "ulaw", 4) || !memcmp(compression, "ULAW", 4))
- {
- sampleIO |= SampleIO::uLaw;
- sampleIO |= SampleIO::_16bit;
- } else if(!memcmp(compression, "raw ", 4))
- {
- sampleIO |= SampleIO::unsignedPCM;
- }
- if(mayNormalize)
- {
- sampleIO.MayNormalize();
- }
- if(soundChunk.CanRead(sampleHeader.offset))
- {
- soundChunk.Skip(sampleHeader.offset);
- }
- ModSample &mptSample = Samples[nSample];
- DestroySampleThreadsafe(nSample);
- mptSample.Initialize();
- mptSample.nLength = sampleInfo.numSampleFrames;
- mptSample.nC5Speed = sampleInfo.GetSampleRate();
- sampleIO.ReadSample(mptSample, soundChunk);
- // Read MARK and INST chunk to extract sample loops
- FileReader markerChunk(chunks.GetChunk(AIFFChunk::idMARK));
- AIFFInstrumentChunk instrHeader;
- if(markerChunk.IsValid() && chunks.GetChunk(AIFFChunk::idINST).ReadStruct(instrHeader))
- {
- uint16 numMarkers = markerChunk.ReadUint16BE();
- std::vector<AIFFMarker> markers;
- markers.reserve(numMarkers);
- for(size_t i = 0; i < numMarkers; i++)
- {
- AIFFMarker marker;
- if(!markerChunk.ReadStruct(marker))
- {
- break;
- }
- markers.push_back(marker);
- markerChunk.Skip(marker.nameLength + ((marker.nameLength % 2u) == 0 ? 1 : 0));
- }
- if(instrHeader.sustainLoop.playMode != AIFFInstrumentLoop::noLoop)
- {
- mptSample.uFlags.set(CHN_SUSTAINLOOP);
- mptSample.uFlags.set(CHN_PINGPONGSUSTAIN, instrHeader.sustainLoop.playMode == AIFFInstrumentLoop::loopBidi);
- }
- if(instrHeader.releaseLoop.playMode != AIFFInstrumentLoop::noLoop)
- {
- mptSample.uFlags.set(CHN_LOOP);
- mptSample.uFlags.set(CHN_PINGPONGLOOP, instrHeader.releaseLoop.playMode == AIFFInstrumentLoop::loopBidi);
- }
- // Read markers
- for(const auto &m : markers)
- {
- if(m.id == instrHeader.sustainLoop.beginLoop)
- mptSample.nSustainStart = m.position;
- if(m.id == instrHeader.sustainLoop.endLoop)
- mptSample.nSustainEnd = m.position;
- if(m.id == instrHeader.releaseLoop.beginLoop)
- mptSample.nLoopStart = m.position;
- if(m.id == instrHeader.releaseLoop.endLoop)
- mptSample.nLoopEnd = m.position;
- }
- mptSample.SanitizeLoops();
- }
- // Extract sample name
- FileReader nameChunk(chunks.GetChunk(AIFFChunk::idNAME));
- if(nameChunk.IsValid())
- {
- nameChunk.ReadString<mpt::String::spacePadded>(m_szNames[nSample], nameChunk.GetLength());
- } else
- {
- m_szNames[nSample] = "";
- }
- mptSample.Convert(MOD_TYPE_IT, GetType());
- mptSample.PrecomputeLoops(*this, false);
- return true;
- }
- static bool AUIsAnnotationLineWithField(const std::string &line)
- {
- std::size_t pos = line.find('=');
- if(pos == std::string::npos)
- {
- return false;
- }
- if(pos == 0)
- {
- return false;
- }
- const auto field = std::string_view(line).substr(0, pos);
- // Scan for invalid chars
- for(auto c : field)
- {
- if(!mpt::is_in_range(c, 'a', 'z') && !mpt::is_in_range(c, 'A', 'Z') && !mpt::is_in_range(c, '0', '9') && c != '-' && c != '_')
- {
- return false;
- }
- }
- return true;
- }
- static std::string AUTrimFieldFromAnnotationLine(const std::string &line)
- {
- if(!AUIsAnnotationLineWithField(line))
- {
- return line;
- }
- std::size_t pos = line.find('=');
- return line.substr(pos + 1);
- }
- static std::string AUGetAnnotationFieldFromLine(const std::string &line)
- {
- if(!AUIsAnnotationLineWithField(line))
- {
- return std::string();
- }
- std::size_t pos = line.find('=');
- return line.substr(0, pos);
- }
- bool CSoundFile::ReadAUSample(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize)
- {
- file.Rewind();
- // Verify header
- const auto magic = file.ReadArray<char, 4>();
- const bool bigEndian = !std::memcmp(magic.data(), ".snd", 4);
- const bool littleEndian = !std::memcmp(magic.data(), "dns.", 4);
- if(!bigEndian && !littleEndian)
- return false;
- auto readUint32 = std::bind(bigEndian ? &FileReader::ReadUint32BE : &FileReader::ReadUint32LE, file);
- uint32 dataOffset = readUint32(); // must be divisible by 8 according to spec, however, there are files that ignore this requirement
- uint32 dataSize = readUint32();
- uint32 encoding = readUint32();
- uint32 sampleRate = readUint32();
- uint32 channels = readUint32();
- // According to spec, a minimum 8 byte annotation field after the header fields is required,
- // however, there are files in the wild that violate this requirement.
- // Thus, check for 24 instead of 32 here.
- if(dataOffset < 24) // data offset points inside header
- {
- return false;
- }
- if(channels < 1 || channels > 2)
- return false;
- SampleIO sampleIO(SampleIO::_8bit, channels == 1 ? SampleIO::mono : SampleIO::stereoInterleaved, bigEndian ? SampleIO::bigEndian : SampleIO::littleEndian, SampleIO::signedPCM);
- switch(encoding)
- {
- case 1: sampleIO |= SampleIO::_16bit; // u-law
- sampleIO |= SampleIO::uLaw; break;
- case 2: break; // 8-bit linear PCM
- case 3: sampleIO |= SampleIO::_16bit; break; // 16-bit linear PCM
- case 4: sampleIO |= SampleIO::_24bit; break; // 24-bit linear PCM
- case 5: sampleIO |= SampleIO::_32bit; break; // 32-bit linear PCM
- case 6: sampleIO |= SampleIO::_32bit; // 32-bit IEEE floating point
- sampleIO |= SampleIO::floatPCM;
- break;
- case 7: sampleIO |= SampleIO::_64bit; // 64-bit IEEE floating point
- sampleIO |= SampleIO::floatPCM;
- break;
- case 27: sampleIO |= SampleIO::_16bit; // a-law
- sampleIO |= SampleIO::aLaw; break;
- default: return false;
- }
- if(!file.LengthIsAtLeast(dataOffset))
- {
- return false;
- }
- FileTags tags;
- // This reads annotation metadata as written by OpenMPT, sox, ffmpeg.
- // Additionally, we fall back to just reading the whole field as a single comment.
- // We only read up to the first \0 byte.
- file.Seek(24);
- std::string annotation;
- file.ReadString<mpt::String::maybeNullTerminated>(annotation, dataOffset - 24);
- annotation = mpt::replace(annotation, std::string("\r\n"), std::string("\n"));
- annotation = mpt::replace(annotation, std::string("\r"), std::string("\n"));
- mpt::Charset charset = mpt::IsUTF8(annotation) ? mpt::Charset::UTF8 : mpt::Charset::ISO8859_1;
- const auto lines = mpt::String::Split<std::string>(annotation, "\n");
- bool hasFields = false;
- for(const auto &line : lines)
- {
- if(AUIsAnnotationLineWithField(line))
- {
- hasFields = true;
- break;
- }
- }
- if(hasFields)
- {
- std::map<std::string, std::vector<std::string>> linesPerField;
- std::string lastField = "comment";
- for(const auto &line : lines)
- {
- if(AUIsAnnotationLineWithField(line))
- {
- lastField = mpt::ToLowerCaseAscii(mpt::trim(AUGetAnnotationFieldFromLine(line)));
- }
- linesPerField[lastField].push_back(AUTrimFieldFromAnnotationLine(line));
- }
- tags.title = mpt::ToUnicode(charset, mpt::String::Combine(linesPerField["title" ], std::string("\n")));
- tags.artist = mpt::ToUnicode(charset, mpt::String::Combine(linesPerField["artist" ], std::string("\n")));
- tags.album = mpt::ToUnicode(charset, mpt::String::Combine(linesPerField["album" ], std::string("\n")));
- tags.trackno = mpt::ToUnicode(charset, mpt::String::Combine(linesPerField["track" ], std::string("\n")));
- tags.genre = mpt::ToUnicode(charset, mpt::String::Combine(linesPerField["genre" ], std::string("\n")));
- tags.comments = mpt::ToUnicode(charset, mpt::String::Combine(linesPerField["comment"], std::string("\n")));
- } else
- {
- // Most applications tend to write their own name here,
- // thus there is little use in interpreting the string as a title.
- annotation = mpt::trim_right(annotation, std::string("\r\n"));
- tags.comments = mpt::ToUnicode(charset, annotation);
- }
- file.Seek(dataOffset);
- ModSample &mptSample = Samples[nSample];
- DestroySampleThreadsafe(nSample);
- mptSample.Initialize();
- SmpLength length = mpt::saturate_cast<SmpLength>(file.BytesLeft());
- if(dataSize != 0xFFFFFFFF)
- LimitMax(length, dataSize);
- mptSample.nLength = (length * 8u) / (sampleIO.GetEncodedBitsPerSample() * channels);
- mptSample.nC5Speed = sampleRate;
- m_szNames[nSample] = mpt::ToCharset(GetCharsetInternal(), GetSampleNameFromTags(tags));
- if(mayNormalize)
- {
- sampleIO.MayNormalize();
- }
- sampleIO.ReadSample(mptSample, file);
- mptSample.Convert(MOD_TYPE_IT, GetType());
- mptSample.PrecomputeLoops(*this, false);
- return true;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- // ITS Samples
- bool CSoundFile::ReadITSSample(SAMPLEINDEX nSample, FileReader &file, bool rewind)
- {
- if(rewind)
- {
- file.Rewind();
- }
- ITSample sampleHeader;
- if(!file.ReadStruct(sampleHeader)
- || memcmp(sampleHeader.id, "IMPS", 4))
- {
- return false;
- }
- DestroySampleThreadsafe(nSample);
- ModSample &sample = Samples[nSample];
- file.Seek(sampleHeader.ConvertToMPT(sample));
- m_szNames[nSample] = mpt::String::ReadBuf(mpt::String::spacePaddedNull, sampleHeader.name);
- if(sample.uFlags[CHN_ADLIB])
- {
- OPLPatch patch;
- file.ReadArray(patch);
- sample.SetAdlib(true, patch);
- InitOPL();
- if(!SupportsOPL())
- {
- AddToLog(LogInformation, U_("OPL instruments are not supported by this format."));
- }
- } else if(!sample.uFlags[SMP_KEEPONDISK])
- {
- sampleHeader.GetSampleFormat().ReadSample(sample, file);
- } else
- {
- // External sample
- size_t strLen;
- file.ReadVarInt(strLen);
- #ifdef MPT_EXTERNAL_SAMPLES
- std::string filenameU8;
- file.ReadString<mpt::String::maybeNullTerminated>(filenameU8, strLen);
- mpt::PathString filename = mpt::PathString::FromUTF8(filenameU8);
- if(!filename.empty())
- {
- if(file.GetOptionalFileName())
- {
- filename = filename.RelativePathToAbsolute(file.GetOptionalFileName()->GetPath());
- }
- if(!LoadExternalSample(nSample, filename))
- {
- AddToLog(LogWarning, U_("Unable to load sample: ") + filename.ToUnicode());
- }
- } else
- {
- sample.uFlags.reset(SMP_KEEPONDISK);
- }
- #else
- file.Skip(strLen);
- #endif // MPT_EXTERNAL_SAMPLES
- }
- sample.Convert(MOD_TYPE_IT, GetType());
- sample.PrecomputeLoops(*this, false);
- return true;
- }
- bool CSoundFile::ReadITISample(SAMPLEINDEX nSample, FileReader &file)
- {
- ITInstrument instrumentHeader;
- file.Rewind();
- if(!file.ReadStruct(instrumentHeader)
- || memcmp(instrumentHeader.id, "IMPI", 4))
- {
- return false;
- }
- file.Rewind();
- ModInstrument dummy;
- ITInstrToMPT(file, dummy, instrumentHeader.trkvers);
- // Old SchismTracker versions set nos=0
- const SAMPLEINDEX nsamples = std::max(static_cast<SAMPLEINDEX>(instrumentHeader.nos), *std::max_element(std::begin(dummy.Keyboard), std::end(dummy.Keyboard)));
- if(!nsamples)
- return false;
- // Preferrably read the middle-C sample
- auto sample = dummy.Keyboard[NOTE_MIDDLEC - NOTE_MIN];
- if(sample > 0)
- sample--;
- else
- sample = 0;
- file.Seek(file.GetPosition() + sample * sizeof(ITSample));
- return ReadITSSample(nSample, file, false);
- }
- bool CSoundFile::ReadITIInstrument(INSTRUMENTINDEX nInstr, FileReader &file)
- {
- ITInstrument instrumentHeader;
- SAMPLEINDEX smp = 0;
- file.Rewind();
- if(!file.ReadStruct(instrumentHeader)
- || memcmp(instrumentHeader.id, "IMPI", 4))
- {
- return false;
- }
- if(nInstr > GetNumInstruments()) m_nInstruments = nInstr;
- ModInstrument *pIns = new (std::nothrow) ModInstrument();
- if(pIns == nullptr)
- {
- return false;
- }
- DestroyInstrument(nInstr, deleteAssociatedSamples);
- Instruments[nInstr] = pIns;
- file.Rewind();
- ITInstrToMPT(file, *pIns, instrumentHeader.trkvers);
- // Old SchismTracker versions set nos=0
- const SAMPLEINDEX nsamples = std::max(static_cast<SAMPLEINDEX>(instrumentHeader.nos), *std::max_element(std::begin(pIns->Keyboard), std::end(pIns->Keyboard)));
- // In order to properly compute the position, in file, of eventual extended settings
- // such as "attack" we need to keep the "real" size of the last sample as those extra
- // setting will follow this sample in the file
- FileReader::off_t extraOffset = file.GetPosition();
- // Reading Samples
- std::vector<SAMPLEINDEX> samplemap(nsamples, 0);
- for(SAMPLEINDEX i = 0; i < nsamples; i++)
- {
- smp = GetNextFreeSample(nInstr, smp + 1);
- if(smp == SAMPLEINDEX_INVALID) break;
- samplemap[i] = smp;
- const FileReader::off_t offset = file.GetPosition();
- if(!ReadITSSample(smp, file, false))
- smp--;
- extraOffset = std::max(extraOffset, file.GetPosition());
- file.Seek(offset + sizeof(ITSample));
- }
- if(GetNumSamples() < smp) m_nSamples = smp;
- // Adjust sample assignment
- for(auto &sample : pIns->Keyboard)
- {
- if(sample > 0 && sample <= nsamples)
- {
- sample = samplemap[sample - 1];
- }
- }
- if(file.Seek(extraOffset))
- {
- // Read MPT crap
- ReadExtendedInstrumentProperties(pIns, file);
- }
- pIns->Convert(MOD_TYPE_IT, GetType());
- pIns->Sanitize(GetType());
- return true;
- }
- #ifndef MODPLUG_NO_FILESAVE
- bool CSoundFile::SaveITIInstrument(INSTRUMENTINDEX nInstr, std::ostream &f, const mpt::PathString &filename, bool compress, bool allowExternal) const
- {
- ITInstrument iti;
- ModInstrument *pIns = Instruments[nInstr];
- if((!pIns) || (filename.empty() && allowExternal)) return false;
- auto instSize = iti.ConvertToIT(*pIns, false, *this);
- // Create sample assignment table
- std::vector<SAMPLEINDEX> smptable;
- std::vector<uint8> smpmap(GetNumSamples(), 0);
- for(size_t i = 0; i < NOTE_MAX; i++)
- {
- const SAMPLEINDEX smp = pIns->Keyboard[i];
- if(smp && smp <= GetNumSamples())
- {
- if(!smpmap[smp - 1])
- {
- // We haven't considered this sample yet.
- smptable.push_back(smp);
- smpmap[smp - 1] = static_cast<uint8>(smptable.size());
- }
- iti.keyboard[i * 2 + 1] = smpmap[smp - 1];
- } else
- {
- iti.keyboard[i * 2 + 1] = 0;
- }
- }
- iti.nos = static_cast<uint8>(smptable.size());
- smpmap.clear();
- uint32 filePos = instSize;
- mpt::IO::WritePartial(f, iti, instSize);
- filePos += mpt::saturate_cast<uint32>(smptable.size() * sizeof(ITSample));
- // Writing sample headers + data
- std::vector<SampleIO> sampleFlags;
- for(auto smp : smptable)
- {
- ITSample itss;
- itss.ConvertToIT(Samples[smp], GetType(), compress, compress, allowExternal);
- const bool isExternal = itss.cvt == ITSample::cvtExternalSample;
- mpt::String::WriteBuf(mpt::String::nullTerminated, itss.name) = m_szNames[smp];
- itss.samplepointer = filePos;
- mpt::IO::Write(f, itss);
- // Write sample
- auto curPos = mpt::IO::TellWrite(f);
- mpt::IO::SeekAbsolute(f, filePos);
- if(!isExternal)
- {
- filePos += mpt::saturate_cast<uint32>(itss.GetSampleFormat(0x0214).WriteSample(f, Samples[smp]));
- } else
- {
- #ifdef MPT_EXTERNAL_SAMPLES
- const std::string filenameU8 = GetSamplePath(smp).AbsolutePathToRelative(filename.GetPath()).ToUTF8();
- const size_t strSize = filenameU8.size();
- size_t intBytes = 0;
- if(mpt::IO::WriteVarInt(f, strSize, &intBytes))
- {
- filePos += mpt::saturate_cast<uint32>(intBytes + strSize);
- mpt::IO::WriteRaw(f, filenameU8.data(), strSize);
- }
- #endif // MPT_EXTERNAL_SAMPLES
- }
- mpt::IO::SeekAbsolute(f, curPos);
- }
- mpt::IO::SeekEnd(f);
- // Write 'MPTX' extension tag
- mpt::IO::WriteRaw(f, "XTPM", 4);
- WriteInstrumentHeaderStructOrField(pIns, f); // Write full extended header.
- return true;
- }
- #endif // MODPLUG_NO_FILESAVE
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // 8SVX / 16SVX / MAUD Samples
- // IFF File Header
- struct IFFHeader
- {
- char form[4]; // "FORM"
- uint32be size;
- char magic[4]; // "8SVX", "16SV", "MAUD"
- };
- MPT_BINARY_STRUCT(IFFHeader, 12)
- // General IFF Chunk header
- struct IFFChunk
- {
- // 32-Bit chunk identifiers
- enum ChunkIdentifiers
- {
- // 8SVX / 16SV
- idVHDR = MagicBE("VHDR"),
- idBODY = MagicBE("BODY"),
- idCHAN = MagicBE("CHAN"),
- // MAUD
- idMHDR = MagicBE("MHDR"),
- idMDAT = MagicBE("MDAT"),
- idNAME = MagicBE("NAME"),
- };
- uint32be id; // See ChunkIdentifiers
- uint32be length; // Chunk size without header
- size_t GetLength() const
- {
- if(length == 0) // Broken files
- return std::numeric_limits<size_t>::max();
- return length;
- }
- ChunkIdentifiers GetID() const
- {
- return static_cast<ChunkIdentifiers>(id.get());
- }
- };
- MPT_BINARY_STRUCT(IFFChunk, 8)
- struct IFFSampleHeader
- {
- uint32be oneShotHiSamples; // Samples in the high octave 1-shot part
- uint32be repeatHiSamples; // Samples in the high octave repeat part
- uint32be samplesPerHiCycle; // Samples/cycle in high octave, else 0
- uint16be samplesPerSec; // Data sampling rate
- uint8be octave; // Octaves of waveforms
- uint8be compression; // Data compression technique used
- uint32be volume;
- };
- MPT_BINARY_STRUCT(IFFSampleHeader, 20)
- bool CSoundFile::ReadIFFSample(SAMPLEINDEX nSample, FileReader &file)
- {
- file.Rewind();
- IFFHeader fileHeader;
- if(!file.ReadStruct(fileHeader)
- || memcmp(fileHeader.form, "FORM", 4)
- || (memcmp(fileHeader.magic, "8SVX", 4) && memcmp(fileHeader.magic, "16SV", 4) && memcmp(fileHeader.magic, "MAUD", 4)))
- {
- return false;
- }
- const auto chunks = file.ReadChunks<IFFChunk>(2);
- FileReader sampleData;
- SampleIO sampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::bigEndian, SampleIO::signedPCM);
- uint32 numSamples = 0, sampleRate = 0, loopStart = 0, loopLength = 0, volume = 0;
- if(!memcmp(fileHeader.magic, "MAUD", 4))
- {
- FileReader mhdrChunk = chunks.GetChunk(IFFChunk::idMHDR);
- sampleData = chunks.GetChunk(IFFChunk::idMDAT);
- if(!mhdrChunk.LengthIs(32)
- || !sampleData.IsValid())
- {
- return false;
- }
- numSamples = mhdrChunk.ReadUint32BE();
- const uint16 bitsPerSample = mhdrChunk.ReadUint16BE();
- mhdrChunk.Skip(2); // bits per sample after decompression
- sampleRate = mhdrChunk.ReadUint32BE();
- const auto [clockDivide, channelInformation, numChannels, compressionType] = mhdrChunk.ReadArray<uint16be, 4>();
- if(!clockDivide)
- return false;
- else
- sampleRate /= clockDivide;
- if(numChannels != (channelInformation + 1))
- return false;
- if(numChannels == 2)
- sampleIO |= SampleIO::stereoInterleaved;
- if(bitsPerSample == 8 && compressionType == 0)
- sampleIO |= SampleIO::unsignedPCM;
- else if(bitsPerSample == 8 && compressionType == 2)
- sampleIO |= SampleIO::aLaw;
- else if(bitsPerSample == 8 && compressionType == 3)
- sampleIO |= SampleIO::uLaw;
- else if(bitsPerSample == 16 && compressionType == 0)
- sampleIO |= SampleIO::_16bit;
- else
- return false;
- } else
- {
- FileReader vhdrChunk = chunks.GetChunk(IFFChunk::idVHDR);
- FileReader chanChunk = chunks.GetChunk(IFFChunk::idCHAN);
- sampleData = chunks.GetChunk(IFFChunk::idBODY);
- IFFSampleHeader sampleHeader;
- if(!sampleData.IsValid()
- || !vhdrChunk.IsValid()
- || !vhdrChunk.ReadStruct(sampleHeader))
- {
- return false;
- }
- const uint8 bytesPerSample = memcmp(fileHeader.magic, "8SVX", 4) ? 2 : 1;
- const uint8 numChannels = chanChunk.ReadUint32BE() == 6 ? 2 : 1;
- const uint8 bytesPerFrame = bytesPerSample * numChannels;
- // While this is an Amiga format, the 16SV version appears to be only used on PC, and only with little-endian sample data.
- if(bytesPerSample == 2)
- sampleIO = SampleIO(SampleIO::_16bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::signedPCM);
- if(numChannels == 2)
- sampleIO |= SampleIO::stereoSplit;
- loopStart = sampleHeader.oneShotHiSamples / bytesPerFrame;
- loopLength = sampleHeader.repeatHiSamples / bytesPerFrame;
- sampleRate = sampleHeader.samplesPerSec;
- volume = sampleHeader.volume;
- numSamples = mpt::saturate_cast<SmpLength>(sampleData.GetLength() / bytesPerFrame);
- }
- DestroySampleThreadsafe(nSample);
- ModSample &sample = Samples[nSample];
- sample.Initialize();
- sample.nLength = numSamples;
- sample.nLoopStart = loopStart;
- sample.nLoopEnd = sample.nLoopStart + loopLength;
- if((sample.nLoopStart + 4 < sample.nLoopEnd) && (sample.nLoopEnd <= sample.nLength))
- sample.uFlags.set(CHN_LOOP);
- sample.nC5Speed = sampleRate;
- if(!sample.nC5Speed)
- sample.nC5Speed = 22050;
- sample.nVolume = static_cast<uint16>(volume / 256);
- if(!sample.nVolume || sample.nVolume > 256)
- sample.nVolume = 256;
- sample.Convert(MOD_TYPE_IT, GetType());
- FileReader nameChunk = chunks.GetChunk(IFFChunk::idNAME);
- if(nameChunk.IsValid())
- nameChunk.ReadString<mpt::String::maybeNullTerminated>(m_szNames[nSample], nameChunk.GetLength());
- else
- m_szNames[nSample] = "";
- sampleIO.ReadSample(sample, sampleData);
- sample.PrecomputeLoops(*this, false);
- return true;
- }
- OPENMPT_NAMESPACE_END
|