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)