diff options
author | Robin Gareus <robin@gareus.org> | 2019-01-02 15:33:23 +0100 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2019-01-02 16:48:03 +0100 |
commit | ac9329f907bf011395968764c82d7c7c13675b7c (patch) | |
tree | cd91d325e839b30e743d2a7aff61153e08a443f2 /libs/fluidsynth | |
parent | 754591e2ee23a322a1ad3cb66ca65ce7e21d3d33 (diff) |
Update Fluidsynth to v2.0.3
see https://github.com/FluidSynth/fluidsynth/releases/tag/v2.0.3
Diffstat (limited to 'libs/fluidsynth')
-rw-r--r-- | libs/fluidsynth/README | 9 | ||||
-rw-r--r-- | libs/fluidsynth/fluidsynth/midi.h | 4 | ||||
-rw-r--r-- | libs/fluidsynth/fluidsynth/sfont.h | 6 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_defsfont.c | 945 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_midi.c | 93 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_mod.c | 199 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_mod.h | 1 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_rev.c | 1566 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_rvoice.c | 58 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_samplecache.c | 36 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_sffile.c | 275 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_sfont.c | 15 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_sfont.h | 2 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_synth.c | 40 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_sys.c | 71 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_sys.h | 8 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_voice.c | 212 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluid_voice.h | 1 | ||||
-rw-r--r-- | libs/fluidsynth/src/fluidsynth_priv.h | 4 | ||||
-rw-r--r-- | libs/fluidsynth/wscript | 2 |
20 files changed, 2340 insertions, 1207 deletions
diff --git a/libs/fluidsynth/README b/libs/fluidsynth/README index 451f88e55f..3fcf3159f0 100644 --- a/libs/fluidsynth/README +++ b/libs/fluidsynth/README @@ -1,7 +1,14 @@ This is a stripped down version of fluidsynth (library only) from git://github.com/FluidSynth/fluidsynth.git -rev. v2.0.2 (6e9d84f02a7a0f7e436c2adffc4a065608f490ba) +rev. v2.0.3-16-g8b18205 fluidsynth is licensed in terms of the LGPL-2+, see individual source files for (C) holders. + +--- + +The source is imported in the repository in order to allow static linking, +with hidden visibility. This is required for a-fluidsynth.lv2 (plugin must +be self-contained and not rely on external shared libs) and to make it +available to to libardour on all platforms that Ardour is. diff --git a/libs/fluidsynth/fluidsynth/midi.h b/libs/fluidsynth/fluidsynth/midi.h index cd30e2088b..9ddeef054a 100644 --- a/libs/fluidsynth/fluidsynth/midi.h +++ b/libs/fluidsynth/fluidsynth/midi.h @@ -53,8 +53,12 @@ FLUIDSYNTH_API int fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *dat int size, int dynamic); FLUIDSYNTH_API int fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dynamic); +FLUIDSYNTH_API int fluid_midi_event_get_text(fluid_midi_event_t *evt, + void **data, int *size); FLUIDSYNTH_API int fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int dynamic); +FLUIDSYNTH_API int fluid_midi_event_get_lyrics(fluid_midi_event_t *evt, + void **data, int *size); /** * MIDI router rule type. diff --git a/libs/fluidsynth/fluidsynth/sfont.h b/libs/fluidsynth/fluidsynth/sfont.h index 55413a9e24..0df4d6d4a8 100644 --- a/libs/fluidsynth/fluidsynth/sfont.h +++ b/libs/fluidsynth/fluidsynth/sfont.h @@ -184,12 +184,10 @@ typedef void (*fluid_sfont_iteration_start_t)(fluid_sfont_t *sfont); /** * Virtual SoundFont preset iteration function. * @param sfont Virtual SoundFont - * @param preset Caller supplied uninitialized buffer to fill in with current preset information * @return NULL when no more presets are available, otherwise the a pointer to the current preset * - * Should store preset information to the caller supplied \a preset structure - * and advance the internal iteration state to the next preset for subsequent - * calls. + * Returns preset information to the caller. The returned buffer is only valid until a subsequent + * call to this function. */ typedef fluid_preset_t *(*fluid_sfont_iteration_next_t)(fluid_sfont_t *sfont); diff --git a/libs/fluidsynth/src/fluid_defsfont.c b/libs/fluidsynth/src/fluid_defsfont.c index 6e19eb71af..d7960cbb9c 100644 --- a/libs/fluidsynth/src/fluid_defsfont.c +++ b/libs/fluidsynth/src/fluid_defsfont.c @@ -682,6 +682,140 @@ fluid_defpreset_next(fluid_defpreset_t *defpreset) return defpreset->next; } +/* + * Adds global and local modulators list to the voice. This is done in 2 steps: + * - Step 1: Local modulators replace identic global modulators. + * - Step 2: global + local modulators are added to the voice using mode. + * + * Instrument zone list (local/global) must be added using FLUID_VOICE_OVERWRITE. + * Preset zone list (local/global) must be added using FLUID_VOICE_ADD. + * + * @param voice voice instance. + * @param global_mod global list of modulators. + * @param local_mod local list of modulators. + * @param mode Determines how to handle an existing identical modulator. + * #FLUID_VOICE_ADD to add (offset) the modulator amounts, + * #FLUID_VOICE_OVERWRITE to replace the modulator, +*/ +static void +fluid_defpreset_noteon_add_mod_to_voice(fluid_voice_t *voice, + fluid_mod_t *global_mod, fluid_mod_t *local_mod, + int mode) +{ + fluid_mod_t *mod; + /* list for 'sorting' global/local modulators */ + fluid_mod_t *mod_list[FLUID_NUM_MOD]; + int mod_list_count, i; + + /* identity_limit_count is the modulator upper limit number to handle with + * existing identical modulators. + * When identity_limit_count is below the actual number of modulators, this + * will restrict identity check to this upper limit, + * This is useful when we know by advance that there is no duplicate with + * modulators at index above this limit. This avoid wasting cpu cycles at + * noteon. + */ + int identity_limit_count; + + /* Step 1: Local modulators replace identic global modulators. */ + + /* local (instrument zone/preset zone), modulators: Put them all into a list. */ + mod_list_count = 0; + + while(local_mod) + { + /* As modulators number in local_mod list was limited to FLUID_NUM_MOD at + soundfont loading time (fluid_limit_mod_list()), here we don't need + to check if mod_list is full. + */ + mod_list[mod_list_count++] = local_mod; + local_mod = local_mod->next; + } + + /* global (instrument zone/preset zone), modulators. + * Replace modulators with the same definition in the global list: + * (Instrument zone: SF 2.01 page 69, 'bullet' 8) + * (Preset zone: SF 2.01 page 69, second-last bullet). + * + * mod_list contains local modulators. Now we know that there + * is no global modulator identic to another global modulator (this has + * been checked at soundfont loading time). So global modulators + * are only checked against local modulators number. + */ + + /* Restrict identity check to the number of local modulators */ + identity_limit_count = mod_list_count; + + while(global_mod) + { + /* 'Identical' global modulators are ignored. + * SF2.01 section 9.5.1 + * page 69, 'bullet' 3 defines 'identical'. */ + + for(i = 0; i < identity_limit_count; i++) + { + if(fluid_mod_test_identity(global_mod, mod_list[i])) + { + break; + } + } + + /* Finally add the new modulator to the list. */ + if(i >= identity_limit_count) + { + /* Although local_mod and global_mod lists was limited to + FLUID_NUM_MOD at soundfont loading time, it is possible that + local + global modulators exceeds FLUID_NUM_MOD. + So, checks if mod_list_count reachs the limit. + */ + if(mod_list_count >= FLUID_NUM_MOD) + { + /* mod_list is full, we silently forget this modulator and + next global modulators. When mod_list will be added to the + voice, a warning will be displayed if the voice list is full. + (see fluid_voice_add_mod_local()). + */ + break; + } + + mod_list[mod_list_count++] = global_mod; + } + + global_mod = global_mod->next; + } + + /* Step 2: global + local modulators are added to the voice using mode. */ + + /* + * mod_list contains local and global modulators, we know that: + * - there is no global modulator identic to another global modulator, + * - there is no local modulator identic to another local modulator, + * So these local/global modulators are only checked against + * actual number of voice modulators. + */ + + /* Restrict identity check to the actual number of voice modulators */ + /* Acual number of voice modulators : defaults + [instruments] */ + identity_limit_count = voice->mod_count; + + for(i = 0; i < mod_list_count; i++) + { + + mod = mod_list[i]; + /* in mode FLUID_VOICE_OVERWRITE disabled instruments modulators CANNOT be skipped. */ + /* in mode FLUID_VOICE_ADD disabled preset modulators can be skipped. */ + + if((mode == FLUID_VOICE_OVERWRITE) || (mod->amount != 0)) + { + /* Instrument modulators -supersede- existing (default) modulators. + SF 2.01 page 69, 'bullet' 6 */ + + /* Preset modulators -add- to existing instrument modulators. + SF2.01 page 70 first bullet on page */ + fluid_voice_add_mod_local(voice, mod, mode, identity_limit_count); + } + } +} /* * fluid_defpreset_noteon @@ -695,9 +829,6 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c fluid_voice_zone_t *voice_zone; fluid_list_t *list; fluid_voice_t *voice; - fluid_mod_t *mod; - fluid_mod_t *mod_list[FLUID_NUM_MOD]; /* list for 'sorting' preset modulators */ - int mod_list_count; int i; global_preset_zone = fluid_defpreset_get_global_zone(defpreset); @@ -769,63 +900,12 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c } /* for all generators */ - /* global instrument zone, modulators: Put them all into a - * list. */ - - mod_list_count = 0; - - if(global_inst_zone) - { - mod = global_inst_zone->mod; - - while(mod) - { - mod_list[mod_list_count++] = mod; - mod = mod->next; - } - } - - /* local instrument zone, modulators. - * Replace modulators with the same definition in the list: - * SF 2.01 page 69, 'bullet' 8 - */ - mod = inst_zone->mod; - - while(mod) - { - - /* 'Identical' modulators will be deleted by setting their - * list entry to NULL. The list length is known, NULL - * entries will be ignored later. SF2.01 section 9.5.1 - * page 69, 'bullet' 3 defines 'identical'. */ - - for(i = 0; i < mod_list_count; i++) - { - if(mod_list[i] && fluid_mod_test_identity(mod, mod_list[i])) - { - mod_list[i] = NULL; - } - } - - /* Finally add the new modulator to to the list. */ - mod_list[mod_list_count++] = mod; - mod = mod->next; - } - - /* Add instrument modulators (global / local) to the voice. */ - for(i = 0; i < mod_list_count; i++) - { - - mod = mod_list[i]; - - if(mod != NULL) /* disabled modulators CANNOT be skipped. */ - { - - /* Instrument modulators -supersede- existing (default) - * modulators. SF 2.01 page 69, 'bullet' 6 */ - fluid_voice_add_mod(voice, mod, FLUID_VOICE_OVERWRITE); - } - } + /* Adds instrument zone modulators (global and local) to the voice.*/ + fluid_defpreset_noteon_add_mod_to_voice(voice, + /* global instrument modulators */ + global_inst_zone ? global_inst_zone->mod : NULL, + inst_zone->mod, /* local instrument modulators */ + FLUID_VOICE_OVERWRITE); /* mode */ /* Preset level, generators */ @@ -868,57 +948,12 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c } } /* for all generators */ - - /* Global preset zone, modulators: put them all into a - * list. */ - mod_list_count = 0; - - if(global_preset_zone) - { - mod = global_preset_zone->mod; - - while(mod) - { - mod_list[mod_list_count++] = mod; - mod = mod->next; - } - } - - /* Process the modulators of the local preset zone. Kick - * out all identical modulators from the global preset zone - * (SF 2.01 page 69, second-last bullet) */ - - mod = preset_zone->mod; - - while(mod) - { - for(i = 0; i < mod_list_count; i++) - { - if(mod_list[i] && fluid_mod_test_identity(mod, mod_list[i])) - { - mod_list[i] = NULL; - } - } - - /* Finally add the new modulator to the list. */ - mod_list[mod_list_count++] = mod; - mod = mod->next; - } - - /* Add preset modulators (global / local) to the voice. */ - for(i = 0; i < mod_list_count; i++) - { - mod = mod_list[i]; - - if((mod != NULL) && (mod->amount != 0)) /* disabled modulators can be skipped. */ - { - - /* Preset modulators -add- to existing instrument / - * default modulators. SF2.01 page 70 first bullet on - * page */ - fluid_voice_add_mod(voice, mod, FLUID_VOICE_ADD); - } - } + /* Adds preset zone modulators (global and local) to the voice.*/ + fluid_defpreset_noteon_add_mod_to_voice(voice, + /* global preset modulators */ + global_preset_zone ? global_preset_zone->mod : NULL, + preset_zone->mod, /* local preset modulators */ + FLUID_VOICE_ADD); /* mode */ /* add the synthesis process to the synthesis loop. */ fluid_synth_start_voice(synth, voice); @@ -930,7 +965,6 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c * class - for example when using stereo samples) */ } - } } @@ -1105,17 +1139,11 @@ new_fluid_preset_zone(char *name) } /* - * delete_fluid_preset_zone + * delete list of modulators. */ -void -delete_fluid_preset_zone(fluid_preset_zone_t *zone) +static void delete_fluid_list_mod(fluid_mod_t *mod) { - fluid_mod_t *mod, *tmp; - fluid_list_t *list; - - fluid_return_if_fail(zone != NULL); - - mod = zone->mod; + fluid_mod_t *tmp; while(mod) /* delete the modulators */ { @@ -1123,6 +1151,19 @@ delete_fluid_preset_zone(fluid_preset_zone_t *zone) mod = mod->next; delete_fluid_mod(tmp); } +} + +/* + * delete_fluid_preset_zone + */ +void +delete_fluid_preset_zone(fluid_preset_zone_t *zone) +{ + fluid_list_t *list; + + fluid_return_if_fail(zone != NULL); + + delete_fluid_list_mod(zone->mod); for(list = zone->voice_zone; list != NULL; list = fluid_list_next(list)) { @@ -1186,71 +1227,269 @@ static int fluid_preset_zone_create_voice_zones(fluid_preset_zone_t *preset_zone return FLUID_OK; } +/** + * Checks if modulator mod is identic to another modulator in the list + * (specs SF 2.0X 7.4, 7.8). + * @param mod, modulator list. + * @param name, if not NULL, pointer on a string displayed as warning. + * @return TRUE if mod is identic to another modulator, FALSE otherwise. + */ +static int +fluid_zone_is_mod_identic(fluid_mod_t *mod, char *name) +{ + fluid_mod_t *next = mod->next; + + while(next) + { + /* is mod identic to next ? */ + if(fluid_mod_test_identity(mod, next)) + { + if(name) + { + FLUID_LOG(FLUID_WARN, "Ignoring identic modulator %s", name); + } + + return TRUE; + } + + next = next->next; + } + + return FALSE; +} + +/** + * Limits the number of modulators in a modulator list. + * This is appropriate to internal synthesizer modulators tables + * which have a fixed size (FLUID_NUM_MOD). + * + * @param zone_name, zone name + * @param list_mod, address of pointer on modulator list. + */ +static void fluid_limit_mod_list(char *zone_name, fluid_mod_t **list_mod) +{ + int mod_idx = 0; /* modulator index */ + fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ + fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ + + while(mod) + { + if((mod_idx + 1) > FLUID_NUM_MOD) + { + /* truncation of list_mod */ + if(mod_idx) + { + prev_mod->next = NULL; + } + else + { + *list_mod = NULL; + } + + delete_fluid_list_mod(mod); + FLUID_LOG(FLUID_WARN, "%s, modulators count limited to %d", zone_name, + FLUID_NUM_MOD); + break; + } + + mod_idx++; + prev_mod = mod; + mod = mod->next; + } +} + +/** + * Checks and remove invalid modulators from a zone modulators list. + * - checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1). + * - checks identic modulators in the list (specs SF 2.01 7.4, 7.8). + * @param zone_name, zone name. + * @param list_mod, address of pointer on modulators list. + */ +static void +fluid_zone_check_mod(char *zone_name, fluid_mod_t **list_mod) +{ + fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ + fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ + int mod_idx = 0; /* modulator index */ + + while(mod) + { + char zone_mod_name[256]; + fluid_mod_t *next = mod->next; + + /* prepare modulator name: zonename/#modulator */ + FLUID_SNPRINTF(zone_mod_name, sizeof(zone_mod_name), "%s/mod%d", zone_name, mod_idx); + + /* has mod invalid sources ? */ + if(!fluid_mod_check_sources(mod, zone_mod_name) + /* or is mod identic to any following modulator ? */ + || fluid_zone_is_mod_identic(mod, zone_mod_name)) + { + /* the modulator is useless so we remove it */ + if(prev_mod) + { + prev_mod->next = next; + } + else + { + *list_mod = next; + } + + delete_fluid_mod(mod); /* freeing */ + } + else + { + prev_mod = mod; + } + + mod = next; + mod_idx++; + } + + /* limits the size of modulators list */ + fluid_limit_mod_list(zone_name, list_mod); +} + /* - * fluid_preset_zone_import_sfont + * fluid_zone_gen_import_sfont + * Imports generators from sfzone to gen and range. + * @param gen, pointer on destination generators table. + * @param range, pointer on destination range generators. + * @param sfzone, pointer on soundfont zone generators. */ -int -fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_defsfont_t *defsfont) +static void +fluid_zone_gen_import_sfont(fluid_gen_t *gen, fluid_zone_range_t *range, SFZone *sfzone) { fluid_list_t *r; SFGen *sfgen; - SFInst *sfinst; - int count; - for(count = 0, r = sfzone->gen; r != NULL; count++) + for(r = sfzone->gen; r != NULL;) { sfgen = (SFGen *)fluid_list_get(r); switch(sfgen->id) { case GEN_KEYRANGE: - zone->range.keylo = sfgen->amount.range.lo; - zone->range.keyhi = sfgen->amount.range.hi; + range->keylo = sfgen->amount.range.lo; + range->keyhi = sfgen->amount.range.hi; break; case GEN_VELRANGE: - zone->range.vello = sfgen->amount.range.lo; - zone->range.velhi = sfgen->amount.range.hi; + range->vello = sfgen->amount.range.lo; + range->velhi = sfgen->amount.range.hi; break; case GEN_ATTENUATION: /* EMU8k/10k hardware applies a scale factor to initial attenuation generator values set at * preset and instrument level */ - zone->gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword * EMU_ATTENUATION_FACTOR; - zone->gen[sfgen->id].flags = GEN_SET; + gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword * EMU_ATTENUATION_FACTOR; + gen[sfgen->id].flags = GEN_SET; break; default: /* FIXME: some generators have an unsigne word amount value but i don't know which ones */ - zone->gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; - zone->gen[sfgen->id].flags = GEN_SET; + gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; + gen[sfgen->id].flags = GEN_SET; break; } r = fluid_list_next(r); } +} - if((sfzone->instsamp != NULL) && (sfzone->instsamp->data != NULL)) +/* + * fluid_zone_mod_source_import_sfont + * Imports source information from sf_source to src and flags. + * @param src, pointer on destination modulator source. + * @param flags, pointer on destination modulator flags. + * @param sf_source, soundfont modulator source. + * @return return TRUE if success, FALSE if source type is unknow. + */ +static int +fluid_zone_mod_source_import_sfont(unsigned char *src, unsigned char *flags, unsigned short sf_source) +{ + int type; + unsigned char flags_dest; /* destination flags */ + + /* sources */ + *src = sf_source & 127; /* index of source, seven-bit value, SF2.01 section 8.2, page 50 */ + + /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ + flags_dest = 0; + + if(sf_source & (1 << 7)) { - sfinst = sfzone->instsamp->data; + flags_dest |= FLUID_MOD_CC; + } + else + { + flags_dest |= FLUID_MOD_GC; + } - zone->inst = find_inst_by_idx(defsfont, sfinst->idx); + /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ + if(sf_source & (1 << 8)) + { + flags_dest |= FLUID_MOD_NEGATIVE; + } + else + { + flags_dest |= FLUID_MOD_POSITIVE; + } - if(zone->inst == NULL) - { - zone->inst = fluid_inst_import_sfont(zone, sfinst, defsfont); - } + /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ + if(sf_source & (1 << 9)) + { + flags_dest |= FLUID_MOD_BIPOLAR; + } + else + { + flags_dest |= FLUID_MOD_UNIPOLAR; + } - if(zone->inst == NULL) - { - return FLUID_FAILED; - } + /* modulator source types: SF2.01 section 8.2.1 page 52 */ + type = sf_source >> 10; + type &= 63; /* type is a 6-bit value */ - if(fluid_preset_zone_create_voice_zones(zone) == FLUID_FAILED) - { - return FLUID_FAILED; - } + if(type == 0) + { + flags_dest |= FLUID_MOD_LINEAR; } + else if(type == 1) + { + flags_dest |= FLUID_MOD_CONCAVE; + } + else if(type == 2) + { + flags_dest |= FLUID_MOD_CONVEX; + } + else if(type == 3) + { + flags_dest |= FLUID_MOD_SWITCH; + } + else + { + *flags = flags_dest; + /* This shouldn't happen - unknown type! */ + return FALSE; + } + + *flags = flags_dest; + return TRUE; +} + +/* + * fluid_zone_mod_import_sfont + * Imports modulators from sfzone to modulators list mod. + * @param zone_name, zone name. + * @param mod, address of pointer on modulators list to return. + * @param sfzone, pointer on soundfont zone. + * @return FLUID_OK if success, FLUID_FAILED otherwise. + */ +static int +fluid_zone_mod_import_sfont(char *zone_name, fluid_mod_t **mod, SFZone *sfzone) +{ + fluid_list_t *r; + int count; /* Import the modulators (only SF2.1 and higher) */ for(count = 0, r = sfzone->mod; r != NULL; count++) @@ -1258,7 +1497,6 @@ fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_ SFMod *mod_src = (SFMod *)fluid_list_get(r); fluid_mod_t *mod_dest = new_fluid_mod(); - int type; if(mod_dest == NULL) { @@ -1271,128 +1509,45 @@ fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_ mod_dest->amount = mod_src->amount; /* *** Source *** */ - mod_dest->src1 = mod_src->src & 127; /* index of source 1, seven-bit value, SF2.01 section 8.2, page 50 */ - mod_dest->flags1 = 0; - - /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ - if(mod_src->src & (1 << 7)) - { - mod_dest->flags1 |= FLUID_MOD_CC; - } - else - { - mod_dest->flags1 |= FLUID_MOD_GC; - } - - /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ - if(mod_src->src & (1 << 8)) - { - mod_dest->flags1 |= FLUID_MOD_NEGATIVE; - } - else - { - mod_dest->flags1 |= FLUID_MOD_POSITIVE; - } - - /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ - if(mod_src->src & (1 << 9)) - { - mod_dest->flags1 |= FLUID_MOD_BIPOLAR; - } - else - { - mod_dest->flags1 |= FLUID_MOD_UNIPOLAR; - } - - /* modulator source types: SF2.01 section 8.2.1 page 52 */ - type = (mod_src->src) >> 10; - type &= 63; /* type is a 6-bit value */ - - if(type == 0) - { - mod_dest->flags1 |= FLUID_MOD_LINEAR; - } - else if(type == 1) - { - mod_dest->flags1 |= FLUID_MOD_CONCAVE; - } - else if(type == 2) - { - mod_dest->flags1 |= FLUID_MOD_CONVEX; - } - else if(type == 3) - { - mod_dest->flags1 |= FLUID_MOD_SWITCH; - } - else + if(!fluid_zone_mod_source_import_sfont(&mod_dest->src1, &mod_dest->flags1, mod_src->src)) { /* This shouldn't happen - unknown type! * Deactivate the modulator by setting the amount to 0. */ mod_dest->amount = 0; + } + + /* Note: When primary source input (src1) is set to General Controller 'No Controller', + output will be forced to 0.0 at synthesis time (see fluid_mod_get_value()). + That means that the minimum value of the modulator will be always 0.0. + We need to force amount value to 0 to ensure a correct evaluation of the minimum + value later (see fluid_voice_get_lower_boundary_for_attenuation()). + */ + if(((mod_dest->flags1 & FLUID_MOD_CC) == FLUID_MOD_GC) && + (mod_dest->src1 == FLUID_MOD_NONE)) + { + mod_dest->amount = 0; } /* *** Dest *** */ mod_dest->dest = mod_src->dest; /* index of controlled generator */ /* *** Amount source *** */ - mod_dest->src2 = mod_src->amtsrc & 127; /* index of source 2, seven-bit value, SF2.01 section 8.2, p.50 */ - mod_dest->flags2 = 0; - - /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ - if(mod_src->amtsrc & (1 << 7)) - { - mod_dest->flags2 |= FLUID_MOD_CC; - } - else - { - mod_dest->flags2 |= FLUID_MOD_GC; - } - - /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ - if(mod_src->amtsrc & (1 << 8)) - { - mod_dest->flags2 |= FLUID_MOD_NEGATIVE; - } - else - { - mod_dest->flags2 |= FLUID_MOD_POSITIVE; - } - - /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ - if(mod_src->amtsrc & (1 << 9)) - { - mod_dest->flags2 |= FLUID_MOD_BIPOLAR; - } - else - { - mod_dest->flags2 |= FLUID_MOD_UNIPOLAR; - } - - /* modulator source types: SF2.01 section 8.2.1 page 52 */ - type = (mod_src->amtsrc) >> 10; - type &= 63; /* type is a 6-bit value */ - - if(type == 0) - { - mod_dest->flags2 |= FLUID_MOD_LINEAR; - } - else if(type == 1) - { - mod_dest->flags2 |= FLUID_MOD_CONCAVE; - } - else if(type == 2) - { - mod_dest->flags2 |= FLUID_MOD_CONVEX; - } - else if(type == 3) - { - mod_dest->flags2 |= FLUID_MOD_SWITCH; - } - else + if(!fluid_zone_mod_source_import_sfont(&mod_dest->src2, &mod_dest->flags2, mod_src->amtsrc)) { /* This shouldn't happen - unknown type! * Deactivate the modulator by setting the amount to 0. */ mod_dest->amount = 0; + } + /* Note: When secondary source input (src2) is set to General Controller 'No Controller', + output will be forced to +1.0 at synthesis time (see fluid_mod_get_value()). + That means that this source will behave unipolar only. We need to force the + unipolar flag to ensure to ensure a correct evaluation of the minimum + value later (see fluid_voice_get_lower_boundary_for_attenuation()). + */ + if(((mod_dest->flags2 & FLUID_MOD_CC) == FLUID_MOD_GC) && + (mod_dest->src2 == FLUID_MOD_NONE)) + { + mod_dest->flags2 &= ~FLUID_MOD_BIPOLAR; } /* *** Transform *** */ @@ -1410,11 +1565,11 @@ fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_ * in amount. */ if(count == 0) { - zone->mod = mod_dest; + *mod = mod_dest; } else { - fluid_mod_t *last_mod = zone->mod; + fluid_mod_t *last_mod = *mod; /* Find the end of the list */ while(last_mod->next != NULL) @@ -1428,10 +1583,47 @@ fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_ r = fluid_list_next(r); } /* foreach modulator */ + /* checks and removes invalid modulators in modulators list*/ + fluid_zone_check_mod(zone_name, mod); return FLUID_OK; } /* + * fluid_preset_zone_import_sfont + */ +int +fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_defsfont_t *defsfont) +{ + /* import the generators */ + fluid_zone_gen_import_sfont(zone->gen, &zone->range, sfzone); + + if((sfzone->instsamp != NULL) && (sfzone->instsamp->data != NULL)) + { + SFInst *sfinst = sfzone->instsamp->data; + + zone->inst = find_inst_by_idx(defsfont, sfinst->idx); + + if(zone->inst == NULL) + { + zone->inst = fluid_inst_import_sfont(zone, sfinst, defsfont); + } + + if(zone->inst == NULL) + { + return FLUID_FAILED; + } + + if(fluid_preset_zone_create_voice_zones(zone) == FLUID_FAILED) + { + return FLUID_FAILED; + } + } + + /* Import the modulators (only SF2.1 and higher) */ + return fluid_zone_mod_import_sfont(zone->name, &zone->mod, sfzone); +} + +/* * fluid_preset_zone_get_inst */ fluid_inst_t * @@ -1541,7 +1733,9 @@ fluid_inst_import_sfont(fluid_preset_zone_t *preset_zone, SFInst *sfinst, fluid_ { sfzone = (SFZone *)fluid_list_get(p); - FLUID_SNPRINTF(zone_name, sizeof(zone_name), "%s/%d", inst->name, count); + /* integrates preset zone name in instrument zone name */ + FLUID_SNPRINTF(zone_name, sizeof(zone_name), "%s/%s/%d", preset_zone->name, + inst->name, count); inst_zone = new_fluid_inst_zone(zone_name); @@ -1661,18 +1855,9 @@ new_fluid_inst_zone(char *name) void delete_fluid_inst_zone(fluid_inst_zone_t *zone) { - fluid_mod_t *mod, *tmp; - fluid_return_if_fail(zone != NULL); - mod = zone->mod; - - while(mod) /* delete the modulators */ - { - tmp = mod; - mod = mod->next; - delete_fluid_mod(tmp); - } + delete_fluid_list_mod(zone->mod); FLUID_FREE(zone->name); FLUID_FREE(zone); @@ -1693,43 +1878,8 @@ fluid_inst_zone_next(fluid_inst_zone_t *zone) int fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, SFZone *sfzone, fluid_defsfont_t *defsfont) { - fluid_list_t *r; - SFGen *sfgen; - int count; - - for(count = 0, r = sfzone->gen; r != NULL; count++) - { - sfgen = (SFGen *)fluid_list_get(r); - - switch(sfgen->id) - { - case GEN_KEYRANGE: - inst_zone->range.keylo = sfgen->amount.range.lo; - inst_zone->range.keyhi = sfgen->amount.range.hi; - break; - - case GEN_VELRANGE: - inst_zone->range.vello = sfgen->amount.range.lo; - inst_zone->range.velhi = sfgen->amount.range.hi; - break; - - case GEN_ATTENUATION: - /* EMU8k/10k hardware applies a scale factor to initial attenuation generator values set at - * preset and instrument level */ - inst_zone->gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword * EMU_ATTENUATION_FACTOR; - inst_zone->gen[sfgen->id].flags = GEN_SET; - break; - - default: - /* FIXME: some generators have an unsigned word amount value but - i don't know which ones */ - inst_zone->gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; - inst_zone->gen[sfgen->id].flags = GEN_SET; - break; - } - - r = fluid_list_next(r); - } + /* import the generators */ + fluid_zone_gen_import_sfont(inst_zone->gen, &inst_zone->range, sfzone); /* FIXME */ /* if (zone->gen[GEN_EXCLUSIVECLASS].flags == GEN_SET) { */ @@ -1743,182 +1893,7 @@ fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, SFZone *sfzone, fluid } /* Import the modulators (only SF2.1 and higher) */ - for(count = 0, r = sfzone->mod; r != NULL; count++) - { - SFMod *mod_src = (SFMod *)fluid_list_get(r); - int type; - fluid_mod_t *mod_dest; - - mod_dest = new_fluid_mod(); - - if(mod_dest == NULL) - { - return FLUID_FAILED; - } - - mod_dest->next = NULL; /* pointer to next modulator, this is the end of the list now.*/ - - /* *** Amount *** */ - mod_dest->amount = mod_src->amount; - - /* *** Source *** */ - mod_dest->src1 = mod_src->src & 127; /* index of source 1, seven-bit value, SF2.01 section 8.2, page 50 */ - mod_dest->flags1 = 0; - - /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ - if(mod_src->src & (1 << 7)) - { - mod_dest->flags1 |= FLUID_MOD_CC; - } - else - { - mod_dest->flags1 |= FLUID_MOD_GC; - } - - /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ - if(mod_src->src & (1 << 8)) - { - mod_dest->flags1 |= FLUID_MOD_NEGATIVE; - } - else - { - mod_dest->flags1 |= FLUID_MOD_POSITIVE; - } - - /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ - if(mod_src->src & (1 << 9)) - { - mod_dest->flags1 |= FLUID_MOD_BIPOLAR; - } - else - { - mod_dest->flags1 |= FLUID_MOD_UNIPOLAR; - } - - /* modulator source types: SF2.01 section 8.2.1 page 52 */ - type = (mod_src->src) >> 10; - type &= 63; /* type is a 6-bit value */ - - if(type == 0) - { - mod_dest->flags1 |= FLUID_MOD_LINEAR; - } - else if(type == 1) - { - mod_dest->flags1 |= FLUID_MOD_CONCAVE; - } - else if(type == 2) - { - mod_dest->flags1 |= FLUID_MOD_CONVEX; - } - else if(type == 3) - { - mod_dest->flags1 |= FLUID_MOD_SWITCH; - } - else - { - /* This shouldn't happen - unknown type! - * Deactivate the modulator by setting the amount to 0. */ - mod_dest->amount = 0; - } - - /* *** Dest *** */ - mod_dest->dest = mod_src->dest; /* index of controlled generator */ - - /* *** Amount source *** */ - mod_dest->src2 = mod_src->amtsrc & 127; /* index of source 2, seven-bit value, SF2.01 section 8.2, page 50 */ - mod_dest->flags2 = 0; - - /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ - if(mod_src->amtsrc & (1 << 7)) - { - mod_dest->flags2 |= FLUID_MOD_CC; - } - else - { - mod_dest->flags2 |= FLUID_MOD_GC; - } - - /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ - if(mod_src->amtsrc & (1 << 8)) - { - mod_dest->flags2 |= FLUID_MOD_NEGATIVE; - } - else - { - mod_dest->flags2 |= FLUID_MOD_POSITIVE; - } - - /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ - if(mod_src->amtsrc & (1 << 9)) - { - mod_dest->flags2 |= FLUID_MOD_BIPOLAR; - } - else - { - mod_dest->flags2 |= FLUID_MOD_UNIPOLAR; - } - - /* modulator source types: SF2.01 section 8.2.1 page 52 */ - type = (mod_src->amtsrc) >> 10; - type &= 63; /* type is a 6-bit value */ - - if(type == 0) - { - mod_dest->flags2 |= FLUID_MOD_LINEAR; - } - else if(type == 1) - { - mod_dest->flags2 |= FLUID_MOD_CONCAVE; - } - else if(type == 2) - { - mod_dest->flags2 |= FLUID_MOD_CONVEX; - } - else if(type == 3) - { - mod_dest->flags2 |= FLUID_MOD_SWITCH; - } - else - { - /* This shouldn't happen - unknown type! - * Deactivate the modulator by setting the amount to 0. */ - mod_dest->amount = 0; - } - - /* *** Transform *** */ - /* SF2.01 only uses the 'linear' transform (0). - * Deactivate the modulator by setting the amount to 0 in any other case. - */ - if(mod_src->trans != 0) - { - mod_dest->amount = 0; - } - - /* Store the new modulator in the zone - * The order of modulators will make a difference, at least in an instrument context: - * The second modulator overwrites the first one, if they only differ in amount. */ - if(count == 0) - { - inst_zone->mod = mod_dest; - } - else - { - fluid_mod_t *last_mod = inst_zone->mod; - - /* Find the end of the list */ - while(last_mod->next != NULL) - { - last_mod = last_mod->next; - } - - last_mod->next = mod_dest; - } - - r = fluid_list_next(r); - } /* foreach modulator */ - - return FLUID_OK; + return fluid_zone_mod_import_sfont(inst_zone->name, &inst_zone->mod, sfzone); } /* diff --git a/libs/fluidsynth/src/fluid_midi.c b/libs/fluidsynth/src/fluid_midi.c index b82bfdf618..8deb0b0d1b 100644 --- a/libs/fluidsynth/src/fluid_midi.c +++ b/libs/fluidsynth/src/fluid_midi.c @@ -36,6 +36,7 @@ static long fluid_getlength(unsigned char *s); */ static char *fluid_file_read_full(fluid_file fp, size_t *length); static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, void *data, int size, int dynamic); +static void fluid_midi_event_get_sysex_LOCAL(fluid_midi_event_t *evt, void **data, int *size); #define READ_FULL_INITIAL_BUFLEN 1024 static fluid_track_t *new_fluid_track(int num); @@ -82,6 +83,41 @@ static int fluid_midi_file_get_division(fluid_midi_file *midifile); */ /** + * Check if a file is a MIDI file. + * @param filename Path to the file to check + * @return TRUE if it could be a MIDI file, FALSE otherwise + * + * The current implementation only checks for the "MThd" header in the file. + * It is useful only to distinguish between SoundFont and MIDI files. + */ +int fluid_is_midifile(const char *filename) +{ + FILE *fp = FLUID_FOPEN(filename, "rb"); + uint32_t id; + int retcode = FALSE; + + do + { + if(fp == NULL) + { + return retcode; + } + + if(FLUID_FREAD(&id, sizeof(id), 1, fp) != 1) + { + break; + } + + retcode = (id == FLUID_FOURCC('M', 'T', 'h', 'd')); + } + while(0); + + FLUID_FCLOSE(fp); + + return retcode; +} + +/** * Return a new MIDI file handle for parsing an already-loaded MIDI file. * @internal * @param buffer Pointer to full contents of MIDI file (borrows the pointer). @@ -1271,8 +1307,6 @@ fluid_midi_event_set_pitch(fluid_midi_event_t *evt, int val) * should be freed when the event is freed (only applies if event gets destroyed * with delete_fluid_midi_event()) * @return Always returns #FLUID_OK - * - * @note Unlike the other event assignment functions, this one sets evt->type. */ int fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, int size, int dynamic) @@ -1291,7 +1325,6 @@ fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, int size, int dy * @return Always returns #FLUID_OK * * @since 2.0.0 - * @note Unlike the other event assignment functions, this one sets evt->type. */ int fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dynamic) @@ -1301,6 +1334,25 @@ fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dyn } /** + * Get the text of a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to return text data on. + * @param size Pointer to return text size on. + * @return Returns #FLUID_OK if \p data and \p size previously set by + * fluid_midi_event_set_text() have been successfully retrieved. + * Else #FLUID_FAILED is returned and \p data and \p size are not changed. + * @since 2.0.3 + */ +int fluid_midi_event_get_text(fluid_midi_event_t *evt, void **data, int *size) +{ + fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); + fluid_return_val_if_fail(evt->type == MIDI_TEXT, FLUID_FAILED); + + fluid_midi_event_get_sysex_LOCAL(evt, data, size); + return FLUID_OK; +} + +/** * Assign lyric data to a MIDI event structure. * @param evt MIDI event structure * @param data Pointer to lyric data @@ -1310,7 +1362,6 @@ fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dyn * @return Always returns #FLUID_OK * * @since 2.0.0 - * @note Unlike the other event assignment functions, this one sets evt->type. */ int fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int dynamic) @@ -1319,6 +1370,25 @@ fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int d return FLUID_OK; } +/** + * Get the lyric of a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to return lyric data on. + * @param size Pointer to return lyric size on. + * @return Returns #FLUID_OK if \p data and \p size previously set by + * fluid_midi_event_set_lyrics() have been successfully retrieved. + * Else #FLUID_FAILED is returned and \p data and \p size are not changed. + * @since 2.0.3 + */ +int fluid_midi_event_get_lyrics(fluid_midi_event_t *evt, void **data, int *size) +{ + fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); + fluid_return_val_if_fail(evt->type == MIDI_LYRIC, FLUID_FAILED); + + fluid_midi_event_get_sysex_LOCAL(evt, data, size); + return FLUID_OK; +} + static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, void *data, int size, int dynamic) { evt->type = type; @@ -1327,6 +1397,19 @@ static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, evt->param2 = dynamic; } +static void fluid_midi_event_get_sysex_LOCAL(fluid_midi_event_t *evt, void **data, int *size) +{ + if(data) + { + *data = evt->paramptr; + } + + if(size) + { + *size = evt->param1; + } +} + /****************************************************** * * fluid_track_t @@ -1598,7 +1681,7 @@ new_fluid_player(fluid_synth_t *synth) fluid_settings_getint(synth->settings, "player.reset-synth", &i); fluid_player_handle_reset_synth(player, NULL, i); - + fluid_settings_callback_int(synth->settings, "player.reset-synth", fluid_player_handle_reset_synth, player); diff --git a/libs/fluidsynth/src/fluid_mod.c b/libs/fluidsynth/src/fluid_mod.c index 9a48ed45d8..5e57455d4d 100644 --- a/libs/fluidsynth/src/fluid_mod.c +++ b/libs/fluidsynth/src/fluid_mod.c @@ -245,10 +245,7 @@ fluid_mod_get_source_value(const unsigned char mod_src, static fluid_real_t fluid_mod_transform_source_value(fluid_real_t val, unsigned char mod_flags, const fluid_real_t range) { - /* normalized value, i.e. usually in the range [0;1] - * - * if val was retrieved from pitch_bend then [-0.5;0.5] - */ + /* normalized value, i.e. usually in the range [0;1] */ const fluid_real_t val_norm = val / range; /* we could also only switch case the lower nibble of mod_flags, however @@ -364,7 +361,22 @@ fluid_mod_transform_source_value(fluid_real_t val, unsigned char mod_flags, cons } /* - * fluid_mod_get_value + * fluid_mod_get_value. + * Computes and return modulator output following SF2.01 + * (See SoundFont Modulator Controller Model Chapter 9.5). + * + * Output = Transform(Amount * Map(primary source input) * Map(secondary source input)) + * + * Notes: + * 1)fluid_mod_get_value, ignores the Transform operator. The result is: + * + * Output = Amount * Map(primary source input) * Map(secondary source input) + * + * 2)When primary source input (src1) is set to General Controller 'No Controller', + * output is forced to 0. + * + * 3)When secondary source input (src2) is set to General Controller 'No Controller', + * output is forced to +1.0 */ fluid_real_t fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice) @@ -418,6 +430,9 @@ fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice) /* transform the input value */ v1 = fluid_mod_transform_source_value(v1, mod->flags1, range1); } + /* When primary source input (src1) is set to General Controller 'No Controller', + output is forced to 0.0 + */ else { return 0.0; @@ -437,6 +452,9 @@ fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice) /* transform the second input value */ v2 = fluid_mod_transform_source_value(v2, mod->flags2, range2); } + /* When secondary source input (src2) is set to General Controller 'No Controller', + output is forced to +1.0 + */ else { v2 = 1.0f; @@ -487,6 +505,177 @@ size_t fluid_mod_sizeof() } /** + * Checks if modulator with source 1 other than CC is FLUID_MOD_NONE. + * + * @param mod, modulator. + * @return TRUE if modulator source 1 other than cc is FLUID_MOD_NONE, FALSE otherwise. + */ +static int +fluid_mod_is_src1_none(const fluid_mod_t *mod) +{ + return(((mod->flags1 & FLUID_MOD_CC) == 0) && (mod->src1 == FLUID_MOD_NONE)); +} + +/** + * Checks if modulators source other than CC source is invalid. + * (specs SF 2.01 7.4, 7.8, 8.2.1) + * + * @param mod, modulator. + * @param src1_select, source input selection to check. + * 1 to check src1 source. + * 0 to check src2 source. + * @return FALSE if selected modulator source other than cc is invalid, TRUE otherwise. + */ +static int +fluid_mod_check_non_cc_source(const fluid_mod_t *mod, unsigned char src1_select) +{ + unsigned char flags, src; + + if(src1_select) + { + flags = mod->flags1; + src = mod->src1; + } + else + { + flags = mod->flags2; + src = mod->src2; + } + + return(((flags & FLUID_MOD_CC) != 0) /* src is a CC */ + /* SF2.01 section 8.2.1: Constant value */ + || ((src == FLUID_MOD_NONE) + || (src == FLUID_MOD_VELOCITY) /* Note-on velocity */ + || (src == FLUID_MOD_KEY) /* Note-on key number */ + || (src == FLUID_MOD_KEYPRESSURE) /* Poly pressure */ + || (src == FLUID_MOD_CHANNELPRESSURE) /* Channel pressure */ + || (src == FLUID_MOD_PITCHWHEEL) /* Pitch wheel */ + || (src == FLUID_MOD_PITCHWHEELSENS) /* Pitch wheel sensitivity */ + )); +} + +/** + * Checks if modulator CC source is invalid (specs SF 2.01 7.4, 7.8, 8.2.1). + * @param mod, modulator. + * @src1_select, source input selection: + * 1 to check src1 source or + * 0 to check src2 source. + * @return FALSE if selected modulator's source CC is invalid, TRUE otherwise. + */ +static int +fluid_mod_check_cc_source(const fluid_mod_t *mod, unsigned char src1_select) +{ + unsigned char flags, src; + + if(src1_select) + { + flags = mod->flags1; + src = mod->src1; + } + else + { + flags = mod->flags2; + src = mod->src2; + } + + return(((flags & FLUID_MOD_CC) == 0) /* src is non CC */ + || ((src != BANK_SELECT_MSB) + && (src != BANK_SELECT_LSB) + && (src != DATA_ENTRY_MSB) + && (src != DATA_ENTRY_LSB) + /* is src not NRPN_LSB, NRPN_MSB, RPN_LSB, RPN_MSB */ + && ((src < NRPN_LSB) || (RPN_MSB < src)) + /* is src not ALL_SOUND_OFF, ALL_CTRL_OFF, LOCAL_CONTROL, ALL_NOTES_OFF ? */ + /* is src not OMNI_OFF, OMNI_ON, POLY_OFF, POLY_ON ? */ + && (src < ALL_SOUND_OFF) + /* CC lsb shouldn't allowed to modulate (spec SF 2.01 - 8.2.1) + However, as long fluidsynth will use only CC 7 bits resolution, + it is safe to ignore these SF recommendations on CC receive. + See explanations in fluid_synth_cc_LOCAL() */ + /* uncomment next line to forbid CC lsb */ + /* && ((src < 32) || (63 < src)) */ + )); +} + +/** + * Checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1) + * @param mod, modulator. + * @param name,if not NULL, pointer on a string displayed as a warning. + * @return TRUE if modulator sources src1, src2 are valid, FALSE otherwise. + */ +int fluid_mod_check_sources(const fluid_mod_t *mod, const char *name) +{ + static const char *invalid_non_cc_src = + "Invalid modulator, using non-CC source %s.src%d=%d"; + static const char *invalid_cc_src = + "Invalid modulator, using CC source %s.src%d=%d"; + static const char *src1_is_none = + "Modulator with source 1 none %s.src1=%d"; + + /* checks valid non cc sources */ + if(!fluid_mod_check_non_cc_source(mod, 1)) /* check src1 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 1, mod->src1); + } + + return FALSE; + } + + /* + When src1 is non CC source FLUID_MOD_NONE, the modulator is valid but + the output of this modulator will be forced to 0 at synthesis time. + Also this modulator cannot be used to overwrite a default modulator (as + there is no default modulator with src1 source equal to FLUID_MOD_NONE). + Consequently it is useful to return FALSE to indicate this modulator + being useless. It will be removed later with others invalid modulators. + */ + if(fluid_mod_is_src1_none(mod)) + { + if(name) + { + FLUID_LOG(FLUID_WARN, src1_is_none, name, mod->src1); + } + + return FALSE; + } + + if(!fluid_mod_check_non_cc_source(mod, 0)) /* check src2 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 2, mod->src2); + } + + return FALSE; + } + + /* checks valid cc sources */ + if(!fluid_mod_check_cc_source(mod, 1)) /* check src1 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 1, mod->src1); + } + + return FALSE; + } + + if(!fluid_mod_check_cc_source(mod, 0)) /* check src2 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 2, mod->src2); + } + + return FALSE; + } + + return TRUE; +} + +/** * Checks if two modulators are identical in sources, flags and destination. * @param mod1 First modulator * @param mod2 Second modulator diff --git a/libs/fluidsynth/src/fluid_mod.h b/libs/fluidsynth/src/fluid_mod.h index e834baa513..ec8e967a35 100644 --- a/libs/fluidsynth/src/fluid_mod.h +++ b/libs/fluidsynth/src/fluid_mod.h @@ -44,6 +44,7 @@ struct _fluid_mod_t }; fluid_real_t fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice); +int fluid_mod_check_sources(const fluid_mod_t *mod, const char *name); #ifdef DEBUG void fluid_dump_modulator(fluid_mod_t *mod); diff --git a/libs/fluidsynth/src/fluid_rev.c b/libs/fluidsynth/src/fluid_rev.c index 894afc5a05..9acee96150 100644 --- a/libs/fluidsynth/src/fluid_rev.c +++ b/libs/fluidsynth/src/fluid_rev.c @@ -1,548 +1,1358 @@ -/* +/****************************************************************************** + * FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * + * FDN REVERB + * + * Freeverb used by fluidsynth (v.1.1.10 and previous) is based on + * Schroeder-Moorer reverberator: + * https://ccrma.stanford.edu/~jos/pasp/Freeverb.html + * + * This FDN reverberation is based on jot FDN reverberator. + * https://ccrma.stanford.edu/~jos/Reverb/FDN_Late_Reverberation.html + * Like Freeverb it is a late reverb which is convenient for Fluidsynth. + * + * + * .-------------------. + * .-----------------| | + * | - | Feedback | + * | .--------------| Matrix | + * | | |___________________| + * | | /|\ /|\ + * \|/ | .---------. .-------. | - | .------. + * .->+ ---->| Delay 0 |-|L.P.F 0|--*-------->| |-> out + * .---------. | | |_________| |_______| | | | left + * |Tone | | | - - | |Stereo| + * In ->|corrector|--* | - - | | unit | + * mono |_________| | \|/ .---------. .-------. | | |-> out + * ---->+ ->| Delay 7 |-|L.P.F 7|--------*-->| | right + * |_________| |_______| |______| + * /|\ /|\ /|\ /|\ + * | | | | + * roomsize --/ | width --/ | + * damp ------/ level ------/ + * + * It takes a monophonic input and produces a stereo output. + * + * The parameters are the same than for Freeverb. + * Also the default response of these parameters are the same than for Freeverb: + * - roomsize (0 to 1): control the reverb time from 0.7 to 12.5 s. + * This reverberation time is ofen called T60DC. + * + * - damp (0 to 1): controls the reverb time frequency dependency. + * This controls the reverb time for the frequency sample rate/2 + * + * When 0, the reverb time for high frequencies is the same as + * for DC frequency. + * When > 0, high frequencies have less reverb time than lower frequencies. + * + * - width (0 to 100): controls the left/right output separation. + * When 0, there are no separation and the signal on left and right. + * output is the same. This sounds like a monophonic signal. + * When 100, the separation between left and right is maximum. + * + * - level (0 to 1), controls the ouput level reverberation. + * + * This FDN reverb produces a better quality reverberation tail than Freeverb with + * far less ringing by using modulated delay lines that help to cancel + * the building of a lot of resonances in the reverberation tail even when + * using only 8 delays lines (NBR_DELAYS = 8) (default). + * + * Although 8 lines give good result, using 12 delays lines brings the overall + * frequency density quality a bit higher. This quality augmentation is noticeable + * particularly when using long reverb time (roomsize = 1) on solo instrument with + * long release time. Of course the cpu load augmentation is +50% relatively + * to 8 lines. + * + * As a general rule the reverberation tail quality is easier to perceive by ear + * when using: + * - percussive instruments (i.e piano and others). + * - long reverb time (roomsize = 1). + * - no damping (damp = 0). + * + * + * The cpu load for 8 lines is a bit lower than for freeverb (- 3%), + * but higher for 12 lines (+ 41%). + * + * + * The memory consumption is less than for freeverb. This saves 147480 bytes + * for 8 lines (- 72%) and 110152 bytes for 12 lines (- 54 %). + * (see the results table below). + * + * Two macros are usable at compiler time: + * - NBR_DELAYS: number of delay lines. 8 (default) or 12. + * - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response of + * roomsize parameter. + * When this macro is not defined (the default), roomsize has the same + * response that Freeverb, that is: + * - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). + * + * When this macro is defined, roomsize behaves linearly: + * - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). + * This linear response is convenient when using GUI controls. + * + * -------------------------------------------------------------------------- + * Compare table: + * Note: the cpu load in % are relative each to other. These values are + * given by the fluidsynth profile commands. + * -------------------------------------------------------------------------- + * reverb | NBR_DELAYS | Performances | memory size | quality + * | | (cpu_load: %) | (bytes) | + * ========================================================================== + * freeverb | 2 x 8 comb | 0.670 % | 204616 | ringing + * | 2 x 4 all-pass | | | + * ----------|--------------------------------------------------------------- + * FDN | 8 | 0.650 % | 57136 | far less + * modulated | |(feeverb - 3%) |(freeverb - 72%) | ringing + * |--------------------------------------------------------------- + * | 12 | 0.942 % | 94464 | best than + * | |(freeverb + 41%) |(freeverb - 54%) | 8 lines + *--------------------------------------------------------------------------- + * + * + *---------------------------------------------------------------------------- + * 'Denormalise' method to avoid loss of performance. + * -------------------------------------------------- + * According to music-dsp thread 'Denormalise', Pentium processors + * have a hardware 'feature', that is of interest here, related to + * numeric underflow. We have a recursive filter. The output decays + * exponentially, if the input stops. So the numbers get smaller and + * smaller... At some point, they reach 'denormal' level. This will + * lead to drastic spikes in the CPU load. The effect was reproduced + * with the reverb - sometimes the average load over 10 s doubles!!. + * + * The 'undenormalise' macro fixes the problem: As soon as the number + * is close enough to denormal level, the macro forces the number to + * 0.0f. The original macro is: + * + * #define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f + * + * This will zero out a number when it reaches the denormal level. + * Advantage: Maximum dynamic range Disadvantage: We'll have to check + * every sample, expensive. The alternative macro comes from a later + * mail from Jon Watte. It will zap a number before it reaches + * denormal level. Jon suggests to run it once per block instead of + * every sample. + */ - Freeverb +/* Denormalising part II: + * + * Another method fixes the problem cheaper: Use a small DC-offset in + * the filter calculations. Now the signals converge not against 0, + * but against the offset. The constant offset is invisible from the + * outside world (i.e. it does not appear at the output. There is a + * very small turn-on transient response, which should not cause + * problems. + */ +#include "fluid_rev.h" +#include "fluid_sys.h" - Written by Jezar at Dreampoint, June 2000 - http://www.dreampoint.co.uk - This code is public domain +/*---------------------------------------------------------------------------- + Configuration macros at compiler time. + + 3 macros are usable at compiler time: + - NBR_DELAYs: number of delay lines. 8 (default) or 12. + - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response for + roomsize parameter. + - DENORMALISING enable denormalising handling. +-----------------------------------------------------------------------------*/ +/* Number of delay lines (must be only 8 or 12) + 8 is the default. + 12 produces a better quality but is +50% cpu expensive +*/ +#define NBR_DELAYS 8 /* default*/ + +/* response curve of parameter roomsize */ +/* + The default response is the same as Freeverb: + - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). - Translated to C by Peter Hanappe, Mai 2001 + when ROOMSIZE_RESPONSE_LINEAR is defined, the response is: + - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). */ +//#define ROOMSIZE_RESPONSE_LINEAR -#include "fluid_sys.h" -#include "fluid_rev.h" +/* DENORMALISING enable denormalising handling */ +#define DENORMALISING -/*************************************************************** - * - * REVERB - */ +#ifdef DENORMALISING +#define DC_OFFSET 1e-8 +#else +#define DC_OFFSET 0.0 +#endif -/* Denormalising: - * - * We have a recursive filter. The output decays exponentially, if the input - * stops. So the numbers get smaller and smaller... At some point, they reach - * 'denormal' level. On some platforms this will lead to drastic spikes in the - * CPU load. This is especially noticable on some older Pentium (especially - * Pentium 3) processors, but even more modern Intel Core processors still show - * reduced performance with denormals. While there are compile-time switches to - * treat denormals as zero for a lot of processors, those are not available or - * effective on all platforms. - * - * The fix used here: Use a small DC-offset in the filter calculations. Now - * the signals converge not against 0, but against the offset. The constant - * offset is invisible from the outside world (i.e. it does not appear at the - * output. There is a very small turn-on transient response, which should not - * cause problems. +/*---------------------------------------------------------------------------- + Initial internal reverb settings (at reverb creation time) +-----------------------------------------------------------------------------*/ +/* SCALE_WET_WIDTH is a compensation weight factor to get an output + amplitude (wet) rather independent of the width setting. + 0: the output amplitude is fully dependant on the width setting. + >0: the output amplitude is less dependant on the width setting. + With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather + independent of width setting (see fluid_revmodel_update()). */ -#define DC_OFFSET ((fluid_real_t)1e-8) +#define SCALE_WET_WIDTH 0.2f -typedef struct _fluid_allpass fluid_allpass; -typedef struct _fluid_comb fluid_comb; +/* It is best to inject the input signal less ofen. This contributes to obtain +a flatter response on comb filter. So the input gain is set to 0.1 rather 1.0. */ +#define FIXED_GAIN 0.1f /* input gain */ -struct _fluid_allpass -{ - fluid_real_t feedback; - fluid_real_t *buffer; - int bufsize; - int bufidx; -}; +/* SCALE_WET is adjusted to 5.0 to get internal output level equivalent to freeverb */ +#define SCALE_WET 5.0f /* scale output gain */ -void fluid_allpass_init(fluid_allpass *allpass); -void fluid_allpass_setfeedback(fluid_allpass *allpass, fluid_real_t val); -fluid_real_t fluid_allpass_getfeedback(fluid_allpass *allpass); +/*---------------------------------------------------------------------------- + Internal FDN late reverb settings +-----------------------------------------------------------------------------*/ -static void -fluid_allpass_setbuffer(fluid_allpass *allpass, int size) -{ - allpass->bufidx = 0; - allpass->buffer = FLUID_ARRAY(fluid_real_t, size); - allpass->bufsize = size; -} +/*-- Reverberation time settings ---------------------------------- + MIN_DC_REV_TIME est defined egal to the minimum value of freeverb: + MAX_DC_REV_TIME est defined egal to the maximum value of freeverb: + T60DC is computed from gi and the longuest delay line in freeverb: L8 = 1617 + T60 = -3 * Li * T / log10(gi) + T60 = -3 * Li * / (log10(gi) * sr) -static void -fluid_allpass_release(fluid_allpass *allpass) -{ - FLUID_FREE(allpass->buffer); -} + - Li: length of comb filter delay line. + - sr: sample rate. + - gi: the feedback gain. -void -fluid_allpass_init(fluid_allpass *allpass) -{ - int i; - int len = allpass->bufsize; - fluid_real_t *buf = allpass->buffer; + The minimum value for freeverb correspond to gi = 0.7. + with Mi = 1617, sr at 44100 Hz, and gi = 0.7 => MIN_DC_REV_TIME = 0.7 s - for(i = 0; i < len; i++) - { - buf[i] = DC_OFFSET; /* this is not 100 % correct. */ - } -} + The maximum value for freeverb correspond to gi = 0.98. + with Mi = 1617, sr at 44100 Hz, and gi = 0.98 => MAX_DC_REV_TIME = 12.5 s +*/ -void -fluid_allpass_setfeedback(fluid_allpass *allpass, fluid_real_t val) -{ - allpass->feedback = val; -} +#define MIN_DC_REV_TIME 0.7f /* minimum T60DC reverb time: seconds */ +#define MAX_DC_REV_TIME 12.5f /* maximumm T60DC time in seconds */ +#define RANGE_REV_TIME (MAX_DC_REV_TIME - MIN_DC_REV_TIME) -fluid_real_t -fluid_allpass_getfeedback(fluid_allpass *allpass) -{ - return allpass->feedback; -} +/* macro to compute internal reverberation time versus roomsize parameter */ +#define GET_DC_REV_TIME(roomsize) (MIN_DC_REV_TIME + RANGE_REV_TIME * roomsize) -#define fluid_allpass_process(_allpass, _input) \ -{ \ - fluid_real_t output; \ - fluid_real_t bufout; \ - bufout = _allpass.buffer[_allpass.bufidx]; \ - output = bufout-_input; \ - _allpass.buffer[_allpass.bufidx] = _input + (bufout * _allpass.feedback); \ - if (++_allpass.bufidx >= _allpass.bufsize) { \ - _allpass.bufidx = 0; \ - } \ - _input = output; \ -} +/*-- Modulation related settings ----------------------------------*/ +/* For many instruments, the range for MOD_FREQ and MOD_DEPTH should be: -struct _fluid_comb + MOD_DEPTH: [3..6] (in samples). + MOD_FREQ: [0.5 ..2.0] (in Hz). + + Values below the lower limits are often not sufficient to cancel unwanted + "ringing"(resonant frequency). + Values above upper limits augment the unwanted "chorus". + + With NBR_DELAYS to 8: + MOD_DEPTH must be >= 4 to cancel the unwanted "ringing".[4..6]. + With NBR_DELAYS to 12: + MOD_DEPTH to 3 is sufficient to cancel the unwanted "ringing".[3..6] +*/ +#define MOD_DEPTH 4 /* modulation depth (samples)*/ +#define MOD_RATE 50 /* modulation rate (samples)*/ +#define MOD_FREQ 1.0f /* modulation frequency (Hz) */ +/* + Number of samples to add to the desired length of a delay line. This + allow to take account of modulation interpolation. + 1 is sufficient with MOD_DEPTH equal to 6. +*/ +#define INTERP_SAMPLES_NBR 1 + +/* phase offset between modulators waveform */ +#define MOD_PHASE (360.0/(float) NBR_DELAYS) + +#if (NBR_DELAYS == 8) + #define DELAY_L0 601 + #define DELAY_L1 691 + #define DELAY_L2 773 + #define DELAY_L3 839 + #define DELAY_L4 919 + #define DELAY_L5 997 + #define DELAY_L6 1061 + #define DELAY_L7 1129 +#elif (NBR_DELAYS == 12) + #define DELAY_L0 601 + #define DELAY_L1 691 + #define DELAY_L2 773 + #define DELAY_L3 839 + #define DELAY_L4 919 + #define DELAY_L5 997 + #define DELAY_L6 1061 + #define DELAY_L7 1093 + #define DELAY_L8 1129 + #define DELAY_L9 1151 + #define DELAY_L10 1171 + #define DELAY_L11 1187 +#endif + +/* Delay lines length table (in samples) */ +static const int delay_length[NBR_DELAYS] = { - fluid_real_t feedback; - fluid_real_t filterstore; - fluid_real_t damp1; - fluid_real_t damp2; - fluid_real_t *buffer; - int bufsize; - int bufidx; + DELAY_L0, DELAY_L1, DELAY_L2, DELAY_L3, + DELAY_L4, DELAY_L5, DELAY_L6, DELAY_L7, +#if (NBR_DELAYS == 12) + DELAY_L8, DELAY_L9, DELAY_L10, DELAY_L11 +#endif }; -void fluid_comb_setbuffer(fluid_comb *comb, int size); -void fluid_comb_release(fluid_comb *comb); -void fluid_comb_init(fluid_comb *comb); -void fluid_comb_setdamp(fluid_comb *comb, fluid_real_t val); -fluid_real_t fluid_comb_getdamp(fluid_comb *comb); -void fluid_comb_setfeedback(fluid_comb *comb, fluid_real_t val); -fluid_real_t fluid_comb_getfeedback(fluid_comb *comb); -void -fluid_comb_setbuffer(fluid_comb *comb, int size) -{ - comb->filterstore = 0; - comb->bufidx = 0; - comb->buffer = FLUID_ARRAY(fluid_real_t, size); - comb->bufsize = size; -} +/*---------------------------------------------------------------------------*/ +/* The FDN late feed back matrix: A + T + A = P - 2 / N * u * u + N N N N -void -fluid_comb_release(fluid_comb *comb) + N: the matrix dimension (i.e NBR_DELAYS). + P: permutation matrix. + u: is a colomn vector of 1. + +*/ +#define FDN_MATRIX_FACTOR (fluid_real_t)(-2.0 / NBR_DELAYS) + +/*---------------------------------------------------------------------------- + Internal FDN late structures and static functions +-----------------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------------- + Delay absorbent low pass filter +-----------------------------------------------------------------------------*/ +typedef struct { - FLUID_FREE(comb->buffer); + fluid_real_t buffer; + fluid_real_t b0, a1; /* filter coefficients */ +} fdn_delay_lpf; + +/*----------------------------------------------------------------------------- + Sets coefficients for delay absorbent low pass filter. + @param lpf pointer on low pass filter structure. + @param b0,a1 coefficients. +-----------------------------------------------------------------------------*/ +static void set_fdn_delay_lpf(fdn_delay_lpf *lpf, + fluid_real_t b0, fluid_real_t a1) +{ + lpf->b0 = b0; + lpf->a1 = a1; } -void -fluid_comb_init(fluid_comb *comb) +/*----------------------------------------------------------------------------- + Process delay absorbent low pass filter. + @param mod_delay modulated delay line. + @param in, input sample. + @param out output sample. +-----------------------------------------------------------------------------*/ +/* process low pass damping filter (input, output, delay) */ +#define process_damping_filter(in,out,mod_delay) \ +{\ + out = in * mod_delay->dl.damping.b0 - mod_delay->dl.damping.buffer * \ + mod_delay->dl.damping.a1;\ + mod_delay->dl.damping.buffer = out;\ +}\ + + +/*----------------------------------------------------------------------------- + Delay line : + The delay line is composed of the line plus an absorbent low pass filter + to get frequency dependant reverb time. +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t *line; /* buffer line */ + int size; /* effective internal size (in samples) */ + /*-------------*/ + int line_in; /* line in position */ + int line_out; /* line out position */ + /*-------------*/ + fdn_delay_lpf damping; /* damping low pass filter */ +} delay_line; + + +/*----------------------------------------------------------------------------- + Clears a delay line to DC_OFFSET float value. + @param dl pointer on delay line structure +-----------------------------------------------------------------------------*/ +static void clear_delay_line(delay_line *dl) { int i; - fluid_real_t *buf = comb->buffer; - int len = comb->bufsize; - for(i = 0; i < len; i++) + for(i = 0; i < dl->size; i++) { - buf[i] = DC_OFFSET; /* This is not 100 % correct. */ + dl->line[i] = DC_OFFSET; } } -void -fluid_comb_setdamp(fluid_comb *comb, fluid_real_t val) +/*----------------------------------------------------------------------------- + Push a sample val into the delay line +-----------------------------------------------------------------------------*/ +#define push_in_delay_line(dl, val) \ +{\ + dl->line[dl->line_in] = val;\ + /* Incrementation and circular motion if necessary */\ + if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ +}\ + +/*----------------------------------------------------------------------------- + Modulator for modulated delay line +-----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------- + Sinusoidal modulator +-----------------------------------------------------------------------------*/ +/* modulator are integrated in modulated delay line */ +typedef struct { - comb->damp1 = val; - comb->damp2 = 1 - val; -} - -fluid_real_t -fluid_comb_getdamp(fluid_comb *comb) + fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ + fluid_real_t buffer1; /* buffer1 */ + fluid_real_t buffer2; /* buffer2 */ + fluid_real_t reset_buffer2;/* reset value of buffer2 */ +} sinus_modulator; + +/*----------------------------------------------------------------------------- + Sets the frequency of sinus oscillator. + + @param mod pointer on modulator structure. + @param freq frequency of the oscillator in Hz. + @param sample_rate sample rate on audio output in Hz. + @param phase initial phase of the oscillator in degree (0 to 360). +-----------------------------------------------------------------------------*/ +static void set_mod_frequency(sinus_modulator *mod, + float freq, float sample_rate, float phase) { - return comb->damp1; -} + fluid_real_t w = 2 * M_PI * freq / sample_rate; /* intial angle */ -void -fluid_comb_setfeedback(fluid_comb *comb, fluid_real_t val) -{ - comb->feedback = val; + mod->a1 = 2 * cos(w); + mod->buffer2 = sin(2 * M_PI * phase / 360 - w); /* y(n-1) = sin(-intial angle) */ + mod->buffer1 = sin(2 * M_PI * phase / 360); /* y(n) = sin(initial phase) */ + mod->reset_buffer2 = sin(M_PI / 2.0 - w); /* reset value for PI/2 */ } -fluid_real_t -fluid_comb_getfeedback(fluid_comb *comb) +/*----------------------------------------------------------------------------- + Gets current value of sinus modulator: + y(n) = a1 . y(n-1) - y(n-2) + out = a1 . buffer1 - buffer2 + + @param pointer on modulator structure. + @return current value of the modulator sine wave. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_sinus(sinus_modulator *mod) { - return comb->feedback; -} + fluid_real_t out; + out = mod->a1 * mod->buffer1 - mod->buffer2; + mod->buffer2 = mod->buffer1; -#define fluid_comb_process(_comb, _input, _output) \ -{ \ - fluid_real_t _tmp = _comb.buffer[_comb.bufidx]; \ - _comb.filterstore = (_tmp * _comb.damp2) + (_comb.filterstore * _comb.damp1); \ - _comb.buffer[_comb.bufidx] = _input + (_comb.filterstore * _comb.feedback); \ - if (++_comb.bufidx >= _comb.bufsize) { \ - _comb.bufidx = 0; \ - } \ - _output += _tmp; \ -} + if(out >= 1.0) /* reset in case of instability near PI/2 */ + { + out = 1.0; /* forces output to the right value */ + mod->buffer2 = mod->reset_buffer2; + } -#define numcombs 8 -#define numallpasses 4 -#define fixedgain 0.015f -/* scale_wet_width is a compensation weight factor to get an output - amplitude (wet) rather independent of the width setting. - 0: the output amplitude is fully dependant on the width setting. - >0: the output amplitude is less dependant on the width setting. - With a scale_wet_width of 0.2 the output amplitude is rather - independent of width setting (see fluid_revmodel_update()). - */ -#define scale_wet_width 0.2f -#define scalewet 3.0f -#define scaledamp 1.0f -#define scaleroom 0.28f -#define offsetroom 0.7f -#define stereospread 23 + if(out <= -1.0) /* reset in case of instability near -PI/2 */ + { + out = -1.0; /* forces output to the right value */ + mod->buffer2 = - mod->reset_buffer2; + } -/* - These values assume 44.1KHz sample rate - they will probably be OK for 48KHz sample rate - but would need scaling for 96KHz (or other) sample rates. - The values were obtained by listening tests. -*/ -#define combtuningL1 1116 -#define combtuningR1 (1116 + stereospread) -#define combtuningL2 1188 -#define combtuningR2 (1188 + stereospread) -#define combtuningL3 1277 -#define combtuningR3 (1277 + stereospread) -#define combtuningL4 1356 -#define combtuningR4 (1356 + stereospread) -#define combtuningL5 1422 -#define combtuningR5 (1422 + stereospread) -#define combtuningL6 1491 -#define combtuningR6 (1491 + stereospread) -#define combtuningL7 1557 -#define combtuningR7 (1557 + stereospread) -#define combtuningL8 1617 -#define combtuningR8 (1617 + stereospread) -#define allpasstuningL1 556 -#define allpasstuningR1 (556 + stereospread) -#define allpasstuningL2 441 -#define allpasstuningR2 (441 + stereospread) -#define allpasstuningL3 341 -#define allpasstuningR3 (341 + stereospread) -#define allpasstuningL4 225 -#define allpasstuningR4 (225 + stereospread) + mod->buffer1 = out; + return out; +} -struct _fluid_revmodel_t +/*----------------------------------------------------------------------------- + Modulated delay line. The line is composed of: + - the delay line with its damping low pass filter. + - the sinusoidal modulator. + - center output position modulated by the modulator. + - variable rate control of center output position. + - first order All-Pass interpolator. +-----------------------------------------------------------------------------*/ +typedef struct { - fluid_real_t roomsize; - fluid_real_t damp; - fluid_real_t level, wet1, wet2; - fluid_real_t width; - fluid_real_t gain; - /* - The following are all declared inline - to remove the need for dynamic allocation - with its subsequent error-checking messiness - */ - /* Comb filters */ - fluid_comb combL[numcombs]; - fluid_comb combR[numcombs]; - /* Allpass filters */ - fluid_allpass allpassL[numallpasses]; - fluid_allpass allpassR[numallpasses]; -}; - -static void fluid_revmodel_update(fluid_revmodel_t *rev); -static void fluid_revmodel_init(fluid_revmodel_t *rev); -void fluid_set_revmodel_buffers(fluid_revmodel_t *rev, fluid_real_t sample_rate); - -fluid_revmodel_t * -new_fluid_revmodel(fluid_real_t sample_rate) + /* delay line with damping low pass filter member */ + delay_line dl; /* delayed line */ + /*---------------------------*/ + /* Sinusoidal modulator member */ + sinus_modulator mod; /* sinus modulator */ + /*-------------------------*/ + /* center output position members */ + fluid_real_t center_pos_mod; /* center output position modulated by modulator */ + int mod_depth; /* modulation depth (in samples) */ + /*-------------------------*/ + /* variable rate control of center output position */ + int index_rate; /* index rate to know when to update center_pos_mod */ + int mod_rate; /* rate at which center_pos_mod is updated */ + /*-------------------------*/ + /* first order All-Pass interpolator members */ + fluid_real_t frac_pos_mod; /* fractional position part between samples) */ + /* previous value used when interpolating using fractional */ + fluid_real_t buffer; +} mod_delay_line; + + +/*----------------------------------------------------------------------------- + Modulated delay line initialization. + + Sets the length line ( alloc delay samples). + Remark: the function sets the internal size accordling to the length delay_length. + As the delay line is a modulated line, its internal size is augmented by mod_depth. + The size is also augmented by INTERP_SAMPLES_NBR to take account of interpolation. + + @param mdl, pointer on modulated delay line. + @param delay_length the length of the delay line in samples. + @param mod_depth depth of the modulation in samples (amplitude of the sine wave). + @param mod_rate the rate of the modulation in samples. + @return FLUID_OK if success , FLUID_FAILED if memory error. + + Return FLUID_OK if success, FLUID_FAILED if memory error. +-----------------------------------------------------------------------------*/ +static int set_mod_delay_line(mod_delay_line *mdl, + int delay_length, + int mod_depth, + int mod_rate + ) { - fluid_revmodel_t *rev; - rev = FLUID_NEW(fluid_revmodel_t); + /*-----------------------------------------------------------------------*/ + /* checks parameter */ + if(delay_length < 1) + { + return FLUID_FAILED; + } - if(rev == NULL) + /* limits mod_depth to the requested delay length */ + if(mod_depth >= delay_length) { - return NULL; + FLUID_LOG(FLUID_INFO, + "fdn reverb: modulation depth has been limited"); + mod_depth = delay_length - 1; } - fluid_set_revmodel_buffers(rev, sample_rate); + mdl->mod_depth = mod_depth; + /*----------------------------------------------------------------------- + allocates delay_line and initialize members: + - line, size, line_in, line_out... + */ + { + /* total size of the line: + size = INTERP_SAMPLES_NBR + mod_depth + delay_length */ + mdl->dl.size = delay_length + mod_depth + INTERP_SAMPLES_NBR; + mdl->dl.line = FLUID_ARRAY(fluid_real_t, mdl->dl.size); - /* Set default values */ - fluid_allpass_setfeedback(&rev->allpassL[0], 0.5f); - fluid_allpass_setfeedback(&rev->allpassR[0], 0.5f); - fluid_allpass_setfeedback(&rev->allpassL[1], 0.5f); - fluid_allpass_setfeedback(&rev->allpassR[1], 0.5f); - fluid_allpass_setfeedback(&rev->allpassL[2], 0.5f); - fluid_allpass_setfeedback(&rev->allpassR[2], 0.5f); - fluid_allpass_setfeedback(&rev->allpassL[3], 0.5f); - fluid_allpass_setfeedback(&rev->allpassR[3], 0.5f); + if(! mdl->dl.line) + { + return FLUID_FAILED; + } - rev->gain = fixedgain; + clear_delay_line(&mdl->dl); /* clears the buffer */ - return rev; + /* Initializes line_in to the start of the buffer */ + mdl->dl.line_in = 0; + /* Initializes line_out index INTERP_SAMPLES_NBR samples after line_in */ + /* so that the delay between line_out and line_in is: + mod_depth + delay_length */ + mdl->dl.line_out = mdl->dl.line_in + INTERP_SAMPLES_NBR; + } + + /* Damping low pass filter -------------------*/ + mdl->dl.damping.buffer = 0; + /*------------------------------------------------------------------------ + Initializes modulation members: + - modulated center position: center_pos_mod + - index rate to know when to update center_pos_mod:index_rate + - modulation rate (the speed at which center_pos_mod is modulated: mod_rate + - interpolator member: buffer, frac_pos_mod + -------------------------------------------------------------------------*/ + /* Sets the modulation rate. This rate defines how often + the center position (center_pos_mod ) is modulated . + The value is expressed in samples. The default value is 1 that means that + center_pos_mod is updated at every sample. + For example with a value of 2, the center position position will be + updated only one time every 2 samples only. + */ + mdl->mod_rate = 1; /* default modulation rate: every one sample */ + + if(mod_rate > mdl->dl.size) + { + FLUID_LOG(FLUID_INFO, + "fdn reverb: modulation rate is out of range"); + } + else + { + mdl->mod_rate = mod_rate; + } + + /* Initializes the modulated center position (center_pos_mod) so that: + - the delay between line_out and center_pos_mod is mod_depth. + - the delay between center_pos_mod and line_in is delay_length. + */ + mdl->center_pos_mod = (fluid_real_t) INTERP_SAMPLES_NBR + mod_depth; + + /* index rate to control when to update center_pos_mod */ + /* Important: must be set to get center_pos_mod immediatly used for the + reading of first sample (see get_mod_delay()) */ + mdl->index_rate = mdl->mod_rate; + + /* initializes 1st order All-Pass interpolator members */ + mdl->buffer = 0; /* previous delay sample value */ + mdl->frac_pos_mod = 0; /* fractional position (between consecutives sample) */ + return FLUID_OK; } -void -delete_fluid_revmodel(fluid_revmodel_t *rev) +/*----------------------------------------------------------------------------- + Reads the sample value out of the modulated delay line. + @param mdl, pointer on modulated delay line. + @return the sample value. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_delay(mod_delay_line *mdl) { - int i; - fluid_return_if_fail(rev != NULL); + fluid_real_t out_index; /* new modulated index position */ + int int_out_index; /* integer part of out_index */ + fluid_real_t out; /* value to return */ + + /* Checks if the modulator must be updated (every mod_rate samples). */ + /* Important: center_pos_mod must be used immediatly for the + first sample. So, mdl->index_rate must be initialized + to mdl->mod_rate (set_mod_delay_line()) */ - for(i = 0; i < numcombs; i++) + if(++mdl->index_rate >= mdl->mod_rate) { - fluid_comb_release(&rev->combL[i]); - fluid_comb_release(&rev->combR[i]); + mdl->index_rate = 0; + + /* out_index = center position (center_pos_mod) + sinus waweform */ + out_index = mdl->center_pos_mod + + get_mod_sinus(&mdl->mod) * mdl->mod_depth; + + /* extracts integer part in int_out_index */ + if(out_index >= 0.0f) + { + int_out_index = (int)out_index; /* current integer part */ + + /* forces read index (line_out) with integer modulation value */ + /* Boundary check and circular motion as needed */ + if((mdl->dl.line_out = int_out_index) >= mdl->dl.size) + { + mdl->dl.line_out -= mdl->dl.size; + } + } + else /* negative */ + { + int_out_index = (int)(out_index - 1); /* previous integer part */ + /* forces read index (line_out) with integer modulation value */ + /* circular motion as needed */ + mdl->dl.line_out = int_out_index + mdl->dl.size; + } + + /* extracts fractionnal part. (it will be used when interpolating + between line_out and line_out +1) and memorize it. + Memorizing is necessary for modulation rate above 1 */ + mdl->frac_pos_mod = out_index - int_out_index; + + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((mdl->center_pos_mod += mdl->mod_rate) >= mdl->dl.size) + { + mdl->center_pos_mod -= mdl->dl.size; + } } - for(i = 0; i < numallpasses; i++) + /* First order all-pass interpolation ----------------------------------*/ + /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ + /* begins interpolation: read current sample */ + out = mdl->dl.line[mdl->dl.line_out]; + + /* updates line_out to the next sample. + Boundary check and circular motion as needed */ + if(++mdl->dl.line_out >= mdl->dl.size) { - fluid_allpass_release(&rev->allpassL[i]); - fluid_allpass_release(&rev->allpassR[i]); + mdl->dl.line_out -= mdl->dl.size; } - FLUID_FREE(rev); + /* Fractional interpolation beetween next sample (at next position) and + previous output added to current sample. + */ + out += mdl->frac_pos_mod * (mdl->dl.line[mdl->dl.line_out] - mdl->buffer); + mdl->buffer = out; /* memorizes current output */ + return out; } -void -fluid_set_revmodel_buffers(fluid_revmodel_t *rev, fluid_real_t sample_rate) +/*----------------------------------------------------------------------------- + Late structure +-----------------------------------------------------------------------------*/ +struct _fluid_late { + fluid_real_t samplerate; /* sample rate */ + /*----- High pass tone corrector -------------------------------------*/ + fluid_real_t tone_buffer; + fluid_real_t b1, b2; + /*----- Modulated delay lines lines ----------------------------------*/ + mod_delay_line mod_delay_lines[NBR_DELAYS]; + /*-----------------------------------------------------------------------*/ + /* Output coefficients for separate Left and right stereo outputs */ + fluid_real_t out_left_gain[NBR_DELAYS]; /* Left delay lines' output gains */ + fluid_real_t out_right_gain[NBR_DELAYS];/* Right delay lines' output gains*/ +}; - float srfactor = sample_rate / 44100.0f; - - fluid_comb_setbuffer(&rev->combL[0], combtuningL1 * srfactor); - fluid_comb_setbuffer(&rev->combR[0], combtuningR1 * srfactor); - fluid_comb_setbuffer(&rev->combL[1], combtuningL2 * srfactor); - fluid_comb_setbuffer(&rev->combR[1], combtuningR2 * srfactor); - fluid_comb_setbuffer(&rev->combL[2], combtuningL3 * srfactor); - fluid_comb_setbuffer(&rev->combR[2], combtuningR3 * srfactor); - fluid_comb_setbuffer(&rev->combL[3], combtuningL4 * srfactor); - fluid_comb_setbuffer(&rev->combR[3], combtuningR4 * srfactor); - fluid_comb_setbuffer(&rev->combL[4], combtuningL5 * srfactor); - fluid_comb_setbuffer(&rev->combR[4], combtuningR5 * srfactor); - fluid_comb_setbuffer(&rev->combL[5], combtuningL6 * srfactor); - fluid_comb_setbuffer(&rev->combR[5], combtuningR6 * srfactor); - fluid_comb_setbuffer(&rev->combL[6], combtuningL7 * srfactor); - fluid_comb_setbuffer(&rev->combR[6], combtuningR7 * srfactor); - fluid_comb_setbuffer(&rev->combL[7], combtuningL8 * srfactor); - fluid_comb_setbuffer(&rev->combR[7], combtuningR8 * srfactor); - fluid_allpass_setbuffer(&rev->allpassL[0], allpasstuningL1 * srfactor); - fluid_allpass_setbuffer(&rev->allpassR[0], allpasstuningR1 * srfactor); - fluid_allpass_setbuffer(&rev->allpassL[1], allpasstuningL2 * srfactor); - fluid_allpass_setbuffer(&rev->allpassR[1], allpasstuningR2 * srfactor); - fluid_allpass_setbuffer(&rev->allpassL[2], allpasstuningL3 * srfactor); - fluid_allpass_setbuffer(&rev->allpassR[2], allpasstuningR3 * srfactor); - fluid_allpass_setbuffer(&rev->allpassL[3], allpasstuningL4 * srfactor); - fluid_allpass_setbuffer(&rev->allpassR[3], allpasstuningR4 * srfactor); - - /* Clear all buffers */ - fluid_revmodel_init(rev); -} +typedef struct _fluid_late fluid_late; +/*----------------------------------------------------------------------------- + fluidsynth reverb structure +-----------------------------------------------------------------------------*/ +struct _fluid_revmodel_t +{ + /* reverb parameters */ + fluid_real_t roomsize; /* acting on reverb time */ + fluid_real_t damp; /* acting on frequency dependent reverb time */ + fluid_real_t level, wet1, wet2; /* output level */ + fluid_real_t width; /* width stereo separation */ + + /* fdn reverberation structure */ + fluid_late late; +}; +/*----------------------------------------------------------------------------- + Updates Reverb time and absorbent filters coefficients from parameters: -static void -fluid_revmodel_init(fluid_revmodel_t *rev) + @param late pointer on late structure. + @param roomsize (0 to 1): acting on reverb time. + @param damping (0 to 1): acting on absorbent damping filter. + + Design formulas: + https://ccrma.stanford.edu/~jos/Reverb/First_Order_Delay_Filter_Design.html + https://ccrma.stanford.edu/~jos/Reverb/Tonal_Correction_Filter.html +-----------------------------------------------------------------------------*/ +static void update_rev_time_damping(fluid_late *late, + fluid_real_t roomsize, fluid_real_t damp) { int i; + fluid_real_t sample_period = 1 / late->samplerate; /* Sampling period */ + fluid_real_t dc_rev_time; /* Reverb time at 0 Hz (in seconds) */ + + fluid_real_t alpha; - for(i = 0; i < numcombs; i++) + /*-------------------------------------------- + Computes dc_rev_time and alpha + ----------------------------------------------*/ { - fluid_comb_init(&rev->combL[i]); - fluid_comb_init(&rev->combR[i]); + fluid_real_t gi_tmp, ai_tmp; +#ifdef ROOMSIZE_RESPONSE_LINEAR + /* roomsize parameter behave linearly: + * - roomsize (0 to 1) controls reverb time linearly (0.7 to 10 s). + * This linear response is convenient when using GUI controls. + */ + /*----------------------------------------- + Computes dc_rev_time + ------------------------------------------*/ + dc_rev_time = GET_DC_REV_TIME(roomsize); + /* computes gi_tmp from dc_rev_time using relation E2 */ + gi_tmp = (fluid_real_t) pow(10, -3 * delay_length[NBR_DELAYS - 1] * + sample_period / dc_rev_time); /* E2 */ +#else + /* roomsize parameters have the same response that Freeverb, that is: + * - roomsize (0 to 1) controls concave reverb time (0.7 to 10 s). + */ + { + /*----------------------------------------- + Computes dc_rev_time + ------------------------------------------*/ + fluid_real_t gi_min, gi_max; + /* values gi_min et gi_max are computed using E2 for the line with + maximum delay */ + gi_max = (fluid_real_t)pow(10, -3 * delay_length[NBR_DELAYS - 1] * + sample_period / MAX_DC_REV_TIME); /* E2 */ + gi_min = (fluid_real_t)pow(10, -3 * delay_length[NBR_DELAYS - 1] * + sample_period / MIN_DC_REV_TIME); /* E2 */ + /* gi = f(roomsize, gi_max, gi_min) */ + gi_tmp = gi_min + roomsize * (gi_max - gi_min); + /* Computes T60DC from gi using inverse of relation E2.*/ + dc_rev_time = -3 * delay_length[NBR_DELAYS - 1] * sample_period / log10(gi_tmp); + } +#endif /* ROOMSIZE_RESPONSE_LINEAR */ + /*-------------------------------------------- + Computes alpha + ----------------------------------------------*/ + /* Computes alpha from damp,ai_tmp,gi_tmp using relation R */ + /* - damp (0 to 1) controls concave reverb time for fs/2 frequency (T60DC to 0) */ + ai_tmp = 1.0 * damp; + alpha = sqrt(1 / (1 - ai_tmp / (20 * log10(gi_tmp) * log(10) / 80))); /* R */ } - for(i = 0; i < numallpasses; i++) + /* updates tone corrector coefficients b1,b2 from alpha */ { - fluid_allpass_init(&rev->allpassL[i]); - fluid_allpass_init(&rev->allpassR[i]); + /* + Beta = (1 - alpha) / (1 + alpha) + b1 = 1/(1-beta) + b2 = beta * b1 + */ + fluid_real_t beta = (1 - alpha) / (1 + alpha); + late->b1 = 1 / (1 - beta); + late->b2 = beta * late->b1; + late->tone_buffer = 0.0f; } -} -void -fluid_revmodel_reset(fluid_revmodel_t *rev) -{ - fluid_revmodel_init(rev); + /* updates damping coefficients of all lines (gi , ai) from dc_rev_time, alpha */ + for(i = 0; i < NBR_DELAYS; i++) + { + /* iir low pass filter gain */ + fluid_real_t gi = (fluid_real_t)pow(10, -3 * delay_length[i] * + sample_period / dc_rev_time); + + /* iir low pass filter feedback gain */ + fluid_real_t ai = (fluid_real_t)(20 * log10(gi) * log(10) / 80 * + (1 - 1 / pow(alpha, 2))); + /* b0 = gi * (1 - ai), a1 = - ai */ + set_fdn_delay_lpf(&late->mod_delay_lines[i].dl.damping, + gi * (1 - ai), -ai); + } } -void -fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, - fluid_real_t *left_out, fluid_real_t *right_out) +/*----------------------------------------------------------------------------- + Updates stereo coefficients + @param late pointer on late structure + @param wet level integrated in stereo coefficients. +-----------------------------------------------------------------------------*/ +static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) { - int i, k = 0; - fluid_real_t outL, outR, input; + int i; - for(k = 0; k < FLUID_BUFSIZE; k++) + for(i = 0; i < NBR_DELAYS; i++) { + /* delay lines output gains vectors Left and Right + + L R + 0 | 1 1| + 1 |-1 1| + 2 | 1 -1| + 3 |-1 -1| + + 4 | 1 1| + 5 |-1 1| + stereo gain = 6 | 1 -1| + 7 |-1 -1| - outL = outR = 0; + 8 | 1 1| + 9 |-1 1| + 10| 1 -1| + 11|-1 -1| + */ - /* The original Freeverb code expects a stereo signal and 'input' - * is set to the sum of the left and right input sample. Since - * this code works on a mono signal, 'input' is set to twice the - * input sample. */ - input = (2.0f * in[k] + DC_OFFSET) * rev->gain; + late->out_left_gain[i] = wet1; - /* Accumulate comb filters in parallel */ - for(i = 0; i < numcombs; i++) + /* Sets Left coefficients first */ + if(i % 2) /* Left is 1,-1, 1,-1, 1,-1,.... */ { - fluid_comb_process(rev->combL[i], input, outL); - fluid_comb_process(rev->combR[i], input, outR); + late->out_left_gain[i] *= -1; } - /* Feed through allpasses in series */ - for(i = 0; i < numallpasses; i++) + /* Now sets right gain as function of Left */ + /* for right line 1,2,5,6,9,10,13,14, right = - left */ + if((i == 1) || (i == 2) || (i == 5) || (i == 6) || (i == 9) || (i == 10) || (i == 13) || (i == 14)) { - fluid_allpass_process(rev->allpassL[i], outL); - fluid_allpass_process(rev->allpassR[i], outR); + /* Right is reverse of Left */ + late->out_right_gain[i] = -1 * late->out_left_gain[i]; } + else /* for Right : line 0,3,4,7,8,11,12,15 */ + { + /* Right is same as Left */ + late->out_right_gain[i] = late->out_left_gain[i]; + } + } +} - /* Remove the DC offset */ - outL -= DC_OFFSET; - outR -= DC_OFFSET; +/*----------------------------------------------------------------------------- + fluid_late destructor. + @param late pointer on late structure. +-----------------------------------------------------------------------------*/ +static void delete_fluid_rev_late(fluid_late *late) +{ + int i; + fluid_return_if_fail(late != NULL); - /* Calculate output REPLACING anything already there */ - left_out[k] = outL * rev->wet1 + outR * rev->wet2; - right_out[k] = outR * rev->wet1 + outL * rev->wet2; + /* free the delay lines */ + for(i = 0; i < NBR_DELAYS; i++) + { + FLUID_FREE(late->mod_delay_lines[i].dl.line); } } -void -fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, - fluid_real_t *left_out, fluid_real_t *right_out) +/*----------------------------------------------------------------------------- + Creates the fdn reverb. + @param late, pointer on the fnd late reverb to initialize. + @param sample_rate the sample rate. + @return FLUID_OK if success, FLUID_FAILED otherwise. +-----------------------------------------------------------------------------*/ +static int create_fluid_rev_late(fluid_late *late, fluid_real_t sample_rate) { - int i, k = 0; - fluid_real_t outL, outR, input; + int result; /* return value */ + int i; - for(k = 0; k < FLUID_BUFSIZE; k++) - { + FLUID_MEMSET(late, 0, sizeof(fluid_late)); - outL = outR = 0; + late->samplerate = sample_rate; - /* The original Freeverb code expects a stereo signal and 'input' - * is set to the sum of the left and right input sample. Since - * this code works on a mono signal, 'input' is set to twice the - * input sample. */ - input = (2.0f * in[k] + DC_OFFSET) * rev->gain; + /*-------------------------------------------------------------------------- + First initialize the modulated delay lines + */ - /* Accumulate comb filters in parallel */ - for(i = 0; i < numcombs; i++) - { - fluid_comb_process(rev->combL[i], input, outL); - fluid_comb_process(rev->combR[i], input, outR); - } + for(i = 0; i < NBR_DELAYS; i++) + { + /* sets local delay lines's parameters */ + result = set_mod_delay_line(&late->mod_delay_lines[i], + delay_length[i], MOD_DEPTH, MOD_RATE); - /* Feed through allpasses in series */ - for(i = 0; i < numallpasses; i++) + if(result == FLUID_FAILED) { - fluid_allpass_process(rev->allpassL[i], outL); - fluid_allpass_process(rev->allpassR[i], outR); + return FLUID_FAILED; } - /* Remove the DC offset */ - outL -= DC_OFFSET; - outR -= DC_OFFSET; + /* Sets local Modulators parameters: frequency and phase + Each modulateur are shifted of MOD_PHASE degree + */ + set_mod_frequency(&late->mod_delay_lines[i].mod, + MOD_FREQ * MOD_RATE, + sample_rate, + (float)(MOD_PHASE * i)); + } + + /*-----------------------------------------------------------------------*/ + update_stereo_coefficient(late, 1.0f); + return FLUID_OK; +} + +/* + Clears the delay lines. + + @param rev pointer on the reverb. +*/ +static void +fluid_revmodel_init(fluid_revmodel_t *rev) +{ + int i; - /* Calculate output MIXING with anything already there */ - left_out[k] += outL * rev->wet1 + outR * rev->wet2; - right_out[k] += outR * rev->wet1 + outL * rev->wet2; + /* clears all the delay lines */ + for(i = 0; i < NBR_DELAYS; i ++) + { + clear_delay_line(&rev->late.mod_delay_lines[i].dl); } } + +/* + updates internal parameters. + + @param rev pointer on the reverb. +*/ static void fluid_revmodel_update(fluid_revmodel_t *rev) { - /* Recalculate internal values after parameter change */ - int i; + /* Recalculate internal values after parameters change */ /* The stereo amplitude equation (wet1 and wet2 below) have a tendency to produce high amplitude with high width values ( 1 < width < 100). This results in an unwanted noisy output clipped by the audio card. - To avoid this dependency, we divide by (1 + rev->width * scale_wet_width) - Actually, with a scale_wet_width of 0.2, (regardless of level setting), + To avoid this dependency, we divide by (1 + rev->width * SCALE_WET_WIDTH) + Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), the output amplitude (wet) seems rather independent of width setting */ - fluid_real_t wet = (rev->level * scalewet) / - (1.0f + rev->width * scale_wet_width); + fluid_real_t wet = (rev->level * SCALE_WET) / + (1.0f + rev->width * SCALE_WET_WIDTH); /* wet1 and wet2 are used by the stereo effect controled by the width setting for producing a stereo ouptput from a monophonic reverb signal. Please see the note above about a side effect tendency */ + rev->wet1 = wet * (rev->width / 2.0f + 0.5f); rev->wet2 = wet * ((1.0f - rev->width) / 2.0f); - for(i = 0; i < numcombs; i++) + /* integrates wet1 in stereo coefficient (this will save one multiply) */ + update_stereo_coefficient(&rev->late, rev->wet1); + + if(rev->wet1 > 0.0) { - fluid_comb_setfeedback(&rev->combL[i], rev->roomsize); - fluid_comb_setfeedback(&rev->combR[i], rev->roomsize); + rev->wet2 /= rev->wet1; } - for(i = 0; i < numcombs; i++) + /* Reverberation time and damping */ + update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); +} + +/*---------------------------------------------------------------------------- + Reverb API +-----------------------------------------------------------------------------*/ + +/* +* Creates a reverb. +* @param sample_rate sample rate in Hz. +* @return pointer on the new reverb or NULL if memory error. +* Reverb API. +*/ +fluid_revmodel_t * +new_fluid_revmodel(fluid_real_t sample_rate) +{ + fluid_revmodel_t *rev; + rev = FLUID_NEW(fluid_revmodel_t); + + if(rev == NULL) + { + return NULL; + } + + /* create fdn reverb */ + if(create_fluid_rev_late(&rev->late, sample_rate) != FLUID_OK) { - fluid_comb_setdamp(&rev->combL[i], rev->damp); - fluid_comb_setdamp(&rev->combR[i], rev->damp); + delete_fluid_revmodel(rev); + return NULL; } + + return rev; } -/** - * Set one or more reverb parameters. - * @param rev Reverb instance - * @param set One or more flags from #fluid_revmodel_set_t indicating what - * parameters to set (#FLUID_REVMODEL_SET_ALL to set all parameters) - * @param roomsize Reverb room size - * @param damping Reverb damping - * @param width Reverb width - * @param level Reverb level - */ +/* +* free the reverb. +* @param pointer on rev to free. +* Reverb API. +*/ +void +delete_fluid_revmodel(fluid_revmodel_t *rev) +{ + fluid_return_if_fail(rev != NULL); + delete_fluid_rev_late(&rev->late); + FLUID_FREE(rev); +} + +/* +* Sets one or more reverb parameters. +* @param rev Reverb instance. +* @param set One or more flags from #fluid_revmodel_set_t indicating what +* parameters to set (#FLUID_REVMODEL_SET_ALL to set all parameters). +* @param roomsize Reverb room size. +* @param damping Reverb damping. +* @param width Reverb width. +* @param level Reverb level. +* +* Reverb API. +*/ void fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, fluid_real_t damping, fluid_real_t width, fluid_real_t level) { + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_ROOMSIZE) { - /* With upper limit above 1.07, the output amplitude will grow - exponentially. So, keeping this upper limit to 1.0 seems sufficient - as it produces yet a long reverb time */ fluid_clip(roomsize, 0.0f, 1.0f); - rev->roomsize = (roomsize * scaleroom) + offsetroom; + rev->roomsize = roomsize; } + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_DAMPING) { - rev->damp = damping * scaledamp; + fluid_clip(damping, 0.0f, 1.0f); + rev->damp = damping; } + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_WIDTH) { rev->width = width; } + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_LEVEL) { fluid_clip(level, 0.0f, 1.0f); rev->level = level; } + /* updates internal parameters */ fluid_revmodel_update(rev); } +/* +* Applies a sample rate change on the reverb. +* @param rev the reverb. +* @param sample_rate new sample rate value. +* +* Reverb API. +*/ void fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate) { int i; - for(i = 0; i < numcombs; i++) + /* updates modulator frequency according to sample rate change */ + for(i = 0; i < NBR_DELAYS; i++) { - fluid_comb_release(&rev->combL[i]); - fluid_comb_release(&rev->combR[i]); + set_mod_frequency(&rev->late.mod_delay_lines[i].mod, + MOD_FREQ * MOD_RATE, + sample_rate, + (float)(MOD_PHASE * i)); } - for(i = 0; i < numallpasses; i++) + /* updates damping filter coefficients according to sample rate change */ + rev->late.samplerate = sample_rate; + update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); + + /* clears all delay lines */ + fluid_revmodel_init(rev); +} + +/* +* Damps the reverb by clearing the delay lines. +* @param rev the reverb. +* +* Reverb API. +*/ +void +fluid_revmodel_reset(fluid_revmodel_t *rev) +{ + fluid_revmodel_init(rev); +} + +/*----------------------------------------------------------------------------- +* fdn reverb process replace. +* @param rev pointer on reverb. +* @param in monophonic buffer input (FLUID_BUFSIZE sample). +* @param left_out stereo left processed output (FLUID_BUFSIZE sample). +* @param right_out stereo right processed output (FLUID_BUFSIZE sample). +* +* The processed reverb is replacing anything there in out. +* Reverb API. +-----------------------------------------------------------------------------*/ +void +fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k; + + fluid_real_t xn; /* mono input x(n) */ + fluid_real_t out_tone_filter; /* tone corrector output */ + fluid_real_t out_left, out_right; /* output stereo Left and Right */ + fluid_real_t matrix_factor; /* partial matrix computation */ + fluid_real_t delay_out_s; /* sample */ + fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ + + for(k = 0; k < FLUID_BUFSIZE; k++) { - fluid_allpass_release(&rev->allpassL[i]); - fluid_allpass_release(&rev->allpassR[i]); + /* stereo output */ + out_left = out_right = 0; + +#ifdef DENORMALISING + /* Input is adjusted by DC_OFFSET. */ + xn = (in[k]) * FIXED_GAIN + DC_OFFSET; +#else + xn = (in[k]) * FIXED_GAIN; +#endif + + /*-------------------------------------------------------------------- + tone correction. + */ + out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; + rev->late.tone_buffer = xn; + xn = out_tone_filter; + /*-------------------------------------------------------------------- + process feedback delayed network: + - xn is the input signal. + - before inserting in the line input we first we get the delay lines + output, filter them and compute output in delay_out[]. + - also matrix_factor is computed (to simplify further matrix product) + ---------------------------------------------------------------------*/ + /* We begin with the modulated output delay line + damping filter */ + matrix_factor = 0; + + for(i = 0; i < NBR_DELAYS; i++) + { + mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; + /* get current modulated output */ + delay_out_s = get_mod_delay(mdl); + + /* process low pass damping filter + (input:delay_out_s, output:delay_out_s) */ + process_damping_filter(delay_out_s, delay_out_s, mdl); + + /* Result in delay_out[], and matrix_factor. + These wil be use later during input line process */ + delay_out[i] = delay_out_s; /* result in delay_out[] */ + matrix_factor += delay_out_s; /* result in matrix_factor */ + + /* Process stereo output */ + /* stereo left = left + out_left_gain * delay_out */ + out_left += rev->late.out_left_gain[i] * delay_out_s; + /* stereo right= right+ out_right_gain * delay_out */ + out_right += rev->late.out_right_gain[i] * delay_out_s; + } + + /* now we process the input delay line.Each input is a combination of + - xn: input signal + - delay_out[] the output of a delay line given by a permutation matrix P + - and matrix_factor. + This computes: in_delay_line = xn + (delay_out[] * matrix A) with + an algorithm equivalent but faster than using a product with matrix A. + */ + /* matrix_factor = output sum * (-2.0)/N */ + matrix_factor *= FDN_MATRIX_FACTOR; + matrix_factor += xn; /* adds reverb input signal */ + + for(i = 1; i < NBR_DELAYS; i++) + { + /* delay_in[i-1] = delay_out[i] + matrix_factor */ + delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; + push_in_delay_line(dl, delay_out[i] + matrix_factor); + } + + /* last line input (NB_DELAY-1) */ + /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ + { + delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; + push_in_delay_line(dl, delay_out[0] + matrix_factor); + } + + /*-------------------------------------------------------------------*/ +#ifdef DENORMALISING + /* Removes the DC offset */ + out_left -= DC_OFFSET; + out_right -= DC_OFFSET; +#endif + + /* Calculates stereo output REPLACING anything already there: */ + /* + left_out[k] = out_left * rev->wet1 + out_right * rev->wet2; + right_out[k] = out_right * rev->wet1 + out_left * rev->wet2; + + As wet1 is integrated in stereo coefficient wet 1 is now + integrated in out_left and out_right we simplify previous + relation by suppression of one multiply as this: + + left_out[k] = out_left + out_right * rev->wet2; + right_out[k] = out_right + out_left * rev->wet2; + */ + left_out[k] = out_left + out_right * rev->wet2; + right_out[k] = out_right + out_left * rev->wet2; } +} + + +/*----------------------------------------------------------------------------- +* fdn reverb process mix. +* @param rev pointer on reverb. +* @param in monophonic buffer input (FLUID_BUFSIZE samples). +* @param left_out stereo left processed output (FLUID_BUFSIZE samples). +* @param right_out stereo right processed output (FLUID_BUFSIZE samples). +* +* The processed reverb is mixed in out with samples already there in out. +* Reverb API. +-----------------------------------------------------------------------------*/ +void fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k; + + fluid_real_t xn; /* mono input x(n) */ + fluid_real_t out_tone_filter; /* tone corrector output */ + fluid_real_t out_left, out_right; /* output stereo Left and Right */ + fluid_real_t matrix_factor; /* partial matrix term */ + fluid_real_t delay_out_s; /* sample */ + fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ + + for(k = 0; k < FLUID_BUFSIZE; k++) + { + /* stereo output */ + out_left = out_right = 0; +#ifdef DENORMALISING + /* Input is adjusted by DC_OFFSET. */ + xn = (in[k]) * FIXED_GAIN + DC_OFFSET; +#else + xn = (in[k]) * FIXED_GAIN; +#endif + + /*-------------------------------------------------------------------- + tone correction + */ + out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; + rev->late.tone_buffer = xn; + xn = out_tone_filter; + /*-------------------------------------------------------------------- + process feedback delayed network: + - xn is the input signal. + - before inserting in the line input we first we get the delay lines + output, filter them and compute output in local delay_out[]. + - also matrix_factor is computed (to simplify further matrix product). + ---------------------------------------------------------------------*/ + /* We begin with the modulated output delay line + damping filter */ + matrix_factor = 0; + + for(i = 0; i < NBR_DELAYS; i++) + { + mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; + /* get current modulated output */ + delay_out_s = get_mod_delay(mdl); + + /* process low pass damping filter + (input:delay_out_s, output:delay_out_s) */ + process_damping_filter(delay_out_s, delay_out_s, mdl); + + /* Result in delay_out[], and matrix_factor. + These wil be use later during input line process */ + delay_out[i] = delay_out_s; /* result in delay_out[] */ + matrix_factor += delay_out_s; /* result in matrix_factor */ + + /* Process stereo output */ + /* stereo left = left + out_left_gain * delay_out */ + out_left += rev->late.out_left_gain[i] * delay_out_s; + /* stereo right= right+ out_right_gain * delay_out */ + out_right += rev->late.out_right_gain[i] * delay_out_s; + } + + /* now we process the input delay line. Each input is a combination of: + - xn: input signal + - delay_out[] the output of a delay line given by a permutation matrix P + - and matrix_factor. + This computes: in_delay_line = xn + (delay_out[] * matrix A) with + an algorithm equivalent but faster than using a product with matrix A. + */ + /* matrix_factor = output sum * (-2.0)/N */ + matrix_factor *= FDN_MATRIX_FACTOR; + matrix_factor += xn; /* adds reverb input signal */ + + for(i = 1; i < NBR_DELAYS; i++) + { + /* delay_in[i-1] = delay_out[i] + matrix_factor */ + delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; + push_in_delay_line(dl, delay_out[i] + matrix_factor); + } - fluid_set_revmodel_buffers(rev, sample_rate); + /* last line input (NB_DELAY-1) */ + /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ + { + delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; + push_in_delay_line(dl, delay_out[0] + matrix_factor); + } + + /*-------------------------------------------------------------------*/ +#ifdef DENORMALISING + /* Removes the DC offset */ + out_left -= DC_OFFSET; + out_right -= DC_OFFSET; +#endif + /* Calculates stereo output MIXING anything already there: */ + /* + left_out[k] += out_left * rev->wet1 + out_right * rev->wet2; + right_out[k] += out_right * rev->wet1 + out_left * rev->wet2; + + As wet1 is integrated in stereo coefficient wet 1 is now + integrated in out_left and out_right we simplify previous + relation by suppression of one multiply as this: + + left_out[k] += out_left + out_right * rev->wet2; + right_out[k] += out_right + out_left * rev->wet2; + */ + left_out[k] += out_left + out_right * rev->wet2; + right_out[k] += out_right + out_left * rev->wet2; + } } diff --git a/libs/fluidsynth/src/fluid_rvoice.c b/libs/fluidsynth/src/fluid_rvoice.c index f6565bf0b1..6b64c9a979 100644 --- a/libs/fluidsynth/src/fluid_rvoice.c +++ b/libs/fluidsynth/src/fluid_rvoice.c @@ -305,6 +305,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) { int ticks = voice->envlfo.ticks; int count, is_looping; + fluid_real_t modenv_val; /******************* sample sanity check **********/ @@ -361,6 +362,12 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) /******************* phase **********************/ + /* SF2.04 section 8.1.2 #26: + * attack of modEnv is convex ?!? + */ + modenv_val = (fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) + ? fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)) + : fluid_adsr_env_get_val(&voice->envlfo.modenv); /* Calculate the number of samples, that the DSP loop advances * through the original waveform with each step in the output * buffer. It is the ratio between the frequencies of original @@ -369,7 +376,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) voice->dsp.pitchoffset + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch - + fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_pitch) + + modenv_val * voice->envlfo.modenv_to_pitch) / voice->dsp.root_pitch_hz; /******************* portamento ****************/ @@ -455,7 +462,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) fluid_iir_filter_calc(&voice->resonant_filter, voice->dsp.output_rate, fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + - fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_fc); + modenv_val * voice->envlfo.modenv_to_fc); fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); @@ -585,7 +592,7 @@ fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks) /* A voice is turned off during the attack section of the volume * envelope. The attack section ramps up linearly with * amplitude. The other sections use logarithmic scaling. Calculate new - * volenv_val to achieve equievalent amplitude during the release phase + * volenv_val to achieve equivalent amplitude during the release phase * for seamless volume transition. */ if(fluid_adsr_env_get_val(&voice->envlfo.volenv) > 0) @@ -598,6 +605,24 @@ fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks) } } + if(fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) + { + /* A voice is turned off during the attack section of the modulation + * envelope. The attack section use convex scaling with pitch and filter + * frequency cutoff (see fluid_rvoice_write(): modenv_val = fluid_convex(127 * modenv.val) + * The other sections use linear scaling: modenv_val = modenv.val + * + * Calculate new modenv.val to achieve equivalent modenv_val during the release phase + * for seamless pitch and filter frequency cutoff transition. + */ + if(fluid_adsr_env_get_val(&voice->envlfo.modenv) > 0) + { + fluid_real_t env_value = fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)); + fluid_clip(env_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.modenv, env_value); + } + } + fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVRELEASE); fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVRELEASE); } @@ -650,11 +675,12 @@ static FLUID_INLINE void fluid_rvoice_local_retrigger_attack(fluid_rvoice_t *voi DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack) { fluid_rvoice_t *voice = obj; - int section = fluid_adsr_env_get_section(&voice->envlfo.volenv); + int section; /* volume or modulation section */ /*------------------------------------------------------------------------- Section skip for volume envelope --------------------------------------------------------------------------*/ + section = fluid_adsr_env_get_section(&voice->envlfo.volenv); if(section >= FLUID_VOICE_ENVHOLD) { /* DECAY, SUSTAIN,RELEASE section use logarithmic scaling. Calculates new @@ -672,16 +698,30 @@ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack) /* skips to Attack section from any section */ /* Update vol and attack data */ fluid_rvoice_local_retrigger_attack(voice); + /*------------------------------------------------------------------------- Section skip for modulation envelope --------------------------------------------------------------------------*/ + section = fluid_adsr_env_get_section(&voice->envlfo.modenv); + if(section >= FLUID_VOICE_ENVHOLD) + { + /* DECAY, SUSTAIN,RELEASE section use linear scaling. + Since v 2.1 , as recommended by soundfont 2.01/2.4 spec, ATTACK section + uses convex shape (see fluid_rvoice_write() - fluid_convex()). + Calculate new modenv value (new_value) for seamless attack transition. + Here we need the inverse of fluid_convex() function defined as: + new_value = pow(10, (1 - current_val) . FLUID_PEAK_ATTENUATION / -200 . 2.0) + For performance reason we use fluid_cb2amp(Val) = pow(10, val/-200) with + val = (1 – current_val) . FLUID_PEAK_ATTENUATION / 2.0 + */ + fluid_real_t new_value; /* new modenv value */ + new_value = fluid_cb2amp((1.0f - fluid_adsr_env_get_val(&voice->envlfo.modenv)) + * FLUID_PEAK_ATTENUATION / 2.0); + fluid_clip(new_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.modenv, new_value); + } /* Skips from any section to ATTACK section */ fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVATTACK); - /* Actually (v 1.1.6) all sections are linear, so there is no need to - correct val value. However soundfont 2.01/2.4 spec. says that Attack should - be convex (see issue #153 from Christian Collins). In the case Attack - section would be changed to a non linear shape it will be necessary to do - a correction for seamless val transition. Here is the place to do this */ } /** diff --git a/libs/fluidsynth/src/fluid_samplecache.c b/libs/fluidsynth/src/fluid_samplecache.c index 773e19feea..036964f584 100644 --- a/libs/fluidsynth/src/fluid_samplecache.c +++ b/libs/fluidsynth/src/fluid_samplecache.c @@ -60,8 +60,10 @@ struct _fluid_samplecache_entry_t static fluid_list_t *samplecache_list = NULL; static fluid_mutex_t samplecache_mutex = FLUID_MUTEX_INIT; -static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type); -static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type); +static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, + unsigned int sample_end, int sample_type, time_t mtime); +static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, + unsigned int sample_end, int sample_type, time_t mtime); static void delete_samplecache_entry(fluid_samplecache_entry_t *entry); static int fluid_get_file_modification_time(char *filename, time_t *modification_time); @@ -75,14 +77,20 @@ int fluid_samplecache_load(SFData *sf, { fluid_samplecache_entry_t *entry; int ret; + time_t mtime; fluid_mutex_lock(samplecache_mutex); - entry = get_samplecache_entry(sf, sample_start, sample_end, sample_type); + if(fluid_get_file_modification_time(sf->fname, &mtime) == FLUID_FAILED) + { + mtime = 0; + } + + entry = get_samplecache_entry(sf, sample_start, sample_end, sample_type, mtime); if(entry == NULL) { - entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type); + entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type, mtime); if(entry == NULL) { @@ -180,7 +188,8 @@ unlock_exit: static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, - int sample_type) + int sample_type, + time_t mtime) { fluid_samplecache_entry_t *entry; @@ -202,12 +211,6 @@ static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, goto error_exit; } - if(fluid_get_file_modification_time(entry->filename, &entry->modification_time) == FLUID_FAILED) - { - FLUID_LOG(FLUID_WARN, "Unable to read modificaton time of soundfont file."); - entry->modification_time = 0; - } - entry->sf_samplepos = sf->samplepos; entry->sf_samplesize = sf->samplesize; entry->sf_sample24pos = sf->sample24pos; @@ -215,6 +218,7 @@ static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, entry->sample_start = sample_start; entry->sample_end = sample_end; entry->sample_type = sample_type; + entry->modification_time = mtime; entry->sample_count = fluid_sffile_read_sample_data(sf, sample_start, sample_end, sample_type, &entry->sample_data, &entry->sample_data24); @@ -244,18 +248,12 @@ static void delete_samplecache_entry(fluid_samplecache_entry_t *entry) static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, - int sample_type) + int sample_type, + time_t mtime) { - time_t mtime; fluid_list_t *entry_list; fluid_samplecache_entry_t *entry; - if(fluid_get_file_modification_time(sf->fname, &mtime) == FLUID_FAILED) - { - FLUID_LOG(FLUID_WARN, "Unable to read modificaton time of soundfont file."); - mtime = 0; - } - entry_list = samplecache_list; while(entry_list) diff --git a/libs/fluidsynth/src/fluid_sffile.c b/libs/fluidsynth/src/fluid_sffile.c index 40055e8189..302eea72a7 100644 --- a/libs/fluidsynth/src/fluid_sffile.c +++ b/libs/fluidsynth/src/fluid_sffile.c @@ -34,75 +34,79 @@ Borrowed from Smurf SoundFont Editor by Josh Green =================================================================*/ -/* - functions for loading data from sfont files, with appropriate byte swapping - on big endian machines. Sfont IDs are not swapped because the ID read is - equivalent to the matching ID list in memory regardless of LE/BE machine -*/ - -/* sf file chunk IDs */ -enum -{ - UNKN_ID, - RIFF_ID, - LIST_ID, - SFBK_ID, - INFO_ID, - SDTA_ID, - PDTA_ID, /* info/sample/preset */ - - IFIL_ID, - ISNG_ID, - INAM_ID, - IROM_ID, /* info ids (1st byte of info strings) */ - IVER_ID, - ICRD_ID, - IENG_ID, - IPRD_ID, /* more info ids */ - ICOP_ID, - ICMT_ID, - ISFT_ID, /* and yet more info ids */ - - SNAM_ID, - SMPL_ID, /* sample ids */ - PHDR_ID, - PBAG_ID, - PMOD_ID, - PGEN_ID, /* preset ids */ - IHDR_ID, - IBAG_ID, - IMOD_ID, - IGEN_ID, /* instrument ids */ - SHDR_ID, /* sample info */ - SM24_ID -}; +/* FOURCC definitions */ +#define RIFF_FCC FLUID_FOURCC('R','I','F','F') +#define LIST_FCC FLUID_FOURCC('L','I','S','T') +#define SFBK_FCC FLUID_FOURCC('s','f','b','k') +#define INFO_FCC FLUID_FOURCC('I','N','F','O') +#define SDTA_FCC FLUID_FOURCC('s','d','t','a') +#define PDTA_FCC FLUID_FOURCC('p','d','t','a') /* info/sample/preset */ + +#define IFIL_FCC FLUID_FOURCC('i','f','i','l') +#define ISNG_FCC FLUID_FOURCC('i','s','n','g') +#define INAM_FCC FLUID_FOURCC('I','N','A','M') +#define IROM_FCC FLUID_FOURCC('i','r','o','m') /* info ids (1st byte of info strings) */ +#define IVER_FCC FLUID_FOURCC('i','v','e','r') +#define ICRD_FCC FLUID_FOURCC('I','C','R','D') +#define IENG_FCC FLUID_FOURCC('I','E','N','G') +#define IPRD_FCC FLUID_FOURCC('I','P','R','D') /* more info ids */ +#define ICOP_FCC FLUID_FOURCC('I','C','O','P') +#define ICMT_FCC FLUID_FOURCC('I','C','M','T') +#define ISFT_FCC FLUID_FOURCC('I','S','F','T') /* and yet more info ids */ + +#define SNAM_FCC FLUID_FOURCC('s','n','a','m') +#define SMPL_FCC FLUID_FOURCC('s','m','p','l') /* sample ids */ +#define PHDR_FCC FLUID_FOURCC('p','h','d','r') +#define PBAG_FCC FLUID_FOURCC('p','b','a','g') +#define PMOD_FCC FLUID_FOURCC('p','m','o','d') +#define PGEN_FCC FLUID_FOURCC('p','g','e','n') /* preset ids */ +#define IHDR_FCC FLUID_FOURCC('i','n','s','t') +#define IBAG_FCC FLUID_FOURCC('i','b','a','g') +#define IMOD_FCC FLUID_FOURCC('i','m','o','d') +#define IGEN_FCC FLUID_FOURCC('i','g','e','n') /* instrument ids */ +#define SHDR_FCC FLUID_FOURCC('s','h','d','r') /* sample info */ +#define SM24_FCC FLUID_FOURCC('s','m','2','4') + +/* Set when the FCC code is unknown */ +#define UNKN_ID FLUID_N_ELEMENTS(idlist) /* - * This declares a char array containing the SF2 chunk identifiers. This - * array is being accessed like an uint32 below to simplify id comparison. - * To make sure it is suitably aligned for uint32 access, we must wrap it - * inside a union along with a uint32 telling the compiler to align it - * for integer access and avoiding undefined behaviour. - * This basically is the C89 equivalent to what is written in C11 as: - * alignas(uint32_t) static const char idlist[] = {}; - * - * See: EXP36-C. Do not cast pointers into more strictly aligned pointer - * types - SEI CERT C Coding Standard + * This declares a uint32_t array containing the SF2 chunk identifiers. */ -static const union fluid_idlist +static const uint32_t idlist[] = { - /* - * Cannot be char c[ ], because in C89, arrays wraped in unions - * must have a fixed size. Otherwise the size of the union would depend - * on the initialization of its first member, which results in - * different sizes for different instances of the same union type. - */ - char c[116]; - uint32_t i; -} idlist = {"RIFFLISTsfbkINFOsdtapdtaifilisngINAMiromiverICRDIENGIPRD" - "ICOPICMTISFTsnamsmplphdrpbagpmodpgeninstibagimodigenshdrsm24" - }; - + RIFF_FCC, + LIST_FCC, + SFBK_FCC, + INFO_FCC, + SDTA_FCC, + PDTA_FCC, + + IFIL_FCC, + ISNG_FCC, + INAM_FCC, + IROM_FCC, + IVER_FCC, + ICRD_FCC, + IENG_FCC, + IPRD_FCC, + ICOP_FCC, + ICMT_FCC, + ISFT_FCC, + + SNAM_FCC, + SMPL_FCC, + PHDR_FCC, + PBAG_FCC, + PMOD_FCC, + PGEN_FCC, + IHDR_FCC, + IBAG_FCC, + IMOD_FCC, + IGEN_FCC, + SHDR_FCC, + SM24_FCC +}; /* generator types */ typedef enum @@ -205,8 +209,6 @@ static const unsigned short invalid_preset_gen[] = }; -#define CHNKIDSTR(id) &idlist.c[(id - 1) * 4] - /* sfont file chunk sizes */ #define SF_PHDR_SIZE (38) #define SF_BAG_SIZE (4) @@ -322,6 +324,56 @@ static void delete_zone(SFZone *zone); static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data); static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24); +/** + * Check if a file is a SoundFont file. + * @param filename Path to the file to check + * @return TRUE if it could be a SoundFont, FALSE otherwise + * + * @note The current implementation only checks for the "RIFF" and "sfbk" headers in + * the file. It is useful to distinguish between SoundFont and other (e.g. MIDI) files. + */ +int fluid_is_soundfont(const char *filename) +{ + FILE *fp = FLUID_FOPEN(filename, "rb"); + uint32_t fcc; + int retcode = FALSE; + + do + { + if(fp == NULL) + { + return retcode; + } + + if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) + { + break; + } + + if(fcc != RIFF_FCC) + { + break; + } + + if(FLUID_FSEEK(fp, 4, SEEK_CUR)) + { + break; + } + + if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) + { + break; + } + + retcode = (fcc == SFBK_FCC); + } + while(0); + + FLUID_FCLOSE(fp); + + return retcode; +} + /* * Open a SoundFont file and parse it's contents into a SFData structure. * @@ -511,17 +563,17 @@ void fluid_sffile_close(SFData *sf) static int chunkid(uint32_t id) { unsigned int i; - const uint32_t *p = &idlist.i; - for(i = 0; i < sizeof(idlist) / sizeof(idlist.i); i++, p += 1) + for(i = 0; i < FLUID_N_ELEMENTS(idlist); i++) { - if(*p == id) + if(idlist[i] == id) { - return (i + 1); + break; } } - return UNKN_ID; + /* Return chunk id or UNKN_ID if not found */ + return i; } static int load_header(SFData *sf) @@ -530,7 +582,7 @@ static int load_header(SFData *sf) READCHUNK(sf, &chunk); /* load RIFF chunk */ - if(chunkid(chunk.id) != RIFF_ID) + if(chunk.id != RIFF_FCC) { /* error if not RIFF */ FLUID_LOG(FLUID_ERR, "Not a RIFF file"); @@ -539,7 +591,7 @@ static int load_header(SFData *sf) READID(sf, &chunk.id); /* load file ID */ - if(chunkid(chunk.id) != SFBK_ID) + if(chunk.id != SFBK_FCC) { /* error if not SFBK_ID */ FLUID_LOG(FLUID_ERR, "Not a SoundFont file"); @@ -558,7 +610,7 @@ static int load_header(SFData *sf) return FALSE; } - if(chunkid(chunk.id) != INFO_ID) + if(chunk.id != INFO_FCC) { FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting INFO chunk"); return FALSE; @@ -575,7 +627,7 @@ static int load_header(SFData *sf) return FALSE; } - if(chunkid(chunk.id) != SDTA_ID) + if(chunk.id != SDTA_FCC) { FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting SAMPLE chunk"); return FALSE; @@ -592,7 +644,7 @@ static int load_header(SFData *sf) return FALSE; } - if(chunkid(chunk.id) != PDTA_ID) + if(chunk.id != PDTA_FCC) { FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting HYDRA chunk"); return FALSE; @@ -637,7 +689,7 @@ static int read_listchunk(SFData *sf, SFChunk *chunk) { READCHUNK(sf, chunk); /* read list chunk */ - if(chunkid(chunk->id) != LIST_ID) /* error if ! list chunk */ + if(chunk->id != LIST_FCC) /* error if ! list chunk */ { FLUID_LOG(FLUID_ERR, "Invalid chunk id in level 0 parse"); return FALSE; @@ -651,8 +703,11 @@ static int read_listchunk(SFData *sf, SFChunk *chunk) static int process_info(SFData *sf, int size) { SFChunk chunk; - unsigned char id; - char *item; + union + { + char *chr; + uint32_t *fcc; + } item; unsigned short ver; while(size > 0) @@ -660,9 +715,7 @@ static int process_info(SFData *sf, int size) READCHUNK(sf, &chunk); size -= 8; - id = chunkid(chunk.id); - - if(id == IFIL_ID) + if(chunk.id == IFIL_FCC) { /* sound font version chunk? */ if(chunk.size != 4) @@ -703,7 +756,7 @@ static int process_info(SFData *sf, int size) return FALSE; } } - else if(id == IVER_ID) + else if(chunk.id == IVER_FCC) { /* ROM version chunk? */ if(chunk.size != 4) @@ -717,34 +770,35 @@ static int process_info(SFData *sf, int size) READW(sf, ver); sf->romver.minor = ver; } - else if(id != UNKN_ID) + else if(chunkid(chunk.id) != UNKN_ID) { - if((id != ICMT_ID && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2)) + if((chunk.id != ICMT_FCC && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2)) { FLUID_LOG(FLUID_ERR, "INFO sub chunk %.4s has invalid chunk size of %d bytes", &chunk.id, chunk.size); return FALSE; } - /* alloc for chunk id and da chunk */ - if(!(item = FLUID_MALLOC(chunk.size + 1))) + /* alloc for chunk fcc and da chunk */ + if(!(item.fcc = FLUID_MALLOC(chunk.size + sizeof(uint32_t) + 1))) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } /* attach to INFO list, fluid_sffile_close will cleanup if FAIL occurs */ - sf->info = fluid_list_append(sf->info, item); + sf->info = fluid_list_append(sf->info, item.fcc); - *(unsigned char *)item = id; + /* save chunk fcc and update pointer to data value */ + *item.fcc++ = chunk.id; - if(sf->fcbs->fread(&item[1], chunk.size, sf->sffd) == FLUID_FAILED) + if(sf->fcbs->fread(item.chr, chunk.size, sf->sffd) == FLUID_FAILED) { return FALSE; } - /* force terminate info item (don't forget uint8 info ID) */ - *(item + chunk.size) = '\0'; + /* force terminate info item */ + item.chr[chunk.size] = '\0'; } else { @@ -777,7 +831,7 @@ static int process_sdta(SFData *sf, unsigned int size) READCHUNK(sf, &chunk); size -= 8; - if(chunkid(chunk.id) != SMPL_ID) + if(chunk.id != SMPL_FCC) { FLUID_LOG(FLUID_ERR, "Expected SMPL chunk found invalid id instead"); return FALSE; @@ -810,7 +864,7 @@ static int process_sdta(SFData *sf, unsigned int size) READCHUNK(sf, &chunk); size -= 8; - if(chunkid(chunk.id) == SM24_ID) + if(chunk.id == SM24_FCC) { int sm24size, sdtahalfsize; @@ -850,29 +904,24 @@ ret: static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size) { - unsigned int id; - const char *expstr; - - expstr = CHNKIDSTR(expid); /* in case we need it */ - READCHUNK(sf, chunk); *size -= 8; - if((id = chunkid(chunk->id)) != expid) + if(chunk->id != expid) { - FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", expstr); + FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", &expid); return FALSE; } if(chunk->size % reclen) /* valid chunk size? */ { - FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", expstr, reclen); + FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", &expid, reclen); return FALSE; } if((*size -= chunk->size) < 0) { - FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", expstr); + FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", &expid); return FALSE; } @@ -883,7 +932,7 @@ static int process_pdta(SFData *sf, int size) { SFChunk chunk; - if(!pdtahelper(sf, PHDR_ID, SF_PHDR_SIZE, &chunk, &size)) + if(!pdtahelper(sf, PHDR_FCC, SF_PHDR_SIZE, &chunk, &size)) { return FALSE; } @@ -893,7 +942,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, PBAG_ID, SF_BAG_SIZE, &chunk, &size)) + if(!pdtahelper(sf, PBAG_FCC, SF_BAG_SIZE, &chunk, &size)) { return FALSE; } @@ -903,7 +952,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, PMOD_ID, SF_MOD_SIZE, &chunk, &size)) + if(!pdtahelper(sf, PMOD_FCC, SF_MOD_SIZE, &chunk, &size)) { return FALSE; } @@ -913,7 +962,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, PGEN_ID, SF_GEN_SIZE, &chunk, &size)) + if(!pdtahelper(sf, PGEN_FCC, SF_GEN_SIZE, &chunk, &size)) { return FALSE; } @@ -923,7 +972,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, IHDR_ID, SF_IHDR_SIZE, &chunk, &size)) + if(!pdtahelper(sf, IHDR_FCC, SF_IHDR_SIZE, &chunk, &size)) { return FALSE; } @@ -933,7 +982,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, IBAG_ID, SF_BAG_SIZE, &chunk, &size)) + if(!pdtahelper(sf, IBAG_FCC, SF_BAG_SIZE, &chunk, &size)) { return FALSE; } @@ -943,7 +992,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, IMOD_ID, SF_MOD_SIZE, &chunk, &size)) + if(!pdtahelper(sf, IMOD_FCC, SF_MOD_SIZE, &chunk, &size)) { return FALSE; } @@ -953,7 +1002,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, IGEN_ID, SF_GEN_SIZE, &chunk, &size)) + if(!pdtahelper(sf, IGEN_FCC, SF_GEN_SIZE, &chunk, &size)) { return FALSE; } @@ -963,7 +1012,7 @@ static int process_pdta(SFData *sf, int size) return FALSE; } - if(!pdtahelper(sf, SHDR_ID, SF_SHDR_SIZE, &chunk, &size)) + if(!pdtahelper(sf, SHDR_FCC, SF_SHDR_SIZE, &chunk, &size)) { return FALSE; } diff --git a/libs/fluidsynth/src/fluid_sfont.c b/libs/fluidsynth/src/fluid_sfont.c index 405bec6c64..548a80abce 100644 --- a/libs/fluidsynth/src/fluid_sfont.c +++ b/libs/fluidsynth/src/fluid_sfont.c @@ -512,9 +512,13 @@ delete_fluid_sample(fluid_sample_t *sample) /** * Returns the size of the fluid_sample_t structure. * - * Useful in low latency scenarios e.g. to allocate a sample on the stack. + * Useful in low latency scenarios e.g. to allocate a pool of samples. * * @return Size of fluid_sample_t in bytes + * + * @note It is recommend to zero initialize the memory before using the object. + * + * @warning Do NOT allocate samples on the stack and assign them to a voice! */ size_t fluid_sample_sizeof() { @@ -563,16 +567,17 @@ fluid_sample_set_sound_data(fluid_sample_t *sample, fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); fluid_return_val_if_fail(data != NULL, FLUID_FAILED); - fluid_return_val_if_fail(nbframes == 0, FLUID_FAILED); + fluid_return_val_if_fail(nbframes != 0, FLUID_FAILED); /* in case we already have some data */ if((sample->data != NULL || sample->data24 != NULL) && sample->auto_free) { FLUID_FREE(sample->data); FLUID_FREE(sample->data24); - sample->data = NULL; - sample->data24 = NULL; } + + sample->data = NULL; + sample->data24 = NULL; if(copy_data) { @@ -635,6 +640,8 @@ error_rec: FLUID_LOG(FLUID_ERR, "Out of memory"); FLUID_FREE(sample->data); FLUID_FREE(sample->data24); + sample->data = NULL; + sample->data24 = NULL; return FLUID_FAILED; #undef SAMPLE_LOOP_MARGIN diff --git a/libs/fluidsynth/src/fluid_sfont.h b/libs/fluidsynth/src/fluid_sfont.h index 24773353df..28609e96aa 100644 --- a/libs/fluidsynth/src/fluid_sfont.h +++ b/libs/fluidsynth/src/fluid_sfont.h @@ -183,8 +183,6 @@ struct _fluid_sample_t * @return Should return #FLUID_OK */ int (*notify)(fluid_sample_t *sample, int reason); - - void *userdata; /**< User defined data */ }; diff --git a/libs/fluidsynth/src/fluid_synth.c b/libs/fluidsynth/src/fluid_synth.c index 79c5a9c87c..b4524a2acf 100644 --- a/libs/fluidsynth/src/fluid_synth.c +++ b/libs/fluidsynth/src/fluid_synth.c @@ -227,7 +227,7 @@ void fluid_synth_settings(fluid_settings_t *settings) #else fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 1, 0); #endif - + fluid_settings_register_int(settings, "synth.min-note-length", 10, 0, 65535, 0); fluid_settings_register_int(settings, "synth.threadsafe-api", 1, 0, 1, FLUID_HINT_TOGGLED); @@ -1331,6 +1331,13 @@ fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mo fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(mod != NULL, FLUID_FAILED); + + /* Checks if modulators sources are valid */ + if(!fluid_mod_check_sources(mod, "api fluid_synth_add_default_mod mod")) + { + return FLUID_FAILED; + } + fluid_synth_api_enter(synth); default_mod = synth->default_mod; @@ -1902,7 +1909,7 @@ fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, int len, int bank = 0, prog, channels; double tunedata[128]; int keys[128]; - char name[17]; + char name[17]={0}; int note, frac, frac2; uint8_t chksum; int i, count, index; @@ -1973,7 +1980,8 @@ fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, int len, } *resptr++ = prog; - FLUID_STRNCPY(resptr, name, 16); + /* copy 16 ASCII characters (potentially not null terminated) to the sysex buffer */ + FLUID_MEMCPY(resptr, name, 16); resptr += 16; for(i = 0; i < 128; i++) @@ -3368,7 +3376,7 @@ fluid_synth_nwrite_float(fluid_synth_t *synth, int len, fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(left != NULL, FLUID_FAILED); fluid_return_val_if_fail(right != NULL, FLUID_FAILED); - + /* First, take what's still available in the buffer */ count = 0; num = synth->cur; @@ -3521,7 +3529,7 @@ fluid_synth_nwrite_float(fluid_synth_t *synth, int len, /** * mixes the samples of \p in to \p out - * + * * @param out the output sample buffer to mix to * @param ooff sample offset in \p out * @param in the rvoice_mixer input sample buffer to mix from @@ -3539,6 +3547,7 @@ static FLUID_INLINE void fluid_synth_mix_single_buffer(float *FLUID_RESTRICT out if(out != NULL) { int j; + for(j = 0; j < num; j++) { out[j + ooff] += (float) in[buf_idx * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + ioff]; @@ -3687,7 +3696,7 @@ fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], for(i = 0; i < nfxchan; i++) { int buf_idx = f * nfxchan + i; - + float *out_buf = fx[(buf_idx * 2) % nfx]; fluid_synth_mix_single_buffer(out_buf, 0, fx_left_in, synth->cur, buf_idx, num); @@ -3730,10 +3739,10 @@ fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], for(i = 0; i < nfxchan; i++) { int buf_idx = f * nfxchan + i; - + float *out_buf = fx[(buf_idx * 2) % nfx]; fluid_synth_mix_single_buffer(out_buf, count, fx_left_in, 0, buf_idx, num); - + out_buf = fx[(buf_idx * 2 + 1) % nfx]; fluid_synth_mix_single_buffer(out_buf, count, fx_right_in, 0, buf_idx, num); } @@ -3784,7 +3793,7 @@ fluid_synth_write_float(fluid_synth_t *synth, int len, float cpu_load; fluid_profile_ref_var(prof_ref); - + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(lout != NULL, FLUID_FAILED); fluid_return_val_if_fail(rout != NULL, FLUID_FAILED); @@ -3853,10 +3862,12 @@ static FLUID_INLINE int16_t round_clip_to_i16(float x) { long i; + if(x >= 0.0f) { i = (long)(x + 0.5f); - if (FLUID_UNLIKELY(i > 32767)) + + if(FLUID_UNLIKELY(i > 32767)) { i = 32767; } @@ -3864,12 +3875,13 @@ round_clip_to_i16(float x) else { i = (long)(x - 0.5f); - if (FLUID_UNLIKELY(i < -32768)) + + if(FLUID_UNLIKELY(i < -32768)) { i = -32768; } } - + return (int16_t)i; } @@ -4347,11 +4359,11 @@ fluid_synth_alloc_voice_LOCAL(fluid_synth_t *synth, fluid_sample_t *sample, int ) { // Replacement of default_vel2att modulator by custom_breath2att_modulator - fluid_voice_add_mod(voice, &custom_breath2att_mod, FLUID_VOICE_DEFAULT); + fluid_voice_add_mod_local(voice, &custom_breath2att_mod, FLUID_VOICE_DEFAULT, 0); } else { - fluid_voice_add_mod(voice, default_mod, FLUID_VOICE_DEFAULT); + fluid_voice_add_mod_local(voice, default_mod, FLUID_VOICE_DEFAULT, 0); } // Next default modulator to add to the voice diff --git a/libs/fluidsynth/src/fluid_sys.c b/libs/fluidsynth/src/fluid_sys.c index cce778b3c4..28911ee7fc 100644 --- a/libs/fluidsynth/src/fluid_sys.c +++ b/libs/fluidsynth/src/fluid_sys.c @@ -276,71 +276,6 @@ fluid_error() } /** - * Check if a file is a MIDI file. - * @param filename Path to the file to check - * @return TRUE if it could be a MIDI file, FALSE otherwise - * - * The current implementation only checks for the "MThd" header in the file. - * It is useful only to distinguish between SoundFont and MIDI files. - */ -int -fluid_is_midifile(const char *filename) -{ - FILE *fp = fopen(filename, "rb"); - char id[4]; - - if(fp == NULL) - { - return 0; - } - - if(fread((void *) id, 1, 4, fp) != 4) - { - fclose(fp); - return 0; - } - - fclose(fp); - - return FLUID_STRNCMP(id, "MThd", 4) == 0; -} - -/** - * Check if a file is a SoundFont file. - * @param filename Path to the file to check - * @return TRUE if it could be a SoundFont, FALSE otherwise - * - * @note The current implementation only checks for the "RIFF" and "sfbk" headers in - * the file. It is useful to distinguish between SoundFont and other (e.g. MIDI) files. - */ -int -fluid_is_soundfont(const char *filename) -{ - FILE *fp = fopen(filename, "rb"); - char riff_id[4], sfbk_id[4]; - - if(fp == NULL) - { - return 0; - } - - if((fread((void *) riff_id, 1, sizeof(riff_id), fp) != sizeof(riff_id)) || - (fseek(fp, 4, SEEK_CUR) != 0) || - (fread((void *) sfbk_id, 1, sizeof(sfbk_id), fp) != sizeof(sfbk_id))) - { - goto error_rec; - } - - fclose(fp); - return (FLUID_STRNCMP(riff_id, "RIFF", sizeof(riff_id)) == 0) && - (FLUID_STRNCMP(sfbk_id, "sfbk", sizeof(sfbk_id)) == 0); - -error_rec: - fclose(fp); - return 0; -} - -/** * Suspend the execution of the current thread for the specified amount of time. * @param milliseconds to wait. */ @@ -1280,7 +1215,11 @@ fluid_istream_readline(fluid_istream_t in, fluid_ostream_t out, char *prompt, FLUID_SNPRINTF(buf, len, "%s", line); buf[len - 1] = 0; - add_history(buf); + + if(buf[0] != '\0') + { + add_history(buf); + } free(line); return 1; diff --git a/libs/fluidsynth/src/fluid_sys.h b/libs/fluidsynth/src/fluid_sys.h index d95f6159f2..1229386550 100644 --- a/libs/fluidsynth/src/fluid_sys.h +++ b/libs/fluidsynth/src/fluid_sys.h @@ -77,6 +77,14 @@ #define FLUID_LE32TOH(x) GINT32_FROM_LE(x) #define FLUID_LE16TOH(x) GINT16_FROM_LE(x) +#if FLUID_IS_BIG_ENDIAN +#define FLUID_FOURCC(_a, _b, _c, _d) \ + (uint32_t)(((uint32_t)(_a) << 24) | ((uint32_t)(_b) << 16) | ((uint32_t)(_c) << 8) | (uint32_t)(_d)) +#else +#define FLUID_FOURCC(_a, _b, _c, _d) \ + (uint32_t)(((uint32_t)(_d) << 24) | ((uint32_t)(_c) << 16) | ((uint32_t)(_b) << 8) | (uint32_t)(_a)) +#endif + #define fluid_return_if_fail(cond) \ if(cond) \ diff --git a/libs/fluidsynth/src/fluid_voice.c b/libs/fluidsynth/src/fluid_voice.c index 2f146ba02d..7c6265745b 100644 --- a/libs/fluidsynth/src/fluid_voice.c +++ b/libs/fluidsynth/src/fluid_voice.c @@ -516,7 +516,6 @@ fluid_voice_calculate_gen_pitch(fluid_voice_t *voice) voice->gen[GEN_PITCH].val = fluid_voice_calculate_pitch(voice, fluid_voice_get_actual_key(voice)); } - /* * fluid_voice_calculate_runtime_synthesis_parameters * @@ -1153,7 +1152,9 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) * Recalculate voice parameters for a given control. * @param voice the synthesis voice * @param cc flag to distinguish between a continous control and a channel control (pitch bend, ...) - * @param ctrl the control number + * @param ctrl the control number: + * when >=0, only modulators's destination having ctrl as source are updated. + * when -1, all modulators's destination are updated (regardless of ctrl). * * In this implementation, I want to make sure that all controllers * are event based: the parameter values of the DSP algorithm should @@ -1161,57 +1162,83 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) * iteration of the audio cycle (which would probably be feasible if * the synth was made in silicon). * - * The update is done in three steps: - * - * - first, we look for all the modulators that have the changed - * controller as a source. This will yield a list of generators that - * will be changed because of the controller event. + * The update is done in two steps: * - * - For every changed generator, calculate its new value. This is the - * sum of its original value plus the values of al the attached - * modulators. + * - step 1: first, we look for all the modulators that have the changed + * controller as a source. This will yield a generator that will be changed + * because of the controller event. * - * - For every changed generator, convert its value to the correct - * unit of the corresponding DSP parameter + * - step 2: For this generator, calculate its new value. This is the + * sum of its original value plus the values of all the attached modulators. + * The generator flag is set to indicate the parameters must be updated. + * This avoid the risk to call 'fluid_voice_update_param' several + * times for the same generator if several modulators have that generator as + * destination. So every changed generators are updated only once. + */ + + /* bit table for each generator being updated. The bits are packed in variables + Each variable have NBR_BIT_BY_VAR bits represented by NBR_BIT_BY_VAR_LN2. + The size of the table is the number of variables: SIZE_UPDATED_GEN_BIT. + + Note: In this implementation NBR_BIT_BY_VAR_LN2 is set to 5 (convenient for 32 bits cpu) + but this could be set to 6 for 64 bits cpu. */ + +#define NBR_BIT_BY_VAR_LN2 5 /* for 32 bits variables */ +#define NBR_BIT_BY_VAR (1 << NBR_BIT_BY_VAR_LN2) +#define NBR_BIT_BY_VAR_ANDMASK (NBR_BIT_BY_VAR - 1) +#define SIZE_UPDATED_GEN_BIT ((GEN_LAST + NBR_BIT_BY_VAR_ANDMASK) / NBR_BIT_BY_VAR) + +#define is_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] & (1 << (gen & NBR_BIT_BY_VAR_ANDMASK))) +#define set_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] |= (1 << (gen & NBR_BIT_BY_VAR_ANDMASK))) + int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl) { int i, k; fluid_mod_t *mod; - int gen; + uint32_t gen; fluid_real_t modval; + /* Clears registered bits table of updated generators */ + uint32_t updated_gen_bit[SIZE_UPDATED_GEN_BIT] = {0}; + /* printf("Chan=%d, CC=%d, Src=%d, Val=%d\n", voice->channel->channum, cc, ctrl, val); */ for(i = 0; i < voice->mod_count; i++) { - mod = &voice->mod[i]; /* step 1: find all the modulators that have the changed controller - * as input source. */ - if(fluid_mod_has_source(mod, cc, ctrl)) + as input source. When ctrl is -1 all modulators destination + are updated */ + if(ctrl < 0 || fluid_mod_has_source(mod, cc, ctrl)) { - gen = fluid_mod_get_dest(mod); - modval = 0.0; - /* step 2: for every changed modulator, calculate the modulation - * value of its associated generator */ - for(k = 0; k < voice->mod_count; k++) + /* Skip if this generator has already been updated */ + if(!is_gen_updated(updated_gen_bit, gen)) { - if(fluid_mod_has_dest(&voice->mod[k], gen)) + modval = 0.0; + + /* step 2: for every attached modulator, calculate the modulation + * value for the generator gen */ + for(k = 0; k < voice->mod_count; k++) { - modval += fluid_mod_get_value(&voice->mod[k], voice); + if(fluid_mod_has_dest(&voice->mod[k], gen)) + { + modval += fluid_mod_get_value(&voice->mod[k], voice); + } } - } - fluid_gen_set_mod(&voice->gen[gen], modval); + fluid_gen_set_mod(&voice->gen[gen], modval); - /* step 3: now that we have the new value of the generator, - * recalculate the parameter values that are derived from the - * generator */ - fluid_voice_update_param(voice, gen); + /* now recalculate the parameter values that are derived from the + generator */ + fluid_voice_update_param(voice, gen); + + /* set the bit that indicates this generator is updated */ + set_gen_updated(updated_gen_bit, gen); + } } } @@ -1222,47 +1249,11 @@ int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl) * Update all the modulators. This function is called after a * ALL_CTRL_OFF MIDI message has been received (CC 121). * + * All destination of all modulators must be updated. */ int fluid_voice_modulate_all(fluid_voice_t *voice) { - fluid_mod_t *mod; - int i, k, gen; - fluid_real_t modval; - - /* Loop through all the modulators. - - FIXME: we should loop through the set of generators instead of - the set of modulators. We risk to call 'fluid_voice_update_param' - several times for the same generator if several modulators have - that generator as destination. It's not an error, just a wast of - energy (think polution, global warming, unhappy musicians, - ...) */ - - for(i = 0; i < voice->mod_count; i++) - { - - mod = &voice->mod[i]; - gen = fluid_mod_get_dest(mod); - modval = 0.0; - - /* Accumulate the modulation values of all the modulators with - * destination generator 'gen' */ - for(k = 0; k < voice->mod_count; k++) - { - if(fluid_mod_has_dest(&voice->mod[k], gen)) - { - modval += fluid_mod_get_value(&voice->mod[k], voice); - } - } - - fluid_gen_set_mod(&voice->gen[gen], modval); - - /* Update the parameter values that are depend on the generator - * 'gen' */ - fluid_voice_update_param(voice, gen); - } - - return FLUID_OK; + return fluid_voice_modulate(voice, 0, -1); } /** legato update functions --------------------------------------------------*/ @@ -1474,10 +1465,10 @@ fluid_voice_stop(fluid_voice_t *voice) } /** - * Adds a modulator to the voice. - * @param voice Voice instance - * @param mod Modulator info (copied) - * @param mode Determines how to handle an existing identical modulator + * Adds a modulator to the voice if the modulator has valid sources. + * @param voice Voice instance. + * @param mod Modulator info (copied). + * @param mode Determines how to handle an existing identical modulator. * #FLUID_VOICE_ADD to add (offset) the modulator amounts, * #FLUID_VOICE_OVERWRITE to replace the modulator, * #FLUID_VOICE_DEFAULT when adding a default modulator - no duplicate should @@ -1486,32 +1477,42 @@ fluid_voice_stop(fluid_voice_t *voice) void fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode) { - int i; + /* Ignore the modulator if its sources inputs are invalid */ + if(fluid_mod_check_sources(mod, "api fluid_voice_add_mod mod")) + { + fluid_voice_add_mod_local(voice, mod, mode, FLUID_NUM_MOD); + } +} - /* - * Some soundfonts come with a huge number of non-standard - * controllers, because they have been designed for one particular - * sound card. Discard them, maybe print a warning. - */ +/** + * Adds a modulator to the voice. + * local version of fluid_voice_add_mod function. Called at noteon time. + * @param voice, mod, mode, same as for fluid_voice_add_mod() (see above). + * @param check_limit_count is the modulator number limit to handle with existing + * identical modulator(i.e mode FLUID_VOICE_OVERWRITE, FLUID_VOICE_ADD). + * - When FLUID_NUM_MOD, all the voices modulators (since the previous call) + * are checked for identity. + * - When check_count_limit is below the actual number of voices modulators + * (voice->mod_count), this will restrict identity check to this number, + * This is usefull when we know by advance that there is no duplicate with + * modulators at index above this limit. This avoid wasting cpu cycles at noteon. + */ +void +fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count) +{ + int i; - if(((mod->flags1 & FLUID_MOD_CC) == 0) - && ((mod->src1 != FLUID_MOD_NONE) /* SF2.01 section 8.2.1: Constant value */ - && (mod->src1 != FLUID_MOD_VELOCITY) /* Note-on velocity */ - && (mod->src1 != FLUID_MOD_KEY) /* Note-on key number */ - && (mod->src1 != FLUID_MOD_KEYPRESSURE) /* Poly pressure */ - && (mod->src1 != FLUID_MOD_CHANNELPRESSURE) /* Channel pressure */ - && (mod->src1 != FLUID_MOD_PITCHWHEEL) /* Pitch wheel */ - && (mod->src1 != FLUID_MOD_PITCHWHEELSENS)))/* Pitch wheel sensitivity */ + /* check_limit_count cannot be above voice->mod_count */ + if(check_limit_count > voice->mod_count) { - FLUID_LOG(FLUID_WARN, "Ignoring invalid controller, using non-CC source %i.", mod->src1); - return; + check_limit_count = voice->mod_count; } if(mode == FLUID_VOICE_ADD) { /* if identical modulator exists, add them */ - for(i = 0; i < voice->mod_count; i++) + for(i = 0; i < check_limit_count; i++) { if(fluid_mod_test_identity(&voice->mod[i], mod)) { @@ -1526,7 +1527,7 @@ fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode) { /* if identical modulator exists, replace it (only the amount has to be changed) */ - for(i = 0; i < voice->mod_count; i++) + for(i = 0; i < check_limit_count; i++) { if(fluid_mod_test_identity(&voice->mod[i], mod)) { @@ -1704,9 +1705,10 @@ int fluid_voice_get_velocity(const fluid_voice_t *voice) * A lower boundary for the attenuation (as in 'the minimum * attenuation of this voice, with volume pedals, modulators * etc. resulting in minimum attenuation, cannot fall below x cB) is - * calculated. This has to be called during fluid_voice_init, after + * calculated. This has to be called during fluid_voice_start, after * all modulators have been run on the voice once. Also, * voice->attenuation has to be initialized. + * (see fluid_voice_calculate_runtime_synthesis_parameters()) */ static fluid_real_t fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice) @@ -1733,30 +1735,42 @@ fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice) { fluid_real_t current_val = fluid_mod_get_value(mod, voice); - fluid_real_t v = fabs(mod->amount); + /* min_val is the possible minimum value for this modulator. + it depends of 3 things : + 1)the minimum values of src1,src2 (i.e -1 if mapping is bipolar + or 0 if mapping is unipolar). + 2)the sign of amount. + 3)absolute value of amount. + + When at least one source mapping is bipolar: + min_val is -|amount| regardless the sign of amount. + When both sources mapping are unipolar: + min_val is -|amount|, if amount is negative. + min_val is 0, if amount is positive + */ + fluid_real_t min_val = fabs(mod->amount); - if((mod->src1 == FLUID_MOD_PITCHWHEEL) - || (mod->flags1 & FLUID_MOD_BIPOLAR) + /* Can this modulator produce a negative contribution? */ + if((mod->flags1 & FLUID_MOD_BIPOLAR) || (mod->flags2 & FLUID_MOD_BIPOLAR) || (mod->amount < 0)) { - /* Can this modulator produce a negative contribution? */ - v *= -1.0; + min_val *= -1.0; /* min_val = - |amount|*/ } else { /* No negative value possible. But still, the minimum contribution is 0. */ - v = 0; + min_val = 0; } /* For example: * - current_val=100 * - min_val=-4000 - * - possible_att_reduction_cB += 4100 + * - possible reduction contribution of this modulator = current_val - min_val = 4100 */ - if(current_val > v) + if(current_val > min_val) { - possible_att_reduction_cB += (current_val - v); + possible_att_reduction_cB += (current_val - min_val); } } } diff --git a/libs/fluidsynth/src/fluid_voice.h b/libs/fluidsynth/src/fluid_voice.h index 6038a1a9f3..fb0b28239f 100644 --- a/libs/fluidsynth/src/fluid_voice.h +++ b/libs/fluidsynth/src/fluid_voice.h @@ -157,6 +157,7 @@ void fluid_voice_release(fluid_voice_t *voice); void fluid_voice_noteoff(fluid_voice_t *voice); void fluid_voice_off(fluid_voice_t *voice); void fluid_voice_stop(fluid_voice_t *voice); +void fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count); void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice); int fluid_voice_kill_excl(fluid_voice_t *voice); diff --git a/libs/fluidsynth/src/fluidsynth_priv.h b/libs/fluidsynth/src/fluidsynth_priv.h index 5de758dc0a..384785fccd 100644 --- a/libs/fluidsynth/src/fluidsynth_priv.h +++ b/libs/fluidsynth/src/fluidsynth_priv.h @@ -103,7 +103,7 @@ #endif #if HAVE_IO_H -#include <io.h> +#include <io.h> // _open(), _close(), read(), write() on windows #endif #if HAVE_SIGNAL_H @@ -262,7 +262,7 @@ typedef FILE *fluid_file; #define FLUID_STRNCPY(_dst,_src,_n) \ do { strncpy(_dst,_src,_n); \ - (_dst)[(_n)-1]=0; \ + (_dst)[(_n)-1]='\0'; \ }while(0) #define FLUID_STRCHR(_s,_c) strchr(_s,_c) diff --git a/libs/fluidsynth/wscript b/libs/fluidsynth/wscript index 3531e2e3d7..724e3aaf6c 100644 --- a/libs/fluidsynth/wscript +++ b/libs/fluidsynth/wscript @@ -65,7 +65,7 @@ def build(bld): 'src/fluid_synth_monopoly.c', 'src/fluid_sys.c' ], - cflags = [ bld.env['compiler_flags_dict']['pic'], '-fvisibility=hidden', '-std=gnu99' ], + cflags = [ bld.env['compiler_flags_dict']['pic'], '-fvisibility=hidden', '-std=gnu99', '-Wno-unused-function' ], includes = ['.', 'src/' ], target = 'libfluidsynth', use = 'libfluidsynth_includes', |