#include "PommeSound.h" #include "Utilities/bigendianstreams.h" #include #include static void AIFFAssert(bool condition, const char* message) { if (!condition) { throw std::runtime_error(message); } } static void ParseCOMM(Pomme::BigEndianIStream& f, Pomme::Sound::SampledSoundInfo& info, bool isAIFC) { info.nChannels = f.Read(); info.nPackets = f.Read(); info.codecBitDepth = f.Read(); info.sampleRate = f.Read80BitFloat(); if (isAIFC) { info.compressionType = f.Read(); f.ReadPascalString(2); // This is a human-friendly compression name. Skip it. } else { info.compressionType = 'NONE'; } switch (info.compressionType) { case 'NONE': info.bigEndian = true; info.isCompressed = false; break; case 'twos': info.bigEndian = true; info.isCompressed = false; break; case 'sowt': info.bigEndian = false; info.isCompressed = false; break; case 'raw ': info.bigEndian = true; info.isCompressed = false; break; case 'MAC3': info.bigEndian = true; info.isCompressed = true; break; case 'ima4': info.bigEndian = true; info.isCompressed = true; break; case 'ulaw': info.bigEndian = true; info.isCompressed = true; break; case 'alaw': info.bigEndian = true; info.isCompressed = true; break; default: throw std::runtime_error("unknown AIFF-C compression type"); } } static void ParseMARK(Pomme::BigEndianIStream& f, std::map& markers) { int16_t nMarkers = f.Read(); for (int16_t i = 0; i < nMarkers; i++) { uint16_t markerID = f.Read(); uint32_t markerPosition = f.Read(); f.ReadPascalString(2); // skip name markers[markerID] = markerPosition; } } static void ParseINST(Pomme::BigEndianIStream& f, Pomme::Sound::SampledSoundInfo& info, std::map& markers) { info.baseNote = f.Read(); f.Skip(1); // detune f.Skip(1); // lowNote f.Skip(1); // highNote f.Skip(1); // lowVelocity f.Skip(1); // highVelocity f.Skip(2); // gain uint16_t playMode = f.Read(); uint16_t beginLoopMarkerID = f.Read(); uint16_t endLoopMarkerID = f.Read(); f.Skip(2); f.Skip(2); f.Skip(2); switch (playMode) { case 0: break; case 1: info.loopStart = markers.at(beginLoopMarkerID); info.loopEnd = markers.at(endLoopMarkerID); break; default: throw std::runtime_error("unsupported AIFF INST playMode"); } } static std::streampos GetSoundInfoFromAIFF(std::istream& input, Pomme::Sound::SampledSoundInfo& info) { Pomme::BigEndianIStream f(input); AIFFAssert('FORM' == f.Read(), "AIFF: invalid FORM"); auto formSize = f.Read(); auto endOfForm = f.Tell() + std::streampos(formSize); auto formType = f.Read(); AIFFAssert(formType == 'AIFF' || formType == 'AIFC', "AIFF: not an AIFF or AIFC file"); bool gotCOMM = false; std::streampos sampledSoundDataOffset = 0; info = {}; info.compressionType = 'NONE'; info.isCompressed = false; info.baseNote = 60; // Middle C std::map markers; while (f.Tell() != endOfForm) { auto ckID = f.Read(); auto ckSize = f.Read(); std::streampos endOfChunk = f.Tell() + std::streampos(ckSize); switch (ckID) { case 'FVER': { uint32_t timestamp = f.Read(); AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER"); break; } case 'COMM': // common chunk, 2-85 ParseCOMM(f, info, formType=='AIFC'); gotCOMM = true; break; case 'MARK': ParseMARK(f, markers); break; case 'INST': ParseINST(f, info, markers); break; case 'SSND': { AIFFAssert(gotCOMM, "AIFF: reached SSND before COMM"); AIFFAssert(0 == f.Read(), "AIFF: unexpected offset/blockSize in SSND"); // sampled sound data is here sampledSoundDataOffset = f.Tell(); const int ssndSize = ckSize - 8; info.compressedLength = ssndSize; if (!info.isCompressed) { info.decompressedLength = info.compressedLength; } else { auto codec = Pomme::Sound::GetCodec(info.compressionType); info.decompressedLength = info.nChannels * info.nPackets * codec->SamplesPerPacket() * 2; } f.Skip(ssndSize); break; } default: f.Goto(int(endOfChunk)); break; } AIFFAssert(f.Tell() == endOfChunk, "AIFF: incorrect end-of-chunk position"); // skip zero pad byte if odd position if ((f.Tell() & 1) == 1) { f.Skip(1); } } f.Goto(sampledSoundDataOffset); return sampledSoundDataOffset; } SndListHandle Pomme::Sound::LoadAIFFAsResource(std::istream& stream) { Pomme::Sound::SampledSoundInfo info = {}; std::streampos ssndStart = GetSoundInfoFromAIFF(stream, info); char* dataOffset = nullptr; SndListHandle h = info.MakeStandaloneResource(&dataOffset); stream.seekg(ssndStart, std::ios::beg); stream.read(dataOffset, info.compressedLength); return h; }