diff --git a/dlls/dmime/Makefile.in b/dlls/dmime/Makefile.in index f8f622f8f3d..0f7b7db609e 100644 --- a/dlls/dmime/Makefile.in +++ b/dlls/dmime/Makefile.in @@ -4,6 +4,7 @@ PARENTSRC = ../dmusic SOURCES = \ audiopath.c \ + band.c \ dmime.idl \ dmime_main.c \ dmobject.c \ diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index 40db1d2232c..439511d15cc 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -18,6 +18,7 @@ #include "dmusic_midi.h" #include "dmime_private.h" +#include "dmusic_band.h" #include "winternl.h" WINE_DEFAULT_DEBUG_CHANNEL(dmime); @@ -30,9 +31,19 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmime); #define GET_BE_DWORD(x) RtlUlongByteSwap(x) #endif +struct midi_event +{ + MUSIC_TIME delta_time; + BYTE status; + BYTE data[2]; +}; + struct midi_parser { + IDirectMusicTrack *bandtrack; + MUSIC_TIME time; IStream *stream; + DWORD division; }; static HRESULT stream_read_at_most(IStream *stream, void *buffer, ULONG size, ULONG *bytes_left) @@ -62,26 +73,27 @@ static HRESULT read_variable_length_number(IStream *stream, DWORD *out, ULONG *b return S_OK; } -static HRESULT read_midi_event(IStream *stream, BYTE *last_status, ULONG *bytes_left) +static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE *last_status, ULONG *bytes_left) { - BYTE byte, status, meta_type; + BYTE byte, status_type, meta_type; DWORD length; LARGE_INTEGER offset; HRESULT hr = S_OK; DWORD delta_time; if ((hr = read_variable_length_number(stream, &delta_time, bytes_left)) != S_OK) return hr; + event->delta_time = delta_time; if ((hr = stream_read_at_most(stream, &byte, 1, bytes_left)) != S_OK) return hr; if (byte & 0x80) { - status = *last_status = byte; + event->status = *last_status = byte; if ((hr = stream_read_at_most(stream, &byte, 1, bytes_left)) != S_OK) return hr; } - else status = *last_status; + else event->status = *last_status; - if (status == MIDI_META) + if (event->status == MIDI_META) { meta_type = byte; @@ -98,8 +110,9 @@ static HRESULT read_midi_event(IStream *stream, BYTE *last_status, ULONG *bytes_ *bytes_left -= length; } TRACE("MIDI meta event type %#02x, length %lu, time +%lu\n", meta_type, length, delta_time); + return S_OK; } - else if (status == MIDI_SYSEX1 || status == MIDI_SYSEX2) + else if (event->status == MIDI_SYSEX1 || event->status == MIDI_SYSEX2) { if (byte & 0x80) { @@ -112,33 +125,74 @@ static HRESULT read_midi_event(IStream *stream, BYTE *last_status, ULONG *bytes_ offset.QuadPart = length; if (FAILED(hr = IStream_Seek(stream, offset, STREAM_SEEK_CUR, NULL))) return hr; *bytes_left -= length; - FIXME("MIDI sysex event type %#02x, length %lu, time +%lu. not supported\n", status, length, delta_time); + FIXME("MIDI sysex event type %#02x, length %lu, time +%lu. not supported\n", event->status, + length, delta_time); + return S_OK; + } + + status_type = event->status & 0xf0; + if (status_type == MIDI_PROGRAM_CHANGE) + { + event->data[0] = byte; + TRACE("MIDI program change event status %#02x, data: %#02x, time +%lu\n", event->status, + event->data[0], delta_time); } else { - if ((status & 0xf0) != MIDI_PROGRAM_CHANGE && (status & 0xf0) != MIDI_CHANNEL_PRESSURE && - (hr = stream_read_at_most(stream, &byte, 1, bytes_left)) != S_OK) + if (status_type != MIDI_CHANNEL_PRESSURE && (hr = stream_read_at_most(stream, &byte, 1, bytes_left)) != S_OK) return hr; - FIXME("MIDI event status %#02x, time +%lu, not supported\n", status, delta_time); + FIXME("MIDI event status %#02x, time +%lu, not supported\n", event->status, delta_time); } return S_OK; } +static HRESULT midi_parser_handle_program_change(struct midi_parser *parser, struct midi_event *event) +{ + HRESULT hr; + DMUS_IO_INSTRUMENT instrument; + IDirectMusicBand *band; + DMUS_BAND_PARAM band_param; + MUSIC_TIME dmusic_time = (ULONGLONG)parser->time * DMUS_PPQ / parser->division; + instrument.dwPChannel = event->status & 0xf; + instrument.dwFlags = DMUS_IO_INST_PATCH; + instrument.dwPatch = event->data[0]; + if (FAILED(hr = CoCreateInstance(&CLSID_DirectMusicBand, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicBand, (void **)&band))) + return hr; + hr = band_add_instrument(band, &instrument); + + if (SUCCEEDED(hr)) + { + TRACE("Adding band at time %lu\n", dmusic_time); + band_param.pBand = band; + band_param.mtTimePhysical = dmusic_time; + hr = IDirectMusicTrack_SetParam(parser->bandtrack, &GUID_BandParam, dmusic_time, &band_param); + } + else WARN("Failed to add instrument to band\n"); + + IDirectMusicBand_Release(band); + return hr; +} + static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment8 *segment) { WORD i = 0; - TRACE("(%p, %p): stub\n", parser, segment); + HRESULT hr; + MUSIC_TIME music_length = 0; + + TRACE("(%p, %p): semi-stub\n", parser, segment); + for (i = 0;; i++) { - HRESULT hr; BYTE magic[4] = {0}, last_status = 0; DWORD length_be; ULONG length; ULONG read = 0; + struct midi_event event = {0}; TRACE("Start parsing track %u\n", i); - if ((hr = IStream_Read(parser->stream, magic, sizeof(magic), &read)) != S_OK) return hr; + if ((hr = IStream_Read(parser->stream, magic, sizeof(magic), &read)) != S_OK) break; if (read < sizeof(magic)) break; if (memcmp(magic, "MTrk", 4) != 0) break; @@ -148,20 +202,32 @@ static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment length = GET_BE_DWORD(length_be); TRACE("Track %u, length %lu bytes\n", i, length); - while ((hr = read_midi_event(parser->stream, &last_status, &length)) == S_OK) - ; + while ((hr = read_midi_event(parser->stream, &event, &last_status, &length)) == S_OK) + { + parser->time += event.delta_time; + if ((event.status & 0xf0) == MIDI_PROGRAM_CHANGE) + hr = midi_parser_handle_program_change(parser, &event); + if (FAILED(hr)) break; + } - if (FAILED(hr)) return hr; + if (FAILED(hr)) break; TRACE("End of track %u\n", i); + if (parser->time > music_length) music_length = parser->time; + parser->time = 0; } TRACE("End of file\n"); - return S_FALSE; + + music_length = (ULONGLONG)music_length * DMUS_PPQ / parser->division + 1; + if (SUCCEEDED(hr)) hr = IDirectMusicSegment8_SetLength(segment, music_length); + if (SUCCEEDED(hr)) hr = IDirectMusicSegment8_InsertTrack(segment, parser->bandtrack, 0xffff); + return hr; } static void midi_parser_destroy(struct midi_parser *parser) { IStream_Release(parser->stream); + IDirectMusicTrack_Release(parser->bandtrack); free(parser); } @@ -202,9 +268,17 @@ static 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; - IStream_AddRef(stream); - *out_parser = parser; + parser->division = division; + hr = CoCreateInstance(&CLSID_DirectMusicBandTrack, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicTrack, (void **)&parser->bandtrack); + if (FAILED(hr)) + { + free(parser); + return hr; + } + *out_parser = parser; + IStream_AddRef(stream); return hr; } diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 7e1b419251e..41b34944f9d 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1600,6 +1600,25 @@ static void _expect_track(IDirectMusicSegment8 *seg, REFCLSID expect, const char static void test_midi(void) { + static const DWORD message_types[] = + { + DMUS_PMSGT_MIDI, + DMUS_PMSGT_NOTE, + DMUS_PMSGT_SYSEX, + DMUS_PMSGT_NOTIFICATION, + DMUS_PMSGT_TEMPO, + DMUS_PMSGT_CURVE, + DMUS_PMSGT_TIMESIG, + DMUS_PMSGT_PATCH, + DMUS_PMSGT_TRANSPOSE, + DMUS_PMSGT_CHANNEL_PRIORITY, + DMUS_PMSGT_STOP, + DMUS_PMSGT_DIRTY, + DMUS_PMSGT_WAVE, + DMUS_PMSGT_LYRIC, + DMUS_PMSGT_SCRIPTLYRIC, + DMUS_PMSGT_USER, + }; static const char midi_meta_set_tempo[] = { 0x04, /* delta time = 4 */ @@ -1608,15 +1627,27 @@ static void test_midi(void) 0x03, /* event data lenght, 3 bytes */ 0x03,0x0d,0x40 /* tempo, 200000 us per quarter-note, i.e. 300 bpm */ }; + static const char midi_program_change[] = + { + 0x04, /* delta time = 4 */ + 0xc1, /* event type, program change, channel 1 */ + 0x30, /* event data, patch 48 */ + }; IDirectMusicSegment8 *segment = NULL; IDirectMusicTrack *track = NULL; IDirectMusicLoader8 *loader; + IDirectMusicTool *tool; + IDirectMusicPerformance *performance; + IDirectMusicGraph *graph; IPersistStream *persist; IStream *stream; LARGE_INTEGER zero = { .QuadPart = 0 }; ULARGE_INTEGER position = { .QuadPart = 0 }; WCHAR test_mid[MAX_PATH], bogus_mid[MAX_PATH]; HRESULT hr; + ULONG ret; + DMUS_PMSG *msg; + DMUS_PATCH_PMSG *patch; #include struct { @@ -1649,7 +1680,7 @@ static void test_midi(void) &IID_IDirectMusicSegment, test_mid, (void **)&segment); ok(hr == S_OK, "got %#lx\n", hr); - todo_wine expect_track(segment, BandTrack, -1, 0); + expect_track(segment, BandTrack, -1, 0); todo_wine expect_track(segment, ChordTrack, -1, 1); todo_wine expect_track(segment, TempoTrack, -1, 2); todo_wine expect_track(segment, TimeSigTrack, -1, 3); @@ -1691,7 +1722,7 @@ static void test_midi(void) IPersistStream_Release(persist); IStream_Release(stream); /* TempoTrack and TimeSigTrack seems to be optional. */ - todo_wine expect_track(segment, BandTrack, -1, 0); + expect_track(segment, BandTrack, -1, 0); todo_wine expect_track(segment, ChordTrack, -1, 1); todo_wine expect_track(segment, SeqTrack, -1, 2); IDirectMusicSegment_Release(segment); @@ -1727,7 +1758,7 @@ static void test_midi(void) "got %lld\n", position.QuadPart); IPersistStream_Release(persist); IStream_Release(stream); - todo_wine expect_track(segment, BandTrack, -1, 0); + expect_track(segment, BandTrack, -1, 0); todo_wine expect_track(segment, ChordTrack, -1, 1); todo_wine expect_track(segment, TempoTrack, -1, 2); todo_wine expect_track(segment, SeqTrack, -1, 3); @@ -1763,11 +1794,102 @@ static void test_midi(void) ok(position.QuadPart == sizeof(header) + sizeof(track_header) + 4, "got %lld\n", position.QuadPart); IPersistStream_Release(persist); IStream_Release(stream); - todo_wine expect_track(segment, BandTrack, -1, 0); + expect_track(segment, BandTrack, -1, 0); todo_wine expect_track(segment, ChordTrack, -1, 1); /* there is no tempo track. */ todo_wine expect_track(segment, SeqTrack, -1, 2); IDirectMusicSegment_Release(segment); + + /* parse MIDI file with program change event. */ + + hr = CoCreateInstance(&CLSID_DirectMusicSegment, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicSegment, (void **)&segment); + ok(hr == S_OK, "got %#lx\n", hr); + + hr = IDirectMusicSegment_QueryInterface(segment, &IID_IPersistStream, (void **)&persist); + ok(hr == S_OK, "got %#lx\n", hr); + hr = CreateStreamOnHGlobal(0, TRUE, &stream); + ok(hr == S_OK, "got %#lx\n", hr); + header.format = GET_BE_WORD(123); + header.count = GET_BE_WORD(123); + header.ppqn = GET_BE_WORD(123); + header.length = GET_BE_DWORD(sizeof(header) - 8); + hr = IStream_Write(stream, &header, sizeof(header), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + track_header.length = RtlUlongByteSwap(sizeof(track_header) - 8 + sizeof(midi_program_change)); + hr = IStream_Write(stream, &track_header, sizeof(track_header), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_program_change, sizeof(midi_program_change), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Seek(stream, zero, 0, NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IPersistStream_Load(persist, stream); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Seek(stream, zero, STREAM_SEEK_CUR, &position); + ok(hr == S_OK, "got %#lx\n", hr); + ok(position.QuadPart == sizeof(header) + sizeof(track_header) + sizeof(midi_program_change), + "got %lld\n", position.QuadPart); + IPersistStream_Release(persist); + IStream_Release(stream); + expect_track(segment, BandTrack, -1, 0); + todo_wine expect_track(segment, ChordTrack, -1, 1); + todo_wine expect_track(segment, SeqTrack, -1, 2); + + hr = test_tool_create(message_types, ARRAY_SIZE(message_types), &tool); + ok(hr == S_OK, "got %#lx\n", hr); + + hr = CoCreateInstance(&CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicPerformance, (void **)&performance); + ok(hr == S_OK, "got %#lx\n", hr); + + hr = CoCreateInstance(&CLSID_DirectMusicGraph, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicGraph, (void **)&graph); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IDirectMusicGraph_InsertTool(graph, (IDirectMusicTool *)tool, NULL, 0, -1); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IDirectMusicPerformance_SetGraph(performance, graph); + ok(hr == S_OK, "got %#lx\n", hr); + IDirectMusicGraph_Release(graph); + + /* now play the segment, and check produced messages + * wine generates: DIRTY, PATCH, DIRTY. + * native generates: DIRTY, PATCH + */ + + hr = IDirectMusicPerformance_Init(performance, NULL, 0, 0); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IDirectMusicPerformance_PlaySegment(performance, (IDirectMusicSegment *)segment, 0x800, 0, NULL); + ok(hr == S_OK, "got %#lx\n", hr); + + ret = test_tool_wait_message(tool, 500, (DMUS_PMSG **)&msg); + ok(!ret, "got %#lx\n", ret); + ok(msg->dwType == DMUS_PMSGT_DIRTY, "got %#lx, expected DIRTY\n", msg->dwType); + hr = IDirectMusicPerformance_FreePMsg(performance, msg); + ok(hr == S_OK, "got %#lx\n", hr); + + ret = test_tool_wait_message(tool, 500, (DMUS_PMSG **)&msg); + ok(!ret, "got %#lx\n", ret); + ok(msg->dwType == DMUS_PMSGT_PATCH, "got msg type %#lx, expected PATCH\n", msg->dwType); + ok(msg->dwPChannel == 1, "got pchannel %lu, expected 1\n", msg->dwPChannel); + todo_wine ok(msg->mtTime == 23, "got mtTime %lu, expected 23\n", msg->mtTime); + patch = (DMUS_PATCH_PMSG *)msg; + ok(patch->byInstrument == 0x30, "got instrument %#x, expected 0x30\n", patch->byInstrument); + hr = IDirectMusicPerformance_FreePMsg(performance, msg); + ok(hr == S_OK, "got %#lx\n", hr); + + ret = test_tool_wait_message(tool, 500, (DMUS_PMSG **)&msg); + todo_wine ok(ret == WAIT_TIMEOUT, "unexpected message\n"); + if (!ret) + { + hr = IDirectMusicPerformance_FreePMsg(performance, msg); + ok(hr == S_OK, "got %#lx\n", hr); + } + + hr = IDirectMusicPerformance_CloseDown(performance); + ok(hr == S_OK, "got %#lx\n", hr); + IDirectMusicPerformance_Release(performance); + IDirectMusicTool_Release(tool); + IDirectMusicSegment_Release(segment); IDirectMusicLoader8_Release(loader); } diff --git a/dlls/dmusic/band.c b/dlls/dmusic/band.c index 370b85c2941..cfced6692a8 100644 --- a/dlls/dmusic/band.c +++ b/dlls/dmusic/band.c @@ -527,3 +527,17 @@ HRESULT band_send_messages(IDirectMusicBand *iface, IDirectMusicPerformance *per return hr; } + +HRESULT band_add_instrument(IDirectMusicBand *iface, DMUS_IO_INSTRUMENT *instrument) +{ + struct band *This = impl_from_IDirectMusicBand(iface); + struct instrument_entry *entry; + + TRACE("%p, %p\n", iface, instrument); + + if (!(entry = calloc(1, sizeof(*entry)))) return E_OUTOFMEMORY; + entry->instrument = *instrument; + list_add_tail(&This->instruments, &entry->entry); + + return S_OK; +} diff --git a/dlls/dmusic/dmusic_band.h b/dlls/dmusic/dmusic_band.h index e189acb7230..fe40e3fb7f1 100644 --- a/dlls/dmusic/dmusic_band.h +++ b/dlls/dmusic/dmusic_band.h @@ -18,8 +18,10 @@ */ #include "dmusici.h" +#include "dmusicf.h" extern HRESULT create_dmband(REFIID riid, void **ret_iface); extern HRESULT band_connect_to_collection(IDirectMusicBand *iface, IDirectMusicCollection *collection); extern HRESULT band_send_messages(IDirectMusicBand *iface, IDirectMusicPerformance *performance, IDirectMusicGraph *graph, MUSIC_TIME time, DWORD track_id); +HRESULT band_add_instrument(IDirectMusicBand *iface, DMUS_IO_INSTRUMENT *instrument);