dmime: Parse MIDI program change events and generate a bandtrack.
This commit is contained in:
parent
aebcb1a996
commit
05347b9703
5 changed files with 236 additions and 23 deletions
|
@ -4,6 +4,7 @@ PARENTSRC = ../dmusic
|
|||
|
||||
SOURCES = \
|
||||
audiopath.c \
|
||||
band.c \
|
||||
dmime.idl \
|
||||
dmime_main.c \
|
||||
dmobject.c \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue