code:
// Portsmf (also known as Allegro):
// music representation system, with
//      extensible in-memory sequence structure
//      upward compatible with MIDI
//      implementations in C++ and Serpent
//      external, text-based representation
//      compatible with Aura
//
// SERIALBUFFER CLASS
//
// The Serial_buffer class is defined to support serialization and
// unserialization. A Serial_buffer is just a block of memory with
// a length and a read/write pointer. When writing, it can expand.
//
// SERIALIZATION
//
// The Alg_track class has static members:
//    ser_buf -- a Serial_buffer
// When objects are serialized, they are first written to
// ser_buf, which is expanded whenever necessary. Then, when
// the length is known, new memory is allocated and the data
// is copied to a correctly-sized buffer and returned to caller.
// The "external" (callable from outside the library)
// serialization functions are:
//    Alg_track::serialize()
//    Alg_seq::serialize()
// The "internal" serialization functions to be called from within
// the library are:
//    Alg_track::serialize_track(bool text)
//    Alg_seq::serialize_seq(bool text)
//    Alg_track::serialize_parameter(
//            Alg_parameter *parm, bool text)
// These internal serialize functions append data to ser_buf The text
// flag says to write an ascii representation as opposed to binary.
//
// UNSERIALIZATION:
//
// The Alg_track class has a static member:
//     unserialize(char *buffer, long len)
// that will unserialize anything -- an Alg_track or an Alg_seq.
// No other function should be called from outside the library.
// Internal unserialize functions are:
//     Alg_seq::unserialize_seq()
//     Alg_track::unserialize_track()
//     Alg_track::unserialize_parameter(Alg_parameter_ptr parm_ptr)
// Just as serialization uses ser_buf for output, unserialization uses
// unser_buf for reading. unser_buf is another static member of Alg_track.

#ifndef ALLEGRO_H
#define ALLEGRO_H
#include <assert.h>
#include <istream>
#include <ostream>

#define ALG_EPS 0.000001 // epsilon
#define ALG_DEFAULT_BPM 100.0 // default tempo

// are d1 and d2 within epsilon of each other?
bool within(double d1, double d2, double epsilon);

char *heapify(const char *s); // put a string on the heap

// Alg_attribute is an atom in the symbol table
// with the special addition that the last
// character is prefixed to the string; thus,
// the attribute 'tempor' (a real) is stored
// as 'rtempor'. To get the string name, just
// use attribute+1.
typedef const char *Alg_attribute;
#define alg_attr_name(a) ((a) + 1)
#define alg_attr_type(a) (*(a))

// Alg_atoms is a symbol table of Alg_attributes and other
// unique strings
class Alg_atoms {
public:
    Alg_atoms() {
        maxlen = len = 0;
        atoms = NULL;
    }
    // Note: the code is possibly more correct and faster without the
    // following destructor, which will only run after the program takes
    // a normal exit. Cleaning up after the program exit slows down the exit,
    // and will cause problems if any other destructor tries to reference an
    // Alg_atom (which will now be freed). The advantage of this code is
    // that Alg_atoms will not be reported as memory leaks by automation
    // that doesn't know better. -RBD
    virtual ~Alg_atoms() {
        for (int i = 0; i < len; i++) {
            delete atoms[i];
        }
        if (atoms) delete [] atoms;
    }
    // insert/lookup an atttribute
    Alg_attribute insert_attribute(Alg_attribute attr);
    // insert/lookup attribute by name (without prefixed type)
    Alg_attribute insert_string(const char *name);
private:
    long maxlen;
    long len;
    Alg_attribute *atoms;

    // insert an Attriubute not in table after moving attr to heap
    Alg_attribute insert_new(const char *name, char attr_type);
    void expand(); // make more space
};

extern Alg_atoms symbol_table;

// an attribute/value pair. Since Alg_attribute names imply type,
// we try to keep attributes and values packaged together as
// Alg_parameter class
typedef class Alg_parameter {
public:
    // This constructor guarantees that an Alg_parameter can be
    // deleted safely without further initialization. It does not
    // do anything useful, so it is expected that the creator will
    // set attr and store a value in the appropriate union field.
    Alg_attribute attr;
    union {
        double r;// real
        const char *s; // string
        long i;  // integer
        bool l;  // logical
        const char *a; // symbol (atom)
    }; // anonymous union

    Alg_parameter() { attr = "i"; }
    ~Alg_parameter();
    void copy(Alg_parameter *); // copy from another parameter
    const char attr_type() { return alg_attr_type(attr); }
    const char *attr_name() { return alg_attr_name(attr); }
    void set_attr(Alg_attribute a) { attr = a; }
    void show();
} *Alg_parameter_ptr;

// a list of attribute/value pairs
typedef class Alg_parameters {
public:
    class Alg_parameters *next;
    Alg_parameter parm;

    Alg_parameters(Alg_parameters *list) {
        next = list;
    }

    //~Alg_parameters() { }

    // each of these routines takes address of pointer to the list
    // insertion is performed without checking whether or not a
    // parameter already exists with this attribute. See find() and
    // remove_key() to assist in checking for and removing existing
    // parameters.
    // Note also that these insert_* methods convert name to an
    // attribute. If you have already done the symbol table lookup/insert
    // you can do these operations faster (in which case we should add
    // another set of functions that take attributes as arguments.)
    static void insert_real(Alg_parameters **list, const char *name, double r);
    // insert string will copy string to heap
    static void insert_string(Alg_parameters **list, const char *name,
                              const char *s);
    static void insert_integer(Alg_parameters **list, const char *name, long i);
    static void insert_logical(Alg_parameters **list, const char *name, bool l);
    static void insert_atom(Alg_parameters **list, const char *name,
                            const char *s);
    static Alg_parameters *remove_key(Alg_parameters **list, const char *name);
    // find an attribute/value pair
    Alg_parameter_ptr find(Alg_attribute attr);
} *Alg_parameters_ptr;

// these are type codes associated with certain attributes
// see Alg_track::find() where these are bit positions in event_type_mask
#define ALG_NOTE 0 // This is a note, not an update
#define ALG_GATE 1 // "gate"
#define ALG_BEND 2 // "bend"
#define ALG_CONTROL 3 // "control"
#define ALG_PROGRAM 4 // "program"
#define ALG_PRESSURE 5 // "pressure"
#define ALG_KEYSIG 6 // "keysig"
#define ALG_TIMESIG_NUM 7 // "timesig_num"
#define ALG_TIMESIG_DEN 8 // "timesig_den"
#define ALG_OTHER 9 // any other value

