# HG changeset patch # User John Tsiombikas # Date 1327569911 -7200 # Node ID 4264abea8b06b9f5f3dec116a7438360acd4e062 smf-lite initial commit diff -r 000000000000 -r 4264abea8b06 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,6 @@ +\.d$ +\.o$ +\.swp$ +smflite.a$ +^libsmflite.so +^smflite.dylib$ diff -r 000000000000 -r 4264abea8b06 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,61 @@ +PREFIX = /usr/local + +src = $(wildcard src/*.c) +obj = $(src:.c=.o) +dep = $(obj:.o=.d) + +name = smflite +lib_a = lib$(name).a + +ifeq ($(shell uname -s), Darwin) + lib_so = $(name).dylib + shared = -dynamiclib +else + abi = 0 + rev = 0 + devlink = lib$(name).so + soname = $(devlink).$(abi) + lib_so = $(soname).$(rev) + shared = -shared -Wl,-soname=$(soname) + pic = -fPIC +endif + +CFLAGS = -pedantic -Wall -g $(pic) + +.PHONY: all +all: $(lib_a) $(lib_so) + +$(lib_so): $(obj) + $(CC) $(shared) -o $@ $(obj) $(LDFLAGS) + +$(lib_a): $(obj) + $(AR) rcs $@ $(obj) + +-include $(dep) + +%.d: %.c + @$(CPP) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@ + +.PHONY: clean +clean: + rm -f $(obj) $(lib_so) $(lib_a) + +.PHONY: install +install: $(lib_so) $(lib_a) + mkdir -p $(INSTDIR)$(PREFIX)/include $(INSTDIR)$(PREFIX)/lib + cp src/smf.h $(INSTDIR)$(PREFIX)/include/smf.h + cp $(lib_a) $(INSTDIR)$(PREFIX)/lib/$(lib_a) + cp $(lib_so) $(INSTDIR)$(PREFIX)/lib/$(lib_so) + [ -n "$(soname)" ] && \ + cd $(INSTDIR)$(PREFIX)/lib && \ + ln -s $(lib_so) $(soname) && \ + ln -s $(soname) $(devlink) || true + +.PHONY: uninstall +uninstall: + rm -f $(INSTDIR)$(PREFIX)/include/smf.h + rm -f $(INSTDIR)$(PREFIX)/lib/$(lib_a) + rm -f $(INSTDIR)$(PREFIX)/lib/$(lib_so) + [ -n "$(soname)" ] && \ + rm -f $(INSTDIR)$(PREFIX)/lib/$(soname) && \ + rm -f $(INSTDIR)$(PREFIX)/lib/$(devlink) || true diff -r 000000000000 -r 4264abea8b06 src/config.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.h Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,6 @@ +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#define PACKAGE_VERSION "1.3-lite" + +#endif /* CONFIG_H_ */ diff -r 000000000000 -r 4264abea8b06 src/fake_glib.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fake_glib.c Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include "fake_glib.h" + +FakeGPtrArray *fg_ptr_array_new(void) +{ + FakeGPtrArray *arr; + + if(!(arr = malloc(sizeof *arr))) { + return 0; + } + arr->pdata = 0; + arr->len = 0; + return arr; +} + +void **fg_ptr_array_free(FakeGPtrArray *arr, int free_seg) +{ + void **res; + + if(!arr) { + return 0; + } + + if(free_seg) { + free(arr->pdata); + res = 0; + } else { + res = arr->pdata; + } + free(arr); + return res; +} + +void fg_ptr_array_add(FakeGPtrArray *arr, void *data) +{ + int idx = arr->len++; + + arr->pdata = realloc(arr->pdata, arr->len * sizeof *arr->pdata); + assert(arr->pdata); + + arr->pdata[idx] = data; +} + +int fg_ptr_array_remove(FakeGPtrArray *arr, void *data) +{ + int i; + + for(i=0; ilen; i++) { + if(arr->pdata[i] == data) { + fg_ptr_array_remove_index(arr, i); + return 1; + } + } + + return 0; +} + +void *fg_ptr_array_remove_index(FakeGPtrArray *arr, unsigned int idx) +{ + void *data = arr->pdata[idx]; + int rest = --arr->len - idx; + if(rest > 0) { + memmove(arr->pdata + idx, arr->pdata + idx + 1, rest * sizeof *arr->pdata); + } + return data; +} + +void fg_ptr_array_sort(FakeGPtrArray *arr, FakeGCompareFunc cmp) +{ + qsort(arr->pdata, arr->len, sizeof *arr->pdata, cmp); +} + +/* -- logging -- */ + +void fg_warning(const char *fmt, ...) +{ + va_list ap; + + printf("warning: "); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +void fg_critical(const char *fmt, ...) +{ + va_list ap; + + printf("critical: "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if(getenv("G_DEBUG")) { + abort(); + } +} + +void fg_error(const char *fmt, ...) +{ + va_list ap; + + printf("error: "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + abort(); +} + +void fg_debug(const char *fmt, ...) +{ + va_list ap; + + printf("debug: "); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} diff -r 000000000000 -r 4264abea8b06 src/fake_glib.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fake_glib.h Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,40 @@ +#ifndef FAKE_GLIB_H_ +#define FAKE_GLIB_H_ + +typedef struct FakeGPtrArray { + void **pdata; + unsigned int len; +} FakeGPtrArray; + +typedef int (*FakeGCompareFunc)(const void*, const void*); + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +typedef int gint; +typedef void* gpointer; +typedef const void* gconstpointer; + +#define fg_ptr_array_index(arr, idx) ((arr)->pdata[idx]) + +FakeGPtrArray *fg_ptr_array_new(void); +void **fg_ptr_array_free(FakeGPtrArray *arr, int free_seg); + +void fg_ptr_array_add(FakeGPtrArray *arr, void *data); +int fg_ptr_array_remove(FakeGPtrArray *arr, void *data); +void *fg_ptr_array_remove_index(FakeGPtrArray *arr, unsigned int idx); + +void fg_ptr_array_sort(FakeGPtrArray *arr, FakeGCompareFunc cmp); + +/* -- logging -- */ +#define fg_message printf +void fg_warning(const char *fmt, ...); +void fg_critical(const char *fmt, ...); +void fg_error(const char *fmt, ...); +void fg_debug(const char *fmt, ...); + +#endif /* FAKE_GLIB_H_ */ diff -r 000000000000 -r 4264abea8b06 src/smf.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf.c Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,1116 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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 +#include +#include +#include +#include +#ifdef __MINGW32__ +#include +#else /* ! __MINGW32__ */ +#include +#endif /* ! __MINGW32__ */ +#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 = malloc(sizeof(smf_t)); + if (smf == NULL) { + fg_critical("Cannot allocate smf_t structure: %s", strerror(errno)); + return (NULL); + } + + memset(smf, 0, sizeof(smf_t)); + + smf->tracks_array = fg_ptr_array_new(); + assert(smf->tracks_array); + + smf->tempo_array = fg_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(fg_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); + fg_ptr_array_free(smf->tracks_array, TRUE); + fg_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 = malloc(sizeof(smf_track_t)); + if (track == NULL) { + fg_critical("Cannot allocate smf_track_t structure: %s", strerror(errno)); + return (NULL); + } + + memset(track, 0, sizeof(smf_track_t)); + track->next_event_number = -1; + + track->events_array = fg_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, from last to first. */ + while (track->events_array->len > 0) + smf_event_delete(fg_ptr_array_index(track->events_array, track->events_array->len - 1)); + + if (track->smf) + smf_track_remove_from_smf(track); + + assert(track->events_array->len == 0); + assert(track->number_of_events == 0); + fg_ptr_array_free(track->events_array, TRUE); + + 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) +{ + int cantfail; + + assert(track->smf == NULL); + + track->smf = smf; + fg_ptr_array_add(smf->tracks_array, track); + + smf->number_of_tracks++; + track->track_number = smf->number_of_tracks; + + if (smf->number_of_tracks > 1) { + cantfail = smf_set_format(smf, 1); + assert(!cantfail); + } +} + +/** + * Detaches track from the smf. + */ +void +smf_track_remove_from_smf(smf_track_t *track) +{ + int i, j; + smf_track_t *tmp; + smf_event_t *ev; + + assert(track->smf != NULL); + + track->smf->number_of_tracks--; + + assert(track->smf->tracks_array); + fg_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 = malloc(sizeof(smf_event_t)); + if (event == NULL) { + fg_critical("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->time_seconds = -1.0; + 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(void *midi_data, int len) +{ + smf_event_t *event; + + event = smf_event_new(); + if (event == NULL) + return (NULL); + + event->midi_buffer_length = len; + event->midi_buffer = malloc(event->midi_buffer_length); + if (event->midi_buffer == NULL) { + fg_critical("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) +{ + int len; + + smf_event_t *event; + + event = smf_event_new(); + if (event == NULL) + return (NULL); + + if (first_byte < 0) { + fg_critical("First byte of MIDI message cannot be < 0"); + smf_event_delete(event); + + return (NULL); + } + + if (first_byte > 255) { + fg_critical("smf_event_new_from_bytes: first byte is %d, which is larger than 255.", first_byte); + return (NULL); + } + + if (!is_status_byte(first_byte)) { + fg_critical("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) { + fg_critical("smf_event_new_from_bytes: second byte is %d, which is larger than 255.", second_byte); + return (NULL); + } + + if (is_status_byte(second_byte)) { + fg_critical("smf_event_new_from_bytes: second byte cannot be a status byte."); + return (NULL); + } + } + + if (len > 2) { + if (third_byte > 255) { + fg_critical("smf_event_new_from_bytes: third byte is %d, which is larger than 255.", third_byte); + return (NULL); + } + + if (is_status_byte(third_byte)) { + fg_critical("smf_event_new_from_bytes: third byte cannot be a status byte."); + return (NULL); + } + } + + event->midi_buffer_length = len; + event->midi_buffer = malloc(event->midi_buffer_length); + if (event->midi_buffer == NULL) { + fg_critical("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) +{ + smf_event_t *a, *b; + + /* "The comparison function for fg_ptr_array_sort() doesn't take the pointers + from the array as arguments, it takes pointers to the pointers in the array." */ + a = (smf_event_t *)*(gpointer *)aa; + b = (smf_event_t *)*(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, int 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) +{ + int i, last_pulses = 0; + + assert(track->smf != NULL); + assert(event->track == NULL); + assert(event->delta_time_pulses == -1); + assert(event->time_pulses >= 0); + assert(event->time_seconds >= 0.0); + + 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 == -1); + 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); + fg_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. */ + fg_ptr_array_add(track->events_array, event); + fg_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, int 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, int 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); +} + +int +smf_track_add_eot_seconds(smf_track_t *track, double seconds) +{ + smf_event_t *event, *last_event; + + last_event = smf_track_get_last_event(track); + if (last_event != NULL) { + if (last_event->time_seconds > seconds) + return (-2); + } + + event = smf_event_new_from_bytes(0xFF, 0x2F, 0x00); + if (event == NULL) + return (-1); + + smf_track_add_event_seconds(track, event, seconds); + + return (0); +} + +/** + * Detaches event from its track. + */ +void +smf_event_remove_from_track(smf_event_t *event) +{ + int i, 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--; + fg_ptr_array_remove(track->events_array, event); + + if (track->number_of_events == 0) + track->next_event_number = -1; + + /* 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 = -1; + event->delta_time_pulses = -1; + event->time_pulses = -1; + event->time_seconds = -1.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. + */ +int +smf_set_format(smf_t *smf, int format) +{ + assert(format == 0 || format == 1); + + if (smf->number_of_tracks > 1 && format == 0) { + fg_critical("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. + */ +int +smf_set_ppqn(smf_t *smf, int ppqn) +{ + assert(ppqn > 0); + + 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; + + /* End of track? */ + if (track->next_event_number == -1) + return (NULL); + + assert(track->next_event_number >= 1); + assert(track->number_of_events > 0); + + 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 = -1; + } + + 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 == -1) + 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 *)fg_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, int event_number) +{ + smf_event_t *event; + + assert(event_number >= 1); + + if (event_number > track->number_of_events) + return (NULL); + + event = fg_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, 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 == -1) + 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) +{ + void *notused; + + notused = smf_get_next_event(smf); +} + +/** + * \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 = -1; + 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; + } + + smf->last_seek_position = event->time_seconds; + + return (0); +} + +/** + * Seeks the SMF to the given position. For example, after seeking to 1.0 seconds, + * smf_get_next_event will return first event that happens after the first second of song. + */ +int +smf_seek_to_seconds(smf_t *smf, double seconds) +{ + smf_event_t *event; + + assert(seconds >= 0.0); + + if (seconds == smf->last_seek_position) { +#if 0 + g_debug("Avoiding seek to %f seconds.", seconds); +#endif + return (0); + } + + smf_rewind(smf); + +#if 0 + g_debug("Seeking to %f seconds.", seconds); +#endif + + for (;;) { + event = smf_peek_next_event(smf); + + if (event == NULL) { + fg_critical("Trying to seek past the end of song."); + return (-1); + } + + if (event->time_seconds < seconds) + smf_skip_next_event(smf); + else + break; + } + + smf->last_seek_position = seconds; + + 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. + */ +int +smf_seek_to_pulses(smf_t *smf, int pulses) +{ + smf_event_t *event; + + assert(pulses >= 0); + + smf_rewind(smf); + +#if 0 + g_debug("Seeking to %d pulses.", pulses); +#endif + + for (;;) { + event = smf_peek_next_event(smf); + + if (event == NULL) { + fg_critical("Trying to seek past the end of song."); + return (-1); + } + + if (event->time_pulses < pulses) + smf_skip_next_event(smf); + else + break; + } + + smf->last_seek_position = event->time_seconds; + + return (0); +} + +/** + * \return Length of SMF, in pulses. + */ +int +smf_get_length_pulses(const smf_t *smf) +{ + int pulses = 0, i; + + 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 Length of SMF, in seconds. + */ +double +smf_get_length_seconds(const smf_t *smf) +{ + int i; + double seconds = 0.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_seconds > seconds) + seconds = event->time_seconds; + } + + return (seconds); +} + +/** + * \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); +} + diff -r 000000000000 -r 4264abea8b06 src/smf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf.h Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,417 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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 + * + * Public interface declaration for libsmf, Standard MIDI File format library. + */ + +/** + * + * \mainpage libsmf - general usage instructions + * + * An smf_t structure represents a "song". Every valid smf contains one or more tracks. + * Tracks contain zero or more events. Libsmf doesn't care about actual MIDI data, as long + * as it is valid from the MIDI specification point of view - it may be realtime message, + * SysEx, whatever. + * + * The only field in smf_t, smf_track_t, smf_event_t and smf_tempo_t structures your + * code may modify is event->midi_buffer and event->midi_buffer_length. Do not modify + * other fields, _ever_. You may read them, though. Do not declare static instances + * of these types, i.e. never do something like this: "smf_t smf;". Always use + * "smf_t *smf = smf_new();". The same applies to smf_track_t and smf_event_t. + * + * Say you want to load a Standard MIDI File (.mid) file and play it back somehow. This is (roughly) + * how you do this: + * + * \code + * smf_t *smf; + * smf_event_t *event; + * + * smf = smf_load(file_name); + * if (smf == NULL) { + * Whoops, something went wrong. + * return; + * } + * + * while ((event = smf_get_next_event(smf)) != NULL) { + * if (smf_event_is_metadata(event)) + * continue; + * + * wait until event->time_seconds. + * feed_to_midi_output(event->midi_buffer, event->midi_buffer_length); + * } + * + * smf_delete(smf); + * + * \endcode + * + * Saving works like this: + * + * \code + * + * smf_t *smf; + * smf_track_t *track; + * smf_event_t *event; + * + * smf = smf_new(); + * if (smf == NULL) { + * Whoops. + * return; + * } + * + * for (int i = 1; i <= number of tracks; i++) { + * track = smf_track_new(); + * if (track == NULL) { + * Whoops. + * return; + * } + * + * smf_add_track(smf, track); + * + * for (int j = 1; j <= number of events you want to put into this track; j++) { + * event = smf_event_new_from_pointer(your MIDI message, message length); + * if (event == NULL) { + * Whoops. + * return; + * } + * + * smf_track_add_event_seconds(track, event, seconds since start of the song); + * } + * } + * + * ret = smf_save(smf, file_name); + * if (ret) { + * Whoops, saving failed for some reason. + * return; + * } + * + * smf_delete(smf); + * + * \endcode + * + * There are two basic ways of getting MIDI data out of smf - sequential or by track/event number. You may + * mix them if you need to. First one is used in the example above - seek to the point from which you want + * the playback to start (using smf_seek_to_seconds(), smf_seek_to_pulses() or smf_seek_to_event()) and then + * do smf_get_next_event() in loop, until it returns NULL. Calling smf_load() causes the smf to be rewound + * to the start of the song. + * + * Getting events by number works like this: + * + * \code + * + * smf_track_t *track = smf_get_track_by_number(smf, track_number); + * smf_event_t *event = smf_track_get_event_by_number(track, event_number); + * + * \endcode + * + * To create new event, use smf_event_new(), smf_event_new_from_pointer() or smf_event_new_from_bytes(). + * First one creates an empty event - you need to manually allocate (using malloc(3)) buffer for + * MIDI data, write MIDI data into it, put the address of that buffer into event->midi_buffer, + * and the length of MIDI data into event->midi_buffer_length. Note that deleting the event + * (using smf_event_delete()) will free the buffer. + * + * Second form does most of this for you: it takes an address of the buffer containing MIDI data, + * allocates storage and copies MIDI data into it. + * + * Third form is useful for manually creating short events, up to three bytes in length, for + * example Note On or Note Off events. It simply takes three bytes and creates MIDI event containing + * them. If you need to create MIDI message that takes only two bytes, pass -1 as the third byte. + * For one byte message (System Realtime), pass -1 as second and third byte. + * + * To free an event, use smf_event_delete(). + * + * To add event to the track, use smf_track_add_event_delta_pulses(), smf_track_add_event_pulses(), + * or smf_track_add_event_seconds(). The difference between them is in the way you specify the time of + * the event - with the first one, you specify it as an interval, in pulses, from the previous event + * in this track; with the second one, you specify it as pulses from the start of the song, and with the + * last one, you specify it as seconds from the start of the song. Obviously, the first version can + * only append events at the end of the track. + * + * To remove an event from the track it's attached to, use smf_event_remove_from_track(). You may + * want to free the event (using smf_event_delete()) afterwards. + * + * To create new track, use smf_track_new(). To add track to the smf, use smf_add_track(). + * To remove track from its smf, use smf_track_remove_from_smf(). To free the track structure, + * use smf_track_delete(). + * + * Note that libsmf keeps things consistent. If you free (using smf_track_delete()) a track that + * is attached to an smf and contains events, libsmf will detach the events, free them, detach + * the track, free it etc. + * + * Tracks and events are numbered consecutively, starting from one. If you remove a track or event, + * the rest of tracks/events will get renumbered. To get the number of a given event in its track, use event->event_number. + * To get the number of track in its smf, use track->track_number. To get the number of events in the track, + * use track->number_of_events. To get the number of tracks in the smf, use smf->number_of_tracks. + * + * In SMF File Format, each track has to end with End Of Track metaevent. If you load SMF file using smf_load(), + * that will be the case. If you want to create or edit an SMF, you don't need to worry about EOT events; + * libsmf automatically takes care of them for you. If you try to save an SMF with tracks that do not end + * with EOTs, smf_save() will append them. If you try to add event that happens after EOT metaevent, libsmf + * will remove the EOT. If you want to add EOT manually, you can, of course, using smf_track_add_eot_seconds() + * or smf_track_add_eot_pulses(). + * + * Each event carries three time values - event->time_seconds, which is seconds since the start of the song, + * event->time_pulses, which is PPQN clocks since the start of the song, and event->delta_pulses, which is PPQN clocks + * since the previous event in that track. These values are invalid if the event is not attached to the track. + * If event is attached, all three values are valid. Time of the event is specified when adding the event + * (using smf_track_add_event_seconds(), smf_track_add_event_pulses() or smf_track_add_event_delta_pulses()); the remaining + * two values are computed from that. + * + * Tempo related stuff happens automatically - when you add a metaevent that + * is Tempo Change or Time Signature, libsmf adds that event to the tempo map. If you remove + * Tempo Change event that is in the middle of the song, the rest of the events will have their + * event->time_seconds recomputed from event->time_pulses before smf_event_remove_from_track() function returns. + * Adding Tempo Change in the middle of the song works in a similar way. + * + * MIDI data (event->midi_buffer) is always kept in normalized form - it always begins with status byte + * (no running status), there are no System Realtime events embedded in them etc. Events like SysExes + * are in "on the wire" form, without embedded length that is used in SMF file format. Obviously + * libsmf "normalizes" MIDI data during loading and "denormalizes" (adding length to SysExes, escaping + * System Common and System Realtime messages etc) during writing. + * + * Note that you always have to first add the track to smf, and then add events to the track. + * Doing it the other way around will trip asserts. Also, try to add events at the end of the track and remove + * them from the end of the track, that's much more efficient. + * + * All the libsmf functions have prefix "smf_". First argument for routines whose names start with + * "smf_event_" is "smf_event_t *", for routines whose names start with "smf_track_" - "smf_track_t *", + * and for plain "smf_" - "smf_t *". The only exception are smf_whatever_new routines. + * Library does not use any global variables and is thread-safe, + * as long as you don't try to work on the same SMF (smf_t and its descendant tracks and events) from several + * threads at once without protecting it with mutex. Library depends on glib and nothing else. License is + * BSD, two clause, which basically means you can use it freely in your software, both Open Source (including + * GPL) and closed source. + * + */ + +#ifndef SMF_H +#define SMF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + +struct FakeGPtrArray; + +/** Represents a "song", that is, collection of one or more tracks. */ +struct smf_struct { + int format; + + /** These fields are extracted from "division" field of MThd header. Valid is _either_ ppqn or frames_per_second/resolution. */ + int ppqn; + int frames_per_second; + int resolution; + int number_of_tracks; + + /** These are private fields using only by loading and saving routines. */ + FILE *stream; + void *file_buffer; + int file_buffer_length; + int next_chunk_offset; + int expected_number_of_tracks; + + /** Private, used by smf.c. */ + struct FakeGPtrArray *tracks_array; + double last_seek_position; + + /** Private, used by smf_tempo.c. */ + /** Array of pointers to smf_tempo_struct. */ + struct FakeGPtrArray *tempo_array; +}; + +typedef struct smf_struct smf_t; + +/** Describes a single tempo or time signature change. */ +struct smf_tempo_struct { + int time_pulses; + double time_seconds; + int microseconds_per_quarter_note; + int numerator; + int denominator; + int clocks_per_click; + int notes_per_note; +}; + +typedef struct smf_tempo_struct smf_tempo_t; + +/** Represents a single track. */ +struct smf_track_struct { + smf_t *smf; + + int track_number; + int number_of_events; + + /** These are private fields using only by loading and saving routines. */ + void *file_buffer; + int file_buffer_length; + int last_status; /* Used for "running status". */ + + /** Private, used by smf.c. */ + /** Offset into buffer, used in parse_next_event(). */ + int next_event_offset; + int next_event_number; + + /** Absolute time of next event on events_queue. */ + int time_of_next_event; + struct FakeGPtrArray *events_array; + + /** API consumer is free to use this for whatever purpose. NULL in freshly allocated track. + Note that tracks might be deallocated not only explicitly, by calling smf_track_delete(), + but also implicitly, e.g. when calling smf_delete() with tracks still added to + the smf; there is no mechanism for libsmf to notify you about removal of the track. */ + void *user_pointer; +}; + +typedef struct smf_track_struct smf_track_t; + +/** Represents a single MIDI event or metaevent. */ +struct smf_event_struct { + /** Pointer to the track, or NULL if event is not attached. */ + smf_track_t *track; + + /** Number of this event in the track. Events are numbered consecutively, starting from one. */ + int event_number; + + /** Note that the time fields are invalid, if event is not attached to a track. */ + /** Time, in pulses, since the previous event on this track. */ + int delta_time_pulses; + + /** Time, in pulses, since the start of the song. */ + int time_pulses; + + /** Time, in seconds, since the start of the song. */ + double time_seconds; + + /** Tracks are numbered consecutively, starting from 1. */ + int track_number; + + /** Pointer to the buffer containing MIDI message. This is freed by smf_event_delete. */ + unsigned char *midi_buffer; + + /** Length of the MIDI message in the buffer, in bytes. */ + int midi_buffer_length; + + /** API consumer is free to use this for whatever purpose. NULL in freshly allocated event. + Note that events might be deallocated not only explicitly, by calling smf_event_delete(), + but also implicitly, e.g. when calling smf_track_delete() with events still added to + the track; there is no mechanism for libsmf to notify you about removal of the event. */ + void *user_pointer; +}; + +typedef struct smf_event_struct smf_event_t; + +/* Routines for manipulating smf_t. */ +smf_t *smf_new(void) WARN_UNUSED_RESULT; +void smf_delete(smf_t *smf); + +int smf_set_format(smf_t *smf, int format) WARN_UNUSED_RESULT; +int smf_set_ppqn(smf_t *smf, int format) WARN_UNUSED_RESULT; + +char *smf_decode(const smf_t *smf) WARN_UNUSED_RESULT; + +smf_track_t *smf_get_track_by_number(const smf_t *smf, int track_number) WARN_UNUSED_RESULT; + +smf_event_t *smf_peek_next_event(smf_t *smf) WARN_UNUSED_RESULT; +smf_event_t *smf_get_next_event(smf_t *smf) WARN_UNUSED_RESULT; +void smf_skip_next_event(smf_t *smf); + +void smf_rewind(smf_t *smf); +int smf_seek_to_seconds(smf_t *smf, double seconds) WARN_UNUSED_RESULT; +int smf_seek_to_pulses(smf_t *smf, int pulses) WARN_UNUSED_RESULT; +int smf_seek_to_event(smf_t *smf, const smf_event_t *event) WARN_UNUSED_RESULT; + +int smf_get_length_pulses(const smf_t *smf) WARN_UNUSED_RESULT; +double smf_get_length_seconds(const smf_t *smf) WARN_UNUSED_RESULT; +int smf_event_is_last(const smf_event_t *event) WARN_UNUSED_RESULT; + +void smf_add_track(smf_t *smf, smf_track_t *track); +void smf_track_remove_from_smf(smf_track_t *track); + +/* Routines for manipulating smf_track_t. */ +smf_track_t *smf_track_new(void) WARN_UNUSED_RESULT; +void smf_track_delete(smf_track_t *track); + +smf_event_t *smf_track_get_next_event(smf_track_t *track) WARN_UNUSED_RESULT; +smf_event_t *smf_track_get_event_by_number(const smf_track_t *track, int event_number) WARN_UNUSED_RESULT; +smf_event_t *smf_track_get_last_event(const smf_track_t *track) WARN_UNUSED_RESULT; + +void smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, int pulses); +void smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, int pulses); +void smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds); +int smf_track_add_eot_delta_pulses(smf_track_t *track, int delta) WARN_UNUSED_RESULT; +int smf_track_add_eot_pulses(smf_track_t *track, int pulses) WARN_UNUSED_RESULT; +int smf_track_add_eot_seconds(smf_track_t *track, double seconds) WARN_UNUSED_RESULT; +void smf_event_remove_from_track(smf_event_t *event); + +/* Routines for manipulating smf_event_t. */ +smf_event_t *smf_event_new(void) WARN_UNUSED_RESULT; +smf_event_t *smf_event_new_from_pointer(void *midi_data, int len) WARN_UNUSED_RESULT; +smf_event_t *smf_event_new_from_bytes(int first_byte, int second_byte, int third_byte) WARN_UNUSED_RESULT; +smf_event_t *smf_event_new_textual(int type, const char *text); +void smf_event_delete(smf_event_t *event); + +int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_is_system_realtime(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_is_system_common(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_is_sysex(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_is_eot(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_is_textual(const smf_event_t *event) WARN_UNUSED_RESULT; +char *smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT; +char *smf_event_extract_text(const smf_event_t *event) WARN_UNUSED_RESULT; + +/* Routines for loading SMF files. */ +smf_t *smf_load(const char *file_name) WARN_UNUSED_RESULT; +smf_t *smf_load_from_memory(const void *buffer, const int buffer_length) WARN_UNUSED_RESULT; + +/* Routine for writing SMF files. */ +int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT; + +/* Routines for manipulating smf_tempo_t. */ +smf_tempo_t *smf_get_tempo_by_pulses(const smf_t *smf, int pulses) WARN_UNUSED_RESULT; +smf_tempo_t *smf_get_tempo_by_seconds(const smf_t *smf, double seconds) WARN_UNUSED_RESULT; +smf_tempo_t *smf_get_tempo_by_number(const smf_t *smf, int number) WARN_UNUSED_RESULT; +smf_tempo_t *smf_get_last_tempo(const smf_t *smf) WARN_UNUSED_RESULT; + +const char *smf_get_version(void) WARN_UNUSED_RESULT; + +#ifdef __cplusplus +} +#endif + +#endif /* SMF_H */ + diff -r 000000000000 -r 4264abea8b06 src/smf_decode.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf_decode.c Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,638 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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 + * + * Event decoding routines. + * + */ + +#include +#include +#include +#include +#include +#ifdef __MINGW32__ +#include +#else /* ! __MINGW32__ */ +#include +#endif /* ! __MINGW32__ */ +#include +#include "smf.h" +#include "smf_private.h" + +#define BUFFER_SIZE 1024 + +/** + * \return Nonzero if event is metaevent. You should never send metaevents; + * they are not really MIDI messages. They carry information like track title, + * time signature etc. + */ +int +smf_event_is_metadata(const smf_event_t *event) +{ + assert(event->midi_buffer); + assert(event->midi_buffer_length > 0); + + if (event->midi_buffer[0] == 0xFF) + return (1); + + return (0); +} + +/** + * \return Nonzero if event is System Realtime. + */ +int +smf_event_is_system_realtime(const smf_event_t *event) +{ + assert(event->midi_buffer); + assert(event->midi_buffer_length > 0); + + if (smf_event_is_metadata(event)) + return (0); + + if (event->midi_buffer[0] >= 0xF8) + return (1); + + return (0); +} + +/** + * \return Nonzero if event is System Common. + */ +int +smf_event_is_system_common(const smf_event_t *event) +{ + assert(event->midi_buffer); + assert(event->midi_buffer_length > 0); + + if (event->midi_buffer[0] >= 0xF0 && event->midi_buffer[0] <= 0xF7) + return (1); + + return (0); +} +/** + * \return Nonzero if event is SysEx message. + */ +int +smf_event_is_sysex(const smf_event_t *event) +{ + assert(event->midi_buffer); + assert(event->midi_buffer_length > 0); + + if (event->midi_buffer[0] == 0xF0) + return (1); + + return (0); +} + +static char * +smf_event_decode_textual(const smf_event_t *event, const char *name) +{ + int off = 0; + char *buf, *extracted; + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode_textual: malloc failed."); + return (NULL); + } + + extracted = smf_event_extract_text(event); + if (extracted == NULL) { + free(buf); + return (NULL); + } + + snprintf(buf + off, BUFFER_SIZE - off, "%s: %s", name, extracted); + + return (buf); +} + +static char * +smf_event_decode_metadata(const smf_event_t *event) +{ + int off = 0, mspqn, flats, isminor; + char *buf; + + static const char *const major_keys[] = {"Fb", "Cb", "Gb", "Db", "Ab", + "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", "G#"}; + + static const char *const minor_keys[] = {"Dbm", "Abm", "Ebm", "Bbm", "Fm", + "Cm", "Gm", "Dm", "Am", "Em", "Bm", "F#m", "C#m", "G#m", "D#m", "A#m", "E#m"}; + + assert(smf_event_is_metadata(event)); + + switch (event->midi_buffer[1]) { + case 0x01: + return (smf_event_decode_textual(event, "Text")); + + case 0x02: + return (smf_event_decode_textual(event, "Copyright")); + + case 0x03: + return (smf_event_decode_textual(event, "Sequence/Track Name")); + + case 0x04: + return (smf_event_decode_textual(event, "Instrument")); + + case 0x05: + return (smf_event_decode_textual(event, "Lyric")); + + case 0x06: + return (smf_event_decode_textual(event, "Marker")); + + case 0x07: + return (smf_event_decode_textual(event, "Cue Point")); + + case 0x08: + return (smf_event_decode_textual(event, "Program Name")); + + case 0x09: + return (smf_event_decode_textual(event, "Device (Port) Name")); + + default: + break; + } + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode_metadata: malloc failed."); + return (NULL); + } + + switch (event->midi_buffer[1]) { + case 0x00: + off += snprintf(buf + off, BUFFER_SIZE - off, "Sequence number"); + break; + + /* http://music.columbia.edu/pipermail/music-dsp/2004-August/061196.html */ + case 0x20: + if (event->midi_buffer_length < 4) { + fg_critical("smf_event_decode_metadata: truncated MIDI message."); + goto error; + } + + off += snprintf(buf + off, BUFFER_SIZE - off, "Channel Prefix: %d", event->midi_buffer[3]); + break; + + case 0x21: + if (event->midi_buffer_length < 4) { + fg_critical("smf_event_decode_metadata: truncated MIDI message."); + goto error; + } + + off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Port: %d", event->midi_buffer[3]); + break; + + case 0x2F: + off += snprintf(buf + off, BUFFER_SIZE - off, "End Of Track"); + break; + + case 0x51: + if (event->midi_buffer_length < 6) { + fg_critical("smf_event_decode_metadata: truncated MIDI message."); + goto error; + } + + mspqn = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5]; + + off += snprintf(buf + off, BUFFER_SIZE - off, "Tempo: %d microseconds per quarter note, %.2f BPM", + mspqn, 60000000.0 / (double)mspqn); + break; + + case 0x54: + off += snprintf(buf + off, BUFFER_SIZE - off, "SMPTE Offset"); + break; + + case 0x58: + if (event->midi_buffer_length < 7) { + fg_critical("smf_event_decode_metadata: truncated MIDI message."); + goto error; + } + + off += snprintf(buf + off, BUFFER_SIZE - off, + "Time Signature: %d/%d, %d clocks per click, %d notated 32nd notes per quarter note", + event->midi_buffer[3], (int)pow(2, event->midi_buffer[4]), event->midi_buffer[5], + event->midi_buffer[6]); + break; + + case 0x59: + if (event->midi_buffer_length < 5) { + fg_critical("smf_event_decode_metadata: truncated MIDI message."); + goto error; + } + + flats = event->midi_buffer[3]; + isminor = event->midi_buffer[4]; + + if (isminor != 0 && isminor != 1) { + fg_critical("smf_event_decode_metadata: last byte of the Key Signature event has invalid value %d.", isminor); + goto error; + } + + off += snprintf(buf + off, BUFFER_SIZE - off, "Key Signature: "); + + if (flats > 8 && flats < 248) { + off += snprintf(buf + off, BUFFER_SIZE - off, "%d %s, %s key", abs((int8_t)flats), + flats > 127 ? "flats" : "sharps", isminor ? "minor" : "major"); + } else { + int i = (flats - 248) & 255; + + assert(i >= 0 && i < sizeof(minor_keys) / sizeof(*minor_keys)); + assert(i >= 0 && i < sizeof(major_keys) / sizeof(*major_keys)); + + if (isminor) + off += snprintf(buf + off, BUFFER_SIZE - off, "%s", minor_keys[i]); + else + off += snprintf(buf + off, BUFFER_SIZE - off, "%s", major_keys[i]); + } + + break; + + case 0x7F: + off += snprintf(buf + off, BUFFER_SIZE - off, "Proprietary (aka Sequencer) Event, length %d", + event->midi_buffer_length); + break; + + default: + goto error; + } + + return (buf); + +error: + free(buf); + + return (NULL); +} + +static char * +smf_event_decode_system_realtime(const smf_event_t *event) +{ + int off = 0; + char *buf; + + assert(smf_event_is_system_realtime(event)); + + if (event->midi_buffer_length != 1) { + fg_critical("smf_event_decode_system_realtime: event length is not 1."); + return (NULL); + } + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode_system_realtime: malloc failed."); + return (NULL); + } + + switch (event->midi_buffer[0]) { + case 0xF8: + off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Clock (realtime)"); + break; + + case 0xF9: + off += snprintf(buf + off, BUFFER_SIZE - off, "Tick (realtime)"); + break; + + case 0xFA: + off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Start (realtime)"); + break; + + case 0xFB: + off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Continue (realtime)"); + break; + + case 0xFC: + off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Stop (realtime)"); + break; + + case 0xFE: + off += snprintf(buf + off, BUFFER_SIZE - off, "Active Sense (realtime)"); + break; + + default: + free(buf); + return (NULL); + } + + return (buf); +} + +static char * +smf_event_decode_sysex(const smf_event_t *event) +{ + int off = 0; + char *buf, manufacturer, subid, subid2; + + assert(smf_event_is_sysex(event)); + + if (event->midi_buffer_length < 5) { + fg_critical("smf_event_decode_sysex: truncated MIDI message."); + return (NULL); + } + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode_sysex: malloc failed."); + return (NULL); + } + + manufacturer = event->midi_buffer[1]; + + if (manufacturer == 0x7F) { + off += snprintf(buf + off, BUFFER_SIZE - off, "SysEx, realtime, channel %d", event->midi_buffer[2]); + } else if (manufacturer == 0x7E) { + off += snprintf(buf + off, BUFFER_SIZE - off, "SysEx, non-realtime, channel %d", event->midi_buffer[2]); + } else { + off += snprintf(buf + off, BUFFER_SIZE - off, "SysEx, manufacturer 0x%x", manufacturer); + + return (buf); + } + + subid = event->midi_buffer[3]; + subid2 = event->midi_buffer[4]; + + if (subid == 0x01) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Header"); + + else if (subid == 0x02) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Data Packet"); + + else if (subid == 0x03) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Request"); + + else if (subid == 0x04 && subid2 == 0x01) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Master Volume"); + + else if (subid == 0x05 && subid2 == 0x01) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Loop Point Retransmit"); + + else if (subid == 0x05 && subid2 == 0x02) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Loop Point Request"); + + else if (subid == 0x06 && subid2 == 0x01) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Identity Request"); + + else if (subid == 0x06 && subid2 == 0x02) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Identity Reply"); + + else if (subid == 0x08 && subid2 == 0x00) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Bulk Tuning Dump Request"); + + else if (subid == 0x08 && subid2 == 0x01) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Bulk Tuning Dump"); + + else if (subid == 0x08 && subid2 == 0x02) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Single Note Tuning Change"); + + else if (subid == 0x08 && subid2 == 0x03) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Bulk Tuning Dump Request (Bank)"); + + else if (subid == 0x08 && subid2 == 0x04) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Key Based Tuning Dump"); + + else if (subid == 0x08 && subid2 == 0x05) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Scale/Octave Tuning Dump, 1 byte format"); + + else if (subid == 0x08 && subid2 == 0x06) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Scale/Octave Tuning Dump, 2 byte format"); + + else if (subid == 0x08 && subid2 == 0x07) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Single Note Tuning Change (Bank)"); + + else if (subid == 0x09) + off += snprintf(buf + off, BUFFER_SIZE - off, ", General MIDI %s", subid2 == 0 ? "disable" : "enable"); + + else if (subid == 0x7C) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Wait"); + + else if (subid == 0x7D) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Cancel"); + + else if (subid == 0x7E) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump NAK"); + + else if (subid == 0x7F) + off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump ACK"); + + else + off += snprintf(buf + off, BUFFER_SIZE - off, ", Unknown"); + + return (buf); +} + +static char * +smf_event_decode_system_common(const smf_event_t *event) +{ + int off = 0; + char *buf; + + assert(smf_event_is_system_common(event)); + + if (smf_event_is_sysex(event)) + return (smf_event_decode_sysex(event)); + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode_system_realtime: malloc failed."); + return (NULL); + } + + switch (event->midi_buffer[0]) { + case 0xF1: + off += snprintf(buf + off, BUFFER_SIZE - off, "MTC Quarter Frame"); + break; + + case 0xF2: + off += snprintf(buf + off, BUFFER_SIZE - off, "Song Position Pointer"); + break; + + case 0xF3: + off += snprintf(buf + off, BUFFER_SIZE - off, "Song Select"); + break; + + case 0xF6: + off += snprintf(buf + off, BUFFER_SIZE - off, "Tune Request"); + break; + + default: + free(buf); + return (NULL); + } + + return (buf); +} + +static void +note_from_int(char *buf, int note_number) +{ + int note, octave; + char *names[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; + + octave = note_number / 12 - 1; + note = note_number % 12; + + sprintf(buf, "%s%d", names[note], octave); +} + +/** + * \return Textual representation of the event given, or NULL, if event is unknown. + * Returned string looks like this: + * + * Note On, channel 1, note F#3, velocity 0 + * + * You should free the returned string afterwards, using free(3). + */ +char * +smf_event_decode(const smf_event_t *event) +{ + int off = 0, channel; + char *buf, note[5]; + + if (smf_event_is_metadata(event)) + return (smf_event_decode_metadata(event)); + + if (smf_event_is_system_realtime(event)) + return (smf_event_decode_system_realtime(event)); + + if (smf_event_is_system_common(event)) + return (smf_event_decode_system_common(event)); + + if (!smf_event_length_is_valid(event)) { + fg_critical("smf_event_decode: incorrect MIDI message length."); + return (NULL); + } + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode: malloc failed."); + return (NULL); + } + + /* + 1, because user-visible channels used to be in range <1-16>. */ + channel = (event->midi_buffer[0] & 0x0F) + 1; + + switch (event->midi_buffer[0] & 0xF0) { + case 0x80: + note_from_int(note, event->midi_buffer[1]); + off += snprintf(buf + off, BUFFER_SIZE - off, "Note Off, channel %d, note %s, velocity %d", + channel, note, event->midi_buffer[2]); + break; + + case 0x90: + note_from_int(note, event->midi_buffer[1]); + off += snprintf(buf + off, BUFFER_SIZE - off, "Note On, channel %d, note %s, velocity %d", + channel, note, event->midi_buffer[2]); + break; + + case 0xA0: + note_from_int(note, event->midi_buffer[1]); + off += snprintf(buf + off, BUFFER_SIZE - off, "Aftertouch, channel %d, note %s, pressure %d", + channel, note, event->midi_buffer[2]); + break; + + case 0xB0: + off += snprintf(buf + off, BUFFER_SIZE - off, "Controller, channel %d, controller %d, value %d", + channel, event->midi_buffer[1], event->midi_buffer[2]); + break; + + case 0xC0: + off += snprintf(buf + off, BUFFER_SIZE - off, "Program Change, channel %d, controller %d", + channel, event->midi_buffer[1]); + break; + + case 0xD0: + off += snprintf(buf + off, BUFFER_SIZE - off, "Channel Pressure, channel %d, pressure %d", + channel, event->midi_buffer[1]); + break; + + case 0xE0: + off += snprintf(buf + off, BUFFER_SIZE - off, "Pitch Wheel, channel %d, value %d", + channel, ((int)event->midi_buffer[2] << 7) | (int)event->midi_buffer[2]); + break; + + default: + free(buf); + return (NULL); + } + + return (buf); +} + +/** + * \return Textual representation of the data extracted from MThd header, or NULL, if something goes wrong. + * Returned string looks like this: + * + * format: 1 (several simultaneous tracks); number of tracks: 4; division: 192 PPQN. + * + * You should free the returned string afterwards, using free(3). + */ +char * +smf_decode(const smf_t *smf) +{ + int off = 0; + char *buf; + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + fg_critical("smf_event_decode: malloc failed."); + return (NULL); + } + + off += snprintf(buf + off, BUFFER_SIZE - off, "format: %d ", smf->format); + + switch (smf->format) { + case 0: + off += snprintf(buf + off, BUFFER_SIZE - off, "(single track)"); + break; + + case 1: + off += snprintf(buf + off, BUFFER_SIZE - off, "(several simultaneous tracks)"); + break; + + case 2: + off += snprintf(buf + off, BUFFER_SIZE - off, "(several independent tracks)"); + break; + + default: + off += snprintf(buf + off, BUFFER_SIZE - off, "(INVALID FORMAT)"); + break; + } + + off += snprintf(buf + off, BUFFER_SIZE - off, "; number of tracks: %d", smf->number_of_tracks); + + if (smf->ppqn != 0) + off += snprintf(buf + off, BUFFER_SIZE - off, "; division: %d PPQN", smf->ppqn); + else + off += snprintf(buf + off, BUFFER_SIZE - off, "; division: %d FPS, %d resolution", smf->frames_per_second, smf->resolution); + + return (buf); +} + diff -r 000000000000 -r 4264abea8b06 src/smf_load.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf_load.c Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,933 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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 + * + * Standard MIDI File format loader. + * + */ + +/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */ + +#include +#include +#include +#include +#include +#include +#ifdef __MINGW32__ +#include +#else /* ! __MINGW32__ */ +#include +#endif /* ! __MINGW32__ */ +#include "smf.h" +#include "smf_private.h" + +/** + * Returns pointer to the next SMF chunk in smf->buffer, based on length of the previous one. + * Returns NULL in case of error. + */ +static struct chunk_header_struct * +next_chunk(smf_t *smf) +{ + struct chunk_header_struct *chunk; + void *next_chunk_ptr; + + assert(smf->file_buffer != NULL); + assert(smf->file_buffer_length > 0); + assert(smf->next_chunk_offset >= 0); + + if (smf->next_chunk_offset + sizeof(struct chunk_header_struct) >= smf->file_buffer_length) { + fg_critical("SMF warning: no more chunks left."); + return (NULL); + } + + next_chunk_ptr = (unsigned char *)smf->file_buffer + smf->next_chunk_offset; + + chunk = (struct chunk_header_struct *)next_chunk_ptr; + + if (!isalpha(chunk->id[0]) || !isalpha(chunk->id[1]) || !isalpha(chunk->id[2]) || !isalpha(chunk->id[3])) { + fg_critical("SMF error: chunk signature contains at least one non-alphanumeric byte."); + return (NULL); + } + + /* + * XXX: On SPARC, after compiling with "-fast" option there will be SIGBUS here. + * Please compile with -xmemalign=8i". + */ + smf->next_chunk_offset += sizeof(struct chunk_header_struct) + ntohl(chunk->length); + + if (smf->next_chunk_offset > smf->file_buffer_length) { + fg_critical("SMF warning: malformed chunk; truncated file?"); + smf->next_chunk_offset = smf->file_buffer_length; + } + + return (chunk); +} + +/** + * Returns 1, iff signature of the "chunk" is the same as string passed as "signature". + */ +static int +chunk_signature_matches(const struct chunk_header_struct *chunk, const char *signature) +{ + if (!memcmp(chunk->id, signature, 4)) + return (1); + + return (0); +} + +/** + * Verifies if MThd header looks OK. Returns 0 iff it does. + */ +static int +parse_mthd_header(smf_t *smf) +{ + int len; + struct chunk_header_struct *mthd, *tmp_mthd; + + /* Make sure compiler didn't do anything stupid. */ + assert(sizeof(struct chunk_header_struct) == 8); + + /* + * We could just do "mthd = smf->file_buffer;" here, but this way we wouldn't + * get useful error messages. + */ + if (smf->file_buffer_length < 6) { + fg_critical("SMF error: file is too short, it cannot be a MIDI file."); + + return (-1); + } + + tmp_mthd = smf->file_buffer; + + if (!chunk_signature_matches(tmp_mthd, "MThd")) { + fg_critical("SMF error: MThd signature not found, is that a MIDI file?"); + + return (-2); + } + + /* Ok, now use next_chunk(). */ + mthd = next_chunk(smf); + if (mthd == NULL) + return (-3); + + assert(mthd == tmp_mthd); + + len = ntohl(mthd->length); + if (len != 6) { + fg_critical("SMF error: MThd chunk length %d, must be 6.", len); + + return (-4); + } + + return (0); +} + +/** + * Parses MThd chunk, filling "smf" structure with values extracted from it. Returns 0 iff everything went OK. + */ +static int +parse_mthd_chunk(smf_t *smf) +{ + signed char first_byte_of_division, second_byte_of_division; + + struct mthd_chunk_struct *mthd; + + assert(sizeof(struct mthd_chunk_struct) == 14); + + if (parse_mthd_header(smf)) + return (1); + + mthd = (struct mthd_chunk_struct *)smf->file_buffer; + + smf->format = ntohs(mthd->format); + if (smf->format < 0 || smf->format > 2) { + fg_critical("SMF error: bad MThd format field value: %d, valid values are 0-2, inclusive.", smf->format); + return (-1); + } + + if (smf->format == 2) { + fg_critical("SMF file uses format #2, no support for that yet."); + return (-2); + } + + smf->expected_number_of_tracks = ntohs(mthd->number_of_tracks); + if (smf->expected_number_of_tracks <= 0) { + fg_critical("SMF error: bad number of tracks: %d, must be greater than zero.", smf->expected_number_of_tracks); + return (-3); + } + + /* XXX: endianess? */ + first_byte_of_division = *((signed char *)&(mthd->division)); + second_byte_of_division = *((signed char *)&(mthd->division) + 1); + + if (first_byte_of_division >= 0) { + smf->ppqn = ntohs(mthd->division); + smf->frames_per_second = 0; + smf->resolution = 0; + } else { + smf->ppqn = 0; + smf->frames_per_second = - first_byte_of_division; + smf->resolution = second_byte_of_division; + } + + if (smf->ppqn == 0) { + fg_critical("SMF file uses FPS timing instead of PPQN, no support for that yet."); + return (-4); + } + + return (0); +} + +/** + * Interprets Variable Length Quantity pointed at by "buf" and puts its value into "value" and number + * of bytes consumed into "len", making sure it does not read past "buf" + "buffer_length". + * Explanation of Variable Length Quantities is here: http://www.borg.com/~jglatt/tech/midifile/vari.htm + * Returns 0 iff everything went OK, different value in case of error. + */ +static int +extract_vlq(const unsigned char *buf, const int buffer_length, int *value, int *len) +{ + int val = 0; + const unsigned char *c = buf; + + assert(buffer_length > 0); + + for (;;) { + if (c >= buf + buffer_length) { + fg_critical("End of buffer in extract_vlq()."); + return (-1); + } + + val = (val << 7) + (*c & 0x7F); + + if (*c & 0x80) + c++; + else + break; + }; + + *value = val; + *len = c - buf + 1; + + if (*len > 4) { + fg_critical("SMF error: Variable Length Quantities longer than four bytes are not supported yet."); + return (-2); + } + + return (0); +} + +/** + * Returns 1 if the given byte is a valid status byte, 0 otherwise. + */ +int +is_status_byte(const unsigned char status) +{ + return (status & 0x80); +} + +static int +is_sysex_byte(const unsigned char status) +{ + if (status == 0xF0) + return (1); + + return (0); +} + +static int +is_escape_byte(const unsigned char status) +{ + if (status == 0xF7) + return (1); + + return (0); +} + +/** + * Just like expected_message_length(), but only for System Exclusive messages. + * Note that value returned by this thing here is the length of SysEx "on the wire", + * not the number of bytes that this sysex takes in the file - in SMF format sysex + * contains VLQ telling how many bytes it takes, "on the wire" format does not have + * this. + */ +static int +expected_sysex_length(const unsigned char status, const unsigned char *second_byte, const int buffer_length, int *consumed_bytes) +{ + int sysex_length, len; + + assert(status == 0xF0); + + if (buffer_length < 3) { + fg_critical("SMF error: end of buffer in expected_sysex_length()."); + return (-1); + } + + if (extract_vlq(second_byte, buffer_length, &sysex_length, &len)) + return (-1); + + if (consumed_bytes != NULL) + *consumed_bytes = len; + + /* +1, because the length does not include status byte. */ + return (sysex_length + 1); +} + +static int +expected_escaped_length(const unsigned char status, const unsigned char *second_byte, const int buffer_length, int *consumed_bytes) +{ + /* -1, because we do not want to account for 0x7F status. */ + return (expected_sysex_length(status, second_byte, buffer_length, consumed_bytes) - 1); +} + +/** + * Returns expected length of the midi message (including the status byte), in bytes, for the given status byte. + * The "second_byte" points to the expected second byte of the MIDI message. "buffer_length" is the buffer + * length limit, counting from "second_byte". Returns value < 0 iff there was an error. + */ +static int +expected_message_length(unsigned char status, const unsigned char *second_byte, const int buffer_length) +{ + /* Make sure this really is a valid status byte. */ + assert(is_status_byte(status)); + + /* We cannot use this routine for sysexes. */ + assert(!is_sysex_byte(status)); + + /* We cannot use this routine for escaped events. */ + assert(!is_escape_byte(status)); + + /* Buffer length may be zero, for e.g. realtime messages. */ + assert(buffer_length >= 0); + + /* Is this a metamessage? */ + if (status == 0xFF) { + if (buffer_length < 2) { + fg_critical("SMF error: end of buffer in expected_message_length()."); + return (-1); + } + + /* + * Format of this kind of messages is like this: 0xFF 0xwhatever 0xlength and then "length" bytes. + * Second byte points to this: ^^^^^^^^^^ + */ + return (*(second_byte + 1) + 3); + } + + if ((status & 0xF0) == 0xF0) { + switch (status) { + case 0xF2: /* Song Position Pointer. */ + return (3); + + case 0xF1: /* MTC Quarter Frame. */ + case 0xF3: /* Song Select. */ + return (2); + + case 0xF6: /* Tune Request. */ + case 0xF8: /* MIDI Clock. */ + case 0xF9: /* Tick. */ + case 0xFA: /* MIDI Start. */ + case 0xFB: /* MIDI Continue. */ + case 0xFC: /* MIDI Stop. */ + case 0xFE: /* Active Sense. */ + return (1); + + default: + fg_critical("SMF error: unknown 0xFx-type status byte '0x%x'.", status); + return (-2); + } + } + + /* Filter out the channel. */ + status &= 0xF0; + + switch (status) { + case 0x80: /* Note Off. */ + case 0x90: /* Note On. */ + case 0xA0: /* AfterTouch. */ + case 0xB0: /* Control Change. */ + case 0xE0: /* Pitch Wheel. */ + return (3); + + case 0xC0: /* Program Change. */ + case 0xD0: /* Channel Pressure. */ + return (2); + + default: + fg_critical("SMF error: unknown status byte '0x%x'.", status); + return (-3); + } +} + +static int +extract_sysex_event(const unsigned char *buf, const int buffer_length, smf_event_t *event, int *len, int last_status) +{ + int status, message_length, vlq_length; + const unsigned char *c = buf; + + status = *buf; + + assert(is_sysex_byte(status)); + + c++; + + message_length = expected_sysex_length(status, c, buffer_length - 1, &vlq_length); + + if (message_length < 0) + return (-3); + + c += vlq_length; + + if (vlq_length + message_length >= buffer_length) { + fg_critical("End of buffer in extract_sysex_event()."); + return (-5); + } + + event->midi_buffer_length = message_length; + event->midi_buffer = malloc(event->midi_buffer_length); + if (event->midi_buffer == NULL) { + fg_critical("Cannot allocate memory in extract_sysex_event(): %s", strerror(errno)); + return (-4); + } + + event->midi_buffer[0] = status; + memcpy(event->midi_buffer + 1, c, message_length - 1); + + *len = vlq_length + message_length; + + return (0); +} + +static int +extract_escaped_event(const unsigned char *buf, const int buffer_length, smf_event_t *event, int *len, int last_status) +{ + int status, message_length, vlq_length; + const unsigned char *c = buf; + + status = *buf; + + assert(is_escape_byte(status)); + + c++; + + message_length = expected_escaped_length(status, c, buffer_length - 1, &vlq_length); + + if (message_length < 0) + return (-3); + + c += vlq_length; + + if (vlq_length + message_length >= buffer_length) { + fg_critical("End of buffer in extract_escaped_event()."); + return (-5); + } + + event->midi_buffer_length = message_length; + event->midi_buffer = malloc(event->midi_buffer_length); + if (event->midi_buffer == NULL) { + fg_critical("Cannot allocate memory in extract_escaped_event(): %s", strerror(errno)); + return (-4); + } + + memcpy(event->midi_buffer, c, message_length); + + if (smf_event_is_valid(event)) { + fg_critical("Escaped event is invalid."); + return (-1); + } + + if (smf_event_is_system_realtime(event) || smf_event_is_system_common(event)) { + fg_warning("Escaped event is not System Realtime nor System Common."); + } + + *len = vlq_length + message_length; + + return (0); +} + + +/** + * Puts MIDI data extracted from from "buf" into "event" and number of consumed bytes into "len". + * In case valid status is not found, it uses "last_status" (so called "running status"). + * Returns 0 iff everything went OK, value < 0 in case of error. + */ +static int +extract_midi_event(const unsigned char *buf, const int buffer_length, smf_event_t *event, int *len, int last_status) +{ + int status, message_length; + const unsigned char *c = buf; + + assert(buffer_length > 0); + + /* Is the first byte the status byte? */ + if (is_status_byte(*c)) { + status = *c; + c++; + + } else { + /* No, we use running status then. */ + status = last_status; + } + + if (!is_status_byte(status)) { + fg_critical("SMF error: bad status byte (MSB is zero)."); + return (-1); + } + + if (is_sysex_byte(status)) + return (extract_sysex_event(buf, buffer_length, event, len, last_status)); + + if (is_escape_byte(status)) + return (extract_escaped_event(buf, buffer_length, event, len, last_status)); + + /* At this point, "c" points to first byte following the status byte. */ + message_length = expected_message_length(status, c, buffer_length - (c - buf)); + + if (message_length < 0) + return (-3); + + if (message_length - 1 > buffer_length - (c - buf)) { + fg_critical("End of buffer in extract_midi_event()."); + return (-5); + } + + event->midi_buffer_length = message_length; + event->midi_buffer = malloc(event->midi_buffer_length); + if (event->midi_buffer == NULL) { + fg_critical("Cannot allocate memory in extract_midi_event(): %s", strerror(errno)); + return (-4); + } + + event->midi_buffer[0] = status; + memcpy(event->midi_buffer + 1, c, message_length - 1); + + *len = c + message_length - 1 - buf; + + return (0); +} + +/** + * Locates, basing on track->next_event_offset, the next event data in track->buffer, + * interprets it, allocates smf_event_t and fills it properly. Returns smf_event_t + * or NULL, if there was an error. Allocating event means adding it to the track; + * see smf_event_new(). + */ +static smf_event_t * +parse_next_event(smf_track_t *track) +{ + int time = 0, len, buffer_length; + unsigned char *c, *start; + + smf_event_t *event = smf_event_new(); + if (event == NULL) + goto error; + + c = start = (unsigned char *)track->file_buffer + track->next_event_offset; + + assert(track->file_buffer != NULL); + assert(track->file_buffer_length > 0); + assert(track->next_event_offset > 0); + + buffer_length = track->file_buffer_length - track->next_event_offset; + assert(buffer_length > 0); + + /* First, extract time offset from previous event. */ + if (extract_vlq(c, buffer_length, &time, &len)) + goto error; + + c += len; + buffer_length -= len; + + if (buffer_length <= 0) + goto error; + + /* Now, extract the actual event. */ + if (extract_midi_event(c, buffer_length, event, &len, track->last_status)) + goto error; + + c += len; + buffer_length -= len; + track->last_status = event->midi_buffer[0]; + track->next_event_offset += c - start; + + smf_track_add_event_delta_pulses(track, event, time); + + return (event); + +error: + if (event != NULL) + smf_event_delete(event); + + return (NULL); +} + +/** + * Takes "len" characters starting in "buf", making sure it does not access past the length of the buffer, + * and makes ordinary, zero-terminated string from it. May return NULL if there was any problem. + */ +static char * +make_string(const unsigned char *buf, const int buffer_length, int len) +{ + char *str; + + assert(buffer_length > 0); + assert(len > 0); + + if (len > buffer_length) { + fg_critical("End of buffer in make_string()."); + + len = buffer_length; + } + + str = malloc(len + 1); + if (str == NULL) { + fg_critical("Cannot allocate memory in make_string()."); + return (NULL); + } + + memcpy(str, buf, len); + str[len] = '\0'; + + return (str); +} + +/** + * \return 1, if passed a metaevent containing text, that is, Text, Copyright, + * Sequence/Track Name, Instrument, Lyric, Marker, Cue Point, Program Name, + * or Device Name; 0 otherwise. + */ +int +smf_event_is_textual(const smf_event_t *event) +{ + if (!smf_event_is_metadata(event)) + return (0); + + if (event->midi_buffer_length < 4) + return (0); + + if (event->midi_buffer[3] < 1 && event->midi_buffer[3] > 9) + return (0); + + return (1); +} + +/** + * Extracts text from "textual metaevents", such as Text or Lyric. + * + * \return Zero-terminated string extracted from "text events" or NULL, if there was any problem. + */ +char * +smf_event_extract_text(const smf_event_t *event) +{ + int string_length = -1, length_length = -1; + + if (!smf_event_is_textual(event)) + return (NULL); + + if (event->midi_buffer_length < 3) { + fg_critical("smf_event_extract_text: truncated MIDI message."); + return (NULL); + } + + extract_vlq((void *)&(event->midi_buffer[2]), event->midi_buffer_length - 2, &string_length, &length_length); + + if (string_length <= 0) { + fg_critical("smf_event_extract_text: truncated MIDI message."); + return (NULL); + } + + return (make_string((void *)(&event->midi_buffer[2] + length_length), event->midi_buffer_length - 2 - length_length, string_length)); +} + +/** + * Verify if the next chunk really is MTrk chunk, and if so, initialize some track variables and return 0. + * Return different value otherwise. + */ +static int +parse_mtrk_header(smf_track_t *track) +{ + struct chunk_header_struct *mtrk; + + /* Make sure compiler didn't do anything stupid. */ + assert(sizeof(struct chunk_header_struct) == 8); + assert(track->smf != NULL); + + mtrk = next_chunk(track->smf); + + if (mtrk == NULL) + return (-1); + + if (!chunk_signature_matches(mtrk, "MTrk")) { + fg_warning("SMF warning: Expected MTrk signature, got %c%c%c%c instead; ignoring this chunk.", + mtrk->id[0], mtrk->id[1], mtrk->id[2], mtrk->id[3]); + + return (-2); + } + + track->file_buffer = mtrk; + track->file_buffer_length = sizeof(struct chunk_header_struct) + ntohl(mtrk->length); + track->next_event_offset = sizeof(struct chunk_header_struct); + + return (0); +} + +/** + * Return 1 if event is end-of-the-track, 0 otherwise. + */ +static int +event_is_end_of_track(const smf_event_t *event) +{ + if (event->midi_buffer[0] == 0xFF && event->midi_buffer[1] == 0x2F) + return (1); + + return (0); +} + +/** + * \return Nonzero, if event is as long as it should be, from the MIDI specification point of view. + * Does not work for SysExes - it doesn't recognize internal structure of SysEx. + */ +int +smf_event_length_is_valid(const smf_event_t *event) +{ + assert(event); + assert(event->midi_buffer); + + if (event->midi_buffer_length < 1) + return (0); + + /* We cannot use expected_message_length on sysexes. */ + if (smf_event_is_sysex(event)) + return (1); + + if (event->midi_buffer_length != expected_message_length(event->midi_buffer[0], + &(event->midi_buffer[1]), event->midi_buffer_length - 1)) { + + return (0); + } + + return (1); +} + +/** + * \return Nonzero, if MIDI data in the event is valid, 0 otherwise. For example, + * it checks if event length is correct. + */ +/* XXX: this routine requires some more work to detect more errors. */ +int +smf_event_is_valid(const smf_event_t *event) +{ + assert(event); + assert(event->midi_buffer); + assert(event->midi_buffer_length >= 1); + + if (!is_status_byte(event->midi_buffer[0])) { + fg_critical("First byte of MIDI message is not a valid status byte."); + + return (0); + } + + if (!smf_event_length_is_valid(event)) + return (0); + + return (1); +} + +/** + * Parse events and put it on the track. + */ +static int +parse_mtrk_chunk(smf_track_t *track) +{ + smf_event_t *event; + + if (parse_mtrk_header(track)) + return (-1); + + for (;;) { + event = parse_next_event(track); + + /* Couldn't parse an event? */ + if (event == NULL) { + fg_critical("Unable to parse MIDI event; truncating track."); + if (smf_track_add_eot_delta_pulses(track, 0) != 0) { + fg_critical("smf_track_add_eot_delta_pulses failed."); + return (-2); + } + break; + } + + assert(smf_event_is_valid(event)); + + if (event_is_end_of_track(event)) + break; + } + + track->file_buffer = NULL; + track->file_buffer_length = 0; + track->next_event_offset = -1; + + return (0); +} + +/** + * Allocate buffer of proper size and read file contents into it. Close file afterwards. + */ +static int +load_file_into_buffer(void **file_buffer, int *file_buffer_length, const char *file_name) +{ + FILE *stream = fopen(file_name, "rb"); + + if (stream == NULL) { + fg_critical("Cannot open input file: %s", strerror(errno)); + + return (-1); + } + + if (fseek(stream, 0, SEEK_END)) { + fg_critical("fseek(3) failed: %s", strerror(errno)); + + return (-2); + } + + *file_buffer_length = ftell(stream); + if (*file_buffer_length == -1) { + fg_critical("ftell(3) failed: %s", strerror(errno)); + + return (-3); + } + + if (fseek(stream, 0, SEEK_SET)) { + fg_critical("fseek(3) failed: %s", strerror(errno)); + + return (-4); + } + + *file_buffer = malloc(*file_buffer_length); + if (*file_buffer == NULL) { + fg_critical("malloc(3) failed: %s", strerror(errno)); + + return (-5); + } + + if (fread(*file_buffer, 1, *file_buffer_length, stream) != *file_buffer_length) { + fg_critical("fread(3) failed: %s", strerror(errno)); + + return (-6); + } + + if (fclose(stream)) { + fg_critical("fclose(3) failed: %s", strerror(errno)); + + return (-7); + } + + return (0); +} + +/** + * Creates new SMF and fills it with data loaded from the given buffer. + * \return SMF or NULL, if loading failed. + */ +smf_t * +smf_load_from_memory(const void *buffer, const int buffer_length) +{ + int i; + + smf_t *smf = smf_new(); + + smf->file_buffer = (void *)buffer; + smf->file_buffer_length = buffer_length; + smf->next_chunk_offset = 0; + + if (parse_mthd_chunk(smf)) + return (NULL); + + for (i = 1; i <= smf->expected_number_of_tracks; i++) { + smf_track_t *track = smf_track_new(); + if (track == NULL) + return (NULL); + + smf_add_track(smf, track); + + /* Skip unparseable chunks. */ + if (parse_mtrk_chunk(track)) { + fg_warning("SMF warning: Cannot load track."); + smf_track_delete(track); + } + + track->file_buffer = NULL; + track->file_buffer_length = 0; + track->next_event_offset = -1; + } + + if (smf->expected_number_of_tracks != smf->number_of_tracks) { + fg_warning("SMF warning: MThd header declared %d tracks, but only %d found; continuing anyway.", + smf->expected_number_of_tracks, smf->number_of_tracks); + + smf->expected_number_of_tracks = smf->number_of_tracks; + } + + smf->file_buffer = NULL; + smf->file_buffer_length = 0; + smf->next_chunk_offset = -1; + + return (smf); +} + +/** + * Loads SMF file. + * + * \param file_name Path to the file. + * \return SMF or NULL, if loading failed. + */ +smf_t * +smf_load(const char *file_name) +{ + int file_buffer_length; + void *file_buffer; + smf_t *smf; + + if (load_file_into_buffer(&file_buffer, &file_buffer_length, file_name)) + return (NULL); + + smf = smf_load_from_memory(file_buffer, file_buffer_length); + + memset(file_buffer, 0, file_buffer_length); + free(file_buffer); + + if (smf == NULL) + return (NULL); + + smf_rewind(smf); + + return (smf); +} + diff -r 000000000000 -r 4264abea8b06 src/smf_private.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf_private.h Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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. + * + */ + +#ifndef SMF_PRIVATE_H +#define SMF_PRIVATE_H + +#include +#include + +#include "fake_glib.h" + +#include "config.h" + +#define SMF_VERSION PACKAGE_VERSION + +/** + * \file + * + * Private header. Applications using libsmf should use smf.h. + * + */ + +#if defined(__GNUC__) +#define ATTRIBUTE_PACKED __attribute__((__packed__)) +#else +#define ATTRIBUTE_PACKED +#pragma pack(1) +#endif + +/** SMF chunk header, used only by smf_load.c and smf_save.c. */ +struct chunk_header_struct { + char id[4]; + uint32_t length; +} ATTRIBUTE_PACKED; + +/** SMF chunk, used only by smf_load.c and smf_save.c. */ +struct mthd_chunk_struct { + struct chunk_header_struct mthd_header; + uint16_t format; + uint16_t number_of_tracks; + uint16_t division; +} ATTRIBUTE_PACKED; + +#if (!defined __GNUC__) +#pragma pack() +#endif + +void smf_track_add_event(smf_track_t *track, smf_event_t *event); +void smf_init_tempo(smf_t *smf); +void smf_fini_tempo(smf_t *smf); +void smf_create_tempo_map_and_compute_seconds(smf_t *smf); +void maybe_add_to_tempo_map(smf_event_t *event); +void remove_last_tempo_with_pulses(smf_t *smf, int pulses); +int smf_event_is_tempo_change_or_time_signature(const smf_event_t *event) WARN_UNUSED_RESULT; +int smf_event_length_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT; +int is_status_byte(const unsigned char status) WARN_UNUSED_RESULT; + +#endif /* SMF_PRIVATE_H */ + diff -r 000000000000 -r 4264abea8b06 src/smf_save.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf_save.c Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,660 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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 + * + * Standard MIDI File writer. + * + */ + +/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */ + +#include +#include +#include +#include +#include +#ifdef __MINGW32__ +#include +#else /* ! __MINGW32__ */ +#include +#endif /* ! __MINGW32__ */ +#include "smf.h" +#include "smf_private.h" + +#define MAX_VLQ_LENGTH 128 + +/** + * Extends (reallocates) smf->file_buffer and returns pointer to the newly added space, + * that is, pointer to the first byte after the previous buffer end. Returns NULL in case + * of error. + */ +static void * +smf_extend(smf_t *smf, const int length) +{ + int i, previous_file_buffer_length = smf->file_buffer_length; + char *previous_file_buffer = smf->file_buffer; + + /* XXX: Not terribly efficient. */ + smf->file_buffer_length += length; + smf->file_buffer = realloc(smf->file_buffer, smf->file_buffer_length); + if (smf->file_buffer == NULL) { + fg_critical("realloc(3) failed: %s", strerror(errno)); + smf->file_buffer_length = 0; + return (NULL); + } + + /* Fix up pointers. XXX: omgwtf. */ + for (i = 1; i <= smf->number_of_tracks; i++) { + smf_track_t *track; + track = smf_get_track_by_number(smf, i); + if (track->file_buffer != NULL) + track->file_buffer = (char *)track->file_buffer + ((char *)smf->file_buffer - previous_file_buffer); + } + + return ((char *)smf->file_buffer + previous_file_buffer_length); +} + +/** + * Appends "buffer_length" bytes pointed to by "buffer" to the smf, reallocating storage as needed. Returns 0 + * if everything went ok, different value if there was any problem. + */ +static int +smf_append(smf_t *smf, const void *buffer, const int buffer_length) +{ + void *dest; + + dest = smf_extend(smf, buffer_length); + if (dest == NULL) { + fg_critical("Cannot extend track buffer."); + return (-1); + } + + memcpy(dest, buffer, buffer_length); + + return (0); +} + +/** + * Appends MThd header to the track. Returns 0 if everything went ok, different value if not. + */ +static int +write_mthd_header(smf_t *smf) +{ + struct mthd_chunk_struct mthd_chunk; + + memcpy(mthd_chunk.mthd_header.id, "MThd", 4); + mthd_chunk.mthd_header.length = htonl(6); + mthd_chunk.format = htons(smf->format); + mthd_chunk.number_of_tracks = htons(smf->number_of_tracks); + mthd_chunk.division = htons(smf->ppqn); + + return (smf_append(smf, &mthd_chunk, sizeof(mthd_chunk))); +} + +/** + * Extends (reallocates) track->file_buffer and returns pointer to the newly added space, + * that is, pointer to the first byte after the previous buffer end. Returns NULL in case + * of error. + */ +static void * +track_extend(smf_track_t *track, const int length) +{ + void *buf; + + assert(track->smf); + + buf = smf_extend(track->smf, length); + if (buf == NULL) + return (NULL); + + track->file_buffer_length += length; + if (track->file_buffer == NULL) + track->file_buffer = buf; + + return (buf); +} + +/** + * Appends "buffer_length" bytes pointed to by "buffer" to the track, reallocating storage as needed. Returns 0 + * if everything went ok, different value if there was any problem. + */ +static int +track_append(smf_track_t *track, const void *buffer, const int buffer_length) +{ + void *dest; + + dest = track_extend(track, buffer_length); + if (dest == NULL) { + fg_critical("Cannot extend track buffer."); + return (-1); + } + + memcpy(dest, buffer, buffer_length); + + return (0); +} + +static int +format_vlq(unsigned char *buf, int length, unsigned long value) +{ + int i; + unsigned long buffer; + + /* Taken from http://www.borg.com/~jglatt/tech/midifile/vari.htm */ + buffer = value & 0x7F; + + while ((value >>= 7)) { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + } + + for (i = 0;; i++) { + buf[i] = buffer; + + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + + assert(i <= length); + + /* + 1, because "i" is an offset, not a count. */ + return (i + 1); +} + +smf_event_t * +smf_event_new_textual(int type, const char *text) +{ + int vlq_length, text_length, copied_length; + smf_event_t *event; + + assert(type >= 1 && type <= 9); + + text_length = strlen(text); + + event = smf_event_new(); + if (event == NULL) + return (NULL); + + /* "2 +" is for leading 0xFF 0xtype. */ + event->midi_buffer_length = 2 + text_length + MAX_VLQ_LENGTH; + event->midi_buffer = malloc(event->midi_buffer_length); + if (event->midi_buffer == NULL) { + fg_critical("Cannot allocate MIDI buffer structure: %s", strerror(errno)); + smf_event_delete(event); + + return (NULL); + } + + event->midi_buffer[0] = 0xFF; + event->midi_buffer[1] = type; + + vlq_length = format_vlq(event->midi_buffer + 2, MAX_VLQ_LENGTH - 2, text_length); + copied_length = snprintf((char *)event->midi_buffer + vlq_length + 2, event->midi_buffer_length - vlq_length - 2, "%s", text); + + assert(copied_length == text_length); + + event->midi_buffer_length = 2 + vlq_length + text_length; + + return event; +} + +/** + * Appends value, expressed as Variable Length Quantity, to event->track. + */ +static int +write_vlq(smf_event_t *event, unsigned long value) +{ + unsigned char buf[MAX_VLQ_LENGTH]; + int vlq_length; + + vlq_length = format_vlq(buf, MAX_VLQ_LENGTH, value); + + return (track_append(event->track, buf, vlq_length)); +} + +/** + * Appends event time as Variable Length Quantity. Returns 0 if everything went ok, + * different value in case of error. + */ +static int +write_event_time(smf_event_t *event) +{ + assert(event->delta_time_pulses >= 0); + + return (write_vlq(event, event->delta_time_pulses)); +} + +static int +write_sysex_contents(smf_event_t *event) +{ + int ret; + unsigned char sysex_status = 0xF0; + + assert(smf_event_is_sysex(event)); + + ret = track_append(event->track, &sysex_status, 1); + if (ret) + return (ret); + + /* -1, because length does not include status byte. */ + ret = write_vlq(event, event->midi_buffer_length - 1); + if (ret) + return (ret); + + ret = track_append(event->track, event->midi_buffer + 1, event->midi_buffer_length - 1); + if (ret) + return (ret); + + return (0); +} + +/** + * Appends contents of event->midi_buffer wrapped into 0xF7 MIDI event. + */ +static int +write_escaped_event_contents(smf_event_t *event) +{ + int ret; + unsigned char escape_status = 0xF7; + + if (smf_event_is_sysex(event)) + return (write_sysex_contents(event)); + + ret = track_append(event->track, &escape_status, 1); + if (ret) + return (ret); + + ret = write_vlq(event, event->midi_buffer_length); + if (ret) + return (ret); + + ret = track_append(event->track, event->midi_buffer, event->midi_buffer_length); + if (ret) + return (ret); + + return (0); +} + +/** + * Appends contents of event->midi_buffer. Returns 0 if everything went 0, + * different value in case of error. + */ +static int +write_event_contents(smf_event_t *event) +{ + if (smf_event_is_system_realtime(event) || smf_event_is_system_common(event)) + return (write_escaped_event_contents(event)); + + return (track_append(event->track, event->midi_buffer, event->midi_buffer_length)); +} + +/** + * Writes out an event. + */ +static int +write_event(smf_event_t *event) +{ + int ret; + + ret = write_event_time(event); + if (ret) + return (ret); + + ret = write_event_contents(event); + if (ret) + return (ret); + + return (0); +} + +/** + * Writes out MTrk header, except of MTrk chunk length, which is written by write_mtrk_length(). + */ +static int +write_mtrk_header(smf_track_t *track) +{ + struct chunk_header_struct mtrk_header; + + memcpy(mtrk_header.id, "MTrk", 4); + + return (track_append(track, &mtrk_header, sizeof(mtrk_header))); +} + +/** + * Updates MTrk chunk length of a given track. + */ +static int +write_mtrk_length(smf_track_t *track) +{ + struct chunk_header_struct *mtrk_header; + + assert(track->file_buffer != NULL); + assert(track->file_buffer_length >= 6); + + mtrk_header = (struct chunk_header_struct *)track->file_buffer; + mtrk_header->length = htonl(track->file_buffer_length - sizeof(struct chunk_header_struct)); + + return (0); +} + +/** + * Writes out the track. + */ +static int +write_track(smf_track_t *track) +{ + int ret; + smf_event_t *event; + + ret = write_mtrk_header(track); + if (ret) + return (ret); + + while ((event = smf_track_get_next_event(track)) != NULL) { + ret = write_event(event); + if (ret) + return (ret); + } + + ret = write_mtrk_length(track); + if (ret) + return (ret); + + return (0); +} + +/** + * Takes smf->file_buffer and saves it to the file. + */ +static int +write_file(smf_t *smf, const char *file_name) +{ + FILE *stream; + + stream = fopen(file_name, "wb+"); + if (stream == NULL) { + fg_critical("Cannot open input file: %s", strerror(errno)); + + return (-1); + } + + if (fwrite(smf->file_buffer, 1, smf->file_buffer_length, stream) != smf->file_buffer_length) { + fg_critical("fwrite(3) failed: %s", strerror(errno)); + + return (-2); + } + + if (fclose(stream)) { + fg_critical("fclose(3) failed: %s", strerror(errno)); + + return (-3); + } + + return (0); +} + +static void +free_buffer(smf_t *smf) +{ + int i; + smf_track_t *track; + + /* Clear the pointers. */ + memset(smf->file_buffer, 0, smf->file_buffer_length); + free(smf->file_buffer); + smf->file_buffer = NULL; + smf->file_buffer_length = 0; + + for (i = 1; i <= smf->number_of_tracks; i++) { + track = smf_get_track_by_number(smf, i); + assert(track); + track->file_buffer = NULL; + track->file_buffer_length = 0; + } +} + +#ifndef NDEBUG + +/** + * \return Nonzero, if all pointers supposed to be NULL are NULL. Triggers assertion if not. + */ +static int +pointers_are_clear(smf_t *smf) +{ + int i; + + smf_track_t *track; + assert(smf->file_buffer == NULL); + assert(smf->file_buffer_length == 0); + + for (i = 1; i <= smf->number_of_tracks; i++) { + track = smf_get_track_by_number(smf, i); + + assert(track != NULL); + assert(track->file_buffer == NULL); + assert(track->file_buffer_length == 0); + } + + return (1); +} + +#endif /* !NDEBUG */ + +/** + * \return Nonzero, if event is End Of Track metaevent. + */ +int +smf_event_is_eot(const smf_event_t *event) +{ + if (event->midi_buffer_length != 3) + return (0); + + if (event->midi_buffer[0] != 0xFF || event->midi_buffer[1] != 0x2F || event->midi_buffer[2] != 0x00) + return (0); + + return (1); +} + +/** + * Check if SMF is valid and add missing EOT events. + * + * \return 0, if SMF is valid. + */ +static int +smf_validate(smf_t *smf) +{ + int trackno, eventno, eot_found; + smf_track_t *track; + smf_event_t *event; + + if (smf->format < 0 || smf->format > 2) { + fg_critical("SMF error: smf->format is less than zero of greater than two."); + return (-1); + } + + if (smf->number_of_tracks < 1) { + fg_critical("SMF error: number of tracks is less than one."); + return (-2); + } + + if (smf->format == 0 && smf->number_of_tracks > 1) { + fg_critical("SMF error: format is 0, but number of tracks is more than one."); + return (-3); + } + + if (smf->ppqn <= 0) { + fg_critical("SMF error: PPQN has to be > 0."); + return (-4); + } + + for (trackno = 1; trackno <= smf->number_of_tracks; trackno++) { + track = smf_get_track_by_number(smf, trackno); + assert(track); + + eot_found = 0; + + for (eventno = 1; eventno <= track->number_of_events; eventno++) { + event = smf_track_get_event_by_number(track, eventno); + assert(event); + + if (!smf_event_is_valid(event)) { + fg_critical("Event #%d on track #%d is invalid.", eventno, trackno); + return (-5); + } + + if (smf_event_is_eot(event)) { + if (eot_found) { + fg_critical("Duplicate End Of Track event on track #%d.", trackno); + return (-6); + } + + eot_found = 1; + } + } + + if (!eot_found) { + if (smf_track_add_eot_delta_pulses(track, 0)) { + fg_critical("smf_track_add_eot_delta_pulses failed."); + return (-6); + } + } + + } + + return (0); +} + +#ifndef NDEBUG + +static void +assert_smf_event_is_identical(const smf_event_t *a, const smf_event_t *b) +{ + assert(a->event_number == b->event_number); + assert(a->delta_time_pulses == b->delta_time_pulses); + assert(abs(a->time_pulses - b->time_pulses) <= 2); + assert(fabs(a->time_seconds - b->time_seconds) <= 0.01); + assert(a->track_number == b->track_number); + assert(a->midi_buffer_length == b->midi_buffer_length); + assert(memcmp(a->midi_buffer, b->midi_buffer, a->midi_buffer_length) == 0); +} + +static void +assert_smf_track_is_identical(const smf_track_t *a, const smf_track_t *b) +{ + int i; + + assert(a->track_number == b->track_number); + assert(a->number_of_events == b->number_of_events); + + for (i = 1; i <= a->number_of_events; i++) + assert_smf_event_is_identical(smf_track_get_event_by_number(a, i), smf_track_get_event_by_number(b, i)); +} + +static void +assert_smf_is_identical(const smf_t *a, const smf_t *b) +{ + int i; + + assert(a->format == b->format); + assert(a->ppqn == b->ppqn); + assert(a->frames_per_second == b->frames_per_second); + assert(a->resolution == b->resolution); + assert(a->number_of_tracks == b->number_of_tracks); + + for (i = 1; i <= a->number_of_tracks; i++) + assert_smf_track_is_identical(smf_get_track_by_number(a, i), smf_get_track_by_number(b, i)); + + /* We do not need to compare tempos explicitly, as tempo is always computed from track contents. */ +} + +static void +assert_smf_saved_correctly(const smf_t *smf, const char *file_name) +{ + smf_t *saved; + + saved = smf_load(file_name); + assert(saved != NULL); + + assert_smf_is_identical(smf, saved); + + smf_delete(saved); +} + +#endif /* !NDEBUG */ + +/** + * Writes the contents of SMF to the file given. + * \param smf SMF. + * \param file_name Path to the file. + * \return 0, if saving was successfull. + */ +int +smf_save(smf_t *smf, const char *file_name) +{ + int i, error; + smf_track_t *track; + + smf_rewind(smf); + + assert(pointers_are_clear(smf)); + + if (smf_validate(smf)) + return (-1); + + if (write_mthd_header(smf)) + return (-2); + + for (i = 1; i <= smf->number_of_tracks; i++) { + track = smf_get_track_by_number(smf, i); + + assert(track != NULL); + + error = write_track(track); + if (error) { + free_buffer(smf); + return (error); + } + } + + error = write_file(smf, file_name); + + free_buffer(smf); + + if (error) + return (error); + +#ifndef NDEBUG + assert_smf_saved_correctly(smf, file_name); +#endif + + return (0); +} + diff -r 000000000000 -r 4264abea8b06 src/smf_tempo.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/smf_tempo.c Thu Jan 26 11:25:11 2012 +0200 @@ -0,0 +1,448 @@ +/*- + * Copyright (c) 2007, 2008 Edward Tomasz Napierała + * 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 + * + * Tempo map related part. + * + */ + +#include +#include +#include +#include +#include "smf.h" +#include "smf_private.h" + +static double seconds_from_pulses(const smf_t *smf, int pulses); + +/** + * If there is tempo starting at "pulses" already, return it. Otherwise, + * allocate new one, fill it with values from previous one (or default ones, + * if there is no previous one) and attach it to "smf". + */ +static smf_tempo_t * +new_tempo(smf_t *smf, int pulses) +{ + smf_tempo_t *tempo, *previous_tempo = NULL; + + if (smf->tempo_array->len > 0) { + previous_tempo = smf_get_last_tempo(smf); + + /* If previous tempo starts at the same time as new one, reuse it, updating in place. */ + if (previous_tempo->time_pulses == pulses) + return (previous_tempo); + } + + tempo = malloc(sizeof(smf_tempo_t)); + if (tempo == NULL) { + fg_critical("Cannot allocate smf_tempo_t."); + return (NULL); + } + + tempo->time_pulses = pulses; + + if (previous_tempo != NULL) { + tempo->microseconds_per_quarter_note = previous_tempo->microseconds_per_quarter_note; + tempo->numerator = previous_tempo->numerator; + tempo->denominator = previous_tempo->denominator; + tempo->clocks_per_click = previous_tempo->clocks_per_click; + tempo->notes_per_note = previous_tempo->notes_per_note; + } else { + tempo->microseconds_per_quarter_note = 500000; /* Initial tempo is 120 BPM. */ + tempo->numerator = 4; + tempo->denominator = 4; + tempo->clocks_per_click = -1; + tempo->notes_per_note = -1; + } + + fg_ptr_array_add(smf->tempo_array, tempo); + + if (pulses == 0) + tempo->time_seconds = 0.0; + else + tempo->time_seconds = seconds_from_pulses(smf, pulses); + + return (tempo); +} + +static int +add_tempo(smf_t *smf, int pulses, int tempo) +{ + smf_tempo_t *smf_tempo = new_tempo(smf, pulses); + if (smf_tempo == NULL) + return (-1); + + smf_tempo->microseconds_per_quarter_note = tempo; + + return (0); +} + +static int +add_time_signature(smf_t *smf, int pulses, int numerator, int denominator, int clocks_per_click, int notes_per_note) +{ + smf_tempo_t *smf_tempo = new_tempo(smf, pulses); + if (smf_tempo == NULL) + return (-1); + + smf_tempo->numerator = numerator; + smf_tempo->denominator = denominator; + smf_tempo->clocks_per_click = clocks_per_click; + smf_tempo->notes_per_note = notes_per_note; + + return (0); +} + +/** + * \internal + */ +void +maybe_add_to_tempo_map(smf_event_t *event) +{ + if (!smf_event_is_metadata(event)) + return; + + assert(event->track != NULL); + assert(event->track->smf != NULL); + assert(event->midi_buffer_length >= 1); + + /* Tempo Change? */ + if (event->midi_buffer[1] == 0x51) { + int new_tempo = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5]; + if (new_tempo <= 0) { + fg_critical("Ignoring invalid tempo change."); + return; + } + + add_tempo(event->track->smf, event->time_pulses, new_tempo); + } + + /* Time Signature? */ + if (event->midi_buffer[1] == 0x58) { + int numerator, denominator, clocks_per_click, notes_per_note; + + if (event->midi_buffer_length < 7) { + fg_critical("Time Signature event seems truncated."); + return; + } + + numerator = event->midi_buffer[3]; + denominator = (int)pow(2, event->midi_buffer[4]); + clocks_per_click = event->midi_buffer[5]; + notes_per_note = event->midi_buffer[6]; + + add_time_signature(event->track->smf, event->time_pulses, numerator, denominator, clocks_per_click, notes_per_note); + } + + return; +} + +/** + * \internal + * + * This is an internal function, called from smf_track_remove_event when tempo-related + * event being removed does not require recreation of tempo map, i.e. there are no events + * after that one. + */ +void +remove_last_tempo_with_pulses(smf_t *smf, int pulses) +{ + smf_tempo_t *tempo; + + /* XXX: This is a partial workaround for the following problem: we have two tempo-related + events, A and B, that occur at the same time. We remove B, then try to remove + A. However, both tempo changes got coalesced in new_tempo(), so it is impossible + to remove B. */ + if (smf->tempo_array->len == 0) + return; + + tempo = smf_get_last_tempo(smf); + + /* Workaround part two. */ + if (tempo->time_pulses != pulses) + return; + + memset(tempo, 0, sizeof(smf_tempo_t)); + free(tempo); + + fg_ptr_array_remove_index(smf->tempo_array, smf->tempo_array->len - 1); +} + +static double +seconds_from_pulses(const smf_t *smf, int pulses) +{ + double seconds; + smf_tempo_t *tempo; + + tempo = smf_get_tempo_by_pulses(smf, pulses); + assert(tempo); + assert(tempo->time_pulses <= pulses); + + seconds = tempo->time_seconds + (double)(pulses - tempo->time_pulses) * + (tempo->microseconds_per_quarter_note / ((double)smf->ppqn * 1000000.0)); + + return (seconds); +} + +static int +pulses_from_seconds(const smf_t *smf, double seconds) +{ + int pulses = 0; + smf_tempo_t *tempo; + + tempo = smf_get_tempo_by_seconds(smf, seconds); + assert(tempo); + assert(tempo->time_seconds <= seconds); + + pulses = tempo->time_pulses + (seconds - tempo->time_seconds) * + ((double)smf->ppqn * 1000000.0 / tempo->microseconds_per_quarter_note); + + return (pulses); +} + +/** + * \internal + * + * Computes value of event->time_seconds for all events in smf. + * Warning: rewinds the smf. + */ +void +smf_create_tempo_map_and_compute_seconds(smf_t *smf) +{ + smf_event_t *event; + + smf_rewind(smf); + smf_init_tempo(smf); + + for (;;) { + event = smf_get_next_event(smf); + + if (event == NULL) + return; + + maybe_add_to_tempo_map(event); + + event->time_seconds = seconds_from_pulses(smf, event->time_pulses); + } + + /* Not reached. */ +} + +smf_tempo_t * +smf_get_tempo_by_number(const smf_t *smf, int number) +{ + assert(number >= 0); + + if (number >= smf->tempo_array->len) + return (NULL); + + return (fg_ptr_array_index(smf->tempo_array, number)); +} + +/** + * Return last tempo (i.e. tempo with greatest time_pulses) that happens before "pulses". + */ +smf_tempo_t * +smf_get_tempo_by_pulses(const smf_t *smf, int pulses) +{ + int i; + smf_tempo_t *tempo; + + assert(pulses >= 0); + + if (pulses == 0) + return (smf_get_tempo_by_number(smf, 0)); + + assert(smf->tempo_array != NULL); + + for (i = smf->tempo_array->len - 1; i >= 0; i--) { + tempo = smf_get_tempo_by_number(smf, i); + + assert(tempo); + if (tempo->time_pulses < pulses) + return (tempo); + } + + return (NULL); +} + +/** + * Return last tempo (i.e. tempo with greatest time_seconds) that happens before "seconds". + */ +smf_tempo_t * +smf_get_tempo_by_seconds(const smf_t *smf, double seconds) +{ + int i; + smf_tempo_t *tempo; + + assert(seconds >= 0.0); + + if (seconds == 0.0) + return (smf_get_tempo_by_number(smf, 0)); + + assert(smf->tempo_array != NULL); + + for (i = smf->tempo_array->len - 1; i >= 0; i--) { + tempo = smf_get_tempo_by_number(smf, i); + + assert(tempo); + if (tempo->time_seconds < seconds) + return (tempo); + } + + return (NULL); +} + + +/** + * Return last tempo. + */ +smf_tempo_t * +smf_get_last_tempo(const smf_t *smf) +{ + smf_tempo_t *tempo; + + tempo = smf_get_tempo_by_number(smf, smf->tempo_array->len - 1); + assert(tempo); + + return (tempo); +} + +/** + * \internal + * + * Remove all smf_tempo_t structures from SMF. + */ +void +smf_fini_tempo(smf_t *smf) +{ + smf_tempo_t *tempo; + + while (smf->tempo_array->len > 0) { + tempo = fg_ptr_array_index(smf->tempo_array, smf->tempo_array->len - 1); + assert(tempo); + + memset(tempo, 0, sizeof(smf_tempo_t)); + free(tempo); + + fg_ptr_array_remove_index(smf->tempo_array, smf->tempo_array->len - 1); + } + + assert(smf->tempo_array->len == 0); +} + +/** + * \internal + * + * Remove any existing tempos and add default one. + * + * \bug This will abort (by calling fg_error) if new_tempo() (memory allocation there) fails. + */ +void +smf_init_tempo(smf_t *smf) +{ + smf_tempo_t *tempo; + + smf_fini_tempo(smf); + + tempo = new_tempo(smf, 0); + if (tempo == NULL) + fg_error("tempo_init failed, sorry."); +} + +/** + * Returns ->time_pulses of last event on the given track, or 0, if track is empty. + */ +static int +last_event_pulses(const smf_track_t *track) +{ + /* Get time of last event on this track. */ + if (track->number_of_events > 0) { + smf_event_t *previous_event = smf_track_get_last_event(track); + assert(previous_event); + assert(previous_event->time_pulses >= 0); + + return (previous_event->time_pulses); + } + + return (0); +} + +/** + * Adds event to the track at the time "pulses" clocks from the previous event in this track. + * The remaining two time fields will be computed automatically based on the third argument + * and current tempo map. Note that ->delta_pulses is computed by smf.c:smf_track_add_event, + * not here. + */ +void +smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, int delta) +{ + assert(delta >= 0); + assert(event->time_pulses == -1); + assert(event->time_seconds == -1.0); + assert(track->smf != NULL); + + smf_track_add_event_pulses(track, event, last_event_pulses(track) + delta); +} + +/** + * Adds event to the track at the time "pulses" clocks from the start of song. + * The remaining two time fields will be computed automatically based on the third argument + * and current tempo map. + */ +void +smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, int pulses) +{ + assert(pulses >= 0); + assert(event->time_pulses == -1); + assert(event->time_seconds == -1.0); + assert(track->smf != NULL); + + event->time_pulses = pulses; + event->time_seconds = seconds_from_pulses(track->smf, pulses); + smf_track_add_event(track, event); +} + +/** + * Adds event to the track at the time "seconds" seconds from the start of song. + * The remaining two time fields will be computed automatically based on the third argument + * and current tempo map. + */ +void +smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds) +{ + assert(seconds >= 0.0); + assert(event->time_pulses == -1); + assert(event->time_seconds == -1.0); + assert(track->smf != NULL); + + event->time_seconds = seconds; + event->time_pulses = pulses_from_seconds(track->smf, seconds); + smf_track_add_event(track, event); +} +