How do I convert this data to an MIDI file?

895 views Asked by At

I have the Note Index (Each Octave has 12 notes) versus the time(in beats) data. How can I convert this data to a midi file?

Example Data:

time = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 9.0, 9.0, 9.0, 10.0, 10.0, 10.0, 11.0, 11.0]

notes  = [57.0, 59.0, 60.0, 62.0, 64.0, 65.0, 67.0, 57.0, 60.0, 64.0, 65.0, 62.0, 59.0, 64.0, 67.0]

Sample Image for Data in Graph form

The instrument is Piano.

Also, observe that there are multiple notes at the same time.

Thanks in advance.

1

There are 1 answers

1
Former contributor On

I've created a program that converts your data into a MIDI file in C++ before reading that you prefer Python. Anyway, I prefer C++ and Qt, so this example uses the drumstick-file library for the job. Disclaimer: I'm the author of drumstick, just in case somebody complains about spam or self-promotion. Here is the code:

smfbuilder.pro

TEMPLATE = app
CONFIG += c++11 console link_pkgconfig
packagesExist(drumstick-file) {
    message("using pkg-config")
    PKGCONFIG += drumstick-file
} else {
    message("using environment variables")
    INCLUDEPATH += $$(DRUMSTICKINCLUDES)
    LIBS += -L$$(DRUMSTICKLIBS) -ldrumstick-file
}
HEADERS += smfbuilder.h
SOURCES += smfbuilder.cpp

smfbuilder.h

#include <QObject>
#include <drumstick/qsmf.h>

class MidiEvent
{
public:
    long absTime;
    int  status;
    int  data1;
    int  data2;
};

class Sequence : public QList<MidiEvent>
{
public:
    virtual ~Sequence();
    void sort();
};

class SMFBuilder : public QObject
{
    Q_OBJECT
public:
    SMFBuilder();
    void run(QString fileName);
    void generate();

public slots:
    void errorHandler(const QString& errorStr);
    void trackHandler(int track);

private:
    drumstick::QSmf *m_engine;
    Sequence m_sequence;
};

smfbuilder.cpp

#include <QCoreApplication>
#include <QDebug>
#include <QTextCodec>
#include <drumstick/qsmf.h>
#include "smfbuilder.h"

static inline bool eventLessThan(const MidiEvent& s1, const MidiEvent& s2)
{
    return s1.absTime < s2.absTime;
}

void Sequence::sort()
{
    std::stable_sort(begin(), end(), eventLessThan);
}

Sequence::~Sequence()
{
    clear();
}

SMFBuilder::SMFBuilder() : QObject()
{
    m_engine = new drumstick::QSmf(this);
    m_engine->setTextCodec(QTextCodec::codecForName("UTF-8"));
    connect(m_engine, SIGNAL(signalSMFError(const QString&)), 
            this, SLOT(errorHandler(const QString&)));
    connect(m_engine, SIGNAL(signalSMFWriteTrack(int)), 
            this, SLOT(trackHandler(int)));
}

void SMFBuilder::errorHandler(const QString& errorStr)
{
    qWarning() << errorStr;
    exit(1);
}

void SMFBuilder::trackHandler(int )
{
    // meta events
    m_engine->writeBpmTempo(0, 100); // m.m.=100 (andante)
    m_engine->writeTimeSignature(0, 4, 2, 18, 8);  // ts = 4/4
    m_engine->writeKeySignature(0, 0, major_mode); // C major
    m_engine->writeMidiEvent(0, program_chng, 0, 0);  // grand piano
    // note events
    long last_time = 0;
    for(auto ev : m_sequence)
    {
        long delta = ev.absTime - last_time;
        last_time = ev.absTime;
        m_engine->writeMidiEvent(delta,  ev.status, 0, ev.data1, ev.data2);
    }
    // final event
    m_engine->writeMetaEvent(0, end_of_track); 
}

void SMFBuilder::run(QString fileName)
{
    m_engine->setDivision(120); // ticks per quarter note
    m_engine->setFileFormat(0); // single track
    m_engine->setTracks(1);
    m_engine->writeToFile(fileName);
}

void SMFBuilder::generate()
{
    QList<float> times = { 0.0,  1.0,  2.0,  3.0,  4.0,  5.0,  6.0,  9.0,  9.0,  9.0, 10.0, 10.0, 10.0, 11.0, 11.0};
    QList<float> notes = {57.0, 59.0, 60.0, 62.0, 64.0, 65.0, 67.0, 57.0, 60.0, 64.0, 65.0, 62.0, 59.0, 64.0, 67.0};
    long quarter_length = 120; // quarter note duration in ticks
    int velocityOn = 100; // forte

    for ( int i=0; i<times.length(); ++i) {
        long event_time = static_cast<long>(times[i] * quarter_length);
        int midi_note = static_cast<int>(notes[i]);
        // insert note on event
        MidiEvent noteOn;
        noteOn.absTime = event_time;
        noteOn.status = note_on;
        noteOn.data1 = midi_note;
        noteOn.data2 = velocityOn;
        m_sequence.append(noteOn);
        // insert note off event
        MidiEvent noteOff;
        noteOff.absTime = event_time + quarter_length;
        noteOff.status = note_off;
        noteOff.data1 = midi_note;
        noteOff.data2 = 0; // note off
        m_sequence.append(noteOff);
    }
    m_sequence.sort();
}

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);
    SMFBuilder builder;
    builder.generate();
    builder.run("output.mid");
    return 0;
}

Some notes about the code:

  1. You can use two ways for configuring the drumstick dependency. Assuming that you are using Linux, either install the drumstick-devel package from your Linux distribution (or compile it from sources), and manage the dependency with pkg-config, or you define the environment variables DRUMSTICKINCLUDES and DRUMSTICKLIBS in your QtCreator project settings, or your shell.

  2. A MIDI file is like a musical score. There are many musical parameters that your data do not specify, like tempo, tonality, time signature..., I've tried to choose reasonable values, and provide appropriate code comments.

  3. The general strategy is to generate a sequence of midi events, in SMFBuilder::generate() that is later written to the file.

After generating the file "output.mid", this is the representation by Rosegarden:

enter image description here