diff --git a/docs/audio/Soundfont_XML.md b/docs/audio/Soundfont_XML.md index c2d8a572ed..5e532c1206 100644 --- a/docs/audio/Soundfont_XML.md +++ b/docs/audio/Soundfont_XML.md @@ -147,6 +147,7 @@ Begins a new soundfont. Name="" SampleRate="[Sample Rate]" BaseNote="[Note Name]" + FineTune="[Fine Tune]" IsDD="[Bool]" Cached="[Bool]" /> @@ -158,6 +159,7 @@ Begins a new soundfont. - **Name**: The name of this sample. A sample with this name must be present in the samplebank used by the soundfont. - [Optional] **SampleRate**: An overriding sample rate for this sample. **Default comes from the sample file.** - [Optional] **BaseNote**: An overriding root key for this sample. **Default comes from the sample file.** + - [Optional] **FineTune**: An overriding fine tuning for this sample, in cents, in the range [-100, 100]. **Default comes from the sample file.** - [Optional] **IsDD**: Whether this sample is on the Disk Drive. The sample data will come from the samplebank `SampleBankDD`. **Default is `false`.** **NOTE this is not fully implemented, it should always be `false`.** - [Optional] **Cached**: Whether this sample should be added to the `usedSamples` cache. **Default is `false`.** @@ -187,6 +189,7 @@ Begins a new soundfont. Sample="" SampleRate="[Sample Rate]" BaseNote="[Note Name]" + FineTune="[Fine Tune]" /> ``` Defines a single sound effect. @@ -196,6 +199,7 @@ Begins a new soundfont. - **Sample**: The name of the sample associated with this effect. - [Optional] **SampleRate**: An overriding sample rate for this effect. **Default comes from the sample definition.** - [Optional] **BaseNote**: An overriding root key for this effect. **Default comes from the sample definition.** + - [Optional] **FineTune**: An overriding fine tuning for this effect, in cents, in the range [-100, 100]. **Default comes from the sample definition.** --- @@ -229,6 +233,7 @@ Begins a new soundfont. Sample="" SampleRate="[Sample Rate]" BaseNote="[Note Name]" + FineTune="[Fine Tune]" /> ``` Defines a single percussion range. @@ -244,6 +249,7 @@ Begins a new soundfont. - **Sample**: The name of the sample to use. - [Optional] **SampleRate**: An overriding sample rate for this sound. **Default comes from the sample definition.** - [Optional] **BaseNote**: An overriding root key for this sound. **Default comes from the sample definition.** + - [Optional] **FineTune**: An overriding fine tuning for this sound, in cents, in the range [-100, 100]. **Default comes from the sample definition.** --- @@ -275,16 +281,19 @@ Begins a new soundfont. Sample="" SampleRate="[Sample Rate]" BaseNote="[Note Name]" + FineTune="[Fine Tune]" RangeLo="[Note Name]" SampleLo="[Sample Name]" SampleRateLo="[Sample Rate]" BaseNoteLo="[Note Name]" + FineTuneLo="[Fine Tune]" RangeHi="[Note Name]" SampleHi="[Sample Name]" SampleRateHi="[Sample Rate]" BaseNoteHi="[Note Name]" + FineTuneHi="[Fine Tune]" /> ``` Defines an instrument. @@ -297,14 +306,17 @@ Begins a new soundfont. - **Sample**: The name of the middle sample to use for this instrument. - [Optional] **SampleRate**: Sample rate override for the middle sample. **Default is sourced from the sample properties.** - [Optional] **BaseNote**: Base note override for the middle sample. **Default is sourced from the sample properties.** + - [Optional] **FineTune**: Fine tuning override for the middle sample, in cents, in the range [-100, 100]. **Default is sourced from the sample properties.** - [Optional] **RangeLo**: The largest note for SampleLo. SampleLo will be used instead of Sample for keys in the range [0, RangeLo]. **If left unspecified, SampleLo must not be specified. If specified, SampleLo must be specified.** - [Optional] **SampleLo**: The name of the low sample to use for this instrument. - [Optional] **SampleRateLo**: Sample rate override for the low sample. **Default is sourced from the sample properties.** - [Optional] **BaseNoteLo**: Base note override for the low sample. **Default is sourced from the sample properties.** + - [Optional] **FineTuneLo**: Fine tuning override for the low sample, in cents, in the range [-100, 100]. **Default is sourced from the sample properties.** - [Optional] **RangeHi**: The smallest note for SampleHi. SampleHi will be used instead of Sample for keys in the range [RangeHi, 127]. **If left unspecified, SampleHi must not be specified. If specified, SampleHi must be specified.** - [Optional] **SampleHi**: The name of the high sample to use for this instrument. - [Optional] **SampleRateHi**: Sample rate override for the high sample. **Default is sourced from the sample properties.** - [Optional] **BaseNoteHi**: Base note override for the high sample. **Default is sourced from the sample properties.** + - [Optional] **FineTuneHi**: Fine tuning override for the high sample, in cents, in the range [-100, 100]. **Default is sourced from the sample properties.** --- diff --git a/tools/audio/soundfont.h b/tools/audio/soundfont.h index eeed712de2..28bf8fac5f 100644 --- a/tools/audio/soundfont.h +++ b/tools/audio/soundfont.h @@ -29,6 +29,7 @@ typedef struct sample_data { const char *name; double sample_rate; int8_t base_note; + int8_t fine_tune; bool is_dd; bool cached; aifc_data aifc; @@ -66,6 +67,9 @@ typedef struct instr_data { int8_t base_note_mid; int8_t base_note_lo; int8_t base_note_hi; + int8_t fine_tune_mid; + int8_t fine_tune_lo; + int8_t fine_tune_hi; envelope_data *envelope; uint16_t release; @@ -97,6 +101,7 @@ typedef struct drum_data { sample_data *sample; double sample_rate; int8_t base_note; + int8_t fine_tune; } drum_data; typedef struct sfx_data { @@ -107,6 +112,7 @@ typedef struct sfx_data { sample_data *sample; double sample_rate; int8_t base_note; + int8_t fine_tune; float tuning; } sfx_data; @@ -155,8 +161,9 @@ typedef struct { size_t match_padding_num; } soundfont; -#define NOTE_UNSET (INT8_MIN) -#define RELEASE_UNSET (UINT16_MAX) +#define NOTE_UNSET (INT8_MIN) +#define FINE_TUNE_UNSET (INT8_MIN) +#define RELEASE_UNSET (UINT16_MAX) envelope_data * sf_get_envelope(soundfont *sf, const char *name); diff --git a/tools/audio/soundfont_compiler.c b/tools/audio/soundfont_compiler.c index 4f9a5779c4..6a47d643b8 100644 --- a/tools/audio/soundfont_compiler.c +++ b/tools/audio/soundfont_compiler.c @@ -64,7 +64,7 @@ midinote_to_z64note(int note) * (with appropriate shifting such that the index for C4 results in 1.0) */ static float -calc_tuning(float sample_rate, int basenote) +calc_tuning(float sample_rate, int basenote, int8_t finetune) { static const float playback_sample_rate = 32000.0f; // Target samplerate in-game is 32KHz static const float pitch_frequencies[] = { @@ -199,7 +199,24 @@ calc_tuning(float sample_rate, int basenote) /* 0x7F */ 0.099213f, // PITCH_AF0 }; - return (sample_rate / playback_sample_rate) * pitch_frequencies[basenote]; + float tuning = (sample_rate / playback_sample_rate) * pitch_frequencies[basenote]; + if (finetune == 0) + return tuning; + + // Compute 2^(x / (12 * 100)) for 8-bit signed integer x + static const double fine_coeffs[5] = { + // Coefficients generated by sollya: + // fpminimax(1.00057778950655486^x, 4, [|SG...|], [-128,128], absolute, 2^(-24)); + // <= 1ulp + 1.0, + 5.776226171292365e-4, + 1.668239519858616e-7, + 3.213055863038328e-11, + 4.639919853633443e-15, + }; + float x = finetune; + tuning *= fine_coeffs[0] + x * (fine_coeffs[1] + x * (fine_coeffs[2] + x * (fine_coeffs[3] + x * fine_coeffs[4]))); + return tuning; } void @@ -317,16 +334,19 @@ read_instrs_info(soundfont *sf, xmlNodePtr instrs) { "Sample", true, xml_parse_c_identifier, offsetof(instr_data, sample_name_mid) }, { "BaseNote", true, xml_parse_note_number, offsetof(instr_data, base_note_mid) }, + { "FineTune", true, xml_parse_fine_tune, offsetof(instr_data, fine_tune_mid) }, { "SampleRate", true, xml_parse_double, offsetof(instr_data, sample_rate_mid) }, { "RangeLo", true, xml_parse_note_number, offsetof(instr_data, sample_low_end) }, { "SampleLo", true, xml_parse_c_identifier, offsetof(instr_data, sample_name_low) }, { "BaseNoteLo", true, xml_parse_note_number, offsetof(instr_data, base_note_lo) }, + { "FineTuneLo", true, xml_parse_fine_tune, offsetof(instr_data, fine_tune_lo) }, { "SampleRateLo", true, xml_parse_double, offsetof(instr_data, sample_rate_lo) }, { "RangeHi", true, xml_parse_note_number, offsetof(instr_data, sample_high_start)}, { "SampleHi", true, xml_parse_c_identifier, offsetof(instr_data, sample_name_high) }, { "BaseNoteHi", true, xml_parse_note_number, offsetof(instr_data, base_note_hi) }, + { "FineTuneHi", true, xml_parse_fine_tune, offsetof(instr_data, fine_tune_hi) }, { "SampleRateHi", true, xml_parse_double, offsetof(instr_data, sample_rate_hi) }, }; @@ -356,6 +376,9 @@ read_instrs_info(soundfont *sf, xmlNodePtr instrs) instr->base_note_mid = NOTE_UNSET; instr->base_note_lo = NOTE_UNSET; instr->base_note_hi = NOTE_UNSET; + instr->fine_tune_mid = FINE_TUNE_UNSET; + instr->fine_tune_lo = FINE_TUNE_UNSET; + instr->fine_tune_hi = FINE_TUNE_UNSET; instr->sample_rate_mid = -1.0; instr->sample_rate_lo = -1.0; instr->sample_rate_hi = -1.0; @@ -478,10 +501,13 @@ read_instrs_info(soundfont *sf, xmlNodePtr instrs) if (instr->base_note_lo == NOTE_UNSET) instr->base_note_lo = instr->sample_low->base_note; + if (instr->fine_tune_lo == FINE_TUNE_UNSET) + instr->fine_tune_lo = instr->sample_low->fine_tune; + if (instr->sample_rate_lo < 0.0) instr->sample_rate_lo = instr->sample_low->sample_rate; - instr->sample_low_tuning = calc_tuning(instr->sample_rate_lo, instr->base_note_lo); + instr->sample_low_tuning = calc_tuning(instr->sample_rate_lo, instr->base_note_lo, instr->fine_tune_lo); } instr->sample_mid = sample_data_forname(sf, instr->sample_name_mid); @@ -492,15 +518,18 @@ read_instrs_info(soundfont *sf, xmlNodePtr instrs) if (instr->base_note_mid == NOTE_UNSET) instr->base_note_mid = instr->sample_mid->base_note; + if (instr->fine_tune_mid == FINE_TUNE_UNSET) + instr->fine_tune_mid = instr->sample_mid->fine_tune; + if (instr->sample_rate_mid < 0.0) instr->sample_rate_mid = instr->sample_mid->sample_rate; - instr->sample_mid_tuning = calc_tuning(instr->sample_rate_mid, instr->base_note_mid); + instr->sample_mid_tuning = calc_tuning(instr->sample_rate_mid, instr->base_note_mid, instr->fine_tune_mid); // Some tuning values don't decompose properly into a samplerate and basenote, they must be accounted for here // for matching. So far this has only been seen for an Instrument mid sample. // NOTE: Keep in sync with the BAD_FLOATS list in extraction/tuning.py - if (f2i(instr->sample_mid_tuning) == 0x3E7319DF /* 0.237403377 */) // diff = 2^-24 + if (sf->matching && f2i(instr->sample_mid_tuning) == 0x3E7319DF /* 0.237403377 */) // diff = 2^-24 instr->sample_mid_tuning = i2f(0x3E7319E3 /* 0.237403437 */); if (instr->sample_name_high != NULL) { @@ -512,10 +541,13 @@ read_instrs_info(soundfont *sf, xmlNodePtr instrs) if (instr->base_note_hi == NOTE_UNSET) instr->base_note_hi = instr->sample_high->base_note; + if (instr->fine_tune_hi == FINE_TUNE_UNSET) + instr->fine_tune_hi = instr->sample_high->fine_tune; + if (instr->sample_rate_hi < 0.0) instr->sample_rate_hi = instr->sample_high->sample_rate; - instr->sample_high_tuning = calc_tuning(instr->sample_rate_hi, instr->base_note_hi); + instr->sample_high_tuning = calc_tuning(instr->sample_rate_hi, instr->base_note_hi, instr->fine_tune_hi); } // link @@ -544,6 +576,7 @@ read_drums_info(soundfont *sf, xmlNodePtr drums) { "Sample", false, xml_parse_c_identifier, offsetof(drum_data, sample_name) }, { "SampleRate", true, xml_parse_double, offsetof(drum_data, sample_rate) }, { "BaseNote", true, xml_parse_note_number, offsetof(drum_data, base_note) }, + { "FineTune", true, xml_parse_fine_tune, offsetof(drum_data, fine_tune) }, }; LL_FOREACH(xmlNodePtr, drum_node, drums->children) { @@ -560,6 +593,7 @@ read_drums_info(soundfont *sf, xmlNodePtr drums) drum->note_end = NOTE_UNSET; drum->sample_rate = -1; drum->base_note = NOTE_UNSET; + drum->fine_tune = FINE_TUNE_UNSET; drum->release = RELEASE_UNSET; if (drum_node->properties == NULL) { @@ -605,13 +639,16 @@ read_drums_info(soundfont *sf, xmlNodePtr drums) // set basenote if not overridden if (drum->base_note == NOTE_UNSET) { - if (drum->sample->aifc.has_inst) { + if (drum->sample->aifc.has_inst) drum->base_note = drum->sample->base_note; - } else { + else error("No basenote for drum (line %d)", drum_node->line); - } } + // set finetune if not overridden + if (drum->fine_tune == FINE_TUNE_UNSET) + drum->fine_tune = drum->sample->fine_tune; + // link link_drum: if (sf->drums == NULL) { @@ -633,6 +670,7 @@ read_sfx_info(soundfont *sf, xmlNodePtr effects) { "Sample", false, xml_parse_c_identifier, offsetof(sfx_data, sample_name)}, { "SampleRate", true, xml_parse_double, offsetof(sfx_data, sample_rate)}, { "BaseNote", true, xml_parse_note_number, offsetof(sfx_data, base_note) }, + { "FineTune", true, xml_parse_fine_tune, offsetof(sfx_data, fine_tune) }, }; LL_FOREACH(xmlNodePtr, sfx_node, effects->children) { @@ -652,6 +690,7 @@ read_sfx_info(soundfont *sf, xmlNodePtr effects) } else { sfx->sample_rate = -1; sfx->base_note = NOTE_UNSET; + sfx->fine_tune = FINE_TUNE_UNSET; xml_parse_node_by_spec(sfx, sfx_node, sfx_spec, ARRAY_COUNT(sfx_spec)); sfx->sample = sample_data_forname(sf, sfx->sample_name); @@ -662,10 +701,13 @@ read_sfx_info(soundfont *sf, xmlNodePtr effects) if (sfx->base_note == NOTE_UNSET) sfx->base_note = sfx->sample->base_note; + if (sfx->fine_tune == FINE_TUNE_UNSET) + sfx->fine_tune = sfx->sample->fine_tune; + if (sfx->sample_rate == -1) sfx->sample_rate = sfx->sample->sample_rate; - sfx->tuning = calc_tuning(sfx->sample_rate, sfx->base_note); + sfx->tuning = calc_tuning(sfx->sample_rate, sfx->base_note, sfx->fine_tune); } // link @@ -696,6 +738,7 @@ read_samples_info(soundfont *sf, xmlNodePtr samples) {"Name", false, xml_parse_c_identifier, offsetof(sample_data, name) }, { "SampleRate", true, xml_parse_double, offsetof(sample_data, sample_rate)}, { "BaseNote", true, xml_parse_note_number, offsetof(sample_data, base_note) }, + { "FineTune", true, xml_parse_fine_tune, offsetof(sample_data, fine_tune) }, { "IsDD", true, xml_parse_bool, offsetof(sample_data, is_dd) }, { "Cached", true, xml_parse_bool, offsetof(sample_data, cached) }, }; @@ -717,6 +760,7 @@ read_samples_info(soundfont *sf, xmlNodePtr samples) sample->sample_rate = -1.0; sample->base_note = NOTE_UNSET; + sample->fine_tune = FINE_TUNE_UNSET; sample->is_dd = defaults.is_dd; sample->cached = defaults.cached; @@ -740,6 +784,9 @@ read_samples_info(soundfont *sf, xmlNodePtr samples) error("No basenote for sample %s (line %d)", sample->name, sample_node->line); } + if (sample->fine_tune == FINE_TUNE_UNSET) + sample->fine_tune = sample->aifc.detune; + if (!sample->aifc.has_book) error("No vadpcm codebook for sample %s (line %d)", sample->name, sample_node->line); @@ -1403,7 +1450,7 @@ emit_c_drums(FILE *out, soundfont *sf) if (note > 127) note -= 128; - float tuning = calc_tuning(drum->sample_rate, note); + float tuning = calc_tuning(drum->sample_rate, note, drum->fine_tune); fprintf(out, " SF%d_%s_ENTRY(" F32_FMT "f),\n", sf->info.index, drum->name, tuning); } diff --git a/tools/audio/xml.c b/tools/audio/xml.c index 41f07a6454..54c0e661f4 100644 --- a/tools/audio/xml.c +++ b/tools/audio/xml.c @@ -235,6 +235,20 @@ err: error("Invalid note %s", value); } +/** + * Parse fine tuning in the range [-100, 100] + */ +void +xml_parse_fine_tune(const char *value, void *out) +{ + int v = xml_str_to_int(value); + if (v < -100 || v > 100) + error("Value %d out of range for fine tuning", v); + int8_t vs8 = v; + + copy_out(out, vs8); +} + void xml_parse_string(const char *value, void *out) { diff --git a/tools/audio/xml.h b/tools/audio/xml.h index dabfbcfefe..8e19000233 100644 --- a/tools/audio/xml.h +++ b/tools/audio/xml.h @@ -39,6 +39,8 @@ xml_parse_s8(const char *value, void *out); void xml_parse_note_number(const char *value, void *out); void +xml_parse_fine_tune(const char *value, void *out); +void xml_parse_string(const char *value, void *out); void xml_parse_c_identifier(const char *value, void *out);