dmime: Support playing secondary and control segments.
[wine.git] / dlls / dmusic / wave.c
blob27f7224af490f44569e345d9e92b69fd0697fccb
1 /*
2 * Copyright 2023 RĂ©mi Bernon for CodeWeavers
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include "dmusic_private.h"
20 #include "soundfont.h"
22 WINE_DEFAULT_DEBUG_CHANNEL(dmusic);
24 struct sample
26 WSMPL head;
27 WLOOP loops[];
30 C_ASSERT(sizeof(struct sample) == offsetof(struct sample, loops[0]));
32 struct wave
34 IUnknown IUnknown_iface;
35 struct dmobject dmobj;
36 LONG ref;
38 struct sample *sample;
39 WAVEFORMATEX *format;
40 UINT data_size;
41 void *data;
44 static inline struct wave *impl_from_IUnknown(IUnknown *iface)
46 return CONTAINING_RECORD(iface, struct wave, IUnknown_iface);
49 static HRESULT WINAPI wave_QueryInterface(IUnknown *iface, REFIID riid, void **ret_iface)
51 struct wave *This = impl_from_IUnknown(iface);
53 TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface);
55 if (IsEqualIID(riid, &IID_IUnknown))
57 *ret_iface = &This->IUnknown_iface;
58 IUnknown_AddRef(&This->IUnknown_iface);
59 return S_OK;
62 if (IsEqualIID(riid, &IID_IDirectMusicObject))
64 *ret_iface = &This->dmobj.IDirectMusicObject_iface;
65 IDirectMusicObject_AddRef(&This->dmobj.IDirectMusicObject_iface);
66 return S_OK;
69 if (IsEqualIID(riid, &IID_IPersistStream))
71 *ret_iface = &This->dmobj.IPersistStream_iface;
72 IDirectMusicObject_AddRef(&This->dmobj.IPersistStream_iface);
73 return S_OK;
76 WARN("(%p, %s, %p): not found\n", This, debugstr_dmguid(riid), ret_iface);
77 *ret_iface = NULL;
78 return E_NOINTERFACE;
81 static ULONG WINAPI wave_AddRef(IUnknown *iface)
83 struct wave *This = impl_from_IUnknown(iface);
84 LONG ref = InterlockedIncrement(&This->ref);
85 TRACE("(%p) ref=%ld\n", This, ref);
86 return ref;
89 static ULONG WINAPI wave_Release(IUnknown *iface)
91 struct wave *This = impl_from_IUnknown(iface);
92 LONG ref = InterlockedDecrement(&This->ref);
94 TRACE("(%p) ref=%ld\n", This, ref);
96 if (!ref)
98 free(This->format);
99 free(This->data);
100 free(This->sample);
101 free(This);
104 return ref;
107 static const IUnknownVtbl unknown_vtbl =
109 wave_QueryInterface,
110 wave_AddRef,
111 wave_Release,
114 static HRESULT parse_wsmp_chunk(struct wave *This, IStream *stream, struct chunk_entry *chunk)
116 struct sample *sample;
117 WSMPL wsmpl;
118 HRESULT hr;
119 UINT size;
121 if (chunk->size < sizeof(wsmpl)) return E_INVALIDARG;
122 if (FAILED(hr = stream_read(stream, &wsmpl, sizeof(wsmpl)))) return hr;
123 if (chunk->size != wsmpl.cbSize + sizeof(WLOOP) * wsmpl.cSampleLoops) return E_INVALIDARG;
124 if (wsmpl.cbSize != sizeof(wsmpl)) return E_INVALIDARG;
125 if (wsmpl.cSampleLoops > 1) FIXME("Not implemented: found more than one wave loop\n");
127 size = offsetof(struct sample, loops[wsmpl.cSampleLoops]);
128 if (!(sample = malloc(size))) return E_OUTOFMEMORY;
129 sample->head = wsmpl;
131 size = sizeof(WLOOP) * wsmpl.cSampleLoops;
132 if (FAILED(hr = stream_read(stream, sample->loops, size))) free(sample);
133 else This->sample = sample;
135 return hr;
138 static HRESULT parse_wave_chunk(struct wave *This, IStream *stream, struct chunk_entry *parent)
140 struct chunk_entry chunk = {.parent = parent};
141 HRESULT hr;
143 while ((hr = stream_next_chunk(stream, &chunk)) == S_OK)
145 switch (MAKE_IDTYPE(chunk.id, chunk.type))
147 case mmioFOURCC('f','m','t',' '):
148 if (!(This->format = malloc(chunk.size))) return E_OUTOFMEMORY;
149 hr = stream_chunk_get_data(stream, &chunk, This->format, chunk.size);
150 break;
152 case mmioFOURCC('d','a','t','a'):
153 if (!(This->data = malloc(chunk.size))) return E_OUTOFMEMORY;
154 hr = stream_chunk_get_data(stream, &chunk, This->data, chunk.size);
155 if (SUCCEEDED(hr)) This->data_size = chunk.size;
156 break;
158 case FOURCC_WSMP:
159 hr = parse_wsmp_chunk(This, stream, &chunk);
160 break;
162 case mmioFOURCC('w','a','v','u'):
163 case mmioFOURCC('s','m','p','l'):
164 case MAKE_IDTYPE(FOURCC_LIST, DMUS_FOURCC_INFO_LIST):
165 break;
167 default:
168 FIXME("Ignoring chunk %s %s\n", debugstr_fourcc(chunk.id), debugstr_fourcc(chunk.type));
169 break;
172 if (FAILED(hr)) break;
175 if (This->format && This->data) return S_OK;
176 return hr;
179 static inline struct wave *impl_from_IDirectMusicObject(IDirectMusicObject *iface)
181 return CONTAINING_RECORD(iface, struct wave, dmobj.IDirectMusicObject_iface);
184 static HRESULT WINAPI wave_object_ParseDescriptor(IDirectMusicObject *iface,
185 IStream *stream, DMUS_OBJECTDESC *desc)
187 struct chunk_entry chunk = {0};
188 HRESULT hr;
190 TRACE("(%p, %p, %p)\n", iface, stream, desc);
192 if (!stream || !desc) return E_POINTER;
194 if ((hr = stream_next_chunk(stream, &chunk)) == S_OK)
196 switch (MAKE_IDTYPE(chunk.id, chunk.type))
198 case MAKE_IDTYPE(FOURCC_RIFF, mmioFOURCC('W','A','V','E')):
199 hr = dmobj_parsedescriptor(stream, &chunk, desc,
200 DMUS_OBJ_NAME_INFO | DMUS_OBJ_OBJECT | DMUS_OBJ_VERSION);
201 break;
203 default:
204 WARN("Invalid wave chunk %s %s\n", debugstr_fourcc(chunk.id), debugstr_fourcc(chunk.type));
205 hr = DMUS_E_CHUNKNOTFOUND;
206 break;
210 if (FAILED(hr)) return hr;
212 TRACE("returning descriptor:\n");
213 dump_DMUS_OBJECTDESC(desc);
214 return S_OK;
217 static const IDirectMusicObjectVtbl wave_object_vtbl =
219 dmobj_IDirectMusicObject_QueryInterface,
220 dmobj_IDirectMusicObject_AddRef,
221 dmobj_IDirectMusicObject_Release,
222 dmobj_IDirectMusicObject_GetDescriptor,
223 dmobj_IDirectMusicObject_SetDescriptor,
224 wave_object_ParseDescriptor,
227 static inline struct wave *impl_from_IPersistStream(IPersistStream *iface)
229 return CONTAINING_RECORD(iface, struct wave, dmobj.IPersistStream_iface);
232 static HRESULT WINAPI wave_persist_stream_Load(IPersistStream *iface, IStream *stream)
234 struct wave *This = impl_from_IPersistStream(iface);
235 struct chunk_entry chunk = {0};
236 HRESULT hr;
238 TRACE("(%p, %p)\n", This, stream);
240 if (!stream) return E_POINTER;
242 if ((hr = stream_next_chunk(stream, &chunk)) == S_OK)
244 switch (MAKE_IDTYPE(chunk.id, chunk.type))
246 case MAKE_IDTYPE(FOURCC_RIFF, mmioFOURCC('W','A','V','E')):
247 hr = parse_wave_chunk(This, stream, &chunk);
248 break;
250 default:
251 WARN("Invalid wave chunk %s %s\n", debugstr_fourcc(chunk.id), debugstr_fourcc(chunk.type));
252 hr = DMUS_E_UNSUPPORTED_STREAM;
253 break;
257 stream_skip_chunk(stream, &chunk);
258 return hr;
261 static const IPersistStreamVtbl wave_persist_stream_vtbl =
263 dmobj_IPersistStream_QueryInterface,
264 dmobj_IPersistStream_AddRef,
265 dmobj_IPersistStream_Release,
266 dmobj_IPersistStream_GetClassID,
267 unimpl_IPersistStream_IsDirty,
268 wave_persist_stream_Load,
269 unimpl_IPersistStream_Save,
270 unimpl_IPersistStream_GetSizeMax,
273 HRESULT wave_create(IDirectMusicObject **ret_iface)
275 struct wave *obj;
277 if (!(obj = calloc(1, sizeof(*obj)))) return E_OUTOFMEMORY;
278 obj->IUnknown_iface.lpVtbl = &unknown_vtbl;
279 obj->ref = 1;
280 dmobject_init(&obj->dmobj, &CLSID_DirectSoundWave, &obj->IUnknown_iface);
281 obj->dmobj.IDirectMusicObject_iface.lpVtbl = &wave_object_vtbl;
282 obj->dmobj.IPersistStream_iface.lpVtbl = &wave_persist_stream_vtbl;
284 *ret_iface = &obj->dmobj.IDirectMusicObject_iface;
285 return S_OK;
288 HRESULT wave_create_from_chunk(IStream *stream, struct chunk_entry *parent, IDirectMusicObject **ret_iface)
290 struct wave *This;
291 IDirectMusicObject *iface;
292 HRESULT hr;
294 TRACE("(%p, %p, %p)\n", stream, parent, ret_iface);
296 if (FAILED(hr = wave_create(&iface))) return hr;
297 This = impl_from_IDirectMusicObject(iface);
299 if (FAILED(hr = parse_wave_chunk(This, stream, parent)))
301 IDirectMusicObject_Release(iface);
302 return DMUS_E_UNSUPPORTED_STREAM;
305 if (TRACE_ON(dmusic))
307 UINT i;
309 TRACE("*** Created DirectMusicWave %p\n", This);
310 TRACE(" - format: %p\n", This->format);
311 if (This->format)
313 TRACE(" - wFormatTag: %u\n", This->format->wFormatTag);
314 TRACE(" - nChannels: %u\n", This->format->nChannels);
315 TRACE(" - nSamplesPerSec: %lu\n", This->format->nSamplesPerSec);
316 TRACE(" - nAvgBytesPerSec: %lu\n", This->format->nAvgBytesPerSec);
317 TRACE(" - nBlockAlign: %u\n", This->format->nBlockAlign);
318 TRACE(" - wBitsPerSample: %u\n", This->format->wBitsPerSample);
319 TRACE(" - cbSize: %u\n", This->format->cbSize);
321 if (This->sample)
323 TRACE(" - sample: {size: %lu, unity_note: %u, fine_tune: %d, attenuation: %ld, options: %#lx, loops: %lu}\n",
324 This->sample->head.cbSize, This->sample->head.usUnityNote,
325 This->sample->head.sFineTune, This->sample->head.lAttenuation,
326 This->sample->head.fulOptions, This->sample->head.cSampleLoops);
327 for (i = 0; i < This->sample->head.cSampleLoops; i++)
328 TRACE(" - loops[%u]: {size: %lu, type: %lu, start: %lu, length: %lu}\n", i,
329 This->sample->loops[i].cbSize, This->sample->loops[i].ulType,
330 This->sample->loops[i].ulStart, This->sample->loops[i].ulLength);
334 *ret_iface = iface;
335 return S_OK;
338 HRESULT wave_create_from_soundfont(struct soundfont *soundfont, UINT index, IDirectMusicObject **ret_iface)
340 struct sf_sample *sf_sample = soundfont->shdr + index;
341 struct sample *sample = NULL;
342 WAVEFORMATEX *format = NULL;
343 HRESULT hr = E_OUTOFMEMORY;
344 UINT data_size, offset;
345 struct wave *This;
346 void *data = NULL;
347 IDirectMusicObject *iface;
349 TRACE("(%p, %u, %p)\n", soundfont, index, ret_iface);
351 if (sf_sample->sample_link) FIXME("Stereo sample not supported\n");
353 if (!(format = calloc(1, sizeof(*format)))) goto failed;
354 format->wFormatTag = WAVE_FORMAT_PCM;
355 format->nChannels = 1;
356 format->wBitsPerSample = 16;
357 format->nSamplesPerSec = sf_sample->sample_rate;
358 format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8;
359 format->nAvgBytesPerSec = format->nBlockAlign * format->nSamplesPerSec;
361 if (!(sample = calloc(1, offsetof(struct sample, loops[1])))) goto failed;
362 sample->head.cbSize = sizeof(sample->head);
363 sample->head.cSampleLoops = 1;
364 sample->loops[0].ulStart = sf_sample->start_loop - sf_sample->start;
365 sample->loops[0].ulLength = sf_sample->end_loop - sf_sample->start_loop;
367 data_size = sf_sample->end - sf_sample->start;
368 if (!(data = malloc(data_size * format->nBlockAlign))) goto failed;
369 offset = sf_sample->start * format->nBlockAlign / format->nChannels;
370 memcpy(data, soundfont->sdta + offset, data_size);
372 if (FAILED(hr = wave_create(&iface))) goto failed;
374 This = impl_from_IDirectMusicObject(iface);
375 This->format = format;
376 This->sample = sample;
377 This->data_size = data_size;
378 This->data = data;
380 if (TRACE_ON(dmusic))
382 UINT i;
384 TRACE("*** Created DirectMusicWave %p\n", This);
385 TRACE(" - format: %p\n", This->format);
386 if (This->format)
388 TRACE(" - wFormatTag: %u\n", This->format->wFormatTag);
389 TRACE(" - nChannels: %u\n", This->format->nChannels);
390 TRACE(" - nSamplesPerSec: %lu\n", This->format->nSamplesPerSec);
391 TRACE(" - nAvgBytesPerSec: %lu\n", This->format->nAvgBytesPerSec);
392 TRACE(" - nBlockAlign: %u\n", This->format->nBlockAlign);
393 TRACE(" - wBitsPerSample: %u\n", This->format->wBitsPerSample);
394 TRACE(" - cbSize: %u\n", This->format->cbSize);
397 TRACE(" - sample: {size: %lu, unity_note: %u, fine_tune: %d, attenuation: %ld, options: %#lx, loops: %lu}\n",
398 This->sample->head.cbSize, This->sample->head.usUnityNote,
399 This->sample->head.sFineTune, This->sample->head.lAttenuation,
400 This->sample->head.fulOptions, This->sample->head.cSampleLoops);
401 for (i = 0; i < This->sample->head.cSampleLoops; i++)
402 TRACE(" - loops[%u]: {size: %lu, type: %lu, start: %lu, length: %lu}\n", i,
403 This->sample->loops[i].cbSize, This->sample->loops[i].ulType,
404 This->sample->loops[i].ulStart, This->sample->loops[i].ulLength);
407 *ret_iface = iface;
408 return S_OK;
410 failed:
411 free(data);
412 free(sample);
413 free(format);
414 return hr;
417 HRESULT wave_download_to_port(IDirectMusicObject *iface, IDirectMusicPortDownload *port, DWORD *id)
419 struct download_buffer
421 DMUS_DOWNLOADINFO info;
422 ULONG offsets[2];
423 DMUS_WAVE wave;
424 DMUS_WAVEDATA data;
425 } *buffer;
427 struct wave *This = impl_from_IDirectMusicObject(iface);
428 DWORD size = offsetof(struct download_buffer, data.byData[This->data_size]);
429 IDirectMusicDownload *download;
430 HRESULT hr;
432 if (FAILED(hr = IDirectMusicPortDownload_AllocateBuffer(port, size, &download))) return hr;
434 if (SUCCEEDED(hr = IDirectMusicDownload_GetBuffer(download, (void **)&buffer, &size))
435 && SUCCEEDED(hr = IDirectMusicPortDownload_GetDLId(port, &buffer->info.dwDLId, 1)))
437 buffer->info.dwDLType = DMUS_DOWNLOADINFO_WAVE;
438 buffer->info.dwNumOffsetTableEntries = 2;
439 buffer->info.cbSize = size;
441 buffer->offsets[0] = offsetof(struct download_buffer, wave);
442 buffer->offsets[1] = offsetof(struct download_buffer, data);
444 buffer->wave.WaveformatEx = *This->format;
445 buffer->wave.ulWaveDataIdx = 1;
446 buffer->wave.ulCopyrightIdx = 0;
447 buffer->wave.ulFirstExtCkIdx = 0;
449 buffer->data.cbSize = This->data_size;
450 memcpy(buffer->data.byData, This->data, This->data_size);
452 if (SUCCEEDED(hr = IDirectMusicPortDownload_Download(port, download))) *id = buffer->info.dwDLId;
453 else WARN("Failed to download wave to port, hr %#lx\n", hr);
456 IDirectMusicDownload_Release(download);
457 return hr;
460 HRESULT wave_download_to_dsound(IDirectMusicObject *iface, IDirectSound *dsound, IDirectSoundBuffer **ret_iface)
462 struct wave *This = impl_from_IDirectMusicObject(iface);
463 DSBUFFERDESC desc =
465 .dwSize = sizeof(desc),
466 .dwBufferBytes = This->data_size,
467 .lpwfxFormat = This->format,
469 IDirectSoundBuffer *buffer;
470 HRESULT hr;
471 void *data;
472 DWORD size;
474 TRACE("%p, %p, %p\n", This, dsound, ret_iface);
476 if (FAILED(hr = IDirectSound_CreateSoundBuffer(dsound, &desc, &buffer, NULL)))
478 WARN("Failed to create direct sound buffer, hr %#lx\n", hr);
479 return hr;
482 if (SUCCEEDED(hr = IDirectSoundBuffer_Lock(buffer, 0, This->data_size, &data, &size, NULL, 0, 0)))
484 memcpy(data, This->data, This->data_size);
485 hr = IDirectSoundBuffer_Unlock(buffer, data, This->data_size, NULL, 0);
488 if (FAILED(hr))
490 WARN("Failed to download wave to dsound, hr %#lx\n", hr);
491 IDirectSoundBuffer_Release(buffer);
492 return hr;
495 *ret_iface = buffer;
496 return S_OK;
499 HRESULT wave_get_duration(IDirectMusicObject *iface, REFERENCE_TIME *duration)
501 struct wave *This = impl_from_IDirectMusicObject(iface);
502 *duration = (REFERENCE_TIME)This->data_size * 10000000 / This->format->nAvgBytesPerSec;
503 return S_OK;