From 77fda7913d9fa1576208a8830e7c9cbba1034e52 Mon Sep 17 00:00:00 2001 From: Iliyas Jorio Date: Sun, 25 Jul 2021 17:22:10 +0200 Subject: [PATCH] Load AIFF-C files as sound resources --- src/Pomme.h | 3 + src/PommeSound.h | 4 +- src/Sound/AIFF.cpp | 148 +++++++++++++++++++------ src/Sound/SoundManager.cpp | 51 +++++++-- src/Sound/SoundUtilities.cpp | 171 ++++++++++++++++------------- src/Utilities/bigendianstreams.cpp | 7 +- src/Utilities/bigendianstreams.h | 2 +- 7 files changed, 264 insertions(+), 122 deletions(-) diff --git a/src/Pomme.h b/src/Pomme.h index d1917dd..b1183d1 100644 --- a/src/Pomme.h +++ b/src/Pomme.h @@ -481,6 +481,9 @@ Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetT // Pomme extension void Pomme_PauseAllChannels(Boolean pause); +// Pomme extension +SndListHandle Pomme_SndLoadFileAsResource(short fRefNum); + #ifdef __cplusplus } #endif diff --git a/src/PommeSound.h b/src/PommeSound.h index 70a91ce..4d46a55 100644 --- a/src/PommeSound.h +++ b/src/PommeSound.h @@ -14,8 +14,6 @@ namespace Pomme::Sound void Shutdown(); - void ReadAIFF(std::istream& input, cmixer::WavStream& output); - struct SampledSoundInfo { int16_t nChannels; @@ -100,5 +98,7 @@ namespace Pomme::Sound void GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info); + std::streampos GetSoundInfoFromAIFF(std::istream& input, SampledSoundInfo& info); + std::unique_ptr GetCodec(uint32_t fourCC); } diff --git a/src/Sound/AIFF.cpp b/src/Sound/AIFF.cpp index 30a21be..504baec 100644 --- a/src/Sound/AIFF.cpp +++ b/src/Sound/AIFF.cpp @@ -1,6 +1,7 @@ #include "PommeSound.h" #include "Utilities/bigendianstreams.h" #include +#include static void AIFFAssert(bool condition, const char* message) { @@ -10,7 +11,84 @@ static void AIFFAssert(bool condition, const char* message) } } -void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output) +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"); + } +} + +std::streampos Pomme::Sound::GetSoundInfoFromAIFF(std::istream& input, SampledSoundInfo& info) { BigEndianIStream f(input); @@ -20,14 +98,15 @@ void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output) auto formType = f.Read(); AIFFAssert(formType == 'AIFF' || formType == 'AIFC', "AIFF: not an AIFF or AIFC file"); - // COMM chunk contents - int nChannels = 0; - int nPackets = 0; - int bitDepth = 0; - int sampleRate = 0; - uint32_t compressionType = 'NONE'; - 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) { @@ -39,52 +118,48 @@ void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output) { case 'FVER': { - auto timestamp = f.Read(); + uint32_t timestamp = f.Read(); AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER"); break; } case 'COMM': // common chunk, 2-85 - { - nChannels = f.Read(); - nPackets = f.Read(); - bitDepth = f.Read(); - sampleRate = (int)f.Read80BitFloat(); - if (formType == 'AIFC') - { - compressionType = f.Read(); - f.ReadPascalString(); // This is a human-friendly compression name. Skip it. - } + 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; - if (compressionType == 'NONE') + info.compressedLength = ssndSize; + + if (!info.isCompressed) { - // Raw big-endian PCM -- just init the WavStream without decoding - auto spanOut = output.GetBuffer(ssndSize); - f.Read(spanOut.data(), ssndSize); - output.Init(sampleRate, bitDepth, nChannels, true, spanOut); + info.decompressedLength = info.compressedLength; } else { - auto ssnd = std::vector(ssndSize); - f.Read(ssnd.data(), ssndSize); - auto codec = Pomme::Sound::GetCodec(compressionType); - auto spanIn = std::span(ssnd); - auto spanOut = output.GetBuffer(nChannels * nPackets * codec->SamplesPerPacket() * 2); - codec->Decode(nChannels, spanIn, spanOut); - output.Init(sampleRate, 16, nChannels, false, spanOut); + auto codec = Pomme::Sound::GetCodec(info.compressionType); + info.decompressedLength = info.nChannels * info.nPackets * codec->SamplesPerPacket() * 2; } + f.Skip(ssndSize); + break; } @@ -94,6 +169,15 @@ void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output) } 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; } diff --git a/src/Sound/SoundManager.cpp b/src/Sound/SoundManager.cpp index 4ccbdda..938c60d 100644 --- a/src/Sound/SoundManager.cpp +++ b/src/Sound/SoundManager.cpp @@ -197,7 +197,7 @@ public: //----------------------------------------------------------------------------- // Internal utilities -static inline ChannelImpl& GetImpl(SndChannelPtr chan) +static inline ChannelImpl& GetChannelImpl(SndChannelPtr chan) { return *(ChannelImpl*) chan->channelImpl; } @@ -309,7 +309,7 @@ OSErr SndDisposeChannel(SndChannelPtr macChanPtr, Boolean quietNow) { TODO2("SndDisposeChannel: quietNow == false is not implemented"); } - delete &GetImpl(macChanPtr); + delete &GetChannelImpl(macChanPtr); return noErr; } @@ -317,7 +317,7 @@ OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatu { *theStatus = {}; - auto& source = GetImpl(chan).source; + auto& source = GetChannelImpl(chan).source; theStatus->scChannelPaused = source.GetState() == cmixer::CM_STATE_PAUSED; theStatus->scChannelBusy = source.GetState() == cmixer::CM_STATE_PLAYING; @@ -331,7 +331,8 @@ static void InstallSoundInChannel(SndChannelPtr chan, const Ptr sampledSoundHead //--------------------------------- // Get internal channel - auto& impl = GetImpl(chan); + + auto& impl = GetChannelImpl(chan); impl.Recycle(); //--------------------------------- @@ -391,7 +392,7 @@ static void InstallSoundInChannel(SndChannelPtr chan, const Ptr sampledSoundHead OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd) { - auto& impl = GetImpl(chan); + auto& impl = GetChannelImpl(chan); // Discard the high bit of the command (it indicates whether an 'snd ' resource has associated data). switch (cmd->cmd & 0x7FFF) @@ -509,13 +510,41 @@ OSErr SndStartFilePlay( return unimpErr; } - auto& impl = GetImpl(chan); + auto& impl = GetChannelImpl(chan); impl.Recycle(); - auto& stream = Pomme::Files::GetStream(fRefNum); + auto& fileStream = Pomme::Files::GetStream(fRefNum); + // Rewind -- the file might've been fully played already and we might just be trying to loop it - stream.seekg(0, std::ios::beg); - Pomme::Sound::ReadAIFF(stream, impl.source); + fileStream.seekg(0, std::ios::beg); + + // Get metadata from AIFF + Pomme::Sound::SampledSoundInfo info = {}; + std::streampos sampledSoundDataOffset = GetSoundInfoFromAIFF(fileStream, info); + + // Have file stream seek to start of SSND sampled sound data + if (sampledSoundDataOffset <= 0) + throw std::runtime_error("dubious offset to SSND data"); + fileStream.seekg(sampledSoundDataOffset, std::ios::beg); + + // Read samples into WavStream + if (!info.isCompressed) + { + // Raw big-endian PCM -- just init the WavStream without decoding + auto spanOut = impl.source.GetBuffer(info.decompressedLength); + fileStream.read(spanOut.data(), info.decompressedLength); + impl.source.Init(info.sampleRate, info.codecBitDepth, info.nChannels, info.bigEndian, spanOut); + } + else + { + auto ssnd = std::vector(info.compressedLength); + fileStream.read(ssnd.data(), info.compressedLength); + auto codec = Pomme::Sound::GetCodec(info.compressionType); + auto spanIn = std::span(ssnd); + auto spanOut = impl.source.GetBuffer(info.decompressedLength); + codec->Decode(info.nChannels, spanIn, spanOut); + impl.source.Init(info.sampleRate, 16, info.nChannels, false, spanOut); + } if (theCompletion) { @@ -541,7 +570,7 @@ OSErr SndStartFilePlay( OSErr SndPauseFilePlay(SndChannelPtr chan) { // TODO: check that chan is being used for play from disk - GetImpl(chan).source.TogglePause(); + GetChannelImpl(chan).source.TogglePause(); return noErr; } @@ -550,7 +579,7 @@ OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow) // TODO: check that chan is being used for play from disk if (!quietNow) TODO2("quietNow==false not supported yet, sound will be cut off immediately instead"); - GetImpl(chan).source.Stop(); + GetChannelImpl(chan).source.Stop(); return noErr; } diff --git a/src/Sound/SoundUtilities.cpp b/src/Sound/SoundUtilities.cpp index 1c7ec11..fa9f1e4 100644 --- a/src/Sound/SoundUtilities.cpp +++ b/src/Sound/SoundUtilities.cpp @@ -7,25 +7,13 @@ using namespace Pomme::Sound; -//----------------------------------------------------------------------------- -// Cookie-cutter sound command list. -// Used to generate 'snd ' resources. - -static const uint8_t kSampledSoundCommandList[20] = +enum SoundResourceType { - 0,1, // format - 0,1, // modifier count - 0,5, // modifier "sampled synth" - 0,0,0,0, // init bits - 0,1, // command count - 0x80,soundCmd, // command soundCmd (high bit set) - 0,0, // param1 - 0,0,0,20, // param2 (offset) - // Sample data follows + kSoundResourceType_Standard = 0x0001, + kSoundResourceType_HyperCard = 0x0002, + kSoundResourceType_Pomme = 'po', // Pomme extension: only sampled data, no command list }; -constexpr int kSampledSoundCommandListLength = sizeof(kSampledSoundCommandList); - //----------------------------------------------------------------------------- // 'snd ' resource header @@ -37,7 +25,6 @@ struct SampledSoundHeader SInt32 stdSH_nBytes; SInt32 cmpSH_nChannels; SInt32 extSH_nChannels; - SInt32 nativeSH_nBytes; }; UnsignedFixed fixedSampleRate; UInt32 loopStart; @@ -54,14 +41,14 @@ constexpr const char* kSampledSoundHeaderPackFormat = "IiIIIbb"; enum SampledSoundEncoding { - stdSH = 0x00, - nativeSH_mono16 = 0x10, // pomme extension - nativeSH_stereo16 = 0x11, // pomme extension - cmpSH = 0xFE, - extSH = 0xFF, + kSampledSoundEncoding_stdSH = 0x00, // standard sound header (noncompressed 8-bit mono sample data) + kSampledSoundEncoding_cmpSH = 0xFE, // compressed sound header + kSampledSoundEncoding_extSH = 0xFF, // extended sound header (noncompressed 8/16-bit mono or stereo) }; +//----------------------------------------------------------------------------- // IM:S:2-58 "MyGetSoundHeaderOffset" + OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset) { memstream sndStream((Ptr) *sndHandle, GetHandleSize((Handle) sndHandle)); @@ -71,7 +58,7 @@ OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset) SInt16 format = f.Read(); switch (format) { - case 1: // Standard 'snd ' resource + case kSoundResourceType_Standard: { SInt16 modifierCount = f.Read(); SInt16 synthType = f.Read(); @@ -89,10 +76,15 @@ OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset) break; } - case 2: // HyperCard sampled-sound format + case kSoundResourceType_HyperCard: f.Skip(2); // Skip reference count (for application use) break; + case kSoundResourceType_Pomme: + *offset = 2; + // return now - our own sound resources just have sampled data, no commands + return noErr; + default: return badFormat; } @@ -122,8 +114,22 @@ OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset) return badFormat; } +//----------------------------------------------------------------------------- + void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info) { + // Check if this snd resource is in Pomme's internal format. + // If so, the resource's header is a raw SampledSoundInfo record, + // which lets us bypass the parsing of a real Mac snd resource. + if (0 == memcmp("POMM", sndhdr, 4)) + { + memcpy(&info, sndhdr+4, sizeof(SampledSoundInfo)); + info.dataStart = sndhdr+4+sizeof(SampledSoundInfo); + return; + } + + // It's a real Mac snd resource. Parse it. + // Prep the BE reader on the header. memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42); Pomme::BigEndianIStream f(headerInput); @@ -149,7 +155,7 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info) switch (header.encoding) { - case 0x00: // stdSH - standard sound header (noncompressed 8-bit mono sample data) + case kSampledSoundEncoding_stdSH: info.compressionType = 'raw '; // unsigned (in AIFF-C files, 'NONE' means signed!) info.isCompressed = false; info.bigEndian = false; @@ -161,25 +167,12 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info) info.decompressedLength = info.compressedLength; break; - case nativeSH_mono16: // pomme extension for little-endian PCM data - case nativeSH_stereo16: - info.compressionType = 'sowt'; - info.isCompressed = false; - info.bigEndian = false; - info.codecBitDepth = 16; - info.nChannels = header.encoding == nativeSH_mono16 ? 1 : 2; - info.nPackets = header.nativeSH_nBytes / (2 * info.nChannels); - info.dataStart = sndhdr + f.Tell(); - info.compressedLength = header.nativeSH_nBytes; - info.decompressedLength = info.compressedLength; - break; - - case 0xFE: // cmpSH - compressed sound header + case kSampledSoundEncoding_cmpSH: { info.nPackets = f.Read(); - f.Skip(14); + f.Skip(14); // skip AIFFSampleRate(10), markerChunk(4) info.compressionType = f.Read(); - f.Skip(20); + f.Skip(20); // skip futureUse2(4), stateVars(4), leftOverSamples(4), compressionID(2), packetSize(2), snthID(2) if (info.compressionType == 0) // Assume MACE-3 { @@ -202,12 +195,12 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info) break; } - case 0xFF: // extSH - extended sound header (noncompressed 8/16-bit mono or stereo) + case kSampledSoundEncoding_extSH: { info.nPackets = f.Read(); - f.Skip(22); + f.Skip(22); // skip AIFFSampleRate(10), markerChunk(4), instrumentChunks(10), AESRecording(4) info.codecBitDepth = f.Read(); - f.Skip(14); + f.Skip(14); // skip futureUse1(2), futureUse2(4), futureUse3(4), futureUse4(4) info.isCompressed = false; info.bigEndian = true; @@ -227,6 +220,8 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info) } } +//----------------------------------------------------------------------------- + void Pomme::Sound::GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info) { long offsetToHeader; @@ -238,56 +233,82 @@ void Pomme::Sound::GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInf GetSoundInfo(sndhdr, info); } +//----------------------------------------------------------------------------- +// Extension: load AIFF file as resource + +SndListHandle Pomme_SndLoadFileAsResource(short fRefNum) +{ + auto& stream = Pomme::Files::GetStream(fRefNum); + + Pomme::Sound::SampledSoundInfo info = {}; + std::streampos ssndStart = Pomme::Sound::GetSoundInfoFromAIFF(stream, info); + + stream.seekg(ssndStart, std::ios::beg); + + Handle h = NewHandleClear(2 + 4 + sizeof(SampledSoundInfo) + info.compressedLength); + memcpy(*h, "poPOMM", 6); + memcpy(*h+6, &info, sizeof(SampledSoundInfo)); + stream.read(*h+6+sizeof(SampledSoundInfo), info.compressedLength); + + return (SndListHandle) h; +} + //----------------------------------------------------------------------------- // Extension: decompress Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader) { - SampledSoundInfo info; - GetSoundInfoFromSndResource((Handle) *sndHandlePtr, info); + SampledSoundInfo inInfo; + GetSoundInfoFromSndResource((Handle) *sndHandlePtr, inInfo); - // We only handle cmpSH (compressed) 'snd ' resources. - if (!info.isCompressed) + if (!inInfo.dataStart) { - return false; + throw std::runtime_error("cannot decompress snd resource without dataStart"); } - int outInitialSize = kSampledSoundCommandListLength + kSampledSoundHeaderLength; + Handle h = NewHandleClear(2 + 4 + sizeof(SampledSoundInfo) + inInfo.decompressedLength); + const char* inDataStart = inInfo.dataStart; + char* outDataStart = *h+6+sizeof(SampledSoundInfo); - std::unique_ptr codec = Pomme::Sound::GetCodec(info.compressionType); + SampledSoundInfo outInfo = inInfo; + outInfo.dataStart = nullptr; + outInfo.isCompressed = false; + outInfo.compressedLength = outInfo.decompressedLength; - // Decompress - SndListHandle outHandle = (SndListHandle) NewHandle(outInitialSize + info.decompressedLength); - auto spanIn = std::span(info.dataStart, info.compressedLength); - auto spanOut = std::span((char*) *outHandle + outInitialSize, info.decompressedLength); - codec->Decode(info.nChannels, spanIn, spanOut); + if (!inInfo.isCompressed) + { + // Raw big-endian PCM + if (inInfo.decompressedLength != inInfo.compressedLength) + throw std::runtime_error("decompressedLength != compressedLength???"); - // ------------------------------------------------------ - // Now we have the PCM data. - // Put the output 'snd ' resource together. + memcpy(outDataStart, inDataStart, inInfo.decompressedLength); + } + else + { + auto codec = Pomme::Sound::GetCodec(inInfo.compressionType); + auto spanIn = std::span(inDataStart, inInfo.compressedLength); + auto spanOut = std::span(outDataStart, inInfo.decompressedLength); + codec->Decode(inInfo.nChannels, spanIn, spanOut); - SampledSoundHeader shOut = {}; - shOut.zero = 0; - shOut.nativeSH_nBytes = info.decompressedLength; - shOut.fixedSampleRate = static_cast(info.sampleRate * 65536.0); - shOut.loopStart = info.loopStart; - shOut.loopEnd = info.loopEnd; - shOut.encoding = info.nChannels == 2 ? nativeSH_stereo16 : nativeSH_mono16; - shOut.baseFrequency = info.baseNote; + outInfo.compressionType = 'swot'; + outInfo.bigEndian = false; + outInfo.codecBitDepth = 16; + outInfo.nPackets = codec->SamplesPerPacket() * inInfo.nPackets; + } - ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast(&shOut)); - - memcpy(*outHandle, kSampledSoundCommandList, kSampledSoundCommandListLength); - memcpy((char*) *outHandle + kSampledSoundCommandListLength, &shOut, kSampledSoundHeaderLength); + // Write header + memcpy(*h, "poPOMM", 6); + memcpy(*h+6, &outInfo, sizeof(SampledSoundInfo)); // Nuke compressed sound handle, replace it with the decopmressed one we've just created DisposeHandle((Handle) *sndHandlePtr); - *sndHandlePtr = outHandle; - *offsetToHeader = kSampledSoundCommandListLength; + *sndHandlePtr = (SndListHandle) h; + *offsetToHeader = 2; + // Check offset long offsetCheck = 0; - OSErr err = GetSoundHeaderOffset(outHandle, &offsetCheck); - if (err != noErr || offsetCheck != kSampledSoundCommandListLength) + OSErr err = GetSoundHeaderOffset((SndListHandle) h, &offsetCheck); + if (err != noErr || offsetCheck != 2) { throw std::runtime_error("Incorrect decompressed sound header offset"); } diff --git a/src/Utilities/bigendianstreams.cpp b/src/Utilities/bigendianstreams.cpp index 786421d..5d215ae 100644 --- a/src/Utilities/bigendianstreams.cpp +++ b/src/Utilities/bigendianstreams.cpp @@ -44,11 +44,16 @@ std::vector Pomme::BigEndianIStream::ReadBytes(size_t n) return buf; } -std::string Pomme::BigEndianIStream::ReadPascalString() +std::string Pomme::BigEndianIStream::ReadPascalString(int padToAlignment) { int length = Read(); auto bytes = ReadBytes(length); bytes.push_back('\0'); + + int padding = (length + 1) % padToAlignment; + if (padding != 0) + Skip(padding); + return std::string((const char*) &bytes.data()[0]); } diff --git a/src/Utilities/bigendianstreams.h b/src/Utilities/bigendianstreams.h index bb382c6..f5becf4 100644 --- a/src/Utilities/bigendianstreams.h +++ b/src/Utilities/bigendianstreams.h @@ -37,7 +37,7 @@ namespace Pomme std::vector ReadBytes(size_t n); - std::string ReadPascalString(); + std::string ReadPascalString(int padToAlignment = 1); std::string ReadPascalString_FixedLengthRecord(const int maxChars);