mirror of https://github.com/AxioDL/amuse.git
355 lines
12 KiB
C++
355 lines
12 KiB
C++
#pragma once
|
|
|
|
#include <unordered_map>
|
|
#include <cstdint>
|
|
#include "Common.hpp"
|
|
|
|
namespace amuse {
|
|
class AudioGroupData;
|
|
class AudioGroupDatabase;
|
|
|
|
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 = 0;
|
|
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 = 0;
|
|
Value<atInt16> x40_hist1 = 0;
|
|
Value<atInt16> x42_hist2 = 0;
|
|
Value<atInt16> x44_loop_ps = 0;
|
|
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 VADPCMHeader : BigDNA {
|
|
AT_DECL_DNA
|
|
Value<uint32_t> m_pitchSampleRate;
|
|
Value<uint32_t> m_numSamples;
|
|
Value<uint32_t> m_loopStartSample;
|
|
Value<uint32_t> m_loopLengthSamples;
|
|
};
|
|
|
|
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;
|
|
Value<atUint32> end;
|
|
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) */
|
|
};
|
|
|
|
enum class SampleFileState {
|
|
NoData,
|
|
MemoryOnlyWAV,
|
|
MemoryOnlyCompressed,
|
|
WAVRecent,
|
|
CompressedRecent,
|
|
CompressedNoWAV,
|
|
WAVNoCompressed
|
|
};
|
|
|
|
/** 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;
|
|
|
|
void _setLoopStartSample(atUint32 sample) {
|
|
m_loopLengthSamples += m_loopStartSample - sample;
|
|
m_loopStartSample = sample;
|
|
}
|
|
void setLoopEndSample(atUint32 sample) { m_loopLengthSamples = sample + 1 - m_loopStartSample; }
|
|
};
|
|
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 EntryData {
|
|
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;
|
|
|
|
/* Use middle C when pitch is (impossibly low) default */
|
|
atUint8 getPitch() const { return m_pitch == 0 ? atUint8(60) : m_pitch; }
|
|
atUint32 getNumSamples() const { return m_numSamples & 0xffffff; }
|
|
SampleFormat getSampleFormat() const { return SampleFormat(m_numSamples >> 24); }
|
|
bool isFormatDSP() const {
|
|
SampleFormat fmt = getSampleFormat();
|
|
return fmt == SampleFormat::DSP || fmt == SampleFormat::DSP_DRUM;
|
|
}
|
|
|
|
bool isLooped() const { return m_loopLengthSamples != 0 && m_loopStartSample != 0xffffffff; }
|
|
|
|
void _setLoopStartSample(atUint32 sample) {
|
|
m_loopLengthSamples += m_loopStartSample - sample;
|
|
m_loopStartSample = sample;
|
|
}
|
|
void setLoopStartSample(atUint32 sample);
|
|
atUint32 getLoopStartSample() const { return m_loopStartSample; }
|
|
void setLoopEndSample(atUint32 sample) { m_loopLengthSamples = sample + 1 - m_loopStartSample; }
|
|
atUint32 getLoopEndSample() const { return m_loopStartSample + m_loopLengthSamples - 1; }
|
|
|
|
EntryData() = default;
|
|
|
|
template <athena::Endian DNAE>
|
|
EntryData(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>
|
|
EntryData(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>
|
|
EntryData(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 loadLooseDSP(SystemStringView dspPath);
|
|
void loadLooseVADPCM(SystemStringView vadpcmPath);
|
|
void loadLooseWAV(SystemStringView wavPath);
|
|
|
|
void patchMetadataDSP(SystemStringView dspPath);
|
|
void patchMetadataVADPCM(SystemStringView vadpcmPath);
|
|
void patchMetadataWAV(SystemStringView wavPath);
|
|
};
|
|
/* This double-wrapper allows Voices to keep a strong reference on
|
|
* a single instance of loaded loose data without being unexpectedly
|
|
* clobbered */
|
|
struct Entry {
|
|
ObjToken<EntryData> m_data;
|
|
|
|
Entry() : m_data(MakeObj<EntryData>()) {}
|
|
|
|
template <athena::Endian DNAE>
|
|
Entry(const EntryDNA<DNAE>& in) : m_data(MakeObj<EntryData>(in)) {}
|
|
|
|
template <athena::Endian DNAE>
|
|
Entry(const MusyX1SdirEntry<DNAE>& in) : m_data(MakeObj<EntryData>(in)) {}
|
|
|
|
template <athena::Endian DNAE>
|
|
Entry(const MusyX1AbsSdirEntry<DNAE>& in) : m_data(MakeObj<EntryData>(in)) {}
|
|
|
|
template <athena::Endian DNAEn>
|
|
EntryDNA<DNAEn> toDNA(SFXId id) const {
|
|
return m_data->toDNA<DNAEn>(id);
|
|
}
|
|
|
|
void loadLooseData(SystemStringView basePath);
|
|
SampleFileState getFileState(SystemStringView basePath, SystemString* pathOut = nullptr) const;
|
|
void patchSampleMetadata(SystemStringView basePath) const;
|
|
};
|
|
|
|
private:
|
|
std::unordered_map<SampleId, ObjToken<Entry>> m_entries;
|
|
static void _extractWAV(SampleId id, const EntryData& ent, amuse::SystemStringView destDir,
|
|
const unsigned char* samp);
|
|
static void _extractCompressed(SampleId id, const EntryData& ent, amuse::SystemStringView destDir,
|
|
const unsigned char* samp, bool compressWAV = false);
|
|
|
|
public:
|
|
AudioGroupSampleDirectory() = default;
|
|
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, ObjToken<Entry>>& sampleEntries() const { return m_entries; }
|
|
std::unordered_map<SampleId, ObjToken<Entry>>& sampleEntries() { 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;
|
|
|
|
void reloadSampleData(SystemStringView groupPath);
|
|
|
|
std::pair<std::vector<uint8_t>, std::vector<uint8_t>> toGCNData(const AudioGroupDatabase& group) const;
|
|
|
|
AudioGroupSampleDirectory(const AudioGroupSampleDirectory&) = delete;
|
|
AudioGroupSampleDirectory& operator=(const AudioGroupSampleDirectory&) = delete;
|
|
AudioGroupSampleDirectory(AudioGroupSampleDirectory&&) = default;
|
|
AudioGroupSampleDirectory& operator=(AudioGroupSampleDirectory&&) = default;
|
|
};
|
|
|
|
using SampleEntry = AudioGroupSampleDirectory::Entry;
|
|
using SampleEntryData = AudioGroupSampleDirectory::EntryData;
|
|
} // namespace amuse
|