Experimental audio support

Special thanks to Arisotura for helping out :)
This commit is contained in:
Sean Maas 2021-02-22 21:32:43 -05:00
parent f4e7a6b4b9
commit 0453c965ec
14 changed files with 301 additions and 75 deletions

View file

@ -1,7 +1,7 @@
FROM devkitpro/devkitarm:latest as build
RUN apt update
RUN apt -y install build-essential bsdmainutils
RUN apt -y install build-essential bsdmainutils sox
RUN mkdir /sm64
WORKDIR /sm64

View file

@ -236,6 +236,7 @@ ULTRA_BIN_DIRS := lib/bin
ifeq ($(TARGET_NDS),1)
SRC_DIRS += src/nds
ARM7_SRC_DIRS := src/nds/arm7
else
SRC_DIRS += asm
ULTRA_SRC_DIRS += lib/asm
@ -269,14 +270,21 @@ ifeq ($(TARGET_NDS),1)
guScaleF.c \
guTranslateF.c
ULTRA_C_FILES := $(addprefix lib/src/,$(ULTRA_C_FILES))
ARM7_C_FILES := $(foreach dir,$(ARM7_SRC_DIRS),$(wildcard $(dir)/*.c))
ARM7_O_FILES := $(foreach file,$(ARM7_C_FILES),$(BUILD_DIR)/arm7/$(file:.c=.o))
endif
# Sound files
SOUND_BANK_FILES := $(wildcard sound/sound_banks/*.json)
SOUND_SAMPLE_DIRS := $(wildcard sound/samples/*)
SOUND_SAMPLE_AIFFS := $(foreach dir,$(SOUND_SAMPLE_DIRS),$(wildcard $(dir)/*.aiff))
ifdef TARGET_NDS
SOUND_SAMPLE_AIFCS := $(foreach file,$(SOUND_SAMPLE_AIFFS),$(BUILD_DIR)/$(file:.aiff=.ima))
else
SOUND_SAMPLE_TABLES := $(foreach file,$(SOUND_SAMPLE_AIFFS),$(BUILD_DIR)/$(file:.aiff=.table))
SOUND_SAMPLE_AIFCS := $(foreach file,$(SOUND_SAMPLE_AIFFS),$(BUILD_DIR)/$(file:.aiff=.aifc))
endif
SOUND_SEQUENCE_DIRS := sound/sequences sound/sequences/$(VERSION)
# all .m64 files in SOUND_SEQUENCE_DIRS, plus all .m64 files that are generated from .s files in SOUND_SEQUENCE_DIRS
SOUND_SEQUENCE_FILES := \
@ -298,6 +306,10 @@ GODDARD_O_FILES := $(foreach file,$(GODDARD_C_FILES),$(BUILD_DIR)/$(file:.c=.o))
# Automatic dependency files
DEP_FILES := $(O_FILES:.o=.d) $(ULTRA_O_FILES:.o=.d) $(GODDARD_O_FILES:.o=.d) $(BUILD_DIR)/$(LD_SCRIPT).d
ifeq ($(TARGET_NDS),1)
DEP_FILES += $(ARM7_O_FILES:.o=.d)
endif
# Files with GLOBAL_ASM blocks
ifeq ($(NON_MATCHING),0)
ifeq ($(VERSION),sh)
@ -381,14 +393,18 @@ ifeq ($(TARGET_NDS),1)
LIBDIRS := $(DEVKITPRO)/libnds
TARGET_CFLAGS := -march=armv5te -mtune=arm946e-s -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) -DTARGET_NDS -DARM9 -D_LANGUAGE_C -DNO_SEGMENTED_MEMORY -DLIBFAT
TARGET_LDFLAGS := -lfat -lnds9 -specs=dsi_arm9.specs -g -mthumb -mthumb-interwork $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ARM7_TARGET_CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) -DTARGET_NDS -DARM7
CC_CHECK := $(CC)
CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(CC_CFLAGS) $(TARGET_CFLAGS) -Wall -Wextra -Wno-format-security -DNON_MATCHING -DAVOID_UB $(DEF_INC_CFLAGS)
ARM7_CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(CC_CFLAGS) $(ARM7_TARGET_CFLAGS) -Wall -Wextra -Wno-format-security $(DEF_INC_CFLAGS)
ASFLAGS := $(foreach i,$(INCLUDE_DIRS),-I$(i)) $(foreach d,$(DEFINES),--defsym $(d))
CFLAGS := -fno-strict-aliasing -fwrapv $(OPT_FLAGS) $(TARGET_CFLAGS) $(DEF_INC_CFLAGS)
LDFLAGS := $(TARGET_LDFLAGS)
LDFLAGS := -lfat -lnds9 -specs=dsi_arm9.specs -g -mthumb -mthumb-interwork $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ARM7_CFLAGS := -fno-strict-aliasing -fwrapv $(OPT_FLAGS) $(ARM7_TARGET_CFLAGS) $(DEF_INC_CFLAGS)
ARM7_LDFLAGS := -lnds7 -specs=ds_arm7.specs -g -mthumb-interwork $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
else
@ -537,6 +553,10 @@ endif
ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) $(GODDARD_SRC_DIRS) $(ULTRA_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(TEXT_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) rsp include) $(MIO0_DIR) $(addprefix $(MIO0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION)
ifeq ($(TARGET_NDS),1)
ALL_DIRS += $(addprefix $(BUILD_DIR)/arm7/,$(ARM7_SRC_DIRS))
endif
# Make sure build directory exists before compiling anything
DUMMY != mkdir -p $(ALL_DIRS)
@ -611,6 +631,11 @@ endif
# Sound File Generation #
#==============================================================================#
ifdef TARGET_NDS
$(BUILD_DIR)/%.ima: %.aiff
$(call print,Encoding IMA:,$<,$@)
$(V)sox $^ $@
else
$(BUILD_DIR)/%.table: %.aiff
$(call print,Extracting codebook:,$<,$@)
$(V)$(AIFF_EXTRACT_CODEBOOK) $< >$@
@ -618,6 +643,7 @@ $(BUILD_DIR)/%.table: %.aiff
$(BUILD_DIR)/%.aifc: $(BUILD_DIR)/%.table %.aiff
$(call print,Encoding VADPCM:,$<,$@)
$(V)$(VADPCM_ENC) -c $^ $@
endif
$(ENDIAN_BITWIDTH): $(TOOLS_DIR)/determine-endian-bitwidth.c
@$(PRINT) "$(GREEN)Generating endian-bitwidth $(NO_COL)\n"
@ -720,6 +746,13 @@ $(BUILD_DIR)/%.o: $(BUILD_DIR)/%.c
@$(CC_CHECK) $(CC_CHECK_CFLAGS) -MMD -MP -MT $@ -MF $(BUILD_DIR)/$*.d $<
$(V)$(CC) -c $(CFLAGS) -o $@ $<
ifeq ($(TARGET_NDS),1)
$(BUILD_DIR)/arm7/%.o: %.c
$(call print,Compiling:,$<,$@)
@$(CC_CHECK) $(ARM7_CC_CHECK_CFLAGS) -MMD -MP -MT $@ -MF $(BUILD_DIR)/arm7/$*.d $<
$(V)$(CC) -c $(ARM7_CFLAGS) -o $@ $<
endif
# Alternate compiler flags needed for matching
ifeq ($(COMPILER),ido)
$(BUILD_DIR)/levels/%/leveldata.o: OPT_FLAGS := -g
@ -797,9 +830,10 @@ $(BUILD_DIR)/rsp/%.bin $(BUILD_DIR)/rsp/%_data.bin: rsp/%.s
# Build NDS ROM
ifeq ($(TARGET_NDS),1)
$(ROM): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(ULTRA_O_FILES) $(GODDARD_O_FILES)
$(LD) -L $(BUILD_DIR) -o $@.elf $(O_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(LDFLAGS)
ndstool -c $@ -9 $@.elf
$(ROM): $(O_FILES) $(ARM7_O_FILES) $(MIO0_FILES:.mio0=.o) $(ULTRA_O_FILES) $(GODDARD_O_FILES)
$(LD) -L $(BUILD_DIR) -o $@.arm9.elf $(O_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(LDFLAGS)
$(LD) -L $(BUILD_DIR) -o $@.arm7.elf $(ARM7_O_FILES) $(ARM7_LDFLAGS)
ndstool -c $@ -9 $@.arm9.elf -7 $@.arm7.elf
else
# Run linker script through the C preprocessor

View file

@ -16,9 +16,9 @@ A prior copy of the game is required to extract the assets.
## Linux installation
* First follow [the guide for installing devkitPro packages](https://devkitpro.org/wiki/Getting_Started), also installing the `nds-dev` group as mentioned
* Install the needed tools
* Debian/Ubuntu: `sudo apt install -y build-essential git python`
* Fedora: `sudo dnf install gcc make git python`
* Arch/derivatives like Manjaro: `sudo pacman -S base-devel git python`
* Debian/Ubuntu: `sudo apt install -y build-essential git python sox`
* Fedora: `sudo dnf install gcc make git python sox`
* Arch/derivatives like Manjaro: `sudo pacman -S base-devel git python sox`
* Clone this repository and change to its directory
```
git clone https://github.com/Hydr8gon/sm64.git

View file

@ -433,7 +433,6 @@ extern void func_802ad74c(u32 bits, u32 arg);
extern void func_802ad770(u32 bits, s8 arg);
static void update_background_music_after_sound(u8 bank, u8 soundIndex);
static void update_game_sound(void);
static void fade_channel_volume_scale(u8 player, u8 channelId, u8 targetScale, u16 fadeTimer);
void process_level_music_dynamics(void);
static u8 begin_background_music_fade(u16 fadeDuration);
@ -1341,7 +1340,7 @@ void audio_signal_game_loop_tick(void) {
/**
* Called from threads: thread4_sound, thread5_game_loop (EU and SH only)
*/
static void update_game_sound(void) {
void update_game_sound(void) {
u8 soundStatus;
u8 i;
u8 soundId;

View file

@ -32,6 +32,7 @@ struct SPTask *func_sh_802f5a80(void);
#endif
void play_sound(s32 soundBits, f32 *pos);
void audio_signal_game_loop_tick(void);
void update_game_sound(void);
void seq_player_fade_out(u8 player, u16 fadeDuration);
void fade_volume_scale(u8 player, u8 targetScale, u16 fadeDuration);
void seq_player_lower_volume(u8 player, u16 fadeDuration, u8 percentage);

View file

@ -1314,6 +1314,10 @@ void audio_reset_session(void) {
#endif
gNotes = soundAlloc(&gNotesAndBuffersPool, gMaxSimultaneousNotes * sizeof(struct Note));
#ifdef TARGET_NDS
// Point to the uncached RAM mirror so both CPUs can access the data reliably
gNotes = (struct Note*)((u32)gNotes + 0xA000000);
#endif
note_init_all();
init_note_free_list();

View file

@ -2750,7 +2750,7 @@ void process_sequences(UNUSED s32 iterationsRemaining) {
#endif
}
}
#if defined(VERSION_JP) || defined(VERSION_US)
#if (defined(VERSION_JP) || defined(VERSION_US)) && !defined(TARGET_NDS)
reclaim_notes();
#endif
process_notes();

53
src/nds/arm7/main.c Normal file
View file

@ -0,0 +1,53 @@
#include "../nds_include.h"
#include "nds_audio.h"
struct Note *gNotes;
static bool running;
static void send_input(void) {
inputGetAndSend();
}
static void update_audio(void) {
// Request an audio update from the ARM9
IPC_SendSync(0);
swiIntrWait(0, IRQ_IPC_SYNC);
// Play the current notes
play_notes(gNotes);
}
static void power_down(void) {
running = false;
}
int main(void) {
irqInit();
fifoInit();
touchInit();
readUserSettings();
installSystemFIFO();
setPowerButtonCB(power_down);
SetYtrigger(80);
irqSet(IRQ_VCOUNT, send_input);
irqEnable(IRQ_VCOUNT | IRQ_IPC_SYNC);
// Get a pointer to the audio data from the ARM9
while (!fifoCheckValue32(FIFO_USER_01));
gNotes = (struct Note*)fifoGetValue32(FIFO_USER_01);
// Prepare to update the audio at 240 Hz
enableSound();
timerStart(0, ClockDivider_64, TIMER_FREQ_64(240), update_audio);
running = true;
// Wait idly for interrupts
while (running) {
swiWaitForVBlank();
}
return 0;
}

48
src/nds/arm7/nds_audio.c Normal file
View file

@ -0,0 +1,48 @@
#include "../nds_include.h"
#include "nds_audio.h"
static u32 calculate_vol_pan(struct Note *note) {
// Calculate the DS volume and pan values for a note
u32 vol = (note->targetVolLeft + note->targetVolRight) / 2;
u32 pan = (vol << 14) / note->targetVolLeft;
vol >>= 7;
pan >>= 8;
if (vol > 127) vol = 127;
if (pan > 127) pan = 127;
return SOUND_VOL(vol) | SOUND_PAN(pan);
}
void play_notes(struct Note *notes) {
// Play notes on the 16 sound channels of the DS
// The samples are converted to DS ADPCM at compile time, so they can be played directly
for (int i = 0; i < 16; i++) {
struct Note *note = &notes[i];
if (note->enabled && note->sound != NULL) {
if (note->needsInit) {
const struct AudioBankSample *sample = note->sound->sample;
const u32 loop = (sample->loop->count ? SOUND_REPEAT : SOUND_ONE_SHOT);
// Ensure the channel is properly reset
SCHANNEL_CR(i) &= ~SCHANNEL_ENABLE;
// Start playing a note on the current channel
SCHANNEL_SOURCE(i) = (u32)sample->sampleAddr;
SCHANNEL_REPEAT_POINT(i) = sample->loop->start / sizeof(u32);
SCHANNEL_LENGTH(i) = (sample->loop->end - sample->loop->start) / sizeof(u32);
SCHANNEL_TIMER(i) = SOUND_FREQ((u16)(note->frequency * 32768));
SCHANNEL_CR(i) = SCHANNEL_ENABLE | SOUND_FORMAT_ADPCM | calculate_vol_pan(note) | loop;
note->needsInit = false;
} else if (SCHANNEL_CR(i) & SCHANNEL_ENABLE) {
// Update the parameters of a currently playing note
SCHANNEL_TIMER(i) = SOUND_FREQ((u16)(note->frequency * 32768));
SCHANNEL_CR(i) = (SCHANNEL_CR(i) & ~(SOUND_VOL(127) | SOUND_PAN(127))) | calculate_vol_pan(note);
}
} else {
// Disable the channel if no note should play
SCHANNEL_CR(i) &= ~SCHANNEL_ENABLE;
}
}
}

8
src/nds/arm7/nds_audio.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef NDS_AUDIO_H
#define NDS_AUDIO_H
#include "audio/load.h"
extern void play_notes(struct Note *notes);
#endif // NDS_AUDIO_H

86
src/nds/main.c Normal file
View file

@ -0,0 +1,86 @@
#include <stdio.h>
#include "nds_include.h"
#include <fat.h>
#include "audio/data.h"
#include "audio/external.h"
#include "audio/load.h"
#include "audio/seqplayer.h"
#include "game/game_init.h"
#include "nds_renderer.h"
OSMesg D_80339BEC;
OSMesgQueue gSIEventMesgQueue;
s8 gResetTimer;
s8 D_8032C648;
s8 gDebugLevelSelect;
s8 gShowProfiler;
s8 gShowDebugText;
u8 audio_state;
static u8 audio_step;
void set_vblank_handler(UNUSED s32 index, UNUSED struct VblankHandler *handler, UNUSED OSMesgQueue *queue, UNUSED OSMesg *msg) {
}
void dispatch_audio_sptask(UNUSED struct SPTask *spTask) {
}
void send_display_list(struct SPTask *spTask) {
draw_frame((Gfx*)spTask->task.t.data_ptr);
}
static void update_audio(void) {
// Update audio at the ARM7's request
if (audio_state == 0) {
// Update the audio logic at 30 Hz
if ((audio_step = (audio_step + 1) & 7) == 0) {
update_game_sound();
gAudioFrameCount += 2;
gAudioRandom = ((gAudioRandom + gAudioFrameCount) * gAudioFrameCount);
}
// Update the sequences at 240 Hz
process_sequences(0);
} else if (audio_state == 1) {
// Disable audio
for (int i = 0; i < 16; i++) {
gNotes[i].enabled = false;
}
audio_state = 2;
}
// Tell the ARM7 it can go ahead
IPC_SendSync(0);
}
int main(void) {
static u64 pool[0x165000 / sizeof(u64)];
main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0]));
gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT);
renderer_init();
#ifdef LIBFAT
if (!fatInitDefault()) {
printf("Failed to initialize libfat!\n");
}
#endif
audio_init();
sound_init();
// Set up audio on the ARM9 side
irqSet(IRQ_IPC_SYNC, update_audio);
irqEnable(IRQ_IPC_SYNC);
// Give the ARM7 a pointer to the audio data
fifoSendValue32(FIFO_USER_01, (u32)gNotes);
// Run the game
thread5_game_loop(NULL);
return 0;
}

