Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions src/synth/fluid_synth.c
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,36 @@ static FLUID_INLINE unsigned int fluid_synth_get_min_note_length_LOCAL(fluid_syn
return (unsigned int)(i * synth->sample_rate / 1000.0f);
}

/* Create the default tuning (bank 0, prog 0) and assign it to all channels. See MIDI RP-020:
* Defaults for Scale/Octave Tuning
* "If tuning presets are not supported by the instrument, it is assumed that the initial tuning of the instrument is equal
* temperament. If [tuning] presets are supported, it is suggested that the first [tuning] preset, selected by Bank 0H and Preset 0H, would be
* equal temperament. Tuning adjusters should begin by selecting Bank 0H, Preset 0H in order to start from equal
* temperament, if that is the desired behavior."
*
* This ensures that MTS SysEx messages modifying tuning 0/0 will automatically affect all channels.
*/
static FLUID_INLINE int fluid_synth_reinitialize_tuning(fluid_synth_t *synth)
{
int i, res;

// Unassign the default tuning from all channels, unreferenced and delete it
res = fluid_synth_replace_tuning_LOCK(synth, NULL, 0, 0, FALSE);
if(res != FLUID_OK)
{
FLUID_LOG(FLUID_WARN, "Failed to reset default tuning during system reset");
}

/* Reset tuning 0/0 back to equal temperament, as per MIDI RP-020.
* This also propagates the new tuning to all channels currently using tuning 0/0. */
for(i = 0; i < synth->midi_channels && res == FLUID_OK; i++)
{
res = fluid_synth_activate_tuning(synth, i, 0, 0, FALSE);
}

return res;
}

/**
* Create new FluidSynth instance.
* @param settings Configuration parameters to use (used directly).
Expand Down Expand Up @@ -1130,6 +1160,12 @@ new_fluid_synth(fluid_settings_t *settings)
synth->bank_select = FLUID_BANK_STYLE_MMA;
}

if(fluid_synth_reinitialize_tuning(synth) != FLUID_OK)
{
// out of memory
goto error_recovery;
}

fluid_iir_filter_init_table(synth->iir_sincos_table, synth->sample_rate);
fluid_synth_process_event_queue(synth);

Expand Down Expand Up @@ -1310,7 +1346,6 @@ delete_fluid_synth(fluid_synth_t *synth)
FLUID_FREE(synth->voice);
}


/* free the tunings, if any */
if(synth->tuning != NULL)
{
Expand Down Expand Up @@ -2909,7 +2944,7 @@ fluid_synth_system_reset(fluid_synth_t *synth)
static int
fluid_synth_system_reset_LOCAL(fluid_synth_t *synth)
{
int i;
int i, res;

if(synth->verbose)
{
Expand All @@ -2923,6 +2958,8 @@ fluid_synth_system_reset_LOCAL(fluid_synth_t *synth)
fluid_channel_reset(synth->channel[i]);
}

res = fluid_synth_reinitialize_tuning(synth);

/* Basic channel 0, Mode Omni On Poly */
fluid_synth_set_basic_channel(synth, 0, FLUID_CHANNEL_MODE_OMNION_POLY,
synth->midi_channels);
Expand All @@ -2936,7 +2973,7 @@ fluid_synth_system_reset_LOCAL(fluid_synth_t *synth)
synth->portamento_time_has_seen_lsb = 0;
}

return FLUID_OK;
return res;
}

