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..cff498f4dc5 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 instrument_download +{ + DMUS_DOWNLOADINFO info; + ULONG offsets[4]; + DMUS_INSTRUMENT instrument; + DMUS_REGION region; + DMUS_ARTICULATION2 articulation; + CONNECTIONLIST connection_list; + CONNECTION connections[64]; +} DECLSPEC_ALIGN(8); + +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 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 DECLSPEC_ALIGN(8) = + { + .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 midi_message +{ + DMUS_EVENTHEADER header; + DWORD message; +} DECLSPEC_ALIGN(8); + +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 | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index 57a057b68b0..7fe42c86754 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -511,6 +511,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 +587,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 = 60.; + 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 7fe42c86754..b110e43aa47 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -340,6 +340,8 @@ struct synth IKsControl IKsControl_iface; LONG ref;
+ LONG volume0; + LONG volume1; DMUS_PORTCAPS caps; DMUS_PORTPARAMS params; BOOL active; @@ -448,6 +450,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; @@ -491,6 +499,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); } }
@@ -1105,10 +1115,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; @@ -1329,10 +1342,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); @@ -1367,6 +1414,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)); @@ -1975,6 +2028,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 cff498f4dc5..a2c238010bc 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/synth.c | 2 ++ dlls/dmsynth/tests/dmsynth.c | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index b110e43aa47..0516b76af9c 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -600,6 +600,8 @@ static HRESULT WINAPI synth_Open(IDirectMusicSynth8 *iface, DMUS_PORTPARAMS *par
/* native limits the total voice gain to 6 dB */ gain = 60.; + /* compensate gain added in fluid_iir_filter_q_from_dB/fluid_iir_filter_set_q */ + gain -= 15.05; fluid_settings_setnum(This->fluid_settings, "synth.gain", pow(10., gain / 200.));
if (!(This->fluid_synth = new_fluid_synth(This->fluid_settings))) diff --git a/dlls/dmsynth/tests/dmsynth.c b/dlls/dmsynth/tests/dmsynth.c index a2c238010bc..bb6fa0a3eb3 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);
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 8 ++++++++ dlls/dmsynth/tests/dmsynth.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index 0516b76af9c..9c5694740a9 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 CENTER_PAN_GAIN -30.10 + /* from src/rvoice/fluid_rvoice.h */ #define FLUID_LOOP_DURING_RELEASE 1 #define FLUID_LOOP_UNTIL_RELEASE 3 @@ -602,6 +604,8 @@ static HRESULT WINAPI synth_Open(IDirectMusicSynth8 *iface, DMUS_PORTPARAMS *par gain = 60.; /* compensate gain added in fluid_iir_filter_q_from_dB/fluid_iir_filter_set_q */ gain -= 15.05; + /* 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 +1943,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 bb6fa0a3eb3..b67ad4eaf46 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;