View file

@ -2,6 +2,8 @@
#include "lib/src/osContInternal.h"
extern u8 audio_state;
s32 osContInit(UNUSED OSMesgQueue *mq, u8 *controllerBits, UNUSED OSContStatus *status) {
*controllerBits = 1;
return 0;
@ -59,4 +61,8 @@ void osContGetReadData(OSContPad *pad) {
touchRead(&pos);
pad->button |= (pos.px < 128) ? L_CBUTTONS : R_CBUTTONS;
}
if (keysDown() & KEY_SELECT) {
audio_state = !audio_state;
}
}

View file

@ -1,48 +0,0 @@
#include <stdio.h>
#include "nds_include.h"
#include <fat.h>
#include "audio/external.h"
#include "game/game_init.h"
#include "nds_renderer.h"
OSMesg D_80339BEC;
OSMesgQueue gSIEventMesgQueue;
s8 gResetTimer;
s8 D_8032C648;
s8 gDebugLevelSelect;
s8 gShowProfiler;
s8 gShowDebugText;
void set_vblank_handler(UNUSED s32 index, UNUSED struct VblankHandler *handler, UNUSED OSMesgQueue *queue, UNUSED OSMesg *msg) {
}
void dispatch_audio_sptask(UNUSED struct SPTask *spTask) {
}
void send_display_list(struct SPTask *spTask) {
draw_frame((Gfx*)spTask->task.t.data_ptr);
}
int main(void) {
static u64 pool[0x165000 / sizeof(u64)];
main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0]));
gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT);
renderer_init();
#ifdef LIBFAT
if (!fatInitDefault()) {
printf("Failed to initialize libfat!\n");
}
#endif
audio_init();
sound_init();
thread5_game_loop(NULL);
return 0;
}