// abstract superclass of Alg_note and Alg_update:
typedef class Alg_event {
protected:
    bool selected;
    char type; // 'e' event, 'n' note, 'u' update
    long key; // note identifier
    static const char* description; // static buffer for debugging (in Alg_event)
public:
    double time;
    long chan;
    virtual void show() = 0;
    // Note: there is no Alg_event() because Alg_event is an abstract class.
    bool is_note() { return (type == 'n'); }   // tell whether an Alg_event is a note
    bool is_update() { return (type == 'u'); } // tell whether an Alg_event is a parameter update
    char get_type() { return type; }   // return 'n' for note, 'u' for update
    int get_type_code();  // 1 = volume change,      2 = pitch bend,
                          // 3 = control change,     4 = program change,
                          // 5 = pressure change,    6 = key signature,
                          // 7 = time sig numerator, 8 = time sig denominator
    bool get_selected() { return selected; }
    void set_selected(bool b) { selected = b; }
    // Note: notes are identified by a (channel, identifier) pair.
    // For midi, the identifier is the key number (pitch). The identifier
    // does not have to represent pitch; it's main purpose is to identify
    // notes so that they can be named by subsequent update events.
    long get_identifier() { return key; } // get MIDI key or note identifier of note or update
    void set_identifier(long i) { key = i; } // set the identifier
    // In all of these set_ methods, strings are owned by the caller and
    // copied as necessary by the callee. For notes, an attribute/value
    // pair is added to the parameters list. For updates, the single
    // attribute/value parameter pair is overwritten. In all cases, the
    // attribute (first argument) must agree in type with the second arg.
    // The last letter of the attribute implies the type (see below).
    void set_parameter(Alg_parameter_ptr new_parameter);
    void set_string_value(const char *attr, const char *value);
    void set_real_value(const char *attr, double value);
    void set_logical_value(const char *attr, bool value);
    void set_integer_value(const char *attr, long value);
    void set_atom_value(const char *attr, const char *atom);

    // Some note methods. These fail (via assert()) if this is not a note:
    //
    float get_pitch();// get pitch in steps -- use this even for MIDI
    float get_loud(); // get loudness (MIDI velocity)
    // times are in seconds or beats, depending upon the units_are_seconds
    // flag in the containing sequence
    double get_start_time(); // get start time in seconds or beats
    double get_end_time();   // get end time in seconds or beats
    double get_duration();   // get duration in seconds or beats
    void set_pitch(float);
    void set_loud(float);
    void set_duration(double);

    // Notes have lists of attribute values. Attributes are converted
    // to/from strings in this API to avoid explicit use of Alg_attribute
    // types. Attribute names end with a type designation: 's', 'r', 'l',
    // 'i', or 'a'.
    //
    bool has_attribute(const char *attr);      // test if note has attribute/value pair
    char get_attribute_type(const char *attr); // get the associated type:
        // 's' = string,
        // 'r' = real (double), 'l' = logical (bool), 'i' = integer (long),
        // 'a' = atom (char *), a unique string stored in Alg_seq
    // get the string value
    const char *get_string_value(const char *attr, const char *value = NULL);
    // get the real value
    double get_real_value(const char *attr, double value = 0.0);
    // get the logical value
    bool get_logical_value(const char *attr, bool value = false);
    // get the integer value
    long get_integer_value(const char *attr, long value = 0);
    // get the atom value
    const char *get_atom_value(const char *attr, const char *value = NULL);
    void delete_attribute(const char *attr);   // delete an attribute/value pair
        // (ignore if no matching attribute/value pair exists)

    // Some attribute/value methods. These fail if this is not an update.
    // Attributes are converted to/from strings to avoid explicit use
    // of Alg_attribute types.
    //
    const char *get_attribute();    // get the update's attribute (string)
    char get_update_type();   // get the update's type: 's' = string,
        // 'r' = real (double), 'l' = logical (bool), 'i' = integer (long),
        // 'a' = atom (char *), a unique string stored in Alg_seq
    const char *get_string_value(); // get the update's string value
        // Notes: Caller does not own the return value. Do not modify.
        // Do not use after underlying Alg_seq is modified.
    double get_real_value();  // get the update's real value
    bool get_logical_value(); // get the update's logical value
    long get_integer_value(); // get the update's integer value
    const char *get_atom_value();   // get the update's atom value
        // Notes: Caller does not own the return value. Do not modify.
        // The return value's lifetime is forever.

    // Auxiliary function to aid in editing tracks
    // Returns true if the event overlaps the given region
    bool overlap(double t, double len, bool all);

    const char *GetDescription(); // computes a text description of this event
    // the result is in a static buffer, not thread-safe, just for debugging.
    Alg_event() { selected = false; }
    virtual ~Alg_event() {}
} *Alg_event_ptr;

typedef class Alg_note : public Alg_event {
public:
    virtual ~Alg_note();
    Alg_note(Alg_note *); // copy constructor
    float pitch; // pitch in semitones (69 = A440)
    float loud;  // dynamic corresponding to MIDI velocity
    double dur;   // duration in seconds (normally to release point)
    Alg_parameters_ptr parameters; // attribute/value pair list
    Alg_note() { type = 'n'; parameters = NULL; }
    void show();
} *Alg_note_ptr;

typedef class Alg_update : public Alg_event {
public:
    virtual ~Alg_update() {};
    Alg_update(Alg_update *); // copy constructor
    Alg_parameter parameter; // an update contains one attr/value pair

    Alg_update() { type = 'u'; }
    void show();
} *Alg_update_ptr;

// a sequence of Alg_event objects
typedef class Alg_events {
private:
    long maxlen;
    void expand();
protected:
    long len;
    Alg_event_ptr *events; // events is array of pointers
public:
    // sometimes, it is nice to have the time of the last note-off.
    // In the current implementation,
    // this field is set by append to indicate the time of the
    // last note-off in the current unit, so it should be correct after
    // creating a new track and adding notes to it. It is *not*
    // updated after uninsert(), so use it with care.
    double last_note_off;
    // initially false, in_use can be used to mark "do not delete". If an
    // Alg_events instance is deleted while "in_use", an assertion will fail.
    bool in_use;
    virtual int length() { return len; }
    Alg_event_ptr &operator[](int i) {
        assert(i >= 0 && i < len);
        return events[i];
    }
    Alg_events() {
        maxlen = len = 0;
        events = NULL;
        last_note_off = 0;
        in_use = false;
    }
    // destructor deletes the events array, but not the
    // events themselves
    virtual ~Alg_events();
    void set_events(Alg_event_ptr *e, long l, long m) {
        if (events) delete [] events;
        events = e; len = l; maxlen = m; }
    // for use by Alg_track and Alg_seq
    void insert(Alg_event_ptr event);
    void append(Alg_event_ptr event);
    Alg_event_ptr uninsert(long index);
} *Alg_events_ptr;

class Alg_track;

typedef class Alg_event_list : public Alg_events {
protected:
    char type; // 'e' Alg_event_list, 't' Alg_track, 's' Alg_seq
    static const char *last_error_message;
    Alg_track *events_owner; // if this is an Alg_event_list,
        // the events are owned by an Alg_track or an Alg_seq
    static int sequences;  // to keep track of sequence numbers
    int sequence_number;   // this sequence number is incremented
        // whenever an edit is performed on an Alg_track or Alg_seq.
        // When an Alg_event_list is created to contain pointers to
        // a subset of an Alg_track or Alg_seq (the events_owner),
        // the Alg_event_list gets a copy of the events_owner's
        // sequence_number. If the events_owner is edited, the pointers
        // in this Alg_event_list will become invalid. This is detected
        // (for debugging) as differing sequence_numbers.

    // every event list, track, and seq has a duration.
    // Usually the duration is set when the list is constructed, e.g.
    // when you extract from 10 to 15 seconds, the duration is 5 secs.
    // The duration does not tell you when is the last note-off.
    // duration is recorded in both beats and seconds:
    double beat_dur;
    double real_dur;
public:
    // the client should not create one of these, but these are
    // returned from various track and seq operations. An
    // Alg_event_list "knows" the Alg_track or Alg_seq that "owns"
    // the events. All events in an Alg_event_list must belong
    // to the same Alg_track or Alg_seq structure.
    // When applied to an Alg_seq, events are enumerated track
    // by track with increasing indices. This operation is not
    // particularly fast on an Alg_seq.
    virtual Alg_event_ptr const &operator[](int i);
    Alg_event_list() { sequence_number = 0;
        beat_dur = 0.0; real_dur = 0.0; events_owner = NULL; type = 'e'; }
    Alg_event_list(Alg_track *owner);

    char get_type() { return type; }
    Alg_track *get_owner() { return events_owner; }

    // The destructor does not free events because they are owned
    // by a track or seq structure.
    virtual ~Alg_event_list();

    // Returns the duration of the sequence in beats or seconds
    double get_beat_dur() { return beat_dur; }
    void set_beat_dur(double d) { beat_dur = d; }
    double get_real_dur() { return real_dur; }
    void set_real_dur(double d) { real_dur = d; }

    // Events are stored in time order, so when you change the time of
    // an event, you must adjust the position. When you call set_start_time
    // on an Alg_event_list, the Alg_event_list is not modified, but the
    // Alg_track that "owns" the event is modified. If the owner is an
    // Alg_seq, this may require searching the seq for the track containing
    // the event. This will mean a logN search of every track in the seq
    // (but if this turns out to be a problem, we can store each event's
    // track owner in the Alg_event_list.)
    virtual void set_start_time(Alg_event *event, double);
    // get text description of run-time errors detected, clear error
    const char *get_last_error_message() { return last_error_message; }
    // Implementation hint: keep a sequence number on each Alg_track that is
    // incremented anytime there is a structural change. (This behavior is
    // inherited by Alg_seq as well.) Copy the sequence number to any
    // Alg_event_list object when it is created. Whenever you access an
    // Alg_event_list, using operator[], assert that the Alg_event_list sequence
    // number matches the Alg_seq sequence number. This will guarantee that you
    // do not try to retain pointers to events beyond the point where the events
    // may no longer exist.
} *Alg_event_list_ptr, &Alg_event_list_ref;

// Alg_beat is used to contruct a tempo map
typedef class Alg_beat {
public:
    Alg_beat(double t, double b) {
        time = t; beat = b; }
    Alg_beat() {};
    double time;
    double beat;
} *Alg_beat_ptr;

