Reputation: 101
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]
The instrument is Piano.
Also, observe that there are multiple notes at the same time.
Thanks in advance.
Upvotes: 0
Views: 994
Reputation: 2576
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:
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.
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.
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:
Upvotes: 0