Load AIFF-C files as sound resources

This commit is contained in:
Iliyas Jorio 2021-07-25 17:22:10 +02:00
parent 9c223defc2
commit 77fda7913d
7 changed files with 264 additions and 122 deletions

View File

@ -481,6 +481,9 @@ Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetT
// Pomme extension // Pomme extension
void Pomme_PauseAllChannels(Boolean pause); void Pomme_PauseAllChannels(Boolean pause);
// Pomme extension
SndListHandle Pomme_SndLoadFileAsResource(short fRefNum);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -14,8 +14,6 @@ namespace Pomme::Sound
void Shutdown(); void Shutdown();
void ReadAIFF(std::istream& input, cmixer::WavStream& output);
struct SampledSoundInfo struct SampledSoundInfo
{ {
int16_t nChannels; int16_t nChannels;
@ -100,5 +98,7 @@ namespace Pomme::Sound
void GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info); void GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info);
std::streampos GetSoundInfoFromAIFF(std::istream& input, SampledSoundInfo& info);
std::unique_ptr<Pomme::Sound::Codec> GetCodec(uint32_t fourCC); std::unique_ptr<Pomme::Sound::Codec> GetCodec(uint32_t fourCC);
} }

View File

@ -1,6 +1,7 @@
#include "PommeSound.h" #include "PommeSound.h"
#include "Utilities/bigendianstreams.h" #include "Utilities/bigendianstreams.h"
#include <cstdint> #include <cstdint>
#include <map>
static void AIFFAssert(bool condition, const char* message) 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<uint16_t>();
info.nPackets = f.Read<uint32_t>();
info.codecBitDepth = f.Read<uint16_t>();
info.sampleRate = f.Read80BitFloat();
if (isAIFC)
{
info.compressionType = f.Read<uint32_t>();
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<uint16_t, uint32_t>& markers)
{
int16_t nMarkers = f.Read<int16_t>();
for (int16_t i = 0; i < nMarkers; i++)
{
uint16_t markerID = f.Read<uint16_t>();
uint32_t markerPosition = f.Read<uint32_t>();
f.ReadPascalString(2); // skip name
markers[markerID] = markerPosition;
}
}
static void ParseINST(Pomme::BigEndianIStream& f, Pomme::Sound::SampledSoundInfo& info, std::map<uint16_t, uint32_t>& markers)
{
info.baseNote = f.Read<int8_t>();
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>();
uint16_t beginLoopMarkerID = f.Read<uint16_t>();
uint16_t endLoopMarkerID = f.Read<uint16_t>();
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); BigEndianIStream f(input);
@ -20,14 +98,15 @@ void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output)
auto formType = f.Read<uint32_t>(); auto formType = f.Read<uint32_t>();
AIFFAssert(formType == 'AIFF' || formType == 'AIFC', "AIFF: not an AIFF or AIFC file"); 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; bool gotCOMM = false;
std::streampos sampledSoundDataOffset = 0;
info = {};
info.compressionType = 'NONE';
info.isCompressed = false;
info.baseNote = 60; // Middle C
std::map<uint16_t, uint32_t> markers;
while (f.Tell() != endOfForm) while (f.Tell() != endOfForm)
{ {
@ -39,52 +118,48 @@ void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output)
{ {
case 'FVER': case 'FVER':
{ {
auto timestamp = f.Read<uint32_t>(); uint32_t timestamp = f.Read<uint32_t>();
AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER"); AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER");
break; break;
} }
case 'COMM': // common chunk, 2-85 case 'COMM': // common chunk, 2-85
{ ParseCOMM(f, info, formType=='AIFC');
nChannels = f.Read<uint16_t>();
nPackets = f.Read<uint32_t>();
bitDepth = f.Read<uint16_t>();
sampleRate = (int)f.Read80BitFloat();
if (formType == 'AIFC')
{
compressionType = f.Read<uint32_t>();
f.ReadPascalString(); // This is a human-friendly compression name. Skip it.
}
gotCOMM = true; gotCOMM = true;
break; break;
}
case 'MARK':
ParseMARK(f, markers);
break;
case 'INST':
ParseINST(f, info, markers);
break;
case 'SSND': case 'SSND':
{ {
AIFFAssert(gotCOMM, "AIFF: reached SSND before COMM"); AIFFAssert(gotCOMM, "AIFF: reached SSND before COMM");
AIFFAssert(0 == f.Read<uint64_t>(), "AIFF: unexpected offset/blockSize in SSND"); AIFFAssert(0 == f.Read<uint64_t>(), "AIFF: unexpected offset/blockSize in SSND");
// sampled sound data is here // sampled sound data is here
sampledSoundDataOffset = f.Tell();
const int ssndSize = ckSize - 8; const int ssndSize = ckSize - 8;
if (compressionType == 'NONE') info.compressedLength = ssndSize;
if (!info.isCompressed)
{ {
// Raw big-endian PCM -- just init the WavStream without decoding info.decompressedLength = info.compressedLength;
auto spanOut = output.GetBuffer(ssndSize);
f.Read(spanOut.data(), ssndSize);
output.Init(sampleRate, bitDepth, nChannels, true, spanOut);
} }
else else
{ {
auto ssnd = std::vector<char>(ssndSize); auto codec = Pomme::Sound::GetCodec(info.compressionType);
f.Read(ssnd.data(), ssndSize); info.decompressedLength = info.nChannels * info.nPackets * codec->SamplesPerPacket() * 2;
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);
} }
f.Skip(ssndSize);
break; 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"); 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;
} }

View File

@ -197,7 +197,7 @@ public:
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Internal utilities // Internal utilities
static inline ChannelImpl& GetImpl(SndChannelPtr chan) static inline ChannelImpl& GetChannelImpl(SndChannelPtr chan)
{ {
return *(ChannelImpl*) chan->channelImpl; return *(ChannelImpl*) chan->channelImpl;
} }
@ -309,7 +309,7 @@ OSErr SndDisposeChannel(SndChannelPtr macChanPtr, Boolean quietNow)
{ {
TODO2("SndDisposeChannel: quietNow == false is not implemented"); TODO2("SndDisposeChannel: quietNow == false is not implemented");
} }
delete &GetImpl(macChanPtr); delete &GetChannelImpl(macChanPtr);
return noErr; return noErr;
} }
@ -317,7 +317,7 @@ OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatu
{ {
*theStatus = {}; *theStatus = {};
auto& source = GetImpl(chan).source; auto& source = GetChannelImpl(chan).source;
theStatus->scChannelPaused = source.GetState() == cmixer::CM_STATE_PAUSED; theStatus->scChannelPaused = source.GetState() == cmixer::CM_STATE_PAUSED;
theStatus->scChannelBusy = source.GetState() == cmixer::CM_STATE_PLAYING; theStatus->scChannelBusy = source.GetState() == cmixer::CM_STATE_PLAYING;
@ -331,7 +331,8 @@ static void InstallSoundInChannel(SndChannelPtr chan, const Ptr sampledSoundHead
//--------------------------------- //---------------------------------
// Get internal channel // Get internal channel
auto& impl = GetImpl(chan);
auto& impl = GetChannelImpl(chan);
impl.Recycle(); impl.Recycle();
//--------------------------------- //---------------------------------
@ -391,7 +392,7 @@ static void InstallSoundInChannel(SndChannelPtr chan, const Ptr sampledSoundHead
OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd) 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). // Discard the high bit of the command (it indicates whether an 'snd ' resource has associated data).
switch (cmd->cmd & 0x7FFF) switch (cmd->cmd & 0x7FFF)
@ -509,13 +510,41 @@ OSErr SndStartFilePlay(
return unimpErr; return unimpErr;
} }
auto& impl = GetImpl(chan); auto& impl = GetChannelImpl(chan);
impl.Recycle(); 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 // Rewind -- the file might've been fully played already and we might just be trying to loop it
stream.seekg(0, std::ios::beg); fileStream.seekg(0, std::ios::beg);
Pomme::Sound::ReadAIFF(stream, impl.source);
// 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<char>(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) if (theCompletion)
{ {
@ -541,7 +570,7 @@ OSErr SndStartFilePlay(
OSErr SndPauseFilePlay(SndChannelPtr chan) OSErr SndPauseFilePlay(SndChannelPtr chan)
{ {
// TODO: check that chan is being used for play from disk // TODO: check that chan is being used for play from disk
GetImpl(chan).source.TogglePause(); GetChannelImpl(chan).source.TogglePause();
return noErr; return noErr;
} }
@ -550,7 +579,7 @@ OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow)
// TODO: check that chan is being used for play from disk // TODO: check that chan is being used for play from disk
if (!quietNow) if (!quietNow)
TODO2("quietNow==false not supported yet, sound will be cut off immediately instead"); TODO2("quietNow==false not supported yet, sound will be cut off immediately instead");
GetImpl(chan).source.Stop(); GetChannelImpl(chan).source.Stop();
return noErr; return noErr;
} }

View File

@ -7,25 +7,13 @@
using namespace Pomme::Sound; using namespace Pomme::Sound;
//----------------------------------------------------------------------------- enum SoundResourceType
// Cookie-cutter sound command list.
// Used to generate 'snd ' resources.
static const uint8_t kSampledSoundCommandList[20] =
{ {
0,1, // format kSoundResourceType_Standard = 0x0001,
0,1, // modifier count kSoundResourceType_HyperCard = 0x0002,
0,5, // modifier "sampled synth" kSoundResourceType_Pomme = 'po', // Pomme extension: only sampled data, no command list
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
}; };
constexpr int kSampledSoundCommandListLength = sizeof(kSampledSoundCommandList);
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// 'snd ' resource header // 'snd ' resource header
@ -37,7 +25,6 @@ struct SampledSoundHeader
SInt32 stdSH_nBytes; SInt32 stdSH_nBytes;
SInt32 cmpSH_nChannels; SInt32 cmpSH_nChannels;
SInt32 extSH_nChannels; SInt32 extSH_nChannels;
SInt32 nativeSH_nBytes;
}; };
UnsignedFixed fixedSampleRate; UnsignedFixed fixedSampleRate;
UInt32 loopStart; UInt32 loopStart;
@ -54,14 +41,14 @@ constexpr const char* kSampledSoundHeaderPackFormat = "IiIIIbb";
enum SampledSoundEncoding enum SampledSoundEncoding
{ {
stdSH = 0x00, kSampledSoundEncoding_stdSH = 0x00, // standard sound header (noncompressed 8-bit mono sample data)
nativeSH_mono16 = 0x10, // pomme extension kSampledSoundEncoding_cmpSH = 0xFE, // compressed sound header
nativeSH_stereo16 = 0x11, // pomme extension kSampledSoundEncoding_extSH = 0xFF, // extended sound header (noncompressed 8/16-bit mono or stereo)
cmpSH = 0xFE,
extSH = 0xFF,
}; };
//-----------------------------------------------------------------------------
// IM:S:2-58 "MyGetSoundHeaderOffset" // IM:S:2-58 "MyGetSoundHeaderOffset"
OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset) OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset)
{ {
memstream sndStream((Ptr) *sndHandle, GetHandleSize((Handle) sndHandle)); memstream sndStream((Ptr) *sndHandle, GetHandleSize((Handle) sndHandle));
@ -71,7 +58,7 @@ OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset)
SInt16 format = f.Read<SInt16>(); SInt16 format = f.Read<SInt16>();
switch (format) switch (format)
{ {
case 1: // Standard 'snd ' resource case kSoundResourceType_Standard:
{ {
SInt16 modifierCount = f.Read<SInt16>(); SInt16 modifierCount = f.Read<SInt16>();
SInt16 synthType = f.Read<SInt16>(); SInt16 synthType = f.Read<SInt16>();
@ -89,10 +76,15 @@ OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset)
break; break;
} }
case 2: // HyperCard sampled-sound format case kSoundResourceType_HyperCard:
f.Skip(2); // Skip reference count (for application use) f.Skip(2); // Skip reference count (for application use)
break; break;
case kSoundResourceType_Pomme:
*offset = 2;
// return now - our own sound resources just have sampled data, no commands
return noErr;
default: default:
return badFormat; return badFormat;
} }
@ -122,8 +114,22 @@ OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset)
return badFormat; return badFormat;
} }
//-----------------------------------------------------------------------------
void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info) 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. // Prep the BE reader on the header.
memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42); memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42);
Pomme::BigEndianIStream f(headerInput); Pomme::BigEndianIStream f(headerInput);
@ -149,7 +155,7 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info)
switch (header.encoding) 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.compressionType = 'raw '; // unsigned (in AIFF-C files, 'NONE' means signed!)
info.isCompressed = false; info.isCompressed = false;
info.bigEndian = false; info.bigEndian = false;
@ -161,25 +167,12 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info)
info.decompressedLength = info.compressedLength; info.decompressedLength = info.compressedLength;
break; break;
case nativeSH_mono16: // pomme extension for little-endian PCM data case kSampledSoundEncoding_cmpSH:
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
{ {
info.nPackets = f.Read<int32_t>(); info.nPackets = f.Read<int32_t>();
f.Skip(14); f.Skip(14); // skip AIFFSampleRate(10), markerChunk(4)
info.compressionType = f.Read<uint32_t>(); info.compressionType = f.Read<uint32_t>();
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 if (info.compressionType == 0) // Assume MACE-3
{ {
@ -202,12 +195,12 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info)
break; break;
} }
case 0xFF: // extSH - extended sound header (noncompressed 8/16-bit mono or stereo) case kSampledSoundEncoding_extSH:
{ {
info.nPackets = f.Read<int32_t>(); info.nPackets = f.Read<int32_t>();
f.Skip(22); f.Skip(22); // skip AIFFSampleRate(10), markerChunk(4), instrumentChunks(10), AESRecording(4)
info.codecBitDepth = f.Read<int16_t>(); info.codecBitDepth = f.Read<int16_t>();
f.Skip(14); f.Skip(14); // skip futureUse1(2), futureUse2(4), futureUse3(4), futureUse4(4)
info.isCompressed = false; info.isCompressed = false;
info.bigEndian = true; info.bigEndian = true;
@ -227,6 +220,8 @@ void Pomme::Sound::GetSoundInfo(const Ptr sndhdr, SampledSoundInfo& info)
} }
} }
//-----------------------------------------------------------------------------
void Pomme::Sound::GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info) void Pomme::Sound::GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info)
{ {
long offsetToHeader; long offsetToHeader;
@ -238,56 +233,82 @@ void Pomme::Sound::GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInf
GetSoundInfo(sndhdr, info); 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 // Extension: decompress
Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader) Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader)
{ {
SampledSoundInfo info; SampledSoundInfo inInfo;
GetSoundInfoFromSndResource((Handle) *sndHandlePtr, info); GetSoundInfoFromSndResource((Handle) *sndHandlePtr, inInfo);
// We only handle cmpSH (compressed) 'snd ' resources. if (!inInfo.dataStart)
if (!info.isCompressed)
{ {
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<Pomme::Sound::Codec> codec = Pomme::Sound::GetCodec(info.compressionType); SampledSoundInfo outInfo = inInfo;
outInfo.dataStart = nullptr;
outInfo.isCompressed = false;
outInfo.compressedLength = outInfo.decompressedLength;
// Decompress if (!inInfo.isCompressed)
SndListHandle outHandle = (SndListHandle) NewHandle(outInitialSize + info.decompressedLength); {
auto spanIn = std::span(info.dataStart, info.compressedLength); // Raw big-endian PCM
auto spanOut = std::span((char*) *outHandle + outInitialSize, info.decompressedLength); if (inInfo.decompressedLength != inInfo.compressedLength)
codec->Decode(info.nChannels, spanIn, spanOut); throw std::runtime_error("decompressedLength != compressedLength???");
// ------------------------------------------------------ memcpy(outDataStart, inDataStart, inInfo.decompressedLength);
// Now we have the PCM data. }
// Put the output 'snd ' resource together. 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 = {}; outInfo.compressionType = 'swot';
shOut.zero = 0; outInfo.bigEndian = false;
shOut.nativeSH_nBytes = info.decompressedLength; outInfo.codecBitDepth = 16;
shOut.fixedSampleRate = static_cast<UnsignedFixed>(info.sampleRate * 65536.0); outInfo.nPackets = codec->SamplesPerPacket() * inInfo.nPackets;
shOut.loopStart = info.loopStart; }
shOut.loopEnd = info.loopEnd;
shOut.encoding = info.nChannels == 2 ? nativeSH_stereo16 : nativeSH_mono16;
shOut.baseFrequency = info.baseNote;
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&shOut)); // Write header
memcpy(*h, "poPOMM", 6);
memcpy(*outHandle, kSampledSoundCommandList, kSampledSoundCommandListLength); memcpy(*h+6, &outInfo, sizeof(SampledSoundInfo));
memcpy((char*) *outHandle + kSampledSoundCommandListLength, &shOut, kSampledSoundHeaderLength);
// Nuke compressed sound handle, replace it with the decopmressed one we've just created // Nuke compressed sound handle, replace it with the decopmressed one we've just created
DisposeHandle((Handle) *sndHandlePtr); DisposeHandle((Handle) *sndHandlePtr);
*sndHandlePtr = outHandle; *sndHandlePtr = (SndListHandle) h;
*offsetToHeader = kSampledSoundCommandListLength; *offsetToHeader = 2;
// Check offset
long offsetCheck = 0; long offsetCheck = 0;
OSErr err = GetSoundHeaderOffset(outHandle, &offsetCheck); OSErr err = GetSoundHeaderOffset((SndListHandle) h, &offsetCheck);
if (err != noErr || offsetCheck != kSampledSoundCommandListLength) if (err != noErr || offsetCheck != 2)
{ {
throw std::runtime_error("Incorrect decompressed sound header offset"); throw std::runtime_error("Incorrect decompressed sound header offset");
} }

View File

@ -44,11 +44,16 @@ std::vector<unsigned char> Pomme::BigEndianIStream::ReadBytes(size_t n)
return buf; return buf;
} }
std::string Pomme::BigEndianIStream::ReadPascalString() std::string Pomme::BigEndianIStream::ReadPascalString(int padToAlignment)
{ {
int length = Read<uint8_t>(); int length = Read<uint8_t>();
auto bytes = ReadBytes(length); auto bytes = ReadBytes(length);
bytes.push_back('\0'); bytes.push_back('\0');
int padding = (length + 1) % padToAlignment;
if (padding != 0)
Skip(padding);
return std::string((const char*) &bytes.data()[0]); return std::string((const char*) &bytes.data()[0]);
} }

View File

@ -37,7 +37,7 @@ namespace Pomme
std::vector<unsigned char> ReadBytes(size_t n); std::vector<unsigned char> ReadBytes(size_t n);
std::string ReadPascalString(); std::string ReadPascalString(int padToAlignment = 1);
std::string ReadPascalString_FixedLengthRecord(const int maxChars); std::string ReadPascalString_FixedLengthRecord(const int maxChars);