// Alg_beats is a list of Alg_beat objects used in Alg_seq
typedef class Alg_beats {
private:
    long maxlen;
    void expand();
public:
    long len;
    Alg_beat_ptr beats;
    Alg_beat &operator[](int i) {
        assert(i >= 0 && i < len);
        return beats[i];
    }
    Alg_beats() {
        maxlen = len = 0;
        beats = NULL;
        expand();
        beats[0].time = 0;
        beats[0].beat = 0;
        len = 1;
    }
    ~Alg_beats() {
        if (beats) delete[] beats;
    }
    void insert(long i, Alg_beat_ptr beat);
} *Alg_beats_ptr;

typedef class Alg_time_map {
private:
    int refcount;
public:
    Alg_beats beats; // array of Alg_beat
    double last_tempo;
    bool last_tempo_flag;
    Alg_time_map() {
        last_tempo = ALG_DEFAULT_BPM / 60.0; // note: this value ignored until
                // last_tempo_flag is set; nevertheless, the default
                // tempo is 100.
        last_tempo_flag = true;
        refcount = 0;
    }
    Alg_time_map(Alg_time_map *map); // copy constructor
    long length() { return beats.len; }
    void show();
    long locate_time(double time);
    long locate_beat(double beat);
    double beat_to_time(double beat);
    double time_to_beat(double time);
    // Time map manipulations: it is prefered to call the corresponding
    // methods in Alg_seq. If you manipulate an Alg_time_map directly,
    // you should take care to convert all tracks that use the time map
    // to beats or seconds as appropriate: Normally if you insert a beat
    // you want tracks to be in time units and if you insert a tempo change
    // you want tracks to be in beat units.
    void insert_beat(double time, double beat);   // add a point to the map
    bool insert_tempo(double tempo, double beat); // insert a tempo change
    // get the tempo starting at beat
    double get_tempo(double beat);
    // set the tempo over a region
    bool set_tempo(double tempo, double start_beat, double end_beat);
    bool stretch_region(double b0, double b1, double dur);
    void cut(double start, double len, bool units_are_seconds);
    void trim(double start, double end, bool units_are_seconds);
    void paste(double start, Alg_track *tr);
    // insert a span of time. If start is at a tempo change, then
    // the span of time runs at the changed tempo
    void insert_time(double start, double len);
    // insert a span of beats. If start is at a tempo change, the
    // tempo change takes effect before the inserted beats
    void insert_beats(double start, double len);
    void dereference() {
        if (--refcount <= 0) delete this;
    }
    void reference() {
        refcount++;
    }
} *Alg_time_map_ptr;

// Serial_buffer is an abstract class with common elements of
//     Serial_read_buffer and Serial_write_buffer
class Serial_buffer {
  protected:
    char *buffer;
    char *ptr;
    long len;
  public:
    Serial_buffer() {
        buffer = NULL;
        ptr = NULL;
        len = 0;
    }
    virtual ~Serial_buffer() { }

    long get_posn() { return (long) (ptr - buffer); }
    long get_len() { return len; }
};

typedef class Serial_read_buffer : public Serial_buffer {
public:
    // note that a Serial_read_buffer is initialized for reading by
    // setting buffer, but it is not the Serial_read_buffer's responsibility
    // to delete the buffer (owner might want to reuse it), so the destructor
    // does nothing.
    virtual ~Serial_read_buffer() {  }
#if defined(_WIN32)
//#pragma warning(disable: 546) // cast to int is OK, we only want low 7 bits
//#pragma warning(disable: 4311) // type cast pointer to long warning
#endif
    void get_pad() { while ((intptr_t) ptr & 7) ptr++; }
#if defined(_WIN32)
//#pragma warning(default: 4311 546)
#endif
    // Prepare to read n bytes from buf. The caller must manage buf: it is
    // valid until reading is finished, and it is caller's responsibility
    // to free buf when it is no longer needed.
    void init_for_read(void *buf, long n) {
        buffer = (char *) buf;
        ptr = (char *) buf;
        len = n;
    }
    char get_char() { return *ptr++; }
    void unget_chars(int n) { ptr -= n; } // undo n get_char() calls
    long get_int32() { long i = *((long *) ptr); ptr += 4; return i; }
    float get_float() { float f = *((float *) ptr); ptr += 4; return f; }
    double get_double() { double d = *((double *) ptr); ptr += sizeof(double);
                          return d; }
    const char *get_string() { char *s = ptr; char *fence = buffer + len;
                         assert(ptr < fence); (void)fence; // unused variable
                         while (*ptr++) assert(ptr < fence);
                         get_pad();
                         return s; }
    void check_input_buffer(long needed) {
        assert(get_posn() + needed <= len); }
} *Serial_read_buffer_ptr;

typedef class Serial_write_buffer: public Serial_buffer {
  public:
    // Note: allegro.cpp declares one static instance of Serial_buffer to
    // reduce large memory (re)allocations when serializing tracks for UNDO.
    // This destructor will only run when the program exits, which will only
    // add overhead to the exit process, but it will eliminate an incorrect
    // report of memory leakage from automation that doesn't know better. -RBD
    virtual ~Serial_write_buffer() {
        if (buffer) delete [] buffer;
    }
    void init_for_write() { ptr = buffer; }
    // store_long writes a long at a given offset
    void store_long(long offset, long value) {
        assert(offset <= get_posn() - 4);
        long *loc = (long *) (buffer + offset);
        *loc = value;
    }
    void check_buffer(long needed);
    void set_string(const char *s) {
        char *fence = buffer + len;
        assert(ptr < fence); (void)fence; // unused variable
        // two brackets surpress a g++ warning, because this is an
        // assignment operator inside a test.
        while ((*ptr++ = *s++)) assert(ptr < fence);
        // 4311 is type cast pointer to long warning
        // 4312 is type cast long to pointer warning
#if defined(_WIN32)
//#pragma warning(disable: 4311 4312)
#endif
        assert((char *)(((long) (ptr + 7)) & ~7) <= fence);
#if defined(_WIN32)
//#pragma warning(default: 4311 4312)
#endif
        pad(); }
    void set_int32(long v) { *((long *) ptr) = v; ptr += 4; }
    void set_double(double v) { *((double *) ptr) = v; ptr += 8; }
    void set_float(float v) { *((float *) ptr) = v; ptr += 4; }
    void set_char(char v) { *ptr++ = v; }
#if defined(_WIN32)
//#pragma warning(disable: 546) // cast to int is OK, we only want low 7 bits
//#pragma warning(disable: 4311) // type cast pointer to long warning
#endif
    void pad() { while ((intptr_t) ptr & 7) set_char(0); }
#if defined(_WIN32)
//#pragma warning(default: 4311 546)
#endif
    void *to_heap(long *len) {
        *len = get_posn();
        char *newbuf = new char[*len];
        memcpy(newbuf, buffer, *len);
        return newbuf;
    }
} *Serial_write_buffer_ptr;

typedef class Alg_seq *Alg_seq_ptr;

