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.
-- v29: https://gitlab.winehq.org/wine/wine/-/merge_requests/4982
From: Yuxuan Shui yshui@codeweavers.com
Besides a seq track and a tempo track, Windows also adds a "fake" band track to segments loaded from MIDI files, which handles soundfont loading etc. --- dlls/dmime/tests/bogus.mid | Bin 0 -> 163 bytes dlls/dmime/tests/dmime.c | 44 +++++++++++++++++++++++++++++++++++ dlls/dmime/tests/resource.rc | 4 ++++ dlls/dmime/tests/test.mid | Bin 0 -> 163 bytes 4 files changed, 48 insertions(+) create mode 100644 dlls/dmime/tests/bogus.mid create mode 100644 dlls/dmime/tests/test.mid
diff --git a/dlls/dmime/tests/bogus.mid b/dlls/dmime/tests/bogus.mid new file mode 100644 index 0000000000000000000000000000000000000000..5b88aab0f072af1de6a55edf835ee563b136c08c GIT binary patch literal 163 zcmeYb$w*;fU|?flWME=^;2Tnu4dmr8{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|nLi8myh
literal 0 HcmV?d00001
diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 218dc01045d..eaeff2e86bc 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1554,6 +1554,49 @@ static void test_segment(void) while (IDirectMusicSegment_Release(dms)); }
+static void test_midi(void) +{ + IDirectMusicSegment8 *segment = NULL; + IDirectMusicTrack *track = NULL; + IDirectMusicLoader8 *loader; + WCHAR test_mid[MAX_PATH], bogus_mid[MAX_PATH]; + HRESULT hr; + + load_resource(L"test.mid", test_mid); + /* This is a MIDI file with wrong track length. */ + load_resource(L"bogus.mid", bogus_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); + + /* test.mid has 1 seq track, 1 tempo track, and 1 band track */ + 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); + track = NULL; + hr = IDirectMusicSegment8_GetTrack(segment, &CLSID_DirectMusicSeqTrack, 0xffffffff, 0, &track); + todo_wine ok(hr == S_OK, "got %#lx\n", hr); + if (track) IDirectMusicTrack_Release(track); + track = NULL; + hr = IDirectMusicSegment8_GetTrack(segment, &CLSID_DirectMusicTempoTrack, 0xffffffff, 0, &track); + todo_wine ok(hr == S_OK, "got %#lx\n", hr); + if (track) IDirectMusicTrack_Release(track); + track = NULL; + if (segment) IDirectMusicSegment8_Release(segment); + segment = NULL; + + hr = IDirectMusicLoader8_LoadObjectFromFile(loader, &CLSID_DirectMusicSegment, + &IID_IDirectMusicSegment, bogus_mid, (void **)&segment); + ok(hr == S_OK, "got %#lx\n", hr); + if (segment) IDirectMusicSegment8_Release(segment); + + IDirectMusicLoader8_Release(loader); +} + static void _add_track(IDirectMusicSegment8 *seg, REFCLSID class, const char *name, DWORD group) { IDirectMusicTrack *track; @@ -4724,6 +4767,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..4d8f512af84 100644 --- a/dlls/dmime/tests/resource.rc +++ b/dlls/dmime/tests/resource.rc @@ -21,3 +21,7 @@ /* 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 +/* @makedep: bogus.mid */ +bogus.mid RCDATA bogus.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')):
Approving so that we can move forward with more tests ;).
This merge request was approved by Rémi Bernon.
This merge request was approved by Michael Stefaniuc.
Unclear why there are so many test failures but all are unrelated to this MR: appbar.c, clipboard.c, ddraw1.c, ddraw2.c, ddraw4.c, ddraw7.c, device.c, msg.c, shelldispatch.c, shlfolder.c, systray.c, win.c