dmime: Parse MIDI Set Tempo meta events and generate a tempotrack.

This commit is contained in:
Yuxuan Shui 2024-02-15 14:59:29 +00:00 committed by Alexandre Julliard
parent 1b1f216278
commit f0fc4a0d89
2 changed files with 87 additions and 10 deletions

View file

@ -35,18 +35,49 @@ struct midi_event
{
MUSIC_TIME delta_time;
BYTE status;
BYTE data[2];
union
{
struct
{
BYTE data[2];
};
struct
{
BYTE meta_type;
ULONG tempo;
};
};
};
struct midi_parser
{
IDirectMusicTrack *chordtrack;
IDirectMusicTrack *bandtrack;
IDirectMusicTrack *tempotrack;
MUSIC_TIME time;
IStream *stream;
DWORD division;
};
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,
};
static HRESULT stream_read_at_most(IStream *stream, void *buffer, ULONG size, ULONG *bytes_left)
{
HRESULT hr;
@ -76,8 +107,9 @@ static HRESULT read_variable_length_number(IStream *stream, DWORD *out, ULONG *b
static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE *last_status, ULONG *bytes_left)
{
BYTE byte, status_type, meta_type;
BYTE byte, status_type;
DWORD length;
BYTE data[3];
LARGE_INTEGER offset;
HRESULT hr = S_OK;
DWORD delta_time;
@ -96,21 +128,31 @@ static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE *
if (event->status == MIDI_META)
{
meta_type = byte;
event->meta_type = byte;
if ((hr = read_variable_length_number(stream, &length, bytes_left)) != S_OK) return hr;
switch (meta_type)
switch (event->meta_type)
{
case MIDI_META_SET_TEMPO:
if (length != 3)
{
ERR("Invalid MIDI meta event length %lu for set tempo event.\n", length);
return E_FAIL;
}
if (FAILED(hr = stream_read_at_most(stream, data, 3, bytes_left))) return hr;
event->tempo = (data[0] << 16) | (data[1] << 8) | data[2];
break;
default:
if (*bytes_left < length) return S_FALSE;
offset.QuadPart = length;
if (FAILED(hr = IStream_Seek(stream, offset, STREAM_SEEK_CUR, NULL))) return hr;
FIXME("MIDI meta event type %#02x, length %lu, time +%lu. not supported\n", meta_type,
length, delta_time);
FIXME("MIDI meta event type %#02x, length %lu, time +%lu. not supported\n",
event->meta_type, length, delta_time);
*bytes_left -= length;
event->tempo = 0;
}
TRACE("MIDI meta event type %#02x, length %lu, time +%lu\n", meta_type, length, delta_time);
TRACE("MIDI meta event type %#02x, length %lu, time +%lu\n", event->meta_type, length, delta_time);
return S_OK;
}
else if (event->status == MIDI_SYSEX1 || event->status == MIDI_SYSEX2)
@ -148,6 +190,22 @@ static HRESULT read_midi_event(IStream *stream, struct midi_event *event, BYTE *
return S_OK;
}
static HRESULT midi_parser_handle_set_tempo(struct midi_parser *parser, struct midi_event *event)
{
DMUS_TEMPO_PARAM tempo;
MUSIC_TIME dmusic_time = (ULONGLONG)parser->time * DMUS_PPQ / parser->division;
HRESULT hr;
if (!parser->tempotrack && FAILED(hr = CoCreateInstance(&CLSID_DirectMusicTempoTrack, NULL, CLSCTX_INPROC_SERVER,
&IID_IDirectMusicTrack, (void **)&parser->tempotrack)))
return hr;
tempo.mtTime = dmusic_time;
tempo.dblTempo = 60 * 1000000.0 / event->tempo;
TRACE("Adding tempo at time %lu, tempo %f\n", dmusic_time, tempo.dblTempo);
return IDirectMusicTrack_SetParam(parser->tempotrack, &GUID_TempoParam, dmusic_time, &tempo);
}
static HRESULT midi_parser_handle_program_change(struct midi_parser *parser, struct midi_event *event)
{
HRESULT hr;
@ -206,7 +264,9 @@ static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment
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)
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);
if (FAILED(hr)) break;
}
@ -223,6 +283,8 @@ static HRESULT midi_parser_parse(struct midi_parser *parser, IDirectMusicSegment
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);
return hr;
}
@ -232,6 +294,7 @@ static void midi_parser_destroy(struct midi_parser *parser)
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);
free(parser);
}

View file

@ -1646,8 +1646,10 @@ static void test_midi(void)
WCHAR test_mid[MAX_PATH], bogus_mid[MAX_PATH];
HRESULT hr;
ULONG ret;
MUSIC_TIME next;
DMUS_PMSG *msg;
DMUS_PATCH_PMSG *patch;
DMUS_TEMPO_PARAM tempo_param;
#include <pshpack1.h>
struct
{
@ -1682,7 +1684,7 @@ static void test_midi(void)
expect_track(segment, BandTrack, -1, 0);
expect_track(segment, ChordTrack, -1, 1);
todo_wine expect_track(segment, TempoTrack, -1, 2);
expect_track(segment, TempoTrack, -1, 2);
todo_wine expect_track(segment, TimeSigTrack, -1, 3);
todo_wine expect_track(segment, SeqTrack, -1, 4);
/* no more tracks */
@ -1760,8 +1762,20 @@ static void test_midi(void)
IStream_Release(stream);
expect_track(segment, BandTrack, -1, 0);
expect_track(segment, ChordTrack, -1, 1);
todo_wine expect_track(segment, TempoTrack, -1, 2);
expect_track(segment, TempoTrack, -1, 2);
todo_wine 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);
ok(next == 24, "got %ld, expected 24\n", next);
ok(tempo_param.mtTime == 24, "got %ld, expected 24\n", tempo_param.mtTime);
ok(tempo_param.dblTempo == 300.0, "got %f, expected 300.0\n", tempo_param.dblTempo);
hr = IDirectMusicSegment_GetParam(segment, &GUID_TempoParam, -1, DMUS_SEG_ALLTRACKS, 26, &next, &tempo_param);
ok(hr == S_OK, "got %#lx\n", hr);
ok(next == 0, "got %ld, expected 24\n", next);
ok(tempo_param.mtTime == -2, "got %ld, expected -6\n", tempo_param.mtTime);
ok(tempo_param.dblTempo == 300.0, "got %f, expected 300.0\n", tempo_param.dblTempo);
IDirectMusicSegment_Release(segment);
/* parse MIDI file with a track with 0 length, but has an event. */