typedef class Alg_track : public Alg_event_list {
protected:
    Alg_time_map *time_map;
    bool units_are_seconds;
    char *get_string(char **p, long *b);
    long get_int32(char **p, long *b);
    double get_double(char **p, long *b);
    float get_float(char **p, long *b);
    static Serial_read_buffer ser_read_buf;
    static Serial_write_buffer ser_write_buf;
    void serialize_parameter(Alg_parameter *parm);
    // *buffer_ptr points to binary data, bytes_ptr points to how many
    // bytes have been used so far, len is length of binary data
    void unserialize_parameter(Alg_parameter_ptr parm_ptr);
public:
    void serialize_track();
    void unserialize_track();
    virtual Alg_event_ptr const &operator[](int i) {
        assert(i >= 0 && i < len);
        return events[i];
    }
    Alg_track() { units_are_seconds = false; time_map = NULL;
                  set_time_map(NULL); type = 't'; }
    // initialize empty track with a time map
    Alg_track(Alg_time_map *map, bool seconds);

    Alg_event_ptr copy_event(Alg_event_ptr event); // make a complete copy

    Alg_track(Alg_track &track);  // copy constructor, does not copy time_map
    // copy constructor: event_list is copied, map is installed and referenced
    Alg_track(Alg_event_list_ref event_list, Alg_time_map_ptr map,
              bool units_are_seconds);
    virtual ~Alg_track() { // note: do not call set_time_map(NULL)!
        if (time_map) time_map->dereference();
        time_map = NULL; }

    // Returns a buffer containing a serialization of the
    // file.  It will be an ASCII representation unless text is true.
    // *buffer gets a newly allocated buffer pointer. The caller must free it.
    // *len gets the length of the serialized track
    virtual void serialize(void **buffer, long *bytes);

    // Try to read from a memory buffer.  Automatically guess
    // whether it's MIDI or text.
    static Alg_track *unserialize(void *buffer, long len);

    // If the track is really an Alg_seq and you need to access an
    // Alg_seq method, coerce to an Alg_seq with this function:
    Alg_seq_ptr to_alg_seq() {
        return (get_type() == 's' ? (Alg_seq_ptr) this : NULL); }

    // Are we using beats or seconds?
    bool get_units_are_seconds() { return units_are_seconds; }
    // Change units
    virtual void convert_to_beats();
    virtual void convert_to_seconds();
    void set_dur(double dur);
    double get_dur() { return (units_are_seconds ? real_dur : beat_dur); }

    // Every Alg_track may have an associated time_map. If no map is
    // specified, or if you set_time_map(NULL), then the behavior
    // should be as if there is a constant tempo of 100 beats/minute
    // (this constant is determined by ALG_DEFAULT_BPM).
    // Recommendation: create a static global tempo map object. When
    // any operation that needs a tempo map gets NULL, use the global
    // tempo map. (Exception: any operation that would modify the
    // tempo map should raise an error -- you don't want to change the
    // default tempo map.)
    virtual void set_time_map(Alg_time_map *map);
    Alg_time_map *get_time_map() { return time_map; }

    // Methods to create events. The returned event is owned by the caller.
    // Use delete to get rid of it unless you call add() -- see below.
    //
    Alg_note *create_note(double time, int channel, int identifier,
                           float pitch, float loudness, double duration);
    // Note: after create_update(), caller should use set_*_value() to
    // initialize the attribute/value pair:
    Alg_update *create_update(double time, int channel, int identifier);
    // Adds a new event - it is automatically inserted into the
    // correct order in the sequence based on its timestamp.
    // The ownership passes from the caller to this Alg_seq. The
    // event is not copied.
    virtual void add(Alg_event *event) { insert(event); }

    //
    // Editing regions
    //

    // Deletes the notes that start within the given region
    // and returns them in a new sequence.  The start times
    // of the notes in the returned sequence are shifted
    // by -t.  The notes after the region get shifted over
    // to fill the gap. In an Alg_seq, the tempo track is edited
    // in a similar way
    // and the cut tempo information is retained in the new seq.
    // ONLY NOTES THAT START WITHIN THE REGION ARE CUT unless
    // "all" is true in which case all notes that intersect
    // the region are copied. CUT NOTES
    // MAY EXTEND BEYOND THE DURATION OF THE RESULTING SEQ.
    // The return type is the same as this (may be Alg_seq).
    // All times including len are interpreted according to
    // units_are_seconds in the track.
    virtual Alg_track *cut(double t, double len, bool all);

    // Like cut() but doesn't remove the notes from the original
    // sequence. The Alg_events are copied, not shared. ONLY EVENTS
    // THAT START WITHIN THE REGION ARE COPIED unless "all" is true
    // in which case all notes that intersect the region are
    // copied. COPIED NOTES MAY
    // EXTEND BEYOND THE DURATION OF THE RESULTING SEQ.
    // The return type is the same as this (may be Alg_seq).
    virtual Alg_track *copy(double t, double len, bool all);

    // Inserts a sequence in the middle, shifting some notes
    // over by the duration of the seq, which is first converted
    // to the same units (seconds or beats) as this. (This makes
    // a differece because the pasted data may change the tempo,
    // and notes that overlap the borders will then experience
    // a tempo change.)
    // THE SEQ PARAMETER IS NOT MODIFIED, AND Alg_event's ARE
    // COPIED, NOT SHARED.
    // The type of seq must be Alg_seq if seq is an Alg_seq, or
    // Alg_track if seq is an Alg_track or an Alg_event_list.
    virtual void paste(double t, Alg_event_list *seq); // Shifts notes

    // Merges two sequences with a certain offset. The offset is
    // interpreted as either beats or seconds according to the
    // current units of this, and seq is converted to the same
    // units as this. Except for a possible conversion to beats
    // or seconds, the tempo track of seq (if any) is ignored.
    // (There is no way to merge tempo tracks.)
    // THE SEQ PARAMETER IS NOT MODIFIED, AND Alg_event's ARE
    // COPIED, NOT SHARED.
    // The type of seq must be Alg_seq if seq is an Alg_seq, or
    // Alg_track if seq is an Alg_track or an Alg_event_list.
    virtual void merge(double t, Alg_event_list_ptr seq);

    // Deletes and shifts notes to fill the gap. The tempo track
    // is also modified accordingly. ONLY EVENTS THAT START WITHIN
    // THE REGION ARE DELETED unless "all" is true, in which case
    // all notes that intersect the region are cleared.
    // NOTES THAT EXTEND FROM BEFORE THE
    // REGION INTO THE REGION RETAIN THEIR DURATION IN EITHER
    // BEATS OR SECONDS ACCORDING TO THE CURRENT UNITS OF this.
    virtual void clear(double t, double len, bool all);

    // Deletes notes but doesn't shift.  If the "all" argument
    // is true, deletes all notes that intersect the range at all,
    // not just those that start within it. The tempo track is
    // not affected.
    virtual void silence(double t, double len, bool all);

    // Simply shifts notes past time t over by len, which is given
    // in either beats or seconds according to the units of this.
    // The resulting interveal (t, t+len) may in fact contain notes
    // that begin before t. The durations of notes are not changed.
    // If this is an Alg_seq, the tempo track is expanded at t also.
    virtual void insert_silence(double t, double len);

    //
    // Accessing for screen display
    //

    // A useful generic function to retrieve only certain
    // types of events.  The masks should be bit-masks defined
    // somewhere else. Part of the mask allows us to search for
    // selected events. If this is an Alg_seq, search all tracks
    // (otherwise, call track[i].find())
    // If channel_mask == 0, accept ALL channels
    virtual Alg_event_list *find(double t, double len, bool all,
                                 long channel_mask, long event_type_mask);

    virtual void set_in_use(bool flag) { in_use = flag; }
    //
    // MIDI playback
    //
    // See Alg_iterator
} *Alg_track_ptr, &Alg_track_ref;

// Alg_time_sig represents a single time signature;
// although not recommended, time_signatures may have arbitrary
// floating point values, e.g. 4.5 beats per measure
typedef class Alg_time_sig {
public:
    double beat; // when does this take effect?
    double num;  // what is the "numerator" (top number?)
    double den;  // what is the "denominator" (bottom number?)
    Alg_time_sig(double b, double n, double d) {
        beat = b; num = n; den = d;
    }
    Alg_time_sig() {
        beat = 0; num = 0; den = 0;
    }
    void beat_to_measure(double beat, double *measure, double *m_beat,
                         double *num, double *den);

} *Alg_time_sig_ptr;

// Alg_time_sigs is a dynamic array of time signatures
//
// The default (empty) time_sigs has 4/4 time at beat 0.
// Each time_sig object in time_sigs represents the beginning
// of a measure. If there is a beat missing, e.g. in the first
// measure, you can represent this by inserting another
// time_sig at the next measure beginning. Each time_sig implies
// an infinite sequence of full measures until the next time_sig.
// If you insert a time_sig and one already exist near the same
// beat, the old one is replaced, thus re-barring every measure
// until the next time_sig.
class Alg_time_sigs {
private:
    long maxlen;
    void expand(); // make more space
    long len;
    Alg_time_sig_ptr time_sigs;
public:
    Alg_time_sigs() {
        maxlen = len = 0;
        time_sigs = NULL;
    }
    Alg_time_sig &operator[](int i) { // fetch a time signature
        assert(i >= 0 && i < len);
        return time_sigs[i];
    }
    ~Alg_time_sigs() {
        if (time_sigs) delete[] time_sigs;
    }
    void show();
    long length() { return len; }
    int find_beat(double beat);
    // get the number of beats per measure starting at beat
    double get_bar_len(double beat);
    void insert(double beat, double num, double den, bool force = false);
    void cut(double start, double end, double dur); // remove from start to end
    void trim(double start, double end); // retain just start to end
    void paste(double start, Alg_seq *seq);
    void insert_beats(double beat, double len); // insert len beats at beat
    // find the nearest beat (see Alg_seq::nearest_beat) to beat
    double nearest_beat(double beat);
};

// a sequence of Alg_events objects
typedef class Alg_tracks {
private:
    long maxlen;
    void expand();
    void expand_to(int new_max);
    long len;
public:
    Alg_track_ptr *tracks; // tracks is array of pointers
    Alg_track &operator[](int i) {
        assert(i >= 0 && i < len);
        return *tracks[i];
    }
    long length() { return len; }
    Alg_tracks() {
        maxlen = len = 0;
        tracks = NULL;
    }
    ~Alg_tracks();
    // Append a track to tracks. This Alg_tracks becomes the owner of track.
    void append(Alg_track_ptr track);
    void add_track(int track_num, Alg_time_map_ptr time_map, bool seconds);
    void reset();
    void set_in_use(bool flag); // handy to set in_use flag on all tracks
} *Alg_tracks_ptr;

typedef enum {
    alg_no_error = 0,      // no error reading Allegro or MIDI file
    alg_error_open = -800, // could not open Allegro or MIDI file
    alg_error_syntax   // something found in the file that could not be parsed;
    // generally you should ignore syntax errors or look at the printed error
    // messages because there are some things in standard midi files that we do
    // not handle; (maybe we should only set alg_error_syntax when there is a
    // real problem with the file as opposed to when there is some warning
    // message for the user)
} Alg_error;

typedef struct Alg_pending_event {
    void *cookie; // client-provided sequence identifier
    Alg_events *events; // the array of events
    long index; // offset of this event
    bool note_on; // is this a note-on or a note-off (if applicable)?
    double offset; // time offset for events
    double time; // time for this event
} *Alg_pending_event_ptr;

typedef class Alg_iterator {
private:
    long maxlen;
    void expand();
    void expand_to(int new_max);
    long len;
    Alg_seq_ptr seq;
    Alg_pending_event *pending_events;
    // the next four fields are mainly for request_note_off()
    Alg_events_ptr events_ptr; // remembers events containing current event
    long index; // remembers index of current event
    void *cookie; // remembers the cookie associated with next event
    double offset;
    void show();
    bool earlier(int i, int j);
    void insert(Alg_events_ptr events, long index, bool note_on,
                void *cookie, double offset);
    // returns the info on the next pending event in the priority queue
    bool remove_next(Alg_events_ptr &events, long &index, bool &note_on,
                     void *&cookie, double &offset, double &time);
public:
    bool note_off_flag; // remembers if we are iterating over note-off
                        // events as well as note-on and update events
    long length() { return len; }
    Alg_iterator(Alg_seq_ptr s, bool note_off) {
        seq = s;
        note_off_flag = note_off;
        maxlen = len = 0;
        pending_events = NULL;
    }
    // Normally, iteration is over the events in the one sequence used
    // to instatiate the iterator (see above), but with this method, you
    // can add more sequences to the iteration. Events are returned in
    // time order, so effectively sequence events are merged.
    // The optional offset is added to each event time of sequence s
    // before merging/sorting. You should call begin_seq() for each
    // sequence to be included in the iteration unless you call begin()
    // (see below).
    void begin_seq(Alg_seq_ptr s, void *cookie = NULL, double offset = 0.0);
    ~Alg_iterator();
    // Prepare to enumerate events in order. If note_off_flag is true, then
    // iteration_next will merge note-off events into the sequence. If you
    // call begin(), you should not normally call begin_seq(). See above.
    void begin(void *cookie = NULL) { begin_seq(seq, cookie); }
    // return next event (or NULL). If iteration_begin was called with
    // note_off_flag = true, and if note_on is not NULL, then *note_on
    // is set to true when the result value represents a note-on or update.
    // (With note_off_flag, each Alg_note event is returned twice, once
    // at the note-on time, with *note_on == true, and once at the note-off
    // time, with *note_on == false. If a cookie_ptr is passed, then the
    // cookie corresponding to the event is stored at that address
    // If end_time is 0, iterate through the entire sequence, but if
    // end_time is non_zero, stop iterating at the last event before end_time
    Alg_event_ptr next(bool *note_on = NULL, void **cookie_ptr = NULL,
                       double *offset_ptr = NULL, double end_time = 0);
    // Sometimes, the caller wants to receive note-off events for a subset
    // of the notes, typically the notes that are played and need to be
    // turned off. In this case, when a note is turned on, the client
    // should call request_note_off(). This will insert a note-off into
    // the queue for the most recent note returned by next().
    void request_note_off();
    void end();   // clean up after enumerating events
} *Alg_iterator_ptr;

// An Alg_seq is an array of Alg_events, each a sequence of Alg_event,
// with a tempo map and a sequence of time signatures
//
typedef class Alg_seq : public Alg_track {
protected:
    Alg_iterator_ptr pending; // iterator used internally by Alg_seq methods
    void serialize_seq();
    Alg_error error; // error code set by file readers
    // an internal function used for writing Allegro track names
    Alg_event_ptr write_track_name(std::ostream &file, int n,
                                   Alg_events &events);
public:
    int channel_offset_per_track; // used to encode track_num into channel
    Alg_tracks track_list;       // array of Alg_events
    Alg_time_sigs time_sig;
    int beat_x;
    void basic_initialization() {
        error = alg_no_error;
        units_are_seconds = true; type = 's';
        channel_offset_per_track = 0;
        add_track(0); // default is one empty track
    }
    Alg_seq() {
        basic_initialization();
    }
    // copy constructor -- if track is an Alg_seq, make a copy; if
    //    track is just an Alg_track, the track becomes track 0
    Alg_seq(Alg_track_ref track) { seq_from_track(track); }
    Alg_seq(Alg_track_ptr track) { seq_from_track(*track); }
    void seq_from_track(Alg_track_ref tr);
    // create from file:
    Alg_seq(std::istream &file, bool smf, double *offset_ptr = NULL);
    // create from filename
    Alg_seq(const char *filename, bool smf, double *offset_ptr = NULL);
    virtual ~Alg_seq();
    int get_read_error() { return error; }
    void serialize(void **buffer, long *bytes);
    void copy_time_sigs_to(Alg_seq *dest); // a utility function
    void set_time_map(Alg_time_map *map);

    // encode sequence structure into contiguous, moveable memory block
    // address of newly allocated memory is assigned to *buffer, which must
    // be freed by caller; the length of data is assigned to *len
    void unserialize_seq();

    // write an ascii representation to file
    void write(std::ostream &file, bool in_secs, double offset = 0.0);
    // returns true on success
    bool write(const char *filename, double offset = 0.0);
    void smf_write(std::ostream &file);
    bool smf_write(const char *filename);

    // Returns the number of tracks
    int tracks();

    // create a track
    void add_track(int track_num) {
        track_list.add_track(track_num, get_time_map(), units_are_seconds);
    }

    // Return a particular track. This Alg_seq owns the track, so the
    // caller must not delete the result.
    Alg_track_ptr track(int);

    virtual Alg_event_ptr const &operator[](int i);

    virtual void convert_to_seconds();
    virtual void convert_to_beats();

    Alg_track_ptr cut_from_track(int track_num, double start, double dur,
                                 bool all);
    Alg_seq *cut(double t, double len, bool all);
    void insert_silence_in_track(int track_num, double t, double len);
    void insert_silence(double t, double len);
    Alg_track_ptr copy_track(int track_num, double t, double len, bool all);
    Alg_seq *copy(double start, double len, bool all);
    void paste(double start, Alg_seq *seq);
    virtual void clear(double t, double len, bool all);
    virtual void merge(double t, Alg_event_list_ptr seq);
    virtual void silence(double t, double len, bool all);
    void clear_track(int track_num, double start, double len, bool all);
    void silence_track(int track_num, double start, double len, bool all);
    Alg_event_list_ptr find_in_track(int track_num, double t, double len,
                                     bool all, long channel_mask,
                                     long event_type_mask);

    // find index of first score event after time
    long seek_time(double time, int track_num);
    bool insert_beat(double time, double beat);
    // return the time of the beat nearest to time, also returns beat
    // number through beat. This will correspond to an integer number
    // of beats from the nearest previous time signature or 0.0, but
    // since time signatures need not be on integer beat boundaries
    // the beat location may not be on an integer beat (beat locations
    // are measured from the beginning which is beat 0.
    double nearest_beat_time(double time, double *beat);
    // warning: insert_tempo may change representation from seconds to beats
    bool insert_tempo(double bpm, double beat);
    // change the duration from b0 to b1 (beats) to dur (seconds) by
    // scaling the intervening tempos
    bool stretch_region(double b0, double b1, double dur);
    // add_event takes a pointer to an event on the heap. The event is not
    // copied, and this Alg_seq becomes the owner and freer of the event.
    void add_event(Alg_event_ptr event, int track_num);
    void add(Alg_event_ptr event) { assert(false); } // call add_event instead
    // get the tempo starting at beat
    double get_tempo(double beat);
    bool set_tempo(double bpm, double start_beat, double end_beat);

    // get the bar length in beats starting at beat
    double get_bar_len(double beat);
    void set_time_sig(double beat, double num, double den);
    void beat_to_measure(double beat, long *measure, double *m_beat,
                         double *num, double *den);
    // void set_events(Alg_event_ptr *events, long len, long max);
    void merge_tracks();    // move all track data into one track
    void set_in_use(bool flag); // set in_use flag on all tracks
} *Alg_seq_ptr, &Alg_seq_ref;

