Soundfont Compiler: Implement fine-tuning in the final tuning calculation (#1863)

This commit is contained in:
Tharo
2026-03-09 10:47:11 +00:00
committed by GitHub
parent 04d96d3d34
commit 826bc7588e
5 changed files with 95 additions and 13 deletions
+12
View File
@@ -147,6 +147,7 @@ Begins a new soundfont.
Name="<C Identifier>"
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.
- <ins>[Optional]</ins> **SampleRate**: An overriding sample rate for this sample. **Default comes from the sample file.**
- <ins>[Optional]</ins> **BaseNote**: An overriding root key for this sample. **Default comes from the sample file.**
- <ins>[Optional]</ins> **FineTune**: An overriding fine tuning for this sample, in cents, in the range [-100, 100]. **Default comes from the sample file.**
- <ins>[Optional]</ins> **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`.**
- <ins>[Optional]</ins> **Cached**: Whether this sample should be added to the `usedSamples` cache. **Default is `false`.**
@@ -187,6 +189,7 @@ Begins a new soundfont.
Sample="<Sample Name>"
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.
- <ins>[Optional]</ins> **SampleRate**: An overriding sample rate for this effect. **Default comes from the sample definition.**
- <ins>[Optional]</ins> **BaseNote**: An overriding root key for this effect. **Default comes from the sample definition.**
- <ins>[Optional]</ins> **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="<Sample Name>"
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.
- <ins>[Optional]</ins> **SampleRate**: An overriding sample rate for this sound. **Default comes from the sample definition.**
- <ins>[Optional]</ins> **BaseNote**: An overriding root key for this sound. **Default comes from the sample definition.**
- <ins>[Optional]</ins> **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="<Sample Name>"
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.
- <ins>[Optional]</ins> **SampleRate**: Sample rate override for the middle sample. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **BaseNote**: Base note override for the middle sample. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **FineTune**: Fine tuning override for the middle sample, in cents, in the range [-100, 100]. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **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.**
- <ins>[Optional]</ins> **SampleLo**: The name of the low sample to use for this instrument.
- <ins>[Optional]</ins> **SampleRateLo**: Sample rate override for the low sample. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **BaseNoteLo**: Base note override for the low sample. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **FineTuneLo**: Fine tuning override for the low sample, in cents, in the range [-100, 100]. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **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.**
- <ins>[Optional]</ins> **SampleHi**: The name of the high sample to use for this instrument.
- <ins>[Optional]</ins> **SampleRateHi**: Sample rate override for the high sample. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **BaseNoteHi**: Base note override for the high sample. **Default is sourced from the sample properties.**
- <ins>[Optional]</ins> **FineTuneHi**: Fine tuning override for the high sample, in cents, in the range [-100, 100]. **Default is sourced from the sample properties.**
---
+9 -2
View File
@@ -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);
+58 -11
View File
@@ -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);
}
+14
View File
@@ -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)
{
+2
View File
@@ -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);