mirror of
https://github.com/jorio/Pomme.git
synced 2024-12-27 21:31:47 +00:00
Load AIFF-C files as sound resources
This commit is contained in:
parent
9c223defc2
commit
77fda7913d
@ -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
|
||||
|
@ -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<Pomme::Sound::Codec> GetCodec(uint32_t fourCC);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "PommeSound.h"
|
||||
#include "Utilities/bigendianstreams.h"
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
|
||||
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);
|
||||
|
||||
@ -20,14 +98,15 @@ void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output)
|
||||
auto formType = f.Read<uint32_t>();
|
||||
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<uint16_t, uint32_t> 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>();
|
||||
uint32_t timestamp = f.Read<uint32_t>();
|
||||
AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER");
|
||||
break;
|
||||
}
|
||||
|
||||
case 'COMM': // common chunk, 2-85
|
||||
{
|
||||
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.
|
||||
}
|
||||
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<uint64_t>(), "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<char>(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;
|
||||
}
|
||||
|
||||
|
@ -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<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)
|
||||
{
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<SInt16>();
|
||||
switch (format)
|
||||
{
|
||||
case 1: // Standard 'snd ' resource
|
||||
case kSoundResourceType_Standard:
|
||||
{
|
||||
SInt16 modifierCount = f.Read<SInt16>();
|
||||
SInt16 synthType = f.Read<SInt16>();
|
||||
@ -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<int32_t>();
|
||||
f.Skip(14);
|
||||
f.Skip(14); // skip AIFFSampleRate(10), markerChunk(4)
|
||||
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
|
||||
{
|
||||
@ -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<int32_t>();
|
||||
f.Skip(22);
|
||||
f.Skip(22); // skip AIFFSampleRate(10), markerChunk(4), instrumentChunks(10), AESRecording(4)
|
||||
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.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<Pomme::Sound::Codec> 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<UnsignedFixed>(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<char*>(&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");
|
||||
}
|
||||
|
@ -44,11 +44,16 @@ std::vector<unsigned char> Pomme::BigEndianIStream::ReadBytes(size_t n)
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string Pomme::BigEndianIStream::ReadPascalString()
|
||||
std::string Pomme::BigEndianIStream::ReadPascalString(int padToAlignment)
|
||||
{
|
||||
int length = Read<uint8_t>();
|
||||
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]);
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ namespace Pomme
|
||||
|
||||
std::vector<unsigned char> ReadBytes(size_t n);
|
||||
|
||||
std::string ReadPascalString();
|
||||
std::string ReadPascalString(int padToAlignment = 1);
|
||||
|
||||
std::string ReadPascalString_FixedLengthRecord(const int maxChars);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user