mirror of
https://github.com/AxioDL/amuse.git
synced 2025-10-04 08:59:48 +00:00
280 lines
10 KiB
C++
280 lines
10 KiB
C++
#ifndef __AMUSE_AUDIOGROUPSAMPLEDIR_HPP__
|
|
#define __AMUSE_AUDIOGROUPSAMPLEDIR_HPP__
|
|
|
|
#include <unordered_map>
|
|
#include <cstdint>
|
|
#include "Common.hpp"
|
|
|
|
namespace amuse
|
|
{
|
|
class AudioGroupData;
|
|
|
|
struct DSPADPCMHeader : BigDNA
|
|
{
|
|
AT_DECL_DNA
|
|
Value<atUint32> x0_num_samples;
|
|
Value<atUint32> x4_num_nibbles;
|
|
Value<atUint32> x8_sample_rate;
|
|
Value<atUint16> xc_loop_flag;
|
|
Value<atUint16> xe_format = 0; /* 0 for ADPCM */
|
|
Value<atUint32> x10_loop_start_nibble = 0;
|
|
Value<atUint32> x14_loop_end_nibble = 0;
|
|
Value<atUint32> x18_ca = 0;
|
|
Value<atInt16> x1c_coef[8][2];
|
|
Value<atInt16> x3c_gain = 0;
|
|
Value<atInt16> x3e_ps;
|
|
Value<atInt16> x40_hist1;
|
|
Value<atInt16> x42_hist2;
|
|
Value<atInt16> x44_loop_ps;
|
|
Value<atInt16> x46_loop_hist1 = 0;
|
|
Value<atInt16> x48_loop_hist2 = 0;
|
|
Value<atUint8> m_pitch = 0; // Stash this in the padding
|
|
Seek<21, athena::Current> pad;
|
|
};
|
|
|
|
struct WAVFormatChunk : LittleDNA
|
|
{
|
|
AT_DECL_DNA
|
|
Value<atUint16> sampleFmt = 1;
|
|
Value<atUint16> numChannels = 1;
|
|
Value<atUint32> sampleRate;
|
|
Value<atUint32> byteRate; // sampleRate * numChannels * bitsPerSample/8
|
|
Value<atUint16> blockAlign = 2; // numChannels * bitsPerSample/8
|
|
Value<atUint16> bitsPerSample = 16;
|
|
};
|
|
|
|
struct WAVSampleChunk : LittleDNA
|
|
{
|
|
AT_DECL_DNA
|
|
Value<atUint32> smplManufacturer = 0;
|
|
Value<atUint32> smplProduct = 0;
|
|
Value<atUint32> smplPeriod; // 1 / sampleRate in nanoseconds
|
|
Value<atUint32> midiNote; // native MIDI note of sample
|
|
Value<atUint32> midiPitchFrac = 0;
|
|
Value<atUint32> smpteFormat = 0;
|
|
Value<atUint32> smpteOffset = 0;
|
|
Value<atUint32> numSampleLoops = 0;
|
|
Value<atUint32> additionalDataSize = 0;
|
|
};
|
|
|
|
struct WAVSampleLoop : LittleDNA
|
|
{
|
|
AT_DECL_DNA
|
|
Value<atUint32> cuePointId = 0;
|
|
Value<atUint32> loopType = 0; // 0: forward loop
|
|
Value<atUint32> start; // in bytes
|
|
Value<atUint32> end; // in bytes
|
|
Value<atUint32> fraction = 0;
|
|
Value<atUint32> playCount = 0;
|
|
};
|
|
|
|
struct WAVHeader : LittleDNA
|
|
{
|
|
AT_DECL_DNA
|
|
Value<atUint32> riffMagic = SBIG('RIFF');
|
|
Value<atUint32> wavChuckSize; // everything - 8
|
|
Value<atUint32> wavMagic = SBIG('WAVE');
|
|
|
|
Value<atUint32> fmtMagic = SBIG('fmt ');
|
|
Value<atUint32> fmtChunkSize = 16;
|
|
WAVFormatChunk fmtChunk;
|
|
|
|
Value<atUint32> smplMagic = SBIG('smpl');
|
|
Value<atUint32> smplChunkSize = 36; // 36 + numSampleLoops*24
|
|
WAVSampleChunk smplChunk;
|
|
|
|
Value<atUint32> dataMagic = SBIG('data');
|
|
Value<atUint32> dataChunkSize; // numSamples * numChannels * bitsPerSample/8
|
|
};
|
|
|
|
struct WAVHeaderLoop : LittleDNA
|
|
{
|
|
AT_DECL_DNA
|
|
Value<atUint32> riffMagic = SBIG('RIFF');
|
|
Value<atUint32> wavChuckSize; // everything - 8
|
|
Value<atUint32> wavMagic = SBIG('WAVE');
|
|
|
|
Value<atUint32> fmtMagic = SBIG('fmt ');
|
|
Value<atUint32> fmtChunkSize = 16;
|
|
WAVFormatChunk fmtChunk;
|
|
|
|
Value<atUint32> smplMagic = SBIG('smpl');
|
|
Value<atUint32> smplChunkSize = 60; // 36 + numSampleLoops*24
|
|
WAVSampleChunk smplChunk;
|
|
WAVSampleLoop sampleLoop;
|
|
|
|
Value<atUint32> dataMagic = SBIG('data');
|
|
Value<atUint32> dataChunkSize; // numSamples * numChannels * bitsPerSample/8
|
|
};
|
|
|
|
enum class SampleFormat : uint8_t
|
|
{
|
|
DSP, /**< GCN DSP-ucode ADPCM (very common for GameCube games) */
|
|
DSP_DRUM, /**< GCN DSP-ucode ADPCM (seems to be set into drum samples for expanding their amplitude appropriately) */
|
|
PCM, /**< Big-endian PCM found in MusyX2 demo GM instruments */
|
|
N64, /**< 2-stage VADPCM coding with SAMP-embedded codebooks */
|
|
PCM_PC /**< Little-endian PCM found in PC Rogue Squadron (actually enum 0 which conflicts with DSP-ADPCM) */
|
|
};
|
|
|
|
/** Indexes individual samples in SAMP chunk */
|
|
class AudioGroupSampleDirectory
|
|
{
|
|
friend class AudioGroup;
|
|
|
|
public:
|
|
union ADPCMParms {
|
|
struct DSPParms
|
|
{
|
|
uint16_t m_bytesPerFrame;
|
|
uint8_t m_ps;
|
|
uint8_t m_lps;
|
|
int16_t m_hist2;
|
|
int16_t m_hist1;
|
|
int16_t m_coefs[8][2];
|
|
} dsp;
|
|
struct VADPCMParms
|
|
{
|
|
int16_t m_coefs[8][2][8];
|
|
} vadpcm;
|
|
void swapBigDSP();
|
|
void swapBigVADPCM();
|
|
};
|
|
|
|
template <athena::Endian DNAEn>
|
|
struct AT_SPECIALIZE_PARMS(athena::Endian::Big, athena::Endian::Little)
|
|
EntryDNA : BigDNA
|
|
{
|
|
AT_DECL_DNA
|
|
SampleIdDNA<DNAEn> m_sfxId;
|
|
Seek<2, athena::Current> pad;
|
|
Value<atUint32, DNAEn> m_sampleOff;
|
|
Value<atUint32, DNAEn> m_unk;
|
|
Value<atUint8, DNAEn> m_pitch;
|
|
Seek<1, athena::Current> pad2;
|
|
Value<atUint16, DNAEn> m_sampleRate;
|
|
Value<atUint32, DNAEn> m_numSamples; // Top 8 bits is SampleFormat
|
|
Value<atUint32, DNAEn> m_loopStartSample;
|
|
Value<atUint32, DNAEn> m_loopLengthSamples;
|
|
Value<atUint32, DNAEn> m_adpcmParmOffset;
|
|
};
|
|
template <athena::Endian DNAEn>
|
|
struct AT_SPECIALIZE_PARMS(athena::Endian::Big, athena::Endian::Little)
|
|
MusyX1SdirEntry : BigDNA
|
|
{
|
|
AT_DECL_DNA
|
|
SampleIdDNA<DNAEn> m_sfxId;
|
|
Seek<2, athena::Current> pad;
|
|
Value<atUint32, DNAEn> m_sampleOff;
|
|
Value<atUint32, DNAEn> m_pitchSampleRate;
|
|
Value<atUint32, DNAEn> m_numSamples;
|
|
Value<atUint32, DNAEn> m_loopStartSample;
|
|
Value<atUint32, DNAEn> m_loopLengthSamples;
|
|
};
|
|
template <athena::Endian DNAEn>
|
|
struct AT_SPECIALIZE_PARMS(athena::Endian::Big, athena::Endian::Little)
|
|
MusyX1AbsSdirEntry : BigDNA
|
|
{
|
|
AT_DECL_DNA
|
|
SampleIdDNA<DNAEn> m_sfxId;
|
|
Seek<2, athena::Current> pad;
|
|
Value<uint32_t, DNAEn> m_sampleOff;
|
|
Value<uint32_t, DNAEn> m_unk;
|
|
Value<uint32_t, DNAEn> m_pitchSampleRate;
|
|
Value<uint32_t, DNAEn> m_numSamples;
|
|
Value<uint32_t, DNAEn> m_loopStartSample;
|
|
Value<uint32_t, DNAEn> m_loopLengthSamples;
|
|
};
|
|
struct Entry
|
|
{
|
|
atUint32 m_sampleOff = 0;
|
|
atUint32 m_unk = 0;
|
|
atUint8 m_pitch = 0;
|
|
atUint16 m_sampleRate = 0;
|
|
atUint32 m_numSamples = 0; // Top 8 bits is SampleFormat
|
|
atUint32 m_loopStartSample = 0;
|
|
atUint32 m_loopLengthSamples = 0;
|
|
atUint32 m_adpcmParmOffset = 0;
|
|
|
|
/* Stored out-of-band in a platform-dependent way */
|
|
ADPCMParms m_ADPCMParms;
|
|
|
|
/* In-memory storage of an individual sample. Editors use this structure
|
|
* to override the loaded sample with a file-backed version without repacking
|
|
* the sample data into a SAMP block. */
|
|
time_t m_looseModTime = 0;
|
|
std::unique_ptr<uint8_t[]> m_looseData;
|
|
|
|
Entry() = default;
|
|
|
|
template <athena::Endian DNAE>
|
|
Entry(const EntryDNA<DNAE>& in)
|
|
: m_sampleOff(in.m_sampleOff), m_unk(in.m_unk), m_pitch(in.m_pitch),
|
|
m_sampleRate(in.m_sampleRate), m_numSamples(in.m_numSamples),
|
|
m_loopStartSample(in.m_loopStartSample), m_loopLengthSamples(in.m_loopLengthSamples),
|
|
m_adpcmParmOffset(in.m_adpcmParmOffset) {}
|
|
|
|
template <athena::Endian DNAE>
|
|
Entry(const MusyX1SdirEntry<DNAE>& in)
|
|
: m_sampleOff(in.m_sampleOff), m_unk(0), m_pitch(in.m_pitchSampleRate >> 24),
|
|
m_sampleRate(in.m_pitchSampleRate & 0xffff), m_numSamples(in.m_numSamples),
|
|
m_loopStartSample(in.m_loopStartSample), m_loopLengthSamples(in.m_loopLengthSamples),
|
|
m_adpcmParmOffset(0) {}
|
|
|
|
template <athena::Endian DNAE>
|
|
Entry(const MusyX1AbsSdirEntry<DNAE>& in)
|
|
: m_sampleOff(in.m_sampleOff), m_unk(in.m_unk), m_pitch(in.m_pitchSampleRate >> 24),
|
|
m_sampleRate(in.m_pitchSampleRate & 0xffff), m_numSamples(in.m_numSamples),
|
|
m_loopStartSample(in.m_loopStartSample), m_loopLengthSamples(in.m_loopLengthSamples),
|
|
m_adpcmParmOffset(0) {}
|
|
|
|
template <athena::Endian DNAEn>
|
|
EntryDNA<DNAEn> toDNA(SFXId id) const
|
|
{
|
|
EntryDNA<DNAEn> ret;
|
|
ret.m_sfxId.id = id;
|
|
ret.m_sampleOff = m_sampleOff;
|
|
ret.m_unk = m_unk;
|
|
ret.m_pitch = m_pitch;
|
|
ret.m_sampleRate = m_sampleRate;
|
|
ret.m_numSamples = m_numSamples;
|
|
ret.m_loopStartSample = m_loopStartSample;
|
|
ret.m_loopLengthSamples = m_loopLengthSamples;
|
|
ret.m_adpcmParmOffset = m_adpcmParmOffset;
|
|
return ret;
|
|
}
|
|
|
|
void loadLooseData(SystemStringView basePath);
|
|
};
|
|
|
|
private:
|
|
std::unordered_map<SampleId, Entry> m_entries;
|
|
static void _extractWAV(SampleId id, const Entry& ent, amuse::SystemStringView destDir,
|
|
const unsigned char* samp);
|
|
static void _extractCompressed(SampleId id, const Entry& ent, amuse::SystemStringView destDir,
|
|
const unsigned char* samp);
|
|
|
|
AudioGroupSampleDirectory() = default;
|
|
|
|
public:
|
|
AudioGroupSampleDirectory(athena::io::IStreamReader& r, GCNDataTag);
|
|
AudioGroupSampleDirectory(athena::io::IStreamReader& r, const unsigned char* sampData, bool absOffs, N64DataTag);
|
|
AudioGroupSampleDirectory(athena::io::IStreamReader& r, bool absOffs, PCDataTag);
|
|
static AudioGroupSampleDirectory CreateAudioGroupSampleDirectory(const AudioGroupData& data);
|
|
static AudioGroupSampleDirectory CreateAudioGroupSampleDirectory(SystemStringView groupPath);
|
|
|
|
const std::unordered_map<SampleId, Entry>& sampleEntries() const { return m_entries; }
|
|
|
|
void extractWAV(SampleId id, amuse::SystemStringView destDir, const unsigned char* samp) const;
|
|
void extractAllWAV(amuse::SystemStringView destDir, const unsigned char* samp) const;
|
|
void extractCompressed(SampleId id, amuse::SystemStringView destDir, const unsigned char* samp) const;
|
|
void extractAllCompressed(amuse::SystemStringView destDir, const unsigned char* samp) const;
|
|
|
|
AudioGroupSampleDirectory(const AudioGroupSampleDirectory&) = delete;
|
|
AudioGroupSampleDirectory& operator=(const AudioGroupSampleDirectory&) = delete;
|
|
AudioGroupSampleDirectory(AudioGroupSampleDirectory&&) = default;
|
|
AudioGroupSampleDirectory& operator=(AudioGroupSampleDirectory&&) = default;
|
|
};
|
|
}
|
|
|
|
#endif // __AMUSE_AUDIOGROUPSAMPLEDIR_HPP__
|