View file

@ -165,6 +165,31 @@ def parse_aifc(data, name, fname):
loop = parse_aifc_loop(vadpcm_loops) if vadpcm_loops is not None else None
return Aifc(name, fname, audio_data, sample_rate, book, loop)
def parse_ima(data, name, fname, bank_name):
sample_rate = None
loop = None
with open("sound/samples/" + bank_name + "/" + name + ".aiff", "rb") as aiff:
aiff_data = aiff.read()
i = 12
sections = []
while i < len(aiff_data):
tp = aiff_data[i : i + 4]
(le,) = struct.unpack(">I", aiff_data[i + 4 : i + 8])
i += 8
sections.append((tp, aiff_data[i : i + le]))
i = align(i + le, 2)
for (tp, aiff_data) in sections:
if tp == b"COMM":
sample_rate = parse_f80(aiff_data[8:18])
elif tp == b"MARK":
start = (struct.unpack('>I', aiff_data[4:8])[0] >> 1) + 4
end = (struct.unpack('>I', aiff_data[16:20])[0] >> 1) + 4
loop = Loop(start, end, 1, None)
return Aifc(name, fname, b"\0\0\0\0" + data, sample_rate, None, loop)
class ReserveSerializer:
def __init__(self):
@ -566,22 +591,31 @@ def serialize_ctl(bank, base_ser, is_shindou):
# Book
book_addr_buf.append(pack("P", ser.size))
ser.add(pack("ii", aifc.book.order, aifc.book.npredictors))
for x in aifc.book.table:
ser.add(pack("h", x))
if aifc.book is None:
ser.add(pack("ii", 0, 0))
else:
ser.add(pack("ii", aifc.book.order, aifc.book.npredictors))
for x in aifc.book.table:
ser.add(pack("h", x))
ser.align(16)
# Loop
loop_addr_buf.append(pack("P", ser.size))
if aifc.loop is None:
assert sample_len % 9 in [0, 1]
end = sample_len // 9 * 16 + (sample_len % 2) + (sample_len % 9)
ser.add(pack("IIiI", 0, end, 0, 0))
if aifc.fname.endswith(".ima"):
if aifc.loop is None:
ser.add(pack("IIiI", 0, sample_len, 0, 0))
else:
ser.add(pack("IIiI", aifc.loop.start, aifc.loop.end, aifc.loop.count, 0))
else:
ser.add(pack("IIiI", aifc.loop.start, aifc.loop.end, aifc.loop.count, 0))
assert aifc.loop.count != 0
for x in aifc.loop.state:
ser.add(pack("h", x))
if aifc.loop is None:
assert sample_len % 9 in [0, 1]
end = sample_len // 9 * 16 + (sample_len % 2) + (sample_len % 9)
ser.add(pack("IIiI", 0, end, 0, 0))
else:
ser.add(pack("IIiI", aifc.loop.start, aifc.loop.end, aifc.loop.count, 0))
assert aifc.loop.count != 0
for x in aifc.loop.state:
ser.add(pack("h", x))
ser.align(16)
env_name_to_addr = {}
@ -992,14 +1026,15 @@ def main():
entries = []
for f in sorted(os.listdir(dir)):
fname = os.path.join(dir, f)
if not f.endswith(".aifc"):
continue
try:
with open(fname, "rb") as inf:
data = inf.read()
entries.append(parse_aifc(data, f[:-5], fname))
if f.endswith(".aifc"):
entries.append(parse_aifc(data, f[:-5], fname))
elif f.endswith(".ima"):
entries.append(parse_ima(data, f[:-4], fname, name))
except Exception as e:
fail("malformed AIFC file " + fname + ": " + str(e))
fail("malformed audio file " + fname + ": " + str(e))
if entries:
sample_bank = SampleBank(name, entries)
sample_banks.append(sample_bank)