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.
-- v17: 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
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 | 7 +++ dlls/dmime/midi.c | 105 +++++++++++++++++++++++++++++++++++++ dlls/dmime/segment.c | 16 +++++- 4 files changed, 128 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..cdf0810381d 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 @@ -69,6 +70,12 @@ 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*); extern void set_audiopath_primary_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 | 263 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 262 insertions(+), 1 deletion(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index a7e57947c6f..877c3cfbb91 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -29,15 +29,275 @@ 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 *segment) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p): stub\n", This, segment); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_InitPlay(IDirectMusicTrack8 *iface, IDirectMusicSegmentState *segment_state, + IDirectMusicPerformance *performance, void **state_data, DWORD virtual_track_id, DWORD flags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p, %p, %p, %ld, %ld): stub\n", This, segment_state, performance, state_data, + virtual_track_id, flags); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_EndPlay(IDirectMusicTrack8 *iface, void *state_data) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p): stub\n", This, state_data); + 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); + + FIXME("(%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); + + FIXME("(%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); + + FIXME("(%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); + + FIXME("(%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); + + FIXME("(%p, %s): method not implemented\n", This, debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Clone(IDirectMusicTrack8 *iface, MUSIC_TIME start, + MUSIC_TIME end, IDirectMusicTrack **track) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %ld, %ld, %p): stub\n", This, start, end, track); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_PlayEx(IDirectMusicTrack8 *iface, void *state_data, + REFERENCE_TIME start, REFERENCE_TIME end, REFERENCE_TIME offset, DWORD flags, + IDirectMusicPerformance *performance, IDirectMusicSegmentState *segment_state, DWORD virtual_id) +{ + 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, state_data, + wine_dbgstr_longlong(start), wine_dbgstr_longlong(end), wine_dbgstr_longlong(offset), + flags, performance, segment_state, virtual_id); + 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); + + FIXME("(%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); + + FIXME("(%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); + + FIXME("(%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); + FIXME("(%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 +352,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 | 274 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 266 insertions(+), 8 deletions(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index 877c3cfbb91..baec66ea88f 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -29,11 +29,68 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmime); #define GET_BE_DWORD(x) RtlUlongByteSwap(x) #endif
+union event_data +{ + struct + { + BYTE byte[5]; + } fixed; + UINT integer; +}; + +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) @@ -77,11 +134,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); + } + free(This); + }
return ref; } @@ -247,12 +313,166 @@ 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; + LARGE_INTEGER offset; + 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; + + switch (event->type) + { + case MIDI_META_END_OF_TRACK: + if (length != 0) + { + ERR("Invalid MIDI meta event length %lu for end of track event.\n", length); + return E_FAIL; + } + break; + case MIDI_META_SET_TEMPO: + if (length != 3) + { + ERR("Invalid MIDI meta event length %lu for set tempo event.\n", length); + return E_FAIL; + } + if ((hr = stream_read_accumulate(stream, &event->data.fixed.byte[0], length, total_bytes_read)) != S_OK) + return hr; + 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) + { + ERR("Invalid tempo value 0\n"); + return E_FAIL; + } + break; + default: + offset.QuadPart = length; + if (FAILED(hr = IStream_Seek(stream, offset, STREAM_SEEK_CUR, NULL))) return hr; + *total_bytes_read += length; + FIXME("MIDI meta event type %#02x, length %lu, time +%lu. not supported\n", event->type, + length, event->deltaTime); + } + 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; + + offset.QuadPart = length; + if (FAILED(hr = IStream_Seek(stream, offset, STREAM_SEEK_CUR, NULL))) return hr; + *total_bytes_read += length; + 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); + return hr; }
static const IPersistStreamVtbl midi_sequence_track_persiststream_vtbl = @@ -262,12 +482,21 @@ 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; @@ -278,6 +507,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); @@ -288,16 +520,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) @@ -352,6 +609,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 | 75 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 6 deletions(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index baec66ea88f..729b1174f2f 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -89,6 +89,7 @@ struct midi_sequence_track UINT ticks_per_quarter_note; UINT ticks_per_second; UINT event_count; + BOOL has_tempo_events;
struct list events; }; @@ -191,9 +192,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, @@ -201,16 +234,43 @@ static HRESULT WINAPI midi_sequence_track_SetParam(IDirectMusicTrack8 *iface, RE { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface);
- FIXME("(%p, %s, %ld, %p): method not implemented\n", This, debugstr_dmguid(type), time, param); - return E_NOTIMPL; + TRACE("(%p, %s, %ld, %p): semi-stub\n", This, debugstr_dmguid(type), time, param); + 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);
- FIXME("(%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) @@ -439,6 +499,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')) @@ -462,6 +523,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 729b1174f2f..b8ec5fd5da2 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -181,11 +181,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;
FIXME("(%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 b8ec5fd5da2..fea613e5257 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -635,15 +635,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: resolve most of the style issues.
On Tue Feb 6 16:34:45 2024 +0000, Rémi Bernon wrote:
Do we really need this check (and the additional read_accumulate helper it requires)? I understand how it might seem important for correctness, but checks need to be preferably either trivial (like parameter checks), or something that Windows does and that applications would need. Ideally, any check should have a corresponding test to make sure it can be added without making the behavior different (let's imagine applications with bogus MIDI files and Windows maybe just parses whatever it can from them).
I can change this to use `IStream_Seek` to find out the current position, instead of tracking it across reads. is that reasonable?