This significantly speeds up parsing MIDI files with complex tempo-maps. e.g. "Black MIDI Trilogy_2.mid" has 24134 Tempo changes. Prior to this commit parsing that file took over 5 minutes. now it loads in under one seconds (libsmf only; libardour still add overhead, and now needs about 30-40 seconds, previously it took about 10 mins). The problem was that every call to `smf_track_add_event_pulses()` calls `seconds_from_pulses()` which calls `smf_get_tempo_by_seconds()` which iterates over the tempo-map: for every midi-event { for ever tempo until that midi-event {..} } This does not scale to 3.5M events and 24k tempo-changes.
1043 lines
24 KiB
C
1043 lines
24 KiB
C
/*-
|
|
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
|
|
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* Various functions.
|
|
*
|
|
*/
|
|
|
|
/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
#ifdef PLATFORM_WINDOWS
|
|
#include <winsock2.h>
|
|
#else
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#include "smf.h"
|
|
#include "smf_private.h"
|
|
|
|
/**
|
|
* Allocates new smf_t structure.
|
|
* \return pointer to smf_t or NULL.
|
|
*/
|
|
smf_t *
|
|
smf_new(void)
|
|
{
|
|
int cantfail;
|
|
|
|
smf_t *smf = (smf_t*)malloc(sizeof(smf_t));
|
|
if (smf == NULL) {
|
|
g_warning("Cannot allocate smf_t structure: %s", strerror(errno));
|
|
return (NULL);
|
|
}
|
|
|
|
memset(smf, 0, sizeof(smf_t));
|
|
|
|
smf->tracks_array = g_ptr_array_new();
|
|
assert(smf->tracks_array);
|
|
|
|
smf->tempo_array = g_ptr_array_new();
|
|
assert(smf->tempo_array);
|
|
|
|
cantfail = smf_set_ppqn(smf, 120);
|
|
assert(!cantfail);
|
|
|
|
cantfail = smf_set_format(smf, 0);
|
|
assert(!cantfail);
|
|
|
|
smf_init_tempo(smf);
|
|
|
|
return (smf);
|
|
}
|
|
|
|
/**
|
|
* Frees smf and all it's descendant structures.
|
|
*/
|
|
void
|
|
smf_delete(smf_t *smf)
|
|
{
|
|
/* Remove all the tracks, from last to first. */
|
|
while (smf->tracks_array->len > 0)
|
|
smf_track_delete((smf_track_t*)g_ptr_array_index(smf->tracks_array, smf->tracks_array->len - 1));
|
|
|
|
smf_fini_tempo(smf);
|
|
|
|
assert(smf->tracks_array->len == 0);
|
|
assert(smf->number_of_tracks == 0);
|
|
g_ptr_array_free(smf->tracks_array, TRUE);
|
|
g_ptr_array_free(smf->tempo_array, TRUE);
|
|
|
|
memset(smf, 0, sizeof(smf_t));
|
|
free(smf);
|
|
}
|
|
|
|
/**
|
|
* Allocates new smf_track_t structure.
|
|
* \return pointer to smf_track_t or NULL.
|
|
*/
|
|
smf_track_t *
|
|
smf_track_new(void)
|
|
{
|
|
smf_track_t *track = (smf_track_t*)malloc(sizeof(smf_track_t));
|
|
if (track == NULL) {
|
|
g_warning("Cannot allocate smf_track_t structure: %s", strerror(errno));
|
|
return (NULL);
|
|
}
|
|
|
|
memset(track, 0, sizeof(smf_track_t));
|
|
track->next_event_number = 0;
|
|
|
|
track->events_array = g_ptr_array_new();
|
|
assert(track->events_array);
|
|
|
|
return (track);
|
|
}
|
|
|
|
/**
|
|
* Detaches track from its smf and frees it.
|
|
*/
|
|
void
|
|
smf_track_delete(smf_track_t *track)
|
|
{
|
|
assert(track);
|
|
assert(track->events_array);
|
|
|
|
/* Remove all the events */
|
|
unsigned int i;
|
|
for (i = 0; i < track->events_array->len; ++i) {
|
|
smf_event_t* ev = (smf_event_t*)g_ptr_array_index(track->events_array, i);
|
|
free (ev->midi_buffer);
|
|
free (ev);
|
|
}
|
|
|
|
g_ptr_array_remove_range(track->events_array, 0, track->events_array->len);
|
|
track->number_of_events = 0;
|
|
|
|
if (track->smf)
|
|
smf_track_remove_from_smf(track);
|
|
|
|
assert(track->events_array->len == 0);
|
|
g_ptr_array_free(track->events_array, TRUE);
|
|
|
|
if (track->name) {
|
|
free (track->name);
|
|
}
|
|
if (track->instrument) {
|
|
free (track->instrument);
|
|
}
|
|
|
|
memset(track, 0, sizeof(smf_track_t));
|
|
free(track);
|
|
}
|
|
|
|
|
|
/**
|
|
* Appends smf_track_t to smf.
|
|
*/
|
|
void
|
|
smf_add_track(smf_t *smf, smf_track_t *track)
|
|
{
|
|
#ifndef NDEBUG
|
|
int cantfail;
|
|
#endif
|
|
|
|
assert(track->smf == NULL);
|
|
|
|
track->smf = smf;
|
|
g_ptr_array_add(smf->tracks_array, track);
|
|
|
|
smf->number_of_tracks++;
|
|
track->track_number = smf->number_of_tracks;
|
|
|
|
if (smf->number_of_tracks > 1) {
|
|
#ifndef NDEBUG
|
|
cantfail = smf_set_format(smf, 1);
|
|
assert(!cantfail);
|
|
#else
|
|
smf_set_format(smf, 1);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detaches track from the smf.
|
|
*/
|
|
void
|
|
smf_track_remove_from_smf(smf_track_t *track)
|
|
{
|
|
int i;
|
|
size_t j;
|
|
smf_track_t *tmp;
|
|
smf_event_t *ev;
|
|
|
|
assert(track->smf != NULL);
|
|
|
|
track->smf->number_of_tracks--;
|
|
|
|
assert(track->smf->tracks_array);
|
|
g_ptr_array_remove(track->smf->tracks_array, track);
|
|
|
|
/* Renumber the rest of the tracks, so they are consecutively numbered. */
|
|
for (i = track->track_number; i <= track->smf->number_of_tracks; i++) {
|
|
tmp = smf_get_track_by_number(track->smf, i);
|
|
tmp->track_number = i;
|
|
|
|
/*
|
|
* Events have track numbers too. I guess this wasn't a wise
|
|
* decision. ;-/
|
|
*/
|
|
for (j = 1; j <= tmp->number_of_events; j++) {
|
|
ev = smf_track_get_event_by_number(tmp, j);
|
|
ev->track_number = i;
|
|
}
|
|
}
|
|
|
|
track->track_number = -1;
|
|
track->smf = NULL;
|
|
}
|
|
|
|
/**
|
|
* Allocates new smf_event_t structure. The caller is responsible for allocating
|
|
* event->midi_buffer, filling it with MIDI data and setting event->midi_buffer_length properly.
|
|
* Note that event->midi_buffer will be freed by smf_event_delete.
|
|
* \return pointer to smf_event_t or NULL.
|
|
*/
|
|
smf_event_t *
|
|
smf_event_new(void)
|
|
{
|
|
smf_event_t *event = (smf_event_t*)malloc(sizeof(smf_event_t));
|
|
if (event == NULL) {
|
|
g_warning("Cannot allocate smf_event_t structure: %s", strerror(errno));
|
|
return (NULL);
|
|
}
|
|
|
|
memset(event, 0, sizeof(smf_event_t));
|
|
|
|
event->delta_time_pulses = -1;
|
|
event->time_pulses = -1;
|
|
event->track_number = -1;
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Allocates an smf_event_t structure and fills it with "len" bytes copied
|
|
* from "midi_data".
|
|
* \param midi_data Pointer to MIDI data. It sill be copied to the newly allocated event->midi_buffer.
|
|
* \param len Length of the buffer. It must be proper MIDI event length, e.g. 3 for Note On event.
|
|
* \return Event containing MIDI data or NULL.
|
|
*/
|
|
smf_event_t *
|
|
smf_event_new_from_pointer(const void *midi_data, size_t len)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
event = smf_event_new();
|
|
if (event == NULL)
|
|
return (NULL);
|
|
|
|
event->midi_buffer_length = len;
|
|
event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
|
|
if (event->midi_buffer == NULL) {
|
|
g_warning("Cannot allocate MIDI buffer structure: %s", strerror(errno));
|
|
smf_event_delete(event);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
memcpy(event->midi_buffer, midi_data, len);
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Allocates an smf_event_t structure and fills it with at most three bytes of data.
|
|
* For example, if you need to create Note On event, do something like this:
|
|
*
|
|
* smf_event_new_from_bytes(0x90, 0x3C, 0x7f);
|
|
*
|
|
* To create event for MIDI message that is shorter than three bytes, do something
|
|
* like this:
|
|
*
|
|
* smf_event_new_from_bytes(0xC0, 0x42, -1);
|
|
*
|
|
* \param first_byte First byte of MIDI message. Must be valid status byte.
|
|
* \param second_byte Second byte of MIDI message or -1, if message is one byte long.
|
|
* \param third_byte Third byte of MIDI message or -1, if message is two bytes long.
|
|
* \return Event containing MIDI data or NULL.
|
|
*/
|
|
smf_event_t *
|
|
smf_event_new_from_bytes(int first_byte, int second_byte, int third_byte)
|
|
{
|
|
size_t len;
|
|
|
|
smf_event_t *event;
|
|
|
|
event = smf_event_new();
|
|
if (event == NULL)
|
|
return (NULL);
|
|
|
|
if (first_byte < 0) {
|
|
g_warning("First byte of MIDI message cannot be < 0");
|
|
smf_event_delete(event);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
if (first_byte > 255) {
|
|
g_warning("smf_event_new_from_bytes: first byte is %d, which is larger than 255.", first_byte);
|
|
return (NULL);
|
|
}
|
|
|
|
if (!is_status_byte(first_byte)) {
|
|
g_warning("smf_event_new_from_bytes: first byte is not a valid status byte.");
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
if (second_byte < 0)
|
|
len = 1;
|
|
else if (third_byte < 0)
|
|
len = 2;
|
|
else
|
|
len = 3;
|
|
|
|
if (len > 1) {
|
|
if (second_byte > 255) {
|
|
g_warning("smf_event_new_from_bytes: second byte is %d, which is larger than 255.", second_byte);
|
|
return (NULL);
|
|
}
|
|
|
|
if (is_status_byte(second_byte)) {
|
|
g_warning("smf_event_new_from_bytes: second byte cannot be a status byte.");
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
if (len > 2) {
|
|
if (third_byte > 255) {
|
|
g_warning("smf_event_new_from_bytes: third byte is %d, which is larger than 255.", third_byte);
|
|
return (NULL);
|
|
}
|
|
|
|
if (is_status_byte(third_byte)) {
|
|
g_warning("smf_event_new_from_bytes: third byte cannot be a status byte.");
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
event->midi_buffer_length = len;
|
|
event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
|
|
if (event->midi_buffer == NULL) {
|
|
g_warning("Cannot allocate MIDI buffer structure: %s", strerror(errno));
|
|
smf_event_delete(event);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
event->midi_buffer[0] = first_byte;
|
|
if (len > 1)
|
|
event->midi_buffer[1] = second_byte;
|
|
if (len > 2)
|
|
event->midi_buffer[2] = third_byte;
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Detaches event from its track and frees it.
|
|
*/
|
|
void
|
|
smf_event_delete(smf_event_t *event)
|
|
{
|
|
if (event->track != NULL)
|
|
smf_event_remove_from_track(event);
|
|
|
|
if (event->midi_buffer != NULL) {
|
|
memset(event->midi_buffer, 0, event->midi_buffer_length);
|
|
free(event->midi_buffer);
|
|
}
|
|
|
|
memset(event, 0, sizeof(smf_event_t));
|
|
free(event);
|
|
}
|
|
|
|
/**
|
|
* Used for sorting track->events_array.
|
|
*/
|
|
static gint
|
|
events_array_compare_function(gconstpointer aa, gconstpointer bb)
|
|
{
|
|
const smf_event_t *a, *b;
|
|
|
|
/* "The comparison function for g_ptr_array_sort() doesn't take the pointers
|
|
from the array as arguments, it takes pointers to the pointers in the array." */
|
|
a = (const smf_event_t *)*(const gpointer *)aa;
|
|
b = (const smf_event_t *)*(const gpointer *)bb;
|
|
|
|
if (a->time_pulses < b->time_pulses)
|
|
return (-1);
|
|
|
|
if (a->time_pulses > b->time_pulses)
|
|
return (1);
|
|
|
|
/*
|
|
* We need to preserve original order, otherwise things will break
|
|
* when there are several events with the same ->time_pulses.
|
|
* XXX: This is an ugly hack; we should remove sorting altogether.
|
|
*/
|
|
|
|
if (a->event_number < b->event_number)
|
|
return (-1);
|
|
|
|
if (a->event_number > b->event_number)
|
|
return (1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* An assumption here is that if there is an EOT event, it will be at the end of the track.
|
|
*/
|
|
static void
|
|
remove_eot_if_before_pulses(smf_track_t *track, size_t pulses)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
event = smf_track_get_last_event(track);
|
|
|
|
if (event == NULL)
|
|
return;
|
|
|
|
if (!smf_event_is_eot(event))
|
|
return;
|
|
|
|
if (event->time_pulses > pulses)
|
|
return;
|
|
|
|
smf_event_remove_from_track(event);
|
|
}
|
|
|
|
/**
|
|
* Adds the event to the track and computes ->delta_pulses. Note that it is faster
|
|
* to append events to the end of the track than to insert them in the middle.
|
|
* Usually you want to use smf_track_add_event_seconds or smf_track_add_event_pulses
|
|
* instead of this one. Event needs to have ->time_pulses and ->time_seconds already set.
|
|
* If you try to add event after an EOT, EOT event will be automatically deleted.
|
|
*/
|
|
void
|
|
smf_track_add_event(smf_track_t *track, smf_event_t *event)
|
|
{
|
|
size_t i, last_pulses = 0;
|
|
|
|
assert(track->smf != NULL);
|
|
assert(event->track == NULL);
|
|
assert(event->delta_time_pulses == -1);
|
|
|
|
remove_eot_if_before_pulses(track, event->time_pulses);
|
|
|
|
event->track = track;
|
|
event->track_number = track->track_number;
|
|
|
|
if (track->number_of_events == 0) {
|
|
assert(track->next_event_number == 0);
|
|
track->next_event_number = 1;
|
|
}
|
|
|
|
if (track->number_of_events > 0)
|
|
last_pulses = smf_track_get_last_event(track)->time_pulses;
|
|
|
|
track->number_of_events++;
|
|
|
|
/* Are we just appending element at the end of the track? */
|
|
if (last_pulses <= event->time_pulses) {
|
|
event->delta_time_pulses = event->time_pulses - last_pulses;
|
|
assert(event->delta_time_pulses >= 0);
|
|
g_ptr_array_add(track->events_array, event);
|
|
event->event_number = track->number_of_events;
|
|
|
|
/* We need to insert in the middle of the track. XXX: This is slow. */
|
|
} else {
|
|
/* Append, then sort according to ->time_pulses. */
|
|
g_ptr_array_add(track->events_array, event);
|
|
g_ptr_array_sort(track->events_array, events_array_compare_function);
|
|
|
|
/* Renumber entries and fix their ->delta_pulses. */
|
|
for (i = 1; i <= track->number_of_events; i++) {
|
|
smf_event_t *tmp = smf_track_get_event_by_number(track, i);
|
|
tmp->event_number = i;
|
|
|
|
if (tmp->delta_time_pulses != -1)
|
|
continue;
|
|
|
|
if (i == 1) {
|
|
tmp->delta_time_pulses = tmp->time_pulses;
|
|
} else {
|
|
tmp->delta_time_pulses = tmp->time_pulses -
|
|
smf_track_get_event_by_number(track, i - 1)->time_pulses;
|
|
assert(tmp->delta_time_pulses >= 0);
|
|
}
|
|
}
|
|
|
|
/* Adjust ->delta_time_pulses of the next event. */
|
|
if (event->event_number < track->number_of_events) {
|
|
smf_event_t *next_event = smf_track_get_event_by_number(track, event->event_number + 1);
|
|
assert(next_event);
|
|
assert(next_event->time_pulses >= event->time_pulses);
|
|
next_event->delta_time_pulses -= event->delta_time_pulses;
|
|
assert(next_event->delta_time_pulses >= 0);
|
|
}
|
|
}
|
|
|
|
if (smf_event_is_tempo_change_or_time_signature(event)) {
|
|
if (smf_event_is_last(event))
|
|
maybe_add_to_tempo_map(event);
|
|
else
|
|
smf_create_tempo_map_and_compute_seconds(event->track->smf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add End Of Track metaevent. Using it is optional, libsmf will automatically
|
|
* add EOT to the tracks during smf_save, with delta_pulses 0. If you try to add EOT
|
|
* in the middle of the track, it will fail and nonzero value will be returned.
|
|
* If you try to add EOT after another EOT event, it will be added, but the existing
|
|
* EOT event will be removed.
|
|
*
|
|
* \return 0 if everything went ok, nonzero otherwise.
|
|
*/
|
|
int
|
|
smf_track_add_eot_delta_pulses(smf_track_t *track, uint32_t delta)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
event = smf_event_new_from_bytes(0xFF, 0x2F, 0x00);
|
|
if (event == NULL)
|
|
return (-1);
|
|
|
|
smf_track_add_event_delta_pulses(track, event, delta);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
smf_track_add_eot_pulses(smf_track_t *track, size_t pulses)
|
|
{
|
|
smf_event_t *event, *last_event;
|
|
|
|
last_event = smf_track_get_last_event(track);
|
|
if (last_event != NULL) {
|
|
if (last_event->time_pulses > pulses)
|
|
return (-2);
|
|
}
|
|
|
|
event = smf_event_new_from_bytes(0xFF, 0x2F, 0x00);
|
|
if (event == NULL)
|
|
return (-3);
|
|
|
|
smf_track_add_event_pulses(track, event, pulses);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Detaches event from its track.
|
|
*/
|
|
void
|
|
smf_event_remove_from_track(smf_event_t *event)
|
|
{
|
|
size_t i;
|
|
int was_last;
|
|
smf_event_t *tmp;
|
|
smf_track_t *track;
|
|
|
|
assert(event->track != NULL);
|
|
assert(event->track->smf != NULL);
|
|
|
|
track = event->track;
|
|
was_last = smf_event_is_last(event);
|
|
|
|
/* Adjust ->delta_time_pulses of the next event. */
|
|
if (event->event_number < track->number_of_events) {
|
|
tmp = smf_track_get_event_by_number(track, event->event_number + 1);
|
|
assert(tmp);
|
|
tmp->delta_time_pulses += event->delta_time_pulses;
|
|
}
|
|
|
|
track->number_of_events--;
|
|
g_ptr_array_remove(track->events_array, event);
|
|
|
|
if (track->number_of_events == 0)
|
|
track->next_event_number = 0;
|
|
|
|
/* Renumber the rest of the events, so they are consecutively numbered. */
|
|
for (i = event->event_number; i <= track->number_of_events; i++) {
|
|
tmp = smf_track_get_event_by_number(track, i);
|
|
tmp->event_number = i;
|
|
}
|
|
|
|
if (smf_event_is_tempo_change_or_time_signature(event)) {
|
|
/* XXX: This will cause problems, when there is more than one Tempo Change event at a given time. */
|
|
if (was_last)
|
|
remove_last_tempo_with_pulses(event->track->smf, event->time_pulses);
|
|
else
|
|
smf_create_tempo_map_and_compute_seconds(track->smf);
|
|
}
|
|
|
|
event->track = NULL;
|
|
event->event_number = 0;
|
|
event->delta_time_pulses = -1;
|
|
event->time_pulses = 0;
|
|
}
|
|
|
|
/**
|
|
* \return Nonzero if event is Tempo Change or Time Signature metaevent.
|
|
*/
|
|
int
|
|
smf_event_is_tempo_change_or_time_signature(const smf_event_t *event)
|
|
{
|
|
if (!smf_event_is_metadata(event))
|
|
return (0);
|
|
|
|
assert(event->midi_buffer_length >= 2);
|
|
|
|
if (event->midi_buffer[1] == 0x51 || event->midi_buffer[1] == 0x58)
|
|
return (1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Sets "Format" field of MThd header to the specified value. Note that you
|
|
* don't really need to use this, as libsmf will automatically change format
|
|
* from 0 to 1 when you add the second track.
|
|
* \param smf SMF.
|
|
* \param format 0 for one track per file, 1 for several tracks per file.
|
|
* \return 0 if everything went ok, nonzero otherwise.
|
|
*/
|
|
int
|
|
smf_set_format(smf_t *smf, int format)
|
|
{
|
|
assert(format == 0 || format == 1);
|
|
|
|
if (smf->number_of_tracks > 1 && format == 0) {
|
|
g_warning("There is more than one track, cannot set format to 0.");
|
|
return (-1);
|
|
}
|
|
|
|
smf->format = format;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Sets the PPQN ("Division") field of MThd header. This is mandatory, you
|
|
* should call it right after smf_new. Note that changing PPQN will change time_seconds
|
|
* of all the events.
|
|
* \param smf SMF.
|
|
* \param ppqn New PPQN.
|
|
* \return 0 if everything went ok, nonzero otherwise.
|
|
*/
|
|
int
|
|
smf_set_ppqn(smf_t *smf, uint16_t ppqn)
|
|
{
|
|
smf->ppqn = ppqn;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Returns next event from the track given and advances next event counter.
|
|
* Do not depend on End Of Track event being the last event on the track - it
|
|
* is possible that the track will not end with EOT if you haven't added it
|
|
* yet. EOTs are added automatically during smf_save().
|
|
*
|
|
* \return Event or NULL, if there are no more events left in this track.
|
|
*/
|
|
smf_event_t *
|
|
smf_track_get_next_event(smf_track_t *track)
|
|
{
|
|
smf_event_t *event, *next_event;
|
|
|
|
/* Track is empty? */
|
|
if (track->number_of_events == 0)
|
|
return (NULL);
|
|
|
|
/* End of track? */
|
|
if (track->next_event_number == 0)
|
|
return (NULL);
|
|
|
|
assert(track->next_event_number >= 1);
|
|
|
|
event = smf_track_get_event_by_number(track, track->next_event_number);
|
|
|
|
assert(event != NULL);
|
|
|
|
/* Is this the last event in the track? */
|
|
if (track->next_event_number < track->number_of_events) {
|
|
next_event = smf_track_get_event_by_number(track, track->next_event_number + 1);
|
|
assert(next_event);
|
|
|
|
track->time_of_next_event = next_event->time_pulses;
|
|
track->next_event_number++;
|
|
} else {
|
|
track->next_event_number = 0;
|
|
}
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Returns next event from the track given. Does not change next event counter,
|
|
* so repeatedly calling this routine will return the same event.
|
|
* \return Event or NULL, if there are no more events left in this track.
|
|
*/
|
|
static smf_event_t *
|
|
smf_peek_next_event_from_track(smf_track_t *track)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
/* End of track? */
|
|
if (track->next_event_number == 0)
|
|
return (NULL);
|
|
|
|
assert(track->next_event_number >= 1);
|
|
assert(track->events_array->len != 0);
|
|
|
|
event = smf_track_get_event_by_number(track, track->next_event_number);
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* \return Track with a given number or NULL, if there is no such track.
|
|
* Tracks are numbered consecutively starting from one.
|
|
*/
|
|
smf_track_t *
|
|
smf_get_track_by_number(const smf_t *smf, int track_number)
|
|
{
|
|
smf_track_t *track;
|
|
|
|
assert(track_number >= 1);
|
|
|
|
if (track_number > smf->number_of_tracks)
|
|
return (NULL);
|
|
|
|
track = (smf_track_t *)g_ptr_array_index(smf->tracks_array, track_number - 1);
|
|
|
|
assert(track);
|
|
|
|
return (track);
|
|
}
|
|
|
|
/**
|
|
* \return Event with a given number or NULL, if there is no such event.
|
|
* Events are numbered consecutively starting from one.
|
|
*/
|
|
smf_event_t *
|
|
smf_track_get_event_by_number(const smf_track_t *track, size_t event_number)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
assert(event_number >= 1);
|
|
|
|
if (event_number > track->number_of_events)
|
|
return (NULL);
|
|
|
|
event = (smf_event_t*)g_ptr_array_index(track->events_array, event_number - 1);
|
|
|
|
assert(event);
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* \return Last event on the track or NULL, if track is empty.
|
|
*/
|
|
smf_event_t *
|
|
smf_track_get_last_event(const smf_track_t *track)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
if (track->number_of_events == 0)
|
|
return (NULL);
|
|
|
|
event = smf_track_get_event_by_number(track, track->number_of_events);
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Searches for track that contains next event, in time order. In other words,
|
|
* returns the track that contains event that should be played next.
|
|
* \return Track with next event or NULL, if there are no events left.
|
|
*/
|
|
smf_track_t *
|
|
smf_find_track_with_next_event(smf_t *smf)
|
|
{
|
|
int i;
|
|
size_t min_time = 0;
|
|
smf_track_t *track = NULL, *min_time_track = NULL;
|
|
|
|
/* Find track with event that should be played next. */
|
|
for (i = 1; i <= smf->number_of_tracks; i++) {
|
|
track = smf_get_track_by_number(smf, i);
|
|
|
|
assert(track);
|
|
|
|
/* No more events in this track? */
|
|
if (track->next_event_number == 0)
|
|
continue;
|
|
|
|
if (track->time_of_next_event < min_time || min_time_track == NULL) {
|
|
min_time = track->time_of_next_event;
|
|
min_time_track = track;
|
|
}
|
|
}
|
|
|
|
return (min_time_track);
|
|
}
|
|
|
|
/**
|
|
* \return Next event, in time order, or NULL, if there are none left.
|
|
*/
|
|
smf_event_t *
|
|
smf_get_next_event(smf_t *smf)
|
|
{
|
|
smf_event_t *event;
|
|
smf_track_t *track = smf_find_track_with_next_event(smf);
|
|
|
|
if (track == NULL) {
|
|
#if 0
|
|
g_debug("End of the song.");
|
|
#endif
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
event = smf_track_get_next_event(track);
|
|
|
|
assert(event != NULL);
|
|
|
|
event->track->smf->last_seek_position = -1.0;
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Advance the "next event counter". This is functionally the same as calling
|
|
* smf_get_next_event and ignoring the return value.
|
|
*/
|
|
void
|
|
smf_skip_next_event(smf_t *smf)
|
|
{
|
|
smf_event_t *ignored = smf_get_next_event(smf);
|
|
(void) ignored;
|
|
}
|
|
|
|
/**
|
|
* \return Next event, in time order, or NULL, if there are none left. Does
|
|
* not advance position in song.
|
|
*/
|
|
smf_event_t *
|
|
smf_peek_next_event(smf_t *smf)
|
|
{
|
|
smf_event_t *event;
|
|
smf_track_t *track = smf_find_track_with_next_event(smf);
|
|
|
|
if (track == NULL) {
|
|
#if 0
|
|
g_debug("End of the song.");
|
|
#endif
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
event = smf_peek_next_event_from_track(track);
|
|
|
|
assert(event != NULL);
|
|
|
|
return (event);
|
|
}
|
|
|
|
/**
|
|
* Rewinds the SMF. What that means is, after calling this routine, smf_get_next_event
|
|
* will return first event in the song.
|
|
*/
|
|
void
|
|
smf_rewind(smf_t *smf)
|
|
{
|
|
int i;
|
|
smf_track_t *track = NULL;
|
|
smf_event_t *event;
|
|
|
|
assert(smf);
|
|
|
|
smf->last_seek_position = 0.0;
|
|
|
|
for (i = 1; i <= smf->number_of_tracks; i++) {
|
|
track = smf_get_track_by_number(smf, i);
|
|
|
|
assert(track != NULL);
|
|
|
|
if (track->number_of_events > 0) {
|
|
track->next_event_number = 1;
|
|
event = smf_peek_next_event_from_track(track);
|
|
assert(event);
|
|
track->time_of_next_event = event->time_pulses;
|
|
} else {
|
|
track->next_event_number = 0;
|
|
track->time_of_next_event = 0;
|
|
#if 0
|
|
g_warning("Warning: empty track.");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Seeks the SMF to the given event. After calling this routine, smf_get_next_event
|
|
* will return the event that was the second argument of this call.
|
|
*/
|
|
int
|
|
smf_seek_to_event(smf_t *smf, const smf_event_t *target)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
smf_rewind(smf);
|
|
|
|
#if 0
|
|
g_debug("Seeking to event %d, track %d.", target->event_number, target->track->track_number);
|
|
#endif
|
|
|
|
for (;;) {
|
|
event = smf_peek_next_event(smf);
|
|
|
|
/* There can't be NULL here, unless "target" is not in this smf. */
|
|
assert(event);
|
|
|
|
if (event != target)
|
|
smf_skip_next_event(smf);
|
|
else
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Seeks the SMF to the given position. For example, after seeking to 10 pulses,
|
|
* smf_get_next_event will return first event that happens after the first ten pulses.
|
|
* \return 0 if everything went ok, nonzero otherwise.
|
|
*/
|
|
int
|
|
smf_seek_to_pulses(smf_t *smf, size_t pulses)
|
|
{
|
|
smf_event_t *event;
|
|
|
|
smf_rewind(smf);
|
|
|
|
#if 0
|
|
g_debug("Seeking to %d pulses.", pulses);
|
|
#endif
|
|
|
|
for (;;) {
|
|
event = smf_peek_next_event(smf);
|
|
|
|
if (event == NULL) {
|
|
g_warning("Trying to seek past the end of song.");
|
|
return (-1);
|
|
}
|
|
|
|
if (event->time_pulses < pulses)
|
|
smf_skip_next_event(smf);
|
|
else
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* \return Length of SMF, in pulses.
|
|
*/
|
|
size_t
|
|
smf_get_length_pulses(const smf_t *smf)
|
|
{
|
|
int i;
|
|
size_t pulses = 0;
|
|
|
|
for (i = 1; i <= smf->number_of_tracks; i++) {
|
|
smf_track_t *track;
|
|
smf_event_t *event;
|
|
|
|
track = smf_get_track_by_number(smf, i);
|
|
assert(track);
|
|
|
|
event = smf_track_get_last_event(track);
|
|
/* Empty track? */
|
|
if (event == NULL)
|
|
continue;
|
|
|
|
if (event->time_pulses > pulses)
|
|
pulses = event->time_pulses;
|
|
}
|
|
|
|
return (pulses);
|
|
}
|
|
|
|
/**
|
|
* \return Nonzero, if there are no events in the SMF after this one.
|
|
* Note that may be more than one "last event", if they occur at the same time.
|
|
*/
|
|
int
|
|
smf_event_is_last(const smf_event_t *event)
|
|
{
|
|
if (smf_get_length_pulses(event->track->smf) <= event->time_pulses)
|
|
return (1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* \return Version of libsmf.
|
|
*/
|
|
const char *
|
|
smf_get_version(void)
|
|
{
|
|
return (SMF_VERSION);
|
|
}
|
|
|