mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-10 16:46:35 +01:00
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:
parent
41afd1201d
commit
13bcd43423
13 changed files with 5374 additions and 14 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
24
libs/evoral/src/libsmf/COPYING
Normal file
24
libs/evoral/src/libsmf/COPYING
Normal 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.
|
||||
|
||||
4
libs/evoral/src/libsmf/README
Normal file
4
libs/evoral/src/libsmf/README
Normal 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
1112
libs/evoral/src/libsmf/smf.c
Normal file
File diff suppressed because it is too large
Load diff
404
libs/evoral/src/libsmf/smf.h
Normal file
404
libs/evoral/src/libsmf/smf.h
Normal 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 */
|
||||
|
||||
634
libs/evoral/src/libsmf/smf_decode.c
Normal file
634
libs/evoral/src/libsmf/smf_decode.c
Normal 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);
|
||||
}
|
||||
|
||||
922
libs/evoral/src/libsmf/smf_load.c
Normal file
922
libs/evoral/src/libsmf/smf_load.c
Normal 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);
|
||||
}
|
||||
|
||||
80
libs/evoral/src/libsmf/smf_private.h
Normal file
80
libs/evoral/src/libsmf/smf_private.h
Normal 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 */
|
||||
|
||||
656
libs/evoral/src/libsmf/smf_save.c
Normal file
656
libs/evoral/src/libsmf/smf_save.c
Normal 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);
|
||||
}
|
||||
|
||||
448
libs/evoral/src/libsmf/smf_tempo.c
Normal file
448
libs/evoral/src/libsmf/smf_tempo.c
Normal 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);
|
||||
}
|
||||
|
||||
1034
libs/evoral/src/libsmf/smfsh.c
Normal file
1034
libs/evoral/src/libsmf/smfsh.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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 = ''
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue