mirror of https://github.com/zeldaret/mm.git
				
				
				
			
		
			
				
	
	
		
			1819 lines
		
	
	
		
			68 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			1819 lines
		
	
	
		
			68 KiB
		
	
	
	
		
			C
		
	
	
	
/**
 | 
						|
 * SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
 | 
						|
 * SPDX-License-Identifier: MPL-2.0
 | 
						|
 *
 | 
						|
 * This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
						|
 */
 | 
						|
#include <alloca.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include <stdarg.h>
 | 
						|
#include <stdbool.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include "xml.h"
 | 
						|
#include "aifc.h"
 | 
						|
#include "samplebank.h"
 | 
						|
#include "soundfont.h"
 | 
						|
#include "util.h"
 | 
						|
 | 
						|
static_assert(sizeof(float) == sizeof(uint32_t), "Float is assumed to be 32-bit");
 | 
						|
 | 
						|
static float
 | 
						|
i2f(uint32_t i)
 | 
						|
{
 | 
						|
    union {
 | 
						|
        float f;
 | 
						|
        uint32_t i;
 | 
						|
    } fi;
 | 
						|
 | 
						|
    fi.i = i;
 | 
						|
    return fi.f;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t
 | 
						|
f2i(float f)
 | 
						|
{
 | 
						|
    union {
 | 
						|
        float f;
 | 
						|
        uint32_t i;
 | 
						|
    } fi;
 | 
						|
 | 
						|
    fi.f = f;
 | 
						|
    return fi.i;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
midinote_to_z64note(int note)
 | 
						|
{
 | 
						|
    // Converts from MIDI note number (middle C = 60) to Z64 note number (middle C = 39)
 | 
						|
    int z64note = note - 21;
 | 
						|
    if (z64note < 0) // % 128
 | 
						|
        z64note += 128;
 | 
						|
    return z64note;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Calculate the tuning value from a given samplerate and basenote.
 | 
						|
 *
 | 
						|
 * Uses a lookup table (gPitchFrequencies from the audio driver source) to compute the result of `2^(basenote / 12)`
 | 
						|
 * (with appropriate shifting such that the index for C4 results in 1.0)
 | 
						|
 */
 | 
						|
static float
 | 
						|
calc_tuning(float sample_rate, int basenote)
 | 
						|
{
 | 
						|
    static const float playback_sample_rate = 32000.0f; // Target samplerate in-game is 32KHz
 | 
						|
    static const float pitch_frequencies[] = {
 | 
						|
        // gPitchFrequencies in audio driver source
 | 
						|
        /* 0x00 */ 0.105112f,   // PITCH_A0
 | 
						|
        /* 0x01 */ 0.111362f,   // PITCH_BF0
 | 
						|
        /* 0x02 */ 0.117984f,   // PITCH_B0
 | 
						|
        /* 0x03 */ 0.125f,      // PITCH_C1
 | 
						|
        /* 0x04 */ 0.132433f,   // PITCH_DF1
 | 
						|
        /* 0x05 */ 0.140308f,   // PITCH_D1
 | 
						|
        /* 0x06 */ 0.148651f,   // PITCH_EF1
 | 
						|
        /* 0x07 */ 0.15749f,    // PITCH_E1
 | 
						|
        /* 0x08 */ 0.166855f,   // PITCH_F1
 | 
						|
        /* 0x09 */ 0.176777f,   // PITCH_GF1
 | 
						|
        /* 0x0A */ 0.187288f,   // PITCH_G1
 | 
						|
        /* 0x0B */ 0.198425f,   // PITCH_AF1
 | 
						|
        /* 0x0C */ 0.210224f,   // PITCH_A1
 | 
						|
        /* 0x0D */ 0.222725f,   // PITCH_BF1
 | 
						|
        /* 0x0E */ 0.235969f,   // PITCH_B1
 | 
						|
        /* 0x0F */ 0.25f,       // PITCH_C2
 | 
						|
        /* 0x10 */ 0.264866f,   // PITCH_DF2
 | 
						|
        /* 0x11 */ 0.280616f,   // PITCH_D2
 | 
						|
        /* 0x12 */ 0.297302f,   // PITCH_EF2
 | 
						|
        /* 0x13 */ 0.31498f,    // PITCH_E2
 | 
						|
        /* 0x14 */ 0.33371f,    // PITCH_F2
 | 
						|
        /* 0x15 */ 0.353553f,   // PITCH_GF2
 | 
						|
        /* 0x16 */ 0.374577f,   // PITCH_G2
 | 
						|
        /* 0x17 */ 0.39685f,    // PITCH_AF2
 | 
						|
        /* 0x18 */ 0.420448f,   // PITCH_A2
 | 
						|
        /* 0x19 */ 0.445449f,   // PITCH_BF2
 | 
						|
        /* 0x1A */ 0.471937f,   // PITCH_B2
 | 
						|
        /* 0x1B */ 0.5f,        // PITCH_C3
 | 
						|
        /* 0x1C */ 0.529732f,   // PITCH_DF3
 | 
						|
        /* 0x1D */ 0.561231f,   // PITCH_D3
 | 
						|
        /* 0x1E */ 0.594604f,   // PITCH_EF3
 | 
						|
        /* 0x1F */ 0.629961f,   // PITCH_E3
 | 
						|
        /* 0x20 */ 0.66742f,    // PITCH_F3
 | 
						|
        /* 0x21 */ 0.707107f,   // PITCH_GF3
 | 
						|
        /* 0x22 */ 0.749154f,   // PITCH_G3
 | 
						|
        /* 0x23 */ 0.793701f,   // PITCH_AF3
 | 
						|
        /* 0x24 */ 0.840897f,   // PITCH_A3
 | 
						|
        /* 0x25 */ 0.890899f,   // PITCH_BF3
 | 
						|
        /* 0x26 */ 0.943875f,   // PITCH_B3
 | 
						|
        /* 0x27 */ 1.0f,        // PITCH_C4 (Middle C)
 | 
						|
        /* 0x28 */ 1.059463f,   // PITCH_DF4
 | 
						|
        /* 0x29 */ 1.122462f,   // PITCH_D4
 | 
						|
        /* 0x2A */ 1.189207f,   // PITCH_EF4
 | 
						|
        /* 0x2B */ 1.259921f,   // PITCH_E4
 | 
						|
        /* 0x2C */ 1.33484f,    // PITCH_F4
 | 
						|
        /* 0x2D */ 1.414214f,   // PITCH_GF4
 | 
						|
        /* 0x2E */ 1.498307f,   // PITCH_G4
 | 
						|
        /* 0x2F */ 1.587401f,   // PITCH_AF4
 | 
						|
        /* 0x30 */ 1.681793f,   // PITCH_A4
 | 
						|
        /* 0x31 */ 1.781798f,   // PITCH_BF4
 | 
						|
        /* 0x32 */ 1.887749f,   // PITCH_B4
 | 
						|
        /* 0x33 */ 2.0f,        // PITCH_C5
 | 
						|
        /* 0x34 */ 2.118926f,   // PITCH_DF5
 | 
						|
        /* 0x35 */ 2.244924f,   // PITCH_D5
 | 
						|
        /* 0x36 */ 2.378414f,   // PITCH_EF5
 | 
						|
        /* 0x37 */ 2.519842f,   // PITCH_E5
 | 
						|
        /* 0x38 */ 2.66968f,    // PITCH_F5
 | 
						|
        /* 0x39 */ 2.828428f,   // PITCH_GF5
 | 
						|
        /* 0x3A */ 2.996615f,   // PITCH_G5
 | 
						|
        /* 0x3B */ 3.174803f,   // PITCH_AF5
 | 
						|
        /* 0x3C */ 3.363586f,   // PITCH_A5
 | 
						|
        /* 0x3D */ 3.563596f,   // PITCH_BF5
 | 
						|
        /* 0x3E */ 3.775498f,   // PITCH_B5
 | 
						|
        /* 0x3F */ 4.0f,        // PITCH_C6
 | 
						|
        /* 0x40 */ 4.237853f,   // PITCH_DF6
 | 
						|
        /* 0x41 */ 4.489849f,   // PITCH_D6
 | 
						|
        /* 0x42 */ 4.756829f,   // PITCH_EF6
 | 
						|
        /* 0x43 */ 5.039685f,   // PITCH_E6
 | 
						|
        /* 0x44 */ 5.33936f,    // PITCH_F6
 | 
						|
        /* 0x45 */ 5.656855f,   // PITCH_GF6
 | 
						|
        /* 0x46 */ 5.993229f,   // PITCH_G6
 | 
						|
        /* 0x47 */ 6.349606f,   // PITCH_AF6
 | 
						|
        /* 0x48 */ 6.727173f,   // PITCH_A6
 | 
						|
        /* 0x49 */ 7.127192f,   // PITCH_BF6
 | 
						|
        /* 0x4A */ 7.550996f,   // PITCH_B6
 | 
						|
        /* 0x4B */ 8.0f,        // PITCH_C7
 | 
						|
        /* 0x4C */ 8.475705f,   // PITCH_DF7
 | 
						|
        /* 0x4D */ 8.979697f,   // PITCH_D7
 | 
						|
        /* 0x4E */ 9.513658f,   // PITCH_EF7
 | 
						|
        /* 0x4F */ 10.07937f,   // PITCH_E7
 | 
						|
        /* 0x50 */ 10.6787205f, // PITCH_F7
 | 
						|
        /* 0x51 */ 11.31371f,   // PITCH_GF7
 | 
						|
        /* 0x52 */ 11.986459f,  // PITCH_G7
 | 
						|
        /* 0x53 */ 12.699211f,  // PITCH_AF7
 | 
						|
        /* 0x54 */ 13.454346f,  // PITCH_A7
 | 
						|
        /* 0x55 */ 14.254383f,  // PITCH_BF7
 | 
						|
        /* 0x56 */ 15.101993f,  // PITCH_B7
 | 
						|
        /* 0x57 */ 16.0f,       // PITCH_C8
 | 
						|
        /* 0x58 */ 16.95141f,   // PITCH_DF8
 | 
						|
        /* 0x59 */ 17.959395f,  // PITCH_D8
 | 
						|
        /* 0x5A */ 19.027315f,  // PITCH_EF8
 | 
						|
        /* 0x5B */ 20.15874f,   // PITCH_E8
 | 
						|
        /* 0x5C */ 21.35744f,   // PITCH_F8
 | 
						|
        /* 0x5D */ 22.62742f,   // PITCH_GF8
 | 
						|
        /* 0x5E */ 23.972918f,  // PITCH_G8
 | 
						|
        /* 0x5F */ 25.398422f,  // PITCH_AF8
 | 
						|
        /* 0x60 */ 26.908691f,  // PITCH_A8
 | 
						|
        /* 0x61 */ 28.508766f,  // PITCH_BF8
 | 
						|
        /* 0x62 */ 30.203985f,  // PITCH_B8
 | 
						|
        /* 0x63 */ 32.0f,       // PITCH_C9
 | 
						|
        /* 0x64 */ 33.90282f,   // PITCH_DF9
 | 
						|
        /* 0x65 */ 35.91879f,   // PITCH_D9
 | 
						|
        /* 0x66 */ 38.05463f,   // PITCH_EF9
 | 
						|
        /* 0x67 */ 40.31748f,   // PITCH_E9
 | 
						|
        /* 0x68 */ 42.71488f,   // PITCH_F9
 | 
						|
        /* 0x69 */ 45.25484f,   // PITCH_GF9
 | 
						|
        /* 0x6A */ 47.945835f,  // PITCH_G9
 | 
						|
        /* 0x6B */ 50.796845f,  // PITCH_AF9
 | 
						|
        /* 0x6C */ 53.817383f,  // PITCH_A9
 | 
						|
        /* 0x6D */ 57.017532f,  // PITCH_BF9
 | 
						|
        /* 0x6E */ 60.40797f,   // PITCH_B9
 | 
						|
        /* 0x6F */ 64.0f,       // PITCH_C10
 | 
						|
        /* 0x70 */ 67.80564f,   // PITCH_DF10
 | 
						|
        /* 0x71 */ 71.83758f,   // PITCH_D10
 | 
						|
        /* 0x72 */ 76.10926f,   // PITCH_EF10
 | 
						|
        /* 0x73 */ 80.63496f,   // PITCH_E10
 | 
						|
        /* 0x74 */ 85.42976f,   // PITCH_F10
 | 
						|
        /* 0x75 */ 0.055681f,   // PITCH_BFNEG1
 | 
						|
        /* 0x76 */ 0.058992f,   // PITCH_BNEG1
 | 
						|
        /* 0x77 */ 0.0625f,     // PITCH_C0
 | 
						|
        /* 0x78 */ 0.066216f,   // PITCH_DF0
 | 
						|
        /* 0x79 */ 0.070154f,   // PITCH_D0
 | 
						|
        /* 0x7A */ 0.074325f,   // PITCH_EF0
 | 
						|
        /* 0x7B */ 0.078745f,   // PITCH_E0
 | 
						|
        /* 0x7C */ 0.083427f,   // PITCH_F0
 | 
						|
        /* 0x7D */ 0.088388f,   // PITCH_GF0
 | 
						|
        /* 0x7E */ 0.093644f,   // PITCH_G0
 | 
						|
        /* 0x7F */ 0.099213f,   // PITCH_AF0
 | 
						|
    };
 | 
						|
 | 
						|
    return (sample_rate / playback_sample_rate) * pitch_frequencies[basenote];
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
read_envelopes_info(soundfont *sf, xmlNodePtr envelopes)
 | 
						|
{
 | 
						|
    static const xml_attr_spec spec_env = {
 | 
						|
        {"Name",     false, xml_parse_c_identifier, offsetof(envelope_data, name)   },
 | 
						|
        { "Release", false, xml_parse_u8,           offsetof(envelope_data, release)},
 | 
						|
    };
 | 
						|
    static const xml_attr_spec spec_env_pt = {
 | 
						|
        {"Delay", false, xml_parse_s16, offsetof(envelope_point, delay)},
 | 
						|
        { "Arg",  false, xml_parse_s16, offsetof(envelope_point, arg)  },
 | 
						|
    };
 | 
						|
    static const xml_attr_spec spec_env_goto = {
 | 
						|
        {"Index", false, xml_parse_s16, offsetof(envelope_point, arg)},
 | 
						|
    };
 | 
						|
 | 
						|
    LL_FOREACH(xmlNodePtr, env, envelopes->children) {
 | 
						|
        if (env->type != XML_ELEMENT_NODE)
 | 
						|
            continue;
 | 
						|
 | 
						|
        const char *name = XMLSTR_TO_STR(env->name);
 | 
						|
        if (!strequ(name, "Envelope"))
 | 
						|
            error("Unexpected element node %s in envelopes list (line %d)", name, env->line);
 | 
						|
 | 
						|
        envelope_data *envdata;
 | 
						|
 | 
						|
        if (env->children == NULL) {
 | 
						|
            // Empty envelopes for mm
 | 
						|
            envdata = (envelope_data *)malloc(sizeof(envelope_data));
 | 
						|
            envdata->name = NULL;
 | 
						|
            envdata->points = NULL;
 | 
						|
            envdata->release = 0;
 | 
						|
            envdata->n_points = 0;
 | 
						|
        } else {
 | 
						|
            size_t points_cap = 4;
 | 
						|
            size_t points_num = 0;
 | 
						|
 | 
						|
            void *envelopes_data = malloc(sizeof(envelope_data) + points_cap * sizeof(envelope_point));
 | 
						|
            envdata = (envelope_data *)envelopes_data;
 | 
						|
 | 
						|
            xml_parse_node_by_spec(envdata, env, spec_env, ARRAY_COUNT(spec_env));
 | 
						|
 | 
						|
            // Ensure name is unique
 | 
						|
            LL_FOREACH(envelope_data *, envdata2, sf->envelopes) {
 | 
						|
                if (envdata2->name != NULL && strequ(envdata->name, envdata2->name))
 | 
						|
                    error("Duplicate envelope name %s (second occurrence on line %d)", envdata->name, env->line);
 | 
						|
            }
 | 
						|
 | 
						|
            envelope_point *pts = (envelope_point *)(envdata + 1);
 | 
						|
 | 
						|
            LL_FOREACH(xmlNodePtr, env_pt, env->children) {
 | 
						|
                if (points_num >= points_cap) {
 | 
						|
                    points_cap *= 2;
 | 
						|
                    envelopes_data =
 | 
						|
                        realloc(envelopes_data, sizeof(envelope_data) + points_cap * sizeof(envelope_point));
 | 
						|
                    envdata = (envelope_data *)envelopes_data;
 | 
						|
                    pts = (envelope_point *)(envdata + 1);
 | 
						|
                }
 | 
						|
 | 
						|
                envelope_point *pt = &pts[points_num];
 | 
						|
 | 
						|
                if (env_pt->type != XML_ELEMENT_NODE)
 | 
						|
                    continue;
 | 
						|
 | 
						|
                const char *pt_name = XMLSTR_TO_STR(env_pt->name);
 | 
						|
 | 
						|
                if (strequ(pt_name, "Point")) {
 | 
						|
                    xml_parse_node_by_spec(pt, env_pt, spec_env_pt, ARRAY_COUNT(spec_env_pt));
 | 
						|
                } else if (strequ(pt_name, "Disable")) {
 | 
						|
                    pt->delay = ADSR_DISABLE;
 | 
						|
                    pt->arg = 0;
 | 
						|
                } else if (strequ(pt_name, "Goto")) {
 | 
						|
                    pt->delay = ADSR_GOTO;
 | 
						|
                    xml_parse_node_by_spec(pt, env_pt, spec_env_goto, ARRAY_COUNT(spec_env_goto));
 | 
						|
                } else if (strequ(pt_name, "Restart")) {
 | 
						|
                    pt->delay = ADSR_RESTART;
 | 
						|
                    pt->arg = 0;
 | 
						|
                } else if (strequ(pt_name, "Hang")) {
 | 
						|
                    pt->delay = ADSR_HANG;
 | 
						|
                    pt->arg = 0;
 | 
						|
                    // TODO force end here and don't emit an extra hang
 | 
						|
                } else {
 | 
						|
                    error("Unexpected element node %s in envelope definition (line %d)", name, env->line);
 | 
						|
                }
 | 
						|
 | 
						|
                points_num++;
 | 
						|
            }
 | 
						|
            envdata->points = pts;
 | 
						|
            envdata->n_points = points_num;
 | 
						|
        }
 | 
						|
 | 
						|
        envdata->used = false;
 | 
						|
 | 
						|
        // link
 | 
						|
        if (sf->envelopes == NULL) {
 | 
						|
            sf->envelopes = envdata;
 | 
						|
            sf->envelope_last = envdata;
 | 
						|
        } else {
 | 
						|
            sf->envelope_last->next = envdata;
 | 
						|
            sf->envelope_last = envdata;
 | 
						|
        }
 | 
						|
        envdata->next = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
read_instrs_info(soundfont *sf, xmlNodePtr instrs)
 | 
						|
{
 | 
						|
    static const xml_attr_spec instr_spec = {
 | 
						|
        {"ProgramNumber", true,  xml_parse_uint,         offsetof(instr_data, program_number)   },
 | 
						|
        { "Name",         true,  xml_parse_c_identifier, offsetof(instr_data, name)             },
 | 
						|
        { "Envelope",     false, xml_parse_c_identifier, offsetof(instr_data, envelope_name)    },
 | 
						|
        { "Release",      true,  xml_parse_u8,           offsetof(instr_data, release)          },
 | 
						|
 | 
						|
        { "Sample",       true,  xml_parse_c_identifier, offsetof(instr_data, sample_name_mid)  },
 | 
						|
        { "BaseNote",     true,  xml_parse_note_number,  offsetof(instr_data, base_note_mid)    },
 | 
						|
        { "SampleRate",   true,  xml_parse_double,       offsetof(instr_data, sample_rate_mid)  },
 | 
						|
 | 
						|
        { "RangeLo",      true,  xml_parse_note_number,  offsetof(instr_data, sample_low_end)   },
 | 
						|
        { "SampleLo",     true,  xml_parse_c_identifier, offsetof(instr_data, sample_name_low)  },
 | 
						|
        { "BaseNoteLo",   true,  xml_parse_note_number,  offsetof(instr_data, base_note_lo)     },
 | 
						|
        { "SampleRateLo", true,  xml_parse_double,       offsetof(instr_data, sample_rate_lo)   },
 | 
						|
 | 
						|
        { "RangeHi",      true,  xml_parse_note_number,  offsetof(instr_data, sample_high_start)},
 | 
						|
        { "SampleHi",     true,  xml_parse_c_identifier, offsetof(instr_data, sample_name_high) },
 | 
						|
        { "BaseNoteHi",   true,  xml_parse_note_number,  offsetof(instr_data, base_note_hi)     },
 | 
						|
        { "SampleRateHi", true,  xml_parse_double,       offsetof(instr_data, sample_rate_hi)   },
 | 
						|
    };
 | 
						|
 | 
						|
    LL_FOREACH(xmlNodePtr, instr_node, instrs->children) {
 | 
						|
        if (instr_node->type != XML_ELEMENT_NODE)
 | 
						|
            continue;
 | 
						|
 | 
						|
        const char *name = XMLSTR_TO_STR(instr_node->name);
 | 
						|
 | 
						|
        bool is_instr = strequ(name, "Instrument");
 | 
						|
        bool is_instr_unused = strequ(name, "InstrumentUnused");
 | 
						|
 | 
						|
        if (!is_instr && !is_instr_unused)
 | 
						|
            error("Unexpected element node %s in instrument list (line %d)", name, instr_node->line);
 | 
						|
 | 
						|
        instr_data *instr = malloc(sizeof(instr_data));
 | 
						|
 | 
						|
        instr->program_number = (unsigned)-1;
 | 
						|
        instr->name = NULL;
 | 
						|
        instr->sample_name_low = NULL;
 | 
						|
        instr->sample_name_mid = NULL;
 | 
						|
        instr->sample_name_high = NULL;
 | 
						|
        instr->sample_low_end = INSTR_LO_NONE;
 | 
						|
        instr->sample_low = NULL;
 | 
						|
        instr->sample_high_start = INSTR_HI_NONE;
 | 
						|
        instr->sample_high = NULL;
 | 
						|
        instr->base_note_mid = NOTE_UNSET;
 | 
						|
        instr->base_note_lo = NOTE_UNSET;
 | 
						|
        instr->base_note_hi = NOTE_UNSET;
 | 
						|
        instr->sample_rate_mid = -1.0;
 | 
						|
        instr->sample_rate_lo = -1.0;
 | 
						|
        instr->sample_rate_hi = -1.0;
 | 
						|
        instr->release = RELEASE_UNSET;
 | 
						|
        instr->unused = is_instr_unused;
 | 
						|
 | 
						|
        xml_parse_node_by_spec(instr, instr_node, instr_spec, ARRAY_COUNT(instr_spec));
 | 
						|
 | 
						|
        if (!is_instr_unused) {
 | 
						|
            // Check program number, midi program number range is 0-127 but the audio driver reserves 126 and 127 for
 | 
						|
            // sfx and percussion so the range we allow is 0-125
 | 
						|
            if (instr->program_number >= 126)
 | 
						|
                error("Program numbers must be in the range 0-125 (got %u on line %d)", instr->program_number,
 | 
						|
                      instr_node->line);
 | 
						|
 | 
						|
            // Ensure program number is unique
 | 
						|
            unsigned upper = instr->program_number >> 5 & 3;
 | 
						|
            unsigned lower = instr->program_number & 0x1F;
 | 
						|
            if (sf->program_number_bitset[upper] & (1 << lower))
 | 
						|
                error("Duplicate program number %u (second occurrence on line %d)", instr->program_number,
 | 
						|
                      instr_node->line);
 | 
						|
            sf->program_number_bitset[upper] |= (1 << lower);
 | 
						|
 | 
						|
            if (instr->program_number >= sf->info.num_instruments)
 | 
						|
                sf->info.num_instruments = instr->program_number + 1;
 | 
						|
 | 
						|
            // Check name
 | 
						|
            if (instr->name == NULL)
 | 
						|
                error("Instrument must be named (line %d)", instr_node->line);
 | 
						|
        }
 | 
						|
 | 
						|
        // Check envelope
 | 
						|
        instr->envelope = sf_get_envelope(sf, instr->envelope_name);
 | 
						|
        if (instr->envelope == NULL)
 | 
						|
            error("Bad envelope name %s (line %d)", instr->envelope_name, instr_node->line);
 | 
						|
 | 
						|
        // Validate optionals
 | 
						|
        if (instr->release == RELEASE_UNSET)
 | 
						|
            instr->release = instr->envelope->release;
 | 
						|
 | 
						|
        if (instr->sample_name_mid == NULL) {
 | 
						|
            // For a used instrument to have no sample path, it must have sample children and have specified at least
 | 
						|
            // one of RangeLo or RangeHi
 | 
						|
 | 
						|
            if (instr->sample_low_end == INSTR_LO_NONE && instr->sample_high_start == INSTR_HI_NONE)
 | 
						|
                error("Instrument has no mid sample but also does not define a low or high sample (line %d)",
 | 
						|
                      instr_node->line);
 | 
						|
 | 
						|
            if (instr_node->children == NULL)
 | 
						|
                error("Instrument sample list is empty, must specify at least one sample (line %d)", instr_node->line);
 | 
						|
 | 
						|
            bool seen_low = false;
 | 
						|
            bool seen_mid = false;
 | 
						|
            bool seen_high = false;
 | 
						|
 | 
						|
            LL_FOREACH(xmlNodePtr, instr_sample_node, instr_node->children) {
 | 
						|
                if (instr_sample_node->type != XML_ELEMENT_NODE)
 | 
						|
                    continue;
 | 
						|
 | 
						|
                const char *name = XMLSTR_TO_STR(instr_sample_node->name);
 | 
						|
                if (!strequ(name, "Sample"))
 | 
						|
                    error("Unexpected element node %s in instrument sample list (line %d)", name,
 | 
						|
                          instr_sample_node->line);
 | 
						|
 | 
						|
                if (instr_sample_node->properties == NULL)
 | 
						|
                    error("Expected a Low/Mid/High sample path (line %d)", instr_sample_node->line);
 | 
						|
 | 
						|
                xmlAttrPtr attr = instr_sample_node->properties;
 | 
						|
                if (attr->next != NULL)
 | 
						|
                    error("Instrument sample should have exactly one attribute (line %d)", instr_sample_node->line);
 | 
						|
 | 
						|
                const char *attr_name = XMLSTR_TO_STR(attr->name);
 | 
						|
 | 
						|
                bool *seen;
 | 
						|
                const char **name_ptr;
 | 
						|
 | 
						|
                if (strequ(attr_name, "Low")) {
 | 
						|
                    seen = &seen_low;
 | 
						|
                    name_ptr = &instr->sample_name_low;
 | 
						|
 | 
						|
                    if (instr->sample_low_end == INSTR_LO_NONE)
 | 
						|
                        error("Useless Low sample specified (RangeLo is 0) (line %d)", instr_sample_node->line);
 | 
						|
                } else if (strequ(attr_name, "Mid")) {
 | 
						|
                    seen = &seen_mid;
 | 
						|
                    name_ptr = &instr->sample_name_mid;
 | 
						|
                } else if (strequ(attr_name, "High")) {
 | 
						|
                    seen = &seen_high;
 | 
						|
                    name_ptr = &instr->sample_name_high;
 | 
						|
 | 
						|
                    if (instr->sample_high_start == INSTR_HI_NONE)
 | 
						|
                        error("Useless High sample specified (RangeHi is 127) (line %d)", instr_sample_node->line);
 | 
						|
                } else {
 | 
						|
                    error("Unexpected attribute name for instrument sample (line %d)", instr_sample_node->line);
 | 
						|
                }
 | 
						|
 | 
						|
                if (*seen)
 | 
						|
                    error("Duplicate \"%s\" sample specifier in instrument sample (line %d)", attr_name,
 | 
						|
                          instr_sample_node->line);
 | 
						|
                *seen = true;
 | 
						|
 | 
						|
                xmlChar *xvalue = xmlNodeListGetString(instr_sample_node->doc, attr->children, 1);
 | 
						|
                const char *value = XMLSTR_TO_STR(xvalue);
 | 
						|
                xml_parse_c_identifier(value, name_ptr);
 | 
						|
            }
 | 
						|
 | 
						|
            if (!seen_mid && instr->sample_low_end != instr->sample_high_start)
 | 
						|
                error("Unset-but-used Mid sample (line %d)", instr_node->line);
 | 
						|
            if (!seen_low && instr->sample_low_end != 0)
 | 
						|
                error("Unset-but-used Low sample (line %d)", instr_node->line);
 | 
						|
            if (!seen_high && instr->sample_high_start != 0)
 | 
						|
                error("Unset-but-used High sample (line %d)", instr_node->line);
 | 
						|
        }
 | 
						|
 | 
						|
        if (instr->sample_name_low != NULL) {
 | 
						|
            instr->sample_low = sample_data_forname(sf, instr->sample_name_low);
 | 
						|
            if (instr->sample_low == NULL)
 | 
						|
                error("Bad sample name \"%s\" for LOW sample (line %d). Is it defined in <Samples>?",
 | 
						|
                      instr->sample_name_low, instr_node->line);
 | 
						|
 | 
						|
            if (instr->base_note_lo == NOTE_UNSET)
 | 
						|
                instr->base_note_lo = instr->sample_low->base_note;
 | 
						|
 | 
						|
            if (instr->sample_rate_lo < 0.0)
 | 
						|
                instr->sample_rate_lo = instr->sample_low->sample_rate;
 | 
						|
 | 
						|
            instr->sample_low_tuning = calc_tuning(instr->sample_rate_lo, instr->base_note_lo);
 | 
						|
        }
 | 
						|
 | 
						|
        instr->sample_mid = sample_data_forname(sf, instr->sample_name_mid);
 | 
						|
        if (instr->sample_mid == NULL)
 | 
						|
            error("Bad sample name \"%s\" for MID sample (line %d). Is it defined in <Samples>?",
 | 
						|
                  instr->sample_name_mid, instr_node->line);
 | 
						|
 | 
						|
        if (instr->base_note_mid == NOTE_UNSET)
 | 
						|
            instr->base_note_mid = instr->sample_mid->base_note;
 | 
						|
 | 
						|
        if (instr->sample_rate_mid < 0.0)
 | 
						|
            instr->sample_rate_mid = instr->sample_mid->sample_rate;
 | 
						|
 | 
						|
        instr->sample_mid_tuning = calc_tuning(instr->sample_rate_mid, instr->base_note_mid);
 | 
						|
 | 
						|
        // Some tuning values don't decompose properly into a samplerate and basenote, they must be accounted for here
 | 
						|
        // for matching. So far this has only been seen for an Instrument mid sample.
 | 
						|
        // NOTE: Keep in sync with the BAD_FLOATS list in extraction/tuning.py
 | 
						|
        if (f2i(instr->sample_mid_tuning) == 0x3E7319DF /* 0.237403377 */) // diff = 2^-24
 | 
						|
            instr->sample_mid_tuning = i2f(0x3E7319E3 /* 0.237403437 */);
 | 
						|
 | 
						|
        if (instr->sample_name_high != NULL) {
 | 
						|
            instr->sample_high = sample_data_forname(sf, instr->sample_name_high);
 | 
						|
            if (instr->sample_high == NULL)
 | 
						|
                error("Bad sample name \"%s\" for HIGH sample (line %d). Is it defined in <Samples>?",
 | 
						|
                      instr->sample_name_high, instr_node->line);
 | 
						|
 | 
						|
            if (instr->base_note_hi == NOTE_UNSET)
 | 
						|
                instr->base_note_hi = instr->sample_high->base_note;
 | 
						|
 | 
						|
            if (instr->sample_rate_hi < 0.0)
 | 
						|
                instr->sample_rate_hi = instr->sample_high->sample_rate;
 | 
						|
 | 
						|
            instr->sample_high_tuning = calc_tuning(instr->sample_rate_hi, instr->base_note_hi);
 | 
						|
        }
 | 
						|
 | 
						|
        // link
 | 
						|
        if (sf->instruments == NULL) {
 | 
						|
            sf->instruments = instr;
 | 
						|
            sf->instrument_last = instr;
 | 
						|
        } else {
 | 
						|
            sf->instrument_last->next = instr;
 | 
						|
            sf->instrument_last = instr;
 | 
						|
        }
 | 
						|
        instr->next = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
read_drums_info(soundfont *sf, xmlNodePtr drums)
 | 
						|
{
 | 
						|
    static const xml_attr_spec drum_spec = {
 | 
						|
        {"Name",        false, xml_parse_c_identifier, offsetof(drum_data, name)         },
 | 
						|
        { "Note",       true,  xml_parse_note_number,  offsetof(drum_data, note)         },
 | 
						|
        { "NoteStart",  true,  xml_parse_note_number,  offsetof(drum_data, note_start)   },
 | 
						|
        { "NoteEnd",    true,  xml_parse_note_number,  offsetof(drum_data, note_end)     },
 | 
						|
        { "Pan",        false, xml_parse_int,          offsetof(drum_data, pan)          },
 | 
						|
        { "Envelope",   false, xml_parse_c_identifier, offsetof(drum_data, envelope_name)},
 | 
						|
        { "Release",    true,  xml_parse_u8,           offsetof(drum_data, release)      },
 | 
						|
        { "Sample",     false, xml_parse_c_identifier, offsetof(drum_data, sample_name)  },
 | 
						|
        { "SampleRate", true,  xml_parse_double,       offsetof(drum_data, sample_rate)  },
 | 
						|
        { "BaseNote",   true,  xml_parse_note_number,  offsetof(drum_data, base_note)    },
 | 
						|
    };
 | 
						|
 | 
						|
    LL_FOREACH(xmlNodePtr, drum_node, drums->children) {
 | 
						|
        if (drum_node->type != XML_ELEMENT_NODE)
 | 
						|
            continue;
 | 
						|
 | 
						|
        const char *name = XMLSTR_TO_STR(drum_node->name);
 | 
						|
        if (!strequ(name, "Drum"))
 | 
						|
            error("Unexpected element node %s in drums list (line %d)", name, drum_node->line);
 | 
						|
 | 
						|
        drum_data *drum = malloc(sizeof(drum_data));
 | 
						|
        drum->note = NOTE_UNSET;
 | 
						|
        drum->note_start = NOTE_UNSET;
 | 
						|
        drum->note_end = NOTE_UNSET;
 | 
						|
        drum->sample_rate = -1;
 | 
						|
        drum->base_note = NOTE_UNSET;
 | 
						|
        drum->release = RELEASE_UNSET;
 | 
						|
 | 
						|
        if (drum_node->properties == NULL) {
 | 
						|
            // <Drum/>
 | 
						|
            drum->name = NULL;
 | 
						|
            drum->envelope = NULL;
 | 
						|
            drum->sample_name = NULL;
 | 
						|
            drum->sample = NULL;
 | 
						|
            goto link_drum;
 | 
						|
        }
 | 
						|
 | 
						|
        xml_parse_node_by_spec(drum, drum_node, drum_spec, ARRAY_COUNT(drum_spec));
 | 
						|
 | 
						|
        drum->envelope = sf_get_envelope(sf, drum->envelope_name);
 | 
						|
        if (drum->envelope == NULL)
 | 
						|
            error("Bad envelope name %s (line %d)", drum->envelope_name, drum_node->line);
 | 
						|
 | 
						|
        // validate optionals
 | 
						|
        if (drum->release == RELEASE_UNSET)
 | 
						|
            drum->release = drum->envelope->release;
 | 
						|
 | 
						|
        if (drum->note == NOTE_UNSET) {
 | 
						|
            if (drum->note_start == NOTE_UNSET || drum->note_end == NOTE_UNSET)
 | 
						|
                error("Incomplete note range specification (line %d)", drum_node->line);
 | 
						|
        } else {
 | 
						|
            if (drum->note_start != NOTE_UNSET || drum->note_end != NOTE_UNSET)
 | 
						|
                error("Overspecified note range (line %d)", drum_node->line);
 | 
						|
 | 
						|
            drum->note_start = drum->note_end = drum->note;
 | 
						|
        }
 | 
						|
 | 
						|
        if (drum->note_end < drum->note_start)
 | 
						|
            error("Invalid drum note range: [%d - %d] (line %d)", drum->note_start, drum->note_end, drum_node->line);
 | 
						|
 | 
						|
        drum->sample = sample_data_forname(sf, drum->sample_name);
 | 
						|
        if (drum->sample == NULL)
 | 
						|
            error("Bad sample name \"%s\" (line %d). Is it defined in <Samples>?", drum->sample_name, drum_node->line);
 | 
						|
 | 
						|
        // set final samplerate if not overridden
 | 
						|
        if (drum->sample_rate == -1) {
 | 
						|
            drum->sample_rate = drum->sample->sample_rate;
 | 
						|
        }
 | 
						|
 | 
						|
        // set basenote if not overridden
 | 
						|
        if (drum->base_note == NOTE_UNSET) {
 | 
						|
            if (drum->sample->aifc.has_inst) {
 | 
						|
                drum->base_note = drum->sample->base_note;
 | 
						|
            } else {
 | 
						|
                error("No basenote for drum (line %d)", drum_node->line);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // link
 | 
						|
    link_drum:
 | 
						|
        if (sf->drums == NULL) {
 | 
						|
            sf->drums = drum;
 | 
						|
            sf->drums_last = drum;
 | 
						|
        } else {
 | 
						|
            sf->drums_last->next = drum;
 | 
						|
            sf->drums_last = drum;
 | 
						|
        }
 | 
						|
        drum->next = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
read_sfx_info(soundfont *sf, xmlNodePtr effects)
 | 
						|
{
 | 
						|
    static const xml_attr_spec sfx_spec = {
 | 
						|
        {"Name",        false, xml_parse_c_identifier, offsetof(sfx_data, name)       },
 | 
						|
        { "Sample",     false, xml_parse_c_identifier, offsetof(sfx_data, sample_name)},
 | 
						|
        { "SampleRate", true,  xml_parse_double,       offsetof(sfx_data, sample_rate)},
 | 
						|
        { "BaseNote",   true,  xml_parse_note_number,  offsetof(sfx_data, base_note)  },
 | 
						|
    };
 | 
						|
 | 
						|
    LL_FOREACH(xmlNodePtr, sfx_node, effects->children) {
 | 
						|
        if (sfx_node->type != XML_ELEMENT_NODE)
 | 
						|
            continue;
 | 
						|
 | 
						|
        const char *name = XMLSTR_TO_STR(sfx_node->name);
 | 
						|
        if (!strequ(name, "Effect"))
 | 
						|
            error("Unexpected element node %s in effects list (line %d)", name, sfx_node->line);
 | 
						|
 | 
						|
        sf->info.num_effects++;
 | 
						|
 | 
						|
        sfx_data *sfx = malloc(sizeof(sfx_data));
 | 
						|
 | 
						|
        if (sfx_node->properties == NULL) {
 | 
						|
            sfx->sample = NULL;
 | 
						|
        } else {
 | 
						|
            sfx->sample_rate = -1;
 | 
						|
            sfx->base_note = NOTE_UNSET;
 | 
						|
            xml_parse_node_by_spec(sfx, sfx_node, sfx_spec, ARRAY_COUNT(sfx_spec));
 | 
						|
 | 
						|
            sfx->sample = sample_data_forname(sf, sfx->sample_name);
 | 
						|
            if (sfx->sample == NULL)
 | 
						|
                error("Bad sample name \"%s\" (line %d). Is it defined in <Samples>?", sfx->sample_name,
 | 
						|
                      sfx_node->line);
 | 
						|
 | 
						|
            if (sfx->base_note == NOTE_UNSET)
 | 
						|
                sfx->base_note = sfx->sample->base_note;
 | 
						|
 | 
						|
            if (sfx->sample_rate == -1)
 | 
						|
                sfx->sample_rate = sfx->sample->sample_rate;
 | 
						|
 | 
						|
            sfx->tuning = calc_tuning(sfx->sample_rate, sfx->base_note);
 | 
						|
        }
 | 
						|
 | 
						|
        // link
 | 
						|
        if (sf->sfx == NULL) {
 | 
						|
            sf->sfx = sfx;
 | 
						|
            sf->sfx_last = sfx;
 | 
						|
        } else {
 | 
						|
            sf->sfx_last->next = sfx;
 | 
						|
            sf->sfx_last = sfx;
 | 
						|
        }
 | 
						|
        sfx->next = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    bool is_dd;
 | 
						|
    bool cached;
 | 
						|
} sample_data_defaults;
 | 
						|
 | 
						|
void
 | 
						|
read_samples_info(soundfont *sf, xmlNodePtr samples)
 | 
						|
{
 | 
						|
    static const xml_attr_spec samples_spec = {
 | 
						|
        {"IsDD",    true, xml_parse_bool, offsetof(sample_data_defaults, is_dd) },
 | 
						|
        { "Cached", true, xml_parse_bool, offsetof(sample_data_defaults, cached)},
 | 
						|
    };
 | 
						|
    static const xml_attr_spec sample_spec = {
 | 
						|
        {"Name",        false, xml_parse_c_identifier, offsetof(sample_data, name)       },
 | 
						|
        { "SampleRate", true,  xml_parse_double,       offsetof(sample_data, sample_rate)},
 | 
						|
        { "BaseNote",   true,  xml_parse_note_number,  offsetof(sample_data, base_note)  },
 | 
						|
        { "IsDD",       true,  xml_parse_bool,         offsetof(sample_data, is_dd)      },
 | 
						|
        { "Cached",     true,  xml_parse_bool,         offsetof(sample_data, cached)     },
 | 
						|
    };
 | 
						|
 | 
						|
    sample_data_defaults defaults;
 | 
						|
    defaults.is_dd = false;
 | 
						|
    defaults.cached = false;
 | 
						|
    xml_parse_node_by_spec(&defaults, samples, samples_spec, ARRAY_COUNT(samples_spec));
 | 
						|
 | 
						|
    LL_FOREACH(xmlNodePtr, sample_node, samples->children) {
 | 
						|
        if (sample_node->type != XML_ELEMENT_NODE)
 | 
						|
            continue;
 | 
						|
 | 
						|
        const char *name = XMLSTR_TO_STR(sample_node->name);
 | 
						|
        if (!strequ(name, "Sample"))
 | 
						|
            error("Unexpected element node %s in samples list (line %d)", name, sample_node->line);
 | 
						|
 | 
						|
        sample_data *sample = malloc(sizeof(sample_data));
 | 
						|
 | 
						|
        sample->sample_rate = -1.0;
 | 
						|
        sample->base_note = NOTE_UNSET;
 | 
						|
        sample->is_dd = defaults.is_dd;
 | 
						|
        sample->cached = defaults.cached;
 | 
						|
 | 
						|
        xml_parse_node_by_spec(sample, sample_node, sample_spec, ARRAY_COUNT(sample_spec));
 | 
						|
 | 
						|
        samplebank *sb = (sample->is_dd) ? &sf->sbdd : &sf->sb;
 | 
						|
 | 
						|
        const char *sample_path = samplebank_path_forname(sb, sample->name);
 | 
						|
        if (sample_path == NULL)
 | 
						|
            error("Bad sample name %s, does it exist in the samplebank? (line %d)", sample->name, sample_node->line);
 | 
						|
 | 
						|
        aifc_read(&sample->aifc, sample_path, NULL, NULL);
 | 
						|
 | 
						|
        if (sample->sample_rate == -1.0)
 | 
						|
            sample->sample_rate = sample->aifc.sample_rate;
 | 
						|
 | 
						|
        if (sample->base_note == NOTE_UNSET) {
 | 
						|
            if (sample->aifc.has_inst)
 | 
						|
                sample->base_note = midinote_to_z64note(sample->aifc.basenote);
 | 
						|
            else
 | 
						|
                error("No basenote for sample %s (line %d)", sample->name, sample_node->line);
 | 
						|
        }
 | 
						|
 | 
						|
        if (!sample->aifc.has_book)
 | 
						|
            error("No vadpcm codebook for sample %s (line %d)", sample->name, sample_node->line);
 | 
						|
 | 
						|
        // link
 | 
						|
        if (sf->samples == NULL) {
 | 
						|
            sf->samples = sample;
 | 
						|
            sf->sample_last = sample;
 | 
						|
        } else {
 | 
						|
            sf->sample_last->next = sample;
 | 
						|
            sf->sample_last = sample;
 | 
						|
        }
 | 
						|
        sample->next = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static bool
 | 
						|
is_hex(char c)
 | 
						|
{
 | 
						|
    return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F');
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
from_hex(char c)
 | 
						|
{
 | 
						|
    if ('0' <= c && c <= '9')
 | 
						|
        return c - '0';
 | 
						|
    if ('A' <= c && c <= 'F')
 | 
						|
        return c - 'A' + 10;
 | 
						|
    assert(false);
 | 
						|
    return -0xABABABAB;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
read_match_padding(soundfont *sf, xmlNodePtr padding_decl)
 | 
						|
{
 | 
						|
    if (padding_decl->properties != NULL)
 | 
						|
        error("Unexpected properties for MatchPadding declaration (line %d)", padding_decl->line);
 | 
						|
 | 
						|
    if (padding_decl->children == NULL || padding_decl->children->content == NULL)
 | 
						|
        error("No data declared for MatchPadding (line %d)", padding_decl->line);
 | 
						|
 | 
						|
    if (padding_decl->children->next != NULL)
 | 
						|
        error("Unexpected layout for MatchPadding declaration (line %d)", padding_decl->line);
 | 
						|
 | 
						|
    const char *data_str = XMLSTR_TO_STR(padding_decl->children->content);
 | 
						|
    size_t data_len = strlen(data_str);
 | 
						|
 | 
						|
    // We expect padding to be bytes like 0xAB separated by comma or whitespace, so string length / 5 is the upper bound
 | 
						|
    uint8_t *padding = malloc(data_len / 5);
 | 
						|
 | 
						|
    size_t k = 0;
 | 
						|
    bool must_be_delimiter = false;
 | 
						|
 | 
						|
    for (size_t i = 0; i < data_len - 4; i++) {
 | 
						|
        if (isspace(data_str[i]) || data_str[i] == ',') {
 | 
						|
            must_be_delimiter = false;
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        if (must_be_delimiter)
 | 
						|
            error("Malformed padding data, expected a space or comma at position %ld", i);
 | 
						|
 | 
						|
        if (data_str[i + 0] != '0' || data_str[i + 1] != 'x')
 | 
						|
            error("Malformed padding data, expected an 0x prefix at position %ld", i);
 | 
						|
 | 
						|
        char c1 = toupper(data_str[i + 2]);
 | 
						|
        char c2 = toupper(data_str[i + 3]);
 | 
						|
 | 
						|
        if (!is_hex(c1) || !is_hex(c2))
 | 
						|
            error("Malformed padding data, expected hexadecimal digits at position %ld", i + 2);
 | 
						|
 | 
						|
        padding[k++] = (from_hex(c1) << 4) | from_hex(c2);
 | 
						|
        must_be_delimiter = true;
 | 
						|
        i += 3;
 | 
						|
    }
 | 
						|
 | 
						|
    sf->match_padding = padding;
 | 
						|
    sf->match_padding_num = k;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Emit a padding statement that pads to the next 0x10 byte boundary. Assumes that `pos` measures from an 0x10-byte
 | 
						|
 * aligned location.
 | 
						|
 */
 | 
						|
static void
 | 
						|
emit_padding_stmt(FILE *out, unsigned pos)
 | 
						|
{
 | 
						|
    switch (ALIGN16(pos) - pos) {
 | 
						|
        case 0:
 | 
						|
            // Already aligned, pass silently
 | 
						|
            break;
 | 
						|
        case 4:
 | 
						|
            fprintf(out, "SF_PAD4();\n");
 | 
						|
            break;
 | 
						|
        case 8:
 | 
						|
            fprintf(out, "SF_PAD8();\n");
 | 
						|
            break;
 | 
						|
        case 0xC:
 | 
						|
            fprintf(out, "SF_PADC();\n");
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            // We don't expect to need to support alignment from anything less than word-aligned.
 | 
						|
            error("[Internal] Bad alignment generated");
 | 
						|
            break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
size_t
 | 
						|
emit_c_header(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    size_t size = 0;
 | 
						|
 | 
						|
    fprintf(out, "// HEADER\n\n");
 | 
						|
 | 
						|
    // Generate externs for use in the header.
 | 
						|
 | 
						|
    if (sf->drums != NULL)
 | 
						|
        fprintf(out, "extern Drum* SF%d_DRUMS_PTR_LIST[];\n\n", sf->info.index);
 | 
						|
 | 
						|
    if (sf->sfx != NULL)
 | 
						|
        fprintf(out, "extern SoundEffect SF%d_SFX_LIST[];\n\n", sf->info.index);
 | 
						|
 | 
						|
    if (sf->instruments != NULL) {
 | 
						|
        // Externs are emitted in struct order
 | 
						|
        LL_FOREACH(instr_data *, instr, sf->instruments) {
 | 
						|
            if (instr->unused)
 | 
						|
                continue;
 | 
						|
            fprintf(out, "extern Instrument SF%d_%s;\n", sf->info.index, instr->name);
 | 
						|
        }
 | 
						|
        fprintf(out, "\n");
 | 
						|
    }
 | 
						|
 | 
						|
    // Generate the header itself: drums -> sfx -> instruments.
 | 
						|
 | 
						|
    // We always need to write pointers for drums and sfx even if they are NULL.
 | 
						|
 | 
						|
    uint32_t pos = 0;
 | 
						|
 | 
						|
    if (sf->drums != NULL)
 | 
						|
        fprintf(out, "NO_REORDER SECTION_DATA Drum** SF%d_DRUMS_PTR_LIST_PTR = SF%d_DRUMS_PTR_LIST;\n", sf->info.index,
 | 
						|
                sf->info.index);
 | 
						|
    else
 | 
						|
        fprintf(out, "NO_REORDER SECTION_DATA Drum** SF%d_DRUMS_PTR_LIST_PTR = NULL;\n", sf->info.index);
 | 
						|
 | 
						|
    pos += 4;
 | 
						|
    size += 4;
 | 
						|
 | 
						|
    if (sf->sfx != NULL)
 | 
						|
        fprintf(out, "NO_REORDER SECTION_DATA SoundEffect* SF%d_SFX_LIST_PTR = SF%d_SFX_LIST;\n", sf->info.index,
 | 
						|
                sf->info.index);
 | 
						|
    else
 | 
						|
        fprintf(out, "NO_REORDER SECTION_DATA SoundEffect* SF%d_SFX_LIST_PTR = NULL;\n", sf->info.index);
 | 
						|
 | 
						|
    pos += 4;
 | 
						|
    size += 4;
 | 
						|
 | 
						|
    if (sf->instruments != NULL) {
 | 
						|
        const char **instr_names = calloc(sf->info.num_instruments, sizeof(const char *));
 | 
						|
 | 
						|
        // The instrument pointer table is indexed by program number. Since sf->instruments is sorted by struct index
 | 
						|
        // we must first sort by program number.
 | 
						|
        LL_FOREACH(instr_data *, instr, sf->instruments) {
 | 
						|
            if (instr->unused)
 | 
						|
                continue; // Unused instruments are not included in the table and have no meaningful program number
 | 
						|
            instr_names[instr->program_number] = instr->name;
 | 
						|
        }
 | 
						|
 | 
						|
        fprintf(out, "NO_REORDER SECTION_DATA Instrument* SF%d_INSTRUMENT_PTR_LIST[] = {\n", sf->info.index);
 | 
						|
 | 
						|
        for (unsigned i = 0; i < sf->info.num_instruments; i++) {
 | 
						|
            if (instr_names[i] == NULL)
 | 
						|
                fprintf(out, "    NULL,\n");
 | 
						|
            else
 | 
						|
                fprintf(out, "    &SF%d_%s,\n", sf->info.index, instr_names[i]);
 | 
						|
            pos += 4;
 | 
						|
            size += 4;
 | 
						|
        }
 | 
						|
        fprintf(out, "};\n");
 | 
						|
 | 
						|
        free(instr_names);
 | 
						|
    }
 | 
						|
 | 
						|
    // Pad the header to the next 0x10-byte boundary.
 | 
						|
    emit_padding_stmt(out, pos);
 | 
						|
    fprintf(out, "\n");
 | 
						|
 | 
						|
    return ALIGN16(size);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Convert the compression type as indicated in the AIFC to the corresponding SampleCodec enum value.
 | 
						|
 * These must be kept in sync with the SampleCodec definition!
 | 
						|
 */
 | 
						|
static const char *
 | 
						|
codec_enum(uint32_t compression_type, const char *origin_file)
 | 
						|
{
 | 
						|
    switch (compression_type) {
 | 
						|
        case CC4('A', 'D', 'P', '9'):
 | 
						|
            return "CODEC_ADPCM";
 | 
						|
 | 
						|
        case CC4('H', 'P', 'C', 'M'):
 | 
						|
            return "CODEC_S8";
 | 
						|
 | 
						|
        case CC4('A', 'D', 'P', '5'):
 | 
						|
            return "CODEC_SMALL_ADPCM";
 | 
						|
 | 
						|
        case CC4('R', 'V', 'R', 'B'):
 | 
						|
            return "CODEC_REVERB";
 | 
						|
 | 
						|
        case CC4('N', 'O', 'N', 'E'):
 | 
						|
            return "CODEC_S16";
 | 
						|
    }
 | 
						|
    error("Bad compression type in aifc file %s", origin_file);
 | 
						|
    __builtin_unreachable();
 | 
						|
}
 | 
						|
 | 
						|
static unsigned int
 | 
						|
codec_frame_size(uint32_t compression_type)
 | 
						|
{
 | 
						|
    switch (compression_type) {
 | 
						|
        case CC4('A', 'D', 'P', '9'):
 | 
						|
            return 9;
 | 
						|
 | 
						|
        case CC4('A', 'D', 'P', '5'):
 | 
						|
            return 5;
 | 
						|
 | 
						|
        default: // TODO should any others not use 16?
 | 
						|
            return 16;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Compare the codebooks of two samples. Returns true if they are identical.
 | 
						|
 */
 | 
						|
static bool
 | 
						|
samples_books_equal(sample_data *s1, sample_data *s2)
 | 
						|
{
 | 
						|
    int32_t s1_order = s1->aifc.book.order;
 | 
						|
    int32_t s1_npredictors = s1->aifc.book.npredictors;
 | 
						|
    int32_t s2_order = s1->aifc.book.order;
 | 
						|
    int32_t s2_npredictors = s1->aifc.book.npredictors;
 | 
						|
 | 
						|
    if (s1_order != s2_order || s1_npredictors != s2_npredictors)
 | 
						|
        return false;
 | 
						|
    return !memcmp(*s1->aifc.book_state, *s2->aifc.book_state, 8 * (unsigned)s1_order * (unsigned)s1_npredictors);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Writes all samples, their codebooks and their loops to C structures.
 | 
						|
 */
 | 
						|
size_t
 | 
						|
emit_c_samples(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    size_t size = 0;
 | 
						|
 | 
						|
    if (sf->samples == NULL)
 | 
						|
        return size;
 | 
						|
 | 
						|
    int i = 0;
 | 
						|
    LL_FOREACH(sample_data *, sample, sf->samples) {
 | 
						|
        // Determine if we need to write a new book structure. If we've already emitted a book structure with the
 | 
						|
        // same contents we use that instead.
 | 
						|
 | 
						|
        bool new_book = true;
 | 
						|
        const char *bookname = sample->name;
 | 
						|
 | 
						|
        LL_FOREACH(sample_data *, sample2, sf->samples) {
 | 
						|
            if (sample2 == sample)
 | 
						|
                // Caught up to our current position, we need to write a new book.
 | 
						|
                break;
 | 
						|
 | 
						|
            if (samples_books_equal(sample, sample2)) {
 | 
						|
                // A book that we've already seen is the same as this one. Since the book we are comparing to here is
 | 
						|
                // the first such book, this is guaranteed to have already been written and we move the reference to
 | 
						|
                // this one.
 | 
						|
                new_book = false;
 | 
						|
                bookname = sample2->name;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        fprintf(out, "// SAMPLE %d\n\n", i);
 | 
						|
 | 
						|
        // Write the sample header
 | 
						|
 | 
						|
        samplebank *sb = (sample->is_dd) ? &sf->sbdd : &sf->sb;
 | 
						|
 | 
						|
        // Note: We could skip writing the book extern if new_book is false, but it's probably not worth the extra code
 | 
						|
        fprintf(out,
 | 
						|
                // clang-format off
 | 
						|
               "extern u8 %s_%s_Off[];"         "\n"
 | 
						|
               "extern AdpcmBook SF%d_%s_BOOK;" "\n"
 | 
						|
               "extern AdpcmLoop SF%d_%s_LOOP;" "\n"
 | 
						|
                                                "\n",
 | 
						|
                // clang-format on
 | 
						|
                sb->name, sample->name, sf->info.index, bookname, sf->info.index, sample->name);
 | 
						|
 | 
						|
        const char *codec_name = codec_enum(sample->aifc.compression_type, sample->aifc.path);
 | 
						|
 | 
						|
        fprintf(out,
 | 
						|
                // clang-format off
 | 
						|
               "NO_REORDER SECTION_DATA ALIGNED(16) Sample SF%d_%s_HEADER = {"  "\n"
 | 
						|
               "    "
 | 
						|
#ifdef SFC_MM
 | 
						|
                // MM has an extra unused field in the sample structure compared to OoT
 | 
						|
                   "%d, "
 | 
						|
#endif
 | 
						|
                   "%s, %d, %s, %s,"                                            "\n"
 | 
						|
               "    0x%06lX,"                                                   "\n"
 | 
						|
               "    %s_%s_Off,"                                                 "\n"
 | 
						|
               "    &SF%d_%s_LOOP,"                                             "\n"
 | 
						|
               "    &SF%d_%s_BOOK,"                                             "\n"
 | 
						|
               "};"                                                             "\n"
 | 
						|
                                                                                "\n",
 | 
						|
                // clang-format on
 | 
						|
                sf->info.index, sample->name,
 | 
						|
#ifdef SFC_MM
 | 
						|
                0,
 | 
						|
#endif
 | 
						|
                codec_name, sample->is_dd, BOOL_STR(sample->cached), BOOL_STR(false), sample->aifc.ssnd_size, sb->name,
 | 
						|
                sample->name, sf->info.index, sample->name, sf->info.index, bookname);
 | 
						|
        size += 0x10;
 | 
						|
 | 
						|
        // Write the book if it hasn't been deduplicated.
 | 
						|
 | 
						|
        if (new_book) {
 | 
						|
            // Since books are variable-size structures and we want to support a C89 compiler, we first write the
 | 
						|
            // header as one structure and the book state as an array. We then declare a weak symbol for the book
 | 
						|
            // header to alias it to the correct type without casts, avoiding potential type conflicts with externs.
 | 
						|
            size_t book_size = 0;
 | 
						|
 | 
						|
            fprintf(out,
 | 
						|
                    // clang-format off
 | 
						|
                   "NO_REORDER SECTION_DATA ALIGNED(16) AdpcmBookHeader SF%d_%s_BOOK_HEADER = {"    "\n"
 | 
						|
                   "    %d, %d,"                                                                    "\n"
 | 
						|
                   "};"                                                                             "\n"
 | 
						|
                   "NO_REORDER SECTION_DATA s16 SF%d_%s_BOOK_DATA[] = {"                            "\n",
 | 
						|
                    // clang-format on
 | 
						|
                    sf->info.index, bookname, sample->aifc.book.order, sample->aifc.book.npredictors, sf->info.index,
 | 
						|
                    bookname);
 | 
						|
            book_size += 8;
 | 
						|
 | 
						|
            for (size_t j = 0; j < (unsigned)sample->aifc.book.order * (unsigned)sample->aifc.book.npredictors; j++) {
 | 
						|
                fprintf(
 | 
						|
                    out,
 | 
						|
                    // clang-format off
 | 
						|
                       "    (s16)0x%04X, (s16)0x%04X, (s16)0x%04X, (s16)0x%04X, "
 | 
						|
                           "(s16)0x%04X, (s16)0x%04X, (s16)0x%04X, (s16)0x%04X,\n",
 | 
						|
                    // clang-format on
 | 
						|
                    (uint16_t)(*sample->aifc.book_state)[j * 8 + 0], (uint16_t)(*sample->aifc.book_state)[j * 8 + 1],
 | 
						|
                    (uint16_t)(*sample->aifc.book_state)[j * 8 + 2], (uint16_t)(*sample->aifc.book_state)[j * 8 + 3],
 | 
						|
                    (uint16_t)(*sample->aifc.book_state)[j * 8 + 4], (uint16_t)(*sample->aifc.book_state)[j * 8 + 5],
 | 
						|
                    (uint16_t)(*sample->aifc.book_state)[j * 8 + 6], (uint16_t)(*sample->aifc.book_state)[j * 8 + 7]);
 | 
						|
            }
 | 
						|
 | 
						|
            fprintf(out,
 | 
						|
                    // clang-format off
 | 
						|
                   "};"                                                 "\n"
 | 
						|
                   "#pragma weak SF%d_%s_BOOK = SF%d_%s_BOOK_HEADER"    "\n",
 | 
						|
                    // clang-format on
 | 
						|
                    sf->info.index, bookname, sf->info.index, bookname);
 | 
						|
 | 
						|
            // We assume here that book structures begin on 0x10-byte boundaries. Book structures are always
 | 
						|
            // `4 + 4 + 8 * order * npredictors` large, emit a padding statement to the next 0x10-byte boundary.
 | 
						|
            book_size += 2 * 8 * (unsigned)sample->aifc.book.order * (unsigned)sample->aifc.book.npredictors;
 | 
						|
            emit_padding_stmt(out, book_size);
 | 
						|
            fprintf(out, "\n");
 | 
						|
 | 
						|
            size += ALIGN16(book_size);
 | 
						|
        }
 | 
						|
 | 
						|
        // Write the loop
 | 
						|
 | 
						|
        // Can't use sample->aifc.num_frames directly, the original vadpcm_enc tool occasionally got the number
 | 
						|
        // of frames wrong (off-by-1) which we must reproduce here for matching (rather than reproducing it in the
 | 
						|
        // aifc and wav/aiff files themselves)
 | 
						|
        uint32_t frame_count = (sample->aifc.ssnd_size * 16) / codec_frame_size(sample->aifc.compression_type);
 | 
						|
 | 
						|
        // We cannot deduplicate or skip writing loops in general as the audio driver assumes that at least a loop
 | 
						|
        // header exists for every sample. We could deduplicate on the special case that two samples have the same
 | 
						|
        // frame count? TODO
 | 
						|
 | 
						|
        if (!sample->aifc.has_loop || sample->aifc.loop.count == 0) {
 | 
						|
            // No loop present, or a loop with a count of 0 was explicitly written into the aifc.
 | 
						|
            // Write a header only, using the same weak symbol trick as with books.
 | 
						|
 | 
						|
            uint32_t start;
 | 
						|
            uint32_t end;
 | 
						|
            uint32_t count;
 | 
						|
 | 
						|
            if (!sample->aifc.has_loop) {
 | 
						|
                // No loop, write a loop header that spans the entire sample with a count of 0.
 | 
						|
                // The audio driver expects that a loop structure always exists for a sample.
 | 
						|
                start = 0;
 | 
						|
                end = frame_count;
 | 
						|
                count = 0;
 | 
						|
            } else {
 | 
						|
                // There is a count=0 loop in the aifc file, trust it.
 | 
						|
                start = sample->aifc.loop.start;
 | 
						|
                end = sample->aifc.loop.end;
 | 
						|
                count = sample->aifc.loop.count;
 | 
						|
            }
 | 
						|
 | 
						|
            fprintf(out,
 | 
						|
                    // clang-format off
 | 
						|
                   "NO_REORDER SECTION_DATA ALIGNED(16) AdpcmLoopHeader SF%d_%s_LOOP_HEADER = {"    "\n"
 | 
						|
                   "    %u, %u, %u, 0,"                                                             "\n"
 | 
						|
                   "};"                                                                             "\n"
 | 
						|
                   "#pragma weak SF%d_%s_LOOP = SF%d_%s_LOOP_HEADER"                                "\n"
 | 
						|
                                                                                                    "\n",
 | 
						|
                    // clang-format on
 | 
						|
                    sf->info.index, sample->name, start, end, count, sf->info.index, sample->name, sf->info.index,
 | 
						|
                    sample->name);
 | 
						|
            size += 0x10;
 | 
						|
        } else {
 | 
						|
            // With state, since loop states are a fixed size there is no need for a weak alias.
 | 
						|
 | 
						|
            // Some soundfonts include the total frame count of the sample, but not all of them.
 | 
						|
            // Set the frame count to 0 here to inhibit writing it into the loop structure if this is
 | 
						|
            // a soundfont that does not include it.
 | 
						|
            if (!sf->info.loops_have_frames)
 | 
						|
                frame_count = 0;
 | 
						|
 | 
						|
            char count_str[12];
 | 
						|
 | 
						|
            if (sample->aifc.loop.count == 0xFFFFFFFF)
 | 
						|
                snprintf(count_str, sizeof(count_str), "0x%08X", sample->aifc.loop.count);
 | 
						|
            else
 | 
						|
                snprintf(count_str, sizeof(count_str), "%u", sample->aifc.loop.count);
 | 
						|
 | 
						|
            fprintf(out,
 | 
						|
                    // clang-format off
 | 
						|
                   "NO_REORDER SECTION_DATA ALIGNED(16) AdpcmLoop SF%d_%s_LOOP = {"     "\n"
 | 
						|
                   "    { %u, %u, %s, %u },"                                            "\n"
 | 
						|
                   "    {"                                                              "\n"
 | 
						|
                   "        (s16)0x%04X, (s16)0x%04X, (s16)0x%04X, (s16)0x%04X,"        "\n"
 | 
						|
                   "        (s16)0x%04X, (s16)0x%04X, (s16)0x%04X, (s16)0x%04X,"        "\n"
 | 
						|
                   "        (s16)0x%04X, (s16)0x%04X, (s16)0x%04X, (s16)0x%04X,"        "\n"
 | 
						|
                   "        (s16)0x%04X, (s16)0x%04X, (s16)0x%04X, (s16)0x%04X,"        "\n"
 | 
						|
                   "    },"                                                             "\n"
 | 
						|
                   "};"                                                                 "\n"
 | 
						|
                                                                                        "\n",
 | 
						|
                    // clang-format on
 | 
						|
                    sf->info.index, sample->name, sample->aifc.loop.start, sample->aifc.loop.end, count_str,
 | 
						|
                    frame_count, (uint16_t)sample->aifc.loop.state[0], (uint16_t)sample->aifc.loop.state[1],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[2], (uint16_t)sample->aifc.loop.state[3],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[4], (uint16_t)sample->aifc.loop.state[5],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[6], (uint16_t)sample->aifc.loop.state[7],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[8], (uint16_t)sample->aifc.loop.state[9],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[10], (uint16_t)sample->aifc.loop.state[11],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[12], (uint16_t)sample->aifc.loop.state[13],
 | 
						|
                    (uint16_t)sample->aifc.loop.state[14], (uint16_t)sample->aifc.loop.state[15]);
 | 
						|
            size += 0x30;
 | 
						|
        }
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Write envelope structures.
 | 
						|
 */
 | 
						|
size_t
 | 
						|
emit_c_envelopes(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    size_t size = 0;
 | 
						|
 | 
						|
    if (sf->envelopes == NULL)
 | 
						|
        return size;
 | 
						|
 | 
						|
    fprintf(out, "// ENVELOPES\n\n");
 | 
						|
 | 
						|
    size_t empty_num = 0;
 | 
						|
 | 
						|
    LL_FOREACH(envelope_data *, envdata, sf->envelopes) {
 | 
						|
        if (sf->matching && envdata->name == NULL) {
 | 
						|
            // For MM: write 16 bytes of 0 when matching
 | 
						|
 | 
						|
            fprintf(out,
 | 
						|
                    // clang-format off
 | 
						|
                   "NO_REORDER SECTION_DATA ALIGNED(16) EnvelopePoint SF%d_ENV_EMPTY_%lu[] = {"     "\n"
 | 
						|
                   "    { 0, 0, },"                                                                 "\n"
 | 
						|
                   "    { 0, 0, },"                                                                 "\n"
 | 
						|
                   "    { 0, 0, },"                                                                 "\n"
 | 
						|
                   "    { 0, 0, },"                                                                 "\n"
 | 
						|
                   "};"                                                                             "\n"
 | 
						|
                                                                                                    "\n",
 | 
						|
                    // clang-format on
 | 
						|
                    sf->info.index, empty_num);
 | 
						|
 | 
						|
            empty_num++;
 | 
						|
            size += 0x10;
 | 
						|
        } else {
 | 
						|
            fprintf(out, "NO_REORDER SECTION_DATA ALIGNED(16) EnvelopePoint SF%d_%s[] = {\n", sf->info.index,
 | 
						|
                    envdata->name);
 | 
						|
 | 
						|
            // Write all points
 | 
						|
            for (size_t j = 0; j < envdata->n_points; j++) {
 | 
						|
                envelope_point *pt = &envdata->points[j];
 | 
						|
 | 
						|
                switch (pt->delay) {
 | 
						|
                    case ADSR_DISABLE:
 | 
						|
                        fprintf(out, "    ENVELOPE_DISABLE(),\n");
 | 
						|
                        break;
 | 
						|
                    case ADSR_GOTO:
 | 
						|
                        fprintf(out, "    ENVELOPE_GOTO(%d),\n", pt->arg);
 | 
						|
                        break;
 | 
						|
                    case ADSR_HANG:
 | 
						|
                        fprintf(out, "    ENVELOPE_HANG(),\n");
 | 
						|
                        break;
 | 
						|
                    case ADSR_RESTART:
 | 
						|
                        fprintf(out, "    ENVELOPE_RESTART(),\n");
 | 
						|
                        break;
 | 
						|
                    default:
 | 
						|
                        fprintf(out, "    ENVELOPE_POINT(%5d, %5d),\n", pt->delay, pt->arg);
 | 
						|
                        break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Automatically add a HANG command at the end
 | 
						|
            fprintf(out, "    ENVELOPE_HANG(),\n"
 | 
						|
                         "};\n");
 | 
						|
 | 
						|
            // Pad to 0x10-byte boundary
 | 
						|
            size_t env_size = 4 * (envdata->n_points + 1);
 | 
						|
            emit_padding_stmt(out, env_size);
 | 
						|
            fprintf(out, "\n");
 | 
						|
 | 
						|
            size += ALIGN16(env_size);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
#define F32_FMT "%.22f"
 | 
						|
 | 
						|
size_t
 | 
						|
emit_c_instruments(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    size_t size = 0;
 | 
						|
 | 
						|
    fprintf(out, "// INSTRUMENTS\n\n");
 | 
						|
 | 
						|
    size_t unused_instr_num = 0;
 | 
						|
 | 
						|
    LL_FOREACH(instr_data *, instr, sf->instruments) {
 | 
						|
        if (instr->unused) {
 | 
						|
            fprintf(out, "NO_REORDER SECTION_DATA Instrument SF%d_INSTR_UNUSED_%lu = {\n", sf->info.index,
 | 
						|
                    unused_instr_num);
 | 
						|
            unused_instr_num++;
 | 
						|
        } else {
 | 
						|
            fprintf(out, "NO_REORDER SECTION_DATA Instrument SF%d_%s = {\n", sf->info.index, instr->name);
 | 
						|
        }
 | 
						|
 | 
						|
        char nlo[5];
 | 
						|
        snprintf(nlo, sizeof(nlo), "%3d", instr->sample_low_end);
 | 
						|
        char nhi[5];
 | 
						|
        snprintf(nhi, sizeof(nhi), "%3d", instr->sample_high_start);
 | 
						|
 | 
						|
        fprintf(out,
 | 
						|
                // clang-format off
 | 
						|
               "    false,"         "\n"
 | 
						|
               "    %s,"            "\n"
 | 
						|
               "    %s,"            "\n"
 | 
						|
               "    %d,"            "\n"
 | 
						|
               "    SF%d_%s,"       "\n",
 | 
						|
                // clang-format on
 | 
						|
                (instr->sample_low_end == INSTR_LO_NONE) ? "INSTR_SAMPLE_LO_NONE" : nlo,
 | 
						|
                (instr->sample_high_start == INSTR_HI_NONE) ? "INSTR_SAMPLE_HI_NONE" : nhi, instr->release,
 | 
						|
                sf->info.index, instr->envelope_name);
 | 
						|
 | 
						|
        if (instr->sample_low != NULL)
 | 
						|
            fprintf(out, "    { &SF%d_%s_HEADER, " F32_FMT "f },\n", sf->info.index, instr->sample_name_low,
 | 
						|
                    instr->sample_low_tuning);
 | 
						|
        else
 | 
						|
            fprintf(out, "    INSTR_SAMPLE_NONE,\n");
 | 
						|
 | 
						|
        fprintf(out, "    { &SF%d_%s_HEADER, " F32_FMT "f },\n", sf->info.index, instr->sample_name_mid,
 | 
						|
                instr->sample_mid_tuning);
 | 
						|
 | 
						|
        if (instr->sample_high != NULL)
 | 
						|
            fprintf(out, "    { &SF%d_%s_HEADER, " F32_FMT "f },\n", sf->info.index, instr->sample_name_high,
 | 
						|
                    instr->sample_high_tuning);
 | 
						|
        else
 | 
						|
            fprintf(out, "    INSTR_SAMPLE_NONE,\n");
 | 
						|
 | 
						|
        fprintf(out, "};\n\n");
 | 
						|
 | 
						|
        size += 0x20;
 | 
						|
    }
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
size_t
 | 
						|
emit_c_drums(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    size_t size = 0;
 | 
						|
 | 
						|
    if (sf->drums == NULL)
 | 
						|
        return size;
 | 
						|
 | 
						|
    fprintf(out, "// DRUMS\n\n");
 | 
						|
 | 
						|
    // Prepare pointer table data to be filled in while writing the drum structures. Init to 0 so if any low notes are
 | 
						|
    // not covered by any drum group the name will be NULL.
 | 
						|
    struct {
 | 
						|
        const char *name;
 | 
						|
        int n;
 | 
						|
    } ptr_table[64];
 | 
						|
    memset(ptr_table, 0, sizeof(ptr_table));
 | 
						|
 | 
						|
    // While writing the drum structures we record the maximum note covered by this soundfont. Some "oddball" soundfonts
 | 
						|
    // like soundfont 0 do not have an array entry for all 64 notes. We use this to know when to stop writing entries in
 | 
						|
    // the pointer table.
 | 
						|
    int max_note = -1;
 | 
						|
 | 
						|
    LL_FOREACH(drum_data *, drum, sf->drums) {
 | 
						|
        if (drum->name == NULL) {
 | 
						|
            max_note++;
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        if (drum->note_end > max_note)
 | 
						|
            max_note = drum->note_end;
 | 
						|
 | 
						|
        size_t length = drum->note_end - drum->note_start + 1;
 | 
						|
 | 
						|
        // Drum structures are duplicated for each note in the range they cover, the basenote for each is incremented
 | 
						|
        // by one but the data is otherwise identical. We write a preprocessor definition to make the resulting source
 | 
						|
        // more compact for easier inspection.
 | 
						|
 | 
						|
        fprintf(out,
 | 
						|
                // clang-format off
 | 
						|
               "#define SF%d_%s_ENTRY(tuning) \\"               "\n"
 | 
						|
               "    { \\"                                       "\n"
 | 
						|
               "        %d, \\"                                 "\n"
 | 
						|
               "        %d, \\"                                 "\n"
 | 
						|
               "        false, \\"                              "\n"
 | 
						|
               "        { &SF%d_%s_HEADER, (tuning) }, \\"      "\n"
 | 
						|
               "        SF%d_%s, \\"                            "\n"
 | 
						|
               "    }"                                          "\n"
 | 
						|
               "NO_REORDER SECTION_DATA Drum SF%d_%s[%lu] = {"  "\n",
 | 
						|
                // clang-format on
 | 
						|
                sf->info.index, drum->name, drum->release, drum->pan, sf->info.index, drum->sample->name,
 | 
						|
                sf->info.index, drum->envelope->name, sf->info.index, drum->name, length);
 | 
						|
 | 
						|
        // Write each structure while building the drum pointer table
 | 
						|
 | 
						|
        if (drum->note_end + 1 > 64)
 | 
						|
            error("Bad drum range for drum spanning %d to %d, should be within 0 to 63", drum->note_start,
 | 
						|
                  drum->note_end);
 | 
						|
 | 
						|
        for (size_t note_offset = 0; note_offset < length; note_offset++) {
 | 
						|
            size_t ptr_offset = drum->note_start + note_offset;
 | 
						|
 | 
						|
            ptr_table[ptr_offset].name = drum->name;
 | 
						|
            ptr_table[ptr_offset].n = note_offset;
 | 
						|
 | 
						|
            // wrap note on overflow
 | 
						|
            int note = drum->base_note + note_offset;
 | 
						|
            if (note > 127)
 | 
						|
                note -= 128;
 | 
						|
 | 
						|
            float tuning = calc_tuning(drum->sample_rate, note);
 | 
						|
 | 
						|
            fprintf(out, "    SF%d_%s_ENTRY(" F32_FMT "f),\n", sf->info.index, drum->name, tuning);
 | 
						|
        }
 | 
						|
 | 
						|
        fprintf(out, "};\n\n");
 | 
						|
        size += 0x10 * length;
 | 
						|
    }
 | 
						|
 | 
						|
    // Write the drum pointer table. Always start at 0 and end at the maximum used note. If any low notes are not used,
 | 
						|
    // NULL is written into the array.
 | 
						|
 | 
						|
    size_t table_len = max_note + 1;
 | 
						|
    if (table_len > 64)
 | 
						|
        error("Bad drum pointer table length %lu, should be at most 64", table_len);
 | 
						|
 | 
						|
    fprintf(out, "NO_REORDER SECTION_DATA ALIGNED(16) Drum* SF%d_DRUMS_PTR_LIST[%lu] = {\n", sf->info.index, table_len);
 | 
						|
 | 
						|
    for (size_t i = 0; i < table_len; i++) {
 | 
						|
        if (ptr_table[i].name == NULL) {
 | 
						|
            fprintf(out, "    NULL,\n");
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        if (i != 0 && ptr_table[i].n == 0) // Add some space between different drum groups
 | 
						|
            fprintf(out, "\n");
 | 
						|
        fprintf(out, "    &SF%d_%s[%d],\n", sf->info.index, ptr_table[i].name, ptr_table[i].n);
 | 
						|
    }
 | 
						|
 | 
						|
    sf->info.num_drums = table_len;
 | 
						|
 | 
						|
    fprintf(out, "};\n");
 | 
						|
    emit_padding_stmt(out, table_len * 4);
 | 
						|
    fprintf(out, "\n");
 | 
						|
 | 
						|
    size += ALIGN16(table_len * 4);
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
size_t
 | 
						|
emit_c_effects(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    size_t size = 0;
 | 
						|
 | 
						|
    if (sf->sfx == NULL)
 | 
						|
        return size;
 | 
						|
 | 
						|
    fprintf(out, "// EFFECTS\n\n");
 | 
						|
 | 
						|
    // Effects are all contained in the same array. We write empty <Effect/> entries as NULL entries in this array.
 | 
						|
 | 
						|
    fprintf(out, "NO_REORDER SECTION_DATA ALIGNED(16) SoundEffect SF%d_SFX_LIST[] = {\n", sf->info.index);
 | 
						|
 | 
						|
    LL_FOREACH(sfx_data *, sfx, sf->sfx) {
 | 
						|
        if (sfx->sample != NULL)
 | 
						|
            fprintf(out, "    { { &SF%d_%s_HEADER, " F32_FMT "f } },\n", sf->info.index, sfx->sample->name,
 | 
						|
                    sfx->tuning);
 | 
						|
        else
 | 
						|
            fprintf(out, "    { { NULL, 0.0f } },\n");
 | 
						|
 | 
						|
        size += 8;
 | 
						|
    }
 | 
						|
 | 
						|
    fprintf(out, "};\n\n");
 | 
						|
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
emit_c_match_padding(FILE *out, soundfont *sf, size_t size)
 | 
						|
{
 | 
						|
    if (sf->match_padding != NULL && sf->match_padding_num != 0) {
 | 
						|
        // Sometimes a soundfont will have non-zero padding at the end, add these values manually
 | 
						|
        size_t expected = sf->match_padding_num;
 | 
						|
 | 
						|
        // Don't pad any further than the next 0x10 byte boundary
 | 
						|
        size_t remaining = ALIGN16(size) - size;
 | 
						|
        size_t amount = (expected > remaining) ? remaining : expected;
 | 
						|
 | 
						|
        fprintf(out, "// MATCH PADDING\n\n");
 | 
						|
 | 
						|
        fprintf(out, "NO_REORDER SECTION_DATA u8 SF%d_MATCH_PADDING[] = {\n", sf->info.index);
 | 
						|
        for (size_t i = 0; i < amount; i++)
 | 
						|
            fprintf(out, "    0x%02X,\n", sf->match_padding[i]);
 | 
						|
        fprintf(out, "};\n\n");
 | 
						|
 | 
						|
        size += amount;
 | 
						|
    }
 | 
						|
 | 
						|
    if (sf->info.pad_to_size != 0) {
 | 
						|
        if (sf->info.pad_to_size <= size) {
 | 
						|
            warning("PadToSize directive ignored.");
 | 
						|
        } else {
 | 
						|
            fprintf(out, "// MATCH SIZE PADDING\n\n");
 | 
						|
 | 
						|
            // pad to given size
 | 
						|
            size_t amount = sf->info.pad_to_size - size;
 | 
						|
            fprintf(out, "NO_REORDER SECTION_DATA u8 SF%d_MATCH_PADDING_TO_SIZE[%lu] = { 0 };\n", sf->info.index,
 | 
						|
                    amount);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
emit_h_instruments(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    if (sf->instruments == NULL)
 | 
						|
        return;
 | 
						|
 | 
						|
    // Example output:
 | 
						|
    // #define FONT{Index}_INSTR_{EnumName} {EnumValue}
 | 
						|
 | 
						|
    LL_FOREACH(instr_data *, instr, sf->instruments) {
 | 
						|
        if (!instr->unused) {
 | 
						|
            fprintf(out, "#define SF%d_%s %d\n", sf->info.index, instr->name, instr->program_number);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    fprintf(out, "\n");
 | 
						|
}
 | 
						|
 | 
						|
static const char *
 | 
						|
z64_note_name(int note_num)
 | 
						|
{
 | 
						|
    static const char *const note_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",
 | 
						|
    };
 | 
						|
    return note_names[note_num];
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
emit_h_drums(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    if (sf->drums == NULL)
 | 
						|
        return;
 | 
						|
 | 
						|
    // Emit drum defines in groups, named like [DrumName]_[NoteName]
 | 
						|
    // e.g. a drum called "MY_DRUM" with a sample basenote of C4 covering a note range of 0..3 looks like
 | 
						|
    // #define MY_DRUM_C4  0
 | 
						|
    // #define MY_DRUM_DF4 1
 | 
						|
    // #define MY_DRUM_D4  2
 | 
						|
    // #define MY_DRUM_EF4 3
 | 
						|
 | 
						|
    LL_FOREACH(drum_data *, drum, sf->drums) {
 | 
						|
        if (drum->name == NULL)
 | 
						|
            continue;
 | 
						|
 | 
						|
        int length = drum->note_end - drum->note_start + 1;
 | 
						|
 | 
						|
        for (int note_offset = 0; note_offset < length; note_offset++) {
 | 
						|
            // wrap note on overflow
 | 
						|
            int note = drum->base_note + note_offset;
 | 
						|
            if (note > 127)
 | 
						|
                note -= 128;
 | 
						|
 | 
						|
            fprintf(out, "#define SF%d_%s_%s %d\n", sf->info.index, drum->name, z64_note_name(note),
 | 
						|
                    drum->note_start + note_offset);
 | 
						|
        }
 | 
						|
 | 
						|
        fprintf(out, "\n");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
emit_h_effects(FILE *out, soundfont *sf)
 | 
						|
{
 | 
						|
    if (sf->sfx == NULL)
 | 
						|
        return;
 | 
						|
 | 
						|
    int i = 0;
 | 
						|
    LL_FOREACH(sfx_data *, sfx, sf->sfx) {
 | 
						|
        if (sfx->sample != NULL)
 | 
						|
            fprintf(out, "#define SF%d_%s %d\n", sf->info.index, sfx->name, i);
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
    fprintf(out, "\n");
 | 
						|
}
 | 
						|
 | 
						|
NORETURN static void
 | 
						|
usage(const char *progname)
 | 
						|
{
 | 
						|
    fprintf(stderr, "Usage: %s [--matching] <filename.xml> <out.c> <out.h> <out.name>\n", progname);
 | 
						|
    exit(EXIT_FAILURE);
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
main(int argc, char **argv)
 | 
						|
{
 | 
						|
    char *filename_in = NULL;
 | 
						|
    char *filename_out_c = NULL;
 | 
						|
    char *filename_out_h = NULL;
 | 
						|
    char *filename_out_name = NULL;
 | 
						|
    const char *mdfilename = NULL;
 | 
						|
    FILE *mdfile;
 | 
						|
    xmlDocPtr document;
 | 
						|
    soundfont sf;
 | 
						|
 | 
						|
    sf.matching = false;
 | 
						|
 | 
						|
    // parse args
 | 
						|
 | 
						|
#define arg_error(fmt, ...)                       \
 | 
						|
    do {                                          \
 | 
						|
        fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
 | 
						|
        usage(argv[0]);                           \
 | 
						|
    } while (0)
 | 
						|
 | 
						|
    int argn = 0;
 | 
						|
    for (int i = 1; i < argc; i++) {
 | 
						|
        if (argv[i][0] == '-') {
 | 
						|
            // Optional args
 | 
						|
 | 
						|
            if (strequ(argv[i], "--matching")) {
 | 
						|
                if (sf.matching)
 | 
						|
                    arg_error("Received --matching option twice");
 | 
						|
 | 
						|
                sf.matching = true;
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (strequ(argv[i], "--makedepend")) {
 | 
						|
                if (mdfilename != NULL)
 | 
						|
                    arg_error("Received --makedepend option twice");
 | 
						|
                if (i + 1 == argc)
 | 
						|
                    arg_error("--makedepend missing required argument");
 | 
						|
 | 
						|
                mdfilename = argv[++i];
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            arg_error("Unknown option \"%s\"", argv[i]);
 | 
						|
        } else {
 | 
						|
            // Required args
 | 
						|
 | 
						|
            switch (argn) {
 | 
						|
                case 0:
 | 
						|
                    filename_in = argv[i];
 | 
						|
                    break;
 | 
						|
                case 1:
 | 
						|
                    filename_out_c = argv[i];
 | 
						|
                    break;
 | 
						|
                case 2:
 | 
						|
                    filename_out_h = argv[i];
 | 
						|
                    break;
 | 
						|
                case 3:
 | 
						|
                    filename_out_name = argv[i];
 | 
						|
                    break;
 | 
						|
                default:
 | 
						|
                    arg_error("Unknown positional argument \"%s\"", argv[i]);
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
            argn++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (argn != 4)
 | 
						|
        arg_error("Not enough positional arguments");
 | 
						|
 | 
						|
#undef arg_error
 | 
						|
 | 
						|
    document = xmlReadFile(filename_in, NULL, XML_PARSE_NONET);
 | 
						|
    if (document == NULL)
 | 
						|
        return EXIT_FAILURE;
 | 
						|
 | 
						|
    xmlNodePtr root = xmlDocGetRootElement(document);
 | 
						|
    if (!strequ(XMLSTR_TO_STR(root->name), "Soundfont"))
 | 
						|
        error("Root node must be <Soundfont>");
 | 
						|
    read_soundfont_info(&sf, root);
 | 
						|
 | 
						|
    sf.envelopes = sf.envelope_last = NULL;
 | 
						|
 | 
						|
    // read all envelopes first irrespective of their positioning in the xml
 | 
						|
    LL_FOREACH(xmlNodePtr, node, root->children) {
 | 
						|
        const char *name = XMLSTR_TO_STR(node->name);
 | 
						|
 | 
						|
        if (strequ(name, "Envelopes"))
 | 
						|
            read_envelopes_info(&sf, node);
 | 
						|
    }
 | 
						|
 | 
						|
    // read all samples
 | 
						|
    sf.samples = NULL;
 | 
						|
    LL_FOREACH(xmlNodePtr, node, root->children) {
 | 
						|
        const char *name = XMLSTR_TO_STR(node->name);
 | 
						|
 | 
						|
        if (strequ(name, "Samples"))
 | 
						|
            read_samples_info(&sf, node);
 | 
						|
    }
 | 
						|
 | 
						|
    // read all instruments
 | 
						|
    memset(sf.program_number_bitset, 0, sizeof(sf.program_number_bitset));
 | 
						|
    sf.instruments = NULL;
 | 
						|
    sf.drums = NULL;
 | 
						|
    sf.sfx = NULL;
 | 
						|
    LL_FOREACH(xmlNodePtr, node, root->children) {
 | 
						|
        const char *name = XMLSTR_TO_STR(node->name);
 | 
						|
 | 
						|
        if (strequ(name, "Instruments"))
 | 
						|
            read_instrs_info(&sf, node);
 | 
						|
        if (strequ(name, "Drums"))
 | 
						|
            read_drums_info(&sf, node);
 | 
						|
        if (strequ(name, "Effects"))
 | 
						|
            read_sfx_info(&sf, node);
 | 
						|
    }
 | 
						|
 | 
						|
    // read match padding if it exists
 | 
						|
    sf.match_padding = NULL;
 | 
						|
    LL_FOREACH(xmlNodePtr, node, root->children) {
 | 
						|
        const char *name = XMLSTR_TO_STR(node->name);
 | 
						|
 | 
						|
        if (strequ(name, "MatchPadding"))
 | 
						|
            read_match_padding(&sf, node);
 | 
						|
    }
 | 
						|
 | 
						|
    // emit C source
 | 
						|
 | 
						|
    FILE *out_c = fopen(filename_out_c, "w");
 | 
						|
    fprintf(out_c, "#include \"soundfont_file.h\"\n\n");
 | 
						|
 | 
						|
    size_t size = 0;
 | 
						|
    size += emit_c_header(out_c, &sf);
 | 
						|
    size += emit_c_samples(out_c, &sf);
 | 
						|
    size += emit_c_envelopes(out_c, &sf);
 | 
						|
    size += emit_c_instruments(out_c, &sf);
 | 
						|
    size += emit_c_drums(out_c, &sf);
 | 
						|
    size += emit_c_effects(out_c, &sf);
 | 
						|
    emit_c_match_padding(out_c, &sf, size);
 | 
						|
 | 
						|
    fclose(out_c);
 | 
						|
 | 
						|
    // emit C header
 | 
						|
 | 
						|
    FILE *out_h = fopen(filename_out_h, "w");
 | 
						|
    fprintf(out_h,
 | 
						|
            // clang-format off
 | 
						|
           "#ifndef SOUNDFONT_%d_H_"            "\n"
 | 
						|
           "#define SOUNDFONT_%d_H_"            "\n"
 | 
						|
                                                "\n",
 | 
						|
            // clang-format on
 | 
						|
            sf.info.index, sf.info.index);
 | 
						|
 | 
						|
    fprintf(out_h,
 | 
						|
            // clang-format off
 | 
						|
           "#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);
 | 
						|
 | 
						|
    fprintf(out_h,
 | 
						|
            // clang-format off
 | 
						|
           "#define %s_ID %d"                   "\n"
 | 
						|
                                                "\n"
 | 
						|
           "#define SF%d_NUM_INSTRUMENTS %d"    "\n"
 | 
						|
           "#define SF%d_NUM_DRUMS       %d"    "\n"
 | 
						|
           "#define SF%d_NUM_SFX         %d"    "\n"
 | 
						|
                                                "\n",
 | 
						|
            // clang-format on
 | 
						|
            sf.info.name, sf.info.index, sf.info.index, sf.info.num_instruments, sf.info.index, sf.info.num_drums,
 | 
						|
            sf.info.index, sf.info.num_effects);
 | 
						|
 | 
						|
    emit_h_instruments(out_h, &sf);
 | 
						|
    emit_h_drums(out_h, &sf);
 | 
						|
    emit_h_effects(out_h, &sf);
 | 
						|
 | 
						|
    fprintf(out_h, "#endif\n");
 | 
						|
    fclose(out_h);
 | 
						|
 | 
						|
    // emit name marker
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
    if (mdfilename != NULL) {
 | 
						|
        mdfile = fopen(mdfilename, "w");
 | 
						|
        if (mdfile == NULL)
 | 
						|
            error("Unable to open dependency file [%s] for writing", mdfilename);
 | 
						|
 | 
						|
        // Begin rule + depend on the soundfont xml input
 | 
						|
        fprintf(mdfile, "%s %s %s: \\\n    %s", filename_out_c, filename_out_h, filename_out_name, filename_in);
 | 
						|
 | 
						|
        // Depend on the referenced samplebank xmls
 | 
						|
        if (sf.info.bank_path != NULL)
 | 
						|
            fprintf(mdfile, " \\\n    %s", sf.info.bank_path);
 | 
						|
        if (sf.info.bank_path_dd != NULL)
 | 
						|
            fprintf(mdfile, " \\\n    %s", sf.info.bank_path_dd);
 | 
						|
 | 
						|
        // Depend on the aifc files used by this soundfont
 | 
						|
        LL_FOREACH(sample_data *, sample, sf.samples) {
 | 
						|
            fprintf(mdfile, " \\\n    %s", sample->aifc.path);
 | 
						|
        }
 | 
						|
 | 
						|
        fputs("\n", mdfile);
 | 
						|
        fclose(mdfile);
 | 
						|
    }
 | 
						|
 | 
						|
    // done
 | 
						|
 | 
						|
    xmlFreeDoc(document);
 | 
						|
    return EXIT_SUCCESS;
 | 
						|
}
 |