code:
// allegrosmfwr.cpp -- Allegro Standard Midi File Write

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
#include "allegro.h"

// event_queue is a list element that keeps track of pending
// things to write to a track, including note-ons, note-offs,
// updates, tempo changes, and time signatures
//
class event_queue{
public:
  char type;//'n' for note, 'o' for off, 's' for time signature,
            // 'c' for tempo changes
  double time;
  long index; //of the event in mSeq->notes
  class event_queue *next;
  event_queue(char t, double when, long x, class event_queue *n) {
        type = t; time = when; index = x; next = n; }
};

class Alg_smf_write {
public:
    Alg_smf_write(Alg_seq_ptr seq);
    ~Alg_smf_write();
    long channels_per_track; // used to encode track number into chan field
    // chan is actual_channel + channels_per_track * track_number
    // default is 100, set this to 0 to merge all tracks to 16 channels

    void write(ostream &file /* , midiFileFormat = 1 */);

private:
    long previous_divs; // time in ticks of most recently written event

    void write_track(int i);
    void write_tempo(int divs, int tempo);
    void write_tempo_change(int i);
    void write_time_signature(int i);
    void write_note(Alg_note_ptr note, bool on);
    void write_update(Alg_update_ptr update);
    void write_text(Alg_update_ptr update, char type);
    void write_binary(int type_byte, const char *msg);
    void write_midi_channel_prefix(Alg_update_ptr update);
    void write_smpteoffset(Alg_update_ptr update, char *s);
    void write_data(int data);
    int to_midi_channel(int channel);
    int to_track(int channel);

    ostream *out_file;

    Alg_seq_ptr seq;

    int num_tracks; // number of tracks not counting tempo track
    int division; // divisions per quarter note, default = 120
    int initial_tempo;

    int timesig_num; // numerator of time signature
    int timesig_den; // denominator of time signature
    double timesig_when; // time of time signature

    int keysig;          // number of sharps (+) or flats (-), -99 for undefined
    char keysig_mode; // 'M' or 'm' for major/minor
    double keysig_when;    // time of key signature

    void write_delta(double event_time);
    void write_varinum(int num);
    void write_16bit(int num);
    void write_24bit(int num);
    void write_32bit(int num);
};

#define ROUND(x) (int) ((x)+0.5)

Alg_smf_write::Alg_smf_write(Alg_seq_ptr a_seq)
{
    out_file = NULL;

    // at 100bpm (a nominal tempo value), we would like a division
    // to represent 1ms of time. So
    // d ticks/beat * 100 beats/min = 60,000 ms/min * 1 tick/ms
    // solving for d, d = 600
    division = 600;         // divisions per quarter note
    timesig_num = timesig_den = 0; // initially undefined
    keysig = -99;
    keysig_mode = 0;
    initial_tempo = 500000;

    seq = a_seq;

    previous_divs = 0; // used to compute deltas for midifile
}

Alg_smf_write::~Alg_smf_write()
{
}

// sorting is quite subtle due to rounding
// For example, suppose times from a MIDI file are exact, but in
// decimal round to TW0.4167 Q0.3333. Since the time in whole notes
// rounded up, this note will start late. Even though the duration
// rounded down, the amount is 1/4 as much because units are quarter
// notes. Therefore, the total roundup is 0.0001 beats. This is
// enough to cause the note to sort later in the queue, perhaps
// coming after a new note-on on the same pitch, and resulting in
// a turning on-off, on-off into on, on, off, off if data is moved
// to Allegro (ascii) format with rounding and then back to SMF.
//
// The solution here is to consider things that round to the same
// tick to be simultaneous. Then, be sure to deal with note-offs
// before note-ons. We're going to do that by using event_queue
// times that are rounded to the nearest tick time. Except note-offs
// are going to go in with times that are 1/4 tick earlier so they
// get scheduled first, but still end up on the same tick.
//
event_queue* push(event_queue *queue, event_queue *event)
{
    // printf("push: %.6g, %c, %d\n", event->time, event->type, event->index);
    if (queue == NULL) {
        event->next = NULL;
        return event;
    }

    event_queue *marker1 = NULL;
    event_queue *marker2 = queue;
    while (marker2 != NULL && marker2->time <= event->time) {
        marker1 = marker2;
        marker2 = marker2->next;
    }
    event->next = marker2;
    if (marker1 != NULL) {
        marker1->next=event;
        return queue;
    } else return event;
}

void print_queue(event_queue *q)
{
    printf("Printing queue. . .\n");
    event_queue *q2=q;
    while (q2) {
        printf("%c at %f ;", q2->type, q2->time);
        q2 = q2->next;
    }
    printf("\nDone printing.\n");
}

void Alg_smf_write::write_note(Alg_note_ptr note, bool on)
{
    double event_time = (on ? note->time : note->time + note->dur);
    write_delta(event_time);

    //printf("deltaDivisions: %d, beats elapsed: %g, on? %c\n", deltaDivisions, note->time, on);

    char chan = char(note->chan & 15);
    int pitch = int(note->pitch + 0.5);
    if (pitch < 0) {
          pitch = pitch % 12;
    } else if (pitch > 127) {
        pitch = (pitch % 12) + 120; // put pitch in 10th octave
        if (pitch > 127) pitch -= 12; // or 9th octave
    }
    out_file->put(0x90 + chan);
    out_file->put(pitch);
    if (on) {
        int vel = (int) note->loud;
        if (vel <= 0) vel = 1;
        write_data(vel);
    } else out_file->put(0); // note-off indicated by velocty zero
}

void Alg_smf_write::write_midi_channel_prefix(Alg_update_ptr update)
{
   if (update->chan >= 0) { // write MIDI Channel Prefix
        write_delta(update->time);
        out_file->put('\xFF'); // Meta Event
        out_file->put('\x20'); // Type code for MIDI Channel Prefix
        out_file->put(1); // length
        out_file->put(to_midi_channel(update->chan));
        // one thing odd about the Std MIDI File spec is that once
        // you turn on MIDI Channel Prefix, there seems to be no
        // way to cancel it unless a non-Meta event shows up. We
        // don't do any analysis to avoid assigning channels to
        // meta events.
    }
}

void Alg_smf_write::write_text(Alg_update_ptr update, char type)
{
    write_midi_channel_prefix(update);
    write_delta(update->time);
    out_file->put('\xFF');
    out_file->put(type);
    out_file->put((char) strlen(update->parameter.s));
    *out_file << update->parameter.s;
}

void Alg_smf_write::write_smpteoffset(Alg_update_ptr update, char *s)
{
    write_midi_channel_prefix(update);
    write_delta(update->time);
    out_file->put('\xFF'); // meta event
    out_file->put('\x54'); // smpte offset type code
    out_file->put(5); // length
    for (int i = 0; i < 5; i++) *out_file << s[i];
}

// write_data - limit data to the range of [0...127] and write it
void Alg_smf_write::write_data(int data)
{
    if (data < 0) data = 0;
    else if (data > 0x7F) data = 0x7F;

    out_file->put(data);
}

int Alg_smf_write::to_midi_channel(int channel)
{
    // allegro track number is stored as multiple of 100
    // also mask off all but 4 channel bits just in case
    if (channels_per_track > 0) channel %= channels_per_track;
    return channel & 0xF;
}

int Alg_smf_write::to_track(int channel)
{
    if (channel == -1) return 0;
    return channel / channels_per_track;
}

static char hex_to_nibble(char c)
{
    if (isalpha(c)) {
        return 10 + (toupper(c) - 'A');
    } else {
        return c - '0';
    }
}

static char hex_to_char(const char *s)
{
    return (hex_to_nibble(s[0]) << 4) + hex_to_nibble(s[1]);
}

void Alg_smf_write::write_binary(int type_byte, const char *msg)
{
    int len = strlen(msg) / 2;
    out_file->put(type_byte);
    write_varinum(len);
    for (int i = 0; i < len; i++) {
        out_file->put(hex_to_char(msg));
        msg += 2;
    }
}

void Alg_smf_write::write_update(Alg_update_ptr update)
{
    const char *name = update->parameter.attr_name();

    /****Non-Meta Events****/
    if (!strcmp(name, "pressurer")) {
        write_delta(update->time);
        if (update->get_identifier() < 0) { // channel pressure message
            out_file->put(0xD0 + to_midi_channel(update->chan));
            write_data((int)(update->parameter.r * 127));
        } else { // just 1 key -- poly pressure
            out_file->put(0xA0 + to_midi_channel(update->chan));
            write_data(update->get_identifier());
            write_data((int)(update->parameter.r * 127));
        }
    } else if (!strcmp(name, "programi")) {
        write_delta(update->time);
        out_file->put(0xC0 + to_midi_channel(update->chan));
        write_data(update->parameter.i);
    } else if (!strcmp(name, "bendr")) {
        int temp = ROUND(0x2000 * (update->parameter.r + 1));
        if (temp > 0x3fff) temp = 0x3fff; // 14 bits maximum
        if (temp < 0) temp = 0;
        int c1 = temp & 0x7F; // low 7 bits
        int c2 = temp >> 7;   // high 7 bits
        write_delta(update->time);
        out_file->put(0xE0 + to_midi_channel(update->chan));
        write_data(c1);
        write_data(c2);
    } else if (!strncmp(name, "control", 7) &&
               update->parameter.attr_type() == 'r') {
      int ctrlnum = atoi(name + 7);
      int val = ROUND(update->parameter.r * 127);
      write_delta(update->time);
      out_file->put(0xB0 + to_midi_channel(update->chan));
      write_data(ctrlnum);
      write_data(val);
    } else if (!strcmp(name, "sysexs") &&
               update->parameter.attr_type() == 's') {
        const char *s = update->parameter.s;
        if (s[0] && s[1] && toupper(s[0]) == 'F' && s[1] == '0') {
            s += 2; // skip the initial "F0" byte in message: it is implied
        }
        write_delta(update->time);
        write_binary(0xF0, s);
    } else if (!strcmp(name, "sqspecifics") &&
               update->parameter.attr_type() == 's') {
        const char *s = update->parameter.s;
        write_delta(update->time);
        out_file->put('\xFF');
        write_binary(0x7F, s);

    /****Text Events****/
    } else if (!strcmp(name, "texts")) {
        write_text(update, 0x01);
    } else if (!strcmp(name, "copyrights")) {
        write_text(update, 0x02);
    } else if (!strcmp(name, "seqnames") || !strcmp(name, "tracknames")) {
        write_text(update, 0x03);
    } else if (!strcmp(name, "instruments")) {
        write_text(update, 0x04);
    } else if (!strcmp(name, "lyrics")) {
        write_text(update, 0x05);
    } else if (!strcmp(name, "markers")) {
        write_text(update, 0x06);
    } else if (!strcmp(name, "cues")) {
        write_text(update, 0x07);
    } else if (!strcmp(name, "miscs")) {
        write_text(update, 0x08);

    /****Other Events****/
    } else if (!strcmp(name, "smpteoffsets")) {
#define decimal(p) (((p)[0] - '0') * 10 + ((p)[1] - '0'))
        // smpteoffset is specified as "24fps:00h:10m:00s:11.00f"
        // the following simple parser does not reject all badly
        // formatted strings, but it should parse good strings ok
        const char *s = update->parameter.s;
        int len = strlen(s);
        char smpteoffset[5];
        if (len < 24) return; // not long enough, must be bad format
        int fps = 0;
        if (s[0] == '2') {
            if (s[1] == '4') fps = 0;
            else if (s[1] == '5') fps = 1;
            else if (s[1] == '9') {
                fps = 2;
                if (len != 27) return; // not right length
                s += 3; // cancel effect of longer string
            }
        } else fps = 3;
        s += 6;   int hours = decimal(s);
        s += 4;   int mins = decimal(s);
        s += 4;   int secs = decimal(s);
        s += 4;   int frames = decimal(s);
        s += 3;   int subframes = decimal(s);
        smpteoffset[0] = (fps << 6) + hours;
        smpteoffset[1] = mins;
        smpteoffset[2] = secs;
        smpteoffset[3] = frames;
        smpteoffset[4] = subframes;
        write_smpteoffset(update, smpteoffset);

    // key signature is special because it takes two events in the Alg_seq
    // structure to make one midi file event. When we encounter one or
    // the other event, we'll just record it in the Alg_smf_write object.
    // After both events are seen, we write the data. (See below.)
    } else if (!strcmp(name, "keysigi")) {
        keysig = update->parameter.i;
        keysig_when = update->time;
    } else if (!strcmp(name, "modea")) {
        if (!strcmp(alg_attr_name(update->parameter.a), "major"))
            keysig_mode = 'M';
        else keysig_mode = 'm';
        keysig_when = update->time;
    }
    if (keysig != -99 && keysig_mode) { // write when both are defined
        write_delta(keysig_when);
        out_file->put('\xFF');
        out_file->put('\x59');
        out_file->put(2);
        // mask off high bits so that this value appears to be positive
        // i.e. -1 -> 0xFF (otherwise, write_data will clip -1 to 0)
        out_file->put(keysig & 0xFF);
        out_file->put(keysig_mode == 'm');
        keysig = -99;
        keysig_mode = false;
    }
    //printf("Update: %s, key: %g\n", update->parameter.attr_name(), update->key);
}

