1
0
mirror of https://github.com/wine-mirror/wine synced 2024-06-29 06:14:34 +00:00

dmime: Parse MIDI program change events and generate a bandtrack.

This commit is contained in:
Yuxuan Shui 2024-02-27 15:43:52 +00:00 committed by Alexandre Julliard
parent aebcb1a996
commit 05347b9703
5 changed files with 236 additions and 23 deletions

View File

@ -4,6 +4,7 @@ PARENTSRC = ../dmusic
SOURCES = \
audiopath.c \
band.c \
dmime.idl \
dmime_main.c \
dmobject.c \

View File

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

View File

@ -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 <pshpack1.h>
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);
}

View File

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

View File

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