// see Alg_seq::Alg_seq() constructors that read from files
// the following are for internal library implementation and are
// moved to *_internal.h header files.
//Alg_seq_ptr alg_read(std::istream &file, Alg_seq_ptr new_seq);
//Alg_seq_ptr alg_smf_read(std::istream &file, Alg_seq_ptr new_seq);
#endif

repo_name: CYBERDEViL/LMMS

license: mit


code:
#include "assert.h"
#include "stdlib.h"
#include "string.h"
#include "ctype.h"
#include "trace.h"
#include <string>
#include <fstream>
#include <algorithm>
#include "strparse.h"
#include "allegro.h"
#include "algrd_internal.h"

using namespace std;

#define streql(s1, s2) (strcmp(s1, s2) == 0)
#define field_max 80

class Alg_reader {
public:
    istream *file;
    string input_line;
    int line_no;
    String_parse line_parser;
    bool line_parser_flag;
    string field;
    bool error_flag;
    Alg_seq_ptr seq;
    double tsnum;
    double tsden;
    double offset;
    bool offset_found;

    Alg_reader(istream *a_file, Alg_seq_ptr new_seq);
    void readline();
    Alg_parameters_ptr process_attributes(Alg_parameters_ptr attributes,
                                          double time);
    bool parse();
    long parse_chan(string &field);
    long parse_int(string &field);
    int find_real_in(string &field, int n);
    double parse_real(string &field);
    void parse_error(string &field, long offset, char *message);
    void parse_error(string &field, long offset, const char *message);
    double parse_dur(string &field, double base);
    double parse_after_dur(double dur, string &field, int n, double base);
    double parse_loud(string &field);
    long parse_key(string &field);
    double parse_pitch(string &field);
    long parse_after_key(int key, string &field, int n);
    long find_int_in(string &field, int n);
    bool parse_attribute(string &field, Alg_parameter_ptr parm);
    bool parse_val(Alg_parameter_ptr param, string &s, int i);
    bool check_type(char type_char, Alg_parameter_ptr param);
};

double Alg_reader::parse_pitch(string &field)
{
    if (isdigit(field[1])) {
        int last = find_real_in(field, 1);
        string real_string = field.substr(1, last - 1);
        return atof(real_string.c_str());
    } else {
        return (double) parse_key(field);
    }
}

// it is the responsibility of the caller to delete
// the seq
Alg_reader::Alg_reader(istream *a_file, Alg_seq_ptr new_seq)
{
    file = a_file; // save the file
    line_parser_flag = false;
    line_no = 0;
    tsnum = 4; // default time signature
    tsden = 4;
    seq = new_seq;
    offset = 0.0;
    offset_found = false;
}

Alg_error alg_read(istream &file, Alg_seq_ptr new_seq, double *offset_ptr)
    // read a sequence from allegro file
{
    assert(new_seq);
    Alg_reader alg_reader(&file, new_seq);
    bool err = alg_reader.parse();
    if (!err && offset_ptr) {
        *offset_ptr = alg_reader.offset;
    }
    return (err ? alg_error_syntax : alg_no_error);
}

void Alg_reader::readline()
{
    // a word about memory management: this Alg_reader has a
    // member variable input_line that holds a line of input
    // it is reused for each line. input_line is parsed by
    // line_parser, which holds a reference to input_line
    line_parser_flag = false;
    if (getline(*file, input_line)) {
        line_parser.init(&input_line);
        line_parser_flag = true;
        error_flag = false;
    }
}

Alg_parameters_ptr Alg_reader::process_attributes(
        Alg_parameters_ptr attributes, double time)
{
    // print "process_attributes:", attributes
    bool ts_flag = false;
    if (attributes) {
        Alg_parameters_ptr a;
        bool in_seconds = seq->get_units_are_seconds();
        if ((a = Alg_parameters::remove_key(&attributes, "tempor"))) {
            double tempo = a->parm.r;
            seq->insert_tempo(tempo, seq->get_time_map()->time_to_beat(time));
        }
        if ((a = Alg_parameters::remove_key(&attributes, "beatr"))) {
            double beat = a->parm.r;
            seq->insert_beat(time, beat);
        }
        if ((a = Alg_parameters::remove_key(&attributes, "timesig_numr"))) {
            tsnum = a->parm.r;
            ts_flag = true;
        }
        if ((a = Alg_parameters::remove_key(&attributes, "timesig_denr"))) {
            tsden = a->parm.r;
            ts_flag = true;
        }
        if (ts_flag) {
            seq->set_time_sig(seq->get_time_map()->time_to_beat(time),
            tsnum, tsden);
        }
        if (in_seconds) seq->convert_to_seconds();
    }
    return attributes; // in case it was modified
}

