There are still some issues I need to fix, mainly around timing conversion between MIDI and dmusic. Right now MIDI files seem to be cut off before the end is reached.
Please have a look at the general approach in the meantime, I need to know if this is the right way to do this or not.
-- v13: dmime: Create a band track for MIDI segments. dmime/test: Add test for getting band track from midi file. dmime: Implement IDirectMusicTrack_Play for MIDI tracks. dmime: Implement getting/setting TempoParam for MIDI tracks. dmime: Parse MIDI tracks. dmime: Add stubs for MIDI tracks dmime: Parse MIDI headers. dmime/test: add MIDI loading test
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/tests/dmime.c | 20 ++++++++++++++++++++ dlls/dmime/tests/resource.rc | 2 ++ dlls/dmime/tests/test.mid | Bin 0 -> 163 bytes 3 files changed, 22 insertions(+) create mode 100644 dlls/dmime/tests/test.mid
diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 26fdf3ef2a5..d4be6130ed9 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1554,6 +1554,25 @@ static void test_segment(void) while (IDirectMusicSegment_Release(dms)); }
+static void test_midi(void) +{ + IDirectMusicSegment8 *segment = NULL; + IDirectMusicLoader8 *loader; + WCHAR test_mid[MAX_PATH]; + HRESULT hr; + + load_resource(L"test.mid", test_mid); + + hr = CoCreateInstance(&CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicLoader8, (void **)&loader); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IDirectMusicLoader8_LoadObjectFromFile(loader, &CLSID_DirectMusicSegment, + &IID_IDirectMusicSegment, test_mid, (void **)&segment); + ok(hr == S_OK, "got %#lx\n", hr); + IDirectMusicLoader8_Release(loader); + if (segment) IDirectMusicSegment8_Release(segment); +} + static void _add_track(IDirectMusicSegment8 *seg, REFCLSID class, const char *name, DWORD group) { IDirectMusicTrack *track; @@ -4706,6 +4725,7 @@ START_TEST(dmime) test_audiopathconfig(); test_graph(); test_segment(); + test_midi(); test_gettrack(); test_segment_param(); test_track(); diff --git a/dlls/dmime/tests/resource.rc b/dlls/dmime/tests/resource.rc index d49b647b934..e6a06ae105b 100644 --- a/dlls/dmime/tests/resource.rc +++ b/dlls/dmime/tests/resource.rc @@ -21,3 +21,5 @@ /* ffmpeg -f lavfi -i "sine=frequency=600" -t 0.1 -ar 44100 -f wav -acodec pcm_u8 test.wav */ /* @makedep: test.wav */ test.wav RCDATA test.wav +/* @makedep: test.mid */ +test.mid RCDATA test.mid diff --git a/dlls/dmime/tests/test.mid b/dlls/dmime/tests/test.mid new file mode 100644 index 0000000000000000000000000000000000000000..7a46eb85d11d61ba23528912e5ce9c6fd1a0e533 GIT binary patch literal 163 zcmeYb$w*;fU|?flWME=^;2Tnu4dmrA{AXqj$V|-3XZRn%!onoM!SFwl2`KSDkePj< z0>g$%1_q$6RG>Z{hLn1S)Ov;k3=A9CQy91$7@`>%Vu30Z85tNR*aS4n+b}SqQw+`l b?e;bd49*OU|MeN5mP#O53b71m1V|nLiNz$G
literal 0 HcmV?d00001
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/Makefile.in | 1 + dlls/dmime/dmime_private.h | 6 +++ dlls/dmime/midi.c | 105 +++++++++++++++++++++++++++++++++++++ dlls/dmime/segment.c | 16 +++++- 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 dlls/dmime/midi.c
diff --git a/dlls/dmime/Makefile.in b/dlls/dmime/Makefile.in index cf041ef2b27..f8f622f8f3d 100644 --- a/dlls/dmime/Makefile.in +++ b/dlls/dmime/Makefile.in @@ -10,6 +10,7 @@ SOURCES = \ graph.c \ lyricstrack.c \ markertrack.c \ + midi.c \ paramcontroltrack.c \ performance.c \ segment.c \ diff --git a/dlls/dmime/dmime_private.h b/dlls/dmime/dmime_private.h index 96ccd5daf8b..0a8390d85f2 100644 --- a/dlls/dmime/dmime_private.h +++ b/dlls/dmime/dmime_private.h @@ -48,6 +48,7 @@ * Interfaces */ typedef struct IDirectMusicAudioPathImpl IDirectMusicAudioPathImpl; +struct midi_parser;
/***************************************************************************** * ClassFactory @@ -68,6 +69,11 @@ extern HRESULT create_dmsysextrack(REFIID riid, void **ret_iface); extern HRESULT create_dmtempotrack(REFIID riid, void **ret_iface); extern HRESULT create_dmtimesigtrack(REFIID riid, void **ret_iface); extern HRESULT create_dmwavetrack(REFIID riid, void **ret_iface); +/* Create a new MIDI file parser. Note the stream might still be modified even + * when this function fails. */ +extern HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser); +extern HRESULT midi_parser_next_track(struct midi_parser *parser, IDirectMusicTrack **out_track, MUSIC_TIME *out_length); +extern void midi_parser_destroy(struct midi_parser *parser);
extern void set_audiopath_perf_pointer(IDirectMusicAudioPath*,IDirectMusicPerformance8*); extern void set_audiopath_dsound_buffer(IDirectMusicAudioPath*,IDirectSoundBuffer*); diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c new file mode 100644 index 00000000000..a7e57947c6f --- /dev/null +++ b/dlls/dmime/midi.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 Yuxuan Shui for CodeWeavers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "dmime_private.h" +#include "winternl.h" + +WINE_DEFAULT_DEBUG_CHANNEL(dmime); + +#ifdef WORDS_BIGENDIAN +#define GET_BE_WORD(x) (x) +#define GET_BE_DWORD(x) (x) +#else +#define GET_BE_WORD(x) RtlUshortByteSwap(x) +#define GET_BE_DWORD(x) RtlUlongByteSwap(x) +#endif + +struct midi_parser +{ + IStream *stream; +}; + +HRESULT midi_parser_next_track(struct midi_parser *parser, IDirectMusicTrack **out_track, MUSIC_TIME *out_length) +{ + TRACE("(%p, %p, %p): stub\n", parser, out_track, out_length); + return S_FALSE; +} + +HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser) +{ + LARGE_INTEGER offset; + DWORD length; + HRESULT hr; + WORD format, number_of_tracks, division; + struct midi_parser *parser; + + *out_parser = NULL; + + /* Skip over the 'MThd' magic number. */ + offset.QuadPart = 4; + if (FAILED(hr = IStream_Seek(stream, offset, STREAM_SEEK_SET, NULL))) return hr; + + if (FAILED(hr = IStream_Read(stream, &length, sizeof(length), NULL))) return hr; + length = GET_BE_DWORD(length); + if (length != 6) + { + WARN("Invalid MIDI header length %lu\n", length); + return DMUS_E_UNSUPPORTED_STREAM; + } + + if (FAILED(hr = IStream_Read(stream, &format, sizeof(format), NULL))) return hr; + format = GET_BE_WORD(format); + if (format > 2) + { + WARN("Invalid MIDI format %u\n", format); + return DMUS_E_UNSUPPORTED_STREAM; + } + if (format == 2) + { + FIXME("MIDI format 2 not implemented yet\n"); + return DMUS_E_UNSUPPORTED_STREAM; + } + + if (FAILED(hr = IStream_Read(stream, &number_of_tracks, sizeof(number_of_tracks), NULL))) + return hr; + + number_of_tracks = GET_BE_WORD(number_of_tracks); + if (FAILED(hr = IStream_Read(stream, &division, sizeof(division), NULL))) return hr; + division = GET_BE_WORD(division); + if (division & 0x8000) + { + WARN("SMPTE time division not implemented yet\n"); + return DMUS_E_UNSUPPORTED_STREAM; + } + + TRACE("MIDI format %u, %u tracks, division %u\n", format, number_of_tracks, division); + + parser = calloc(1, sizeof(struct midi_parser)); + if (!parser) return E_OUTOFMEMORY; + parser->stream = stream; + IStream_AddRef(stream); + *out_parser = parser; + + return hr; +} + +void midi_parser_destroy(struct midi_parser *parser) +{ + IStream_Release(parser->stream); + free(parser); +} diff --git a/dlls/dmime/segment.c b/dlls/dmime/segment.c index b208f89148c..82e06f4537d 100644 --- a/dlls/dmime/segment.c +++ b/dlls/dmime/segment.c @@ -791,7 +791,10 @@ static inline struct segment *impl_from_IPersistStream(IPersistStream *iface) static HRESULT WINAPI segment_persist_stream_Load(IPersistStream *iface, IStream *stream) { struct segment *This = impl_from_IPersistStream(iface); + IDirectMusicTrack *track; + MUSIC_TIME length; struct chunk_entry chunk = {0}; + struct midi_parser *midi_parser; HRESULT hr;
TRACE("(%p, %p): Loading\n", This, stream); @@ -807,7 +810,18 @@ static HRESULT WINAPI segment_persist_stream_Load(IPersistStream *iface, IStream break;
case mmioFOURCC('M','T','h','d'): - FIXME("MIDI file loading not supported\n"); + hr = midi_parser_new(stream, &midi_parser); + if (FAILED(hr)) break; + This->header.mtLength = 0; + while ((hr = midi_parser_next_track(midi_parser, &track, &length)) == S_OK) + { + hr = segment_append_track(This, track, 1, 0); + IDirectMusicTrack_Release(track); + if (FAILED(hr)) break; + if (length > This->header.mtLength) + This->header.mtLength = length; + } + midi_parser_destroy(midi_parser); break;
case MAKE_IDTYPE(FOURCC_RIFF, mmioFOURCC('W','A','V','E')):
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/midi.c | 261 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 260 insertions(+), 1 deletion(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index a7e57947c6f..f20fc79298d 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -29,15 +29,273 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmime); #define GET_BE_DWORD(x) RtlUlongByteSwap(x) #endif
+struct midi_sequence_track +{ + IDirectMusicTrack8 IDirectMusicTrack8_iface; + struct dmobject dmobj; /* IPersistStream only */ + LONG ref; +}; + +static inline struct midi_sequence_track *impl_from_IDirectMusicTrack8(IDirectMusicTrack8 *iface) +{ + return CONTAINING_RECORD(iface, struct midi_sequence_track, IDirectMusicTrack8_iface); +} + +static HRESULT WINAPI midi_sequence_track_QueryInterface(IDirectMusicTrack8 *iface, REFIID riid, void **ret_iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface); + + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDirectMusicTrack) || + IsEqualIID(riid, &IID_IDirectMusicTrack8)) + *ret_iface = &This->IDirectMusicTrack8_iface; + else if (IsEqualIID(riid, &IID_IPersistStream)) *ret_iface = &This->dmobj.IPersistStream_iface; + else + { + *ret_iface = NULL; + WARN("(%p, %s, %p): not found\n", This, debugstr_dmguid(riid), ret_iface); + return E_NOINTERFACE; + } + + IDirectMusicTrack8_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI midi_sequence_track_AddRef(IDirectMusicTrack8 *iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p): %lu\n", This, ref); + + return ref; +} + +static ULONG WINAPI midi_sequence_track_Release(IDirectMusicTrack8 *iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p): %lu\n", This, ref); + + if (!ref) free(This); + + return ref; +} + +static HRESULT WINAPI midi_sequence_track_Init(IDirectMusicTrack8 *iface, IDirectMusicSegment *pSegment) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p): stub\n", This, pSegment); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_InitPlay(IDirectMusicTrack8 *iface, + IDirectMusicSegmentState *pSegmentState, IDirectMusicPerformance *pPerformance, + void **ppStateData, DWORD dwVirtualTrack8ID, DWORD dwFlags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p, %p, %p, %ld, %ld): stub\n", This, pSegmentState, pPerformance, ppStateData, + dwVirtualTrack8ID, dwFlags); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_EndPlay(IDirectMusicTrack8 *iface, void *pStateData) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p): stub\n", This, pStateData); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_Play(IDirectMusicTrack8 *iface, void *state_data, + MUSIC_TIME start_time, MUSIC_TIME end_time, MUSIC_TIME time_offset, DWORD segment_flags, + IDirectMusicPerformance *performance, IDirectMusicSegmentState *segment_state, DWORD track_id) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %p, %ld, %ld, %ld, %#lx, %p, %p, %ld): stub\n", This, state_data, start_time, end_time, + time_offset, segment_flags, performance, segment_state, track_id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_GetParam(IDirectMusicTrack8 *iface, REFGUID type, + MUSIC_TIME time, MUSIC_TIME *next, void *param) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %ld, %p, %p): method not implemented\n", This, debugstr_dmguid(type), time, next, param); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_SetParam(IDirectMusicTrack8 *iface, REFGUID type, + MUSIC_TIME time, void *param) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %ld, %p): method not implemented\n", This, debugstr_dmguid(type), time, param); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_IsParamSupported(IDirectMusicTrack8 *iface, REFGUID type) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(type)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_AddNotificationType(IDirectMusicTrack8 *iface, REFGUID notiftype) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_RemoveNotificationType(IDirectMusicTrack8 *iface, REFGUID notiftype) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Clone(IDirectMusicTrack8 *iface, MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, IDirectMusicTrack **ppTrack) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %ld, %ld, %p): stub\n", This, mtStart, mtEnd, ppTrack); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_PlayEx(IDirectMusicTrack8 *iface, void *pStateData, + REFERENCE_TIME rtStart, REFERENCE_TIME rtEnd, REFERENCE_TIME rtOffset, DWORD dwFlags, + IDirectMusicPerformance *pPerf, IDirectMusicSegmentState *pSegSt, DWORD dwVirtualID) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p, 0x%s, 0x%s, 0x%s, %ld, %p, %p, %ld): stub\n", This, pStateData, + wine_dbgstr_longlong(rtStart), wine_dbgstr_longlong(rtEnd), + wine_dbgstr_longlong(rtOffset), dwFlags, pPerf, pSegSt, dwVirtualID); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_GetParamEx(IDirectMusicTrack8 *iface, REFGUID type, + REFERENCE_TIME time, REFERENCE_TIME *next, void *param, void *state, DWORD flags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %s, %p, %p, %p, %lx): method not implemented\n", This, debugstr_dmguid(type), + wine_dbgstr_longlong(time), next, param, state, flags); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_SetParamEx(IDirectMusicTrack8 *iface, REFGUID type, + REFERENCE_TIME time, void *param, void *state, DWORD flags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %s, %p, %p, %lx): method not implemented\n", This, debugstr_dmguid(type), + wine_dbgstr_longlong(time), param, state, flags); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Compose(IDirectMusicTrack8 *iface, IUnknown *context, + DWORD trackgroup, IDirectMusicTrack **track) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %p, %ld, %p): method not implemented\n", This, context, trackgroup, track); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Join(IDirectMusicTrack8 *iface, IDirectMusicTrack *newtrack, + MUSIC_TIME join, IUnknown *context, DWORD trackgroup, IDirectMusicTrack **resulttrack) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + TRACE("(%p, %p, %ld, %p, %ld, %p): method not implemented\n", This, newtrack, join, context, + trackgroup, resulttrack); + return E_NOTIMPL; +} +struct IDirectMusicTrack8Vtbl midi_sequence_track_vtbl = +{ + midi_sequence_track_QueryInterface, + midi_sequence_track_AddRef, + midi_sequence_track_Release, + midi_sequence_track_Init, + midi_sequence_track_InitPlay, + midi_sequence_track_EndPlay, + midi_sequence_track_Play, + midi_sequence_track_GetParam, + midi_sequence_track_SetParam, + midi_sequence_track_IsParamSupported, + midi_sequence_track_AddNotificationType, + midi_sequence_track_RemoveNotificationType, + midi_sequence_track_Clone, + midi_sequence_track_PlayEx, + midi_sequence_track_GetParamEx, + midi_sequence_track_SetParamEx, + midi_sequence_track_Compose, + midi_sequence_track_Join, +}; + +static inline struct midi_sequence_track *impl_from_IPersistStream(IPersistStream *iface) +{ + return CONTAINING_RECORD(iface, struct midi_sequence_track, dmobj.IPersistStream_iface); +} + +static HRESULT WINAPI track_IPersistStream_Load(IPersistStream *iface, IStream *stream) +{ + struct midi_sequence_track *This = impl_from_IPersistStream(iface); + + TRACE("(%p, %p): stub\n", This, stream); + return S_OK; +} + +static const IPersistStreamVtbl midi_sequence_track_persiststream_vtbl = +{ + dmobj_IPersistStream_QueryInterface, + dmobj_IPersistStream_AddRef, + dmobj_IPersistStream_Release, + dmobj_IPersistStream_GetClassID, + unimpl_IPersistStream_IsDirty, + track_IPersistStream_Load, + unimpl_IPersistStream_Save, + unimpl_IPersistStream_GetSizeMax, +}; + +HRESULT create_midiseqtrack(REFIID riid, void **ppobj) +{ + struct midi_sequence_track *track; + HRESULT hr; + + *ppobj = NULL; + if (!(track = calloc(1, sizeof(*track)))) return E_OUTOFMEMORY; + track->IDirectMusicTrack8_iface.lpVtbl = &midi_sequence_track_vtbl; + track->ref = 1; + dmobject_init(&track->dmobj, &CLSID_DirectMusicSeqTrack, (IUnknown *)&track->IDirectMusicTrack8_iface); + track->dmobj.IPersistStream_iface.lpVtbl = &midi_sequence_track_persiststream_vtbl; + + hr = IDirectMusicTrack8_QueryInterface(&track->IDirectMusicTrack8_iface, riid, ppobj); + IDirectMusicTrack8_Release(&track->IDirectMusicTrack8_iface); + + return hr; +} + struct midi_parser { IStream *stream; + WORD number_of_tracks, current_track; };
HRESULT midi_parser_next_track(struct midi_parser *parser, IDirectMusicTrack **out_track, MUSIC_TIME *out_length) { TRACE("(%p, %p, %p): stub\n", parser, out_track, out_length); - return S_FALSE; + if (parser->current_track >= parser->number_of_tracks) return S_FALSE; + *out_length = 0; + parser->current_track++; + return create_midiseqtrack(&IID_IDirectMusicTrack, (void **)out_track); }
HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser) @@ -92,6 +350,7 @@ HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser) parser = calloc(1, sizeof(struct midi_parser)); if (!parser) return E_OUTOFMEMORY; parser->stream = stream; + parser->number_of_tracks = number_of_tracks; IStream_AddRef(stream); *out_parser = parser;
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/midi.c | 312 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 304 insertions(+), 8 deletions(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index f20fc79298d..1c6d19e193c 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -29,11 +29,73 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmime); #define GET_BE_DWORD(x) RtlUlongByteSwap(x) #endif
+union event_data +{ + struct + { + BYTE byte[5]; + } fixed; + UINT integer; + struct + { + SIZE_T size; + BYTE *data; + } variable; +}; + +enum meta_event_type +{ + MIDI_META_SEQUENCE_NUMBER = 0x00, + MIDI_META_TEXT_EVENT = 0x01, + MIDI_META_COPYRIGHT_NOTICE = 0x02, + MIDI_META_TRACK_NAME = 0x03, + MIDI_META_INSTRUMENT_NAME = 0x04, + MIDI_META_LYRIC = 0x05, + MIDI_META_MARKER = 0x06, + MIDI_META_CUE_POINT = 0x07, + MIDI_META_CHANNEL_PREFIX_ASSIGNMENT = 0x20, + MIDI_META_END_OF_TRACK = 0x2f, + MIDI_META_SET_TEMPO = 0x51, + MIDI_META_SMPTE_OFFSET = 0x54, + MIDI_META_TIME_SIGNATURE = 0x58, + MIDI_META_KEY_SIGNATURE = 0x59, + MIDI_META_SEQUENCER_SPECIFIC = 0x7f, +}; + +struct event +{ + struct list entry; + DWORD deltaTime; + BYTE status; + /* type of meta events. */ + enum meta_event_type type; + union event_data data; +}; + struct midi_sequence_track { IDirectMusicTrack8 IDirectMusicTrack8_iface; struct dmobject dmobj; /* IPersistStream only */ LONG ref; + + /* MIDI deltaTime can be either tempo dependent or not. If + * ticks_per_quarter_note is 0, then ticks_per_second should be used. + * Converting MIDI time to DirectMusic MUSIC_TIME is complicated. MUSIC_TIME + * is number of quarter notes times DMUS_PPQ, this is tempo dependent. If + * MIDI time is tempo dependent as well, then we just need to multiply it by + * a factor (because MIDI can have a different division of quarter notes). + * + * If MIDI time is not tempo dependent however, the conversion would be + * difficult because tempo information can be stored in another track. And + * we need the tempo information to convert MIDI time to MUSIC_TIME. The + * only reasonable way to do this is to report a "fake", fixed tempo, which + * makes MUSIC_TIME and MIDI time the same. + */ + UINT ticks_per_quarter_note; + UINT ticks_per_second; + UINT event_count; + + struct list events; };
static inline struct midi_sequence_track *impl_from_IDirectMusicTrack8(IDirectMusicTrack8 *iface) @@ -41,6 +103,41 @@ static inline struct midi_sequence_track *impl_from_IDirectMusicTrack8(IDirectMu return CONTAINING_RECORD(iface, struct midi_sequence_track, IDirectMusicTrack8_iface); }
+static BOOL is_event_fixed_length(BYTE status, enum meta_event_type type) +{ + if (status == 0xf0 || status == 0xf7) return FALSE; + + if (status != 0xff) return TRUE; + + switch (type) + { + case MIDI_META_SEQUENCE_NUMBER: + case MIDI_META_CHANNEL_PREFIX_ASSIGNMENT: + case MIDI_META_END_OF_TRACK: + case MIDI_META_SET_TEMPO: + case MIDI_META_SMPTE_OFFSET: + case MIDI_META_TIME_SIGNATURE: + case MIDI_META_KEY_SIGNATURE: return TRUE; + + case MIDI_META_TEXT_EVENT: + case MIDI_META_COPYRIGHT_NOTICE: + case MIDI_META_TRACK_NAME: + case MIDI_META_INSTRUMENT_NAME: + case MIDI_META_LYRIC: + case MIDI_META_MARKER: + case MIDI_META_CUE_POINT: + case MIDI_META_SEQUENCER_SPECIFIC: return FALSE; + default: ERR("Unknown meta event type %02x\n", type); return TRUE; + } +} + +static void free_event(struct event *event) +{ + if (event->status == 0xff && !is_event_fixed_length(event->status, event->type)) + free(event->data.variable.data); + free(event); +} + static HRESULT WINAPI midi_sequence_track_QueryInterface(IDirectMusicTrack8 *iface, REFIID riid, void **ret_iface) { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); @@ -75,11 +172,20 @@ static ULONG WINAPI midi_sequence_track_AddRef(IDirectMusicTrack8 *iface) static ULONG WINAPI midi_sequence_track_Release(IDirectMusicTrack8 *iface) { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + struct event *event, *next_event; ULONG ref = InterlockedDecrement(&This->ref);
TRACE("(%p): %lu\n", This, ref);
- if (!ref) free(This); + if (!ref) + { + LIST_FOR_EACH_ENTRY_SAFE(event, next_event, &This->events, struct event, entry) + { + list_remove(&event->entry); + free_event(event); + } + free(This); + }
return ref; } @@ -245,12 +351,163 @@ static inline struct midi_sequence_track *impl_from_IPersistStream(IPersistStrea return CONTAINING_RECORD(iface, struct midi_sequence_track, dmobj.IPersistStream_iface); }
-static HRESULT WINAPI track_IPersistStream_Load(IPersistStream *iface, IStream *stream) +/* Like IStream_Read, but accumulates the number of bytes read into + * `total_bytes_read`, instead of storing the bytes read in this + * operation. */ +static inline HRESULT stream_read_accumulate(IStream *stream, void *buffer, ULONG size, ULONG *total_bytes_read) +{ + HRESULT hr; + ULONG bytes_read = 0; + + hr = IStream_Read(stream, (BYTE *)buffer, size, &bytes_read); + if (SUCCEEDED(hr)) *total_bytes_read += bytes_read; + return hr; +} + +static HRESULT read_variable_length_number(IStream *stream, DWORD *out, ULONG *total_bytes_read) +{ + BYTE byte; + HRESULT hr = S_OK; + + *out = 0; + do + { + hr = stream_read_accumulate(stream, &byte, 1, total_bytes_read); + if (hr != S_OK) break; + + *out = (*out << 7) | (byte & 0x7f); + } while (byte & 0x80); + return hr; +} + +static HRESULT read_midi_event(IStream *stream, struct event *event, BYTE last_status, ULONG *total_bytes_read) +{ + BYTE byte; + BYTE status_type; + DWORD length; + HRESULT hr = S_OK; + + hr = read_variable_length_number(stream, &event->deltaTime, total_bytes_read); + if (hr != S_OK) return hr; + + hr = stream_read_accumulate(stream, &byte, 1, total_bytes_read); + if (hr != S_OK) return hr; + + if (byte & 0x80) event->status = byte; + else event->status = last_status; /* MIDI running status */ + + if (event->status == 0xff) + { + hr = stream_read_accumulate(stream, &byte, 1, total_bytes_read); + if (hr != S_OK) return hr; + event->type = byte; + + hr = read_variable_length_number(stream, &length, total_bytes_read); + if (hr != S_OK) return hr; + + if (is_event_fixed_length(event->status, event->type)) + { + if (length > sizeof(event->data.fixed.byte)) + { + WARN("Invalid MIDI meta event length %lu for type %#02x\n", length, event->type); + return E_FAIL; + } + hr = stream_read_accumulate(stream, &event->data.fixed.byte[0], length, total_bytes_read); + if (hr == S_OK) + { + if (event->type == MIDI_META_SET_TEMPO) + { + event->data.integer = (event->data.fixed.byte[0] << 16) | + (event->data.fixed.byte[1] << 8) | event->data.fixed.byte[2]; + if (event->data.integer == 0) + { + WARN("Invalid tempo value 0\n"); + hr = E_FAIL; + } + } + } + } + else + { + event->data.variable.data = malloc(length); + if (!event->data.variable.data) return E_OUTOFMEMORY; + event->data.variable.size = length; + hr = stream_read_accumulate(stream, event->data.variable.data, length, total_bytes_read); + } + TRACE("MIDI meta event type %#02x, length %lu, time +%lu\n", event->type, length, event->deltaTime); + } + else if (event->status == 0xf0 || event->status == 0xf7) + { + hr = read_variable_length_number(stream, &length, total_bytes_read); + if (hr != S_OK) return hr; + + event->data.variable.data = malloc(length); + if (!event->data.variable.data) return E_OUTOFMEMORY; + event->data.variable.size = length; + hr = stream_read_accumulate(stream, event->data.variable.data, length, total_bytes_read); + FIXME("MIDI sysex event type %#02x, length %lu, time +%lu. not " + "supported\n", + event->status, length, event->deltaTime); + } + else + { + status_type = event->status & 0xf0; + hr = stream_read_accumulate(stream, &event->data.fixed.byte[0], 1, total_bytes_read); + if (hr == S_OK && status_type != 0xc0 && status_type != 0xd0) + hr = stream_read_accumulate(stream, &event->data.fixed.byte[1], 1, total_bytes_read); + TRACE("MIDI event status %#02x, data1 %#02x, data2 %#02x, time +%lu\n", event->status, + event->data.fixed.byte[0], event->data.fixed.byte[1], event->deltaTime); + } + + return hr; +} + +static HRESULT WINAPI midi_sequence_track_IPersistStream_Load(IPersistStream *iface, IStream *stream) { struct midi_sequence_track *This = impl_from_IPersistStream(iface); + struct event *event = NULL; + DWORD magic, length; + BYTE last_status = 0; + ULONG bytes_read = 0; + BOOL is_end_of_track = FALSE; + HRESULT hr;
TRACE("(%p, %p): stub\n", This, stream); - return S_OK; + + hr = IStream_Read(stream, &magic, sizeof(magic), NULL); + if (FAILED(hr)) return hr; + if (magic != MAKEFOURCC('M', 'T', 'r', 'k')) + { + WARN("Invalid MIDI track magic number %s\n", debugstr_fourcc(magic)); + return DMUS_E_UNSUPPORTED_STREAM; + } + + hr = IStream_Read(stream, &length, sizeof(length), NULL); + if (FAILED(hr)) return hr; + length = GET_BE_DWORD(length); + TRACE("MIDI track length %lu\n", length); + + while (!is_end_of_track) + { + if (!(event = calloc(1, sizeof(*event)))) return E_OUTOFMEMORY; + + hr = read_midi_event(stream, event, last_status, &bytes_read); + if (hr != S_OK) break; + list_add_tail(&This->events, &event->entry); + This->event_count++; + last_status = event->status; + is_end_of_track = event->status == 0xff && event->type == MIDI_META_END_OF_TRACK; + event = NULL; + } + + if (hr == S_OK && bytes_read != length) + { + WARN("Read %lu bytes, expected %lu\n", bytes_read, length); + hr = E_FAIL; + } + + if (hr != S_OK && event) free_event(event); + return hr; }
static const IPersistStreamVtbl midi_sequence_track_persiststream_vtbl = @@ -260,12 +517,22 @@ static const IPersistStreamVtbl midi_sequence_track_persiststream_vtbl = dmobj_IPersistStream_Release, dmobj_IPersistStream_GetClassID, unimpl_IPersistStream_IsDirty, - track_IPersistStream_Load, + midi_sequence_track_IPersistStream_Load, unimpl_IPersistStream_Save, unimpl_IPersistStream_GetSizeMax, };
-HRESULT create_midiseqtrack(REFIID riid, void **ppobj) +static MUSIC_TIME midi_sequence_track_get_length(IDirectMusicTrack8 *iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + struct event *event; + MUSIC_TIME length = 0; + LIST_FOR_EACH_ENTRY(event, &This->events, struct event, entry) + length += event->deltaTime; + return length; +} + +static HRESULT create_midiseqtrack(REFIID riid, void **ppobj, DWORD divison) { struct midi_sequence_track *track; HRESULT hr; @@ -276,6 +543,9 @@ HRESULT create_midiseqtrack(REFIID riid, void **ppobj) track->ref = 1; dmobject_init(&track->dmobj, &CLSID_DirectMusicSeqTrack, (IUnknown *)&track->IDirectMusicTrack8_iface); track->dmobj.IPersistStream_iface.lpVtbl = &midi_sequence_track_persiststream_vtbl; + track->ticks_per_quarter_note = divison; + + list_init(&track->events);
hr = IDirectMusicTrack8_QueryInterface(&track->IDirectMusicTrack8_iface, riid, ppobj); IDirectMusicTrack8_Release(&track->IDirectMusicTrack8_iface); @@ -286,16 +556,41 @@ HRESULT create_midiseqtrack(REFIID riid, void **ppobj) struct midi_parser { IStream *stream; + DWORD division; WORD number_of_tracks, current_track; };
HRESULT midi_parser_next_track(struct midi_parser *parser, IDirectMusicTrack **out_track, MUSIC_TIME *out_length) { - TRACE("(%p, %p, %p): stub\n", parser, out_track, out_length); + IPersistStream *pstream; + HRESULT hr; + MUSIC_TIME midi_length; + if (parser->current_track >= parser->number_of_tracks) return S_FALSE; - *out_length = 0; + + hr = create_midiseqtrack(&IID_IPersistStream, (void **)&pstream, parser->division); + if (FAILED(hr)) return hr; + + hr = IPersistStream_Load(pstream, parser->stream); + if (hr == S_FALSE) + { + WARN("Early end of stream while read MIDI track\n"); + hr = DMUS_E_INVALIDFILE; + } + if (FAILED(hr)) + { + IPersistStream_Release(pstream); + return hr; + } + + hr = IPersistStream_QueryInterface(pstream, &IID_IDirectMusicTrack, (void **)out_track); + IPersistStream_Release(pstream); + if (FAILED(hr)) return hr; + + midi_length = midi_sequence_track_get_length((IDirectMusicTrack8 *)*out_track); + *out_length = (LONGLONG)midi_length * DMUS_PPQ / parser->division; parser->current_track++; - return create_midiseqtrack(&IID_IDirectMusicTrack, (void **)out_track); + return S_OK; }
HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser) @@ -350,6 +645,7 @@ HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser) parser = calloc(1, sizeof(struct midi_parser)); if (!parser) return E_OUTOFMEMORY; parser->stream = stream; + parser->division = division; parser->number_of_tracks = number_of_tracks; IStream_AddRef(stream); *out_parser = parser;
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/midi.c | 73 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 5 deletions(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index 1c6d19e193c..c77aa2419cb 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -94,6 +94,7 @@ struct midi_sequence_track UINT ticks_per_quarter_note; UINT ticks_per_second; UINT event_count; + BOOL has_tempo_events;
struct list events; }; @@ -230,9 +231,41 @@ static HRESULT WINAPI midi_sequence_track_GetParam(IDirectMusicTrack8 *iface, RE MUSIC_TIME time, MUSIC_TIME *next, void *param) { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + struct event *event; + ULONG ticks = 0; + MUSIC_TIME music_time; + DMUS_TEMPO_PARAM *tempo = param; + + TRACE("(%p, %s, %ld, %p, %p): semi-stub\n", This, debugstr_dmguid(type), time, next, param); + if (!param) return E_POINTER; + if (!IsEqualGUID(type, &GUID_TempoParam)) return DMUS_E_GET_UNSUPPORTED; + if (!This->has_tempo_events) return DMUS_E_NOT_FOUND; + if (This->ticks_per_quarter_note == 0) + { + /* Return a "fake" tempo to make MIDI time and MUSIC_TIME equivalent. */ + tempo->mtTime = -time; /* set since the start of MUSIC_TIME. */ + if (next) *next = 0; /* always valid. */ + tempo->dblTempo = (double)This->ticks_per_second / DMUS_PPQ; + return S_OK; + }
- TRACE("(%p, %s, %ld, %p, %p): method not implemented\n", This, debugstr_dmguid(type), time, next, param); - return E_NOTIMPL; + if (next) *next = 0; + tempo->dblTempo = 0; + LIST_FOR_EACH_ENTRY(event, &This->events, struct event, entry) + { + ticks += event->deltaTime; + music_time = (ULONGLONG)ticks * DMUS_PPQ / This->ticks_per_quarter_note; + if (event->status == 0xff && event->type == MIDI_META_SET_TEMPO) + { + if (next) *next = music_time - time; + if (music_time > time) break; + tempo->mtTime = music_time - time; + tempo->dblTempo = 6e7 / event->data.integer; + } + } + if (tempo->dblTempo == 0) return DMUS_E_NOT_FOUND; + TRACE("Found tempo %f at time offset %ld\n", tempo->dblTempo, tempo->mtTime); + return S_OK; }
static HRESULT WINAPI midi_sequence_track_SetParam(IDirectMusicTrack8 *iface, REFGUID type, @@ -241,15 +274,42 @@ static HRESULT WINAPI midi_sequence_track_SetParam(IDirectMusicTrack8 *iface, RE struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface);
TRACE("(%p, %s, %ld, %p): method not implemented\n", This, debugstr_dmguid(type), time, param); - return E_NOTIMPL; + if (!This->has_tempo_events) return DMUS_E_SET_UNSUPPORTED; + + if (IsEqualGUID(type, &GUID_DisableTempo)) + { + if (!param) return DMUS_E_TYPE_DISABLED; + FIXME("GUID_DisableTempo not handled yet\n"); + return S_OK; + } + if (IsEqualGUID(type, &GUID_EnableTempo)) + { + if (!param) return DMUS_E_TYPE_DISABLED; + FIXME("GUID_EnableTempo not handled yet\n"); + return S_OK; + } + if (IsEqualGUID(type, &GUID_TempoParam)) + { + if (!param) return E_POINTER; + FIXME("GUID_TempoParam not handled yet\n"); + return S_OK; + } + + return DMUS_E_SET_UNSUPPORTED; }
static HRESULT WINAPI midi_sequence_track_IsParamSupported(IDirectMusicTrack8 *iface, REFGUID type) { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface);
- TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(type)); - return E_NOTIMPL; + TRACE("(%p, %s): semi-sub\n", This, debugstr_dmguid(type)); + + if (!This->has_tempo_events) return DMUS_E_GET_UNSUPPORTED; + + if (IsEqualGUID(type, &GUID_DisableTempo) || IsEqualGUID(type, &GUID_EnableTempo) || + IsEqualGUID(type, &GUID_TempoParam)) + return S_OK; + return DMUS_E_TYPE_UNSUPPORTED; }
static HRESULT WINAPI midi_sequence_track_AddNotificationType(IDirectMusicTrack8 *iface, REFGUID notiftype) @@ -474,6 +534,7 @@ static HRESULT WINAPI midi_sequence_track_IPersistStream_Load(IPersistStream *if
TRACE("(%p, %p): stub\n", This, stream);
+ This->has_tempo_events = FALSE; hr = IStream_Read(stream, &magic, sizeof(magic), NULL); if (FAILED(hr)) return hr; if (magic != MAKEFOURCC('M', 'T', 'r', 'k')) @@ -497,6 +558,8 @@ static HRESULT WINAPI midi_sequence_track_IPersistStream_Load(IPersistStream *if This->event_count++; last_status = event->status; is_end_of_track = event->status == 0xff && event->type == MIDI_META_END_OF_TRACK; + if (event->status == 0xff && event->type == MIDI_META_SET_TEMPO) + This->has_tempo_events = TRUE; event = NULL; }
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/midi.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index c77aa2419cb..b81229feac6 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -220,11 +220,62 @@ static HRESULT WINAPI midi_sequence_track_Play(IDirectMusicTrack8 *iface, void * IDirectMusicPerformance *performance, IDirectMusicSegmentState *segment_state, DWORD track_id) { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + struct list *cursor; + IDirectMusicGraph *graph; + HRESULT hr; + UINT i; + ULONG ticks = 0; + MUSIC_TIME music_time;
TRACE("(%p, %p, %ld, %ld, %ld, %#lx, %p, %p, %ld): stub\n", This, state_data, start_time, end_time, time_offset, segment_flags, performance, segment_state, track_id);
- return E_NOTIMPL; + if (segment_flags) FIXME("segment_flags %#lx not implemented\n", segment_flags); + if (segment_state) FIXME("segment_state %p not implemented\n", segment_state); + + if (FAILED(hr = IDirectMusicPerformance_QueryInterface(performance, &IID_IDirectMusicGraph, (void **)&graph))) + return hr; + + cursor = list_head(&This->events); + for (i = 0; SUCCEEDED(hr) && i < This->event_count; i++, cursor = list_next(&This->events, cursor)) + { + struct event *item = LIST_ENTRY(cursor, struct event, entry); + DMUS_MIDI_PMSG *msg; + + ticks += item->deltaTime; + if (This->ticks_per_quarter_note) + music_time = (ULONGLONG)ticks * DMUS_PPQ / This->ticks_per_quarter_note; + else + /* This is the "fake" tempo case, ticks and MUSIC_TIME should mean + * the same thing. */ + music_time = ticks; + + if (music_time < start_time || music_time >= end_time) continue; + + if (item->status == 0xff || item->status == 0xf0 || item->status == 0xf7) continue; + + if (FAILED(hr = IDirectMusicPerformance_AllocPMsg(performance, sizeof(*msg), (DMUS_PMSG **)&msg))) + break; + + msg->dwFlags = DMUS_PMSGF_MUSICTIME; + msg->mtTime = music_time; + msg->dwPChannel = item->status & 0xf; + msg->dwVirtualTrackID = track_id; + msg->dwType = DMUS_PMSGT_MIDI; + msg->dwGroupID = 1; + msg->bStatus = item->status; + msg->bByte1 = item->data.fixed.byte[0]; + msg->bByte2 = item->data.fixed.byte[1]; + + if (FAILED(hr = IDirectMusicGraph_StampPMsg(graph, (DMUS_PMSG *)msg)) || + FAILED(hr = IDirectMusicPerformance_SendPMsg(performance, (DMUS_PMSG *)msg))) + { + IDirectMusicPerformance_FreePMsg(performance, (DMUS_PMSG *)msg); + break; + } + } + + return SUCCEEDED(hr) ? S_OK : hr; }
static HRESULT WINAPI midi_sequence_track_GetParam(IDirectMusicTrack8 *iface, REFGUID type,
From: Yuxuan Shui yshui@codeweavers.com
Windows adds a "fake" band track to segments loaded from MIDI files, which handles soundfont loading etc. --- dlls/dmime/tests/dmime.c | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index d4be6130ed9..7ff934ce139 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1557,6 +1557,7 @@ static void test_segment(void) static void test_midi(void) { IDirectMusicSegment8 *segment = NULL; + IDirectMusicTrack *track = NULL; IDirectMusicLoader8 *loader; WCHAR test_mid[MAX_PATH]; HRESULT hr; @@ -1569,6 +1570,12 @@ static void test_midi(void) hr = IDirectMusicLoader8_LoadObjectFromFile(loader, &CLSID_DirectMusicSegment, &IID_IDirectMusicSegment, test_mid, (void **)&segment); ok(hr == S_OK, "got %#lx\n", hr); + + hr = IDirectMusicSegment8_GetTrack(segment, &CLSID_DirectMusicBandTrack, 0xffffffff, 0, &track); + todo_wine ok(hr == S_OK, "unable to get band track from midi file: %#lx\n", hr); + if (track) + IDirectMusicTrack_Release(track); + IDirectMusicLoader8_Release(loader); if (segment) IDirectMusicSegment8_Release(segment); }
From: Yuxuan Shui yshui@codeweavers.com
So that instrument downloading etc. could work. --- dlls/dmime/midi.c | 557 +++++++++++++++++++++++++++++++++++++++ dlls/dmime/tests/dmime.c | 2 +- 2 files changed, 558 insertions(+), 1 deletion(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index b81229feac6..675cf2d6c02 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -671,15 +671,572 @@ struct midi_parser { IStream *stream; DWORD division; + BOOL bandtrack_created; WORD number_of_tracks, current_track; };
+struct downloaded_instrument_entry +{ + struct list entry; + IDirectMusicPort *port; + IDirectMusicDownloadedInstrument *downloaded; +}; + +struct midi_band_track +{ + IDirectMusicTrack8 IDirectMusicTrack8_iface; + IDirectMusicBand IDirectMusicBand_iface; + struct dmobject dmobj; /* IPersistStream only */ + LONG ref; + IDirectMusicCollection *collection; + BOOL auto_download; + struct list downloaded_instruments; +}; + +static inline void midi_band_track_unload(struct midi_band_track *This) +{ + struct downloaded_instrument_entry *entry, *next_entry; + + LIST_FOR_EACH_ENTRY_SAFE(entry, next_entry, &This->downloaded_instruments, + struct downloaded_instrument_entry, entry) + { + IDirectMusicPort_UnloadInstrument(entry->port, entry->downloaded); + IDirectMusicDownloadedInstrument_Release(entry->downloaded); + IDirectMusicPort_Release(entry->port); + list_remove(&entry->entry); + free(entry); + } +} + +static inline void midi_band_track_destroy(struct midi_band_track *This) +{ + midi_band_track_unload(This); + if (This->collection) IDirectMusicCollection_Release(This->collection); + free(This); +} + +static inline struct midi_band_track *impl_from_IDirectMusicBand(IDirectMusicBand *iface) +{ + return CONTAINING_RECORD(iface, struct midi_band_track, IDirectMusicBand_iface); +} + +static HRESULT WINAPI midi_band_track_band_QueryInterface(IDirectMusicBand *iface, REFIID riid, void **ret_iface) +{ + struct midi_band_track *This = impl_from_IDirectMusicBand(iface); + TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface); + return IDirectMusicTrack8_QueryInterface(&This->IDirectMusicTrack8_iface, riid, ret_iface); +} + +static ULONG WINAPI midi_band_track_band_AddRef(IDirectMusicBand *iface) +{ + struct midi_band_track *This = impl_from_IDirectMusicBand(iface); + LONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) ref=%ld\n", This, ref); + return ref; +} + +static ULONG WINAPI midi_band_track_band_Release(IDirectMusicBand *iface) +{ + struct midi_band_track *This = impl_from_IDirectMusicBand(iface); + LONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) ref=%ld\n", This, ref); + if (!ref) midi_band_track_destroy(This); + return ref; +} + +static HRESULT WINAPI midi_band_track_band_CreateSegment(IDirectMusicBand *iface, IDirectMusicSegment **segment) +{ + struct midi_band_track *This = impl_from_IDirectMusicBand(iface); + + FIXME("(%p, %p): stub\n", This, segment); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_band_track_band_Download(IDirectMusicBand *iface, IDirectMusicPerformance *performance) +{ + struct midi_band_track *This = impl_from_IDirectMusicBand(iface); + struct downloaded_instrument_entry *entry; + HRESULT hr = S_OK; + ULONG i; + + TRACE("(%p, %p)\n", This, performance); + if (!This->collection) return S_OK; + + for (i = 0;; i++) + { + DWORD dwPatch; + WCHAR wszName[128]; + IDirectMusicInstrument *instrument; + IDirectMusicDownloadedInstrument *downloaded_instrument; + IDirectMusicPort *download_port; + + if ((hr = IDirectMusicCollection_EnumInstrument(This->collection, i, &dwPatch, wszName, 128)) != S_OK) + break; + + if (FAILED(hr = IDirectMusicCollection_GetInstrument(This->collection, dwPatch, &instrument))) + { + WARN("Failed to get instrument %lu, hr %#lx\n", dwPatch, hr); + continue; + } + hr = IDirectMusicPerformance_DownloadInstrument(performance, instrument, 0, + &downloaded_instrument, NULL, 0, &download_port, NULL, NULL); + IDirectMusicInstrument_Release(instrument); + if (FAILED(hr)) WARN("Failed to download instrument %lu, hr %#lx\n", dwPatch, hr); + else + { + if (!(entry = calloc(1, sizeof(*entry)))) + { + IDirectMusicDownloadedInstrument_Release(downloaded_instrument); + IDirectMusicPort_Release(download_port); + return E_OUTOFMEMORY; + } + entry->port = download_port; + entry->downloaded = downloaded_instrument; + list_add_tail(&This->downloaded_instruments, &entry->entry); + } + } + return hr; +} + +static HRESULT WINAPI midi_band_track_band_Unload(IDirectMusicBand *iface, IDirectMusicPerformance *performance) +{ + struct midi_band_track *This = impl_from_IDirectMusicBand(iface); + + TRACE("(%p, %p)\n", This, performance); + if (performance) FIXME("performance parameter not implemented\n"); + + midi_band_track_unload(This); + return S_OK; +} + +static const IDirectMusicBandVtbl midi_band_vtbl = +{ + midi_band_track_band_QueryInterface, + midi_band_track_band_AddRef, + midi_band_track_band_Release, + midi_band_track_band_CreateSegment, + midi_band_track_band_Download, + midi_band_track_band_Unload, +}; + +static HRESULT WINAPI midi_band_track_object_ParseDescriptor(IDirectMusicObject *iface, + IStream *stream, DMUS_OBJECTDESC *desc) +{ + TRACE("(%p, %p, %p)\n", iface, stream, desc); + + if (!stream || !desc) return E_POINTER; + + return E_NOTIMPL; +} + +static const IDirectMusicObjectVtbl midi_band_object_vtbl = +{ + dmobj_IDirectMusicObject_QueryInterface, + dmobj_IDirectMusicObject_AddRef, + dmobj_IDirectMusicObject_Release, + dmobj_IDirectMusicObject_GetDescriptor, + dmobj_IDirectMusicObject_SetDescriptor, + midi_band_track_object_ParseDescriptor, +}; + +static inline struct midi_band_track *midi_band_track_from_IDirectMusicTrack8(IDirectMusicTrack8 *iface) +{ + return CONTAINING_RECORD(iface, struct midi_band_track, IDirectMusicTrack8_iface); +} + +static HRESULT WINAPI midi_band_track_QueryInterface(IDirectMusicTrack8 *iface, REFIID riid, void **ret_iface) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface); + + *ret_iface = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDirectMusicTrack) || + IsEqualIID(riid, &IID_IDirectMusicTrack8)) + *ret_iface = iface; + else if (IsEqualIID(riid, &IID_IDirectMusicObject)) + *ret_iface = &This->dmobj.IDirectMusicObject_iface; + else if (IsEqualIID(riid, &IID_IPersistStream)) *ret_iface = &This->dmobj.IPersistStream_iface; + else if (IsEqualGUID(riid, &IID_IDirectMusicBand8) || IsEqualGUID(riid, &IID_IDirectMusicBand)) + *ret_iface = &This->IDirectMusicBand_iface; + else + { + WARN("(%p, %s, %p): not found\n", This, debugstr_dmguid(riid), ret_iface); + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*ret_iface); + return S_OK; +} + +static ULONG WINAPI midi_band_track_AddRef(IDirectMusicTrack8 *iface) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + LONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p) ref=%ld\n", This, ref); + + return ref; +} + +static ULONG WINAPI midi_band_track_Release(IDirectMusicTrack8 *iface) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + LONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p) ref=%ld\n", This, ref); + + if (!ref) midi_band_track_destroy(This); + + return ref; +} + +static HRESULT WINAPI midi_band_track_Init(IDirectMusicTrack8 *iface, IDirectMusicSegment *segment) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + FIXME("(%p, %p): stub\n", This, segment); + + if (!segment) return E_POINTER; + return S_OK; +} + +static HRESULT WINAPI midi_band_track_InitPlay(IDirectMusicTrack8 *iface, IDirectMusicSegmentState *segment_state, + IDirectMusicPerformance *performance, void **state_data, DWORD virtual_track8id, DWORD flags) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + IDirectMusicBand *band; + HRESULT hr = S_OK; + + FIXME("(%p, %p, %p, %p, %ld, %lx): semi-stub\n", This, segment_state, performance, state_data, + virtual_track8id, flags); + + if (!performance) return E_POINTER; + + if (This->auto_download) + { + hr = IDirectMusicTrack8_QueryInterface(iface, &IID_IDirectMusicBand, (void **)&band); + if (SUCCEEDED(hr)) + { + hr = IDirectMusicBand_Download(band, performance); + IDirectMusicBand_Release(band); + } + } + + return hr; +} + +static HRESULT WINAPI midi_band_track_EndPlay(IDirectMusicTrack8 *iface, void *state_data) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + IDirectMusicBand *band; + HRESULT hr = S_OK; + + FIXME("(%p, %p): semi-stub\n", This, state_data); + + if (This->auto_download) + { + hr = IDirectMusicTrack8_QueryInterface(iface, &IID_IDirectMusicBand, (void **)&band); + if (SUCCEEDED(hr)) + { + hr = IDirectMusicBand_Unload(band, NULL); + IDirectMusicBand_Release(band); + } + } + + return hr; +} + +static HRESULT WINAPI midi_band_track_Play(IDirectMusicTrack8 *iface, void *state_data, + MUSIC_TIME start_time, MUSIC_TIME end_time, MUSIC_TIME time_offset, DWORD track_flags, + IDirectMusicPerformance *performance, IDirectMusicSegmentState *segment_state, DWORD track_id) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %p, %ld, %ld, %ld, %#lx, %p, %p, %ld): stub\n", This, state_data, start_time, + end_time, time_offset, track_flags, performance, segment_state, track_id); + + if (!performance) return DMUS_S_END; + return S_OK; +} + +static HRESULT WINAPI midi_band_track_GetParam(IDirectMusicTrack8 *iface, REFGUID type, + MUSIC_TIME time, MUSIC_TIME *out_next, void *param) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + DMUS_BAND_PARAM *bandparam = param; + + TRACE("(%p, %s, %ld, %p, %p)\n", This, debugstr_dmguid(type), time, out_next, param); + + if (!type || !param) return E_POINTER; + if (IsEqualGUID(type, &GUID_BandParam)) + { + bandparam->mtTimePhysical = 0; + IDirectMusicTrack8_QueryInterface(iface, &IID_IDirectMusicBand, (void **)&bandparam->pBand); + return S_OK; + } + + return DMUS_E_GET_UNSUPPORTED; +} + +static HRESULT WINAPI midi_band_track_SetParam(IDirectMusicTrack8 *iface, REFGUID type, MUSIC_TIME time, void *param) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %ld, %p): semi-stub\n", This, debugstr_dmguid(type), time, param); + + if (!type) return E_POINTER; + if (FAILED(IDirectMusicTrack8_IsParamSupported(iface, type))) return DMUS_E_TYPE_UNSUPPORTED; + + if (IsEqualGUID(type, &GUID_ConnectToDLSCollection)) + { + if (This->collection) IDirectMusicCollection_Release(This->collection); + This->collection = param; + IDirectMusicCollection_AddRef(This->collection); + } + else if (IsEqualGUID(type, &GUID_Disable_Auto_Download)) This->auto_download = FALSE; + else if (IsEqualGUID(type, &GUID_Enable_Auto_Download)) This->auto_download = TRUE; + else if (IsEqualGUID(type, &GUID_DownloadToAudioPath)) + { + IDirectMusicPerformance *performance; + IDirectMusicAudioPath *audio_path; + IUnknown *object = param; + IDirectMusicBand *band; + HRESULT hr; + + if (FAILED(hr = IDirectMusicAudioPath_QueryInterface(object, &IID_IDirectMusicPerformance8, + (void **)&performance)) && + SUCCEEDED(hr = IDirectMusicAudioPath_QueryInterface(object, + &IID_IDirectMusicAudioPath, (void **)&audio_path))) + { + hr = IDirectMusicAudioPath_GetObjectInPath(audio_path, DMUS_PCHANNEL_ALL, DMUS_PATH_PERFORMANCE, + 0, &GUID_All_Objects, 0, &IID_IDirectMusicPerformance8, (void **)&performance); + IDirectMusicAudioPath_Release(audio_path); + } + + if (FAILED(hr)) + { + WARN("Failed to get IDirectMusicPerformance from param %p\n", param); + return hr; + } + + hr = IDirectMusicTrack8_QueryInterface(iface, &IID_IDirectMusicBand, (void **)&band); + if (SUCCEEDED(hr)) + { + hr = IDirectMusicBand_Download(band, performance); + IDirectMusicBand_Release(band); + } + + IDirectMusicPerformance_Release(performance); + } + else if (IsEqualGUID(type, &GUID_UnloadFromAudioPath)) + { + IDirectMusicBand *band; + HRESULT hr; + + hr = IDirectMusicTrack8_QueryInterface(iface, &IID_IDirectMusicBand, (void **)&band); + if (SUCCEEDED(hr)) + { + hr = IDirectMusicBand_Unload(band, NULL); + IDirectMusicBand_Release(band); + } + } + else FIXME("Unhandled parameter type %s\n", debugstr_dmguid(type)); + + return S_OK; +} + +static HRESULT WINAPI midi_band_track_IsParamSupported(IDirectMusicTrack8 *iface, REFGUID rguidType) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s)\n", This, debugstr_dmguid(rguidType)); + + if (!rguidType) return E_POINTER; + + if (IsEqualGUID(rguidType, &GUID_BandParam) || IsEqualGUID(rguidType, &GUID_Clear_All_Bands) || + IsEqualGUID(rguidType, &GUID_ConnectToDLSCollection) || + IsEqualGUID(rguidType, &GUID_Disable_Auto_Download) || + IsEqualGUID(rguidType, &GUID_Download) || IsEqualGUID(rguidType, &GUID_DownloadToAudioPath) || + IsEqualGUID(rguidType, &GUID_Enable_Auto_Download) || + IsEqualGUID(rguidType, &GUID_IDirectMusicBand) || IsEqualGUID(rguidType, &GUID_StandardMIDIFile) || + IsEqualGUID(rguidType, &GUID_Unload) || IsEqualGUID(rguidType, &GUID_UnloadFromAudioPath)) + { + TRACE("param supported\n"); + return S_OK; + } + + TRACE("param unsupported\n"); + return DMUS_E_TYPE_UNSUPPORTED; +} + +static HRESULT WINAPI midi_band_track_AddNotificationType(IDirectMusicTrack8 *iface, REFGUID notiftype) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_band_track_RemoveNotificationType(IDirectMusicTrack8 *iface, REFGUID notiftype) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_band_track_Clone(IDirectMusicTrack8 *iface, MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, IDirectMusicTrack **ppTrack) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + FIXME("(%p, %ld, %ld, %p): stub\n", This, mtStart, mtEnd, ppTrack); + return S_OK; +} + +static HRESULT WINAPI midi_band_track_PlayEx(IDirectMusicTrack8 *iface, void *state_data, + REFERENCE_TIME rtStart, REFERENCE_TIME rtEnd, REFERENCE_TIME rtOffset, DWORD flags, + IDirectMusicPerformance *performance, IDirectMusicSegmentState *segment_state, DWORD virtual_id) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + FIXME("(%p, %p, 0x%s, 0x%s, 0x%s, %lx, %p, %p, %ld): stub\n", This, state_data, + wine_dbgstr_longlong(rtStart), wine_dbgstr_longlong(rtEnd), + wine_dbgstr_longlong(rtOffset), flags, performance, segment_state, virtual_id); + + return S_OK; +} + +static HRESULT WINAPI midi_band_track_GetParamEx(IDirectMusicTrack8 *iface, REFGUID rguidType, + REFERENCE_TIME rtTime, REFERENCE_TIME *rtNext, void *param, void *state_data, DWORD flags) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + FIXME("(%p, %s, 0x%s, %p, %p, %p, %lx): stub\n", This, debugstr_dmguid(rguidType), + wine_dbgstr_longlong(rtTime), rtNext, param, state_data, flags); + + return S_OK; +} + +static HRESULT WINAPI midi_band_track_SetParamEx(IDirectMusicTrack8 *iface, REFGUID rguidType, + REFERENCE_TIME rtTime, void *param, void *state_data, DWORD flags) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + FIXME("(%p, %s, 0x%s, %p, %p, %lx): stub\n", This, debugstr_dmguid(rguidType), + wine_dbgstr_longlong(rtTime), param, state_data, flags); + + return S_OK; +} + +static HRESULT WINAPI midi_band_track_Compose(IDirectMusicTrack8 *iface, IUnknown *context, + DWORD trackgroup, IDirectMusicTrack **track) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %p, %ld, %p): method not implemented\n", This, context, trackgroup, track); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_band_track_Join(IDirectMusicTrack8 *iface, IDirectMusicTrack *pNewTrack, + MUSIC_TIME mtJoin, IUnknown *pContext, DWORD dwTrackGroup, IDirectMusicTrack **ppResultTrack) +{ + struct midi_band_track *This = midi_band_track_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p, %ld, %p, %ld, %p): stub\n", This, pNewTrack, mtJoin, pContext, dwTrackGroup, ppResultTrack); + return S_OK; +} + +static const IDirectMusicTrack8Vtbl midi_band_track_vtbl = +{ + midi_band_track_QueryInterface, + midi_band_track_AddRef, + midi_band_track_Release, + midi_band_track_Init, + midi_band_track_InitPlay, + midi_band_track_EndPlay, + midi_band_track_Play, + midi_band_track_GetParam, + midi_band_track_SetParam, + midi_band_track_IsParamSupported, + midi_band_track_AddNotificationType, + midi_band_track_RemoveNotificationType, + midi_band_track_Clone, + midi_band_track_PlayEx, + midi_band_track_GetParamEx, + midi_band_track_SetParamEx, + midi_band_track_Compose, + midi_band_track_Join, +}; +static HRESULT WINAPI midi_band_track_persist_stream_Load(IPersistStream *iface, IStream *stream) +{ + TRACE("(%p, %p): stub\n", iface, stream); + return S_OK; +} + +static const IPersistStreamVtbl midi_band_track_persist_stream_vtbl = +{ + dmobj_IPersistStream_QueryInterface, + dmobj_IPersistStream_AddRef, + dmobj_IPersistStream_Release, + dmobj_IPersistStream_GetClassID, + unimpl_IPersistStream_IsDirty, + midi_band_track_persist_stream_Load, + unimpl_IPersistStream_Save, + unimpl_IPersistStream_GetSizeMax, +}; + +static HRESULT create_midi_band_track(IStream *stream, IDirectMusicTrack **track) +{ + DMUS_OBJECTDESC default_desc = + { + .dwSize = sizeof(DMUS_OBJECTDESC), + .dwValidData = DMUS_OBJ_OBJECT | DMUS_OBJ_CLASS, + .guidClass = CLSID_DirectMusicCollection, + .guidObject = GUID_DefaultGMCollection, + }; + HRESULT hr; + IDirectMusicCollection *collection = NULL; + struct midi_band_track *This; + + if (!(This = calloc(1, sizeof(*This)))) return E_OUTOFMEMORY; + This->IDirectMusicTrack8_iface.lpVtbl = &midi_band_track_vtbl; + This->IDirectMusicBand_iface.lpVtbl = &midi_band_vtbl; + This->ref = 1; + dmobject_init(&This->dmobj, &CLSID_DirectMusicBandTrack, (IUnknown *)&This->IDirectMusicTrack8_iface); + This->dmobj.IPersistStream_iface.lpVtbl = &midi_band_track_persist_stream_vtbl; + This->dmobj.IDirectMusicObject_iface.lpVtbl = &midi_band_object_vtbl; + list_init(&This->downloaded_instruments); + + hr = stream_get_object(stream, &default_desc, &IID_IDirectMusicCollection, (void **)&collection); + if (FAILED(hr)) WARN("Failed to load default collection from loader, hr %#lx\n", hr); + + if (collection) + { + This->collection = collection; + IDirectMusicCollection_AddRef(collection); + } + + *track = (IDirectMusicTrack *)&This->IDirectMusicTrack8_iface; + if (collection) IDirectMusicCollection_Release(collection); + return S_OK; +} + HRESULT midi_parser_next_track(struct midi_parser *parser, IDirectMusicTrack **out_track, MUSIC_TIME *out_length) { IPersistStream *pstream; HRESULT hr; MUSIC_TIME midi_length;
+ if (!parser->bandtrack_created) + { + hr = create_midi_band_track(parser->stream, out_track); + if (FAILED(hr)) return hr; + parser->bandtrack_created = TRUE; + *out_length = 0; + return S_OK; + } + if (parser->current_track >= parser->number_of_tracks) return S_FALSE;
hr = create_midiseqtrack(&IID_IPersistStream, (void **)&pstream, parser->division); diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 7ff934ce139..e8c341b9a8a 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1572,7 +1572,7 @@ static void test_midi(void) ok(hr == S_OK, "got %#lx\n", hr);
hr = IDirectMusicSegment8_GetTrack(segment, &CLSID_DirectMusicBandTrack, 0xffffffff, 0, &track); - todo_wine ok(hr == S_OK, "unable to get band track from midi file: %#lx\n", hr); + ok(hr == S_OK, "unable to get band track from midi file: %#lx\n", hr); if (track) IDirectMusicTrack_Release(track);
update: reorder the commits so the stub midi track code is reachable.
update: removed variable length event handling