nuclear@0: /*- nuclear@0: * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa nuclear@0: * All rights reserved. nuclear@0: * nuclear@0: * Redistribution and use in source and binary forms, with or without nuclear@0: * modification, are permitted provided that the following conditions nuclear@0: * are met: nuclear@0: * 1. Redistributions of source code must retain the above copyright nuclear@0: * notice, this list of conditions and the following disclaimer. nuclear@0: * 2. Redistributions in binary form must reproduce the above copyright nuclear@0: * notice, this list of conditions and the following disclaimer in the nuclear@0: * documentation and/or other materials provided with the distribution. nuclear@0: * nuclear@0: * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE nuclear@0: * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, nuclear@0: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY nuclear@0: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL nuclear@0: * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED nuclear@0: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, nuclear@0: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY nuclear@0: * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING nuclear@0: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS nuclear@0: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: * nuclear@0: */ nuclear@0: nuclear@0: /** nuclear@0: * \file nuclear@0: * nuclear@0: * Public interface declaration for libsmf, Standard MIDI File format library. nuclear@0: */ nuclear@0: nuclear@0: /** nuclear@0: * nuclear@0: * \mainpage libsmf - general usage instructions nuclear@0: * nuclear@0: * An smf_t structure represents a "song". Every valid smf contains one or more tracks. nuclear@0: * Tracks contain zero or more events. Libsmf doesn't care about actual MIDI data, as long nuclear@0: * as it is valid from the MIDI specification point of view - it may be realtime message, nuclear@0: * SysEx, whatever. nuclear@0: * nuclear@0: * The only field in smf_t, smf_track_t, smf_event_t and smf_tempo_t structures your nuclear@0: * code may modify is event->midi_buffer and event->midi_buffer_length. Do not modify nuclear@0: * other fields, _ever_. You may read them, though. Do not declare static instances nuclear@0: * of these types, i.e. never do something like this: "smf_t smf;". Always use nuclear@0: * "smf_t *smf = smf_new();". The same applies to smf_track_t and smf_event_t. nuclear@0: * nuclear@0: * Say you want to load a Standard MIDI File (.mid) file and play it back somehow. This is (roughly) nuclear@0: * how you do this: nuclear@0: * nuclear@0: * \code nuclear@0: * smf_t *smf; nuclear@0: * smf_event_t *event; nuclear@0: * nuclear@0: * smf = smf_load(file_name); nuclear@0: * if (smf == NULL) { nuclear@0: * Whoops, something went wrong. nuclear@0: * return; nuclear@0: * } nuclear@0: * nuclear@0: * while ((event = smf_get_next_event(smf)) != NULL) { nuclear@0: * if (smf_event_is_metadata(event)) nuclear@0: * continue; nuclear@0: * nuclear@0: * wait until event->time_seconds. nuclear@0: * feed_to_midi_output(event->midi_buffer, event->midi_buffer_length); nuclear@0: * } nuclear@0: * nuclear@0: * smf_delete(smf); nuclear@0: * nuclear@0: * \endcode nuclear@0: * nuclear@0: * Saving works like this: nuclear@0: * nuclear@0: * \code nuclear@0: * nuclear@0: * smf_t *smf; nuclear@0: * smf_track_t *track; nuclear@0: * smf_event_t *event; nuclear@0: * nuclear@0: * smf = smf_new(); nuclear@0: * if (smf == NULL) { nuclear@0: * Whoops. nuclear@0: * return; nuclear@0: * } nuclear@0: * nuclear@0: * for (int i = 1; i <= number of tracks; i++) { nuclear@0: * track = smf_track_new(); nuclear@0: * if (track == NULL) { nuclear@0: * Whoops. nuclear@0: * return; nuclear@0: * } nuclear@0: * nuclear@0: * smf_add_track(smf, track); nuclear@0: * nuclear@0: * for (int j = 1; j <= number of events you want to put into this track; j++) { nuclear@0: * event = smf_event_new_from_pointer(your MIDI message, message length); nuclear@0: * if (event == NULL) { nuclear@0: * Whoops. nuclear@0: * return; nuclear@0: * } nuclear@0: * nuclear@0: * smf_track_add_event_seconds(track, event, seconds since start of the song); nuclear@0: * } nuclear@0: * } nuclear@0: * nuclear@0: * ret = smf_save(smf, file_name); nuclear@0: * if (ret) { nuclear@0: * Whoops, saving failed for some reason. nuclear@0: * return; nuclear@0: * } nuclear@0: * nuclear@0: * smf_delete(smf); nuclear@0: * nuclear@0: * \endcode nuclear@0: * nuclear@0: * There are two basic ways of getting MIDI data out of smf - sequential or by track/event number. You may nuclear@0: * mix them if you need to. First one is used in the example above - seek to the point from which you want nuclear@0: * the playback to start (using smf_seek_to_seconds(), smf_seek_to_pulses() or smf_seek_to_event()) and then nuclear@0: * do smf_get_next_event() in loop, until it returns NULL. Calling smf_load() causes the smf to be rewound nuclear@0: * to the start of the song. nuclear@0: * nuclear@0: * Getting events by number works like this: nuclear@0: * nuclear@0: * \code nuclear@0: * nuclear@0: * smf_track_t *track = smf_get_track_by_number(smf, track_number); nuclear@0: * smf_event_t *event = smf_track_get_event_by_number(track, event_number); nuclear@0: * nuclear@0: * \endcode nuclear@0: * nuclear@0: * To create new event, use smf_event_new(), smf_event_new_from_pointer() or smf_event_new_from_bytes(). nuclear@0: * First one creates an empty event - you need to manually allocate (using malloc(3)) buffer for nuclear@0: * MIDI data, write MIDI data into it, put the address of that buffer into event->midi_buffer, nuclear@0: * and the length of MIDI data into event->midi_buffer_length. Note that deleting the event nuclear@0: * (using smf_event_delete()) will free the buffer. nuclear@0: * nuclear@0: * Second form does most of this for you: it takes an address of the buffer containing MIDI data, nuclear@0: * allocates storage and copies MIDI data into it. nuclear@0: * nuclear@0: * Third form is useful for manually creating short events, up to three bytes in length, for nuclear@0: * example Note On or Note Off events. It simply takes three bytes and creates MIDI event containing nuclear@0: * them. If you need to create MIDI message that takes only two bytes, pass -1 as the third byte. nuclear@0: * For one byte message (System Realtime), pass -1 as second and third byte. nuclear@0: * nuclear@0: * To free an event, use smf_event_delete(). nuclear@0: * nuclear@0: * To add event to the track, use smf_track_add_event_delta_pulses(), smf_track_add_event_pulses(), nuclear@0: * or smf_track_add_event_seconds(). The difference between them is in the way you specify the time of nuclear@0: * the event - with the first one, you specify it as an interval, in pulses, from the previous event nuclear@0: * in this track; with the second one, you specify it as pulses from the start of the song, and with the nuclear@0: * last one, you specify it as seconds from the start of the song. Obviously, the first version can nuclear@0: * only append events at the end of the track. nuclear@0: * nuclear@0: * To remove an event from the track it's attached to, use smf_event_remove_from_track(). You may nuclear@0: * want to free the event (using smf_event_delete()) afterwards. nuclear@0: * nuclear@0: * To create new track, use smf_track_new(). To add track to the smf, use smf_add_track(). nuclear@0: * To remove track from its smf, use smf_track_remove_from_smf(). To free the track structure, nuclear@0: * use smf_track_delete(). nuclear@0: * nuclear@0: * Note that libsmf keeps things consistent. If you free (using smf_track_delete()) a track that nuclear@0: * is attached to an smf and contains events, libsmf will detach the events, free them, detach nuclear@0: * the track, free it etc. nuclear@0: * nuclear@0: * Tracks and events are numbered consecutively, starting from one. If you remove a track or event, nuclear@0: * the rest of tracks/events will get renumbered. To get the number of a given event in its track, use event->event_number. nuclear@0: * To get the number of track in its smf, use track->track_number. To get the number of events in the track, nuclear@0: * use track->number_of_events. To get the number of tracks in the smf, use smf->number_of_tracks. nuclear@0: * nuclear@0: * In SMF File Format, each track has to end with End Of Track metaevent. If you load SMF file using smf_load(), nuclear@0: * that will be the case. If you want to create or edit an SMF, you don't need to worry about EOT events; nuclear@0: * libsmf automatically takes care of them for you. If you try to save an SMF with tracks that do not end nuclear@0: * with EOTs, smf_save() will append them. If you try to add event that happens after EOT metaevent, libsmf nuclear@0: * will remove the EOT. If you want to add EOT manually, you can, of course, using smf_track_add_eot_seconds() nuclear@0: * or smf_track_add_eot_pulses(). nuclear@0: * nuclear@0: * Each event carries three time values - event->time_seconds, which is seconds since the start of the song, nuclear@0: * event->time_pulses, which is PPQN clocks since the start of the song, and event->delta_pulses, which is PPQN clocks nuclear@0: * since the previous event in that track. These values are invalid if the event is not attached to the track. nuclear@0: * If event is attached, all three values are valid. Time of the event is specified when adding the event nuclear@0: * (using smf_track_add_event_seconds(), smf_track_add_event_pulses() or smf_track_add_event_delta_pulses()); the remaining nuclear@0: * two values are computed from that. nuclear@0: * nuclear@0: * Tempo related stuff happens automatically - when you add a metaevent that nuclear@0: * is Tempo Change or Time Signature, libsmf adds that event to the tempo map. If you remove nuclear@0: * Tempo Change event that is in the middle of the song, the rest of the events will have their nuclear@0: * event->time_seconds recomputed from event->time_pulses before smf_event_remove_from_track() function returns. nuclear@0: * Adding Tempo Change in the middle of the song works in a similar way. nuclear@0: * nuclear@0: * MIDI data (event->midi_buffer) is always kept in normalized form - it always begins with status byte nuclear@0: * (no running status), there are no System Realtime events embedded in them etc. Events like SysExes nuclear@0: * are in "on the wire" form, without embedded length that is used in SMF file format. Obviously nuclear@0: * libsmf "normalizes" MIDI data during loading and "denormalizes" (adding length to SysExes, escaping nuclear@0: * System Common and System Realtime messages etc) during writing. nuclear@0: * nuclear@0: * Note that you always have to first add the track to smf, and then add events to the track. nuclear@0: * Doing it the other way around will trip asserts. Also, try to add events at the end of the track and remove nuclear@0: * them from the end of the track, that's much more efficient. nuclear@0: * nuclear@0: * All the libsmf functions have prefix "smf_". First argument for routines whose names start with nuclear@0: * "smf_event_" is "smf_event_t *", for routines whose names start with "smf_track_" - "smf_track_t *", nuclear@0: * and for plain "smf_" - "smf_t *". The only exception are smf_whatever_new routines. nuclear@0: * Library does not use any global variables and is thread-safe, nuclear@0: * as long as you don't try to work on the same SMF (smf_t and its descendant tracks and events) from several nuclear@0: * threads at once without protecting it with mutex. Library depends on glib and nothing else. License is nuclear@0: * BSD, two clause, which basically means you can use it freely in your software, both Open Source (including nuclear@0: * GPL) and closed source. nuclear@0: * nuclear@0: */ nuclear@0: nuclear@0: #ifndef SMF_H nuclear@0: #define SMF_H nuclear@0: nuclear@0: #ifdef __cplusplus nuclear@0: extern "C" { nuclear@0: #endif nuclear@0: nuclear@0: #include nuclear@0: nuclear@0: #if defined(__GNUC__) && __GNUC__ >= 4 nuclear@0: #define WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) nuclear@0: #else nuclear@0: #define WARN_UNUSED_RESULT nuclear@0: #endif nuclear@0: nuclear@0: struct FakeGPtrArray; nuclear@0: nuclear@0: /** Represents a "song", that is, collection of one or more tracks. */ nuclear@0: struct smf_struct { nuclear@0: int format; nuclear@0: nuclear@0: /** These fields are extracted from "division" field of MThd header. Valid is _either_ ppqn or frames_per_second/resolution. */ nuclear@0: int ppqn; nuclear@0: int frames_per_second; nuclear@0: int resolution; nuclear@0: int number_of_tracks; nuclear@0: nuclear@0: /** These are private fields using only by loading and saving routines. */ nuclear@0: FILE *stream; nuclear@0: void *file_buffer; nuclear@0: int file_buffer_length; nuclear@0: int next_chunk_offset; nuclear@0: int expected_number_of_tracks; nuclear@0: nuclear@0: /** Private, used by smf.c. */ nuclear@0: struct FakeGPtrArray *tracks_array; nuclear@0: double last_seek_position; nuclear@0: nuclear@0: /** Private, used by smf_tempo.c. */ nuclear@0: /** Array of pointers to smf_tempo_struct. */ nuclear@0: struct FakeGPtrArray *tempo_array; nuclear@0: }; nuclear@0: nuclear@0: typedef struct smf_struct smf_t; nuclear@0: nuclear@0: /** Describes a single tempo or time signature change. */ nuclear@0: struct smf_tempo_struct { nuclear@0: int time_pulses; nuclear@0: double time_seconds; nuclear@0: int microseconds_per_quarter_note; nuclear@0: int numerator; nuclear@0: int denominator; nuclear@0: int clocks_per_click; nuclear@0: int notes_per_note; nuclear@0: }; nuclear@0: nuclear@0: typedef struct smf_tempo_struct smf_tempo_t; nuclear@0: nuclear@0: /** Represents a single track. */ nuclear@0: struct smf_track_struct { nuclear@0: smf_t *smf; nuclear@0: nuclear@0: int track_number; nuclear@0: int number_of_events; nuclear@0: nuclear@0: /** These are private fields using only by loading and saving routines. */ nuclear@0: void *file_buffer; nuclear@0: int file_buffer_length; nuclear@0: int last_status; /* Used for "running status". */ nuclear@0: nuclear@0: /** Private, used by smf.c. */ nuclear@0: /** Offset into buffer, used in parse_next_event(). */ nuclear@0: int next_event_offset; nuclear@0: int next_event_number; nuclear@0: nuclear@0: /** Absolute time of next event on events_queue. */ nuclear@0: int time_of_next_event; nuclear@0: struct FakeGPtrArray *events_array; nuclear@0: nuclear@0: /** API consumer is free to use this for whatever purpose. NULL in freshly allocated track. nuclear@0: Note that tracks might be deallocated not only explicitly, by calling smf_track_delete(), nuclear@0: but also implicitly, e.g. when calling smf_delete() with tracks still added to nuclear@0: the smf; there is no mechanism for libsmf to notify you about removal of the track. */ nuclear@0: void *user_pointer; nuclear@0: }; nuclear@0: nuclear@0: typedef struct smf_track_struct smf_track_t; nuclear@0: nuclear@0: /** Represents a single MIDI event or metaevent. */ nuclear@0: struct smf_event_struct { nuclear@0: /** Pointer to the track, or NULL if event is not attached. */ nuclear@0: smf_track_t *track; nuclear@0: nuclear@0: /** Number of this event in the track. Events are numbered consecutively, starting from one. */ nuclear@0: int event_number; nuclear@0: nuclear@0: /** Note that the time fields are invalid, if event is not attached to a track. */ nuclear@0: /** Time, in pulses, since the previous event on this track. */ nuclear@0: int delta_time_pulses; nuclear@0: nuclear@0: /** Time, in pulses, since the start of the song. */ nuclear@0: int time_pulses; nuclear@0: nuclear@0: /** Time, in seconds, since the start of the song. */ nuclear@0: double time_seconds; nuclear@0: nuclear@0: /** Tracks are numbered consecutively, starting from 1. */ nuclear@0: int track_number; nuclear@0: nuclear@0: /** Pointer to the buffer containing MIDI message. This is freed by smf_event_delete. */ nuclear@0: unsigned char *midi_buffer; nuclear@0: nuclear@0: /** Length of the MIDI message in the buffer, in bytes. */ nuclear@0: int midi_buffer_length; nuclear@0: nuclear@0: /** API consumer is free to use this for whatever purpose. NULL in freshly allocated event. nuclear@0: Note that events might be deallocated not only explicitly, by calling smf_event_delete(), nuclear@0: but also implicitly, e.g. when calling smf_track_delete() with events still added to nuclear@0: the track; there is no mechanism for libsmf to notify you about removal of the event. */ nuclear@0: void *user_pointer; nuclear@0: }; nuclear@0: nuclear@0: typedef struct smf_event_struct smf_event_t; nuclear@0: nuclear@0: /* Routines for manipulating smf_t. */ nuclear@0: smf_t *smf_new(void) WARN_UNUSED_RESULT; nuclear@0: void smf_delete(smf_t *smf); nuclear@0: nuclear@0: int smf_set_format(smf_t *smf, int format) WARN_UNUSED_RESULT; nuclear@0: int smf_set_ppqn(smf_t *smf, int format) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: char *smf_decode(const smf_t *smf) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: smf_track_t *smf_get_track_by_number(const smf_t *smf, int track_number) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: smf_event_t *smf_peek_next_event(smf_t *smf) WARN_UNUSED_RESULT; nuclear@0: smf_event_t *smf_get_next_event(smf_t *smf) WARN_UNUSED_RESULT; nuclear@0: void smf_skip_next_event(smf_t *smf); nuclear@0: nuclear@0: void smf_rewind(smf_t *smf); nuclear@0: int smf_seek_to_seconds(smf_t *smf, double seconds) WARN_UNUSED_RESULT; nuclear@0: int smf_seek_to_pulses(smf_t *smf, int pulses) WARN_UNUSED_RESULT; nuclear@0: int smf_seek_to_event(smf_t *smf, const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: int smf_get_length_pulses(const smf_t *smf) WARN_UNUSED_RESULT; nuclear@0: double smf_get_length_seconds(const smf_t *smf) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_last(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: void smf_add_track(smf_t *smf, smf_track_t *track); nuclear@0: void smf_track_remove_from_smf(smf_track_t *track); nuclear@0: nuclear@0: /* Routines for manipulating smf_track_t. */ nuclear@0: smf_track_t *smf_track_new(void) WARN_UNUSED_RESULT; nuclear@0: void smf_track_delete(smf_track_t *track); nuclear@0: nuclear@0: smf_event_t *smf_track_get_next_event(smf_track_t *track) WARN_UNUSED_RESULT; nuclear@0: smf_event_t *smf_track_get_event_by_number(const smf_track_t *track, int event_number) WARN_UNUSED_RESULT; nuclear@0: smf_event_t *smf_track_get_last_event(const smf_track_t *track) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: void smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, int pulses); nuclear@0: void smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, int pulses); nuclear@0: void smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds); nuclear@0: int smf_track_add_eot_delta_pulses(smf_track_t *track, int delta) WARN_UNUSED_RESULT; nuclear@0: int smf_track_add_eot_pulses(smf_track_t *track, int pulses) WARN_UNUSED_RESULT; nuclear@0: int smf_track_add_eot_seconds(smf_track_t *track, double seconds) WARN_UNUSED_RESULT; nuclear@0: void smf_event_remove_from_track(smf_event_t *event); nuclear@0: nuclear@0: /* Routines for manipulating smf_event_t. */ nuclear@0: smf_event_t *smf_event_new(void) WARN_UNUSED_RESULT; nuclear@0: smf_event_t *smf_event_new_from_pointer(void *midi_data, int len) WARN_UNUSED_RESULT; nuclear@0: smf_event_t *smf_event_new_from_bytes(int first_byte, int second_byte, int third_byte) WARN_UNUSED_RESULT; nuclear@0: smf_event_t *smf_event_new_textual(int type, const char *text); nuclear@0: void smf_event_delete(smf_event_t *event); nuclear@0: nuclear@0: int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_system_realtime(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_system_common(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_sysex(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_eot(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: int smf_event_is_textual(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: char *smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: char *smf_event_extract_text(const smf_event_t *event) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: /* Routines for loading SMF files. */ nuclear@0: smf_t *smf_load(const char *file_name) WARN_UNUSED_RESULT; nuclear@0: smf_t *smf_load_from_memory(const void *buffer, const int buffer_length) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: /* Routine for writing SMF files. */ nuclear@0: int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: /* Routines for manipulating smf_tempo_t. */ nuclear@0: smf_tempo_t *smf_get_tempo_by_pulses(const smf_t *smf, int pulses) WARN_UNUSED_RESULT; nuclear@0: smf_tempo_t *smf_get_tempo_by_seconds(const smf_t *smf, double seconds) WARN_UNUSED_RESULT; nuclear@0: smf_tempo_t *smf_get_tempo_by_number(const smf_t *smf, int number) WARN_UNUSED_RESULT; nuclear@0: smf_tempo_t *smf_get_last_tempo(const smf_t *smf) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: const char *smf_get_version(void) WARN_UNUSED_RESULT; nuclear@0: nuclear@0: #ifdef __cplusplus nuclear@0: } nuclear@0: #endif nuclear@0: nuclear@0: #endif /* SMF_H */ nuclear@0: