It feels a bit weird storing and playing MIDI control events directly in seqtrack. I originally thought they were stored as curve items, but curve items would generate `DMUS_CURVE_PMSG`, but these MIDI control events are played verbatim as `DMUS_MIDI_PMSG`.
-- v2: dmime: Handle MIDI control events in MIDI files. dmime: Parse note on/off events and generate a seqtrack. dmime/tests: Call the correct QueryInterface function for DirectMusic track.
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/tests/dmime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 8a95c7f2c21..9c2a9c0e919 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -4122,7 +4122,7 @@ static void test_sequence_track(void) &IID_IDirectMusicTrack, (void **)&track); ok(hr == S_OK, "got %#lx\n", hr);
- hr = IDirectMusicSegment_QueryInterface(track, &IID_IPersistStream, (void **)&persist); + hr = IDirectMusicTrack_QueryInterface(track, &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);
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/dmime_private.h | 2 + dlls/dmime/midi.c | 125 +++++++++++++++++++++++++++++++++++-- dlls/dmime/seqtrack.c | 8 +++ dlls/dmime/tests/dmime.c | 117 ++++++++++++++++++++++++++++++---- 4 files changed, 235 insertions(+), 17 deletions(-)
diff --git a/dlls/dmime/dmime_private.h b/dlls/dmime/dmime_private.h index 1c030dc314d..f2d1947d9a1 100644 --- a/dlls/dmime/dmime_private.h +++ b/dlls/dmime/dmime_private.h @@ -89,6 +89,8 @@ extern BOOL segment_state_has_track(IDirectMusicSegmentState *iface, DWORD track extern HRESULT wave_track_create_from_chunk(IStream *stream, struct chunk_entry *parent, IDirectMusicTrack8 **ret_iface);
+extern void sequence_track_set_items(IDirectMusicTrack8 *track, DMUS_IO_SEQ_ITEM *items, unsigned int count); + extern HRESULT performance_get_dsound(IDirectMusicPerformance8 *iface, IDirectSound **dsound); extern HRESULT performance_send_segment_start(IDirectMusicPerformance8 *iface, MUSIC_TIME music_time, IDirectMusicSegmentState *state); diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index da64803600a..e4aa165815d 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -49,8 +49,20 @@ struct midi_event }; };
+struct midi_seqtrack_item +{ + struct list entry; + DMUS_IO_SEQ_ITEM item; +}; + struct midi_parser { + IDirectMusicTrack8 *seqtrack; + ULONG seqtrack_items_count; + struct list seqtrack_items; + /* Track the initial note on event generated for a note that is currently on. NULL if the note is off. */ + struct midi_seqtrack_item *note_states[128 * 16]; + IDirectMusicTrack *chordtrack; IDirectMusicTrack *bandtrack; IDirectMusicTrack *tempotrack; @@ -174,9 +186,9 @@ static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE * }
status_type = event->status & 0xf0; + event->data[0] = byte; 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); } @@ -184,7 +196,12 @@ static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE * { 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", event->status, delta_time); + event->data[1] = byte; + if (status_type == MIDI_NOTE_ON || status_type == MIDI_NOTE_OFF) + TRACE("MIDI note event status %#02x, data: %#02x, %#02x, time +%lu\n", event->status, + event->data[0], event->data[1], delta_time); + else + FIXME("MIDI event status %#02x, time +%lu, not supported\n", event->status, delta_time); }
return S_OK; @@ -234,11 +251,71 @@ static HRESULT midi_parser_handle_program_change(struct midi_parser *parser, str return hr; }
+static HRESULT midi_parser_handle_note_on_off(struct midi_parser *parser, struct midi_event *event) +{ + BYTE new_velocity = (event->status & 0xf0) == MIDI_NOTE_OFF ? 0 : event->data[1]; /* DirectMusic doesn't have noteoff velocity */ + BYTE note = event->data[0], channel = event->status & 0xf; + DWORD index = (DWORD)channel * 128 + note; + MUSIC_TIME dmusic_time; + struct midi_seqtrack_item *note_state = parser->note_states[index]; + DMUS_IO_SEQ_ITEM *seq_item; + struct midi_seqtrack_item *item; + + /* Testing shows there are 2 cases to deal with here: + * + * 1. Got note on when the note is already on, generate a NOTE event with + * new velocity and duration 1. + * 2. Got note on when the note is off, generate a NOTE event that lasts + * until the next note off event, intervening note on event doesn't matter. + */ + if (new_velocity) + { + TRACE("Adding note event at time %lu, note %u, velocity %u\n", parser->time, note, new_velocity); + + dmusic_time = (ULONGLONG)parser->time * DMUS_PPQ / parser->division; + + item = calloc(1, sizeof(struct midi_seqtrack_item)); + if (!item) return E_OUTOFMEMORY; + + seq_item = &item->item; + seq_item->mtTime = dmusic_time; + seq_item->mtDuration = 1; + seq_item->dwPChannel = channel; + seq_item->bStatus = MIDI_NOTE_ON; + seq_item->bByte1 = note; + seq_item->bByte2 = new_velocity; + list_add_tail(&parser->seqtrack_items, &item->entry); + parser->seqtrack_items_count++; + + if (!note_state) parser->note_states[index] = item; + } + else if (note_state) + { + note_state->item.mtDuration = (ULONGLONG)parser->time * DMUS_PPQ / parser->division - + note_state->item.mtTime; + if (note_state->item.mtDuration == 0) note_state->item.mtDuration = 1; + + TRACE("Note off at time %lu, note %u, duration %ld\n", parser->time, note, note_state->item.mtDuration); + + parser->note_states[index] = NULL; + } + + return S_OK; +} + +static int midi_seqtrack_item_compare(const void *a, const void *b) +{ + const DMUS_IO_SEQ_ITEM *item_a = a, *item_b = b; + return item_a->mtTime - item_b->mtTime; +} + static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment8 *segment) { WORD i = 0; HRESULT hr; MUSIC_TIME music_length = 0; + DMUS_IO_SEQ_ITEM *seq_items = NULL; + struct midi_seqtrack_item *item;
TRACE("(%p, %p): semi-stub\n", parser, segment);
@@ -266,35 +343,70 @@ static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment parser->time += event.delta_time; if (event.status == 0xff && event.meta_type == MIDI_META_SET_TEMPO) hr = midi_parser_handle_set_tempo(parser, &event); - else if ((event.status & 0xf0) == MIDI_PROGRAM_CHANGE) - hr = midi_parser_handle_program_change(parser, &event); + else + { + switch (event.status & 0xf0) + { + case MIDI_NOTE_ON: + case MIDI_NOTE_OFF: + hr = midi_parser_handle_note_on_off(parser, &event); + break; + case MIDI_PROGRAM_CHANGE: + hr = midi_parser_handle_program_change(parser, &event); + break; + default: + FIXME("Unhandled MIDI event type %#02x at time +%lu\n", event.status, parser->time); + break; + } + } if (FAILED(hr)) break; }
if (FAILED(hr)) break; + TRACE("End of track %u\n", i); if (parser->time > music_length) music_length = parser->time; parser->time = 0; + memset(parser->note_states, 0, sizeof(parser->note_states)); } + if (FAILED(hr)) return hr;
TRACE("End of file\n");
+ if ((seq_items = calloc(parser->seqtrack_items_count, sizeof(DMUS_IO_SEQ_ITEM))) == NULL) + return E_OUTOFMEMORY; + + i = 0; + LIST_FOR_EACH_ENTRY(item, &parser->seqtrack_items, struct midi_seqtrack_item, entry) + seq_items[i++] = item->item; + sequence_track_set_items(parser->seqtrack, seq_items, parser->seqtrack_items_count); + qsort(seq_items, parser->seqtrack_items_count, sizeof(DMUS_IO_SEQ_ITEM), midi_seqtrack_item_compare); + 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); if (SUCCEEDED(hr)) hr = IDirectMusicSegment8_InsertTrack(segment, parser->chordtrack, 0xffff); if (SUCCEEDED(hr) && parser->tempotrack) hr = IDirectMusicSegment8_InsertTrack(segment, parser->tempotrack, 0xffff); + if (SUCCEEDED(hr)) + hr = IDirectMusicSegment8_InsertTrack(segment, (IDirectMusicTrack *)parser->seqtrack, 0xffff);
return hr; }
static void midi_parser_destroy(struct midi_parser *parser) { + struct midi_seqtrack_item *item, *next_item; IStream_Release(parser->stream); if (parser->bandtrack) IDirectMusicTrack_Release(parser->bandtrack); if (parser->chordtrack) IDirectMusicTrack_Release(parser->chordtrack); if (parser->tempotrack) IDirectMusicTrack_Release(parser->tempotrack); + if (parser->seqtrack) IDirectMusicTrack_Release(parser->seqtrack); + LIST_FOR_EACH_ENTRY_SAFE(item, next_item, &parser->seqtrack_items, struct midi_seqtrack_item, entry) + { + list_remove(&item->entry); + free(item); + } free(parser); }
@@ -334,6 +446,7 @@ static HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser)
parser = calloc(1, sizeof(struct midi_parser)); if (!parser) return E_OUTOFMEMORY; + list_init(&parser->seqtrack_items); IStream_AddRef(stream); parser->stream = stream; parser->division = division; @@ -342,6 +455,10 @@ static HRESULT midi_parser_new(IStream *stream, struct midi_parser **out_parser) if (SUCCEEDED(hr)) hr = CoCreateInstance(&CLSID_DirectMusicChordTrack, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectMusicTrack, (void **)&parser->chordtrack); + if (SUCCEEDED(hr)) + hr = CoCreateInstance(&CLSID_DirectMusicSeqTrack, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectMusicTrack, (void **)&parser->seqtrack); + if (FAILED(hr)) midi_parser_destroy(parser); else *out_parser = parser; return hr; diff --git a/dlls/dmime/seqtrack.c b/dlls/dmime/seqtrack.c index 3252580afb6..fc50009402a 100644 --- a/dlls/dmime/seqtrack.c +++ b/dlls/dmime/seqtrack.c @@ -483,3 +483,11 @@ HRESULT create_dmseqtrack(REFIID lpcGUID, void **ppobj)
return hr; } + +void sequence_track_set_items(IDirectMusicTrack8 *track, DMUS_IO_SEQ_ITEM *items, unsigned int count) +{ + struct sequence_track *This = impl_from_IDirectMusicTrack8(track); + free(This->items); + This->items = items; + This->count = count; +} diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 9c2a9c0e919..3086dcaf978 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1633,6 +1633,27 @@ static void test_midi(void) 0xc1, /* event type, program change, channel 1 */ 0x30, /* event data, patch 48 */ }; + static const char midi_note_on[] = + { + 0x04, /* delta time = 4 */ + 0x91, /* event type, note on, channel 1 */ + 0x3c, /* event data, middle C */ + 0x40, /* event data, velocity 64 */ + }; + static const char midi_note_off[] = + { + 0x04, /* delta time = 4 */ + 0x81, /* event type, note off, channel 1 */ + 0x3c, /* event data, middle C */ + 0x0, + }; + static const char midi_note_off2[] = + { + 0x60, /* delta time = 96 */ + 0x81, /* event type, note off, channel 1 */ + 0x3c, /* event data, middle C */ + 0x0, + }; IDirectMusicSegment8 *segment = NULL; IDirectMusicTrack *track = NULL; IDirectMusicLoader8 *loader; @@ -1646,8 +1667,10 @@ static void test_midi(void) WCHAR test_mid[MAX_PATH], bogus_mid[MAX_PATH]; HRESULT hr; ULONG ret; + DWORD track_length; MUSIC_TIME next; DMUS_PMSG *msg; + DMUS_NOTE_PMSG *note; DMUS_PATCH_PMSG *patch; DMUS_TEMPO_PARAM tempo_param; #include <pshpack1.h> @@ -1685,8 +1708,8 @@ static void test_midi(void) expect_track(segment, BandTrack, -1, 0); expect_track(segment, ChordTrack, -1, 1); expect_track(segment, TempoTrack, -1, 2); - todo_wine expect_track(segment, TimeSigTrack, -1, 3); - todo_wine expect_track(segment, SeqTrack, -1, 4); + todo_wine expect_guid_track(segment, TimeSigTrack, -1, 0); + expect_guid_track(segment, SeqTrack, -1, 0); /* no more tracks */ hr = IDirectMusicSegment8_GetTrack(segment, &GUID_NULL, -1, 5, &track); ok(hr == DMUS_E_NOT_FOUND, "unexpected extra track\n"); @@ -1726,7 +1749,7 @@ static void test_midi(void) /* TempoTrack and TimeSigTrack seems to be optional. */ expect_track(segment, BandTrack, -1, 0); expect_track(segment, ChordTrack, -1, 1); - todo_wine expect_track(segment, SeqTrack, -1, 2); + expect_track(segment, SeqTrack, -1, 2); IDirectMusicSegment_Release(segment);
/* parse MIDI file with 1 track that has 1 event. */ @@ -1763,7 +1786,7 @@ static void test_midi(void) expect_track(segment, BandTrack, -1, 0); expect_track(segment, ChordTrack, -1, 1); expect_track(segment, TempoTrack, -1, 2); - todo_wine expect_track(segment, SeqTrack, -1, 3); + expect_track(segment, SeqTrack, -1, 3);
hr = IDirectMusicSegment_GetParam(segment, &GUID_TempoParam, -1, DMUS_SEG_ALLTRACKS, 0, &next, &tempo_param); ok(hr == S_OK, "got %#lx\n", hr); @@ -1811,7 +1834,7 @@ static void test_midi(void) expect_track(segment, BandTrack, -1, 0); expect_track(segment, ChordTrack, -1, 1); /* there is no tempo track. */ - todo_wine expect_track(segment, SeqTrack, -1, 2); + expect_track(segment, SeqTrack, -1, 2); IDirectMusicSegment_Release(segment);
/* parse MIDI file with program change event. */ @@ -1830,24 +1853,47 @@ static void test_midi(void) 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)); + track_length = sizeof(midi_program_change) + sizeof(midi_note_on) * 3 + sizeof(midi_note_off); + track_header.length = RtlUlongByteSwap(sizeof(track_header) - 8 + track_length); 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); + + /* Add note on/off events, like this: + * on, on, off, on + * So we can test what happens when we have two consecutive note on, and what happens with trailing note on. */ + hr = IStream_Write(stream, midi_note_on, sizeof(midi_note_on), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_note_on, sizeof(midi_note_on), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_note_off, sizeof(midi_note_off), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_note_on, sizeof(midi_note_on), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + + /* Add a second track, to test the duration of the trailing note. */ + track_header.length = RtlUlongByteSwap(sizeof(track_header) - 8 + sizeof(midi_note_on) + sizeof(midi_note_off)); + hr = IStream_Write(stream, &track_header, sizeof(track_header), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_note_on, sizeof(midi_note_on), NULL); + ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_note_off2, sizeof(midi_note_off2), 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), + ok(position.QuadPart == sizeof(header) + sizeof(track_header) * 2 + track_length + sizeof(midi_note_on) + sizeof(midi_note_off), "got %lld\n", position.QuadPart); IPersistStream_Release(persist); IStream_Release(stream); expect_track(segment, BandTrack, -1, 0); expect_track(segment, ChordTrack, -1, 1); - todo_wine expect_track(segment, SeqTrack, -1, 2); + 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); @@ -1865,11 +1911,7 @@ static void test_midi(void) 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 - */ - + /* now play the segment, and check produced messages */ hr = IDirectMusicPerformance_Init(performance, NULL, 0, 0); ok(hr == S_OK, "got %#lx\n", hr); hr = IDirectMusicPerformance_PlaySegment(performance, (IDirectMusicSegment *)segment, 0x800, 0, NULL); @@ -1891,6 +1933,55 @@ static void test_midi(void) 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_NOTE, "got msg type %#lx, expected NOTE\n", msg->dwType); + ok(msg->mtTime == 24, "got mtTime %lu, expected 24\n", msg->mtTime); + note = (DMUS_NOTE_PMSG *)msg; + ok(note->bMidiValue == 0x3c, "got note %#x, expected 0x3c\n", note->bMidiValue); + ok(note->bVelocity == 0x40, "got velocity %#x, expected 0x40\n", note->bVelocity); + ok(note->mtDuration == 600, "got mtDuration %lu, expected 600\n", note->mtDuration); + ok(note->dwPChannel == 1, "got pchannel %lu, expected 1\n", note->dwPChannel); + 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_NOTE, "got msg type %#lx, expected NOTE\n", msg->dwType); + ok(msg->mtTime == 49, "got mtTime %lu, expected 49\n", msg->mtTime); + note = (DMUS_NOTE_PMSG *)msg; + ok(note->bMidiValue == 0x3c, "got note %#x, expected 0x3c\n", note->bMidiValue); + ok(note->bVelocity == 0x40, "got velocity %#x, expected 0x40\n", note->bVelocity); + ok(note->mtDuration == 50, "got mtDuration %lu, expected 50\n", note->mtDuration); + ok(note->dwPChannel == 1, "got pchannel %lu, expected 1\n", note->dwPChannel); + 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_NOTE, "got msg type %#lx, expected NOTE\n", msg->dwType); + ok(msg->mtTime == 74, "got mtTime %lu, expected 74\n", msg->mtTime); + note = (DMUS_NOTE_PMSG *)msg; + ok(note->bMidiValue == 0x3c, "got note %#x, expected 0x3c\n", note->bMidiValue); + ok(note->bVelocity == 0x40, "got velocity %#x, expected 0x40\n", note->bVelocity); + ok(note->mtDuration == 1, "got mtDuration %lu, expected 1\n", note->mtDuration); + ok(note->dwPChannel == 1, "got pchannel %lu, expected 1\n", note->dwPChannel); + 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_NOTE, "got msg type %#lx, expected NOTE\n", msg->dwType); + ok(msg->mtTime == 124, "got mtTime %lu, expected 124\n", msg->mtTime); + note = (DMUS_NOTE_PMSG *)msg; + ok(note->bMidiValue == 0x3c, "got note %#x, expected 0x3c\n", note->bMidiValue); + ok(note->bVelocity == 0x40, "got velocity %#x, expected 0x40\n", note->bVelocity); + ok(note->mtDuration == 1, "got mtDuration %ld, expected 1\n", note->mtDuration); + ok(note->dwPChannel == 1, "got pchannel %lu, expected 1\n", note->dwPChannel); + hr = IDirectMusicPerformance_FreePMsg(performance, msg); + ok(hr == S_OK, "got %#lx\n", hr); + + /* wine generates an extra DIRTY event. */ ret = test_tool_wait_message(tool, 500, (DMUS_PMSG **)&msg); todo_wine ok(ret == WAIT_TIMEOUT, "unexpected message\n"); if (!ret)
From: Yuxuan Shui yshui@codeweavers.com
Adding them to the seqtrack, and also implementing playing them from the seqtrack. --- dlls/dmime/midi.c | 48 ++++++++++++++++++++++++++-------------- dlls/dmime/seqtrack.c | 42 ++++++++++++++++++++++++++--------- dlls/dmime/tests/dmime.c | 29 +++++++++++++++++++++--- 3 files changed, 89 insertions(+), 30 deletions(-)
diff --git a/dlls/dmime/midi.c b/dlls/dmime/midi.c index e4aa165815d..57b6a3adf90 100644 --- a/dlls/dmime/midi.c +++ b/dlls/dmime/midi.c @@ -187,22 +187,12 @@ static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE *
status_type = event->status & 0xf0; event->data[0] = byte; - if (status_type == MIDI_PROGRAM_CHANGE) - { - TRACE("MIDI program change event status %#02x, data: %#02x, time +%lu\n", event->status, - event->data[0], delta_time); - } - else - { - if (status_type != MIDI_CHANNEL_PRESSURE && (hr = stream_read_at_most(stream, &byte, 1, bytes_left)) != S_OK) - return hr; - event->data[1] = byte; - if (status_type == MIDI_NOTE_ON || status_type == MIDI_NOTE_OFF) - TRACE("MIDI note event status %#02x, data: %#02x, %#02x, time +%lu\n", event->status, - event->data[0], event->data[1], delta_time); - else - FIXME("MIDI event status %#02x, time +%lu, not supported\n", event->status, delta_time); - } + if (status_type != MIDI_PROGRAM_CHANGE && status_type != MIDI_CHANNEL_PRESSURE && + (hr = stream_read_at_most(stream, &byte, 1, bytes_left)) != S_OK) + return hr; + event->data[1] = byte; + TRACE("MIDI event status %#02x, data: %#02x, %#02x, time +%lu\n", event->status, event->data[0], + event->data[1], delta_time);
return S_OK; } @@ -303,6 +293,27 @@ static HRESULT midi_parser_handle_note_on_off(struct midi_parser *parser, struct return S_OK; }
+static HRESULT midi_parser_handle_control(struct midi_parser *parser, struct midi_event *event) +{ + struct midi_seqtrack_item *item; + DMUS_IO_SEQ_ITEM *seq_item; + MUSIC_TIME dmusic_time = (ULONGLONG)parser->time * DMUS_PPQ / parser->division; + + if ((item = calloc(1, sizeof(struct midi_seqtrack_item))) == NULL) return E_OUTOFMEMORY; + + seq_item = &item->item; + seq_item->mtTime = dmusic_time; + seq_item->mtDuration = 0; + seq_item->dwPChannel = event->status & 0xf; + seq_item->bStatus = event->status & 0xf0; + seq_item->bByte1 = event->data[0]; + seq_item->bByte2 = event->data[1]; + list_add_tail(&parser->seqtrack_items, &item->entry); + parser->seqtrack_items_count++; + + return S_OK; +} + static int midi_seqtrack_item_compare(const void *a, const void *b) { const DMUS_IO_SEQ_ITEM *item_a = a, *item_b = b; @@ -351,6 +362,11 @@ static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment case MIDI_NOTE_OFF: hr = midi_parser_handle_note_on_off(parser, &event); break; + case MIDI_CHANNEL_PRESSURE: + case MIDI_PITCH_BEND_CHANGE: + case MIDI_CONTROL_CHANGE: + hr = midi_parser_handle_control(parser, &event); + break; case MIDI_PROGRAM_CHANGE: hr = midi_parser_handle_program_change(parser, &event); break; diff --git a/dlls/dmime/seqtrack.c b/dlls/dmime/seqtrack.c index fc50009402a..1b0e99bd77c 100644 --- a/dlls/dmime/seqtrack.c +++ b/dlls/dmime/seqtrack.c @@ -16,6 +16,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "dmusic_midi.h" #include "dmime_private.h"
WINE_DEFAULT_DEBUG_CHANNEL(dmime); @@ -130,27 +131,46 @@ static HRESULT WINAPI sequence_track_Play(IDirectMusicTrack8 *iface, void *state for (i = 0; SUCCEEDED(hr) &&i < This->count; i++) { DMUS_IO_SEQ_ITEM *item = This->items + i; - DMUS_NOTE_PMSG *msg; + DMUS_PMSG *msg;
if (item->mtTime < start_time) continue; if (item->mtTime >= end_time) continue;
- if (FAILED(hr = IDirectMusicPerformance_AllocPMsg(performance, sizeof(*msg), - (DMUS_PMSG **)&msg))) - break; + if (item->bStatus == MIDI_NOTE_ON) + { + DMUS_NOTE_PMSG *note; + if (FAILED(hr = IDirectMusicPerformance_AllocPMsg(performance, sizeof(*note), + (DMUS_PMSG **)¬e))) + break; + + note->dwType = DMUS_PMSGT_NOTE; + note->mtDuration = item->mtDuration; + note->wMusicValue = item->bByte1; + note->nOffset = item->nOffset; + note->bVelocity = item->bByte2; + note->bFlags = 1; + note->bMidiValue = item->bByte1; + msg = (DMUS_PMSG *)note; + } + else + { + DMUS_MIDI_PMSG *midi; + if (FAILED(hr = IDirectMusicPerformance_AllocPMsg(performance, sizeof(*midi), + (DMUS_PMSG **)&midi))) + break; + + midi->dwType = DMUS_PMSGT_MIDI; + midi->bStatus = item->bStatus; + midi->bByte1 = item->bByte1; + midi->bByte2 = item->bByte2; + msg = (DMUS_PMSG *)midi; + }
msg->mtTime = item->mtTime + time_offset; msg->dwFlags = DMUS_PMSGF_MUSICTIME; msg->dwPChannel = item->dwPChannel; msg->dwVirtualTrackID = track_id; - msg->dwType = DMUS_PMSGT_NOTE; msg->dwGroupID = 1; - msg->mtDuration = item->mtDuration; - msg->wMusicValue = item->bByte1; - msg->nOffset = item->nOffset; - msg->bVelocity = item->bByte2; - msg->bFlags = 1; - msg->bMidiValue = item->bByte1;
if (FAILED(hr = IDirectMusicGraph_StampPMsg(graph, (DMUS_PMSG *)msg)) || FAILED(hr = IDirectMusicPerformance_SendPMsg(performance, (DMUS_PMSG *)msg))) diff --git a/dlls/dmime/tests/dmime.c b/dlls/dmime/tests/dmime.c index 3086dcaf978..9a2cd9d803c 100644 --- a/dlls/dmime/tests/dmime.c +++ b/dlls/dmime/tests/dmime.c @@ -1633,6 +1633,13 @@ static void test_midi(void) 0xc1, /* event type, program change, channel 1 */ 0x30, /* event data, patch 48 */ }; + static const char midi_control_change[] = + { + 0x04, /* delta time = 4 */ + 0xb1, /* event type, control change, channel 1 */ + 0x07, /* event data, channel volume */ + 0x40, /* event data, 64 */ + }; static const char midi_note_on[] = { 0x04, /* delta time = 4 */ @@ -1667,10 +1674,11 @@ static void test_midi(void) WCHAR test_mid[MAX_PATH], bogus_mid[MAX_PATH]; HRESULT hr; ULONG ret; - DWORD track_length; + DWORD track_length, trace2_length; MUSIC_TIME next; DMUS_PMSG *msg; DMUS_NOTE_PMSG *note; + DMUS_MIDI_PMSG *midi; DMUS_PATCH_PMSG *patch; DMUS_TEMPO_PARAM tempo_param; #include <pshpack1.h> @@ -1873,13 +1881,16 @@ static void test_midi(void) ok(hr == S_OK, "got %#lx\n", hr);
/* Add a second track, to test the duration of the trailing note. */ - track_header.length = RtlUlongByteSwap(sizeof(track_header) - 8 + sizeof(midi_note_on) + sizeof(midi_note_off)); + trace2_length = sizeof(midi_note_on) + sizeof(midi_note_off2) + sizeof(midi_control_change); + track_header.length = RtlUlongByteSwap(sizeof(track_header) - 8 + trace2_length); hr = IStream_Write(stream, &track_header, sizeof(track_header), NULL); ok(hr == S_OK, "got %#lx\n", hr); hr = IStream_Write(stream, midi_note_on, sizeof(midi_note_on), NULL); ok(hr == S_OK, "got %#lx\n", hr); hr = IStream_Write(stream, midi_note_off2, sizeof(midi_note_off2), NULL); ok(hr == S_OK, "got %#lx\n", hr); + hr = IStream_Write(stream, midi_control_change, sizeof(midi_control_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); @@ -1887,7 +1898,7 @@ static void test_midi(void) 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) * 2 + track_length + sizeof(midi_note_on) + sizeof(midi_note_off), + ok(position.QuadPart == sizeof(header) + sizeof(track_header) * 2 + track_length + trace2_length, "got %lld\n", position.QuadPart); IPersistStream_Release(persist); IStream_Release(stream); @@ -1981,6 +1992,18 @@ static void test_midi(void) 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_MIDI, "got msg type %#lx, expected MIDI\n", msg->dwType); + ok(msg->mtTime == 649, "got mtTime %lu, expected 649\n", msg->mtTime); + ok(msg->dwPChannel == 1, "got pchannel %lu, expected 1\n", msg->dwPChannel); + midi = (DMUS_MIDI_PMSG *)msg; + ok(midi->bStatus == 0xb0, "got status %#x, expected 0xb1\n", midi->bStatus); + ok(midi->bByte1 == 0x07, "got byte1 %#x, expected 0x07\n", midi->bByte1); + ok(midi->bByte2 == 0x40, "got byte2 %#x, expected 0x40\n", midi->bByte2); + hr = IDirectMusicPerformance_FreePMsg(performance, msg); + ok(hr == S_OK, "got %#lx\n", hr); + /* wine generates an extra DIRTY event. */ ret = test_tool_wait_message(tool, 500, (DMUS_PMSG **)&msg); todo_wine ok(ret == WAIT_TIMEOUT, "unexpected message\n");
On Wed Mar 6 08:41:51 2024 +0000, Rémi Bernon wrote:
Oh sorry, I didn't mean to be lecturing or anything. So I don't really know exactly but I think there's often blank lines after declaration blocks and around traces, unless these traces are very related to the statement before or after it, in which case they would be grouped with blank lines around? Anyway it's not a big deal, and I only said something because I found this large uninterrupted block of code, other places are quite alright.
Understood.
rebased and fixed style issues.
This merge request was approved by Rémi Bernon.
This merge request was approved by Michael Stefaniuc.
Unrelated test failures in ddraw4.c and protocol.c