Include stripped down libsmf code internally.

git-svn-id: svn://localhost/ardour2/branches/3.0@4531 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2009-02-11 17:38:40 +00:00
parent 41afd1201d
commit 13bcd43423
13 changed files with 5374 additions and 14 deletions

View file

@ -20,10 +20,13 @@
#ifndef EVORAL_LIB_SMF_HPP
#define EVORAL_LIB_SMF_HPP
#include <cassert>
#include "evoral/StandardMIDIFile.hpp"
#include <smf.h>
#include <cassert>
struct smf_struct;
struct smf_track_struct;
typedef smf_struct smf_t;
typedef smf_track_struct smf_track_t;
namespace Evoral {
@ -36,13 +39,7 @@ template<typename Time>
class LibSMF : public StandardMIDIFile<Time> {
public:
LibSMF() : _last_ev_time(0), _smf(0), _smf_track(0), _empty(true) {};
virtual ~LibSMF() {
if (_smf) {
smf_delete(_smf);
_smf = 0;
_smf_track = 0;
}
}
virtual ~LibSMF();
void seek_to_start() const;
@ -69,7 +66,7 @@ protected:
private:
static const uint16_t _ppqn = 19200;
Time _last_ev_time; ///< last frame time written, relative to source start
Time _last_ev_time; ///< last frame time written, relative to source start
std::string _path;
smf_t* _smf;

View file

@ -1,13 +1,41 @@
#include "evoral/LibSMF.hpp"
#include "evoral/Event.hpp"
#include <cassert>
/* This file is part of Evoral.
* Copyright (C) 2008 Dave Robillard <http://drobilla.net>
* Copyright (C) 2000-2008 Paul Davis
* Author: Hans Baier
*
* Evoral is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <cassert>
#include <iostream>
#include "evoral/Event.hpp"
#include "evoral/LibSMF.hpp"
#include "libsmf/smf.h"
using namespace std;
namespace Evoral {
template<typename Time>
LibSMF<Time>::~LibSMF()
{
if (_smf) {
smf_delete(_smf);
_smf = 0;
_smf_track = 0;
}
}
/** Attempt to open the SMF file for reading and writing.
*

View file

@ -0,0 +1,24 @@
Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,4 @@
This is a stripped down version of libsmf 1.2 by Edward Tomasz Napiera
for internal use by Evoral. See COPYING for licensing information.
The complete version can be found at <http://libsmf.sf.net>.

1112
libs/evoral/src/libsmf/smf.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,404 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* \file
*
* 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 <stdio.h>
#include <glib.h>
#if defined(__GNUC__) && __GNUC__ >= 4
#define WARN_UNUSED_RESULT __attribute__ ((warn_unused_result))
#else
#define WARN_UNUSED_RESULT
#endif
/** 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. */
GPtrArray *tracks_array;
double last_seek_position;
/** Private, used by smf_tempo.c. */
/** Array of pointers to smf_tempo_struct. */
GPtrArray *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;
GPtrArray *events_array;
};
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;
};
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 */

View file

@ -0,0 +1,634 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* \file
*
* Event decoding routines.
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdint.h>
#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) {
g_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) {
g_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) {
g_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) {
g_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) {
g_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) {
g_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) {
g_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) {
g_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) {
g_critical("smf_event_decode_system_realtime: event length is not 1.");
return (NULL);
}
buf = malloc(BUFFER_SIZE);
if (buf == NULL) {
g_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) {
g_critical("smf_event_decode_sysex: truncated MIDI message.");
return (NULL);
}
buf = malloc(BUFFER_SIZE);
if (buf == NULL) {
g_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) {
g_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)) {
g_critical("smf_event_decode: incorrect MIDI message length.");
return (NULL);
}
buf = malloc(BUFFER_SIZE);
if (buf == NULL) {
g_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) {
g_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);
}

View file