bool Alg_reader::parse()
{
    int voice = 0;
    int key = 60;
    double loud = 100.0;
    double pitch = 60.0;
    double dur = 1.0;
    double time = 0.0;
    int track_num = 0;
    seq->convert_to_seconds();
    //seq->set_real_dur(0.0); // just in case it's not initialized already
    readline();
    bool valid = false; // ignore blank lines
    while (line_parser_flag) {
        bool time_flag = false;
        bool next_flag = false;
        double next = 0;
        bool voice_flag = false;
        bool loud_flag = false;
        bool dur_flag = false;
        bool new_pitch_flag = false; // "P" syntax or "A"-"G" syntax
        double new_pitch = 0.0;
        bool new_key_flag = false;   // "K" syntax
        int new_key = 0;
        Alg_parameters_ptr attributes = NULL;
        if (line_parser.peek() == '#') {
            // look for #track
            line_parser.get_nonspace_quoted(field);
            if (streql(field.c_str(), "#track")) {
                line_parser.get_nonspace_quoted(field); // number
                field.insert(0, " "); // need char at beginning because
                // parse_int ignores the first character of the argument
                track_num = parse_int(field);
                seq->add_track(track_num);

                // maybe we have a sequence or track name
                line_parser.get_remainder(field);
                // if there is a non-space character after #track n then
                // use it as sequence or track name. Note that because we
                // skip over spaces, a sequence or track name cannot begin
                // with leading blanks. Another decision is that the name
                // must be at time zero
                if (field.length() > 0) {
                    // insert the field as sequence name or track name
                    Alg_update_ptr update = new Alg_update;
                    update->chan = -1;
                    update->time = 0;
                    update->set_identifier(-1);
                    // sequence name is whatever is on track 0
                    // other tracks have track names
                    const char *attr =
                            (track_num == 0 ? "seqnames" : "tracknames");
                    update->parameter.set_attr(
                            symbol_table.insert_string(attr));
                    update->parameter.s = heapify(field.c_str());
                    seq->add_event(update, track_num);
                }
            } else if (streql(field.c_str(), "#offset")) {
                if (offset_found) {
                    parse_error(field, 0, "#offset specified twice");
                }
                offset_found = true;
                line_parser.get_nonspace_quoted(field); // number
                field.insert(0, " "); // need char at beginning because
                // parse_real ignores first character in the argument
                offset = parse_real(field);
            }
        } else {
            // we must have a track to insert into
            if (seq->tracks() == 0) seq->add_track(0);
            line_parser.get_nonspace_quoted(field);
            char pk = line_parser.peek();
            // attributes are parsed as two adjacent nonspace_quoted tokens
            // so we have to conditionally call get_nonspace_quoted() again
            if (pk && !isspace(pk)) {
                string field2;
                line_parser.get_nonspace_quoted(field2);
                field.append(field2);
            }
            while (field[0]) {
                char first = toupper(field[0]);
                if (strchr("ABCDEFGKLPUSIQHW-", first)) {
                    valid = true; // it's a note or event
                }
                if (first == 'V') {
                    if (voice_flag) {
                        parse_error(field, 0, "Voice specified twice");
                    } else {
                        voice = parse_chan(field);
                    }
                    voice_flag = true;
                } else if (first == 'T') {
                    if (time_flag) {
                        parse_error(field, 0, "Time specified twice");
                    } else {
                        time = parse_dur(field, 0.0);
                    }
                    time_flag = true;
                } else if (first == 'N') {
                    if (next_flag) {
                        parse_error(field, 0, "Next specified twice");
                    } else {
                        next = parse_dur(field, time);
                    }
                    next_flag = true;
                } else if (first == 'K') {
                    if (new_key_flag) {
                        parse_error(field, 0, "Key specified twice");
                    } else {
                        new_key = parse_key(field);
                        new_key_flag = true;
                    }
                } else if (first == 'L') {
                    if (loud_flag) {
                        parse_error(field, 0, "Loudness specified twice");
                    } else {
                        loud = parse_loud(field);
                    }
                    loud_flag = true;
                } else if (first == 'P') {
                    if (new_pitch_flag) {
                        parse_error(field, 0, "Pitch specified twice");
                    } else {
                        new_pitch = parse_pitch(field);
                        new_pitch_flag = true;
                    }
                } else if (first == 'U') {
                    if (dur_flag) {
                        parse_error(field, 0, "Dur specified twice");
                    } else {
                        dur = parse_dur(field, time);
                        dur_flag = true;
                    }
                } else if (strchr("SIQHW", first)) {
                    if (dur_flag) {
                        parse_error(field, 0, "Dur specified twice");
                    } else {
                        // prepend 'U' to field, copy EOS too
                        field.insert((unsigned int) 0, 1, 'U');
                        dur = parse_dur(field, time);
                        dur_flag = true;
                    }
                } else if (strchr("ABCDEFG", first)) {
                    if (new_pitch_flag) {
                        parse_error(field, 0, "Pitch specified twice");
                    } else {
                        // prepend 'P' to field
                        field.insert((unsigned int) 0, 1, 'P');
                        new_pitch = parse_pitch(field);
                        new_pitch_flag = true;
                    }
                } else if (first == '-') {
                    Alg_parameter parm;
                    if (parse_attribute(field, &parm)) { // enter attribute-value pair
                        attributes = new Alg_parameters(attributes);
                        attributes->parm = parm;
                        parm.s = NULL; // protect string from deletion by destructor
                    }
                } else {
                    parse_error(field, 0, "Unknown field");
                }

                if (error_flag) {
                    field[0] = 0; // exit the loop
                } else {
                    line_parser.get_nonspace_quoted(field);
                    pk = line_parser.peek();
                    // attributes are parsed as two adjacent nonspace_quoted
                    // tokens so we have to conditionally call
                    // get_nonspace_quoted() again
                    if (pk && !isspace(pk)) {
                        string field2;
                        line_parser.get_nonspace_quoted(field2);
                        field.append(field2);
                    }
                }
            }
            // a case analysis:
            // Key < 128 implies pitch unless pitch is explicitly given
            // Pitch implies Key unless key is explicitly given,
            // Pitch is rounded to nearest integer to determine the Key
            //    if necessary, so MIDI files will lose the pitch fraction
            // A-G is a Pitch specification (therefore it implies Key)
            //   K60 P60 -- both are specified, use 'em
            //   K60 P60 C4 -- overconstrained, an error
            //   K60 C4 -- OK, but K60 is already implied by C4
            //   K60 -- OK, pitch is 60
            //   C4 P60 -- over constrained
            //   P60 -- OK, key is 60
            //   P60.1 -- OK, key is 60
            //   C4 -- OK, key is 60, pitch is 60
            //   <nothing> -- OK, key and pitch from before
            //   K200 P60 -- ok, pitch is 60
            //   K200 with neither P60 nor C4 uses
            //       pitch from before

            // figure out what the key/instance is:
            if (new_key_flag) { // it was directly specified
                key = new_key;
            } else if (new_pitch_flag) {
                // pitch was specified, but key was not; get key from pitch
                key = (int) (new_pitch + 0.5); // round to integer key number
            }
            if (new_pitch_flag) {
                pitch = new_pitch;
            } else if (key < 128 && new_key_flag) {
                // no explicit pitch, but key < 128, so it implies pitch
                pitch = key;
                new_pitch_flag = true;
            }
            // now we've acquired new parameters
            // if (it is a note, then enter the note
            if (valid) {
                // change tempo or beat
                attributes = process_attributes(attributes, time);
                // if there's a duration or pitch, make a note:
                if (new_pitch_flag || dur_flag) {
                    Alg_note_ptr note_ptr = new Alg_note;
                    note_ptr->chan = voice;
                    note_ptr->time = time;
                    note_ptr->dur = dur;
                    note_ptr->set_identifier(key);
                    note_ptr->pitch = (float) pitch;
                    note_ptr->loud = (float) loud;
                    note_ptr->parameters = attributes;
                    seq->add_event(note_ptr, track_num); // sort later
                    if (seq->get_real_dur() < (time + dur)) seq->set_real_dur(time + dur);
                } else {
                    int update_key = -1;
                    // key must appear explicitly; otherwise
                    //    update applies to channel
                    if (new_key_flag) {
                        update_key = key;
                    }
                    if (loud_flag) {
                        Alg_update_ptr new_upd = new Alg_update;
                        new_upd->chan = voice;
                        new_upd->time = time;
                        new_upd->set_identifier(update_key);
                        new_upd->parameter.set_attr(symbol_table.insert_string("loudr"));
                        new_upd->parameter.r = pitch;
                        seq->add_event(new_upd, track_num);
                        if (seq->get_real_dur() < time) seq->set_real_dur(time);
                    }
                    if (attributes) {
                        while (attributes) {
                            Alg_update_ptr new_upd = new Alg_update;
                            new_upd->chan = voice;
                            new_upd->time = time;
                            new_upd->set_identifier(update_key);
                            new_upd->parameter = attributes->parm;
                            seq->add_event(new_upd, track_num);
                            Alg_parameters_ptr p = attributes;
                            attributes = attributes->next;
                            p->parm.s = NULL; // so we don't delete the string
                            delete p;
                        }
                    }
                }
                if (next_flag) {
                    time = time + next;
                } else if (dur_flag || new_pitch_flag) { // a note: incr by dur
                    time = time + dur;
                }
            }
        }
        readline();
    }
    if (!error_flag) { // why not convert even if there was an error? -RBD
        seq->convert_to_seconds(); // make sure format is correct
    }
    // real_dur is valid, translate to beat_dur
    seq->set_beat_dur((seq->get_time_map())->time_to_beat(seq->get_real_dur()));
    return error_flag;
}

long Alg_reader::parse_chan(string &field)
{
    const char *int_string = field.c_str() + 1;
    const char *msg = "Integer or - expected";
    const char *p = int_string;
    char c;
    // check that all chars in int_string are digits or '-':
    while ((c = *p++)) {
        if (!isdigit(c) && c != '-') {
            parse_error(field, p - field.c_str() - 1, msg);
            return 0;
        }
    }
    p--; // p now points to end-of-string character
    if (p - int_string == 0) {
        // bad: string length is zero
        parse_error(field, 1, msg);
        return 0;
    }
    if (p - int_string == 1 && int_string[0] == '-') {
        // special case: entire string is "-", interpret as -1
        return -1;
    }
    return atoi(int_string);
}

long Alg_reader::parse_int(string &field)
{
    const char *int_string = field.c_str() + 1;
    const char *msg = "Integer expected";
    const char *p = int_string;
    char c;
    // check that all chars in int_string are digits:
    while ((c = *p++)) {
        if (!isdigit(c)) {
            parse_error(field, p - field.c_str() - 1, msg);
            return 0;
        }
    }
    p--; // p now points to end-of-string character
    if (p - int_string == 0) {
        // bad: string length is zero
        parse_error(field, 1, msg);
        return 0;
    }
    return atoi(int_string);
}

int Alg_reader::find_real_in(string &field, int n)
{
    // scans from offset n to the end of a real constant
    bool decimal = false;
    int len = field.length();
    if (n < len && field[n] == '-') n += 1; // parse one minus sign
    for (int i = n; i < len; i++) {
        char c = field[i];
        if (!isdigit(c)) {
            if (c == '.' && !decimal) {
                decimal = true;
            } else {
                return i;
            }
        }
    }
    return len;
}

double Alg_reader::parse_real(string &field)
{
    const char *msg = "Real expected";
    int last = find_real_in(field, 1);
    string real_string = field.substr(1, last - 1);
    if (last <= 1 || last < (int) field.length()) {
       parse_error(field, 1, msg);
       return 0;
    }
    return atof(real_string.c_str());
}

