mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-30 07:39:42 +02:00
456 lines
13 KiB
C++
456 lines
13 KiB
C++
// midifile reader
|
|
|
|
#include "stdlib.h"
|
|
#include "stdio.h"
|
|
#include "string.h"
|
|
#include "assert.h"
|
|
#include <string>
|
|
#include <fstream>
|
|
#include "allegro.h"
|
|
#include "algsmfrd_internal.h"
|
|
#include "mfmidi.h"
|
|
#include "trace.h"
|
|
|
|
using namespace std;
|
|
|
|
typedef class Alg_note_list {
|
|
public:
|
|
Alg_note_ptr note;
|
|
class Alg_note_list *next;
|
|
Alg_note_list(Alg_note_ptr n, class Alg_note_list *list) {
|
|
note = n; next = list; }
|
|
} *Alg_note_list_ptr;
|
|
|
|
|
|
class Alg_midifile_reader: public Midifile_reader {
|
|
public:
|
|
istream *file;
|
|
Alg_seq_ptr seq;
|
|
int divisions;
|
|
Alg_note_list_ptr note_list;
|
|
Alg_track_ptr track;
|
|
int track_number; // the number of the (current) track
|
|
// chan is actual_channel + channel_offset_per_track * track_num +
|
|
// channel_offset_per_track * port
|
|
long channel_offset_per_track; // used to encode track number into channel
|
|
// default is 0, set this to 0 to merge all tracks to 16 channels
|
|
long channel_offset_per_port; // used to encode port number into channel
|
|
// default is 16, set to 0 to ignore port prefix meta events
|
|
// while reading, this is channel_offset_per_track * track_num
|
|
int channel_offset;
|
|
|
|
Alg_midifile_reader(istream &f, Alg_seq_ptr new_seq) {
|
|
file = &f;
|
|
note_list = NULL;
|
|
seq = new_seq;
|
|
channel_offset_per_track = 0;
|
|
channel_offset_per_port = 16;
|
|
track_number = -1; // no tracks started yet, 1st will be #0
|
|
meta_channel = -1;
|
|
port = 0;
|
|
}
|
|
// delete destroys the seq member as well, so set it to NULL if you
|
|
// copied the pointer elsewhere
|
|
~Alg_midifile_reader();
|
|
// the following is used to load the Alg_seq from the file:
|
|
bool parse();
|
|
|
|
void set_nomerge(bool flag) { Mf_nomerge = flag; }
|
|
void set_skipinit(bool flag) { Mf_skipinit = flag; }
|
|
long get_currtime() { return Mf_currtime; }
|
|
|
|
protected:
|
|
int meta_channel; // the channel for meta events, set by MIDI chan prefix
|
|
int port; // value from the portprefix meta event
|
|
|
|
double get_time();
|
|
void update(int chan, int key, Alg_parameter_ptr param);
|
|
void *Mf_malloc(size_t size) { return malloc(size); }
|
|
void Mf_free(void *obj, size_t size) { free(obj); }
|
|
/* Methods to be called while processing the MIDI file. */
|
|
void Mf_starttrack();
|
|
void Mf_endtrack();
|
|
int Mf_getc();
|
|
void Mf_chanprefix(int chan);
|
|
void Mf_portprefix(int port);
|
|
void Mf_eot();
|
|
void Mf_error(const char *);
|
|
void Mf_header(int,int,int);
|
|
void Mf_on(int,int,int);
|
|
void Mf_off(int,int,int);
|
|
void Mf_pressure(int,int,int);
|
|
void Mf_controller(int,int,int);
|
|
void Mf_pitchbend(int,int,int);
|
|
void Mf_program(int,int);
|
|
void Mf_chanpressure(int,int);
|
|
void binary_msg(int len, unsigned char *msg, const char *attr_string);
|
|
void Mf_sysex(int,unsigned char*);
|
|
void Mf_arbitrary(int,unsigned char*);
|
|
void Mf_metamisc(int,int,unsigned char*);
|
|
void Mf_seqnum(int);
|
|
void Mf_smpte(int,int,int,int,int);
|
|
void Mf_timesig(int,int,int,int);
|
|
void Mf_tempo(int);
|
|
void Mf_keysig(int,int);
|
|
void Mf_sqspecific(int,unsigned char*);
|
|
void Mf_text(int,int,unsigned char*);
|
|
};
|
|
|
|
|
|
Alg_midifile_reader::~Alg_midifile_reader()
|
|
{
|
|
while (note_list) {
|
|
Alg_note_list_ptr to_be_freed = note_list;
|
|
note_list = note_list->next;
|
|
delete to_be_freed;
|
|
}
|
|
finalize(); // free Mf reader memory
|
|
}
|
|
|
|
|
|
bool Alg_midifile_reader::parse()
|
|
{
|
|
channel_offset = 0;
|
|
seq->convert_to_beats();
|
|
midifile();
|
|
seq->set_real_dur(seq->get_time_map()->beat_to_time(seq->get_beat_dur()));
|
|
return midifile_error != 0;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_starttrack()
|
|
{
|
|
// printf("starting new track\n");
|
|
// create a new track that will share the sequence time map
|
|
// since time is in beats, the seconds parameter is false
|
|
track_number++;
|
|
seq->add_track(track_number); // make sure track exists
|
|
track = seq->track(track_number); // keep pointer to current track
|
|
meta_channel = -1;
|
|
port = 0;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_endtrack()
|
|
{
|
|
// note: track is already part of seq, so do not add it here
|
|
// printf("finished track, length %d number %d\n", track->len, track_num / 100);
|
|
channel_offset += seq->channel_offset_per_track;
|
|
track = NULL;
|
|
double now = get_time();
|
|
if (seq->get_beat_dur() < now) seq->set_beat_dur(now);
|
|
meta_channel = -1;
|
|
port = 0;
|
|
}
|
|
|
|
|
|
int Alg_midifile_reader::Mf_getc()
|
|
{
|
|
return file->get();
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_chanprefix(int chan)
|
|
{
|
|
meta_channel = chan;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_portprefix(int p)
|
|
{
|
|
port = p;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_eot()
|
|
{
|
|
meta_channel = -1;
|
|
port = 0;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_error(const char *msg)
|
|
{
|
|
fprintf(stdout, "Midifile reader error: %s\n", msg);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_header(int format, int ntrks, int division)
|
|
{
|
|
if (format > 1) {
|
|
char msg[80];
|
|
#pragma warning(disable: 4996) // msg is long enough
|
|
sprintf(msg, "file format %d not implemented", format);
|
|
#pragma warning(default: 4996)
|
|
Mf_error(msg);
|
|
}
|
|
divisions = division;
|
|
}
|
|
|
|
|
|
double Alg_midifile_reader::get_time()
|
|
{
|
|
double beat = ((double) get_currtime()) / divisions;
|
|
return beat;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_on(int chan, int key, int vel)
|
|
{
|
|
assert(!seq->get_units_are_seconds());
|
|
if (vel == 0) {
|
|
Mf_off(chan, key, vel);
|
|
return;
|
|
}
|
|
Alg_note_ptr note = new Alg_note();
|
|
note_list = new Alg_note_list(note, note_list);
|
|
/* trace("on: %d at %g\n", key, get_time()); */
|
|
note->time = get_time();
|
|
note->chan = chan + channel_offset + port * channel_offset_per_port;
|
|
note->dur = 0;
|
|
note->set_identifier(key);
|
|
note->pitch = (float) key;
|
|
note->loud = (float) vel;
|
|
track->append(note);
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_off(int chan, int key, int vel)
|
|
{
|
|
double time = get_time();
|
|
Alg_note_list_ptr *p = ¬e_list;
|
|
while (*p) {
|
|
if ((*p)->note->get_identifier() == key &&
|
|
(*p)->note->chan ==
|
|
chan + channel_offset + port * channel_offset_per_port) {
|
|
(*p)->note->dur = time - (*p)->note->time;
|
|
// trace("updated %d dur %g\n", (*p)->note->key, (*p)->note->dur);
|
|
Alg_note_list_ptr to_be_freed = *p;
|
|
*p = to_be_freed->next;
|
|
delete to_be_freed;
|
|
} else {
|
|
p = &((*p)->next);
|
|
}
|
|
}
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::update(int chan, int key, Alg_parameter_ptr param)
|
|
{
|
|
Alg_update_ptr update = new Alg_update;
|
|
update->time = get_time();
|
|
update->chan = chan;
|
|
if (chan != -1) {
|
|
update->chan = chan + channel_offset + port * channel_offset_per_port;
|
|
}
|
|
update->set_identifier(key);
|
|
update->parameter = *param;
|
|
// prevent the destructor from destroying the string twice!
|
|
// the new Update takes the string from param
|
|
if (param->attr_type() == 's') param->s = NULL;
|
|
track->append(update);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_pressure(int chan, int key, int val)
|
|
{
|
|
Alg_parameter parameter;
|
|
parameter.set_attr(symbol_table.insert_string("pressurer"));
|
|
parameter.r = val / 127.0;
|
|
update(chan, key, ¶meter);
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_controller(int chan, int control, int val)
|
|
{
|
|
Alg_parameter parameter;
|
|
char name[32];
|
|
#pragma warning(disable: 4996) // name is long enough
|
|
sprintf(name, "control%dr", control);
|
|
#pragma warning(default: 4996)
|
|
parameter.set_attr(symbol_table.insert_string(name));
|
|
parameter.r = val / 127.0;
|
|
update(chan, -1, ¶meter);
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_pitchbend(int chan, int c1, int c2)
|
|
{
|
|
Alg_parameter parameter;
|
|
parameter.set_attr(symbol_table.insert_string("bendr"));
|
|
parameter.r = ((c2 << 7) + c1) / 8192.0 - 1.0;
|
|
update(chan, -1, ¶meter);
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_program(int chan, int program)
|
|
{
|
|
Alg_parameter parameter;
|
|
parameter.set_attr(symbol_table.insert_string("programi"));
|
|
parameter.i = program;
|
|
update(chan, -1, ¶meter);
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_chanpressure(int chan, int val)
|
|
{
|
|
Alg_parameter parameter;
|
|
parameter.set_attr(symbol_table.insert_string("pressurer"));
|
|
parameter.r = val / 127.0;
|
|
update(chan, -1, ¶meter);
|
|
meta_channel = -1;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::binary_msg(int len, unsigned char *msg,
|
|
const char *attr_string)
|
|
{
|
|
Alg_parameter parameter;
|
|
char *hexstr = new char[len * 2 + 1];
|
|
for (int i = 0; i < len; i++) {
|
|
#pragma warning(disable: 4996) // hexstr is long enough
|
|
sprintf(hexstr + 2 * i, "%02x", (0xFF & msg[i]));
|
|
#pragma warning(default: 4996)
|
|
}
|
|
parameter.s = hexstr;
|
|
parameter.set_attr(symbol_table.insert_string(attr_string));
|
|
update(meta_channel, -1, ¶meter);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_sysex(int len, unsigned char *msg)
|
|
{
|
|
// sysex messages become updates with attribute sysexs and a hex string
|
|
binary_msg(len, msg, "sysexs");
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_arbitrary(int len, unsigned char *msg)
|
|
{
|
|
Mf_error("arbitrary data ignored");
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_metamisc(int type, int len, unsigned char *msg)
|
|
{
|
|
char text[128];
|
|
#pragma warning(disable: 4996) // text is long enough
|
|
sprintf(text, "metamsic data, type 0x%x, ignored", type);
|
|
#pragma warning(default: 4996)
|
|
Mf_error(text);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_seqnum(int n)
|
|
{
|
|
Mf_error("seqnum data ignored");
|
|
}
|
|
|
|
|
|
static const char *fpsstr[4] = {"24", "25", "29.97", "30"};
|
|
|
|
void Alg_midifile_reader::Mf_smpte(int hours, int mins, int secs,
|
|
int frames, int subframes)
|
|
{
|
|
// string will look like "24fps:01h:27m:07s:19.00f"
|
|
// 30fps (drop frame) is notated as "29.97fps"
|
|
char text[32];
|
|
int fps = (hours >> 6) & 3;
|
|
hours &= 0x1F;
|
|
#pragma warning(disable: 4996) // text is long enough
|
|
sprintf(text, "%sfps:%02dh:%02dm:%02ds:%02d.%02df",
|
|
fpsstr[fps], hours, mins, secs, frames, subframes);
|
|
#pragma warning(default: 4996)
|
|
Alg_parameter smpteoffset;
|
|
smpteoffset.s = heapify(text);
|
|
smpteoffset.set_attr(symbol_table.insert_string("smpteoffsets"));
|
|
update(meta_channel, -1, &smpteoffset);
|
|
// Mf_error("SMPTE data ignored");
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_timesig(int i1, int i2, int i3, int i4)
|
|
{
|
|
seq->set_time_sig(double(get_currtime()) / divisions, i1, 1 << i2);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_tempo(int tempo)
|
|
{
|
|
double beat = get_currtime();
|
|
beat = beat / divisions; // convert to quarters
|
|
// 6000000 us/min / n us/beat => beat / min
|
|
double bpm = 60000000.0 / tempo;
|
|
seq->insert_tempo(bpm, beat);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_keysig(int key, int mode)
|
|
{
|
|
Alg_parameter key_parm;
|
|
key_parm.set_attr(symbol_table.insert_string("keysigi"));
|
|
// use 0 for C major, 1 for G, -1 for F, etc., that is,
|
|
// the number of sharps, where flats are negative sharps
|
|
key_parm.i = key; //<<<---- fix this
|
|
// use -1 to mean "all channels"
|
|
update(meta_channel, -1, &key_parm);
|
|
Alg_parameter mode_parm;
|
|
mode_parm.set_attr(symbol_table.insert_string("modea"));
|
|
mode_parm.a = (mode == 0 ? symbol_table.insert_string("major") :
|
|
symbol_table.insert_string("minor"));
|
|
update(meta_channel, -1, &mode_parm);
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_sqspecific(int len, unsigned char *msg)
|
|
{
|
|
// sequencer specific messages become updates with attribute sqspecifics
|
|
// and a hex string for the value
|
|
binary_msg(len, msg, "sqspecifics");
|
|
}
|
|
|
|
|
|
char *heapify2(int len, unsigned char *s)
|
|
{
|
|
char *h = new char[len + 1];
|
|
memcpy(h, s, len);
|
|
h[len] = 0;
|
|
return h;
|
|
}
|
|
|
|
|
|
void Alg_midifile_reader::Mf_text(int type, int len, unsigned char *msg)
|
|
{
|
|
Alg_parameter text;
|
|
text.s = heapify2(len, msg);
|
|
const char *attr = "miscs";
|
|
if (type == 1) attr = "texts";
|
|
else if (type == 2) attr = "copyrights";
|
|
else if (type == 3)
|
|
attr = (track_number == 0 ? "seqnames" : "tracknames");
|
|
else if (type == 4) attr = "instruments";
|
|
else if (type == 5) attr = "lyrics";
|
|
else if (type == 6) attr = "markers";
|
|
else if (type == 7) attr = "cues";
|
|
text.set_attr(symbol_table.insert_string(attr));
|
|
update(meta_channel, -1, &text);
|
|
}
|
|
|
|
|
|
// parse file into a seq.
|
|
Alg_error alg_smf_read(istream &file, Alg_seq_ptr new_seq)
|
|
{
|
|
assert(new_seq);
|
|
Alg_midifile_reader ar(file, new_seq);
|
|
bool err = ar.parse();
|
|
ar.seq->set_real_dur(ar.seq->get_time_map()->
|
|
beat_to_time(ar.seq->get_beat_dur()));
|
|
return (err ? alg_error_syntax : alg_no_error);
|
|
}
|