diff --git a/Makefile b/Makefile index 1e304a7744a..7a80f5ad89e 100644 --- a/Makefile +++ b/Makefile @@ -240,6 +240,7 @@ SBC := tools/audio/sbc SFC := tools/audio/sfc SFPATCH := tools/audio/sfpatch ATBLGEN := tools/audio/atblgen +AFILE_SIZES := tools/audio/afile_sizes # We want linemarkers in sequence assembly files for better assembler error messages SEQ_CPP := $(CPP) -x assembler-with-cpp -fno-dollars-in-identifiers SEQ_CPPFLAGS := -D_LANGUAGE_ASEQ -DMML_VERSION=MML_VERSION_OOT $(CPP_DEFINES) -I include -I include/audio -I include/tables/sfx -I $(BUILD_DIR)/assets/audio/soundfonts @@ -844,7 +845,8 @@ $(BUILD_DIR)/assets/audio/soundfonts/%.o: $(BUILD_DIR)/assets/audio/soundfonts/% # patch defined symbols to be ABS symbols so that they remain file-relative offsets forever $(SFPATCH) $(@:.o=.tmp2) $(@:.o=.tmp2) # write start and size symbols afterwards, filename != symbolic name so source symbolic name from the .name file written by sfc - $(OBJCOPY) --add-symbol $$(cat $(<:.c=.name))_Start=.rodata:0,global --redefine-sym __LEN__=$$(cat $(<:.c=.name))_Size $(@:.o=.tmp2) $@ +# also write a .note.name section containing the symbolic name of the soundfont + $(OBJCOPY) --add-symbol $$(cat $(<:.c=.name) | head -c -1)_Start=.rodata:0,global --redefine-sym __LEN__=$$(cat $(<:.c=.name) | head -c -1)_Size --add-section .note.name=$(<:.c=.name) $(@:.o=.tmp2) $@ # cleanup temp files @$(RM) $(@:.o=.tmp) $(@:.o=.tmp2) ifeq ($(AUDIO_BUILD_DEBUG),1) @@ -904,6 +906,16 @@ endif $(BUILD_DIR)/assets/audio/sequence_font_table.o: $(BUILD_DIR)/assets/audio/sequence_font_table.s $(AS) $(ASFLAGS) $< -o $@ +# make headers with file sizes and amounts + +$(BUILD_DIR)/src/audio/session_config.o: $(BUILD_DIR)/assets/audio/soundfont_sizes.h $(BUILD_DIR)/assets/audio/sequence_sizes.h + +$(BUILD_DIR)/assets/audio/soundfont_sizes.h: $(SOUNDFONT_O_FILES) + $(AFILE_SIZES) $@ NUM_SOUNDFONTS SOUNDFONT_SIZES .rodata $^ + +$(BUILD_DIR)/assets/audio/sequence_sizes.h: $(SEQUENCE_O_FILES) + $(AFILE_SIZES) $@ NUM_SEQUENCES SEQUENCE_SIZES .data $^ + # Extra audiobank padding that doesn't belong to any soundfont file $(BUILD_DIR)/assets/audio/audiobank_padding.o: echo ".section .rodata; .fill 0x20" | $(AS) $(ASFLAGS) -o $@ diff --git a/docs/audio/Samplebank_XML.md b/docs/audio/Samplebank_XML.md new file mode 100644 index 00000000000..b9c027bcb7f --- /dev/null +++ b/docs/audio/Samplebank_XML.md @@ -0,0 +1,77 @@ +# Samplebank XML Format Specification + +Samplebank XMLs describe a samplebank file that contains compressed waveform data. It specifies which sample files to include as well as certain global properties such as the index of this samplebank. + +--- + +```xml + +``` +Begins a new samplebank. + +**Attributes** + +- **Name**: The name of the samplebank. +- **Index**: The index of the samplebank for the samplebank table. Must be a unique index for all samplebanks and pointers. +- **Medium**: The storage medium, from the `SampleMedium` enum. +- **CachePolicy**: The cache policy, from the `AudioCacheLoadType` enum. +- [Optional] **BufferBug**: Whether this samplebank suffers from a buffer clearing bug present in the original audio tools. For matching only. + +**Tags** + +- + ```xml + + ``` + Create an alternate index that refers to this samplebank. + + **Attributes** + + - **Index**: The alternative index, must be unique among all samplebanks and pointers. + + --- + +- + ```xml + + ``` + Adds a **compressed** sample file to the samplebank. The sample should be single-channel and big-endian, in a format that is recognizable by the audio driver such as: pcm16, vadpcm, or half-frame vadpcm. + + **Attributes** + + - **Name**: Name of this sample. Must be a valid C language identifier. + - **Path**: Path to aifc file relative to the project root (typically in `$(BUILD_DIR)/assets/audio/samples/`) + + --- + +- + ```xml + + ``` + Adds a binary blob to the samplebank. Intended for matching only when data cannot be identified. + + **Attributes** + + - **Name**: Name of this blob. Must be a valid C language identifier. + - **Path**: Path to binary file, relative to the project root (typically in `$(BUILD_DIR)/assets/audio/samples/`) + + --- + +```xml + +``` +--- diff --git a/docs/audio/Soundfont_XML.md b/docs/audio/Soundfont_XML.md new file mode 100644 index 00000000000..c2d8a572edf --- /dev/null +++ b/docs/audio/Soundfont_XML.md @@ -0,0 +1,319 @@ +# Soundfont XML Format Specification + +Soundfont XMLs describe the layout of a single soundfont. These package raw samples together into instruments, of which there are three kinds: +- **Effects**: These are simple sound effects that just play a single sample without any modulation. +- **Drums**: These define a MIDI-style percussion key map. +- **Instruments**: These are instruments that may be played at any key with up to three voices and may be modulated by an envelope. + +In the specification, `Note Name`s can be either a MIDI note name e.g. `C4` or it may be a **Zelda64** note number, which are related to MIDI note numbers ($n$) by $(n - 21) \mod 128$. + +--- + +```xml + +``` +Begins a new soundfont. + +**Attributes** +- **Name**: Soundfont symbol name. Must be a valid C identifier. +- **Index**: Soundfont index. Must be an integer. +- **Medium**: Storage medium. Must be an enum name from `SampleMedium`. +- **CachePolicy**: Cache policy. Must be an enum name from `AudioCacheLoadType`. +- **SampleBank**: Path to samplebank xml used by this soundfont. +- [Optional] **Indirect**: Pointer index if the samplebank is referenced indirectly. +- [Optional] **SampleBankDD**: Path to samplebank xml used for DD medium. +- [Optional] **IndirectDD**: Pointer index if the DD samplebank is referenced indirectly. +- [Optional] **LoopsHaveFrames**: Whether loops in this soundfont store the total frame count of the sample. Must be a boolean. +- [Optional] **PadToSize**: For matching only. Specifies the total file size the result output should be padded to. +- [Optional] **NumInstruments**: For matching only. Specifies the total number of instrument pointers. Usually this is automatically assigned based on `max(program_number) + 1` but some vanilla banks don't match this way. + +**Tags** + +- + ```xml + + ``` + Lists envelopes defined in this soundfont. + + **Attributes** + + N/A + + **Tags** + + - + ```xml + + ``` + Starts a new envelope. + + **Attributes** + + - **Name**: Unique name for this envelope. Must be a valid C identifier. + - **Release**: Release rate index (into `gAudioCtx.adsrDecayTable`) for this envelope + + **Tags** + + - + ```xml + + ``` + Add a point to the envelope at (delay, arg) + + **Attributes** + + - **Delay**: Duration until the next point + - **Arg**: Value of the envelope at this point + + --- + + - + ```xml + + ``` + Insert a ADSR_DISABLE command + + --- + + - + ```xml + + ``` + Insert a ADSR_HANG command + + --- + + - + ```xml + + ``` + Insert a ADSR_GOTO command + + **Attributes** + + - **Index**: Index of the envelope point to jump to + + --- + + ```xml + + ``` + --- + + ```xml + + ``` + --- + +- + ```xml + + ``` + Begins a list of samples used in this Soundfont. + + **Attributes** + + - [Optional] **IsDD**: Whether all the samples in the list are 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`.** + - [Optional] **Cached**: Whether all the samples in the list should be added to the `usedSamples` cache. **Default is `false`.** + + **Tags** + + - + ```xml + + ``` + Declares a sample used in this soundfont. + + **Attributes** + + - **Name**: The name of this sample. A sample with this name must be present in the samplebank used by the soundfont. + - [Optional] **SampleRate**: An overriding sample rate for this sample. **Default comes from the sample file.** + - [Optional] **BaseNote**: An overriding root key for this sample. **Default comes from the sample file.** + - [Optional] **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`.** + - [Optional] **Cached**: Whether this sample should be added to the `usedSamples` cache. **Default is `false`.** + + --- + + ```xml + + ``` + --- + +- + ```xml + + ``` + Begins a list of sound effects to define for this soundfont. Sound effects correspond to simple sounds that cannot be played at different keys. + + **Attributes** + + N/A + + **Tags** + + - + ```xml + + ``` + Defines a single sound effect. + + **Attributes** + - **Name**: The name of the sound effect, the name is made available in sequence files in the form `SF{n}_{name}` where `n` is the index of this soundfont and `name` is this name. For example, if `n=0` and `name=ExampleEffect` the name to use in sequence files is `SF0_ExampleEffect`. + - **Sample**: The name of the sample associated with this effect. + - [Optional] **SampleRate**: An overriding sample rate for this effect. **Default comes from the sample definition.** + - [Optional] **BaseNote**: An overriding root key for this effect. **Default comes from the sample definition.** + + --- + + ```xml + + ``` + --- + +- + ```xml + + ``` + Begins the percussion definitions for this soundfont. Percussion corresponds to the MIDI notion of percussion, where single samples are mapped across a range of keys. + + **Attributes** + + N/A + + **Tags** + + - + ```xml + + ``` + Defines a single percussion range. + + **Attributes** + - **Name**: The name of this sound. Definitions are emitted for sequence files in the form `SF{n}_{name}_{note}` for every note covered by this sound. + - [Optional] **Note**: The key to map this sound to. Should not overlap with other definitions. **If this field is left unspecified, `NoteStart` and `NoteEnd` become required.** + - [Optional] **NoteStart**: The first key that is mapped to this sound. Should not overlap with other definitions. **If this field is left unspecified, `Note` becomes required. If this field is specified, `NoteEnd` must also be specified.** + - [Optional] **NoteEnd**: The last key that is mapped to this sound. Should not overlap with other definitions. **If this field is left unspecified, `Note` becomes required. If this field is specified, `NoteStart` must also be specified.** + - **Pan**: The stereo weight for this sound. Center=`64`. + - **Envelope**: The envelope to modulate the volume over time with. Must be defined in the `Envelopes` list. + - [Optional] **Release**: An override for the envelope release rate. **Default is the release rate specified in the envelope definition** + - **Sample**: The name of the sample to use. + - [Optional] **SampleRate**: An overriding sample rate for this sound. **Default comes from the sample definition.** + - [Optional] **BaseNote**: An overriding root key for this sound. **Default comes from the sample definition.** + + --- + + ```xml + + ``` + --- + +- + ```xml + + ``` + Begins the instrument definitions for this soundfont. Instruments correspond to the MIDI notion of instruments, with up to 3 samples (voices) per instrument that must map to contiguous ranges of notes. + + **Attributes** + + N/A + + **Tags** + + - + ```xml + + ``` + Defines an instrument. + + **Attributes** + - **ProgramNumber**: MIDI Program Number for this instrument. Must be in the range `0 <= n <= 125` + - **Name**: The name of this instrument. + - **Envelope**: Envelope to use, identified by name. + - [Optional] **Release**: Release rate index override. **Default release rate comes from the chosen envelope.** + - **Sample**: The name of the middle sample to use for this instrument. + - [Optional] **SampleRate**: Sample rate override for the middle sample. **Default is sourced from the sample properties.** + - [Optional] **BaseNote**: Base note override for the middle sample. **Default is sourced from the sample properties.** + - [Optional] **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.** + - [Optional] **SampleLo**: The name of the low sample to use for this instrument. + - [Optional] **SampleRateLo**: Sample rate override for the low sample. **Default is sourced from the sample properties.** + - [Optional] **BaseNoteLo**: Base note override for the low sample. **Default is sourced from the sample properties.** + - [Optional] **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.** + - [Optional] **SampleHi**: The name of the high sample to use for this instrument. + - [Optional] **SampleRateHi**: Sample rate override for the high sample. **Default is sourced from the sample properties.** + - [Optional] **BaseNoteHi**: Base note override for the high sample. **Default is sourced from the sample properties.** + + --- + + ```xml + + ``` + --- + +```xml + +``` +--- diff --git a/docs/audio/build_flowchart.png b/docs/audio/build_flowchart.png new file mode 100644 index 00000000000..9ef26221ee5 Binary files /dev/null and b/docs/audio/build_flowchart.png differ diff --git a/include/audio/aseq.h b/include/audio/aseq.h index ac600738b07..689c42664f5 100644 --- a/include/audio/aseq.h +++ b/include/audio/aseq.h @@ -558,10 +558,9 @@ _RESET_SECTION .macro .startseq name /* Begin a sequence. */ - /* Write the sequence name into a special .name section */ - .pushsection .name, "", @note + /* Write the sequence name into a special .note.name section */ + .pushsection .note.name, "", @note .asciz "\name" - .balign 4 .popsection /* Reset section and write start symbol. */ diff --git a/include/libc/assert.h b/include/libc/assert.h index 96393c0e7c0..dae8aeb651d 100644 --- a/include/libc/assert.h +++ b/include/libc/assert.h @@ -29,7 +29,7 @@ __attribute__((noreturn)) void __assert(const char* assertion, const char* file, // Static/compile-time assertions -#if defined(__GNUC__) || (__STDC_VERSION__ >= 201112L) +#if !defined(__sgi) && (defined(__GNUC__) || (__STDC_VERSION__ >= 201112L)) # define static_assert(cond, msg) _Static_assert(cond, msg) #else # ifndef GLUE diff --git a/include/sfx.h b/include/sfx.h index 47a449e9c43..16d0b122d3c 100644 --- a/include/sfx.h +++ b/include/sfx.h @@ -4,6 +4,7 @@ #include "ultra64.h" #include "versions.h" #include "z64math.h" +#include "libc/assert.h" typedef enum SfxBankType { /* 0 */ BANK_PLAYER, @@ -63,23 +64,48 @@ typedef struct SfxBankEntry { typedef enum SfxId { NA_SE_NONE, // Requesting a sfx with this id will play no sound + NA_SE_PL_BASE = 0x7FF, #include "tables/sfx/playerbank_table.h" + NA_SE_PL_END, + NA_SE_IT_BASE = 0x17FF, #include "tables/sfx/itembank_table.h" + NA_SE_IT_END, + NA_SE_EV_BASE = 0x27FF, #include "tables/sfx/environmentbank_table.h" + NA_SE_EV_END, + NA_SE_EN_BASE = 0x37FF, #include "tables/sfx/enemybank_table.h" + NA_SE_EN_END, + NA_SE_SY_BASE = 0x47FF, #include "tables/sfx/systembank_table.h" + NA_SE_SY_END, + NA_SE_OC_BASE = 0x57FF, #include "tables/sfx/ocarinabank_table.h" + NA_SE_OC_END, + NA_SE_VO_BASE = 0x67FF, #include "tables/sfx/voicebank_table.h" + NA_SE_VO_END, + NA_SE_MAX } SfxId; +// These limits are due to the way Sequence 0 is programmed. There is also a global limit of 512 entries for every bank +// enforced in Audio_PlayActiveSfx in sfx.c +static_assert(NA_SE_PL_END - (NA_SE_PL_BASE + 1) <= 256, "Player Bank SFX Table is limited to 256 entries due to Sequence 0"); +static_assert(NA_SE_IT_END - (NA_SE_IT_BASE + 1) <= 128, "Item Bank SFX Table is limited to 128 entries due to Sequence 0"); +static_assert(NA_SE_EV_END - (NA_SE_EV_BASE + 1) <= 256, "Environment Bank SFX Table is limited to 256 entries due to Sequence 0"); +static_assert(NA_SE_EN_END - (NA_SE_EN_BASE + 1) <= 512, "Enemy Bank SFX Table is limited to 512 entries due to Sequence 0"); +static_assert(NA_SE_SY_END - (NA_SE_SY_BASE + 1) <= 128, "System Bank SFX Table is limited to 128 entries due to Sequence 0"); +static_assert(NA_SE_OC_END - (NA_SE_OC_BASE + 1) <= 128, "Ocarina Bank SFX Table is limited to 128 entries due to Sequence 0"); +static_assert(NA_SE_VO_END - (NA_SE_VO_BASE + 1) <= 256, "Voice Bank SFX Table is limited to 256 entries due to Sequence 0"); + #undef DEFINE_SFX #define SFX_BANK_SHIFT(sfxId) (((sfxId) >> 12) & 0xFF) diff --git a/linker_scripts/soundfont.ld b/linker_scripts/soundfont.ld index d914a7de313..c480e368677 100644 --- a/linker_scripts/soundfont.ld +++ b/linker_scripts/soundfont.ld @@ -4,14 +4,15 @@ OUTPUT_ARCH (mips) SECTIONS { - .rodata : + .rodata ALIGN(16) : { *(.data*) *(.rodata*) . = ALIGN(16); - __LEN__ = . - ADDR(.rodata); } + __LEN__ = ABSOLUTE(SIZEOF(.rodata)); + /DISCARD/ : { *(*); diff --git a/src/audio/session_config.c b/src/audio/session_config.c index 8c2824eee45..27771515a26 100644 --- a/src/audio/session_config.c +++ b/src/audio/session_config.c @@ -1,4 +1,8 @@ #include "global.h" +#include "assets/audio/sequence_sizes.h" +#include "assets/audio/soundfont_sizes.h" +#define SFX_SEQ_SIZE Sequence_0_SIZE +#define SFX_SOUNDFONTS_SIZE (Soundfont_0_SIZE + Soundfont_1_SIZE) AudioContext gAudioCtx; AudioCustomUpdateFunction gAudioCustomUpdateFunction; @@ -9,19 +13,13 @@ const TempoData gTempoData = { SEQTICKS_PER_BEAT, // seqTicksPerBeat }; -// TODO: Extract from table? -#define NUM_SOUNDFONTS 38 -#define SFX_SEQ_SIZE 0x6A90 -#define SFX_SOUNDFONT_1_SIZE 0x3AA0 -#define SFX_SOUNDFONT_2_SIZE 0x17B0 - // Sizes of everything on the init pool #define AI_BUFFERS_SIZE (AIBUF_SIZE * ARRAY_COUNT(gAudioCtx.aiBuffers)) #define SOUNDFONT_LIST_SIZE (NUM_SOUNDFONTS * sizeof(SoundFont)) #if OOT_VERSION < PAL_1_0 || PLATFORM_GC -#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONT_1_SIZE + SFX_SOUNDFONT_2_SIZE) +#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONTS_SIZE) #else -#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONT_1_SIZE + SFX_SOUNDFONT_2_SIZE + 0x10) +#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONTS_SIZE + 0x10) #endif const AudioHeapInitSizes gAudioHeapInitSizes = { diff --git a/src/boot/z_std_dma.c b/src/boot/z_std_dma.c index 72d04dce1b1..350c98e02b5 100644 --- a/src/boot/z_std_dma.c +++ b/src/boot/z_std_dma.c @@ -27,7 +27,7 @@ #endif #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.2:88 pal-1.0:86 pal-1.1:86" + "ntsc-1.2:82 pal-1.0:80 pal-1.1:80" StackEntry sDmaMgrStackInfo; OSMesgQueue sDmaMgrMsgQueue; diff --git a/src/code/fault_gc.c b/src/code/fault_gc.c index 13dfb670adb..af693215907 100644 --- a/src/code/fault_gc.c +++ b/src/code/fault_gc.c @@ -42,8 +42,8 @@ */ #if PLATFORM_GC -#pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-eu-mq-dbg:176 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192" \ - "gc-us-mq:192" +#pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-eu-mq-dbg:160 gc-jp:176 gc-jp-ce:176 gc-jp-mq:176 gc-us:176" \ + "gc-us-mq:176" #include "global.h" #include "alloca.h" diff --git a/src/code/graph.c b/src/code/graph.c index 56765ae5a1f..2564d83dd9b 100644 --- a/src/code/graph.c +++ b/src/code/graph.c @@ -7,8 +7,8 @@ #define GFXPOOL_HEAD_MAGIC 0x1234 #define GFXPOOL_TAIL_MAGIC 0x5678 -#pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:160 ntsc-1.1:160 ntsc-1.2:160 pal-1.0:160 pal-1.1:160" +#pragma increment_block_number "gc-eu:0 gc-eu-mq:0 gc-jp:0 gc-jp-ce:0 gc-jp-mq:0 gc-us:0 gc-us-mq:0 ntsc-1.0:160" \ + "ntsc-1.1:160 ntsc-1.2:160 pal-1.0:160 pal-1.1:160" /** * The time at which the previous `Graph_Update` ended. diff --git a/src/code/main.c b/src/code/main.c index d11630a0a1a..8ad00b52bdc 100644 --- a/src/code/main.c +++ b/src/code/main.c @@ -24,7 +24,7 @@ extern struct IrqMgr gIrqMgr; #endif #pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160 gc-us-mq:160" \ - "ntsc-1.0:148 ntsc-1.1:148 ntsc-1.2:148 pal-1.0:146 pal-1.1:146" + "ntsc-1.0:141 ntsc-1.1:141 ntsc-1.2:141 pal-1.0:139 pal-1.1:139" extern u8 _buffersSegmentEnd[]; diff --git a/src/code/z_bgcheck.c b/src/code/z_bgcheck.c index 129900bdbb4..bb1b3a71f3d 100644 --- a/src/code/z_bgcheck.c +++ b/src/code/z_bgcheck.c @@ -1,7 +1,7 @@ #include "global.h" #include "terminal.h" -#pragma increment_block_number "ntsc-1.0:136 ntsc-1.1:136 ntsc-1.2:136" +#pragma increment_block_number "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128" u16 DynaSSNodeList_GetNextNodeIdx(DynaSSNodeList* nodeList); void BgCheck_GetStaticLookupIndicesFromPos(CollisionContext* colCtx, Vec3f* pos, Vec3i* sector); diff --git a/src/code/z_camera.c b/src/code/z_camera.c index 1ddc538dc5f..d5c97c109aa 100644 --- a/src/code/z_camera.c +++ b/src/code/z_camera.c @@ -3639,7 +3639,7 @@ s32 Camera_KeepOn3(Camera* camera) { } #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:90 ntsc-1.1:90 ntsc-1.2:90 pal-1.0:88 pal-1.1:88" + "ntsc-1.0:83 ntsc-1.1:83 ntsc-1.2:83 pal-1.0:81 pal-1.1:81" s32 Camera_KeepOn4(Camera* camera) { static Vec3f D_8015BD50; diff --git a/src/code/z_collision_check.c b/src/code/z_collision_check.c index 7904809bf9c..0567fd15a11 100644 --- a/src/code/z_collision_check.c +++ b/src/code/z_collision_check.c @@ -16,7 +16,7 @@ #include "z_lib.h" #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:104 ntsc-1.1:104 ntsc-1.2:104 pal-1.0:104 pal-1.1:104" + "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:96 pal-1.0:96 pal-1.1:96" typedef s32 (*ColChkResetFunc)(PlayState*, Collider*); typedef void (*ColChkApplyFunc)(PlayState*, CollisionCheckContext*, Collider*); diff --git a/src/code/z_debug.c b/src/code/z_debug.c index d83dc233754..d94e79e83be 100644 --- a/src/code/z_debug.c +++ b/src/code/z_debug.c @@ -12,8 +12,8 @@ typedef struct InputCombo { /* 0x2 */ u16 press; } InputCombo; // size = 0x4 -#pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192 gc-us-mq:192" \ - "ntsc-1.0:192 ntsc-1.1:192 ntsc-1.2:192 pal-1.0:192 pal-1.1:192" +#pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160 gc-us-mq:160" \ + "ntsc-1.0:160 ntsc-1.1:160 ntsc-1.2:160 pal-1.0:160 pal-1.1:160" RegEditor* gRegEditor; diff --git a/src/code/z_demo.c b/src/code/z_demo.c index 60368ddc37b..4cc4cf1d2cc 100644 --- a/src/code/z_demo.c +++ b/src/code/z_demo.c @@ -1,3 +1,5 @@ +#pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ + "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" #include "global.h" #include "quake.h" #include "z64camera.h" @@ -124,8 +126,8 @@ u16 gCamAtSplinePointsAppliedFrame; u16 gCamEyePointAppliedFrame; u16 gCamAtPointAppliedFrame; -#pragma increment_block_number "gc-eu:186 gc-eu-mq:176 gc-jp:188 gc-jp-ce:188 gc-jp-mq:176 gc-us:188 gc-us-mq:176" \ - "ntsc-1.0:80 ntsc-1.1:80 ntsc-1.2:80 pal-1.0:80 pal-1.1:80" +#pragma increment_block_number "gc-eu:0 gc-eu-mq:0 gc-jp:188 gc-jp-ce:188 gc-jp-mq:0 gc-us:188 gc-us-mq:0" \ + "ntsc-1.0:128 ntsc-1.1:80 ntsc-1.2:80 pal-1.0:80 pal-1.1:80" // Cam ID to return to when a scripted cutscene is finished s16 sReturnToCamId; diff --git a/src/code/z_kankyo.c b/src/code/z_kankyo.c index 4d92cc82b14..2980933cdd0 100644 --- a/src/code/z_kankyo.c +++ b/src/code/z_kankyo.c @@ -1,5 +1,5 @@ -#pragma increment_block_number "gc-eu:244 gc-eu-mq:244 gc-jp:224 gc-jp-ce:224 gc-jp-mq:224 gc-us:224 gc-us-mq:224" \ - "ntsc-1.0:224 ntsc-1.1:224 ntsc-1.2:224 pal-1.0:248 pal-1.1:248" +#pragma increment_block_number "gc-eu:232 gc-eu-mq:232 gc-jp:212 gc-jp-ce:212 gc-jp-mq:212 gc-us:212 gc-us-mq:212" \ + "ntsc-1.0:224 ntsc-1.1:224 ntsc-1.2:224 pal-1.0:240 pal-1.1:240" #include "global.h" #include "ultra64.h" diff --git a/src/code/z_message.c b/src/code/z_message.c index 368a53723e5..35e30abda5d 100644 --- a/src/code/z_message.c +++ b/src/code/z_message.c @@ -8,7 +8,7 @@ #endif #pragma increment_block_number "gc-eu:0 gc-eu-mq:0 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:112 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:96 pal-1.0:128 pal-1.1:128" #if !PLATFORM_GC #define OCARINA_BUTTON_A_PRIM_1_R 80 diff --git a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c index ed4aba58da0..84eb00334b0 100644 --- a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c +++ b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c @@ -7,7 +7,7 @@ #include "assets/objects/object_toki_objects/object_toki_objects.h" #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.2:128" + "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128" #define FLAGS (ACTOR_FLAG_4 | ACTOR_FLAG_5) diff --git a/src/overlays/actors/ovl_Fishing/z_fishing.c b/src/overlays/actors/ovl_Fishing/z_fishing.c index 4c5e2ab731b..26c337e6dc1 100644 --- a/src/overlays/actors/ovl_Fishing/z_fishing.c +++ b/src/overlays/actors/ovl_Fishing/z_fishing.c @@ -35,8 +35,8 @@ #include "cic6105.h" #endif -#pragma increment_block_number "gc-eu:177 gc-eu-mq:177 gc-jp:177 gc-jp-ce:177 gc-jp-mq:177 gc-us:177 gc-us-mq:177" \ - "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" +#pragma increment_block_number "gc-eu:170 gc-eu-mq:170 gc-jp:170 gc-jp-ce:170 gc-jp-mq:170 gc-us:170 gc-us-mq:170" \ + "ntsc-1.0:121 ntsc-1.1:121 ntsc-1.2:121 pal-1.0:121 pal-1.1:121" #define FLAGS ACTOR_FLAG_4 diff --git a/src/overlays/actors/ovl_player_actor/z_player.c b/src/overlays/actors/ovl_player_actor/z_player.c index d5dfcaa4b36..d7dca9a4ae7 100644 --- a/src/overlays/actors/ovl_player_actor/z_player.c +++ b/src/overlays/actors/ovl_player_actor/z_player.c @@ -332,21 +332,21 @@ void Player_Action_CsAction(Player* this, PlayState* play); // .bss part 1 #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:64 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:64 ntsc-1.1:64 ntsc-1.2:64 pal-1.0:128 pal-1.1:128" static s32 D_80858AA0; // TODO: There's probably a way to match BSS ordering with less padding by spreading the variables out and moving // data around. It would be easier if we had more options for controlling BSS ordering in debug. #pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192 gc-us-mq:192" \ - "ntsc-1.0:192 ntsc-1.1:128 ntsc-1.2:192 pal-1.0:192 pal-1.1:192" + "ntsc-1.0:192 ntsc-1.1:192 ntsc-1.2:192 pal-1.0:192 pal-1.1:192" static s32 sSavedCurrentMask; static Vec3f sInteractWallCheckResult; static Input* sControlInput; #pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160 gc-us-mq:160" \ - "ntsc-1.0:128 ntsc-1.1:192 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" // .data diff --git a/tools/audio/.gitignore b/tools/audio/.gitignore index f0d3c612fd1..5864deaf3c1 100644 --- a/tools/audio/.gitignore +++ b/tools/audio/.gitignore @@ -1,5 +1,6 @@ __pycache__/ +afile_sizes atblgen sfpatch sbc diff --git a/tools/audio/Makefile b/tools/audio/Makefile index e93bb158c12..fa74b8c8a97 100644 --- a/tools/audio/Makefile +++ b/tools/audio/Makefile @@ -1,4 +1,4 @@ -PROGRAMS := atblgen sfpatch sbc sfc +PROGRAMS := afile_sizes atblgen sbc sfc sfpatch ifeq ($(shell which xml2-config),) $(error xml2-config not found. Did you install libxml2-dev?) @@ -9,7 +9,7 @@ FORMAT_ARGS := -i -style=file CC := gcc CFLAGS := -Wall -Wextra -pedantic -OPTFLAGS := -Og -g3 +OPTFLAGS := -O2 XML_CFLAGS := $(shell xml2-config --cflags) XML_LDFLAGS := $(shell xml2-config --libs) @@ -30,10 +30,11 @@ format: $(CLANG_FORMAT) $(FORMAT_ARGS) $(shell find . -maxdepth 1 -type f -name "*.[ch]") $(MAKE) -C sampleconv format -atblgen_SOURCES := audio_tablegen.c samplebank.c soundfont.c xml.c util.c -sfpatch_SOURCES := sfpatch.c util.c -sbc_SOURCES := samplebank_compiler.c samplebank.c aifc.c xml.c util.c -sfc_SOURCES := soundfont_compiler.c samplebank.c soundfont.c aifc.c xml.c util.c +afile_sizes_SOURCES := afile_sizes.c util.c +atblgen_SOURCES := audio_tablegen.c samplebank.c soundfont.c xml.c util.c +sbc_SOURCES := samplebank_compiler.c samplebank.c aifc.c xml.c util.c +sfc_SOURCES := soundfont_compiler.c samplebank.c soundfont.c aifc.c xml.c util.c +sfpatch_SOURCES := sfpatch.c util.c atblgen_CFLAGS := $(XML_CFLAGS) sbc_CFLAGS := $(XML_CFLAGS) diff --git a/tools/audio/README.md b/tools/audio/README.md new file mode 100644 index 00000000000..95998dd944a --- /dev/null +++ b/tools/audio/README.md @@ -0,0 +1,59 @@ +# Z64 Audio Tools + +The Z64 Audio Tools work together to implement the full audio asset pipeline + +![](../../docs/audio/build_flowchart.png) + +**Licensing Information** +* The programs `atblgen`, `sampleconv`, `sbc` and `sfc` are (mostly) distributed under MPL-2.0. The VADPCM encoding and decoding portions of `sampleconv` are under CC0-1.0. +* The programs `sfpatch` and `afile_sizes` are distributed under CC0-1.0. +* The extraction tool is distributed under CC0-1.0. + +## sampleconv + +Converts aifc <-> aiff / wav + +Used in extraction and build to convert audio sample data between uncompressed mono 16-bit PCM and the compressed formats used by the audio driver. + +## SampleBank Compiler (sbc) + +Converts samplebank xml + aifc -> asm + +Samplebanks are converted to assembly files for building as it is easier to define the necessary absolute symbols, and they are pure unstructured data. + +## SoundFont Compiler (sfc) + +Converts soundfont & samplebank xml + aifc -> C + +Soundfonts are converted to C rather than assembly as it shares data structures with the audio driver code. Modifying the structures used by the driver without updating `sfc` to write them should error at compile-time rather than crash at runtime. + +## sfpatch + +`Usage: sfpatch in.elf out.elf` + +This tool patches the symbol table of an ELF file (`in.elf`) to make every defined symbol in the file an absolute symbol. This is a required step for building soundfonts from C source as all pointers internal to a soundfont are offset from the start of the soundfont file and not the audiobank segment as a whole. Making all defined symbols ABS symbols prevents the linker from updating their values later, ensuring they remain file-relative. + +## atblgen + +Generates various audio code tables. + +- Samplebank table: Specifies where in the `Audiotable` file each samplebank begins and how large it is. +- Soundfont table: Specifies where in the `Audiobank` files each soundfont begins, how large it is, which samplebanks it uses, and how many instruments/drums/sfx it contains. +- Sequence font table: Contains information on what soundfonts each sequence uses. Generated from the sequence object files that embed a `.note.fonts` section that holds this information. + +The sequence table is not generated as some things in that table are better left manually specified, such as sequence enum names and flags. This also lets us have the sequence table before assembling any sequence files which is nice for some sequence commands like `runseq`. + +## afile_sizes + +Produces header files containing binary file sizes for a given set of object files. Used to produce headers containing soundfont and sequence files and the number of each for use in code files. + +## extraction + +This collection of python files implements the extraction of audio data from a base ROM. + +Files that are designed to be used externally include: +- `audio_extract.py` is the main file for audio extraction, it expects an external script to call `extract_audio_for_version` with the necessary inputs. +- `disassemble_sequence.py` is runnable but is not used in this way in either extraction or building. It may be used to manually disassemble a sequence binary. +- `tuning.py` is runnable but is not used that way in either extraction or building. It may be used to manually determine alternative matches for the samplerate and basenote of a sample as the extraction procedure cannot always determine these uniquely. + +See individual python source files for further details on their purposes. diff --git a/tools/audio/afile_sizes.c b/tools/audio/afile_sizes.c new file mode 100644 index 00000000000..1bd87ef4834 --- /dev/null +++ b/tools/audio/afile_sizes.c @@ -0,0 +1,120 @@ +/* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET */ +/* SPDX-License-Identifier: CC0-1.0 */ +#include +#include +#include +#include + +#include "elf32.h" +#include "util.h" + +static int +usage(const char *progname) +{ + fprintf(stderr, + // clang-format off + "Generates a header containing definitions for the sizes of all the input object files and a" "\n" + "definition for the number of input files." "\n" + "Usage: %s
" "\n" + " header output path: Path to write the generated header to" "\n" + " num define: The name of the definition for the number of input files" "\n" + " header guard: The header guard definition name to be used for the output header" "\n" + " section name: The object file section to output the size of in each definition" "\n" + " object files: List of paths to each object file to be processed, each input object file" "\n" + " must contain the section requested in the section name argument and must" "\n" + " also contain a .note.name section containing the null-terminated symbolic " "\n" + " name of the object that is used to name the size definitions." "\n", + // clang-format on + progname); + return EXIT_FAILURE; +} + +int +main(int argc, char **argv) +{ + const char *progname = argv[0]; + + if (argc < 6) // progname, 4 required args, at least 1 input file + return usage(progname); + + const char *header_out = argv[1]; + const char *num_def = argv[2]; + const char *header_guard = argv[3]; + const char *secname = argv[4]; + int num_files = argc - 5; + char **files = &argv[5]; + + // Open the header for writing, write the header guard + + FILE *out = fopen(header_out, "w"); + if (out == NULL) + error("failed to open output file \"%s\" for writing: %s", header_out, strerror(errno)); + + fprintf(out, + // clang-format off + "#ifndef %s_H_" "\n" + "#define %s_H_" "\n" + "\n", + // clang-format on + header_guard, header_guard); + + // For each input elf file, write the size define + + for (int i = 0; i < num_files; i++) { + const char *path = files[i]; + + size_t data_size; + void *data = elf32_read(path, &data_size); + + Elf32_Shdr *shstrtab = elf32_get_shstrtab(data, data_size); + if (shstrtab == NULL) + error("Input file \"%s\" has no shstrtab?", path); + + // Read in the .note.name section containing the object's symbolic name. + // We run this on both soundfonts and sequences: + // - Soundfont .note.name sections are added with objcopy + // - Sequence .note.name sections are assembled as part of .startseq + + Elf32_Shdr *name_section = elf32_section_forname(".note.name", shstrtab, data, data_size); + if (name_section == NULL) + error("Input file \"%s\" has no name section?", path); + + uint32_t name_section_offset = elf32_read32(name_section->sh_offset); + uint32_t name_section_size = elf32_read32(name_section->sh_size); + validate_read(name_section_offset, name_section_size, data_size); + + const char *object_name = GET_PTR(data, name_section_offset); + if (strnlen(object_name, name_section_size + 1) >= name_section_size) + error("Input file \"%s\" name is not properly terminated?", path); + + // Read the section header for the data we're interested in, the name is given in the program args + + Elf32_Shdr *sec = elf32_section_forname(secname, shstrtab, data, data_size); + if (sec == NULL) + error("Input file \"%s\" has no section named \"%s\"?", path, secname); + + // Assumption: The section size matches the size in the _Size symbol, this is always + // true for soundfonts by nature of how they're built (cf. soundfont.ld) and should always be true + // of sequences since writing anything that results in output data before .startseq and after .endseq + // is essentially undefined. + size_t object_size = elf32_read32(sec->sh_size); + + fprintf(out, "#define %s_SIZE 0x%lX\n", object_name, object_size); + + free(data); + } + + // Write the total number of input files, end the header + + fprintf(out, + // clang-format off + "\n" + "#define %s %d" "\n" + "\n" + "#endif" "\n", + // clang-format on + num_def, num_files); + fclose(out); + + return EXIT_SUCCESS; +} diff --git a/tools/audio/aifc.c b/tools/audio/aifc.c index fcf83f87025..cadad5ae2be 100644 --- a/tools/audio/aifc.c +++ b/tools/audio/aifc.c @@ -534,14 +534,19 @@ aifc_read(aifc_data *af, const char *path, uint8_t *match_buf, size_t *match_buf void aifc_dispose(aifc_data *af) { - free(af->book_state); - af->has_book = false; + if (af->has_book) { + free(af->book_state); + af->has_book = false; + } af->has_loop = false; - free(af->compression_name); + if (af->compression_name != NULL) + free(af->compression_name); - for (size_t i = 0; i < af->num_markers; i++) - free((*af->markers)[i].label); - free(af->markers); + if (af->markers != NULL) { + for (size_t i = 0; i < af->num_markers; i++) + free((*af->markers)[i].label); + free(af->markers); + } } diff --git a/tools/audio/audio_tablegen.c b/tools/audio/audio_tablegen.c index f64ce2512da..a95557021e4 100644 --- a/tools/audio/audio_tablegen.c +++ b/tools/audio/audio_tablegen.c @@ -424,11 +424,11 @@ tablegen_sequences(const char *seq_font_tbl_out, const char *seq_order_path, con if (shstrtab == NULL) error("ELF file \"%s\" has no section header string table?", path); - // The .fonts and .name sections are written when assembling the sequence: - // The .fonts section contains a list of bytes for each soundfont the sequences uses - // The .name section contains the null-terminated name of the sequence as set by .startseq + // The .note.fonts and .note.name sections are written when assembling the sequence: + // The .note.fonts section contains a list of bytes for each soundfont the sequences uses + // The .note.name section contains the null-terminated name of the sequence as set by .startseq - Elf32_Shdr *font_section = elf32_section_forname(".fonts", shstrtab, data, data_size); + Elf32_Shdr *font_section = elf32_section_forname(".note.fonts", shstrtab, data, data_size); if (font_section == NULL) error("Sequence file \"%s\" has no fonts section?", path); @@ -436,7 +436,7 @@ tablegen_sequences(const char *seq_font_tbl_out, const char *seq_order_path, con uint32_t font_section_size = elf32_read32(font_section->sh_size); validate_read(font_section_offset, font_section_size, data_size); - Elf32_Shdr *name_section = elf32_section_forname(".name", shstrtab, data, data_size); + Elf32_Shdr *name_section = elf32_section_forname(".note.name", shstrtab, data, data_size); if (name_section == NULL) error("Sequence file \"%s\" has no name section?", path); diff --git a/tools/audio/extraction/disassemble_sequence.py b/tools/audio/extraction/disassemble_sequence.py index a77a8fac52e..29858e7afcc 100644 --- a/tools/audio/extraction/disassemble_sequence.py +++ b/tools/audio/extraction/disassemble_sequence.py @@ -51,17 +51,7 @@ from typing import Callable, Dict, List, Optional, Tuple from .audiobank_file import AudiobankFile - -pitch_names = ( - "A0", "BF0", "B0", "C1", "DF1", "D1", "EF1", "E1", "F1", "GF1", "G1", "AF1", "A1", "BF1", "B1", "C2", - "DF2", "D2", "EF2", "E2", "F2", "GF2", "G2", "AF2", "A2", "BF2", "B2", "C3", "DF3", "D3", "EF3", "E3", - "F3", "GF3", "G3", "AF3", "A3", "BF3", "B3", "C4", "DF4", "D4", "EF4", "E4", "F4", "GF4", "G4", "AF4", - "A4", "BF4", "B4", "C5", "DF5", "D5", "EF5", "E5", "F5", "GF5", "G5", "AF5", "A5", "BF5", "B5", "C6", - "DF6", "D6", "EF6", "E6", "F6", "GF6", "G6", "AF6", "A6", "BF6", "B6", "C7", "DF7", "D7", "EF7", "E7", - "F7", "GF7", "G7", "AF7", "A7", "BF7", "B7", "C8", "DF8", "D8", "EF8", "E8", "F8", "GF8", "G8", "AF8", - "A8", "BF8", "B8", "C9", "DF9", "D9", "EF9", "E9", "F9", "GF9", "G9", "AF9", "A9", "BF9", "B9", "C10", - "DF10", "D10", "EF10", "E10", "F10", "BFNEG1", "BNEG1", "C0", "DF0", "D0", "EF0", "E0", "F0", "GF0", "G0", "AF0", -) +from .tuning import pitch_names # # VERSIONS @@ -1277,20 +1267,33 @@ def emit(self): outfile.write(f".endseq {self.seq_name}\n") if __name__ == '__main__': - import sys + import argparse + parser = argparse.ArgumentParser(description="Disassemble a Zelda 64 sequence binary") + parser.add_argument("file", help="Sequence binary to disassemble") + parser.add_argument("out", help="Path to output source file") + parser.add_argument("-v", dest="mml_version", required=False, default="OoT", type=str, help="Sample rate (integer)") + args = parser.parse_args() + + in_path = args.file + out_path = args.out + + mml_ver = { + "OoT" : MMLVersion.OOT, + "MM" : MMLVersion.MM, + }.get(args.mml_version, None) - in_path = sys.argv[1] - out_path = sys.argv[2] + if mml_ver is None: + raise Exception("Invalid MML Version, should be 'OoT' or 'MM'") with open(in_path, "rb") as infile: data = bytearray(infile.read()) class FontDummy: - def __init__(self, file_name) -> None: - self.name = file_name - self.file_name = file_name + def __init__(self, name) -> None: + self.name = name + self.file_name = name self.instrument_index_map = {} - disas = SequenceDisassembler(0, data, None, CMD_SPEC, MMLVersion.MM, out_path, "", [FontDummy("wow")], []) + disas = SequenceDisassembler(0, data, None, CMD_SPEC, mml_ver, out_path, "", [FontDummy("dummyfont")], []) disas.analyze() disas.emit() diff --git a/tools/audio/extraction/extraction_xml.py b/tools/audio/extraction/extraction_xml.py index f54b7749f0c..4cdc816f4e4 100644 --- a/tools/audio/extraction/extraction_xml.py +++ b/tools/audio/extraction/extraction_xml.py @@ -67,7 +67,7 @@ def post_init(self, xml_root : Element, version_name : str): in_version = self.in_version(version_include, version_exclude, version_name) if in_version: self.blob_info.append(item.attrib) - self.sample_info_versions.append((item.attrib, in_version)) + self.sample_info_versions.append((item.tag, item.attrib, in_version)) else: print(xml_root.attrib) assert False, item.tag diff --git a/tools/audio/samplebank_compiler.c b/tools/audio/samplebank_compiler.c index f188654f624..cc2bbc76f26 100644 --- a/tools/audio/samplebank_compiler.c +++ b/tools/audio/samplebank_compiler.c @@ -126,7 +126,7 @@ main(int argc, char **argv) sb.name, sb.name); // original tool appears to have a buffer clearing bug involving a buffer sized BUG_BUF_SIZE - match_buf_ptr = (matching) ? match_buf : NULL; + match_buf_ptr = (matching && sb.buffer_bug) ? match_buf : NULL; match_buf_pos = 0; for (size_t i = 0; i < sb.num_samples; i++) { @@ -172,13 +172,13 @@ main(int argc, char **argv) fprintf(outf, ".incbin \"%s\", 0x%lX, 0x%lX\n", path, aifc.ssnd_offset, aifc.ssnd_size); - if (matching && sb.buffer_bug && i == sb.num_samples - 1) { + if (match_buf_ptr != NULL && i == sb.num_samples - 1) { // emplace garbage - size_t end = ALIGN16(match_buf_pos); - fprintf(outf, "\n# Garbage data from buffer bug\n"); + + size_t end = ALIGN16(match_buf_pos); for (; match_buf_pos < end; match_buf_pos++) - fprintf(outf, ".byte 0x%02X\n", match_buf[match_buf_pos]); + fprintf(outf, ".byte 0x%02X\n", match_buf_ptr[match_buf_pos]); } else { fputs("\n.balign 16\n", outf); } diff --git a/tools/audio/sampleconv/Makefile b/tools/audio/sampleconv/Makefile index a023b237e11..d30578543d4 100644 --- a/tools/audio/sampleconv/Makefile +++ b/tools/audio/sampleconv/Makefile @@ -1,7 +1,7 @@ CC := gcc CFLAGS := -Wall -Wextra -MMD -OPTFLAGS := -Og -g3 +OPTFLAGS := -O3 LDFLAGS := CLANG_FORMAT := clang-format-14 diff --git a/tools/audio/sampleconv/src/codec/vadpcm.c b/tools/audio/sampleconv/src/codec/vadpcm.c index 8765fa02cf4..4314b5c60d9 100644 --- a/tools/audio/sampleconv/src/codec/vadpcm.c +++ b/tools/audio/sampleconv/src/codec/vadpcm.c @@ -14,6 +14,18 @@ #include "codec.h" +/** + * Creates FIR filter matrices for each page of the prediction codebook. + * For order=2: each page contains coefficients c0..7 and d0..7, the matrix resembles: + * [ c0, d0, 1, 0, 0, 0, 0, 0, 0, 0 ] + * [ c0, d1, d0, 1, 0, 0, 0, 0, 0, 0 ] + * [ c0, d2, d1, d0, 1, 0, 0, 0, 0, 0 ] + * [ c0, d3, d2, d1, d0, 1, 0, 0, 0, 0 ] + * [ c0, d4, d3, d2, d1, d0, 1, 0, 0, 0 ] + * [ c0, d5, d4, d3, d2, d1, d0, 1, 0, 0 ] + * [ c0, d6, d5, d4, d3, d2, d1, d0, 1, 0 ] + * [ c0, d7, d6, d5, d4, d3, d2, d1, d0, 1 ] + */ int expand_codebook(int16_t *book_data, int32_t ****table_out, int32_t order, int32_t npredictors) { @@ -25,19 +37,25 @@ expand_codebook(int16_t *book_data, int32_t ****table_out, int32_t order, int32_ table[i][j] = MALLOC_CHECKED_INFO((order + 8) * sizeof(int32_t), "order=%d", order); } + // For each page, create the associated matrix for (int32_t i = 0; i < npredictors; i++) { int32_t **table_entry = table[i]; + // Fill the first columns of the FIR filter matrix, up to the "order" column for (int32_t j = 0; j < order; j++) { for (int32_t k = 0; k < 8; k++) table_entry[k][j] = *(book_data++); } + // For each row fill except the first in the "order" column for (int32_t k = 1; k < 8; k++) table_entry[k][order] = table_entry[k - 1][order - 1]; - table_entry[0][order] = 1 << 11; + // Place the 1.0 in the first row of the "order" column + table_entry[0][order] = 1 << 11; // 1.0 in qs4.11 fixed point + // Fill the remaining columns as a shifted-down copy of the previous column, + // adding 0s as-needed. for (int32_t k = 1; k < 8; k++) { int32_t j = 0; @@ -52,6 +70,10 @@ expand_codebook(int16_t *book_data, int32_t ****table_out, int32_t order, int32_ return 0; } +/** + * For each FIR filter matrix associated with a codebook page, pointed to by `table`, store the first + * "order" columns to a codebook at `book_data_out`. + */ int compressed_expanded_codebook(int16_t **book_data_out, int32_t ***table, int order, int npredictors) { @@ -59,9 +81,9 @@ compressed_expanded_codebook(int16_t **book_data_out, int32_t ***table, int orde MALLOC_CHECKED_INFO(sizeof(int16_t) * 8 * order * npredictors, "order=%d, npredictors=%d", order, npredictors); int n = 0; - for (int32_t i = 0; i < npredictors; i++) { - for (int32_t j = 0; j < order; j++) { - for (int32_t k = 0; k < 8; k++) + for (int32_t i = 0; i < npredictors; i++) { // For each matrix + for (int32_t j = 0; j < order; j++) { // For each column + for (int32_t k = 0; k < 8; k++) // For each row book_data[n++] = table[i][k][j]; } } @@ -207,7 +229,8 @@ vdecodeframe(uint8_t *frame, int32_t *prescaled, int32_t *state, int32_t order, int32_t **coef_page = coef_tbl[optimalp]; - // Inner product with predictor coefficients + // Matrix multiplication with FIR filter matrix associated with the book page, the matrix operates on + // 8 samples simultaneously so this needs to be done twice for all 16 samples in the output frame. for (int32_t j = 0; j < 2; j++) { int32_t in_vec[16]; diff --git a/tools/audio/sampleconv/src/codec/vadpcm_tabledesign.c b/tools/audio/sampleconv/src/codec/vadpcm_tabledesign.c index 90bad834991..9fdeb436d77 100644 --- a/tools/audio/sampleconv/src/codec/vadpcm_tabledesign.c +++ b/tools/audio/sampleconv/src/codec/vadpcm_tabledesign.c @@ -10,7 +10,7 @@ #include "../util.h" #include "vadpcm.h" -// Levinson-Durbin algorithm for iteratively solving for prediction coefficients +// Levinson-Durbin algorithm for iteratively solving for prediction and reflection coefficients, given autocorrelation // https://en.wikipedia.org/wiki/Levinson_recursion static int durbin(double *acvec, int order, double *reflection_coeffs, double *prediction_coeffs, double *error) @@ -36,7 +36,7 @@ durbin(double *acvec, int order, double *reflection_coeffs, double *prediction_c reflection_coeffs[i] = prediction_coeffs[i]; if (fabs(reflection_coeffs[i]) > 1.0) { - // incr when a predictor coefficient is > 1 (indicates numerical instability) + // incr when a reflection coefficient has a magnitude > 1 (indicates numerical instability in the model) ret++; } @@ -82,7 +82,7 @@ kfroma(double *in, double *out, int order) int i, j; double div; double temp; - double next[(order + 1)]; + double next[order + 1]; int ret = 0; out[order] = in[order]; @@ -144,29 +144,28 @@ rfroma(double *in, int n, double *out) } static double -model_dist(double *predictors, double *data, int order) +model_dist(double *mean_predictors, double *frame_predictors, int order) { - double autocorrelation_data[order + 1]; - double autocorrelation_predictors[order + 1]; + double autocorrelation_frame_predictors[order + 1]; + double autocorrelation_mean_predictors[order + 1]; double ret; int i, j; - // autocorrelation from data - rfroma(data, order, autocorrelation_data); + // autocorrelation from frame predictors + rfroma(frame_predictors, order, autocorrelation_frame_predictors); - // autocorrelation from predictors + // autocorrelation from current mean predictors (?) for (i = 0; i <= order; i++) { - autocorrelation_predictors[i] = 0.0; - for (j = 0; j <= order - i; j++) { - autocorrelation_predictors[i] += predictors[j] * predictors[i + j]; - } + autocorrelation_mean_predictors[i] = 0.0; + for (j = 0; j <= order - i; j++) + autocorrelation_mean_predictors[i] += mean_predictors[j] * mean_predictors[i + j]; } // compute "model distance" (scaled L2 norm: 2 * inner(ac1, ac2) ) - ret = autocorrelation_data[0] * autocorrelation_predictors[0]; - for (i = 1; i <= order; i++) { - ret += 2 * autocorrelation_data[i] * autocorrelation_predictors[i]; - } + // this compares how good the mean predictors are to the optimal predictors for this frame + ret = autocorrelation_frame_predictors[0] * autocorrelation_mean_predictors[0]; + for (i = 1; i <= order; i++) + ret += 2 * autocorrelation_frame_predictors[i] * autocorrelation_mean_predictors[i]; return ret; } @@ -363,7 +362,8 @@ split(double **predictors, double *delta, int order, int npredictors, double sca } static void -refine(double **predictors, int order, int npredictors, double *data, int data_size, int refine_iters) +refine(double **predictors, int order, int npredictors, double *all_frame_predictors, int num_frame_predictors, + int refine_iters) { int iter; double dist; @@ -376,50 +376,55 @@ refine(double **predictors, int order, int npredictors, double *data, int data_s int counts[npredictors]; double vec[order + 1]; + // For some number of refinement iterations for (iter = 0; iter < refine_iters; iter++) { - // For some number of refinement iterations - - // Initialize averages + // Initialize average autocorrelations memset(counts, 0, npredictors * sizeof(int)); memset(rsums, 0, npredictors * (order + 1) * sizeof(double)); - // Sum autocorrelations - for (i = 0; i < data_size; i++) { + // Sum autocorrelations for averaging for each frame, binning them based on best fitting predictor set + for (i = 0; i < num_frame_predictors; i++) { best_value = 1e30; best_index = 0; - // Find index that minimizes the "model distance" + // Find the choice of predictor that minimizes the "model distance" for this frame for (j = 0; j < npredictors; j++) { - dist = model_dist(predictors[j], &data[(order + 1) * i], order); + // Compare with current mean predictors, the distance metric is based on autocorrelations + dist = model_dist(predictors[j], &all_frame_predictors[(order + 1) * i], order); if (dist < best_value) { + // Record the new best predictors best_value = dist; best_index = j; } } - counts[best_index]++; - rfroma(&data[(order + 1) * i], order, vec); // compute autocorrelation from predictors - + // Compute autocorrelation from optimal predictor + rfroma(&all_frame_predictors[(order + 1) * i], order, vec); + // Add to average autocorrelation for the best predictor choice for (j = 0; j <= order; j++) - rsums[best_index][j] += vec[j]; // add to average autocorrelation + rsums[best_index][j] += vec[j]; + + // Update the counter of how many frames we've summed for this predictor + counts[best_index]++; } - // finalize average autocorrelations + // Finalize average autocorrelations for (i = 0; i < npredictors; i++) { - if (counts[i] > 0) { + if (counts[i] > 1) { + // Need to divide by the number of frames we summed for (j = 0; j <= order; j++) { rsums[i][j] /= counts[i]; } } } + // Update the predictors with the new average autocorrelations in each bin for (i = 0; i < npredictors; i++) { - // compute predictors from average autocorrelation + // Compute predictors and reflection coefficients from average autocorrelation durbin(rsums[i], order, vec, predictors[i], &dummy); - // vec is reflection coeffs - // clamp reflection coeffs + // Clamp reflection coeffs for stability for (j = 1; j <= order; j++) { if (vec[j] >= 1.0) vec[j] = 0.9999999999; @@ -427,44 +432,73 @@ refine(double **predictors, int order, int npredictors, double *data, int data_s vec[j] = -0.9999999999; } - // clamped reflection coeffs -> predictors + // Convert clamped reflection coeffs to stable predictors afromk(vec, predictors[i], order); } } } static int -read_row(int16_t *p, double *row, int order) +read_row(int16_t *out, double *predictors, int order) { - double fval; - int ival; int i, j, k; int overflows; double table[8][order]; + // (discussion is for order=2) + // + // Converts 2 predictors a,b into the coefficients for an FIR filter matrix + // [ c0, d0, 1, 0, 0, 0, 0, 0, 0, 0 ] + // [ c0, d1, d0, 1, 0, 0, 0, 0, 0, 0 ] + // [ c0, d2, d1, d0, 1, 0, 0, 0, 0, 0 ] + // [ c0, d3, d2, d1, d0, 1, 0, 0, 0, 0 ] + // [ c0, d4, d3, d2, d1, d0, 1, 0, 0, 0 ] + // [ c0, d5, d4, d3, d2, d1, d0, 1, 0, 0 ] + // [ c0, d6, d5, d4, d3, d2, d1, d0, 1, 0 ] + // [ c0, d7, d6, d5, d4, d3, d2, d1, d0, 1 ] + // + // Multiplication by this matrix on a vector containing the previous two samples p[-2] and p[-1] and 8 residuals + // s[i] decodes 8 samples p[i] simultaneously. + // Only c0..7 and d0..7 are actually stored in the book, the decoder arranges the rest. + // + // The coefficients are those you get by substituting decoded samples into the prediction model at each step to + // express each p[i] for i in [0, 8) as a linear combination of p[-2], p[-1] and past residuals + // p[i] = a * p[i - 2] * b * p[i - 1] + s[i] + // + // c0 = a, d0 = b + // c1 = ab, d1 = a + b^2 + // c2 = a^2 + ab^2, d2 = 2ab + b^3 + // ... + for (i = 0; i < order; i++) { for (j = 0; j < i; j++) table[i][j] = 0.0; for (j = i; j < order; j++) - table[i][j] = -row[order - j + i]; + table[i][j] = -predictors[order - j + i]; } - - for (i = order; i < 8; i++) + for (i = order; i < 8; i++) { for (j = 0; j < order; j++) table[i][j] = 0.0; + } - for (i = 1; i < 8; i++) - for (j = 1; j <= order; j++) - if (i - j >= 0) + for (i = 1; i < 8; i++) { + for (j = 1; j <= order; j++) { + if (i >= j) { for (k = 0; k < order; k++) - table[i][k] -= row[j] * table[i - j][k]; + table[i][k] -= predictors[j] * table[i - j][k]; + } + } + } + // Convert double-precision book entries into qs4.11 fixed point numbers, + // rounding away from 0 and checking for overflows overflows = 0; - for (i = 0; i < order; i++) { for (j = 0; j < 8; j++) { - fval = table[j][i] * (double)(1 << 11); + int ival; + double fval = table[j][i] * (double)(1 << 11); + if (fval < 0.0) { ival = (int)(fval - 0.5); if (ival < -0x8000) @@ -475,7 +509,7 @@ read_row(int16_t *p, double *row, int order) overflows++; } - *(p++) = ival; + *out++ = ival; } } @@ -517,27 +551,30 @@ tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_dat for (int i = 0; i < num_order; i++) autocorrelation_matrix[i] = MALLOC_CHECKED_INFO(num_order * sizeof(double), "num_order=%d", num_order); + // (back-)align to a multiple of the frame size size_t nframes = num_samples - (num_samples % frame_size); - double *data = + double *all_frame_predictors = MALLOC_CHECKED_INFO(nframes * num_order * sizeof(double), "nframes=%lu, num_order=%d", nframes, num_order); - uint32_t data_size = 0; + uint32_t num_frame_predictors = 0; int16_t *sample = sample_data; - // (back-)align to a multiple of the frame size int16_t *sample_end = sample + nframes; - memset(buffer, 0, frame_size * sizeof(int16_t)); + memset(buffer, 0, frame_size * sizeof(*buffer)); + + // First, compute the optimal set of predictors for every complete frame in the signal, where optimal here means + // the predictors that minimize the mean-square error between the predicted signal and the true signal. for (; sample < sample_end; sample += frame_size) { // Copy sample data into second half of buffer, during the first iteration the first half is 0 while in // later iterations the second half of the previous iteration is shifted into the first half. - memcpy(&buffer[frame_size], sample, frame_size * sizeof(int16_t)); + memcpy(&buffer[frame_size], sample, frame_size * sizeof(*buffer)); // Compute autocorrelation vector of the two vectors in the buffer acvect(&buffer[frame_size], order, frame_size, vec); - // First element is the largest(?) + // First element of autocorrelation has the largest magnitude if (fabs(vec[0]) > design->thresh) { // Over threshold @@ -552,15 +589,20 @@ tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_dat // R = autocorrelation matrix // r = autocorrelation vector // a = linear prediction coefficients - // After this vec contains the prediction coefficients + // After this vec contains the prediction coefficients that minimize the mean-square error. lubksb(autocorrelation_matrix, order, perm, vec); vec[0] = 1.0; // Compute reflection coefficients from prediction coefficients if (kfroma(vec, reflection_coeffs, order) == 0) { // Continue only if numerically stable - data[data_size * num_order + 0] = 1.0; - - // clamp the reflection coefficients + all_frame_predictors[num_frame_predictors * num_order] = 1.0; + + // Clamp the reflection coefficients. Reflection coefficients are clamped rather than the + // predictors themselves as the reflection coefficients have a direct relationship with the + // stability of the model. If the reflection coefficients are outside of the interval (-1,1) + // the model is unstable as the associated transfer function will contain poles outside the + // complex unit disk. If the reflection coefficients are all inside the interval (-1,1) there + // are no poles outside of the unit disk, guaranteeing the stability of the model. for (int i = 1; i < num_order; i++) { if (reflection_coeffs[i] >= 1.0) reflection_coeffs[i] = 0.9999999999; @@ -568,40 +610,44 @@ tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_dat reflection_coeffs[i] = -0.9999999999; } - // Compute prediction coefficients from reflection coefficients - afromk(reflection_coeffs, &data[data_size * num_order], order); - data_size++; + // Compute prediction coefficients from clamped reflection coefficients + afromk(reflection_coeffs, &all_frame_predictors[num_frame_predictors * num_order], order); + num_frame_predictors++; } } } // Move second vector to first vector - memcpy(&buffer[0], &buffer[frame_size], frame_size * sizeof(int16_t)); + memcpy(&buffer[0], &buffer[frame_size], frame_size * sizeof(*buffer)); } + // Now that predictors for every frame have been found, they need to be reduced to a manageable quantity + // (determined by npredictors) to build the prediction codebook that will be exported. First compute the average + // autocorrelation of the prediction models for all frames: + // Create a vector [1.0, 0.0, ..., 0.0] vec[0] = 1.0; for (int i = 1; i < num_order; i++) vec[i] = 0.0; - for (uint32_t i = 0; i < data_size; i++) { - // Compute autocorrelation from predictors - rfroma(&data[i * num_order], order, predictors[0]); + for (uint32_t i = 0; i < num_frame_predictors; i++) { + // Compute autocorrelation from predictors, equivalent to computing the autocorrelation on the signal produced + // by following the prediction model exactly. + rfroma(&all_frame_predictors[i * num_order], order, predictors[0]); for (int k = 1; k < num_order; k++) vec[k] += predictors[0][k]; } for (int i = 1; i < num_order; i++) - vec[i] /= data_size; + vec[i] /= num_frame_predictors; - // vec is the average autocorrelation - - // Compute predictors for average autocorrelation using Levinson-Durbin algorithm + // vec now contains the average autocorrelation. + // Compute predictors for this average autocorrelation using the Levinson-Durbin algorithm. double dummy; durbin(vec, order, reflection_coeffs, predictors[0], &dummy); - // clamp results + // Clamp resulting reflection coefficients to ensure stability for (int i = 1; i < num_order; i++) { if (reflection_coeffs[i] >= 1.0) reflection_coeffs[i] = 0.9999999999; @@ -609,27 +655,37 @@ tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_dat reflection_coeffs[i] = -0.9999999999; } - // Convert clamped reflection coefficients to predictors + // Convert clamped reflection coefficients to stable predictors afromk(reflection_coeffs, predictors[0], order); - // Split and refine predictors + // Starting with the predictors obtained from the average autocorrelation, cluster the predictors for each frame + // via k-means until we have just npredictors worth of output for (unsigned cur_bits = 0; cur_bits < design->bits; cur_bits++) { double split_delta[num_order]; + // Prepare [0, ..., -1, 0] for (int i = 0; i < num_order; i++) split_delta[i] = 0.0; split_delta[order - 1] = -1.0; + // Split the predictors into two halves split(predictors, split_delta, order, 1 << cur_bits, 0.01); - refine(predictors, order, 1 << (1 + cur_bits), data, data_size, design->refine_iters); + + // Update the values of each half to the means of the halves + refine(predictors, order, 1 << (1 + cur_bits), all_frame_predictors, num_frame_predictors, + design->refine_iters); } - int16_t *book_data = MALLOC_CHECKED_INFO((8 * order * npredictors + 2) * sizeof(int16_t), - "order=%d, npredictors=%d", order, npredictors); + // Now we have the reduced set of predictors, write them into the book of size 8 * order * npredictors + + int16_t *book_data = + MALLOC_CHECKED_INFO(8 * order * npredictors * sizeof(int16_t), "order=%d, npredictors=%d", order, npredictors); *order_out = order; *npredictors_out = npredictors; + // As a final step, we need to convert the predictors into coefficients for an FIR filter so that samples in a + // frame can be decoded in parallel taking advantage of the RSP's 8-lane SIMD instruction set. int num_oflow = 0; for (int i = 0; i < npredictors; i++) num_oflow += read_row(&book_data[8 * order * i], predictors[i], order); @@ -641,7 +697,7 @@ tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_dat *book_data_out = book_data; free(buffer); - free(data); + free(all_frame_predictors); for (int i = 0; i < num_order; i++) free(autocorrelation_matrix[i]); diff --git a/tools/audio/sampleconv/src/container/aiff.c b/tools/audio/sampleconv/src/container/aiff.c index 7f824e016a4..7c9a38ff17f 100644 --- a/tools/audio/sampleconv/src/container/aiff.c +++ b/tools/audio/sampleconv/src/container/aiff.c @@ -138,8 +138,12 @@ f64_to_f80(double f64, uint8_t *f80) } f80tmp; // get f64 bits - - uint64_t f64_bits = *(uint64_t *)&f64; + union { + double f; + uint64_t u; + } tp; + tp.f = f64; + uint64_t f64_bits = tp.u; int f64_sgn = F64_GET_SGN(f64_bits); int f64_exponent = F64_GET_EXP(f64_bits); @@ -197,8 +201,12 @@ f80_to_f64(double *f64, uint8_t *f80) ((uint64_t)f64_mantissa_hi << 32) | ((uint64_t)f64_mantissa_lo); // write double - - *f64 = *(double *)&f64_bits; + union { + double f; + uint64_t u; + } tp; + tp.u = f64_bits; + *f64 = tp.f; } int @@ -309,6 +317,8 @@ aiff_aifc_common_read(container_data *out, FILE *in, UNUSED bool matching, uint3 nloops += inst.sustainLoop.playMode != LOOP_PLAYMODE_NONE; nloops += inst.releaseLoop.playMode != LOOP_PLAYMODE_NONE; + out->num_loops = nloops; + out->loops = NULL; if (nloops != 0) { out->loops = MALLOC_CHECKED_INFO(nloops * sizeof(container_loop), "nloops=%lu", nloops); @@ -495,11 +505,21 @@ aiff_aifc_common_read(container_data *out, FILE *in, UNUSED bool matching, uint3 if (!has_comm) error("aiff/aifc has no COMM chunk"); - if (!has_inst) - error("aiff/aifc has no INST chunk"); if (!has_ssnd) error("aiff/aifc has no SSND chunk"); + if (!has_inst) { + out->base_note = 60; // C4 + out->fine_tune = 0; + out->key_low = 0; + out->key_hi = 127; + out->vel_low = 0; + out->vel_hi = 127; + out->gain = 0; + out->num_loops = 0; + out->loops = NULL; + } + if (out->data_type == SAMPLE_TYPE_PCM16) { assert(out->data_size % 2 == 0); assert(out->bit_depth == 16); diff --git a/tools/audio/sampleconv/src/container/wav.c b/tools/audio/sampleconv/src/container/wav.c index 885acb35eba..ad345f76ed9 100644 --- a/tools/audio/sampleconv/src/container/wav.c +++ b/tools/audio/sampleconv/src/container/wav.c @@ -222,6 +222,7 @@ wav_read(container_data *out, const char *path, UNUSED bool matching) smpl.sampler_data = le32toh(smpl.sampler_data); out->num_loops = smpl.num_sample_loops; + out->loops = NULL; if (out->num_loops != 0) { out->loops = MALLOC_CHECKED_INFO(out->num_loops * sizeof(container_loop), "num_loops=%u", out->num_loops); @@ -362,11 +363,11 @@ wav_read(container_data *out, const char *path, UNUSED bool matching) if (!has_inst) { out->base_note = 60; // C4 out->fine_tune = 0; - out->gain = 0; out->key_low = 0; - out->key_hi = 0; + out->key_hi = 127; out->vel_low = 0; - out->vel_hi = 0; + out->vel_hi = 127; + out->gain = 0; } if (!has_smpl) { diff --git a/tools/audio/soundfont_compiler.c b/tools/audio/soundfont_compiler.c index c8ba9633c6f..3dbf18eaa54 100644 --- a/tools/audio/soundfont_compiler.c +++ b/tools/audio/soundfont_compiler.c @@ -1591,7 +1591,7 @@ emit_h_effects(FILE *out, soundfont *sf) NORETURN static void usage(const char *progname) { - fprintf(stderr, "Usage: %s [--matching] \n", progname); + fprintf(stderr, "Usage: %s [--matching] \n", progname); exit(EXIT_FAILURE); } @@ -1749,12 +1749,12 @@ main(int argc, char **argv) fprintf(out_h, // clang-format off - "#ifdef _LANGUAGE_ASEQ" "\n" - ".pushsection .fonts, \"\", @note" "\n" - " .byte %d /*sf id*/" "\n" - ".popsection" "\n" - "#endif" "\n" - "\n", + "#ifdef _LANGUAGE_ASEQ" "\n" + ".pushsection .note.fonts, \"\", @note" "\n" + " .byte %d /*sf id*/" "\n" + ".popsection" "\n" + "#endif" "\n" + "\n", // clang-format on sf.info.index); @@ -1779,8 +1779,11 @@ main(int argc, char **argv) // emit name marker - FILE *out_name = fopen(filename_out_name, "w"); - fprintf(out_name, "%s", sf.info.name); + FILE *out_name = fopen(filename_out_name, "wb"); + // We need to emit an explicit null terminator so that we can run objcopy --add-section to include the name + // in a .note.name section in the compiled object file. This is so that the string that ends up in the .note.name + // section is null-terminated, its length may be verified by any tools that read the name out of this section. + fprintf(out_name, "%s%c", sf.info.name, '\0'); fclose(out_name); // emit dependency file if wanted diff --git a/tools/audio/util.c b/tools/audio/util.c index 8043f957751..c4460647678 100644 --- a/tools/audio/util.c +++ b/tools/audio/util.c @@ -1,6 +1,5 @@ /* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET */ /* SPDX-License-Identifier: CC0-1.0 */ -#define _GNU_SOURCE #include #include #include