/**
Expand Down
16 changes: 13 additions & 3 deletions src/synth/fluid_voice.c
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ fluid_voice_calculate_gain_amplitude(const fluid_voice_t *voice, fluid_real_t ga
fluid_real_t fluid_voice_calculate_pitch(fluid_voice_t *voice, int key)
{
fluid_tuning_t *tuning;
fluid_real_t x, pitch;
fluid_real_t tuned_root_pitch, tuned_key, pitch;

/* Now the nominal pitch of the key is returned.
* Note about SCALETUNE: SF2.01 8.1.3 says, that this generator is a
Expand All @@ -494,10 +494,20 @@ fluid_real_t fluid_voice_calculate_pitch(fluid_voice_t *voice, int key)
*/
if(fluid_channel_has_tuning(voice->channel))
{
fluid_real_t sample_fine_tune;

fluid_real_t root_pitch = voice->root_pitch * (1 / 100.0f);
int root_pitch_int = (int)root_pitch;
// account for voice->sample->pitchadj, without actually accessing that indirection through memory
sample_fine_tune = root_pitch - root_pitch_int;
sample_fine_tune *= 100.0f;

tuning = fluid_channel_get_tuning(voice->channel);
x = fluid_tuning_get_pitch(tuning, (int)(voice->root_pitch / 100.0f));
tuned_root_pitch = fluid_tuning_get_pitch(tuning, (int)(root_pitch));
tuned_root_pitch += sample_fine_tune;
tuned_key = fluid_tuning_get_pitch(tuning, key);
pitch = voice->gen[GEN_SCALETUNE].val / 100.0f *
(fluid_tuning_get_pitch(tuning, key) - x) + x;
(tuned_key - tuned_root_pitch) + tuned_root_pitch;
}
else
{
Expand Down
23 changes: 22 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ADD_FLUID_TEST(test_synth_render_s32)
ADD_FLUID_TEST(test_round_clip)
ADD_FLUID_TEST(test_ABI)
ADD_FLUID_TEST(test_file_seek_tell)
ADD_FLUID_TEST(test_mts_cc_tuning)

if ( GLIB_SUPPORT AND GLib2_VERSION VERSION_GREATER_EQUAL 2.33.12 )
# Earlier versions of GLib had broken comment handling and should not be compared to
Expand Down Expand Up @@ -89,6 +90,7 @@ set(SFE_RENDER_DIR "${RENDERING_OUTPUT_DIR}/sfe")
set(RPN_RENDER_DIR "${RENDERING_OUTPUT_DIR}/rpn")
set(SYSEX_GS_DT1 "${RENDERING_OUTPUT_DIR}/sysex/gs_dt1")
set(LIMITER_RENDER_DIR "${RENDERING_OUTPUT_DIR}/limiter")
set(MTS_RENDER_DIR "${RENDERING_OUTPUT_DIR}/mts")

if(LIBSNDFILE_SUPPORT)
set(FEXT "wav")
Expand Down Expand Up @@ -551,6 +553,23 @@ else ( LIBINSTPATCH_SUPPORT OR ENABLE_NATIVE_DLS )
)
endif (LIBINSTPATCH_SUPPORT OR ENABLE_NATIVE_DLS )


add_custom_target(renderMtsTests
COMMENT "Rendering Midi Tuning Standard Tests"
DEPENDS ${MANUAL_TEST_DEPENDS} create_out_dir
)
set_target_properties(renderMtsTests PROPERTIES
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/manual/mts/"
)
add_render_job(renderMtsTests
"${MTS_RENDER_DIR}/issue102_orig.${FEXT}"
-R 0 -C 0 -g 2 -o synth.device-id=0 ${GENERAL_USER_GS2} issue102_orig/mts.mid
)
add_render_job(renderMtsTests
"${MTS_RENDER_DIR}/issue102_marcus.${FEXT}"
-R 0 -C 0 -g 3 ${GENERAL_USER_GS2} issue102_marcus/mts_marcus.mid
)

# Add a dependency so that rendering targets depends on check_rendering
add_dependencies(check_rendering render1415)
add_dependencies(check_rendering render1417)
Expand All @@ -576,4 +595,6 @@ add_dependencies(check_rendering renderGeneralUserDemo)
add_dependencies(check_rendering renderBankSelect)
add_dependencies(check_rendering renderDMOD)
add_dependencies(check_rendering renderRPN)
add_dependencies(check_rendering renderLimiterTests)
add_dependencies(check_rendering renderMtsTests)
# Temporarily disable limiter tests, because those tests will fail with reference fluidsynth 2.5.x, as it is lacking the limiter
#add_dependencies(check_rendering renderLimiterTests)
4 changes: 2 additions & 2 deletions test/run-manual-regression.sh
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ cmake -S "$ROOT_DIR" -B "$CURRENT_BUILD_DIR" "${CMAKE_GENERATOR_ARGS[@]}" \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DRENDERING_OUTPUT_DIR="$CURRENT_OUTPUT_DIR" \
"${CMAKE_FLAGS[@]}"
cmake --build "$CURRENT_BUILD_DIR" --target check_rendering --parallel $(nproc)
cmake --build "$CURRENT_BUILD_DIR" --target check_rendering --parallel $(nproc) --verbose

# Execute reference rendering in the directory of the current build to ensure we render the same testdata.
# Just override the fluidsynth binary to use the reference one.
Expand All @@ -159,7 +159,7 @@ cmake -S "$ROOT_DIR" -B "$CURRENT_BUILD_DIR" "${CMAKE_GENERATOR_ARGS[@]}" \
-DMANUAL_TEST_FLUIDSYNTH="$REF_FLUIDSYNTH" \
-DRENDERING_OUTPUT_DIR="$REFERENCE_OUTPUT_DIR" \
"${CMAKE_FLAGS[@]}"
cmake --build "$CURRENT_BUILD_DIR" --target check_rendering --parallel $(nproc) || true
cmake --build "$CURRENT_BUILD_DIR" --target check_rendering --parallel $(nproc) --verbose || true

if [[ ! -d "$CURRENT_OUTPUT_DIR" || ! -d "$REFERENCE_OUTPUT_DIR" ]]; then
echo "Manual test output directories were not created." >&2
Expand Down
95 changes: 95 additions & 0 deletions test/test_mts_cc_tuning.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