// see notes on event_queue::push, TICK_TIME converts from beat to
// the number of the nearest tick. The second parameter is an offset in
// quarter ticks. By scheduling with -1, note-offs should get dispatched
// first. Note that TICK_TIME only determines the order of events, so
// it is ok to change units from beats to ticks, saving a divide.
#define TICK_TIME(t, o) (ROUND((t) * division) + 0.25 * (o))

void Alg_smf_write::write_track(int i)
{
    int j = 0; // note index
    Alg_events &notes = seq->track_list[i];
    event_queue *pending = NULL;
    if (notes.length() > 0) {
        pending = new event_queue('n', TICK_TIME(notes[j]->time, 0), 0, NULL);
    }
    if (i == 0) { // track 0 may have tempo and timesig info
        if (seq->get_time_map()->last_tempo_flag || seq->get_time_map()->beats.len > 0) {
            pending = push(pending, new event_queue('c', 0.0, 0, NULL));
        }
        if (seq->time_sig.length() > 0) {
            pending = push(pending, new event_queue('s',
                           TICK_TIME(seq->time_sig[0].beat, 0), 0, NULL));
        }
    }
    while (pending) {
        event_queue *current = pending;
        pending = pending->next;
        if (current->type == 'n') {
            Alg_note_ptr n = (Alg_note_ptr) notes[current->index];
            if (n->is_note()) {
                write_note(n, true);
                pending = push(pending, new event_queue('o',
                      TICK_TIME(n->time + n->dur, -1), current->index, NULL));
            } else if (n->is_update()) {
                Alg_update_ptr u = (Alg_update_ptr) n;
                write_update(u);
            }
            int next = current->index + 1;
            if (next < notes.length()) {
                current->time = TICK_TIME(notes[next]->time, 0);
                current->index = next;
                pending = push(pending, current);
            }
        } else if (current->type == 'o') { //note-off
            Alg_note_ptr n = (Alg_note_ptr) notes[current->index];
            write_note(n, false);
            delete current;
        } else if (current->type == 'c') { // tempo change
            write_tempo_change(current->index);
            current->index++; // -R
            if (current->index < seq->get_time_map()->beats.len) {
                current->time =
                    TICK_TIME(seq->get_time_map()->
                              beats[current->index].beat, 0);
                pending = push(pending, current);
            } else {
                delete current;
            }
        } else if (current->type == 's') { // time sig
            write_time_signature(current->index);
            current->index++;
            if (current->index < seq->time_sig.length()) {
                current->time =
                    TICK_TIME(seq->time_sig[current->index].beat, 0);
                pending = push(pending, current);
            } else {
                delete current;
            }
        }
    }
}

void Alg_smf_write::write_tempo(int divs, int tempo)
{
    //    printf("Inserting tempo %f after %f clocks.\n", tempo, delta);
    write_varinum(divs - previous_divs);
    previous_divs = divs;
    out_file->put('\xFF');
    out_file->put('\x51');
    out_file->put('\x03');
    write_24bit((int)tempo);
}

void Alg_smf_write::write_tempo_change(int i)
    //  i is index of tempo map
{
    // extract tempo map
    Alg_beats &b = seq->get_time_map()->beats;
    double tempo;
    long divs;
    if (i < seq->get_time_map()->beats.len - 1) {
        tempo = 1000000 * ((b[i+1].time - b[i].time) /
                           (b[i+1].beat - b[i].beat));
        divs = ROUND(b[i].beat * division);
        write_tempo(divs, ROUND(tempo));
    } else if (seq->get_time_map()->last_tempo_flag) { // write the final tempo
        divs = ROUND(division * b[i].beat);
        tempo = (1000000.0 / seq->get_time_map()->last_tempo);
        write_tempo(divs, ROUND(tempo));
    }
}

void Alg_smf_write::write_time_signature(int i)
{
    Alg_time_sigs &ts = seq->time_sig;
    write_delta(ts[i].beat);
    // write the time signature
    out_file->put('\xFF');
    out_file->put('\x58');  // time signature
    out_file->put('\x04');     // length of message
    out_file->put(ROUND(ts[i].num));
    int den = ROUND(ts[i].den);
    int den_byte = 0;
    while (den > 1) { // compute the log2 of denominator
        den_byte++;
        den >>= 1;
    }
    out_file->put(den_byte);
    out_file->put(24); // clocks per quarter
    out_file->put(8);  // 32nd notes per 24 clocks
}

void Alg_smf_write::write(ostream &file)
{
    int track_len_offset;
    int track_end_offset;
    int track_len;

    out_file = &file;

    // Header
    file << "MThd";

    write_32bit(6); // chunk length

    write_16bit(1); // format 1 MIDI file

    write_16bit(seq->tracks()); // number of tracks
    write_16bit(division); // divisions per quarter note

    // write_ all tracks
    seq->convert_to_beats();
    int i;
    for (i = 0; i < seq->tracks(); i++) {
        previous_divs = 0;
        *out_file << "MTrk";
        track_len_offset = out_file->tellp();
        write_32bit(0); // track len placeholder

        write_track(i);

        // End of track event
        write_varinum(0);           // delta time
        out_file->put('\xFF');
        out_file->put('\x2F');
        out_file->put('\x00');

        // Go back and write in the length of the track
        track_end_offset = out_file->tellp();
        track_len = track_end_offset - track_len_offset - 4;
        out_file->seekp(track_len_offset);
        write_32bit(track_len);
        out_file->seekp(track_end_offset);
    }
}

void Alg_smf_write::write_16bit(int num)
{
    out_file->put((num & 0xFF00) >> 8);
    out_file->put(num & 0xFF);
}

void Alg_smf_write::write_24bit(int num)
{
    out_file->put((num & 0xFF0000) >> 16);
    out_file->put((num & 0xFF00) >> 8);
    out_file->put((num & 0xFF));
}

void Alg_smf_write::write_32bit(int num)
{
    out_file->put((num & 0xFF000000) >> 24);
    out_file->put((num & 0xFF0000) >> 16);
    out_file->put((num & 0xFF00) >> 8);
    out_file->put((num & 0xFF));
}

void Alg_smf_write::write_delta(double event_time)
{
    // divisions is ideal absolute time in divisions
    long divisions = ROUND(division * event_time);
    long delta_divs = divisions - previous_divs;
    write_varinum(delta_divs);
    previous_divs = divisions;
}

void Alg_smf_write::write_varinum(int value)
{
  if(value<0) value=0;//this line should not have to be here!
  int buffer;

  buffer = value & 0x7f;
  while ((value >>= 7) > 0) {
    buffer <<= 8;
    buffer |= 0x80;
    buffer += (value & 0x7f);
  }

  for(;;) {
    out_file->put(buffer);
    if (buffer & 0x80)
      buffer >>= 8;
    else
      break;
  }
}

void Alg_seq::smf_write(ostream &file)
{
    Alg_smf_write writer(this);
    writer.write(file);
}

bool Alg_seq::smf_write(const char *filename)
{
    ofstream outf(filename, ios::binary | ios::out);
    if (outf.fail()) return false;
    smf_write(outf);
    outf.close();
    return true;
}


code:
// allegrowr.cpp -- write sequence to an Allegro file (text)

#include "assert.h"
#include "stdlib.h"
#include <iostream>
#include <fstream>
#include <iomanip>
#include <errno.h>
#include <string>
#include "memory.h"
using namespace std;
#include "strparse.h"
#include "allegro.h"

// Note about precision: %g prints 6 significant digits. For 1ms precision,
// the maximum magnitude is 999.999, i.e. 1000s < 17minutes. For anything
// over 1000s, time in seconds will be printed with 10ms precision, which
// is not good. Therefore, times and durations are printed as %.4d, which
// gives 100us precision.
// The following define allows you to change this decision:
/* #define TIMFMT "%.4d" */
#define TIMPREC 4
#define TIMFMT fixed << setprecision(TIMPREC)
#define GFMT resetiosflags(ios::floatfield) << setprecision(6)

void parameter_print(ostream &file, Alg_parameter_ptr p)
{
    file << " -" << p->attr_name() << ":";
    switch (p->attr_type()) {
    case 'a':
        file << "'" << alg_attr_name(p->a) << "'";
        break;
    case 'i':
        file << p->i;
        break;
    case 'l':
        file << (p->l ? "true" : "false");
        break;
    case 'r':
        file << p->r;
        break;
    case 's': {
        string str;
        string_escape(str, p->s, "\"");
        file << str;
        break;
    }
    }
}

Alg_event_ptr Alg_seq::write_track_name(ostream &file, int n,
                                        Alg_events &events)
// write #track <n> <trackname-or-sequencename>
// if we write the name on the "#track" line, then we do *not* want
// to write again as an update: "-seqnames:"Jordu", so if we do
// find a name and write it, return a pointer to it so the track
// writer knows what update (if any) to skip
{
    Alg_event_ptr e = NULL; // e is the result, default is NULL
    file << "#track " << n;
    const char *attr = symbol_table.insert_string(
                               n == 0 ? "seqnames" : "tracknames");
    // search for name in events with timestamp of 0
    for (int i = 0; i < events.length(); i++) {
        Alg_event_ptr ue = events[i];
        if (ue->time > 0) break;
        if (ue->is_update()) {
            Alg_update_ptr u = (Alg_update_ptr) ue;
            if (u->parameter.attr == attr) {
                file << " " << u->parameter.s;
                e = ue; // return the update event we found
                break;
            }
        }
    }
    file << endl; // end of line containing #track [<name>]
    return e; // return parameter event with name if one was found
}

void Alg_seq::write(ostream &file, bool in_secs, double offset)
{
    int i, j;
    if (in_secs) convert_to_seconds();
    else convert_to_beats();
    file << "#offset " << offset << endl;
    Alg_event_ptr update_to_skip = write_track_name(file, 0, track_list[0]);
    Alg_beats &beats = time_map->beats;
    for (i = 0; i < beats.len - 1; i++) {
        Alg_beat_ptr b = &(beats[i]);
        if (in_secs) {
            file << "T" << TIMFMT << b->time;
        } else {
            file << "TW" << TIMFMT << b->beat / 4;
        }
        double tempo = (beats[i + 1].beat - b->beat) /
                       (beats[i + 1].time - beats[i].time);
        file << " -tempor:" << GFMT << tempo * 60 << "\n";
    }
    if (time_map->last_tempo_flag) { // we have final tempo:
        Alg_beat_ptr b = &(beats[beats.len - 1]);
        if (in_secs) {
            file << "T" << TIMFMT << b->time;
        } else {
            file << "TW" << TIMFMT << b->beat / 4;
        }
        file << " -tempor:" << GFMT << time_map->last_tempo * 60.0 << "\n";
    }

    // write the time signatures
    for (i = 0; i < time_sig.length(); i++) {
        Alg_time_sig &ts = time_sig[i];
        double time = ts.beat;
        if (in_secs) {
            file << "T" << TIMFMT << time << " V- -timesig_numr:" <<
                    GFMT << ts.num << "\n";
            file << "T" << TIMFMT << time << " V- -timesig_denr:" <<
                    GFMT << ts.den << "\n";
        } else {
            double wholes = ts.beat / 4;
            file << "TW" << TIMFMT << wholes << " V- -timesig_numr:" <<
                    GFMT << ts.num << "\n";
            file << "TW" << TIMFMT << wholes << " V- -timesig_denr:" <<
                    GFMT << ts.den << "\n";
        }
    }

    for (j = 0; j < track_list.length(); j++) {
        Alg_events &notes = track_list[j];
        if (j != 0) update_to_skip = write_track_name(file, j, notes);
        // now write the notes at beat positions
        for (i = 0; i < notes.length(); i++) {
            Alg_event_ptr e = notes[i];
            // if we already wrote this event as a track or sequence name,
            // do not write it again
            if (e == update_to_skip) continue;
            double start = e->time;
            if (in_secs) {
                file << "T" << TIMFMT << start;
            } else {
                file << "TW" << TIMFMT << start / 4;
            }
            // write the channel as Vn or V-
            if (e->chan == -1) file << " V-";
            else file << " V" << e->chan;
            // write the note or update data
            if (e->is_note()) {
                Alg_note_ptr n = (Alg_note_ptr) e;
                double dur = n->dur;
                file << " K" << n->get_identifier() <<
                        " P" << GFMT << n->pitch;
                if (in_secs) {
                    file << " U" << TIMFMT << dur;
                } else {
                    file << " Q" << TIMFMT << dur;
                }
                file << " L" << GFMT << n->loud;
                Alg_parameters_ptr p = n->parameters;
                while (p) {
                    parameter_print(file, &(p->parm));
                    p = p->next;
                }
            } else { // an update
                assert(e->is_update());
                Alg_update_ptr u = (Alg_update_ptr) e;
                if (u->get_identifier() != -1) {
                    file << " K" << u->get_identifier();
                }
                parameter_print(file, &(u->parameter));
            }
            file << "\n";
        }
    }
}