@ -0,0 +1,922 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* \file
*
* Standard MIDI File format loader.
*
*/
/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <errno.h>
#include <ctype.h>
#include <arpa/inet.h>
#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) {
g_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])) {
g_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) {
g_critical("SMF error: malformed chunk; truncated file?");
return (NULL);
}
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) {
g_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")) {
g_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) {
g_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) {
g_critical("SMF error: bad MThd format field value: %d, valid values are 0-2, inclusive.", smf->format);
return (-1);
}
if (smf->format == 2) {
g_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) {
g_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) {
g_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) {
g_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) {
g_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) {
g_critical("SMF error: end of buffer in expected_sysex_length().");
return (-1);
}
extract_vlq(second_byte, buffer_length, &sysex_length, &len);
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) {
g_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:
g_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:
g_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) {
g_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) {
g_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) {
g_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) {
g_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)) {
g_critical("Escaped event is invalid.");
return (-1);
}
if (smf_event_is_system_realtime(event) || smf_event_is_system_common(event)) {
g_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)) {
g_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)) {
g_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) {
g_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) {
g_critical("End of buffer in make_string().");
len = buffer_length;
}
str = malloc(len + 1);
if (str == NULL) {
g_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) {
g_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) {
g_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")) {
g_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])) {
g_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)
return (-1);
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, "r");
if (stream == NULL) {
g_critical("Cannot open input file: %s", strerror(errno));
return (-1);
}
if (fseek(stream, 0, SEEK_END)) {
g_critical("fseek(3) failed: %s", strerror(errno));
return (-2);
}
*file_buffer_length = ftell(stream);
if (*file_buffer_length == -1) {
g_critical("ftell(3) failed: %s", strerror(errno));
return (-3);
}
if (fseek(stream, 0, SEEK_SET)) {
g_critical("fseek(3) failed: %s", strerror(errno));
return (-4);
}
*file_buffer = malloc(*file_buffer_length);
if (*file_buffer == NULL) {
g_critical("malloc(3) failed: %s", strerror(errno));
return (-5);
}
if (fread(*file_buffer, 1, *file_buffer_length, stream) != *file_buffer_length) {
g_critical("fread(3) failed: %s", strerror(errno));
return (-6);
}
if (fclose(stream)) {
g_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)) {
g_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) {
g_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);
}

View file

@ -0,0 +1,80 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef SMF_PRIVATE_H
#define SMF_PRIVATE_H
#include <stdint.h>
#include <sys/types.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 */

View file

@ -0,0 +1,656 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* \file
*
* Standard MIDI File writer.
*
*/
/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <errno.h>
#include <arpa/inet.h>
#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) {
g_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) {
g_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) {
g_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) {
g_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, "w+");
if (stream == NULL) {
g_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) {
g_critical("fwrite(3) failed: %s", strerror(errno));
return (-2);
}
if (fclose(stream)) {
g_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) {
g_critical("SMF error: smf->format is less than zero of greater than two.");
return (-1);
}
if (smf->number_of_tracks < 1) {
g_critical("SMF error: number of tracks is less than one.");
return (-2);
}
if (smf->format == 0 && smf->number_of_tracks > 1) {
g_critical("SMF error: format is 0, but number of tracks is more than one.");
return (-3);
}
if (smf->ppqn <= 0) {
g_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)) {
g_critical("Event #%d on track #%d is invalid.", eventno, trackno);
return (-5);
}
if (smf_event_is_eot(event)) {
if (eot_found) {
g_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)) {
g_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);
}

View file

@ -0,0 +1,448 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* \file
*
* Tempo map related part.
*
*/
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <string.h>
#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) {
g_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;
}
g_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) {
g_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) {
g_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);
g_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 (g_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 = g_ptr_array_index(smf->tempo_array, smf->tempo_array->len - 1);
assert(tempo);
memset(tempo, 0, sizeof(smf_tempo_t));
free(tempo);
g_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 g_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)
g_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);
}

File diff suppressed because it is too large Load diff

View file

@ -26,10 +26,10 @@ def set_options(opt):
def configure(conf):
autowaf.configure(conf)
autowaf.check_tool(conf, 'compiler_cxx')
autowaf.check_pkg(conf, 'glib-2.0', uselib_store='GLIB', atleast_version='2.2', mandatory=True)
autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', atleast_version='2.14.0', mandatory=True)
autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', atleast_version='2.14.0', mandatory=True)
autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False)
autowaf.check_pkg(conf, 'smf', uselib_store='SMF', atleast_version='1.2', mandatory=False)
def build(bld):
# Headers
@ -39,6 +39,22 @@ def build(bld):
# Pkgconfig file
#autowaf.build_pc(bld, 'EVORAL', EVORAL_VERSION, 'GLIBMM GTHREAD')
libsmf = bld.new_task_gen('cc', 'shlib')
libsmf.source = '''
src/libsmf/smf.c
src/libsmf/smf_decode.c
src/libsmf/smf_load.c
src/libsmf/smf_save.c
src/libsmf/smf_tempo.c
'''
libsmf.export_incdirs = ['./src/libsmf']
libsmf.defines = 'SMF_VERSION=\\\"1.2\\\"'
libsmf.includes = ['./src']
libsmf.name = 'libsmf'
libsmf.target = 'smf'
libsmf.uselib = 'GLIB'
libsmf.install_path = ''
# Library
obj = bld.new_task_gen('cxx', 'shlib')
obj.source = '''
@ -59,6 +75,7 @@ def build(bld):
obj.name = 'libevoral'
obj.target = 'evoral'
obj.uselib = 'GLIBMM GTHREAD SMF'
obj.uselib_local = 'libsmf'
obj.vnum = EVORAL_LIB_VERSION
obj.install_path = ''