#include "test.h"
#include "fluidsynth.h"
#include "fluidsynth_priv.h"
#include "fluid_synth.h"
#include "fluid_chan.h"
#include "fluid_tuning.h"

void verify_equal_temperament(fluid_synth_t *synth)

Check warning on line 9 in test/test_mts_cc_tuning.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make the type of this parameter a pointer-to-const. The current type of "synth" is "struct _fluid_synth_t *".

See more on https://sonarcloud.io/project/issues?id=FluidSynth_fluidsynth&issues=AZ0P3_SimOedRvCFlXts&open=AZ0P3_SimOedRvCFlXts&pullRequest=1772
{
int i;
/* Verify that the default tuning is equal-tempered (pitch[key] == key * 100 cents) */
for(i = 0; i < 128; i++)
{
double expected_pitch = i * 100.0;
double actual_pitch = fluid_tuning_get_pitch(fluid_channel_get_tuning(synth->channel[0]), i);
TEST_ASSERT(actual_pitch == expected_pitch);
}
}

/* Tests that channels default to tuning bank 0, prog 0 so that MTS SysEx
* messages that modify tuning 0/0 are automatically applied to all channels.
* See https://github.com/FluidSynth/fluidsynth/issues/102
*/
int main(void)
{
fluid_synth_t *synth;
fluid_settings_t *settings = new_fluid_settings();
int i;

TEST_ASSERT(settings != NULL);

synth = new_fluid_synth(settings);
TEST_ASSERT(synth != NULL);

/* All channels should have tuning bank 0, prog 0 assigned by default */
for(i = 0; i < fluid_synth_count_midi_channels(synth); i++)
{
const fluid_channel_t *chan = synth->channel[i];
TEST_ASSERT(fluid_channel_has_tuning(chan));
TEST_ASSERT(fluid_tuning_get_bank(fluid_channel_get_tuning(chan)) == 0);
TEST_ASSERT(fluid_tuning_get_prog(fluid_channel_get_tuning(chan)) == 0);
}

verify_equal_temperament(synth);

/* Modify the tuning of bank 0, prog 0 - change note 60 (middle C) to 6000 cents */
{

Check warning on line 48 in test/test_mts_cc_tuning.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested code block into a separate function.

See more on https://sonarcloud.io/project/issues?id=FluidSynth_fluidsynth&issues=AZ0P3_SimOedRvCFlXtt&open=AZ0P3_SimOedRvCFlXtt&pullRequest=1772
int key = 60;
double new_pitch = 6.0;
TEST_SUCCESS(fluid_synth_tune_notes(synth, 0, 0, 1, &key, &new_pitch, FALSE));

/* All channels should now reflect the modified tuning */
for(i = 0; i < fluid_synth_count_midi_channels(synth); i++)
{
const fluid_channel_t *chan = synth->channel[i];
TEST_ASSERT(fluid_channel_has_tuning(chan));
TEST_ASSERT(fluid_tuning_get_pitch(fluid_channel_get_tuning(chan), 60) == new_pitch);
}
}

/* After a system reset, channels should still have tuning 0/0 */
TEST_SUCCESS(fluid_synth_system_reset(synth));
for(i = 0; i < fluid_synth_count_midi_channels(synth); i++)
{
const fluid_channel_t *chan = synth->channel[i];
TEST_ASSERT(fluid_channel_has_tuning(chan));
TEST_ASSERT(fluid_tuning_get_bank(fluid_channel_get_tuning(chan)) == 0);
TEST_ASSERT(fluid_tuning_get_prog(fluid_channel_get_tuning(chan)) == 0);
}

/* But the tuning change from before should be gone */
verify_equal_temperament(synth);

/* Test that explicitly deactivating tuning on a channel works */
TEST_SUCCESS(fluid_synth_deactivate_tuning(synth, 2, FALSE));
TEST_ASSERT(!fluid_channel_has_tuning(synth->channel[2]));

/* After another system reset, the channel should again have tuning 0/0 */
TEST_SUCCESS(fluid_synth_system_reset(synth));
TEST_ASSERT(fluid_channel_has_tuning(synth->channel[2]));
/* All channels should have tuning bank 0, prog 0 assigned by default */
for(i = 0; i < fluid_synth_count_midi_channels(synth); i++)
{
const fluid_channel_t *chan = synth->channel[i];
TEST_ASSERT(fluid_channel_has_tuning(chan));
TEST_ASSERT(fluid_tuning_get_bank(fluid_channel_get_tuning(chan)) == 0);
TEST_ASSERT(fluid_tuning_get_prog(fluid_channel_get_tuning(chan)) == 0);
}

delete_fluid_synth(synth);
delete_fluid_settings(settings);

return EXIT_SUCCESS;
}
Loading