bool Alg_seq::write(const char *filename, double offset)
{
    ofstream file(filename);
    if (file.fail()) return false;
    write(file, units_are_seconds, offset);
    file.close();
    return true;
}

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * Read a Standard MIDI File.  Externally-assigned function pointers are
 * called upon recognizing things in the file.  See midifile(3).
 */

/*****************************************************************************
*       Change Log
*       Date    | who : Change
*-----------+-----------------------------------------------------------------
*  2-Mar-92 | GWL : created changelog; MIDIFILE_ERROR to satisfy compiler
*****************************************************************************/

#include "stdio.h"
#include "mfmidi.h"
#include "string.h"
#include "assert.h"

#define MIDIFILE_ERROR -1

/* public stuff */
extern int abort_flag;

void Midifile_reader::midifile()
{
    int ntrks;
    midifile_error = 0;

    ntrks = readheader();
    if (midifile_error) return;
    if (ntrks <= 0) {
        mferror("No tracks!");
        /* no need to return since midifile_error is set */
    }
    while (ntrks-- > 0 && !midifile_error) readtrack();
}

int Midifile_reader::readmt(const char *s, int skip)
    /* read through the "MThd" or "MTrk" header string */
    /* if skip == 1, we attempt to skip initial garbage. */
{
    assert(strlen(s) == 4); // must be "MThd" or "MTrk"
    int nread = 0;
    char b[4];
    char buff[32];
    int c;
    const char *errmsg = "expecting ";

    retry:
    while ( nread<4 ) {
        c = Mf_getc();
        if ( c == EOF ) {
            errmsg = "EOF while expecting ";
            goto err;
        }
        b[nread++] = c;
    }
    /* See if we found the 4 characters we're looking for */
    if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] )
        return(0);
    if ( skip ) {
        /* If we are supposed to skip initial garbage, */
        /* try again with the next character. */
        b[0]=b[1];
        b[1]=b[2];
        b[2]=b[3];
        nread = 3;
        goto retry;
    }
    err:
//#pragma warning(disable: 4996) // strcpy is safe since strings have known lengths
    (void) strcpy(buff,errmsg);
    (void) strcat(buff,s);
//#pragma warning(default: 4996) // turn it back on
    mferror(buff);
    return(0);
}

int Midifile_reader::egetc()
    /* read a single character and abort on EOF */
{
    int c = Mf_getc();

    if ( c == EOF ) {
        mferror("premature EOF");
        return EOF;
    }
    Mf_toberead--;
    return(c);
}

int Midifile_reader::readheader()
    /* read a header chunk */
{
    int format, ntrks, division;

    if ( readmt("MThd",Mf_skipinit) == EOF )
        return(0);

    Mf_toberead = read32bit();
    if (midifile_error) return MIDIFILE_ERROR;
    format = read16bit();
    if (midifile_error) return MIDIFILE_ERROR;
    ntrks = read16bit();
    if (midifile_error) return MIDIFILE_ERROR;
    division = read16bit();
    if (midifile_error) return MIDIFILE_ERROR;

    Mf_header(format,ntrks,division);

    /* flush any extra stuff, in case the length of header is not 6 */
    while ( Mf_toberead > 0 && !midifile_error)
        (void) egetc();
    return(ntrks);
}

void Midifile_reader::readtrack()
    /* read a track chunk */
{
    /* This array is indexed by the high half of a status byte.  It's */
    /* value is either the number of bytes needed (1 or 2) for a channel */
    /* message, or 0 (meaning it's not  a channel message). */
    static int chantype[] = {
        0, 0, 0, 0, 0, 0, 0, 0,       /* 0x00 through 0x70 */
        2, 2, 2, 2, 1, 1, 2, 0        /* 0x80 through 0xf0 */
    };
    long lookfor, lng;
    int c, c1, type;
    int sysexcontinue = 0;  /* 1 if last message was an unfinished sysex */
    int running = 0;        /* 1 when running status used */
    int status = 0;         /* (possibly running) status byte */
    int needed;

    if ( readmt("MTrk",0) == EOF )
        return;

    Mf_toberead = read32bit();

    if (midifile_error) return;

    Mf_currtime = 0L;

    Mf_starttrack();

    while ( Mf_toberead > 0 ) {

        Mf_currtime += readvarinum();   /* delta time */
        if (midifile_error) return;

        c = egetc();
       if (midifile_error) return;

        if ( sysexcontinue && c != 0xf7 ) {
            mferror("didn't find expected continuation of a sysex");
            return;
        }
        if ( (c & 0x80) == 0 ) {        /* running status? */
            if ( status == 0 ) {
                mferror("unexpected running status");
                return;
            }
            running = 1;
       } else {
            status = c;
            running = 0;
        }

        needed = chantype[ (status>>4) & 0xf ];

        if ( needed ) {        /* ie. is it a channel message? */

            if ( running )
                c1 = c;
            else {
                c1 = egetc();
                if (midifile_error) return;
            }
            chanmessage( status, c1, (needed>1) ? egetc() : 0 );
            if (midifile_error) return;
            continue;;
        }

        switch ( c ) {

        case 0xff:                  /* meta event */

            type = egetc();
            if (midifile_error) return;
            /* watch out - Don't combine the next 2 statements */
            lng = readvarinum();
            if (midifile_error) return;
            lookfor = Mf_toberead - lng;
            msginit();

            while ( Mf_toberead > lookfor ) {
                unsigned char c = egetc();
                if (midifile_error) return;
                msgadd(c);
            }
            metaevent(type);
            break;

        case 0xf0:            /* start of system exclusive */

            /* watch out - Don't combine the next 2 statements */
            lng = readvarinum();
            if (midifile_error) return;
            lookfor = Mf_toberead - lng;
            msginit();
            msgadd(0xf0);

            while ( Mf_toberead > lookfor ) {
                c = egetc();
                if (midifile_error) return;
                msgadd(c);
            }
            if ( c==0xf7 || Mf_nomerge==0 )
                sysex();
            else
                sysexcontinue = 1;  /* merge into next msg */
            break;

        case 0xf7:     /* sysex continuation or arbitrary stuff */

            /* watch out - Don't combine the next 2 statements */
            lng = readvarinum();
            if (midifile_error) return;
            lookfor = Mf_toberead - lng;

            if ( ! sysexcontinue )
                msginit();

            while ( Mf_toberead > lookfor ) {
                c = egetc();
                if (midifile_error) return;
                msgadd(c);
            }
            if ( ! sysexcontinue ) {
                    Mf_arbitrary(msgleng(), msg());
            }
            else if ( c == 0xf7 ) {
                sysex();
                sysexcontinue = 0;
            }
            break;
        default:

            badbyte(c);

            break;
        }
    }
        Mf_endtrack();
    return;
}

void Midifile_reader::badbyte(int c)
{
    char buff[32];
//#pragma warning(disable: 4996) // safe in this case
    (void) sprintf(buff,"unexpected byte: 0x%02x",c);
//#pragma warning(default: 4996)
    mferror(buff);
}

void Midifile_reader::metaevent(int type)
{
    int leng = msgleng();
    // made this unsigned to avoid sign extend
    unsigned char *m = msg();

    switch ( type ) {
    case 0x00:
        Mf_seqnum(to16bit(m[0],m[1]));
        break;
    case 0x01:     /* Text event */
    case 0x02:     /* Copyright notice */
    case 0x03:     /* Sequence/Track name */
    case 0x04:     /* Instrument name */
    case 0x05:     /* Lyric */
    case 0x06:     /* Marker */
    case 0x07:     /* Cue point */
    case 0x08:
    case 0x09:
    case 0x0a:
    case 0x0b:
    case 0x0c:
    case 0x0d:
    case 0x0e:
    case 0x0f:
        /* These are all text events */
        Mf_text(type,leng,m);
        break;
    case 0x20:
        Mf_chanprefix(m[0]);
        break;
    case 0x21:
        Mf_portprefix(m[0]);
        break;
    case 0x2f:     /* End of Track */
        Mf_eot();
        break;
    case 0x51:     /* Set tempo */
        Mf_tempo(to32bit(0,m[0],m[1],m[2]));
        break;
    case 0x54:
        Mf_smpte(m[0],m[1],m[2],m[3],m[4]);
        break;
    case 0x58:
        Mf_timesig(m[0],m[1],m[2],m[3]);
        break;
    case 0x59:
        Mf_keysig(m[0],m[1]);
        break;
    case 0x7f:
        Mf_sqspecific(leng,m);
        break;
    default:
        Mf_metamisc(type,leng,m);
    }
}

void Midifile_reader::sysex()
{
    Mf_sysex(msgleng(), msg());
}

void Midifile_reader::chanmessage(int status, int c1, int c2)
{
    int chan = status & 0xf;

    switch ( status & 0xf0 ) {
    case NOTEOFF:
        Mf_off(chan,c1,c2);
        break;
    case NOTEON:
        Mf_on(chan,c1,c2);
        break;
    case PRESSURE:
        Mf_pressure(chan,c1,c2);
        break;
    case CONTROLLER:
        Mf_controller(chan,c1,c2);
        break;
    case PITCHBEND:
        Mf_pitchbend(chan,c1,c2);
        break;
    case PROGRAM:
        Mf_program(chan,c1);
        break;
    case CHANPRESSURE:
        Mf_chanpressure(chan,c1);
        break;
    }
}

/* readvarinum - read a varying-length number, and return the */
/* number of characters it took. */

long Midifile_reader::readvarinum()
{
    long value;
    int c;

    c = egetc();
    if (midifile_error) return 0;

    value = (long) c;
    if ( c & 0x80 ) {
        value &= 0x7f;
        do {
            c = egetc();
            if (midifile_error) return 0;
            value = (value << 7) + (c & 0x7f);
        } while (c & 0x80);
    }
    return (value);
}

long Midifile_reader::to32bit(int c1, int c2, int c3, int c4)
{
    long value = 0L;

    value = (c1 & 0xff);
    value = (value<<8) + (c2 & 0xff);
    value = (value<<8) + (c3 & 0xff);
    value = (value<<8) + (c4 & 0xff);
    return (value);
}

int Midifile_reader::to16bit(int c1, int c2)
{
    return ((c1 & 0xff ) << 8) + (c2 & 0xff);
}

long Midifile_reader::read32bit()
{
    int c1, c2, c3, c4;

    c1 = egetc(); if (midifile_error) return 0;
    c2 = egetc(); if (midifile_error) return 0;
    c3 = egetc(); if (midifile_error) return 0;
    c4 = egetc(); if (midifile_error) return 0;
    return to32bit(c1,c2,c3,c4);
}

int Midifile_reader::read16bit()
{
    int c1, c2;
    c1 = egetc(); if (midifile_error) return 0;
    c2 = egetc(); if (midifile_error) return 0;
    return to16bit(c1,c2);
}

void Midifile_reader::mferror(char *s)
{
    Mf_error(s);
    midifile_error = 1;
}

void Midifile_reader::mferror(const char *s)
{
    mferror(const_cast<char *>(s));
}

/* The code below allows collection of a system exclusive message of */
/* arbitrary length.  The Msgbuff is expanded as necessary.  The only */
/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */

#define MSGINCREMENT 128

Midifile_reader::Midifile_reader()
{
    Mf_nomerge = 0;
    Mf_currtime = 0L;
    Mf_skipinit = 0;
    Mf_toberead = 0;

    Msgbuff = 0;      /* message buffer */
    Msgsize = 0;        /* Size of currently allocated Msg */
    Msgindex = 0;       /* index of next available location in Msg */
}

void Midifile_reader::finalize()
{
    if (Msgbuff) Mf_free(Msgbuff, Msgsize);
    Msgbuff = NULL;
}

void Midifile_reader::msginit()
{
    Msgindex = 0;
}

unsigned char *Midifile_reader::msg()
{
    return(Msgbuff);
}

int Midifile_reader::msgleng()
{
    return(Msgindex);
}

void Midifile_reader::msgadd(int c)
{
    /* If necessary, allocate larger message buffer. */
    if ( Msgindex >= Msgsize )
        msgenlarge();
    Msgbuff[Msgindex++] = c;
}

