dmime: Parse note on/off events and generate a seqtrack.

This commit is contained in:
Yuxuan Shui 2024-02-21 15:27:31 +00:00 committed by Alexandre Julliard
parent 1df0e34293
commit 5ff94358a0
4 changed files with 235 additions and 17 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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)