midiparse: Update duration

Rework things a bit so that we can run over the midi events and fire callbacks
for each of them. We can then use that for calculating the duration and also for
doing playback.
Only parse as many tracks as specified in the header.
Fix default tempo;
Send MIDI tick events every 10ms
This commit is contained in:
Wim Taymans 2013-03-21 15:00:16 +01:00
parent 76080b78af
commit 5492631d84
2 changed files with 318 additions and 189 deletions

View file

@ -60,6 +60,8 @@ enum
/* FILL ME */
};
#define DEFAULT_TEMPO 500000 /* 120 BPM is the default */
typedef struct
{
guint8 *data;
@ -67,10 +69,22 @@ typedef struct
guint offset;
guint8 running_status;
GstClockTime position;
guint64 pulse;
gboolean eot;
} GstMidiTrack;
typedef struct
{
GstFlowReturn (*handle_sysex) (GstMidiParse * parse, GstMidiTrack * track,
guint8 event, guint8 * data, guint length, gpointer user_data);
GstFlowReturn (*handle_meta) (GstMidiParse * parse, GstMidiTrack * track,
guint8 event, guint8 type, guint8 * data,
guint length, gpointer user_data);
GstFlowReturn (*handle_midi) (GstMidiParse * parse,
guint8 event, guint8 * data, guint length, gpointer user_data);
} GstMidiCallbacks;
static void gst_midi_parse_finalize (GObject * object);
static gboolean gst_midi_parse_sink_event (GstPad * pad, GstObject * parent,
@ -360,6 +374,129 @@ parse_varlen (GstMidiParse * midiparse, guint8 * data, guint size,
return 0;
}
static GstFlowReturn
handle_meta_event (GstMidiParse * midiparse, GstMidiTrack * track,
guint8 event, GstMidiCallbacks * callback, gpointer user_data)
{
GstFlowReturn ret;
guint8 type;
guint8 *data;
guint size, consumed;
gint32 length;
track->offset += 1;
data = track->data + track->offset;
size = track->size - track->offset;
if (size < 1)
goto short_file;
type = data[0];
consumed = parse_varlen (midiparse, data + 1, size - 1, &length);
if (consumed == 0)
goto short_file;
data += consumed + 1;
size -= consumed + 1;
if (size < length)
goto short_file;
GST_DEBUG_OBJECT (midiparse, "handle meta event type 0x%02x, length %u",
type, length);
if (callback->handle_meta)
ret = callback->handle_meta (midiparse, track, event, type,
data, length, user_data);
else
ret = GST_FLOW_OK;
switch (type) {
case 0x51:
{
guint32 uspqn = (data[0] << 16) | (data[1] << 8) | data[2];
midiparse->tempo = (uspqn ? uspqn : DEFAULT_TEMPO);
GST_DEBUG_OBJECT (midiparse, "tempo %u", midiparse->tempo);
break;
}
default:
break;
}
track->offset += consumed + length + 1;
return ret;
/* ERRORS */
short_file:
{
GST_DEBUG_OBJECT (midiparse, "not enough data");
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
handle_sysex_event (GstMidiParse * midiparse, GstMidiTrack * track,
guint8 event, GstMidiCallbacks * callback, gpointer user_data)
{
GstFlowReturn ret;
guint8 *data;
guint size, consumed;
gint32 length;
track->offset += 1;
data = track->data + track->offset;
size = track->size - track->offset;
consumed = parse_varlen (midiparse, data, size, &length);
if (consumed == 0)
goto short_file;
data += consumed;
size -= consumed;
if (size < length)
goto short_file;
GST_DEBUG_OBJECT (midiparse, "handle sysex event 0x%02x, length %u",
event, length);
if (callback->handle_sysex)
ret = callback->handle_sysex (midiparse, track, event,
data, length, user_data);
else
ret = GST_FLOW_OK;
track->offset += consumed + length;
return ret;
/* ERRORS */
short_file:
{
GST_DEBUG_OBJECT (midiparse, "not enough data");
return GST_FLOW_ERROR;
}
}
static guint8
event_from_status (GstMidiParse * midiparse, GstMidiTrack * track,
guint8 status)
{
if ((status & 0x80) == 0) {
if ((track->running_status & 0x80) == 0)
return 0;
return track->running_status;
} else {
return status;
}
}
static gboolean
update_track_position (GstMidiParse * midiparse, GstMidiTrack * track)
{
@ -377,11 +514,11 @@ update_track_position (GstMidiParse * midiparse, GstMidiTrack * track)
if (consumed == 0)
goto eot;
track->position += delta_time;
track->pulse += delta_time;
track->offset += consumed;
GST_LOG_OBJECT (midiparse, "updated track to %" GST_TIME_FORMAT,
GST_TIME_ARGS (track->position));
GST_LOG_OBJECT (midiparse, "updated track to pulse %" G_GUINT64_FORMAT,
track->pulse);
return TRUE;
@ -389,25 +526,136 @@ update_track_position (GstMidiParse * midiparse, GstMidiTrack * track)
eot:
{
GST_DEBUG_OBJECT (midiparse, "track ended");
track->position = GST_CLOCK_TIME_NONE;
track->eot = TRUE;
return FALSE;
}
}
static GstFlowReturn
handle_next_event (GstMidiParse * midiparse, GstMidiTrack * track,
GstMidiCallbacks * callback, gpointer user_data)
{
GstFlowReturn ret = GST_FLOW_OK;
guint8 status, event;
guint length;
guint8 *data;
data = &track->data[track->offset];
status = data[0];
event = event_from_status (midiparse, track, status);
GST_LOG_OBJECT (midiparse, "track %p, status 0x%02x, event 0x%02x", track,
status, event);
switch (event & 0xf0) {
case 0xf0:
switch (event) {
case 0xff:
ret =
handle_meta_event (midiparse, track, event, callback, user_data);
break;
case 0xf0:
case 0xf7:
ret =
handle_sysex_event (midiparse, track, event, callback, user_data);
break;
default:
goto unhandled_event;
}
length = 0;
break;
case 0xc0:
case 0xd0:
length = 1;
break;
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
case 0xe0:
length = 2;
break;
default:
goto undefined_status;
}
if (length > 0) {
if (status & 0x80) {
if (callback->handle_midi)
ret = callback->handle_midi (midiparse, event,
data + 1, length, user_data);
track->offset += length + 1;
} else {
if (callback->handle_midi)
ret = callback->handle_midi (midiparse, event,
data, length + 1, user_data);
track->offset += length;
}
}
if (ret == GST_FLOW_OK) {
if (event < 0xF8)
track->running_status = event;
update_track_position (midiparse, track);
}
return ret;
/* ERRORS */
undefined_status:
{
GST_ERROR_OBJECT (midiparse, "Undefined status and invalid running status");
return GST_FLOW_ERROR;
}
unhandled_event:
{
/* we don't know the size so we can't continue parsing */
GST_ERROR_OBJECT (midiparse, "unhandled event 0x%08x", event);
return GST_FLOW_ERROR;
}
}
static void
reset_track (GstMidiParse * midiparse, GstMidiTrack * track)
{
GST_DEBUG_OBJECT (midiparse, "reset track");
track->offset = 0;
track->pulse = 0;
track->eot = FALSE;
track->running_status = 0xff;
update_track_position (midiparse, track);
}
static gboolean
parse_MTrk (GstMidiParse * midiparse, guint8 * data, guint size)
{
GstMidiTrack *track;
GstMidiCallbacks cb = { NULL, NULL, NULL };
/* ignore excess tracks */
if (midiparse->track_count >= midiparse->ntracks)
return TRUE;
track = g_slice_new (GstMidiTrack);
track->data = data;
track->size = size;
track->offset = 0;
track->position = 0;
track->running_status = 0xff;
update_track_position (midiparse, track);
reset_track (midiparse, track);
midiparse->tracks = g_list_append (midiparse->tracks, track);
midiparse->track_count++;
/* now loop over all events and calculate the duration */
while (!track->eot) {
handle_next_event (midiparse, track, &cb, NULL);
}
midiparse->segment.duration = gst_util_uint64_scale (track->pulse,
1000 * midiparse->tempo, midiparse->division);
GST_DEBUG_OBJECT (midiparse, "duration %" GST_TIME_FORMAT,
GST_TIME_ARGS (midiparse->segment.duration));
reset_track (midiparse, track);
return TRUE;
}
@ -547,10 +795,13 @@ gst_midi_parse_parse_song (GstMidiParse * midiparse)
GST_DEBUG_OBJECT (midiparse, "Parsing song");
gst_segment_init (&midiparse->segment, GST_FORMAT_TIME);
midiparse->pulse = 0;
size = gst_adapter_available (midiparse->adapter);
data = gst_adapter_take (midiparse->adapter, size);
midiparse->tempo = 60;
midiparse->tempo = DEFAULT_TEMPO;
if (!find_midi_chunk (midiparse, data, size, &offset, &length))
goto invalid_format;
@ -570,8 +821,6 @@ gst_midi_parse_parse_song (GstMidiParse * midiparse)
gst_pad_set_caps (midiparse->srcpad, outcaps);
gst_caps_unref (outcaps);
gst_segment_init (&midiparse->segment, GST_FORMAT_TIME);
gst_pad_push_event (midiparse->srcpad,
gst_event_new_segment (&midiparse->segment));
@ -595,181 +844,29 @@ invalid_format:
}
static GstFlowReturn
handle_meta_event (GstMidiParse * midiparse, GstMidiTrack * track)
handle_play_midi (GstMidiParse * midiparse,
guint8 event, guint8 * data, guint length, gpointer user_data)
{
guint8 type;
guint8 *data;
guint size, consumed;
gint32 length;
track->offset += 1;
data = track->data + track->offset;
size = track->size - track->offset;
if (size < 1)
goto short_file;
type = data[0];
consumed = parse_varlen (midiparse, data + 1, size - 1, &length);
if (consumed == 0)
goto short_file;
data += consumed + 1;
size -= consumed + 1;
GST_DEBUG_OBJECT (midiparse, "handle meta event type 0x%02x, length %u",
type, length);
switch (type) {
case 0x51:
{
guint32 uspqn = (data[0] << 16) | (data[1] << 8) | data[2];
midiparse->tempo = (uspqn ? uspqn : 1);
GST_DEBUG_OBJECT (midiparse, "tempo %u", midiparse->tempo);
break;
}
default:
break;
}
track->offset += consumed + length + 1;
return GST_FLOW_OK;
/* ERRORS */
short_file:
{
GST_DEBUG_OBJECT (midiparse, "not enough data");
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
handle_sysex_event (GstMidiParse * midiparse, guint8 event,
GstMidiTrack * track)
{
guint8 *data;
guint size, consumed;
gint32 length;
track->offset += 1;
data = track->data + track->offset;
size = track->size - track->offset;
consumed = parse_varlen (midiparse, data, size, &length);
if (consumed == 0)
goto short_file;
data += consumed;
size -= consumed;
GST_DEBUG_OBJECT (midiparse, "handle sysex event 0x%02x, length %u",
event, length);
track->offset += consumed + length;
return GST_FLOW_OK;
/* ERRORS */
short_file:
{
GST_DEBUG_OBJECT (midiparse, "not enough data");
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
handle_next_event (GstMidiParse * midiparse, GstMidiTrack * track)
{
GstFlowReturn ret = GST_FLOW_OK;
guint8 status, event;
guint length;
guint8 *data;
data = &track->data[track->offset];
status = data[0];
GST_LOG_OBJECT (midiparse, "track %p, status 0x%02x", track, status);
if ((status & 0x80) == 0) {
if ((track->running_status & 0x80) == 0)
goto undefined_status;
event = track->running_status;
} else {
event = status;
}
switch (event & 0xf0) {
case 0xf0:
switch (event) {
case 0xff:
ret = handle_meta_event (midiparse, track);
break;
case 0xf0:
case 0xf7:
ret = handle_sysex_event (midiparse, event, track);
break;
default:
GST_ERROR_OBJECT (midiparse, "unhandled event 0x%08x", event);
return GST_FLOW_ERROR;
}
length = 0;
break;
case 0xc0:
case 0xd0:
length = 1;
break;
default:
length = 2;
break;
}
if (length > 0) {
GstBuffer *outbuf;
GstMapInfo info;
GstClockTime position;
outbuf = gst_buffer_new_allocate (NULL, length + 1, NULL);
gst_buffer_map (outbuf, &info, GST_MAP_WRITE);
info.data[0] = event;
if (status & 0x80) {
memcpy (&info.data[1], &data[1], length);
track->offset += length + 1;
} else {
info.data[1] = status;
memcpy (&info.data[2], &data[1], length - 1);
track->offset += length;
}
if (length)
memcpy (&info.data[1], data, length);
gst_buffer_unmap (outbuf, &info);
GST_BUFFER_PTS (outbuf) = gst_util_uint64_scale (track->position,
1000 * midiparse->tempo, midiparse->division);
GST_BUFFER_DTS (outbuf) = GST_BUFFER_PTS (outbuf);
position = midiparse->segment.position;
GST_BUFFER_PTS (outbuf) = position;
GST_BUFFER_DTS (outbuf) = position;
GST_DEBUG_OBJECT (midiparse, "pushing %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_PTS (outbuf)));
GST_TIME_ARGS (position));
ret = gst_pad_push (midiparse->srcpad, outbuf);
}
if (event < 0xF8)
track->running_status = event;
return ret;
/* ERRORS */
undefined_status:
{
GST_ERROR_OBJECT (midiparse, "Undefined status and invalid running status");
return GST_FLOW_ERROR;
}
return gst_pad_push (midiparse->srcpad, outbuf);
}
static GstFlowReturn
@ -777,28 +874,58 @@ gst_midi_parse_do_play (GstMidiParse * midiparse)
{
GstFlowReturn res = GST_FLOW_OK;
GList *walk;
guint64 position, next_position = GST_CLOCK_TIME_NONE;
guint64 pulse, next_pulse = G_MAXUINT64;
GstClockTime position, next_position;
GstMidiCallbacks cb = { NULL, NULL, handle_play_midi };
guint64 tick;
pulse = midiparse->pulse;
position = midiparse->segment.position;
GST_DEBUG_OBJECT (midiparse, "pulse %" G_GUINT64_FORMAT ", position %"
GST_TIME_FORMAT, pulse, GST_TIME_ARGS (position));
for (walk = midiparse->tracks; walk; walk = g_list_next (walk)) {
GstMidiTrack *track = walk->data;
while (track->position == position) {
res = handle_next_event (midiparse, track);
while (!track->eot && track->pulse == pulse) {
res = handle_next_event (midiparse, track, &cb, NULL);
if (res != GST_FLOW_OK)
goto done;
update_track_position (midiparse, track);
}
if (track->position < next_position)
next_position = track->position;
if (!track->eot && track->pulse < next_pulse)
next_pulse = track->pulse;
}
if (next_position == GST_CLOCK_TIME_NONE)
if (next_pulse == G_MAXUINT64)
goto eos;
tick = position / (10 * GST_MSECOND);
GST_DEBUG_OBJECT (midiparse, "current tick %" G_GUINT64_FORMAT, tick);
next_position = gst_util_uint64_scale (next_pulse,
1000 * midiparse->tempo, midiparse->division);
GST_DEBUG_OBJECT (midiparse, "next position %" GST_TIME_FORMAT,
GST_TIME_ARGS (next_position));
/* send 10ms ticks to advance the downstream element */
while (TRUE) {
guint8 midi_tick = 0xf9;
/* get position of next tick */
position = ++tick * (10 * GST_MSECOND);
GST_DEBUG_OBJECT (midiparse, "tick %" G_GUINT64_FORMAT
", position %" GST_TIME_FORMAT, tick, GST_TIME_ARGS (position));
if (position >= next_position)
break;
midiparse->segment.position = position;
handle_play_midi (midiparse, midi_tick, NULL, 0, NULL);
}
midiparse->pulse = next_pulse;
midiparse->segment.position = next_position;
done:

View file

@ -63,6 +63,7 @@ struct _GstMidiParse
guint16 division;
GList *tracks;
guint track_count;
guint64 offset;
GstAdapter *adapter;
@ -70,6 +71,7 @@ struct _GstMidiParse
/* output data */
gboolean discont;
GstSegment segment;
guint64 pulse;
};
struct _GstMidiParseClass