void Midifile_reader::msgenlarge()
{
    unsigned char *newmess;
    unsigned char *oldmess = Msgbuff;
    int oldleng = Msgsize;

    Msgsize += MSGINCREMENT;
    newmess = (unsigned char *) Mf_malloc((sizeof(unsigned char) * Msgsize) );

    /* copy old message into larger new one */
    if ( oldmess != 0 ) {
        memcpy(newmess, oldmess, oldleng);
        Mf_free(oldmess, oldleng);
    }
    Msgbuff = newmess;
}


code:
#define NOTEOFF 0x80
#define NOTEON 0x90
#define PRESSURE 0xa0
#define CONTROLLER 0xb0
#define PITCHBEND 0xe0
#define PROGRAM 0xc0
#define CHANPRESSURE 0xd0

/* These are the strings used in keynote to identify Standard MIDI File */
/* meta text messages. */

#define METATEXT                "Text Event"
#define METACOPYRIGHT           "Copyright Notice"
#define METASEQUENCE            "Sequence/Track Name"
#define METAINSTRUMENT          "Instrument Name"
#define METALYRIC               "Lyric"
#define METAMARKER              "Marker"
#define METACUE                 "Cue Point"
#define METAUNRECOGNIZED        "Unrecognized"

class Midifile_reader {
public:
    void midifile();
    int Mf_nomerge; /* 1 => continue'ed system exclusives are */
				        /* not collapsed. */
    long Mf_currtime; /* current time in delta-time units */
    int Mf_skipinit;   /* 1 if initial garbage should be skipped */
    Midifile_reader();
	// call finalize() when done or you may leak memory.
	void finalize();  /* clean up before deletion */
	// Note: rather than finalize, we should have ~Midifile_reader(),
	// but at least VC++ complains that there is no Mf_free(), even
	// though Mf_free is declared as virtual and this is an abstract
	// class. I don't understand this, so finalize() is a workaround. -RBD

protected:
    int midifile_error;

    virtual void *Mf_malloc(size_t size) = 0; /* malloc() */
    virtual void Mf_free(void *obj, size_t size) = 0; /* free() */
    /* Methods to be called while processing the MIDI file. */
    virtual void Mf_starttrack() = 0;
    virtual void Mf_endtrack() = 0;
    virtual int Mf_getc() = 0;
    virtual void Mf_chanprefix(int) = 0;
    virtual void Mf_portprefix(int) = 0;
    virtual void Mf_eot() = 0;
    virtual void Mf_error(char *) = 0;
    virtual void Mf_header(int,int,int) = 0;
    virtual void Mf_on(int,int,int) = 0;
    virtual void Mf_off(int,int,int) = 0;
    virtual void Mf_pressure(int,int,int) = 0;
    virtual void Mf_controller(int,int,int) = 0;
    virtual void Mf_pitchbend(int,int,int) = 0;
    virtual void Mf_program(int,int) = 0;
    virtual void Mf_chanpressure(int,int) = 0;
    virtual void Mf_sysex(int,unsigned char*) = 0;
    virtual void Mf_arbitrary(int,unsigned char*) = 0;
    virtual void Mf_metamisc(int,int,unsigned char*) = 0;
    virtual void Mf_seqnum(int) = 0;
    virtual void Mf_smpte(int,int,int,int,int) = 0;
    virtual void Mf_timesig(int,int,int,int) = 0;
    virtual void Mf_tempo(int) = 0;
    virtual void Mf_keysig(int,int) = 0;
    virtual void Mf_sqspecific(int,unsigned char*) = 0;
    virtual void Mf_text(int,int,unsigned char*) = 0;

private:
    long Mf_toberead;

    long readvarinum();
    long read32bit();
    int read16bit();
    void msgenlarge();
    unsigned char *msg();
    int readheader();
    void readtrack();
    void sysex();
    void msginit();
    int egetc();
    int msgleng();

    int readmt(const char*,int);
    long to32bit(int,int,int,int);
    int to16bit(int,int);
    void mferror(char *);
    void mferror(const char *);
    void badbyte(int);
    void metaevent(int);
    void msgadd(int);
    void chanmessage(int,int,int);

    unsigned char *Msgbuff;
    long Msgsize;
    long Msgindex;
};


code:
#include <string>
#include <cstring>
// #include <iostream>  -- for debugging (cout)
#include "ctype.h"
using namespace std;
#include "strparse.h"

void String_parse::skip_space()
{
    while ((*str)[pos] && isspace((*str)[pos])) {
        pos = pos + 1;
    }
}

char String_parse::peek()
{
    return (*str)[pos];
}

void String_parse::get_nonspace_quoted(string &field)
{
    field.clear();
    skip_space();
    bool quoted = false;
    if ((*str)[pos] == '"') {
        quoted = true;
        field.append(1, '"');
        pos = pos + 1;
    }
    while ((*str)[pos] && (quoted || !isspace((*str)[pos]))) {
        if ((*str)[pos] == '"') {
            if (quoted) {
                field.append(1, '"');
                pos = pos + 1;
            }
            return;
        }
        if ((*str)[pos] == '\\') {
            pos = pos + 1;
        }
        if ((*str)[pos]) {
            field.append(1, (*str)[pos]);
            pos = pos + 1;
        }
    }
}

static const char *const escape_chars[] = {"\\n", "\\t", "\\\\", "\\r", "\\\""};

void string_escape(string &result, const char *str, const char *quote)
{
    int length = (int) strlen(str);
    if (quote[0]) {
        result.append(1, quote[0]);
    }
    for (int i = 0; i < length; i++) {
        if (!isalnum((unsigned char) str[i])) {
            const char *const chars = "\n\t\\\r\"";
            const char *const special = strchr(chars, str[i]);
            if (special) {
                result.append(escape_chars[special - chars]);
            } else {
                result.append(1, str[i]);
            }
        } else {
            result.append(1, str[i]);
        }
    }
    result.append(1, quote[0]);
}

void String_parse::get_remainder(std::string &field)
{
    field.clear();
    skip_space();
    int len = str->length() - pos;
    if ((len > 0) && ((*str)[len - 1] == '\n')) { // if str ends in newline,
        len--; // reduce length to ignore newline
    }
    field.insert(0, *str, pos, len);
}


code:
// strparse.h -- header for String_parse class

class String_parse {
public:
    int pos;
    std::string *str;
    void init(std::string *s) {
        str = s;
        pos = 0;
    }
    void skip_space();
    char peek();
    void get_nonspace_quoted(std::string &field);
    // get the remaining characters, skipping initial spaces and final return
    void get_remainder(std::string &field);
};

void string_escape(std::string &result, const char *s, const char *quote);


code:
// trace.cpp -- debugging print function
//
// (I think this was created to provide a generic print function
// for use in non-command-line Windows applications where printf
// does not work. Currently, it is not used, but kept around for
// possible debugging needs. -RBD)

#include "stdarg.h"
#include "stdio.h"
#include "crtdbg.h"

void trace(char *format, ...)
{
    char msg[256];
    va_list args;
    va_start(args, format);
    _vsnprintf_s(msg, 256, _TRUNCATE, format, args);
    va_end(args);
#ifdef _DEBUG
    _CrtDbgReport(_CRT_WARN, NULL, NULL, NULL, msg);
#else
    printf(msg);
#endif
}


code: void trace(char *format, ...);


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(multitapecho MultitapEcho.cpp MultitapEchoControls.cpp MultitapEchoControlDialog.cpp MOCFILES MultitapEchoControls.h MultitapEchoControlDialog.h EMBEDDED_RESOURCES artwork.png graph_bg.png logo.png)