void Alg_reader::parse_error(string &field, long offset, char *message)
{
    int position = line_parser.pos - field.length() + offset;
    error_flag = true;
    puts(line_parser.str->c_str());
    for (int i = 0; i < position; i++) {
        putc(' ', stdout);
    }
    putc('^', stdout);
    printf("    %s\n", message);
}

void Alg_reader::parse_error(string &field, long offset, const char *message)
{
    parse_error(field, offset, const_cast<char*>(message));
}

double duration_lookup[] = { 0.25, 0.5, 1.0, 2.0, 4.0 };

double Alg_reader::parse_dur(string &field, double base)
{
    const char *msg = "Duration expected";
    const char *durs = "SIQHW";
    const char *p;
    int last;
    double dur;
    if (field.length() < 2) {
        // fall through to error message
        return -1;
    } else if (isdigit(field[1])) {
        last = find_real_in(field, 1);
        string real_string = field.substr(1, last - 1);
        dur = atof(real_string.c_str());
        // convert dur from seconds to beats
        dur = seq->get_time_map()->time_to_beat(base + dur) -
              seq->get_time_map()->time_to_beat(base);
    } else if ((p = strchr(durs, toupper(field[1])))) {
        dur = duration_lookup[p - durs];
        last = 2;
    } else {
        parse_error(field, 1, msg);
        return 0;
    }
    dur = parse_after_dur(dur, field, last, base);
    dur = seq->get_time_map()->beat_to_time(
              seq->get_time_map()->time_to_beat(base) + dur) - base;
    return dur;
}

double Alg_reader::parse_after_dur(double dur, string &field,
                                   int n, double base)
{
    if ((int) field.length() == n) {
        return dur;
    }
    if (toupper(field[n]) == 'T') {
        return parse_after_dur(dur * 2/3, field, n + 1, base);
    }
    if (field[n] == '.') {
        return parse_after_dur(dur * 1.5, field, n + 1, base);
    }
    if (isdigit(field[n])) {
        int last = find_real_in(field, n);
        string a_string = field.substr(n, last - n);
        double f = atof(a_string.c_str());
        return parse_after_dur(dur * f, field, last, base);
    }
    if (field[n] == '+') {
        string a_string = field.substr(n + 1);
        return dur + parse_dur(
                a_string, seq->get_time_map()->beat_to_time(
                        seq->get_time_map()->time_to_beat(base) + dur));
    }
    parse_error(field, n, "Unexpected character in duration");
    return dur;
}

struct loud_lookup_struct {
    const char *str;
    int val;
} loud_lookup[] = { {"FFF", 127}, {"FF", 120}, {"F", 110}, {"MF", 100},
                    {"MP", 90}, {"P", 80}, {"PP", 70}, {"PPP", 60},
                    {NULL, 0} };

double Alg_reader::parse_loud(string &field)
{
    const char *msg = "Loudness expected";
    if (isdigit(field[1])) {
        return parse_int(field);
    } else {
        string dyn = field.substr(1);
        transform(dyn.begin(), dyn.end(), dyn.begin(), ::toupper);
        for (int i = 0; loud_lookup[i].str; i++) {
            if (streql(loud_lookup[i].str, dyn.c_str())) {
                return (double) loud_lookup[i].val;
            }
        }
    }
    parse_error(field, 1, msg);
    return 100.0;
}

int key_lookup[] = {21, 23, 12, 14, 16, 17, 19};

// the field can be K<number> or K[A-G]<number> or P[A-G]<number>
// (this can be called from parse_pitch() to handle [A-G])
// Notice that the routine ignores the first character: K or P
//
long Alg_reader::parse_key(string &field)
{
    const char *msg = "Pitch expected";
    const char *pitches = "ABCDEFG";
    const char *p;
    if (isdigit(field[1])) {
        // This routine would not have been called if field = "P<number>"
        // so it must be "K<number>" so <number> must be an integer.
        return parse_int(field);
    } else if ((p = strchr(pitches, toupper(field[1])))) {
        long key = key_lookup[p - pitches];
        key = parse_after_key(key, field, 2);
        return key;
    }
    parse_error(field, 1, msg);
    return 0;
}

long Alg_reader::parse_after_key(int key, string &field, int n)
{
    if ((int) field.length() == n) {
        return key;
    }
    char c = toupper(field[n]);
    if (c == 'S') {
        return parse_after_key(key + 1, field, n + 1);
    }
    if (c == 'F') {
        return parse_after_key(key - 1, field, n + 1);
    }
    if (isdigit(field[n])) {
        int last = find_int_in(field, n);
        string octave = field.substr(n, last - n);
        int oct = atoi(octave.c_str());
        return parse_after_key(key + oct * 12, field, last);
    }
    parse_error(field, n, "Unexpected character in pitch");
    return key;
}

long Alg_reader::find_int_in(string &field, int n)
{
    while ((int) field.length() > n && isdigit(field[n])) {
        n = n + 1;
    }
    return n;
}

bool Alg_reader::parse_attribute(string &field, Alg_parameter_ptr param)
{
    int i = 1;
    while (i < (int) field.length()) {
        if (field[i] == ':') {
            string attr = field.substr(1, i - 1);
            char type_char = field[i - 1];
            if (strchr("iarsl", type_char)) {
                param->set_attr(symbol_table.insert_string(attr.c_str()));
                parse_val(param, field, i + 1);
            } else {
                parse_error(field, 0, "attribute needs to end with typecode: i,a,r,s, or l");
            }
            return !error_flag;
        }
        i = i + 1;
    }
    return false;
}

bool Alg_reader::parse_val(Alg_parameter_ptr param, string &s, int i)
{
    int len = (int) s.length();
    if (i >= len) {
        return false;
    }
    if (s[i] == '"') {
        if (!check_type('s', param)) {
            return false;
        }
        // note: (len - i) includes 2 quote characters but no EOS character
        // so total memory to allocate is (len - i) - 1
        char *r = new char[(len - i) - 1];
        strncpy(r, s.c_str() + i + 1, (len - i) - 2);
        r[(len - i) - 2] = 0; // terminate the string
        param->s = r;
    } else if (s[i] == '\'') {
        if (!check_type('a', param)) {
            return false;
        }
        string r = s.substr(i + 1, len - i - 2);
        param->a = symbol_table.insert_string(r.c_str());
    } else if (param->attr_type() == 'l') {
        if (streql(s.c_str() + i, "true") ||
            streql(s.c_str() + i, "t")) {
            param->l = true;
        } else if (streql(s.c_str() + i, "false") ||
                   streql(s.c_str() + i, "nil")) {
            param->l = false;
        } else return false;
    } else if (isdigit(s[i]) || s[i] == '-' || s[i] == '.') {
        int pos = i;
        bool period = false;
        /* int sign = 1; LMMS unused variable */
        if (s[pos] == '-') {
            /* sign = -1; LMMS unused variable */
            pos++;
        }
        while (pos < len) {
            if (isdigit(s[pos])) {
                ;
            } else if (!period && s[pos] == '.') {
                period = true;
            } else {
                parse_error(s, pos, "Unexpected char in number");
                return false;
            }
            pos = pos + 1;
        }
        string r = s.substr(i, len - i);
        if (period) {
            if (!check_type('r', param)) {
                return false;
            }
            param->r = atof(r.c_str());
        } else {
            if (param->attr_type() == 'r') {
                param->r = atoi(r.c_str());
            } else if (!check_type('i', param)) {
                return false;
            } else {
                param->i = atoi(r.c_str());
            }
        }
    } else {
        parse_error(s, i, "invalid value");
        return false;
    }
    return true;
}

bool Alg_reader::check_type(char type_char, Alg_parameter_ptr param)
{
    return param->attr_type() == type_char;
}

//duration_lookup = {"S": 0.5, "I": 0.5, "Q": 1, "H": 2, "W": 4}
//key_lookup = {"C": 12, "D": 14, "E": 16, "F": 17, "G": 19, "A": 21, "B": 23}

/*
def test():
    reader = Alg_reader(open("data\\test.gro", "r"))
    reader.parse()
    score = reader->seq.notes
    print "score:", score
    reader = nil
*/


code: // allegroserial.cpp -- convert track to memory buffer and back to structure


code:
// 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(char *);
    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(char *msg)
{
    fprintf(stdout, "Midifile reader error: %s\n", msg);
}

void Alg_midifile_reader::Mf_error(const char *msg)
{
    Mf_error(const_cast<char*>(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 = &note_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, &parameter);
    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, &parameter);
    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, &parameter);
    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, &parameter);
    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, &parameter);
    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, &parameter);
}

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);
}