From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/dmime_private.h | 15 ++ dlls/dmime/midi.c | 390 ++++++++++++++++++++++++++++++++++++- dlls/dmime/segment.c | 25 ++- dlls/dmime/tests/dmime.c | 2 +- 4 files changed, 422 insertions(+), 10 deletions(-)
diff --git a/dlls/dmime/dmime_private.h b/dlls/dmime/dmime_private.h index 96ccd5daf8b..e7624456abd 100644 --- a/dlls/dmime/dmime_private.h +++ b/dlls/dmime/dmime_private.h @@ -31,6 +31,7 @@ #include "winnt.h" #include "wingdi.h" #include "winuser.h" +#include "winternl.h"
#include "wine/debug.h" #include "wine/list.h" @@ -48,6 +49,7 @@ * Interfaces */ typedef struct IDirectMusicAudioPathImpl IDirectMusicAudioPathImpl; +struct midi_parser;
/***************************************************************************** * ClassFactory @@ -68,6 +70,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*); @@ -111,4 +118,12 @@ typedef struct _DMUS_PRIVATE_TEMPO_PLAY_STATE { * Misc. */
+#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 + #endif /* __WINE_DMIME_PRIVATE_H */ diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index e515ab12568..7d6eed3dabc 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -20,11 +20,73 @@
WINE_DEFAULT_DEBUG_CHANNEL(dmime);
+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) @@ -32,6 +94,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); @@ -66,11 +163,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; } @@ -105,8 +211,8 @@ static HRESULT WINAPI midi_sequence_track_Play(IDirectMusicTrack8 *iface, void * { struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface);
- TRACE("(%p, %p, %ld, %ld, %ld, %#lx, %p, %p, %ld)\n", This, state_data, start_time, end_time, - time_offset, segment_flags, performance, segment_state, track_id); + 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; } @@ -236,12 +342,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 = @@ -251,12 +508,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; @@ -267,9 +534,118 @@ 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);
return hr; } + +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) +{ + IPersistStream *pstream; + HRESULT hr; + MUSIC_TIME midi_length; + + if (parser->current_track >= parser->number_of_tracks) return S_FALSE; + + 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 S_OK; +} + +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; + parser->division = division; + parser->number_of_tracks = number_of_tracks; + 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..c37e76b91cb 100644 --- a/dlls/dmime/segment.c +++ b/dlls/dmime/segment.c @@ -17,9 +17,16 @@ * 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"
+#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 + WINE_DEFAULT_DEBUG_CHANNEL(dmime);
struct track_entry @@ -791,7 +798,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 +817,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')): diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index d23f7b66087..d4be6130ed9 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1568,7 +1568,7 @@ static void test_midi(void) ok(hr == S_OK, "got %#lx\n", hr); hr = IDirectMusicLoader8_LoadObjectFromFile(loader, &CLSID_DirectMusicSegment, &IID_IDirectMusicSegment, test_mid, (void **)&segment); - todo_wine ok(hr == S_OK, "got %#lx\n", hr); + ok(hr == S_OK, "got %#lx\n", hr); IDirectMusicLoader8_Release(loader); if (segment) IDirectMusicSegment8_Release(segment); }