code:
/*
 * MultitapEcho.cpp - a multitap echo delay plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "MultitapEcho.h"
#include "embed.h"
#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT multitapecho_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Multitap Echo",
	QT_TRANSLATE_NOOP( "PluginBrowser", "A multitap echo delay plugin" ),
	"Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>",
	0x0100,
	Plugin::Effect,
	new PluginPixmapLoader( "logo" ),
	NULL,
	NULL
} ;

}

MultitapEchoEffect::MultitapEchoEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ) :
	Effect( &multitapecho_plugin_descriptor, parent, key ),
	m_stages( 1 ),
	m_controls( this ),
	m_buffer( 16100.0f ),
	m_sampleRate( Engine::mixer()->processingSampleRate() ),
	m_sampleRatio( 1.0f / m_sampleRate )
{
	m_work = MM_ALLOC( sampleFrame, Engine::mixer()->framesPerPeriod() );
	m_buffer.reset();
	m_stages = static_cast<int>( m_controls.m_stages.value() );
	updateFilters( 0, 19 );
}

MultitapEchoEffect::~MultitapEchoEffect()
{
	MM_FREE( m_work );
}

void MultitapEchoEffect::updateFilters( int begin, int end )
{
	for( int i = begin; i <= end; ++i )
	{
		for( int s = 0; s < m_stages; ++s )
		{
			setFilterFreq( m_lpFreq[i] * m_sampleRatio, m_filter[i][s] );
		}
	}
}

void MultitapEchoEffect::runFilter( sampleFrame * dst, sampleFrame * src, StereoOnePole & filter, const fpp_t frames )
{
	for( int f = 0; f < frames; ++f )
	{
		dst[f][0] = filter.update( src[f][0], 0 );
		dst[f][1] = filter.update( src[f][1], 1 );
	}
}

bool MultitapEchoEffect::processAudioBuffer( sampleFrame * buf, const fpp_t frames )
{
	if( !isEnabled() || !isRunning () )
	{
		return( false );
	}

	double outSum = 0.0;
	const float d = dryLevel();
	const float w = wetLevel();

	// get processing vars
	const int steps = m_controls.m_steps.value();
	const float stepLength = m_controls.m_stepLength.value();
	const float dryGain = dbfsToAmp( m_controls.m_dryGain.value() );
	const bool swapInputs = m_controls.m_swapInputs.value();

	// check if number of stages has changed
	if( m_controls.m_stages.isValueChanged() )
	{
		m_stages = static_cast<int>( m_controls.m_stages.value() );
		updateFilters( 0, steps - 1 );
	}

	// add dry buffer - never swap inputs for dry
	m_buffer.writeAddingMultiplied( buf, 0, frames, dryGain );

	// swapped inputs?
	if( swapInputs )
	{
		float offset = stepLength;
		for( int i = 0; i < steps; ++i ) // add all steps swapped
		{
			for( int s = 0; s < m_stages; ++s )
			{
				runFilter( m_work, buf, m_filter[i][s], frames );
			}
			m_buffer.writeSwappedAddingMultiplied( m_work, offset, frames, m_amp[i] );
			offset += stepLength;
		}
	}
	else
	{
		float offset = stepLength;
		for( int i = 0; i < steps; ++i ) // add all steps
		{
			for( int s = 0; s < m_stages; ++s )
			{
				runFilter( m_work, buf, m_filter[i][s], frames );
			}
			m_buffer.writeAddingMultiplied( m_work, offset, frames, m_amp[i] );
			offset += stepLength;
		}
	}

	// pop the buffer and mix it into output
	m_buffer.pop( m_work );

	for( int f = 0; f < frames; ++f )
	{
		buf[f][0] = d * buf[f][0] + w * m_work[f][0];
		buf[f][1] = d * buf[f][1] + w * m_work[f][1];
		outSum += buf[f][0]*buf[f][0] + buf[f][1]*buf[f][1];
	}

	checkGate( outSum / frames );

	return isRunning();
}

extern "C"
{

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model* parent, void* data )
{
	return new MultitapEchoEffect( parent, static_cast<const Plugin::Descriptor::SubPluginFeatures::Key *>( data ) );
}

}


code:
/*
 * MultitapEcho.h - a multitap echo delay plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef MULTITAP_ECHO_H
#define MULTITAP_ECHO_H

#include "Effect.h"
#include "MultitapEchoControls.h"
#include "ValueBuffer.h"
#include "RingBuffer.h"
#include "lmms_math.h"
#include "BasicFilters.h"

class MultitapEchoEffect : public Effect
{
public:
	MultitapEchoEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key );
	virtual ~MultitapEchoEffect();
	virtual bool processAudioBuffer( sampleFrame* buf, const fpp_t frames );

	virtual EffectControls* controls()
	{
		return &m_controls;
	}

private:
	void updateFilters( int begin, int end );
	void runFilter( sampleFrame * dst, sampleFrame * src, StereoOnePole & filter, const fpp_t frames );

	inline void setFilterFreq( float fc, StereoOnePole & f )
	{
		const float b1 = expf( -2.0f * F_PI * fc );
		f.setCoeffs( 1.0f - b1, b1 );
	}

	int m_stages;

	MultitapEchoControls m_controls;

	float m_amp [32];
	float m_lpFreq [32];

	RingBuffer m_buffer;
	StereoOnePole m_filter [32][4];

	float m_sampleRate;
	float m_sampleRatio;

	sampleFrame * m_work;

	friend class MultitapEchoControls;

};

#endif


code:
/*
 * MultitapEchoControlDialog.cpp - a multitap echo delay plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QLayout>

#include "MultitapEchoControlDialog.h"
#include "MultitapEchoControls.h"
#include "embed.h"
#include "Graph.h"
#include "PixmapButton.h"
#include "ToolTip.h"
#include "LedCheckbox.h"
#include "Knob.h"
#include "TempoSyncKnob.h"
#include "LcdSpinBox.h"

MultitapEchoControlDialog::MultitapEchoControlDialog( MultitapEchoControls * controls ) :
	EffectControlDialog( controls )
{
	setAutoFillBackground( true );
	QPalette pal;
	pal.setBrush( backgroundRole(),	PLUGIN_NAME::getIconPixmap( "artwork" ) );
	setPalette( pal );
	setFixedSize( 245, 300 );

	// graph widgets

	Graph * ampGraph = new Graph( this, Graph::BarStyle, 204, 105 );
	Graph * lpGraph = new Graph( this, Graph::BarStyle, 204, 105 );

	ampGraph->move( 30, 10 );
	lpGraph->move( 30, 125 );

	ampGraph->setModel( & controls->m_ampGraph );
	lpGraph->setModel( & controls->m_lpGraph );

	pal = QPalette();
	pal.setBrush( backgroundRole(),	PLUGIN_NAME::getIconPixmap("graph_bg") );

	ampGraph->setAutoFillBackground( true );
	ampGraph->setPalette( pal );
	ampGraph->setGraphColor( QColor( 11, 213, 86) );
	ampGraph -> setMaximumSize( 204, 105 );

	lpGraph->setAutoFillBackground( true );
	lpGraph->setPalette( pal );
	lpGraph->setGraphColor( QColor( 0, 200, 187) );
	lpGraph -> setMaximumSize( 204, 105 );

	// steps spinbox

	LcdSpinBox * steps = new LcdSpinBox( 2, this, "Steps" );
	steps->move( 20, 245 );
	steps->setModel( & controls->m_steps );

	// knobs

	TempoSyncKnob * stepLength = new TempoSyncKnob( knobBright_26, this );
	stepLength->move( 100, 245 );
	stepLength->setModel( & controls->m_stepLength );
	stepLength->setLabel( tr( "Length" ) );
	stepLength->setHintText( tr( "Step length:" ) , " ms" );

	Knob * dryGain = new Knob( knobBright_26, this );
	dryGain->move( 150, 245 );
	dryGain->setModel( & controls->m_dryGain );
	dryGain->setLabel( tr( "Dry" ) );
	dryGain->setHintText( tr( "Dry gain:" ) , " dBFS" );

	Knob * stages = new Knob( knobBright_26, this );
	stages->move( 200, 245 );
	stages->setModel( & controls->m_stages );
	stages->setLabel( tr( "Stages" ) );
	stages->setHintText( tr( "Low-pass stages:" ) , "x" );
	// switch led

	LedCheckBox * swapInputs = new LedCheckBox( "Swap inputs", this, tr( "Swap inputs" ), LedCheckBox::Green );
	swapInputs->move( 20, 275 );
	swapInputs->setModel( & controls->m_swapInputs );
	ToolTip::add( swapInputs, tr( "Swap left and right input channels for reflections" ) );
}


code:
/*
 * MultitapEchoControlDialog.h - a multitap echo delay plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef MULTITAP_ECHO_CONTROL_DIALOG_H
#define MULTITAP_ECHO_CONTROL_DIALOG_H

#include "EffectControlDialog.h"

class MultitapEchoControls;

class MultitapEchoControlDialog : public EffectControlDialog
{
	Q_OBJECT
public:
	MultitapEchoControlDialog( MultitapEchoControls * controls );
	virtual ~MultitapEchoControlDialog()
	{
	}
};

#endif


code:
/*
 * MultitapEchoControls.cpp - a multitap echo delay plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDomElement>
#include <QVarLengthArray>

#include "MultitapEchoControls.h"
#include "MultitapEcho.h"
#include "lmms_math.h"
#include "base64.h"

MultitapEchoControls::MultitapEchoControls( MultitapEchoEffect * eff ) :
	EffectControls( eff ),
	m_effect( eff ),
	m_steps( 16, 4, 32, this, "Steps" ),
	m_stepLength( 100.0f, 1.0f, 500.0f, 0.1f, 500.0f, this, "Step length" ),
	m_dryGain( 0.0f, -80.0f, 20.0f, 0.1f, this, "Dry gain" ),
	m_swapInputs( false, this, "Swap inputs" ),
	m_stages( 1.0f, 1.0f, 4.0f, 1.0f, this, "Lowpass stages" ),
	m_ampGraph( -60.0f, 0.0f, 16, this ),
	m_lpGraph( 0.0f, 3.0f, 16, this )
{
	m_stages.setStrictStepSize( true );
	connect( &m_ampGraph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( ampSamplesChanged( int, int ) ) );
	connect( &m_lpGraph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( lpSamplesChanged( int, int ) ) );

	connect( &m_steps, SIGNAL( dataChanged() ), this, SLOT( lengthChanged() ) );
	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) );

	setDefaultAmpShape();
	setDefaultLpShape();
}

MultitapEchoControls::~MultitapEchoControls()
{
}

void MultitapEchoControls::saveSettings( QDomDocument & doc, QDomElement & parent )
{
	m_steps.saveSettings( doc, parent, "steps" );
	m_stepLength.saveSettings( doc, parent, "steplength" );
	m_dryGain.saveSettings( doc, parent, "drygain" );
	m_swapInputs.saveSettings( doc, parent, "swapinputs" );
	m_stages.saveSettings( doc, parent, "stages" );

	QString ampString;
	base64::encode( (const char *) m_ampGraph.samples(), m_ampGraph.length() * sizeof(float), ampString );
	parent.setAttribute( "ampsteps", ampString );

	QString lpString;
	base64::encode( (const char *) m_lpGraph.samples(), m_lpGraph.length() * sizeof(float), lpString );
	parent.setAttribute( "lpsteps", lpString );
}

void MultitapEchoControls::loadSettings( const QDomElement & elem )
{
	m_steps.loadSettings( elem, "steps" );
	m_stepLength.loadSettings( elem, "steplength" );
	m_dryGain.loadSettings( elem, "drygain" );
	m_swapInputs.loadSettings( elem, "swapinputs" );
	m_stages.loadSettings( elem, "stages" );

	int size = 0;
	char * dst = 0;

	base64::decode( elem.attribute( "ampsteps"), &dst, &size );
	m_ampGraph.setSamples( (float*) dst );

	base64::decode( elem.attribute( "lpsteps"), &dst, &size );
	m_lpGraph.setSamples( (float*) dst );

	delete[] dst;
}

void MultitapEchoControls::setDefaultAmpShape()
{
	const int length = m_steps.value();

	QVarLengthArray<float> samples(length);
	for( int i = 0; i < length; ++i )
	{
		samples[i] = 0.0f;
	}

	m_ampGraph.setSamples( &samples[0] );
}

void MultitapEchoControls::setDefaultLpShape()
{
	const int length = m_steps.value();

	QVarLengthArray<float> samples(length);
	for( int i = 0; i < length; ++i )
	{
		samples[i] = 3.0f;
	}

	m_lpGraph.setSamples( &samples[0] );
}

void MultitapEchoControls::ampSamplesChanged( int begin, int end )
{
	const float * samples = m_ampGraph.samples();
	for( int i = begin; i <= end; ++i )
	{
		m_effect->m_amp[i] = dbfsToAmp( samples[i] );
	}
}

void MultitapEchoControls::ampResetClicked()
{
	setDefaultAmpShape();
}

void MultitapEchoControls::lpSamplesChanged( int begin, int end )
{
	//qDebug( "b/e %d - %d", begin, end );
	const float * samples = m_lpGraph.samples();
	for( int i = begin; i <= end; ++i )
	{
		m_effect->m_lpFreq[i] = 20.0f * exp10( samples[i] );
	}
	m_effect->updateFilters( begin, end );
}

void MultitapEchoControls::lpResetClicked()
{
	setDefaultLpShape();
}

void MultitapEchoControls::lengthChanged()
{
	const int len = m_steps.value();
	m_ampGraph.setLength( len );
	ampSamplesChanged( 0, len - 1 );
	m_lpGraph.setLength( len );
	lpSamplesChanged( 0, len - 1 );
	m_effect->updateFilters( 0, len - 1 );
}

void MultitapEchoControls::sampleRateChanged()
{
	m_effect->m_sampleRate = Engine::mixer()->processingSampleRate();
	m_effect->m_sampleRatio = 1.0f / m_effect->m_sampleRate;
	m_effect->updateFilters( 0, 19 );
}


code:
/*
 * MultitapEchoControls.h - a multitap echo delay plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef MULTITAP_ECHO_CONTROLS_H
#define MULTITAP_ECHO_CONTROLS_H

#include "EffectControls.h"
#include "MultitapEchoControlDialog.h"
#include "Knob.h"
#include "Graph.h"

class MultitapEchoEffect;

class MultitapEchoControls : public EffectControls
{
	Q_OBJECT
public:
	MultitapEchoControls( MultitapEchoEffect * eff );
	virtual ~MultitapEchoControls();

	virtual void saveSettings( QDomDocument & doc, QDomElement & parent );
	virtual void loadSettings( const QDomElement & elem );
	inline virtual QString nodeName() const
	{
		return( "multitapechocontrols" );
	}

	void setDefaultAmpShape();
	void setDefaultLpShape();

	virtual int controlCount()
	{
		return( 5 );
	}

	virtual EffectControlDialog * createView()
	{
		return( new MultitapEchoControlDialog( this ) );
	}

private slots:
	void ampSamplesChanged( int, int );
	void ampResetClicked();

	void lpSamplesChanged( int, int );
	void lpResetClicked();

	void lengthChanged();
	void sampleRateChanged();

private:
	MultitapEchoEffect * m_effect;
	IntModel m_steps;
	TempoSyncKnobModel m_stepLength;

	FloatModel m_dryGain;
	BoolModel m_swapInputs;
	FloatModel m_stages;

	graphModel m_ampGraph;
	graphModel m_lpGraph;

	friend class MultitapEchoEffect;
	friend class MultitapEchoControlDialog;
};

#endif


code:
INCLUDE(BuildPlugin)

# Avoid unused warnings for mididata.h
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-variable")
ENDIF()

INCLUDE_DIRECTORIES(adplug/src)

BUILD_PLUGIN(opulenz
	OpulenZ.cpp
	OpulenZ.h
	adplug/src/opl.h
	adplug/src/fmopl.c
	adplug/src/fmopl.h
	adplug/src/temuopl.cpp
	adplug/src/temuopl.h
	MOCFILES OpulenZ.h
	EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png"
)


code:
/*
 * OpulenZ.cpp - AdLib OPL2 FM synth based instrument
 *
 * Copyright (c) 2014 Raine M. Ekman <raine/at/iki/fi>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

// TODO:
// - Better voice allocation: long releases get cut short :(
// - RT safety = get rid of mutex = make emulator code thread-safe

// - Extras:
//   - double release: first release is in effect until noteoff (heard if percussive sound),
//     second is switched in just before key bit cleared (is this useful???)
//   - Unison: 2,3,4, or 9 voices with configurable spread?
//   - Portamento (needs mono mode?)
//     - Pre-bend/post-bend in poly mode could use portamento speed?

#include "OpulenZ.h"
#include "Instrument.h"
#include "Engine.h"
#include "InstrumentPlayHandle.h"
#include "InstrumentTrack.h"
#include "Mixer.h"

#include <QDomDocument>
#include <QFile>
#include <QFileInfo>
#include <QByteArray>
#include <assert.h>
#include <math.h>

#include "opl.h"
#include "temuopl.h"
#include "mididata.h"

#include "embed.h"
#include "debug.h"

#include "Knob.h"
#include "LcdSpinBox.h"
#include "PixmapButton.h"
#include "ToolTip.h"

#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT opulenz_plugin_descriptor =
{
        STRINGIFY( PLUGIN_NAME ),
        "OpulenZ",
        QT_TRANSLATE_NOOP( "PluginBrowser",
			   "2-operator FM Synth" ),
        "Raine M. Ekman <raine/at/iki/fi>",
        0x0100,
        Plugin::Instrument,
        new PluginPixmapLoader( "logo" ),
        "sbi",
        NULL
};

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * )
{
	return( new OpulenzInstrument( static_cast<InstrumentTrack *>( m ) ) );
}

}

// I'd much rather do without a mutex, but it looks like
// the emulator code isn't really ready for threads
QMutex OpulenzInstrument::emulatorMutex;

// Weird ordering of voice parameters
const unsigned int adlib_opadd[OPL2_VOICES] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};

OpulenzInstrument::OpulenzInstrument( InstrumentTrack * _instrument_track ) :
	Instrument( _instrument_track, &opulenz_plugin_descriptor ),
	m_patchModel( 0, 0, 127, this, tr( "Patch" ) ),
	op1_a_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 attack" )  ),
	op1_d_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 decay" )   ),
	op1_s_mdl(3.0, 0.0, 15.0, 1.0, this, tr( "Op 1 sustain" )   ),
	op1_r_mdl(10.0, 0.0, 15.0, 1.0, this, tr( "Op 1 release" )   ),
	op1_lvl_mdl(62.0, 0.0, 63.0, 1.0, this, tr( "Op 1 level" )   ),
	op1_scale_mdl(0.0, 0.0, 3.0, 1.0, this, tr( "Op 1 level scaling" ) ),
	op1_mul_mdl(0.0, 0.0, 15.0, 1.0, this, tr( "Op 1 frequency multiplier" ) ),
	feedback_mdl(0.0, 0.0, 7.0, 1.0, this, tr( "Op 1 feedback" )    ),
	op1_ksr_mdl(false, this, tr( "Op 1 key scaling rate" ) ),
	op1_perc_mdl(false, this, tr( "Op 1 percussive envelope" )   ),
	op1_trem_mdl(true, this, tr( "Op 1 tremolo" )   ),
	op1_vib_mdl(false, this, tr( "Op 1 vibrato" )   ),
	op1_w0_mdl(  ),
	op1_w1_mdl(  ),
	op1_w2_mdl(  ),
	op1_w3_mdl(  ),
	op1_waveform_mdl(0,0,3,this, tr( "Op 1 waveform" ) ),

	op2_a_mdl(1.0, 0.0, 15.0, 1.0, this, tr( "Op 2 attack" )   ),
	op2_d_mdl(3.0, 0.0, 15.0, 1.0, this, tr( "Op 2 decay" )   ),
	op2_s_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 2 sustain" ) ),
	op2_r_mdl(12.0, 0.0, 15.0, 1.0, this, tr( "Op 2 release" )   ),
	op2_lvl_mdl(63.0, 0.0, 63.0, 1.0, this, tr( "Op 2 level" )   ),
	op2_scale_mdl(0.0, 0.0, 3.0, 1.0, this, tr( "Op 2 level scaling" ) ),
	op2_mul_mdl(1.0, 0.0, 15.0, 1.0, this, tr( "Op 2 frequency multiplier" ) ),
	op2_ksr_mdl(false, this, tr( "Op 2 key scaling rate" ) ),
	op2_perc_mdl(false, this, tr( "Op 2 percussive envelope" )   ),
	op2_trem_mdl(false, this, tr( "Op 2 tremolo" )   ),
	op2_vib_mdl(true, this, tr( "Op 2 vibrato" )   ),
	op2_w0_mdl(  ),
	op2_w1_mdl(  ),
	op2_w2_mdl(  ),
	op2_w3_mdl(  ),
	op2_waveform_mdl(0,0,3,this, tr( "Op 2 waveform" ) ),

	fm_mdl(true, this, tr( "FM" )   ),
	vib_depth_mdl(false, this, tr( "Vibrato depth" )   ),
	trem_depth_mdl(false, this, tr( "Tremolo depth" )   )
{

	// Create an emulator - samplerate, 16 bit, mono
	emulatorMutex.lock();
	theEmulator = new CTemuopl(Engine::mixer()->processingSampleRate(), true, false);
	theEmulator->init();
	// Enable waveform selection
	theEmulator->write(0x01,0x20);
	emulatorMutex.unlock();

	//Initialize voice values
	// voiceNote[0] = 0;
	// voiceLRU[0] = 0;
	for(int i=0; i<OPL2_VOICES; ++i) {
		voiceNote[i] = OPL2_VOICE_FREE;
		voiceLRU[i] = i;
	}

	storedname = displayName();

	updatePatch();

	// Can the buffer size change suddenly? I bet that would break lots of stuff
	frameCount = Engine::mixer()->framesPerPeriod();
	renderbuffer = new short[frameCount];

	// Some kind of sane defaults
	pitchbend = 0;
	pitchBendRange = 100; // cents
	RPNcoarse = RPNfine = 255;

	tuneEqual(69, 440);

	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
		 this, SLOT( reloadEmulator() ) );
	// Connect knobs
	// This one's for testing...
	connect( &m_patchModel, SIGNAL( dataChanged() ), this, SLOT( loadGMPatch() ) );
#define MOD_CON( model ) connect( &model, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
	MOD_CON( op1_a_mdl );
	MOD_CON( op1_d_mdl );
	MOD_CON( op1_s_mdl );
	MOD_CON( op1_r_mdl );
	MOD_CON( op1_lvl_mdl );
	MOD_CON( op1_scale_mdl );
	MOD_CON( op1_mul_mdl );
	MOD_CON( feedback_mdl );
	MOD_CON( op1_ksr_mdl );
	MOD_CON( op1_perc_mdl );
	MOD_CON( op1_trem_mdl );
	MOD_CON( op1_vib_mdl );
	MOD_CON( op1_w0_mdl );
	MOD_CON( op1_w1_mdl );
	MOD_CON( op1_w2_mdl );
	MOD_CON( op1_w3_mdl );
	MOD_CON( op1_waveform_mdl );

	MOD_CON( op2_a_mdl );
	MOD_CON( op2_d_mdl );
	MOD_CON( op2_s_mdl );
	MOD_CON( op2_r_mdl );
	MOD_CON( op2_lvl_mdl );
	MOD_CON( op2_scale_mdl );
	MOD_CON( op2_mul_mdl );
	MOD_CON( op2_ksr_mdl );
	MOD_CON( op2_perc_mdl );
	MOD_CON( op2_trem_mdl );
	MOD_CON( op2_vib_mdl );
	MOD_CON( op2_w0_mdl );
	MOD_CON( op2_w1_mdl );
	MOD_CON( op2_w2_mdl );
	MOD_CON( op2_w3_mdl );
	MOD_CON( op2_waveform_mdl );

	MOD_CON( fm_mdl );
	MOD_CON( vib_depth_mdl );
	MOD_CON( trem_depth_mdl );

	// Connect the plugin to the mixer...
	InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track );
	Engine::mixer()->addPlayHandle( iph );
}

OpulenzInstrument::~OpulenzInstrument() {
	delete theEmulator;
	Engine::mixer()->removePlayHandlesOfTypes( instrumentTrack(),
				PlayHandle::TypeNotePlayHandle
				| PlayHandle::TypeInstrumentPlayHandle );
	delete [] renderbuffer;
}

// Samplerate changes when choosing oversampling, so this is more or less mandatory
void OpulenzInstrument::reloadEmulator() {
	delete theEmulator;
	emulatorMutex.lock();
	theEmulator = new CTemuopl(Engine::mixer()->processingSampleRate(), true, false);
	theEmulator->init();
	theEmulator->write(0x01,0x20);
	emulatorMutex.unlock();
	for(int i=0; i<OPL2_VOICES; ++i) {
		voiceNote[i] = OPL2_VOICE_FREE;
		voiceLRU[i] = i;
	}
	updatePatch();
}

// This shall only be called from code protected by the holy Mutex!
void OpulenzInstrument::setVoiceVelocity(int voice, int vel) {
	int vel_adjusted;
	// Velocity calculation, some kind of approximation
	// Only calculate for operator 1 if in adding mode, don't want to change timbre
	if( fm_mdl.value() == false ) {
		vel_adjusted = 63 - ( op1_lvl_mdl.value() * vel/127.0) ;
	} else {
		vel_adjusted = 63 - op1_lvl_mdl.value();
	}
	theEmulator->write(0x40+adlib_opadd[voice],
			   ( (int)op1_scale_mdl.value() & 0x03 << 6) +
			   ( vel_adjusted & 0x3f ) );

	vel_adjusted = 63 - ( op2_lvl_mdl.value() * vel/127.0 );
	// vel_adjusted = 63 - op2_lvl_mdl.value();
	theEmulator->write(0x43+adlib_opadd[voice],
			   ( (int)op2_scale_mdl.value() & 0x03 << 6) +
			   ( vel_adjusted & 0x3f ) );
}

// Pop least recently used voice
int OpulenzInstrument::popVoice() {
	int tmp = voiceLRU[0];
	for( int i=0; i<OPL2_VOICES-1; ++i) {
		voiceLRU[i] = voiceLRU[i+1];
	}
	voiceLRU[OPL2_VOICES-1] = OPL2_NO_VOICE;
#ifdef false
	printf("<-- %d %d %d %d %d %d %d %d %d \n", voiceLRU[0],voiceLRU[1],voiceLRU[2],
	       voiceLRU[3],voiceLRU[4],voiceLRU[5],voiceLRU[6],voiceLRU[7],voiceLRU[8]);
#endif
	return tmp;
}
// Push voice into first free slot
int OpulenzInstrument::pushVoice(int v) {
	int i;
	assert(voiceLRU[OPL2_VOICES-1]==OPL2_NO_VOICE);
	for(i=OPL2_VOICES-1; i>0; --i) {
		if( voiceLRU[i-1] != OPL2_NO_VOICE ) {
			break;
		}
	}
	voiceLRU[i] = v;
#ifdef false
	printf("%d %d %d %d %d %d %d %d %d <-- \n", voiceLRU[0],voiceLRU[1],voiceLRU[2],
	       voiceLRU[3],voiceLRU[4],voiceLRU[5],voiceLRU[6],voiceLRU[7],voiceLRU[8]);
#endif
	return i;
}

bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset )
{
	emulatorMutex.lock();
	int key, vel, voice, tmp_pb;

	switch(event.type()) {
        case MidiNoteOn:
		// to get us in line with MIDI(?)
		key = event.key() +12;
		vel = event.velocity();

		voice = popVoice();
		if( voice != OPL2_NO_VOICE ) {
			// Turn voice on, NB! the frequencies are straight by voice number,
			// not by the adlib_opadd table!
			theEmulator->write(0xA0+voice, fnums[key] & 0xff);
			theEmulator->write(0xB0+voice, 32 + ((fnums[key] & 0x1f00) >> 8) );
			setVoiceVelocity(voice, vel);
			voiceNote[voice] = key;
			velocities[key] = vel;
		}
                break;
        case MidiNoteOff:
                key = event.key() +12;
                for(voice=0; voice<OPL2_VOICES; ++voice) {
                        if( voiceNote[voice] == key ) {
                                theEmulator->write(0xA0+voice, fnums[key] & 0xff);
                                theEmulator->write(0xB0+voice, (fnums[key] & 0x1f00) >> 8 );
                                voiceNote[voice] |= OPL2_VOICE_FREE;
				pushVoice(voice);
                        }
                }
		velocities[key] = 0;
                break;
        case MidiKeyPressure:
                key = event.key() +12;
                vel = event.velocity();
		if( velocities[key] != 0) {
			velocities[key] = vel;
		}
		for(voice=0; voice<OPL2_VOICES; ++voice) {
			if(voiceNote[voice] == key) {
				setVoiceVelocity(voice, vel);
			}
		}
                break;
        case MidiPitchBend:
		// Update fnumber table

		// Neutral = 8192, full downbend = 0, full upbend = 16383
		tmp_pb = ( event.pitchBend()-8192 ) * pitchBendRange / 8192;

		if( tmp_pb != pitchbend ) {
			pitchbend = tmp_pb;
			tuneEqual(69, 440.0);
		}
		// Update pitch of all voices (also released ones)
		for( int v=0; v<OPL2_VOICES; ++v ) {
			int vn = (voiceNote[v] & ~OPL2_VOICE_FREE); // remove the flag bit
			int playing = (voiceNote[v] & OPL2_VOICE_FREE) == 0; // just the flag bit
			theEmulator->write(0xA0+v, fnums[vn] & 0xff);
			theEmulator->write(0xB0+v, (playing ? 32 : 0) + ((fnums[vn] & 0x1f00) >> 8) );
                }
                break;
	case MidiControlChange:
		switch (event.controllerNumber()) {
		case MidiControllerRegisteredParameterNumberLSB:
			RPNfine = event.controllerValue();
			break;
		case MidiControllerRegisteredParameterNumberMSB:
			RPNcoarse = event.controllerValue();
			break;
		case MidiControllerDataEntry:
			if( (RPNcoarse << 8) + RPNfine == MidiPitchBendSensitivityRPN) {
				pitchBendRange = event.controllerValue() * 100;
			}
			break;
		default:
#ifdef LMMS_DEBUG
			printf("Midi CC %02x %02x\n", event.controllerNumber(), event.controllerValue() );
#endif
			break;
		}
		break;
        default:
#ifdef LMMS_DEBUG
                printf("Midi event type %d\n",event.type());
#endif
		break;
        }
	emulatorMutex.unlock();
	return true;
}

QString OpulenzInstrument::nodeName() const
{
        return( opulenz_plugin_descriptor.name );
}

PluginView * OpulenzInstrument::instantiateView( QWidget * _parent )
{
        return( new OpulenzInstrumentView( this, _parent ) );
}

void OpulenzInstrument::play( sampleFrame * _working_buffer )
{
	emulatorMutex.lock();
	theEmulator->update(renderbuffer, frameCount);

	for( fpp_t frame = 0; frame < frameCount; ++frame )
        {
                sample_t s = float(renderbuffer[frame]) / 8192.0;
                for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
                {
                        _working_buffer[frame][ch] = s;
                }
	}
	emulatorMutex.unlock();

	// Throw the data to the track...
	instrumentTrack()->processAudioBuffer( _working_buffer, frameCount, NULL );

}

void OpulenzInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	op1_a_mdl.saveSettings( _doc, _this, "op1_a" );
	op1_d_mdl.saveSettings( _doc, _this, "op1_d" );
	op1_s_mdl.saveSettings( _doc, _this, "op1_s" );
	op1_r_mdl.saveSettings( _doc, _this, "op1_r" );
	op1_lvl_mdl.saveSettings( _doc, _this, "op1_lvl" );
	op1_scale_mdl.saveSettings( _doc, _this, "op1_scale" );
	op1_mul_mdl.saveSettings( _doc, _this, "op1_mul" );
	feedback_mdl.saveSettings( _doc, _this, "feedback" );
	op1_ksr_mdl.saveSettings( _doc, _this, "op1_ksr" );
	op1_perc_mdl.saveSettings( _doc, _this, "op1_perc" );
	op1_trem_mdl.saveSettings( _doc, _this, "op1_trem" );
	op1_vib_mdl.saveSettings( _doc, _this, "op1_vib" );
	op1_waveform_mdl.saveSettings( _doc, _this, "op1_waveform" );

	op2_a_mdl.saveSettings( _doc, _this, "op2_a" );
	op2_d_mdl.saveSettings( _doc, _this, "op2_d" );
	op2_s_mdl.saveSettings( _doc, _this, "op2_s" );
	op2_r_mdl.saveSettings( _doc, _this, "op2_r" );
	op2_lvl_mdl.saveSettings( _doc, _this, "op2_lvl" );
	op2_scale_mdl.saveSettings( _doc, _this, "op2_scale" );
	op2_mul_mdl.saveSettings( _doc, _this, "op2_mul" );
	op2_ksr_mdl.saveSettings( _doc, _this, "op2_ksr" );
	op2_perc_mdl.saveSettings( _doc, _this, "op2_perc" );
	op2_trem_mdl.saveSettings( _doc, _this, "op2_trem" );
	op2_vib_mdl.saveSettings( _doc, _this, "op2_vib" );
	op2_waveform_mdl.saveSettings( _doc, _this, "op2_waveform" );

	fm_mdl.saveSettings( _doc, _this, "fm" );
	vib_depth_mdl.saveSettings( _doc, _this, "vib_depth" );
	trem_depth_mdl.saveSettings( _doc, _this, "trem_depth" );
}

void OpulenzInstrument::loadSettings( const QDomElement & _this )
{
	op1_a_mdl.loadSettings( _this, "op1_a" );
	op1_d_mdl.loadSettings( _this, "op1_d" );
	op1_s_mdl.loadSettings( _this, "op1_s" );
	op1_r_mdl.loadSettings( _this, "op1_r" );
	op1_lvl_mdl.loadSettings( _this, "op1_lvl" );
	op1_scale_mdl.loadSettings( _this, "op1_scale" );
	op1_mul_mdl.loadSettings( _this, "op1_mul" );
	feedback_mdl.loadSettings( _this, "feedback" );
	op1_ksr_mdl.loadSettings( _this, "op1_ksr" );
	op1_perc_mdl.loadSettings( _this, "op1_perc" );
	op1_trem_mdl.loadSettings( _this, "op1_trem" );
	op1_vib_mdl.loadSettings( _this, "op1_vib" );
	op1_waveform_mdl.loadSettings( _this, "op1_waveform" );

	op2_a_mdl.loadSettings( _this, "op2_a" );
	op2_d_mdl.loadSettings( _this, "op2_d" );
	op2_s_mdl.loadSettings( _this, "op2_s" );
	op2_r_mdl.loadSettings( _this, "op2_r" );
	op2_lvl_mdl.loadSettings( _this, "op2_lvl" );
	op2_scale_mdl.loadSettings( _this, "op2_scale" );
	op2_mul_mdl.loadSettings( _this, "op2_mul" );
	op2_ksr_mdl.loadSettings( _this, "op2_ksr" );
	op2_perc_mdl.loadSettings( _this, "op2_perc" );
	op2_trem_mdl.loadSettings( _this, "op2_trem" );
	op2_vib_mdl.loadSettings( _this, "op2_vib" );
	op2_waveform_mdl.loadSettings( _this, "op2_waveform" );

	fm_mdl.loadSettings( _this, "fm" );
	vib_depth_mdl.loadSettings( _this, "vib_depth" );
	trem_depth_mdl.loadSettings( _this, "trem_depth" );

}

// Load a patch into the emulator
void OpulenzInstrument::loadPatch(const unsigned char inst[14]) {
	emulatorMutex.lock();
	for(int v=0; v<OPL2_VOICES; ++v) {
		theEmulator->write(0x20+adlib_opadd[v],inst[0]); // op1 AM/VIB/EG/KSR/Multiplier
		theEmulator->write(0x23+adlib_opadd[v],inst[1]); // op2
		// theEmulator->write(0x40+adlib_opadd[v],inst[2]); // op1 KSL/Output Level - these are handled by noteon/aftertouch code
		// theEmulator->write(0x43+adlib_opadd[v],inst[3]); // op2
		theEmulator->write(0x60+adlib_opadd[v],inst[4]); // op1 A/D
		theEmulator->write(0x63+adlib_opadd[v],inst[5]); // op2
		theEmulator->write(0x80+adlib_opadd[v],inst[6]); // op1 S/R
		theEmulator->write(0x83+adlib_opadd[v],inst[7]); // op2
		theEmulator->write(0xe0+adlib_opadd[v],inst[8]); // op1 waveform
		theEmulator->write(0xe3+adlib_opadd[v],inst[9]); // op2
		theEmulator->write(0xc0+v,inst[10]);             // feedback/algorithm
	}
	emulatorMutex.unlock();
}

void OpulenzInstrument::tuneEqual(int center, float Hz) {
	float tmp;
	for(int n=0; n<128; ++n) {
		tmp = Hz*pow( 2.0, ( n - center ) * ( 1.0 / 12.0 ) + pitchbend * ( 1.0 / 1200.0 ) );
		fnums[n] = Hz2fnum( tmp );
	}
}

// Find suitable F number in lowest possible block
int OpulenzInstrument::Hz2fnum(float Hz) {
	for(int block=0; block<8; ++block) {
		unsigned int fnum = Hz * pow( 2.0, 20.0 - (double)block ) * ( 1.0 / 49716.0 );
		if(fnum<1023) {
			return fnum + (block << 10);
		}
	}
	return 0;
}

// Load one of the default patches
void OpulenzInstrument::loadGMPatch() {
	unsigned char *inst = midi_fm_instruments[m_patchModel.value()];
	loadPatch(inst);
}

// Update patch from the models to the chip emulation
void OpulenzInstrument::updatePatch() {
	unsigned char inst[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	inst[0] = ( op1_trem_mdl.value() ?  128 : 0  ) +
		( op1_vib_mdl.value() ?  64 : 0 ) +
		( op1_perc_mdl.value() ?  0 : 32 ) + // NB. This envelope mode is "perc", not "sus"
		( op1_ksr_mdl.value() ?  16 : 0 ) +
		((int)op1_mul_mdl.value() & 0x0f);
	inst[1] = ( op2_trem_mdl.value() ?  128 : 0  ) +
		( op2_vib_mdl.value() ?  64 : 0 ) +
		( op2_perc_mdl.value() ?  0 : 32 ) + // NB. This envelope mode is "perc", not "sus"
		( op2_ksr_mdl.value() ?  16 : 0 ) +
		((int)op2_mul_mdl.value() & 0x0f);
	inst[2] = ( ((int)op1_scale_mdl.value() & 0x03) << 6 ) +
		(63 - ( (int)op1_lvl_mdl.value() & 0x3f ) );
	inst[3] = ( ((int)op2_scale_mdl.value() & 0x03) << 6 ) +
		(63 - ( (int)op2_lvl_mdl.value() & 0x3f ) );
	inst[4] = ((15 - ((int)op1_a_mdl.value() & 0x0f ) ) << 4 )+
		(15 - ( (int)op1_d_mdl.value() & 0x0f ) );
	inst[5] = ((15 - ( (int)op2_a_mdl.value() & 0x0f ) ) << 4 )+
		(15 - ( (int)op2_d_mdl.value() & 0x0f ) );
	inst[6] = ((15 - ( (int)op1_s_mdl.value() & 0x0f ) ) << 4 ) +
		(15 - ( (int)op1_r_mdl.value() & 0x0f ) );
	inst[7] = ((15 - ( (int)op2_s_mdl.value() & 0x0f ) ) << 4 ) +
		(15 - ( (int)op2_r_mdl.value() & 0x0f ) );
	inst[8] = (int)op1_waveform_mdl.value() & 0x03;
	inst[9] = (int)op2_waveform_mdl.value() & 0x03;
	inst[10] = (fm_mdl.value() ? 0 : 1 ) +
		(((int)feedback_mdl.value() & 0x07 )<< 1);
	// These are always 0 in the list I had?
	inst[11] = 0;
	inst[12] = 0;
	inst[13] = 0;

	// Not part of the per-voice patch info
	theEmulator->write(0xBD, (trem_depth_mdl.value() ? 128 : 0 ) +
			   (vib_depth_mdl.value() ? 64 : 0 ));

	// have to do this, as the level knobs might've changed
	for( int voice = 0; voice < OPL2_VOICES ; ++voice) {
		if(voiceNote[voice] && OPL2_VOICE_FREE == 0) {
			setVoiceVelocity(voice, velocities[voiceNote[voice]] );
		}
	}
#ifdef false
		printf("UPD: %02x %02x %02x %02x %02x -- %02x %02x %02x %02x %02x %02x\n",
		       inst[0], inst[1], inst[2], inst[3], inst[4],
		       inst[5], inst[6], inst[7], inst[8], inst[9], inst[10]);
#endif

	loadPatch(inst);
}

// Load an SBI file into the knob models
void OpulenzInstrument::loadFile( const QString& file ) {
	// http://cd.textfiles.com/soundsensations/SYNTH/SBINS/
	// http://cd.textfiles.com/soundsensations/SYNTH/SBI1198/1198SBI.ZIP
	if( !file.isEmpty() && QFileInfo( file ).exists() )
	{
		QFile sbifile(file);
		if (!sbifile.open(QIODevice::ReadOnly )) {
			printf("Can't open file\n");
			return;
		}

		QByteArray sbidata = sbifile.read(52);
		if( !sbidata.startsWith("SBI\0x1a") ) {
			printf("No SBI signature\n");
			return;
		}
		if( sbidata.size() != 52 ) {
			printf("SBI size error: expected 52, got %d\n",sbidata.size() );
		}

		// Minimum size of SBI if we ignore "reserved" bytes at end
		// https://courses.engr.illinois.edu/ece390/resources/sound/cmf.txt.html
		if( sbidata.size() < 47 ) {
			return;
		}

		QString sbiname = sbidata.mid(4, 32);
		// If user has changed track name... let's hope my logic is valid.
		if( sbiname.size() > 0 && instrumentTrack()->displayName() == storedname ) {
			instrumentTrack()->setName(sbiname);
			storedname = sbiname;
		}

#ifdef false
		printf("SBI: %02x %02x %02x %02x %02x -- %02x %02x %02x %02x %02x %02x\n",
		       (unsigned char)sbidata[36], (unsigned char)sbidata[37], (unsigned char)sbidata[38], (unsigned char)sbidata[39], (unsigned char)sbidata[40],
		       (unsigned char)sbidata[41], (unsigned char)sbidata[42], (unsigned char)sbidata[43], (unsigned char)sbidata[44], (unsigned char)sbidata[45], (unsigned char)sbidata[46]);
#endif
		// Modulator Sound Characteristic (Mult, KSR, EG, VIB, AM)
		op1_trem_mdl.setValue( (sbidata[36] & 0x80 ) == 0x80 ? true : false );
		op1_vib_mdl.setValue( (sbidata[36] & 0x40 ) == 0x40 ? true : false );
		op1_perc_mdl.setValue( (sbidata[36] & 0x20 ) == 0x20 ? false : true );
		op1_ksr_mdl.setValue( (sbidata[36] & 0x10 ) == 0x10 ? true : false );
		op1_mul_mdl.setValue( sbidata[36] & 0x0f );

		// Carrier Sound Characteristic
		op2_trem_mdl.setValue( (sbidata[37] & 0x80 ) == 0x80 ? true : false );
		op2_vib_mdl.setValue( (sbidata[37] & 0x40 ) == 0x40 ? true : false );
		op2_perc_mdl.setValue( (sbidata[37] & 0x20 ) == 0x20 ? false : true );
		op2_ksr_mdl.setValue( (sbidata[37] & 0x10 ) == 0x10 ? true : false );
		op2_mul_mdl.setValue( sbidata[37] & 0x0f );

		// Modulator Scaling/Output Level
		op1_scale_mdl.setValue( (sbidata[38] & 0xc0 ) >> 6 );
		op1_lvl_mdl.setValue( 63 - (sbidata[38] & 0x3f) );

		// Carrier Scaling/Output Level
		op2_scale_mdl.setValue( (sbidata[39] & 0xc0) >> 6 );
		op2_lvl_mdl.setValue( 63 - (sbidata[39] & 0x3f) );

		// Modulator Attack/Decay
		op1_a_mdl.setValue( 15 - ( ( sbidata[40] & 0xf0 ) >> 4 ) );
		op1_d_mdl.setValue( 15 - ( sbidata[40] & 0x0f ) );

		// Carrier Attack/Decay
		op2_a_mdl.setValue( 15 - ( ( sbidata[41] & 0xf0 ) >> 4 ) );
		op2_d_mdl.setValue( 15 - ( sbidata[41] & 0x0f ) );

		// Modulator Sustain/Release
		op1_s_mdl.setValue( 15 - ( ( sbidata[42] & 0xf0 ) >> 4 ) );
		op1_r_mdl.setValue( 15 - ( sbidata[42] & 0x0f ) );

		// Carrier Sustain/Release
		op2_s_mdl.setValue( 15 - ( ( sbidata[43] & 0xf0 ) >> 4 ) );
		op2_r_mdl.setValue( 15 - ( sbidata[43] & 0x0f ) );

		// Modulator Wave Select
		op1_waveform_mdl.setValue( sbidata[44] & 0x03 );

		// Carrier Wave Select
		op2_waveform_mdl.setValue( sbidata[45] & 0x03 );

		// Feedback/Connection
		fm_mdl.setValue( (sbidata[46] & 0x01) == 0x01 ? false : true );
		feedback_mdl.setValue( ( (sbidata[46] & 0x0e ) >> 1 ) );
	}
}

OpulenzInstrumentView::OpulenzInstrumentView( Instrument * _instrument,
                                                        QWidget * _parent ) :
        InstrumentViewFixedSize( _instrument, _parent )
{

#define KNOB_GEN(knobname, hinttext, hintunit,xpos,ypos) \
	knobname = new Knob( knobStyled, this );\
	knobname->setHintText( tr(hinttext), hintunit );\
	knobname->setFixedSize(22,22);\
	knobname->setCenterPointX(11.0);\
	knobname->setCenterPointY(11.0);\
	knobname->setTotalAngle(270.0);\
	knobname->move(xpos,ypos);

#define BUTTON_GEN(buttname, tooltip, xpos, ypos) \
	buttname = new PixmapButton( this, NULL );\
        buttname->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "led_on" ) );\
        buttname->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "led_off" ) );\
	buttname->setCheckable( true );\
        ToolTip::add( buttname, tr( tooltip ) );\
        buttname->move( xpos, ypos );

#define WAVEBUTTON_GEN(buttname, tooltip, xpos, ypos, icon_on, icon_off, buttgroup) \
	buttname = new PixmapButton( this, NULL );\
        buttname->setActiveGraphic( PLUGIN_NAME::getIconPixmap( icon_on ) ); \
        buttname->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( icon_off ) ); \
        ToolTip::add( buttname, tr( tooltip ) );\
        buttname->move( xpos, ypos );\
	buttgroup->addButton(buttname);

	// OP1 knobs & buttons...
	KNOB_GEN(op1_a_kn, "Attack", "", 6, 48);
	KNOB_GEN(op1_d_kn, "Decay", "", 34, 48);
	KNOB_GEN(op1_s_kn, "Sustain", "", 62, 48);
	KNOB_GEN(op1_r_kn, "Release", "", 90, 48);
	KNOB_GEN(op1_lvl_kn, "Level", "", 166, 48);
	KNOB_GEN(op1_scale_kn, "Scale", "", 194, 48);
	KNOB_GEN(op1_mul_kn, "Frequency multiplier", "", 222, 48);
	BUTTON_GEN(op1_ksr_btn, "Keyboard scaling rate", 9, 87);
	BUTTON_GEN(op1_perc_btn, "Percussive envelope", 36, 87);
	BUTTON_GEN(op1_trem_btn, "Tremolo", 65, 87);
	BUTTON_GEN(op1_vib_btn, "Vibrato", 93, 87);
	KNOB_GEN(feedback_kn, "Feedback", "", 128, 48);

	op1_waveform = new automatableButtonGroup( this );
	WAVEBUTTON_GEN(op1_w0_btn,"Sine", 154, 86, "wave1_on", "wave1_off", op1_waveform);
	WAVEBUTTON_GEN(op1_w1_btn,"Half sine", 178, 86, "wave2_on", "wave2_off", op1_waveform);
	WAVEBUTTON_GEN(op1_w2_btn,"Absolute sine", 199, 86, "wave3_on", "wave3_off", op1_waveform);
	WAVEBUTTON_GEN(op1_w3_btn,"Quarter sine", 220, 86, "wave4_on", "wave4_off", op1_waveform);

	// And the same for OP2
	KNOB_GEN(op2_a_kn, "Attack", "", 6, 138);
	KNOB_GEN(op2_d_kn, "Decay", "", 34, 138);
	KNOB_GEN(op2_s_kn, "Sustain", "", 62, 138);
	KNOB_GEN(op2_r_kn, "Release", "", 90, 138);
	KNOB_GEN(op2_lvl_kn, "Level", "", 166, 138);
	KNOB_GEN(op2_scale_kn, "Scale", "", 194, 138);
	KNOB_GEN(op2_mul_kn, "Frequency multiplier", "", 222, 138);
	BUTTON_GEN(op2_ksr_btn, "Keyboard scaling rate", 9, 177);
	BUTTON_GEN(op2_perc_btn, "Percussive envelope", 36, 177);
	BUTTON_GEN(op2_trem_btn, "Tremolo", 65, 177);
	BUTTON_GEN(op2_vib_btn, "Vibrato", 93, 177);

	op2_waveform = new automatableButtonGroup( this );
	WAVEBUTTON_GEN(op2_w0_btn,"Sine", 154, 176, "wave1_on", "wave1_off", op2_waveform);
	WAVEBUTTON_GEN(op2_w1_btn,"Half sine", 178, 176, "wave2_on", "wave2_off", op2_waveform);
	WAVEBUTTON_GEN(op2_w2_btn,"Absolute sine", 199, 176, "wave3_on", "wave3_off", op2_waveform);
	WAVEBUTTON_GEN(op2_w3_btn,"Quarter Sine", 220, 176, "wave4_on", "wave4_off", op2_waveform);

	BUTTON_GEN(fm_btn, "FM", 9, 220);
	BUTTON_GEN(vib_depth_btn, "Vibrato depth", 65, 220);
	BUTTON_GEN(trem_depth_btn, "Tremolo depth", 93, 220);

	setAutoFillBackground( true );
        QPalette pal;
        pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap(
                                                                "artwork" ) );
        setPalette( pal );
}
OpulenzInstrumentView::~OpulenzInstrumentView() {
	// Knobs are QWidgets and our children, so they're
	// destroyed automagically
}

// Returns text for time knob formatted nicely
inline QString OpulenzInstrumentView::knobHintHelper(float n) {
	if(n>1000) {
		return QString::number(n/1000, 'f', 0)+ " s";
	} else if(n>10) {
		return QString::number(n, 'f', 0)+ " ms";
	} else {
		return QString::number(n, 'f', 1)+ " ms";
	}
}

void OpulenzInstrumentView::updateKnobHints()
{
	// Envelope times in ms: t[0] = 0, t[n] = ( 1<<n ) * X, X = 0.11597 for A, 0.6311 for D/R
	// Here some rounding has been applied.
	const float attack_times[16] = {
		0.0, 0.2, 0.4, 0.9, 1.8, 3.7, 7.4,
		15.0, 30.0, 60.0, 120.0, 240.0, 480.0,
		950.0, 1900.0, 3800.0
	};

	const float dr_times[16] = {
		0.0, 1.2, 2.5, 5.0, 10.0, 20.0, 40.0,
		80.0, 160.0, 320.0, 640.0, 1300.0, 2600.0,
		5200.0, 10000.0, 20000.0
	};

	const int fmultipliers[16] = {
		-12, 0, 12, 19, 24, 28, 31, 34, 36, 38, 40, 40, 43, 43, 47, 47
	};

	OpulenzInstrument * m = castModel<OpulenzInstrument>();

	op1_a_kn->setHintText( tr( "Attack" ),
						   " (" + knobHintHelper(attack_times[(int)m->op1_a_mdl.value()]) + ")");
	op2_a_kn->setHintText( tr( "Attack" ),
						   " (" + knobHintHelper(attack_times[(int)m->op2_a_mdl.value()]) + ")");
	op1_d_kn->setHintText( tr( "Decay" ),
						   " (" + knobHintHelper(dr_times[(int)m->op1_d_mdl.value()]) + ")");
	op2_d_kn->setHintText( tr( "Decay" ),
						   " (" + knobHintHelper(dr_times[(int)m->op2_d_mdl.value()]) + ")");
	op1_r_kn->setHintText( tr( "Release" ),
						   " (" + knobHintHelper(dr_times[(int)m->op1_r_mdl.value()]) + ")");
	op2_r_kn->setHintText( tr( "Release" ),
						   " (" + knobHintHelper(dr_times[(int)m->op2_r_mdl.value()]) + ")");
	op1_mul_kn->setHintText( tr( "Frequency multiplier" ),
			       " (" + QString::number(fmultipliers[(int)m->op1_mul_mdl.value()]) + " semitones)");
	op2_mul_kn->setHintText( tr( "Frequency multiplier" ),
			       " (" + QString::number(fmultipliers[(int)m->op2_mul_mdl.value()]) + " semitones)");
}

void OpulenzInstrumentView::modelChanged()
{
	OpulenzInstrument * m = castModel<OpulenzInstrument>();
	// m_patch->setModel( &m->m_patchModel );

	op1_a_kn->setModel( &m->op1_a_mdl );
	op1_d_kn->setModel( &m->op1_d_mdl );
	op1_s_kn->setModel( &m->op1_s_mdl );
	op1_r_kn->setModel( &m->op1_r_mdl );
	op1_lvl_kn->setModel( &m->op1_lvl_mdl );
	op1_scale_kn->setModel( &m->op1_scale_mdl );
	op1_mul_kn->setModel( &m->op1_mul_mdl );
	feedback_kn->setModel( &m->feedback_mdl );
	op1_ksr_btn->setModel( &m->op1_ksr_mdl );
	op1_perc_btn->setModel( &m->op1_perc_mdl );
	op1_trem_btn->setModel( &m->op1_trem_mdl );
	op1_vib_btn->setModel( &m->op1_vib_mdl );
	op1_waveform->setModel( &m->op1_waveform_mdl );

	op2_a_kn->setModel( &m->op2_a_mdl );
	op2_d_kn->setModel( &m->op2_d_mdl );
	op2_s_kn->setModel( &m->op2_s_mdl );
	op2_r_kn->setModel( &m->op2_r_mdl );
	op2_lvl_kn->setModel( &m->op2_lvl_mdl );
	op2_scale_kn->setModel( &m->op2_scale_mdl );
	op2_mul_kn->setModel( &m->op2_mul_mdl );
	op2_ksr_btn->setModel( &m->op2_ksr_mdl );
	op2_perc_btn->setModel( &m->op2_perc_mdl );
	op2_trem_btn->setModel( &m->op2_trem_mdl );
	op2_vib_btn->setModel( &m->op2_vib_mdl );
	op2_waveform->setModel( &m->op2_waveform_mdl );

	fm_btn->setModel( &m->fm_mdl );
	vib_depth_btn->setModel( &m->vib_depth_mdl );
	trem_depth_btn->setModel( &m->trem_depth_mdl );

	// All knobs needing a user friendly unit
	connect( &m->op1_a_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
	connect( &m->op2_a_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );

	connect( &m->op1_d_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
	connect( &m->op2_d_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );

	connect( &m->op1_r_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
	connect( &m->op2_r_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );

	connect( &m->op1_mul_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
	connect( &m->op2_mul_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );

	updateKnobHints();

}