diff --git a/assets/xml/audio/samplebanks/SampleBank_0.xml b/assets/xml/audio/samplebanks/SampleBank_0.xml
index 5de4e44b12..24d4652134 100644
--- a/assets/xml/audio/samplebanks/SampleBank_0.xml
+++ b/assets/xml/audio/samplebanks/SampleBank_0.xml
@@ -12,8 +12,8 @@
-
-
+
+
@@ -22,11 +22,11 @@
-
+
-
+
@@ -41,7 +41,7 @@
-
+
@@ -64,34 +64,34 @@
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
-
+
@@ -101,29 +101,29 @@
-
+
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
@@ -235,10 +235,10 @@
-
-
-
-
+
+
+
+
@@ -257,11 +257,11 @@
-
+
-
-
+
+
@@ -275,7 +275,7 @@
-
+
@@ -284,7 +284,7 @@
-
+
@@ -297,14 +297,14 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -312,125 +312,125 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
diff --git a/assets/xml/audio/samplebanks/SampleBank_2.xml b/assets/xml/audio/samplebanks/SampleBank_2.xml
index 05d2563d1a..191d3a3bbd 100644
--- a/assets/xml/audio/samplebanks/SampleBank_2.xml
+++ b/assets/xml/audio/samplebanks/SampleBank_2.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/assets/xml/audio/samplebanks/SampleBank_4.xml b/assets/xml/audio/samplebanks/SampleBank_4.xml
index 11688e4bc7..73f569fa9a 100644
--- a/assets/xml/audio/samplebanks/SampleBank_4.xml
+++ b/assets/xml/audio/samplebanks/SampleBank_4.xml
@@ -1,8 +1,8 @@
-
-
-
-
+
+
+
+
diff --git a/assets/xml/audio/samplebanks/SampleBank_5.xml b/assets/xml/audio/samplebanks/SampleBank_5.xml
index 899efe7f9c..dc434ac1ea 100644
--- a/assets/xml/audio/samplebanks/SampleBank_5.xml
+++ b/assets/xml/audio/samplebanks/SampleBank_5.xml
@@ -2,8 +2,8 @@
-
-
-
-
+
+
+
+
diff --git a/assets/xml/audio/samplebanks/SampleBank_6.xml b/assets/xml/audio/samplebanks/SampleBank_6.xml
index 6b565dd112..6df4c28028 100644
--- a/assets/xml/audio/samplebanks/SampleBank_6.xml
+++ b/assets/xml/audio/samplebanks/SampleBank_6.xml
@@ -1,9 +1,9 @@
-
+
-
+
diff --git a/tools/audio/extraction/audiobank_file.py b/tools/audio/extraction/audiobank_file.py
index cd4e48740b..0f1caf96ee 100644
--- a/tools/audio/extraction/audiobank_file.py
+++ b/tools/audio/extraction/audiobank_file.py
@@ -129,9 +129,9 @@ class DrumGroup:
notes.append(note)
- # Note values should increase monotonically in a drum group
- note_indices = [pitch_names.index(note) + 21 for note in notes]
- assert all(v == note_indices[0] + i for i,v in enumerate(note_indices))
+ # Drum frequencies should increase monotonically in a drum group
+ # Increasing frequencies correspond to decreasing note values
+ assert all(v == (notes[0] - i) % 128 for i,v in enumerate(notes))
# Assign final rate and note.
# Use first note in the group as the basenote for the whole group, the rest will be filled in during build.
@@ -167,7 +167,7 @@ class DrumGroup:
if self.needs_rate_override:
attributes["SampleRate"] = self.sample_rate
if self.needs_note_override:
- attributes["BaseNote"] = self.base_note
+ attributes["BaseNote"] = pitch_names[self.base_note]
xml.write_element("Drum", attributes)
diff --git a/tools/audio/extraction/audiobank_structs.py b/tools/audio/extraction/audiobank_structs.py
index fa6afa37d0..beb2e1a57d 100644
--- a/tools/audio/extraction/audiobank_structs.py
+++ b/tools/audio/extraction/audiobank_structs.py
@@ -64,7 +64,7 @@ class SoundFontSample: # SampleHeader ?
if rate_override is not None:
attrs["SampleRate"] = rate_override
if note_override is not None:
- attrs["BaseNote"] = note_override
+ attrs["BaseNote"] = pitch_names[note_override]
if self.medium != 0:
attrs["IsDD"] = "true"
if self.cached:
@@ -235,7 +235,7 @@ class SoundFontSound:
if self.needs_rate_override:
attrs["SampleRate"] = self.sample_rate
if self.needs_note_override:
- attrs["BaseNote"] = self.base_note
+ attrs["BaseNote"] = pitch_names[self.base_note]
xml.write_element("Effect", attrs)
@@ -383,7 +383,7 @@ class Instrument:
if self.needs_rate_override[1]:
attributes["SampleRate"] = self.sample_rate[1]
if self.needs_note_override[1]:
- attributes["BaseNote"] = self.base_note[1]
+ attributes["BaseNote"] = pitch_names[self.base_note[1]]
if self.normal_range_lo != 0:
attributes["RangeLo"] = pitch_names[self.normal_range_lo]
@@ -392,7 +392,7 @@ class Instrument:
if self.needs_rate_override[0]:
attributes["SampleRateLo"] = self.sample_rate[0]
if self.needs_note_override[0]:
- attributes["BaseNoteLo"] = self.base_note[0]
+ attributes["BaseNoteLo"] = pitch_names[self.base_note[0]]
if self.normal_range_hi != 127:
attributes["RangeHi"] = pitch_names[self.normal_range_hi]
@@ -401,6 +401,6 @@ class Instrument:
if self.needs_rate_override[2]:
attributes["SampleRateHi"] = self.sample_rate[2]
if self.needs_note_override[2]:
- attributes["BaseNoteHi"] = self.base_note[2]
+ attributes["BaseNoteHi"] = pitch_names[self.base_note[2]]
xml.write_element("Instrument" if not self.unused else "InstrumentUnused", attributes)
diff --git a/tools/audio/extraction/audiotable.py b/tools/audio/extraction/audiotable.py
index 29a27695ae..3a76396c6c 100644
--- a/tools/audio/extraction/audiotable.py
+++ b/tools/audio/extraction/audiotable.py
@@ -11,7 +11,7 @@ from .audio_tables import AudioCodeTableEntry
from .audiobank_structs import AudioSampleCodec, SoundFontSample, AdpcmBook, AdpcmLoop
from .extraction_xml import SampleBankExtractionDescription
from .tuning import pitch_names, note_z64_to_midi, recalc_tuning, rate_from_tuning, rank_rates_notes, BAD_FLOATS
-from .util import align, error, XMLWriter, f32_to_u32
+from .util import align, XMLWriter, f32_to_u32
class AIFCFile:
@@ -205,7 +205,7 @@ class AudioTableSample(AudioTableData):
return ext
def base_note_number(self):
- return note_z64_to_midi(pitch_names.index(self.base_note))
+ return note_z64_to_midi(self.base_note)
def resolve_basenote_rate(self, extraction_sample_info : Optional[Dict[str,str]]):
assert len(self.notes_rates) != 0
@@ -287,7 +287,7 @@ class AudioTableSample(AudioTableData):
if extraction_sample_info is not None:
assert "SampleRate" in extraction_sample_info and "BaseNote" in extraction_sample_info
final_rate = int(extraction_sample_info["SampleRate"])
- final_note = extraction_sample_info["BaseNote"]
+ final_note = pitch_names.index(extraction_sample_info["BaseNote"])
# print(" ",len(FINAL_NOTES_RATES), FINAL_NOTES_RATES)
# if rate_3ds is not None and len(FINAL_NOTES_RATES) == 1:
@@ -644,7 +644,7 @@ class AudioTableFile:
"Name" : sample.name,
"FileName" : sample.filename.replace(sample.codec_file_extension_compressed(), ""),
"SampleRate" : sample.sample_rate,
- "BaseNote" : sample.base_note,
+ "BaseNote" : pitch_names[sample.base_note],
}
xml.write_element("Sample", attrs)
else:
diff --git a/tools/audio/extraction/tuning.py b/tools/audio/extraction/tuning.py
index b538c68e8d..065cd714ff 100644
--- a/tools/audio/extraction/tuning.py
+++ b/tools/audio/extraction/tuning.py
@@ -62,8 +62,14 @@ def note_z64_to_midi(note : int) -> int:
"""
return (21 + note) % 128
-def recalc_tuning(rate : int, note : str) -> float:
- return f32(f32(rate / 32000.0) * u32_to_f32(g_pitch_frequencies[pitch_names.index(note)]))
+def recalc_tuning(rate : int, note : int) -> float:
+ # The tuning formula t(r,n) for n a midi note number is
+ # t = (r / 32000) * 2^{60 - n}
+ # We use a lookup table for the power of 2 calculation for z a z64 note number
+ # t = (r / 32000) * frequencies[78 - z]
+ # The offset by 78 comes from 2*(60 - 21) where 21 relates the z64 and midi note numbers
+ # n = 21 + z
+ return f32(f32(rate / 32000.0) * u32_to_f32(g_pitch_frequencies[(78 - note) % 128]))
def rate_from_tuning(tuning : float) -> Tuple[Tuple[str,int]]:
"""
@@ -86,14 +92,16 @@ def rate_from_tuning(tuning : float) -> Tuple[Tuple[str,int]]:
diff : int = abs(f32_to_u32(tuning2) - tuning_bits)
if diff == 0:
- matches.append((pitch_names[note_val], nominal_rate))
+ matches.append((note_val, nominal_rate))
else:
- diffs.append((diff, (pitch_names[note_val], nominal_rate)))
+ diffs.append((diff, (note_val, nominal_rate)))
# search gPitchFrequencies LUT one by one. We don't exit as soon as a match is found as in general this procedure
# only recovers the correct (rate,note) pair up to multiples of 2, to get the final value we want to select the
# "best" of these pairs by an essentially arbitrary ranking (cf `rank_rates_notes`)
- for note_val,freq_bits in enumerate(g_pitch_frequencies):
+ for i,freq_bits in enumerate(g_pitch_frequencies):
+ # Reflect the note value as in recalc_tuning
+ note_val = (78 - i) % 128
freq : float = u32_to_f32(freq_bits)
# compute the "nominal" samplerate for a given basenote by R = 32000 * (t / f)
@@ -122,32 +130,68 @@ def rank_rates_notes(layouts):
"""
rank = 0
- if 'C4' in notes and rate > 10000:
+ notes_named = [pitch_names[note] for note in notes]
+
+ if 'C4' in notes_named and rate > 10000:
rank += 10000
- elif 'C2' in notes and rate > 10000:
+ elif 'C2' in notes_named and rate > 10000:
rank += 9500
- elif 'D3' in notes and rate > 10000:
+ elif 'D3' in notes_named and rate > 10000:
rank += 8500
- elif 'D4' in notes and rate > 10000:
+ elif 'D4' in notes_named and rate > 10000:
rank += 8000
- elif 'G3' in notes:
+ elif 'C3' in notes_named and rate > 10000:
+ rank += 4000
+ elif 'C5' in notes_named and rate > 10000:
+ rank += 4000
+ elif 'D0' in notes_named:
+ rank += 3500
+ elif 'A9' in notes_named:
+ rank += 3000
+ elif 'A3' in notes_named:
+ rank += 2750
+ elif 'C6' in notes_named:
+ rank += 2750
+ elif 'C8' in notes_named:
+ rank += 2500
+ elif 'C1' in notes_named:
+ rank += 2500
+ elif 'A4' in notes_named:
+ rank += 2500
+ elif 'A0' in notes_named:
+ rank += 2250
+ elif 'B0' in notes_named:
+ rank += 2250
+ elif 'AF9' in notes_named:
rank += 2000
- elif 'F3' in notes:
- rank += 25
- elif 'C0' in notes:
+ elif 'AF0' in notes_named:
+ rank += 2000
+ elif 'G3' in notes_named:
+ rank += 2000
+ elif 'GF4' in notes_named:
+ rank += 100
+ elif 'F9' in notes_named:
rank += 50
- elif 'BF2' in notes:
+ elif 'F3' in notes_named:
+ rank += 25
+ elif 'C0' in notes_named:
+ rank += 50
+ elif 'BF2' in notes_named:
rank += 30
- elif 'B3' in notes:
+ elif 'B3' in notes_named:
rank += 25
- elif 'BF1' in notes:
+ elif 'BF1' in notes_named:
rank += 25
- elif 'E2' in notes:
+ elif 'E2' in notes_named:
rank += 20
- elif 'F6' in notes:
+ elif 'F6' in notes_named:
rank += 15
- elif 'GF2' in notes:
+ elif 'GF2' in notes_named:
rank += 10
+ elif 'BF3' in notes_named:
+ rank += 1
+ elif 'AF2' in notes_named:
+ rank += 1
rank += {
32000 : 200,
@@ -176,7 +220,7 @@ def rank_rates_notes(layouts):
ranked = list(sorted(layouts, key=lambda L : rank_rate_note(*L), reverse=True))
# Ensure the ranking produced a unique best option
- assert rank_rate_note(*ranked[0]) != rank_rate_note(*ranked[1]) , ranked
+ assert rank_rate_note(*ranked[0]) != rank_rate_note(*ranked[1]) , [(rate,tuple(pitch_names[note] for note in notes)) for rate,notes in ranked]
# Output best
return ranked[0]
@@ -185,7 +229,7 @@ if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description="Given either a (rate,note) or a tuning, compute all matching rates/notes.")
- parser.add_argument("-t", dest="tuning", required=False, default=None, type=float, help="Tuning value (float)")
+ parser.add_argument("-t", dest="tuning", required=False, default=None, help="Tuning value (float or hex)")
parser.add_argument("-r", dest="rate", required=False, default=None, type=int, help="Sample rate (integer)")
parser.add_argument("-n", dest="note", required=False, default=None, type=str, help="Base note (note name)")
parser.add_argument("--show-result", required=False, default=False, action="store_true", help="Show recalculated tuning value")
@@ -193,10 +237,10 @@ if __name__ == '__main__':
if args.tuning is not None:
# Take input tuning
- tuning = args.tuning
+ tuning : float = u32_to_f32(int(args.tuning,16)) if args.tuning.startswith("0x") else float(args.tuning)
elif args.rate is not None and args.note is not None:
# Calculate target tuning from input rate and note
- tuning : float = recalc_tuning(args.rate, args.note)
+ tuning : float = recalc_tuning(args.rate, pitch_names.index(args.note))
else:
# Insufficient arguments
parser.print_help()
@@ -206,6 +250,6 @@ if __name__ == '__main__':
for note,rate in notes_rates:
if args.show_result:
- print(rate, note, "->", recalc_tuning(rate, note))
+ print(rate, pitch_names[note], "->", recalc_tuning(rate, note))
else:
- print(rate, note)
+ print(rate, pitch_names[note])
diff --git a/tools/audio/soundfont_compiler.c b/tools/audio/soundfont_compiler.c
index 6a47d643b8..4f37baa557 100644
--- a/tools/audio/soundfont_compiler.c
+++ b/tools/audio/soundfont_compiler.c
@@ -199,7 +199,8 @@ calc_tuning(float sample_rate, int basenote, int8_t finetune)
/* 0x7F */ 0.099213f, // PITCH_AF0
};
- float tuning = (sample_rate / playback_sample_rate) * pitch_frequencies[basenote];
+ // Due to the way the lookup table is arranged, the note needs to be reflected about middle C (z64 note value 39)
+ float tuning = (sample_rate / playback_sample_rate) * pitch_frequencies[(78u - (unsigned)basenote) % 128u];
if (finetune == 0)
return tuning;
@@ -1446,10 +1447,10 @@ emit_c_drums(FILE *out, soundfont *sf)
ptr_table[ptr_offset].n = note_offset;
// wrap note on overflow
- int note = drum->base_note + note_offset;
- if (note > 127)
- note -= 128;
-
+ // drum frequencies increase with drum offset, corresponding to a decrease in note number
+ int note = drum->base_note - note_offset;
+ if (note < 0)
+ note += 128;
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);