// 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__ #define __ALLEGRO__ #include #include #include #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 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; char **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: ~Alg_parameter(); Alg_attribute attr; union { double r;// real char *s; // string long i; // integer bool l; // logical char *a; // symbol (atom) }; // anonymous union void copy(Alg_parameter *); // copy from another parameter char attr_type() { return alg_attr_type(attr); } 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, char *name, double r); // insert string will copy string to heap static void insert_string(Alg_parameters **list, char *name, char *s); static void insert_integer(Alg_parameters **list, char *name, long i); static void insert_logical(Alg_parameters **list, char *name, bool l); static void insert_atom(Alg_parameters **list, char *name, char *s); static Alg_parameters *remove_key(Alg_parameters **list, 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(char *attr, char *value); void set_real_value(char *attr, double value); void set_logical_value(char *attr, bool value); void set_integer_value(char *attr, long value); void set_atom_value(char *attr, 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(char *attr); // test if note has attribute/value pair char get_attribute_type(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 char *get_string_value(char *attr, char *value = NULL); // get the string value double get_real_value(char *attr, double value = 0.0); // get the real value bool get_logical_value(char *attr, bool value = false); // get the logical value long get_integer_value(char *attr, long value = 0); // get the integer value char *get_atom_value(char *attr, char *value = NULL); // get the atom value void delete_attribute(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 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 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; 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; } // 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 &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 // set the tempo over a region bool set_tempo(double tempo, double start_beat, double end_beat); 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 (((long) 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; } char *get_string() { char *s = ptr; char *fence = buffer + len; assert(ptr < fence); 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(char *s) { char *fence = buffer + len; assert(ptr < fence); // two lots of 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 (((long) 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 &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); // // 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); void insert(double beat, double num, double den); void cut(double start, double end); // 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 }; // 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(); } *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 { 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)? } *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; void show(); bool earlier(int i, int j); void insert(Alg_events_ptr events, long index, bool note_on); bool remove_next(Alg_events_ptr &events, long &index, bool ¬e_on); 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; } ~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. void begin(bool note_off_flag = false); // 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 Alg_event_ptr next(bool *note_on = NULL); 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); Alg_seq(std::istream &file, bool smf); // create from file Alg_seq(const char *filename, bool smf); // create from filename 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); // returns true on success bool write(const char *filename); 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 &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); // warning: insert_tempo may change representation from seconds to beats bool insert_tempo(double bpm, double beat); // 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 // warning: set_tempo may change representation from seconds to beats bool set_tempo(double bpm, double start_beat, double end_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 } *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