-- v3: dmsynth: Release the waves when voices finish playing. dmsynth: Implement callback support in synth_Unload(). dmsynth: Call fluid_sample_set_sound_data() with copy_data = FALSE. dmusic: Reuse downloaded waves.
From: Anton Baskanov baskanov@gmail.com
Some soundfonts contain multiple zones at both preset and instrument levels, which leads to creation of multiple regions that reference the same wave. Reusing already downloaded waves prevents running out of memory when loading FluidR3_GM.sf2. --- dlls/dmusic/instrument.c | 62 +++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 17 deletions(-)
diff --git a/dlls/dmusic/instrument.c b/dlls/dmusic/instrument.c index 8311c690877..a524ab2c627 100644 --- a/dlls/dmusic/instrument.c +++ b/dlls/dmusic/instrument.c @@ -26,6 +26,14 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmusic);
static const GUID IID_IDirectMusicInstrumentPRIVATE = { 0xbcb20080, 0xa40c, 0x11d1, { 0x86, 0xbc, 0x00, 0xc0, 0x4f, 0xbf, 0x8f, 0xef } };
+struct downloaded_wave +{ + struct list entry; + + DWORD index; + DWORD id; +}; + #define CONN_SRC_CC2 0x0082 #define CONN_SRC_RPN0 0x0100
@@ -76,6 +84,7 @@ struct instrument INSTHEADER header; IDirectMusicDownload *download; struct collection *collection; + struct list downloaded_waves; struct list articulations; struct list regions; }; @@ -237,6 +246,7 @@ static HRESULT instrument_create(struct collection *collection, IDirectMusicInst instrument->IDirectMusicDownloadedInstrument_iface.lpVtbl = &downloaded_instrument_vtbl; instrument->ref = 1; collection_internal_addref((instrument->collection = collection)); + list_init(&instrument->downloaded_waves); list_init(&instrument->articulations); list_init(&instrument->regions);
@@ -742,6 +752,7 @@ HRESULT instrument_download_to_port(IDirectMusicInstrument *iface, IDirectMusicP IDirectMusicDownloadedInstrument **downloaded) { struct instrument *This = impl_from_IDirectMusicInstrument(iface); + struct downloaded_wave *downloaded_wave; struct articulation *articulation; struct download_buffer *buffer; IDirectMusicDownload *download; @@ -824,12 +835,34 @@ HRESULT instrument_download_to_port(IDirectMusicInstrument *iface, IDirectMusicP dmus_region->WSMP = region->wave_sample; dmus_region->WLOOP[0] = region->wave_loop;
- if (SUCCEEDED(hr = collection_get_wave(This->collection, region->wave_link.ulTableIndex, &wave))) + LIST_FOR_EACH_ENTRY(downloaded_wave, &This->downloaded_waves, struct downloaded_wave, entry) { - hr = wave_download_to_port(wave, port, &dmus_region->WaveLink.ulTableIndex); - IDirectMusicObject_Release(wave); + if (downloaded_wave->index == region->wave_link.ulTableIndex) + break; } - if (FAILED(hr)) goto failed; + if (&downloaded_wave->entry == &This->downloaded_waves) + { + downloaded_wave = calloc(1, sizeof(struct downloaded_wave)); + if (!downloaded_wave) + { + hr = E_OUTOFMEMORY; + goto failed; + } + downloaded_wave->index = region->wave_link.ulTableIndex; + if (SUCCEEDED(hr = collection_get_wave(This->collection, region->wave_link.ulTableIndex, &wave))) + { + hr = wave_download_to_port(wave, port, &downloaded_wave->id); + IDirectMusicObject_Release(wave); + } + if (FAILED(hr)) + { + free(downloaded_wave); + goto failed; + } + list_add_tail(&This->downloaded_waves, &downloaded_wave->entry); + } + + dmus_region->WaveLink.ulTableIndex = downloaded_wave->id;
write_articulation_download(®ion->articulations, buffer->offsets, &ptr, index, &dmus_region->ulRegionArtIdx, &index); @@ -854,35 +887,30 @@ failed: HRESULT instrument_unload_from_port(IDirectMusicDownloadedInstrument *iface, IDirectMusicPortDownload *port) { struct instrument *This = impl_from_IDirectMusicDownloadedInstrument(iface); - struct download_buffer *buffer; - DWORD size; HRESULT hr;
if (!This->download) return DMUS_E_NOT_DOWNLOADED_TO_PORT;
if (FAILED(hr = IDirectMusicPortDownload_Unload(port, This->download))) WARN("Failed to unload instrument download buffer, hr %#lx\n", hr); - else if (SUCCEEDED(hr = IDirectMusicDownload_GetBuffer(This->download, (void **)&buffer, &size))) + else { + struct downloaded_wave *downloaded_wave; IDirectMusicDownload *wave_download; - DMUS_INSTRUMENT *instrument; - BYTE *ptr = (BYTE *)buffer; - DMUS_REGION *region; - UINT index; + void *next;
- instrument = (DMUS_INSTRUMENT *)(ptr + buffer->offsets[0]); - for (index = instrument->ulFirstRegionIdx; index; index = region->ulNextRegionIdx) + LIST_FOR_EACH_ENTRY_SAFE(downloaded_wave, next, &This->downloaded_waves, struct downloaded_wave, entry) { - region = (DMUS_REGION *)(ptr + buffer->offsets[index]); - - if (FAILED(hr = IDirectMusicPortDownload_GetBuffer(port, region->WaveLink.ulTableIndex, &wave_download))) - WARN("Failed to get wave download with id %#lx, hr %#lx\n", region->WaveLink.ulTableIndex, hr); + if (FAILED(hr = IDirectMusicPortDownload_GetBuffer(port, downloaded_wave->id, &wave_download))) + WARN("Failed to get wave download with id %#lx, hr %#lx\n", downloaded_wave->id, hr); else { if (FAILED(hr = IDirectMusicPortDownload_Unload(port, wave_download))) WARN("Failed to unload wave download buffer, hr %#lx\n", hr); IDirectMusicDownload_Release(wave_download); } + list_remove(&downloaded_wave->entry); + free(downloaded_wave); } }
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index 0fe7a92a697..daae8f7fcaa 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -873,8 +873,10 @@ static HRESULT synth_download_wave(struct synth *This, DMUS_DOWNLOADINFO *info, return FLUID_FAILED; }
+ /* Although the doc says there should be 8-frame padding around the data, + * FluidSynth doesn't actually require this since version 1.0.8. */ fluid_sample_set_sound_data(wave->fluid_sample, wave->samples, NULL, wave->sample_count, - wave->format.nSamplesPerSec, TRUE); + wave->format.nSamplesPerSec, FALSE);
EnterCriticalSection(&This->cs); list_add_tail(&This->waves, &wave->entry); @@ -1930,11 +1932,8 @@ static int synth_preset_noteon(fluid_preset_t *fluid_preset, fluid_synth_t *flui else FIXME("Unsupported loop type %lu\n", loop->ulType);
- /* When copy_data is TRUE, fluid_sample_set_sound_data() adds - * 8-frame padding around the sample data. Offset the loop points - * to compensate for this. */ - fluid_voice_gen_set(fluid_voice, GEN_STARTLOOPADDROFS, 8 + loop->ulStart); - fluid_voice_gen_set(fluid_voice, GEN_ENDLOOPADDROFS, 8 + loop->ulStart + loop->ulLength); + fluid_voice_gen_set(fluid_voice, GEN_STARTLOOPADDROFS, loop->ulStart); + fluid_voice_gen_set(fluid_voice, GEN_ENDLOOPADDROFS, loop->ulStart + loop->ulLength); } fluid_voice_gen_set(fluid_voice, GEN_OVERRIDEROOTKEY, region->wave_sample.usUnityNote); fluid_voice_gen_set(fluid_voice, GEN_FINETUNE, region->wave_sample.sFineTune);
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index daae8f7fcaa..3ffb06a3884 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -212,6 +212,9 @@ struct wave LONG ref; UINT id;
+ HRESULT (CALLBACK *callback)(HANDLE handle, HANDLE user_data); + HANDLE user_data; + fluid_sample_t *fluid_sample;
WAVEFORMATEX format; @@ -231,6 +234,8 @@ static void wave_release(struct wave *wave) ULONG ref = InterlockedDecrement(&wave->ref); if (!ref) { + if (wave->callback) + wave->callback(wave, wave->user_data); delete_fluid_sample(wave->fluid_sample); free(wave); } @@ -942,7 +947,6 @@ static HRESULT WINAPI synth_Unload(IDirectMusicSynth8 *iface, HANDLE handle, struct wave *wave;
TRACE("(%p)->(%p, %p, %p)\n", This, handle, callback, user_data); - if (callback) FIXME("Unload callbacks not implemented\n");
EnterCriticalSection(&This->cs); LIST_FOR_EACH_ENTRY(instrument, &This->instruments, struct instrument, entry) @@ -961,6 +965,8 @@ static HRESULT WINAPI synth_Unload(IDirectMusicSynth8 *iface, HANDLE handle, { if (wave == handle) { + wave->callback = callback; + wave->user_data = user_data; list_remove(&wave->entry); LeaveCriticalSection(&This->cs);
From: Anton Baskanov baskanov@gmail.com
--- dlls/dmsynth/synth.c | 25 +++++++++++++++++++++++-- dlls/dmsynth/tests/dmsynth.c | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index 3ffb06a3884..cc095a5e89d 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -651,7 +651,8 @@ static HRESULT WINAPI synth_Close(IDirectMusicSynth8 *iface) LIST_FOR_EACH_ENTRY_SAFE(voice, next, &This->voices, struct voice, entry) { list_remove(&voice->entry); - wave_release(voice->wave); + if (voice->wave) + wave_release(voice->wave); free(voice); }
@@ -1128,6 +1129,7 @@ static HRESULT WINAPI synth_Render(IDirectMusicSynth8 *iface, short *buffer, { struct synth *This = impl_from_IDirectMusicSynth8(iface); struct event *event, *next; + struct voice *voice; int chan;
TRACE("(%p, %p, %ld, %I64d)\n", This, buffer, length, position); @@ -1181,6 +1183,21 @@ static HRESULT WINAPI synth_Render(IDirectMusicSynth8 *iface, short *buffer, LeaveCriticalSection(&This->cs);
if (length) fluid_synth_write_s16(This->fluid_synth, length, buffer, 0, 2, buffer, 1, 2); + + /* fluid_synth_write_s16() does not update the voice status, so we have to + * trigger the update manually */ + fluid_synth_get_active_voice_count(This->fluid_synth); + + LIST_FOR_EACH_ENTRY(voice, &This->voices, struct voice, entry) + { + if (fluid_voice_is_playing(voice->fluid_voice)) + continue; + if (!voice->wave) + continue; + wave_release(voice->wave); + voice->wave = NULL; + } + return S_OK; }
@@ -1907,7 +1924,11 @@ static int synth_preset_noteon(fluid_preset_t *fluid_preset, fluid_synth_t *flui { if (voice->fluid_voice == fluid_voice) { - wave_release(voice->wave); + if (voice->wave) + { + wave_release(voice->wave); + voice->wave = NULL; + } break; } } diff --git a/dlls/dmsynth/tests/dmsynth.c b/dlls/dmsynth/tests/dmsynth.c index cb19e4997b8..b0dd4d8838b 100644 --- a/dlls/dmsynth/tests/dmsynth.c +++ b/dlls/dmsynth/tests/dmsynth.c @@ -1251,7 +1251,7 @@ static void test_IDirectMusicSynth(void) ok(!unload_called, "callback called\n"); hr = IDirectMusicSynth_Unload(synth, instrument_handle, NULL, NULL); ok(hr == S_OK, "got %#lx\n", hr); - todo_wine ok(unload_called, "callback not called\n"); + ok(unload_called, "callback not called\n");
hr = IDirectMusicSynth_Close(synth); ok(hr == S_OK, "got %#lx\n", hr);
On Tue Nov 4 19:03:18 2025 +0000, Anton Baskanov wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/9296/diffs?diff_id=221079&start_sha=25ead99b9c564495e45469d1cd457f8d94bc7cef#10f65150261f79fd17205ded12c05be754cd4053_915_912)
Done.
v3: - Remove and free the `downloaded_wave` structures while iterating.
This merge request was approved by Michael Stefaniuc.
Test failures are unrelated to DMusic