-- v3: dmsynth: Pre-attenuate voices by center pan attenuation. fluidsynth: Disable IIR filter resonance hump compensation. dmsynth: Handle GUID_DMUS_PROP_Volume. dmsynth: Set gain to 6 dB.
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/tests/dmsynth.c | 633 +++++++++++++++++++++++++++++++++++ 1 file changed, 633 insertions(+)
diff --git a/dlls/dmsynth/tests/dmsynth.c b/dlls/dmsynth/tests/dmsynth.c index a30c295c052..32e5c1d1e35 100644 --- a/dlls/dmsynth/tests/dmsynth.c +++ b/dlls/dmsynth/tests/dmsynth.c @@ -21,6 +21,7 @@ #define COBJMACROS
#include <stdio.h> +#include <math.h>
#include "wine/test.h" #include "uuids.h" @@ -619,6 +620,79 @@ static void test_COM_synthsink(void) IDirectMusicSynthSink_Release(dmss); }
+struct test_clock +{ + IReferenceClock IReferenceClock_iface; + + LONG refcount; +}; + +static HRESULT WINAPI test_clock_QueryInterface(IReferenceClock *iface, REFIID iid, void **out) +{ + ok(0, "unexpected %s\n", __func__); + return E_NOINTERFACE; +} + +static ULONG WINAPI test_clock_AddRef(IReferenceClock *iface) +{ + struct test_clock *clock = CONTAINING_RECORD(iface, struct test_clock, IReferenceClock_iface); + return InterlockedIncrement(&clock->refcount); +} + +static ULONG WINAPI test_clock_Release(IReferenceClock *iface) +{ + struct test_clock *clock = CONTAINING_RECORD(iface, struct test_clock, IReferenceClock_iface); + return InterlockedDecrement(&clock->refcount); +} + +static HRESULT WINAPI test_clock_GetTime(IReferenceClock *iface, REFERENCE_TIME *time) +{ + *time = 0; + return S_OK; +} + +static HRESULT WINAPI test_clock_AdviseTime(IReferenceClock *iface, REFERENCE_TIME base_time, REFERENCE_TIME stream_time, HEVENT event, DWORD_PTR *cookie) +{ + ok(0, "unexpected %s\n", __func__); + return E_NOTIMPL; +} + +static HRESULT WINAPI test_clock_AdvisePeriodic(IReferenceClock *iface, REFERENCE_TIME start_time, REFERENCE_TIME period, HSEMAPHORE semaphore, DWORD_PTR *cookie) +{ + ok(0, "unexpected %s\n", __func__); + return E_NOTIMPL; +} + +static HRESULT WINAPI test_clock_Unadvise(IReferenceClock *iface, DWORD_PTR cookie) +{ + ok(0, "unexpected %s\n", __func__); + return E_NOTIMPL; +} + +static const IReferenceClockVtbl test_clock_vtbl = +{ + test_clock_QueryInterface, + test_clock_AddRef, + test_clock_Release, + test_clock_GetTime, + test_clock_AdviseTime, + test_clock_AdvisePeriodic, + test_clock_Unadvise, +}; + +static HRESULT test_clock_create(IReferenceClock **out) +{ + struct test_clock *clock; + + *out = NULL; + if (!(clock = calloc(1, sizeof(*clock)))) return E_OUTOFMEMORY; + clock->IReferenceClock_iface.lpVtbl = &test_clock_vtbl; + clock->refcount = 1; + + *out = &clock->IReferenceClock_iface; + return S_OK; +} + struct test_sink { IDirectMusicSynthSink IDirectMusicSynthSink_iface; @@ -1194,6 +1268,564 @@ static void test_IDirectMusicSynth(void) IDirectMusic_Release(music); }
+#define PI 3.14159265358979323846264 + +struct phase +{ + double duration; + double start_value; + double end_value; + BOOL linear; +}; + +struct envelope +{ + double gain; + double channel_gain[2]; + struct phase delay; + struct phase attack; + struct phase hold; + struct phase decay; + struct phase sustain; + struct phase release; +}; + +static double lerp(double start_value, double end_value, double factor) +{ + return start_value + (end_value - start_value) * factor; +} + +static double get_phase_value(const struct phase *phase, double time) +{ + double start_value_pow; + double end_value_pow; + double value_pow; + + if (phase->linear) + return lerp(phase->start_value, phase->end_value, time / phase->duration); + + start_value_pow = pow(10., phase->start_value / 200.); + end_value_pow = pow(10., phase->end_value / 200.); + value_pow = lerp(start_value_pow, end_value_pow, time / phase->duration); + return log10(value_pow) * 200.; +} + +static void get_phase_min_max(const struct phase *phase, double start_time, double end_time, double *min_value, + double *max_value) +{ + double start_value; + double end_value; + + if (end_time < 0. || start_time > phase->duration) + return; + + start_value = start_time <= 0. ? phase->start_value : get_phase_value(phase, start_time); + end_value = end_time >= phase->duration ? phase->end_value : get_phase_value(phase, end_time); + + *min_value = min(*min_value, min(start_value, end_value)); + *max_value = max(*max_value, max(start_value, end_value)); +} + +static void get_envelope_min_max(const struct envelope *envelope, double start_time, double end_time, double *min_value, + double *max_value) +{ + double time = 0.; + + get_phase_min_max(&envelope->delay, start_time - time, end_time - time, min_value, max_value); + time += envelope->delay.duration; + get_phase_min_max(&envelope->attack, start_time - time, end_time - time, min_value, max_value); + time += envelope->attack.duration; + get_phase_min_max(&envelope->hold, start_time - time, end_time - time, min_value, max_value); + time += envelope->hold.duration; + get_phase_min_max(&envelope->decay, start_time - time, end_time - time, min_value, max_value); + time += envelope->decay.duration; + get_phase_min_max(&envelope->sustain, start_time - time, end_time - time, min_value, max_value); + time += envelope->sustain.duration; + get_phase_min_max(&envelope->release, start_time - time, end_time - time, min_value, max_value); + time += envelope->release.duration; + + if (start_time <= 0.) + { + *min_value = min(*min_value, envelope->delay.start_value); + *max_value = max(*max_value, envelope->delay.start_value); + } + + if (end_time >= time) + { + *min_value = min(*min_value, envelope->release.end_value); + *max_value = max(*max_value, envelope->release.end_value); + } +} + +#define SINE_AMPLITUDE 0.5 +#define SINE_LENGTH 256 + +struct DECLSPEC_ALIGN(8) instrument_download +{ + DMUS_DOWNLOADINFO info; + ULONG offsets[4]; + DMUS_INSTRUMENT instrument; + DMUS_REGION region; + DMUS_ARTICULATION2 articulation; + CONNECTIONLIST connection_list; + CONNECTION connections[64]; +}; + +static void render_sine(IDirectMusicSynth *synth, const struct instrument_download *download, + void *midi, double phase_offset, short (*buffer)[2], DWORD length) +{ + struct instrument_download instrument_download = *download; + struct DECLSPEC_ALIGN(8) wave_download + { + DMUS_DOWNLOADINFO info; + ULONG offsets[2]; + DMUS_WAVE wave; + union + { + DMUS_WAVEDATA wave_data; + struct + { + ULONG size; + short samples[SINE_LENGTH]; + }; + }; + } wave_download = + { + .info = + { + .dwDLType = DMUS_DOWNLOADINFO_WAVE, + .dwDLId = 1, + .dwNumOffsetTableEntries = 2, + .cbSize = sizeof(struct wave_download), + }, + .offsets = + { + offsetof(struct wave_download, wave), + offsetof(struct wave_download, wave_data), + }, + .wave = + { + .ulWaveDataIdx = 1, + .WaveformatEx = + { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 1, + .wBitsPerSample = 16, + .nSamplesPerSec = 44100, + .nAvgBytesPerSec = 88200, + .nBlockAlign = 2, + }, + }, + .wave_data = + { + .cbSize = sizeof(wave_download.samples), + }, + }; + DMUS_PORTPARAMS params = + { + .dwSize = sizeof(DMUS_PORTPARAMS), + .dwValidParams = DMUS_PORTPARAMS_SAMPLERATE | DMUS_PORTPARAMS_EFFECTS, + .dwSampleRate = 44100, + .dwEffectFlags = 0, + }; + HANDLE instrument_handle; + HANDLE wave_handle; + DWORD midi_size; + BOOL can_free; + HRESULT hr; + int i; + + for (i = 0; i < ARRAYSIZE(wave_download.samples); ++i) + { + double phase = (double)i * (2. * PI / (double)SINE_LENGTH) + phase_offset; + double value = cos(phase) * SINE_AMPLITUDE; + wave_download.samples[i] = (short)floor(value * (double)SHRT_MAX + 0.5); + } + + hr = IDirectMusicSynth_Open(synth, ¶ms); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynth_Download(synth, &wave_handle, &wave_download, &can_free); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IDirectMusicSynth_Download(synth, &instrument_handle, &instrument_download, &can_free); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynth_Activate(synth, TRUE); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + midi_size = 0; + for (;;) + { + DMUS_EVENTHEADER *event = (DMUS_EVENTHEADER *)&((char *)midi)[midi_size]; + if (!event->cbEvent) + break; + midi_size += (sizeof(DMUS_EVENTHEADER) + event->cbEvent + 7) & ~7; + } + + hr = IDirectMusicSynth_PlayBuffer(synth, 0, midi, midi_size); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + memset(buffer, 0, length * sizeof(buffer[0])); + hr = IDirectMusicSynth_Render(synth, buffer[0], length, 0); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynth_Activate(synth, FALSE); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynth_Unload(synth, instrument_handle, NULL, NULL); + ok(hr == S_OK, "got hr %#lx.\n", hr); + hr = IDirectMusicSynth_Unload(synth, wave_handle, NULL, NULL); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynth_Close(synth); + ok(hr == S_OK, "got hr %#lx.\n", hr); +} + +typedef double check_value_fn(int line, double time, short i0, short q0, double min_expected_value, + double max_expected_value); + +static void check_envelope(int line, IDirectMusicSynth *synth, const struct instrument_download *download, + void *midi, check_value_fn *check_value, const struct envelope *envelope, BOOL todo) +{ + short buffer_i[44100 * 2][2]; + short buffer_q[44100 * 2][2]; + IDirectMusicSynthSink *sink; + IReferenceClock *clock; + HRESULT hr; + int i; + int j; + + hr = test_sink_create(&sink); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = test_clock_create(&clock); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynthSink_SetMasterClock(sink, clock); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IDirectMusicSynth_SetSynthSink(synth, sink); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + /* Render two sine waves 90° out of phase. This will allow us to measure + * amplitude and frequency at any given point. */ + render_sine(synth, download, midi, 0., buffer_i, ARRAYSIZE(buffer_i)); + render_sine(synth, download, midi, -0.5 * PI, buffer_q, ARRAYSIZE(buffer_q)); + + for (j = 0; j < 2; ++j) + { + double first_deviation_time = DBL_MAX; + double last_deviation_time = -DBL_MIN; + double max_deviation_time = 0.; + double max_deviation = 0.; + + for (i = 0; i < ARRAYSIZE(buffer_i) - 1; ++i) + { + double time, start_time, end_time, min_expected_value, max_expected_value, deviation; + double min_envelope_value, max_envelope_value; + short i0, q0; + + time = (double)i / 44100.; + /* Both FluidSynth and native use a piecewise-linear approximation + * for envelopes. FluidSynth uses fixed 64-sample segments, while + * native uses variable length segments up to 2048 samples. Also + * sometimes native starts an envelope phase 256 samples early for + * some reason. */ + start_time = time - 2048. / 44100.; + end_time = time + (2048. + 256.) / 44100.; + min_envelope_value = DBL_MAX; + max_envelope_value = -DBL_MAX; + get_envelope_min_max(envelope, start_time, end_time, &min_envelope_value, &max_envelope_value); + min_expected_value = min_envelope_value + envelope->gain + envelope->channel_gain[j]; + max_expected_value = max_envelope_value + envelope->gain + envelope->channel_gain[j]; + + i0 = buffer_i[i][j]; + q0 = buffer_q[i][j]; + + deviation = check_value(line, time, i0, q0, min_expected_value, max_expected_value); + if (deviation) + { + first_deviation_time = min(first_deviation_time, time); + last_deviation_time = max(last_deviation_time, time); + if (fabs(deviation) > fabs(max_deviation)) + { + max_deviation_time = time; + max_deviation = deviation; + } + } + } + + todo_wine_if(todo) ok_(__FILE__, line)(!max_deviation, + "got %s channel max deviation %g at %gms, start time %gms, end time %gms.\n", j ? "right" : "left", + max_deviation, max_deviation_time * 1e3, first_deviation_time * 1e3, last_deviation_time * 1e3); + } + + hr = IDirectMusicSynth_SetSynthSink(synth, NULL); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + IReferenceClock_Release(clock); + IDirectMusicSynthSink_Release(sink); +} + +static double check_volume(int line, double time, short i0, short q0, double min_expected_value, + double max_expected_value) +{ + double amplitude = hypot((double)i0, (double)q0) * (1. / SINE_AMPLITUDE / (double)SHRT_MAX); + double min_expected_amplitude; + double max_expected_amplitude; + double eps = 5e-2; + + min_expected_amplitude = pow(10., min_expected_value / 200.) - eps; + max_expected_amplitude = pow(10., max_expected_value / 200.) + eps; + + if (amplitude < min_expected_amplitude) + return amplitude - min_expected_amplitude; + if (amplitude > max_expected_amplitude) + return amplitude - max_expected_amplitude; + + return 0.; +} + +#define check_volume_envelope(synth, download, midi, envelope, todo) \ + check_envelope(__LINE__, synth, download, midi, check_volume, envelope, todo) + +static const struct instrument_download default_instrument_download = +{ + .info = + { + .dwDLType = DMUS_DOWNLOADINFO_INSTRUMENT2, + .dwDLId = 2, + .dwNumOffsetTableEntries = 4, + .cbSize = sizeof(struct instrument_download), + }, + .offsets = + { + offsetof(struct instrument_download, instrument), + offsetof(struct instrument_download, region), + offsetof(struct instrument_download, articulation), + offsetof(struct instrument_download, connection_list), + }, + .instrument = + { + .ulPatch = 0, + .ulFirstRegionIdx = 1, + .ulGlobalArtIdx = 2, + }, + .region = + { + .RangeKey = {.usLow = 0, .usHigh = 127}, + .RangeVelocity = {.usLow = 0, .usHigh = 127}, + .fusOptions = F_RGN_OPTION_SELFNONEXCLUSIVE, + .WaveLink = {.ulChannel = 1, .ulTableIndex = 1}, + .WSMP = {.cbSize = sizeof(WSMPL), .usUnityNote = 60, .fulOptions = F_WSMP_NO_TRUNCATION, .cSampleLoops = 1}, + .WLOOP[0] = {.cbSize = sizeof(WLOOP), .ulType = WLOOP_TYPE_FORWARD, .ulLength = SINE_LENGTH}, + }, + .articulation = {.ulArtIdx = 3}, + .connection_list = + { + .cbSize = sizeof(CONNECTIONLIST), + .cConnections = 0, + }, +}; + +struct DECLSPEC_ALIGN(8) midi_message +{ + DMUS_EVENTHEADER header; + DWORD message; +}; + +static const struct midi_message default_note_on = +{ + .header = + { + .cbEvent = 3, + .dwFlags = DMUS_EVENT_STRUCTURED, + }, + .message = 0x7f3c90, +}; + +static const struct midi_message default_note_off = +{ + .header = + { + .cbEvent = 3, + .rtDelta = 10000000, + .dwFlags = DMUS_EVENT_STRUCTURED, + }, + .message = 0x7f3c80, +}; + +struct midi +{ + struct midi_message messages[15]; + struct midi_message null; +}; + +struct midi default_midi = +{ + .messages = + { + default_note_on, + default_note_off, + }, +}; + +static const struct envelope default_volume_envelope = +{ + .gain = 60. /* base gain */ + 60. /* default GUID_DMUS_PROP_Volume */ - 41.52 /* default CC7 */, + /* center pan gain */ + .channel_gain = + { + -30.10, + -30.10, + }, + .delay = + { + .duration = 0., + .start_value = -960., + .end_value = -960., + .linear = TRUE, + }, + .attack = + { + .duration = 0., + .start_value = -960., + .end_value = 0., + .linear = FALSE, + }, + .hold = + { + .duration = 0., + .start_value = 0., + .end_value = 0., + .linear = TRUE, + }, + .decay = + { + .duration = 0., + .start_value = 0., + .end_value = 0., + .linear = TRUE, + }, + .sustain = + { + .duration = 1000e-3, + .start_value = 0., + .end_value = 0., + .linear = TRUE, + }, + .release = + { + .duration = 0., + .start_value = 0., + .end_value = -960., + .linear = TRUE, + }, +}; + +static void test_IKsControl(void) +{ + IDirectMusicSynth *synth; + struct envelope envelope; + IKsControl *control; + KSPROPERTY property; + DWORD volume_size; + LONG volume; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_DirectMusicSynth, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectMusicSynth, (void **)&synth); + ok(hr == S_OK, "got hr %#lx.\n", hr); + IDirectMusicSynth_QueryInterface(synth, &IID_IKsControl, (void **)&control); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + /* getting volume is unsupported */ + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 0; + property.Flags = KSPROPERTY_TYPE_GET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == DMUS_E_GET_UNSUPPORTED, "got hr %#lx.\n", hr); + + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 1; + property.Flags = KSPROPERTY_TYPE_GET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == DMUS_E_GET_UNSUPPORTED, "got hr %#lx.\n", hr); + + /* out of range id results in DMUS_E_UNKNOWN_PROPERTY */ + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 2; + property.Flags = KSPROPERTY_TYPE_GET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == DMUS_E_UNKNOWN_PROPERTY, "got hr %#lx.\n", hr); + + volume = 0; + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 2; + property.Flags = KSPROPERTY_TYPE_SET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == DMUS_E_UNKNOWN_PROPERTY, "got hr %#lx.\n", hr); + + /* default value for volume 0 is 0 */ + check_volume_envelope(synth, &default_instrument_download, &default_midi, &default_volume_envelope, TRUE); + + volume = 0; + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 0; + property.Flags = KSPROPERTY_TYPE_SET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + check_volume_envelope(synth, &default_instrument_download, &default_midi, &default_volume_envelope, TRUE); + + /* total voice gain is limited to 6 dB */ + volume = 600; + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 0; + property.Flags = KSPROPERTY_TYPE_SET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + envelope = default_volume_envelope; + envelope.gain = 60.; + envelope.channel_gain[0] = 0.; + envelope.channel_gain[1] = 0.; + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + + /* setting volume 0 changes voice gain */ + volume = -600; + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 0; + property.Flags = KSPROPERTY_TYPE_SET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + envelope = default_volume_envelope; + envelope.gain -= 60.; + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + + /* default value for volume 1 is 600 */ + volume = 600; + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 1; + property.Flags = KSPROPERTY_TYPE_SET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + envelope = default_volume_envelope; + envelope.gain -= 60.; + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + + /* gain from volume 0 and 1 is added together */ + volume = 0; + property.Set = GUID_DMUS_PROP_Volume; + property.Id = 1; + property.Flags = KSPROPERTY_TYPE_SET; + hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); + todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + envelope = default_volume_envelope; + envelope.gain -= 120.; + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + + IKsControl_Release(control); + IDirectMusicSynth_Release(synth); +} + static void test_IDirectMusicSynthSink(void) { IReferenceClock *latency_clock; @@ -1383,6 +2015,7 @@ START_TEST(dmsynth) test_COM(); test_COM_synthsink(); test_IDirectMusicSynth(); + test_IKsControl(); test_IDirectMusicSynthSink();
CoUninitialize();
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index 57a057b68b0..a1fb2cfa696 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -46,6 +46,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmsynth);
#define CONN_TRANSFORM(src, ctrl, dst) (((src) & 0x3f) << 10) | (((ctrl) & 0x3f) << 4) | ((dst) & 0xf)
+#define BASE_GAIN 60. + /* from src/rvoice/fluid_rvoice.h */ #define FLUID_LOOP_DURING_RELEASE 1 #define FLUID_LOOP_UNTIL_RELEASE 3 @@ -511,6 +513,7 @@ static HRESULT WINAPI synth_Open(IDirectMusicSynth8 *iface, DMUS_PORTPARAMS *par }; UINT size = sizeof(DMUS_PORTPARAMS); BOOL modified = FALSE; + double gain; UINT id;
TRACE("(%p, %p)\n", This, params); @@ -586,6 +589,11 @@ static HRESULT WINAPI synth_Open(IDirectMusicSynth8 *iface, DMUS_PORTPARAMS *par !!(actual.dwEffectFlags & DMUS_EFFECT_REVERB)); fluid_settings_setint(This->fluid_settings, "synth.chorus.active", !!(actual.dwEffectFlags & DMUS_EFFECT_CHORUS)); + + /* native limits the total voice gain to 6 dB */ + gain = BASE_GAIN; + fluid_settings_setnum(This->fluid_settings, "synth.gain", pow(10., gain / 200.)); + if (!(This->fluid_synth = new_fluid_synth(This->fluid_settings))) { LeaveCriticalSection(&This->cs);
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 56 ++++++++++++++++++++++++++++++++++++ dlls/dmsynth/tests/dmsynth.c | 18 ++++++------ 2 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index a1fb2cfa696..5f4b005f5e6 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -342,6 +342,8 @@ struct synth IKsControl IKsControl_iface; LONG ref;
+ LONG volume0; + LONG volume1; DMUS_PORTCAPS caps; DMUS_PORTPARAMS params; BOOL active; @@ -450,6 +452,12 @@ static ULONG WINAPI synth_Release(IDirectMusicSynth8 *iface) return ref; }
+static void update_channel_volume(struct synth *This, int chan) +{ + double attenuation = (This->volume0 + This->volume1) * -0.1; + fluid_synth_set_gen(This->fluid_synth, chan, GEN_ATTENUATION, attenuation); +} + static void synth_reset_default_values(struct synth *This) { BYTE chan; @@ -493,6 +501,8 @@ static void synth_reset_default_values(struct synth *This)
fluid_synth_cc(This->fluid_synth, chan | 0xb0 /* CONTROL_CHANGE */, 0x64 /* RPN_LSB */, 127); fluid_synth_cc(This->fluid_synth, chan | 0xb0 /* CONTROL_CHANGE */, 0x65 /* RPN_MSB */, 127); + + update_channel_volume(This, chan); } }
@@ -1107,10 +1117,13 @@ static HRESULT WINAPI synth_Render(IDirectMusicSynth8 *iface, short *buffer, { struct synth *This = impl_from_IDirectMusicSynth8(iface); struct event *event, *next; + int chan;
TRACE("(%p, %p, %ld, %I64d)\n", This, buffer, length, position);
EnterCriticalSection(&This->cs); + for (chan = 0; chan < 0x10; chan++) + update_channel_volume(This, chan); LIST_FOR_EACH_ENTRY_SAFE(event, next, &This->events, struct event, entry) { BYTE status = event->midi[0] & 0xf0, chan = event->midi[0] & 0x0f; @@ -1331,10 +1344,44 @@ static ULONG WINAPI synth_control_Release(IKsControl* iface) static HRESULT WINAPI synth_control_KsProperty(IKsControl* iface, PKSPROPERTY Property, ULONG PropertyLength, LPVOID PropertyData, ULONG DataLength, ULONG* BytesReturned) { + struct synth *This = impl_from_IKsControl(iface); + TRACE("(%p, %p, %lu, %p, %lu, %p)\n", iface, Property, PropertyLength, PropertyData, DataLength, BytesReturned);
TRACE("Property = %s - %lu - %lu\n", debugstr_guid(&Property->Set), Property->Id, Property->Flags);
+ if (Property->Flags == KSPROPERTY_TYPE_SET) + { + if (DataLength < sizeof(LONG)) + return E_NOT_SUFFICIENT_BUFFER; + + if (IsEqualGUID(&Property->Set, &GUID_DMUS_PROP_Volume)) + { + LONG volume = max(DMUS_VOLUME_MIN, min(DMUS_VOLUME_MAX, *(LONG*)PropertyData)); + + if (Property->Id == 0) + { + EnterCriticalSection(&This->cs); + This->volume0 = volume; + LeaveCriticalSection(&This->cs); + } + else if (Property->Id == 1) + { + EnterCriticalSection(&This->cs); + This->volume1 = volume; + LeaveCriticalSection(&This->cs); + } + else + return DMUS_E_UNKNOWN_PROPERTY; + } + else + { + FIXME("Unknown property %s\n", debugstr_guid(&Property->Set)); + } + + return S_OK; + } + if (Property->Flags != KSPROPERTY_TYPE_GET) { FIXME("Property flags %lu not yet supported\n", Property->Flags); @@ -1369,6 +1416,12 @@ static HRESULT WINAPI synth_control_KsProperty(IKsControl* iface, PKSPROPERTY Pr *(DWORD*)PropertyData = FALSE; *BytesReturned = sizeof(DWORD); } + else if (IsEqualGUID(&Property->Set, &GUID_DMUS_PROP_Volume)) + { + if (Property->Id >= 2) + return DMUS_E_UNKNOWN_PROPERTY; + return DMUS_E_GET_UNSUPPORTED; + } else { FIXME("Unknown property %s\n", debugstr_guid(&Property->Set)); @@ -1977,6 +2030,9 @@ HRESULT synth_create(IUnknown **ret_iface) obj->IKsControl_iface.lpVtbl = &synth_control_vtbl; obj->ref = 1;
+ obj->volume0 = 0; + obj->volume1 = 600; + obj->caps.dwSize = sizeof(DMUS_PORTCAPS); obj->caps.dwFlags = DMUS_PC_DLS | DMUS_PC_SOFTWARESYNTH | DMUS_PC_DIRECTSOUND | DMUS_PC_DLS2 | DMUS_PC_AUDIOPATH | DMUS_PC_WAVE; obj->caps.guidPort = CLSID_DirectMusicSynth; diff --git a/dlls/dmsynth/tests/dmsynth.c b/dlls/dmsynth/tests/dmsynth.c index 32e5c1d1e35..7662f26ec4b 100644 --- a/dlls/dmsynth/tests/dmsynth.c +++ b/dlls/dmsynth/tests/dmsynth.c @@ -1743,27 +1743,27 @@ static void test_IKsControl(void) property.Id = 0; property.Flags = KSPROPERTY_TYPE_GET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == DMUS_E_GET_UNSUPPORTED, "got hr %#lx.\n", hr); + ok(hr == DMUS_E_GET_UNSUPPORTED, "got hr %#lx.\n", hr);
property.Set = GUID_DMUS_PROP_Volume; property.Id = 1; property.Flags = KSPROPERTY_TYPE_GET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == DMUS_E_GET_UNSUPPORTED, "got hr %#lx.\n", hr); + ok(hr == DMUS_E_GET_UNSUPPORTED, "got hr %#lx.\n", hr);
/* out of range id results in DMUS_E_UNKNOWN_PROPERTY */ property.Set = GUID_DMUS_PROP_Volume; property.Id = 2; property.Flags = KSPROPERTY_TYPE_GET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == DMUS_E_UNKNOWN_PROPERTY, "got hr %#lx.\n", hr); + ok(hr == DMUS_E_UNKNOWN_PROPERTY, "got hr %#lx.\n", hr);
volume = 0; property.Set = GUID_DMUS_PROP_Volume; property.Id = 2; property.Flags = KSPROPERTY_TYPE_SET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == DMUS_E_UNKNOWN_PROPERTY, "got hr %#lx.\n", hr); + ok(hr == DMUS_E_UNKNOWN_PROPERTY, "got hr %#lx.\n", hr);
/* default value for volume 0 is 0 */ check_volume_envelope(synth, &default_instrument_download, &default_midi, &default_volume_envelope, TRUE); @@ -1773,7 +1773,7 @@ static void test_IKsControl(void) property.Id = 0; property.Flags = KSPROPERTY_TYPE_SET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(hr == S_OK, "got hr %#lx.\n", hr); check_volume_envelope(synth, &default_instrument_download, &default_midi, &default_volume_envelope, TRUE);
/* total voice gain is limited to 6 dB */ @@ -1782,7 +1782,7 @@ static void test_IKsControl(void) property.Id = 0; property.Flags = KSPROPERTY_TYPE_SET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain = 60.; envelope.channel_gain[0] = 0.; @@ -1795,7 +1795,7 @@ static void test_IKsControl(void) property.Id = 0; property.Flags = KSPROPERTY_TYPE_SET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain -= 60.; check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); @@ -1806,7 +1806,7 @@ static void test_IKsControl(void) property.Id = 1; property.Flags = KSPROPERTY_TYPE_SET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain -= 60.; check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); @@ -1817,7 +1817,7 @@ static void test_IKsControl(void) property.Id = 1; property.Flags = KSPROPERTY_TYPE_SET; hr = IKsControl_KsProperty(control, &property, sizeof(property), &volume, sizeof(volume), &volume_size); - todo_wine ok(hr == S_OK, "got hr %#lx.\n", hr); + ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain -= 120.; check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE);
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/tests/dmsynth.c | 6 +++--- libs/fluidsynth/src/rvoice/fluid_iir_filter.c | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dlls/dmsynth/tests/dmsynth.c b/dlls/dmsynth/tests/dmsynth.c index 7662f26ec4b..4ecaf5863e1 100644 --- a/dlls/dmsynth/tests/dmsynth.c +++ b/dlls/dmsynth/tests/dmsynth.c @@ -1798,7 +1798,7 @@ static void test_IKsControl(void) ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain -= 60.; - check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, FALSE);
/* default value for volume 1 is 600 */ volume = 600; @@ -1809,7 +1809,7 @@ static void test_IKsControl(void) ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain -= 60.; - check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, FALSE);
/* gain from volume 0 and 1 is added together */ volume = 0; @@ -1820,7 +1820,7 @@ static void test_IKsControl(void) ok(hr == S_OK, "got hr %#lx.\n", hr); envelope = default_volume_envelope; envelope.gain -= 120.; - check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, FALSE);
IKsControl_Release(control); IDirectMusicSynth_Release(synth); diff --git a/libs/fluidsynth/src/rvoice/fluid_iir_filter.c b/libs/fluidsynth/src/rvoice/fluid_iir_filter.c index 79d293c61b2..fcea1793d50 100644 --- a/libs/fluidsynth/src/rvoice/fluid_iir_filter.c +++ b/libs/fluidsynth/src/rvoice/fluid_iir_filter.c @@ -151,6 +151,7 @@ static fluid_real_t fluid_iir_filter_q_from_dB(fluid_real_t q_dB) /* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */ fluid_clip(q_dB, 0.0f, 96.0f);
+#if 0 /* unused in Wine */ /* Short version: Modify the Q definition in a way, that a Q of 0 * dB leads to no resonance hump in the freq. response. * @@ -167,6 +168,7 @@ static fluid_real_t fluid_iir_filter_q_from_dB(fluid_real_t q_dB) * response of a non-resonant filter. This idea is implemented as * follows: */ q_dB -= 3.01f; +#endif /* unused in Wine */
/* The 'sound font' Q is defined in dB. The filter needs a linear q. Convert. */
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 7 +++++++ dlls/dmsynth/tests/dmsynth.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index 5f4b005f5e6..0fe7a92a697 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -47,6 +47,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmsynth); #define CONN_TRANSFORM(src, ctrl, dst) (((src) & 0x3f) << 10) | (((ctrl) & 0x3f) << 4) | ((dst) & 0xf)
#define BASE_GAIN 60. +#define CENTER_PAN_GAIN -30.10
/* from src/rvoice/fluid_rvoice.h */ #define FLUID_LOOP_DURING_RELEASE 1 @@ -602,6 +603,8 @@ static HRESULT WINAPI synth_Open(IDirectMusicSynth8 *iface, DMUS_PORTPARAMS *par
/* native limits the total voice gain to 6 dB */ gain = BASE_GAIN; + /* compensate gain added in synth_preset_noteon */ + gain -= CENTER_PAN_GAIN; fluid_settings_setnum(This->fluid_settings, "synth.gain", pow(10., gain / 200.));
if (!(This->fluid_synth = new_fluid_synth(This->fluid_settings))) @@ -1939,6 +1942,10 @@ static int synth_preset_noteon(fluid_preset_t *fluid_preset, fluid_synth_t *flui add_voice_connections(fluid_voice, &articulation->list, articulation->connections); LIST_FOR_EACH_ENTRY(articulation, ®ion->articulations, struct articulation, entry) add_voice_connections(fluid_voice, &articulation->list, articulation->connections); + /* Unlike FluidSynth, native applies the gain limit after the panning. At + * least for the center pan we can replicate this by applying a panning + * attenuation here. */ + fluid_voice_gen_incr(voice->fluid_voice, GEN_ATTENUATION, -CENTER_PAN_GAIN); fluid_synth_start_voice(synth->fluid_synth, fluid_voice);
LeaveCriticalSection(&synth->cs); diff --git a/dlls/dmsynth/tests/dmsynth.c b/dlls/dmsynth/tests/dmsynth.c index 4ecaf5863e1..6293c1ea4bd 100644 --- a/dlls/dmsynth/tests/dmsynth.c +++ b/dlls/dmsynth/tests/dmsynth.c @@ -1787,7 +1787,7 @@ static void test_IKsControl(void) envelope.gain = 60.; envelope.channel_gain[0] = 0.; envelope.channel_gain[1] = 0.; - check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, TRUE); + check_volume_envelope(synth, &default_instrument_download, &default_midi, &envelope, FALSE);
/* setting volume 0 changes voice gain */ volume = -600;
On Fri Sep 19 07:29:21 2025 +0000, Alexandre Julliard wrote:
I don't know how much changes is acceptable, and we will probably want
to keep differences minimal, but @julliard can maybe tell us whether he prefers external libs to be slightly modified, vs Wine code to workaround their behavior. Patching the bundled libs is OK if the changes are small. Of course getting it fixed upstream is even better.
Done.
v3: - Removed the resonance hump compensation from `fluid_iir_filter_q_from_dB`. - Added a named constant for the base gain.
This merge request was approved by Rémi Bernon.
This merge request was approved by Michael Stefaniuc.