diff --git a/ASM/src/hacks.asm b/ASM/src/hacks.asm index b8dee3b8e..6d5f4a416 100644 --- a/ASM/src/hacks.asm +++ b/ASM/src/hacks.asm @@ -43,7 +43,7 @@ Gameplay_InitSkybox: .area 0x1C lui at, hi(AUDIO_THREAD_INFO_MEM_START) lw a0, lo(AUDIO_THREAD_INFO_MEM_START)(at) - jal 0x800B8654 + jal 0x800B8654 ; AudioLoad_Init lw a1, lo(AUDIO_THREAD_INFO_MEM_SIZE)(at) lw ra, 0x0014(sp) jr ra diff --git a/Audiobank.py b/Audiobank.py index bbe741d4c..e57a4e519 100644 --- a/Audiobank.py +++ b/Audiobank.py @@ -148,6 +148,9 @@ def __init__(self): self.parents: list = [] self.bank_offset = -1 # offset of the sample within the bank. -1 indicates the sample hasn't been placed yet self.original_offset = -1 + self.codec: int = 0 # ADPCM is the only codec that seems to work + self.medium: int = 0 + self.tag: bool = False self.book: AdpcmBook = None self.loop: AdpcmLoop = None self.data: bytearray = None @@ -443,6 +446,7 @@ def __init__(self, inst_id: int, bankdata: bytearray, audiotable_file: bytearray self.lowNoteSample: Sample = Sample.from_rom_data(bankdata, audiotable_file, audiotable_index, self.lowNoteSampleOffset, audiotable_id, self, sampleCache, adpcmbookCache) if self.lowNoteSampleOffset != 0 else None self.normalNoteSample: Sample = Sample.from_rom_data(bankdata, audiotable_file, audiotable_index, self.normalNoteSampleOffset, audiotable_id, self, sampleCache, adpcmbookCache) if self.normalNoteSampleOffset != 0 else None self.highNoteSample: Sample = Sample.from_rom_data(bankdata, audiotable_file, audiotable_index, self.highNoteSampleOffset, audiotable_id, self, sampleCache, adpcmbookCache) if self.highNoteSampleOffset != 0 else None + self.tag: bool = False def get_bytes(self): bytes = bytearray(1) diff --git a/Rom.py b/Rom.py index 8247dfe84..6ca4373ae 100644 --- a/Rom.py +++ b/Rom.py @@ -248,6 +248,18 @@ def rebuild_audio_data(self, audiobank_index_addr: int): # Read audio banks back out and see if they match test = [] + # Update the size of bank 0 if necessary + # Read the sizes from the ROM + # 8010a1b8 and 8010a1bc contain uint32_t with the size of the init pool and the size of the permanent pool. The init pool contains the permanent pool so need to increase them both + initPoolAddress = 0x8010a1b8 - 0x800110A0 + 0xA87000 + permanentPoolAddress = initPoolAddress + 4 + initPoolSize = self.read_int32(initPoolAddress) + permanentPoolSize = self.read_int32(permanentPoolAddress) + initPoolSize = initPoolSize - 0x3AA0 + len(self.audiobanks[0].placed_data) + permanentPoolSize = permanentPoolSize - 0x3AA0 + len(self.audiobanks[0].placed_data) + self.write_int32(initPoolAddress, initPoolSize) + self.write_int32(permanentPoolAddress, permanentPoolSize) + # Read Audiotable index audiotable_index_header: bytearray = self.read_bytes(AUDIOTABLE_INDEX_ADDR, 0x10) audiotable_index_length = int.from_bytes(audiotable_index_header[0:2], 'big') diff --git a/Voices.py b/Voices.py index af7c416c8..e4b2b8fc5 100644 --- a/Voices.py +++ b/Voices.py @@ -433,7 +433,7 @@ def process_pak_sfx_by_id(pak_sfx_id: int, sfx_id_map, pak_sounds, age, settings # Add the ones we have for name, decompressed in pak_opts: _file = io.BytesIO(decompressed) - soundData, numSampleFrames, sampleRate = process_sound_file(name, _file, age, settings, trim=True) + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(name, _file, age, settings, trim=True) _file.close() to_add.append((name, 0, rom_targets[i], soundData, numSampleFrames, sampleRate, None)) i += 1 @@ -446,7 +446,7 @@ def process_pak_sfx_by_id(pak_sfx_id: int, sfx_id_map, pak_sounds, age, settings for i in range(0, len(rom_targets)): name, decompressed = pak_opts[i] _file = io.BytesIO(decompressed) - soundData, numSampleFrames, sampleRate = process_sound_file(name, _file, age, settings, trim=True) + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(name, _file, age, settings, trim=True) _file.close() to_add.append((name, 0, rom_targets[i], soundData, numSampleFrames, sampleRate, None)) pass @@ -460,7 +460,7 @@ def process_pak_sfx_by_id(pak_sfx_id: int, sfx_id_map, pak_sounds, age, settings patch = mapping['patch'] name, decompressed = pak_opts[0] _file = io.BytesIO(decompressed) - soundData, numSampleFrames, sampleRate = process_sound_file(name, _file, age, settings, trim=True) + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(name, _file, age, settings, trim=True) to_add.append((name, 0, rom_targets[0], soundData, numSampleFrames, sampleRate, patch)) _file.close() else: @@ -551,44 +551,71 @@ def patch_voice_pack(rom: Rom, age: VOICE_PACK_AGE, voice_pack: str, settings: S sample_file = voice_map["direct_bank"][bank_str][index_str] with zf.open(sample_file) as f: # Read and process the file - soundData, numSampleFrames, sampleRate = process_sound_file(sample_file, f, age, settings) + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(sample_file, f, age, settings) sfxs.append((sample_file, bank, index, soundData, numSampleFrames, sampleRate, None)) if "direct_bank_inst" in voice_map.keys(): for bank_str in voice_map["direct_bank_inst"].keys(): - bank = int(bank_str, 16) + bank_index = int(bank_str, 16) + bank = rom.audiobanks[bank_index] for index_str in voice_map["direct_bank_inst"][bank_str].keys(): index = int(index_str, 16) - sample_file = voice_map["direct_bank_inst"][bank_str][index_str] - with zf.open(sample_file) as f: - # Read the .aifc file - # Need to get the loop predictors out of it - soundData, numSampleFrames, sampleRate, book, loop = process_aifc_file(f) - inst_patch.append((sample_file,bank, index, soundData, numSampleFrames, sampleRate, book, loop)) + instrument = bank.instruments[index] + instrument.tag = True + instrument_json: dict = voice_map["direct_bank_inst"][bank_str][index_str] + if "lowNote" in instrument_json.keys(): + with zf.open(instrument_json["lowNote"]) as f: + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(f.name, f, age, settings) + # Pad the data to 16 bytes + soundData += bytearray((16 - (len(soundData)%16))%16) + tuning = sampleRate / 32000 + tuning = tuning * instrument_json["lowTuning"] + lowSample = Sample() + lowSample.tag = True + instrument.lowNoteTuning = float(tuning) + lowSample.loop = loop + lowSample.book = book + lowSample.data = soundData + # Update sample data length = length + lowSample.size = len(soundData) + instrument.lowNoteSample = lowSample + if "normalNote" in instrument_json.keys(): + with zf.open(instrument_json["normalNote"]) as f: + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(f.name, f, age, settings) + # Pad the data to 16 bytes + soundData += bytearray((16 - (len(soundData)%16))%16) + tuning = sampleRate / 32000 + tuning = tuning * instrument_json["normalTuning"] + normalSample = Sample() + normalSample.tag = True + instrument.normalNoteTuning = float(tuning) + normalSample.loop = loop + normalSample.book = book + normalSample.data = soundData + # Update sample data length = length + normalSample.size = len(soundData) + instrument.normalNoteSample = normalSample + if "highNote" in instrument_json.keys(): + with zf.open(instrument_json["normalNote"]) as f: + soundData, numSampleFrames, sampleRate, book, loop = process_sound_file(f.name, f, age, settings) + # Pad the data to 16 bytes + soundData += bytearray((16 - (len(soundData)%16))%16) + tuning = sampleRate / 32000 + tuning = tuning * instrument_json["highTuning"] + highSample = Sample() + highSample.tag = True + instrument.highNoteSampleTuning = float(tuning) + highSample.loop = loop + highSample.book = book + highSample.data = soundData + # Update sample data length = length + highSample.size = len(soundData) + instrument.highNoteSample = highSample + if "normalRangeLow" in instrument_json.keys(): + instrument.normalRangeLo = instrument_json["normalRangeLow"] + if "normalRangeHigh" in instrument_json.keys(): + instrument.normalRangeHi = instrument_json["normalRangeHigh"] zf.close() - sfx_data_start = len(rom.audiotable) - - for _, bank_index, inst_id, soundData, numSampleFrames, sampleRate, book, loop in inst_patch: - # Calculate the tuning as sampling rate / 32000. - tuning = sampleRate / 32000 - - # Pad the data to 16 bytes - soundData += bytearray((16 - (len(soundData)%16))%16) - - bank = rom.audiobanks[bank_index] - - inst: Instrument = bank.instruments[inst_id] - - # Update the sfx tuning - inst.normalNoteTuning = float(tuning) - - # Update loop end as numSampleFrames - inst.normalNoteSample.loop = loop - inst.normalNoteSample.book = book - inst.normalNoteSample.data = soundData - # Update sample data length = length - inst.normalNoteSample.size = len(soundData) - # Patch each sfx that we have for _, bank_index, sfx_id, soundData, numSampleFrames, sampleRate, patch in sfxs: # Calculate the tuning as sampling rate / 32000. @@ -633,19 +660,19 @@ def process_sound_file(file_name: str, file: BinaryIO, age: VOICE_PACK_AGE, sett # Check if this is a file format that sf supports filename, ext = os.path.splitext(file_name) if ext.strip('.').upper() in sf.available_formats(): - soundData, numSampleFrames, sampleRate = process_soundfile_file(file, age, settings, trim) + soundData, numSampleFrames, sampleRate, book, loop = process_soundfile_file(file, age, settings, trim) elif ext == ".aifc": soundData, numSampleFrames, sampleRate, book, loop = process_aifc_file(file) elif ext == ".bin": - soundData, numSampleFrames, sampleRate = process_bin_file(file) + soundData, numSampleFrames, sampleRate, book, loop = process_bin_file(file) else: raise Exception(f"Unsupported file format {ext} in custom voice pack.") - return soundData, numSampleFrames, sampleRate + return soundData, numSampleFrames, sampleRate, book, loop # Read an audio file using the soundfile python library -def process_soundfile_file(f: BinaryIO, age: VOICE_PACK_AGE, settings: Settings, trim=False) -> tuple[bytes, int, int]: +def process_soundfile_file(f: BinaryIO, age: VOICE_PACK_AGE, settings: Settings, trim=False) -> tuple[bytes, int, int, AdpcmBook, AdpcmLoop]: data, sampleRate = sf.read(f) if data.ndim == 2 and data.shape[1] == 2: # Convert stereo to mono by averaging the two channels @@ -661,15 +688,15 @@ def process_soundfile_file(f: BinaryIO, age: VOICE_PACK_AGE, settings: Settings, frames = data.tobytes() numSampleFrames = len(data) soundData = adpcm_encode(frames, len(data)) # Encode the raw samples - return soundData, numSampleFrames, sampleRate + return soundData, numSampleFrames, sampleRate, None, None # Used for patching SFX AIFC files that have already been stripped into raw binary ready to patch into the ROM # Assume a vanilla sampling rate of 20000 -def process_bin_file(f: BinaryIO) -> tuple[bytes, int, int]: +def process_bin_file(f: BinaryIO) -> tuple[bytes, int, int, AdpcmBook, AdpcmLoop]: soundData = f.read() numSampleFrames = int(len(soundData) * 16 / 9) sampleRate = 20000 - return (soundData, numSampleFrames, sampleRate) + return (soundData, numSampleFrames, sampleRate, None, None) # Pretty basic aifc file parser. Extracts the already encoded .aifc data metadata from the file def process_aifc_file(f: BinaryIO) -> tuple[bytes, int, int]: