diff --git a/assets/xml/audio/samplebanks/SampleBank_0.xml b/assets/xml/audio/samplebanks/SampleBank_0.xml index bc1c5bb8d8..9526f41ae1 100644 --- a/assets/xml/audio/samplebanks/SampleBank_0.xml +++ b/assets/xml/audio/samplebanks/SampleBank_0.xml @@ -13,8 +13,8 @@ - - + + @@ -22,11 +22,11 @@ - + - + @@ -41,7 +41,7 @@ - + @@ -67,36 +67,36 @@ - + - - - - - - + + + + + + - + - + - + - + - + @@ -106,48 +106,48 @@ - + - - + + - - - - + + + + - - + + - - + + - - - - + + + + - + - + - + - + - + - - + + @@ -238,13 +238,13 @@ - + - - - - + + + + @@ -300,13 +300,13 @@ - - - - - - - + + + + + + + @@ -373,26 +373,26 @@ - + - - - + + + - - - + + + - - + + - + @@ -401,13 +401,13 @@ - - + + - - - - + + + + @@ -417,7 +417,7 @@ - + @@ -428,31 +428,31 @@ - + - - + + - + - - + + - - - - - + + + + + @@ -461,7 +461,7 @@ - + @@ -469,7 +469,7 @@ - + @@ -525,7 +525,7 @@ - + @@ -534,12 +534,12 @@ - + - + @@ -553,124 +553,124 @@ - + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - + - + - - - + + + - - + + - + - - - - + + + + - - - - - - - - - - + + + + + + + + + + - + - - + + - - + + - + - + - + - - - - - + + + + + - - - + + + - - - - + + + + - + - - + + - - - - - + + + + + - + diff --git a/assets/xml/audio/samplebanks/SampleBank_2.xml b/assets/xml/audio/samplebanks/SampleBank_2.xml index 0512a81d82..c07c05f8b8 100644 --- a/assets/xml/audio/samplebanks/SampleBank_2.xml +++ b/assets/xml/audio/samplebanks/SampleBank_2.xml @@ -2,8 +2,8 @@ - - - - + + + + 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);