この記事は Raspberry PiとMIDIインターフェースでデバイス開発をする のシリーズ記事です。
シリーズ目次
- Raspberry PiとMIDIインターフェースでデバイス開発をする
- RtMidiライブラリを使って、MIDIインターフェースを作成する
- SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む(プロトタイプ編)
- SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む(クラス編)
- Raspberry Piに外部スイッチを扱うドライバを実装する
前回の記事をクラス化します。
smf.hpp
#include <chrono>
#include "RtMidi.h"
#include "midi.hpp"
typedef struct _Note
{
int time;
unsigned char message[3];
} Note;
enum struct SmfStatus {
START, PLAY, STOP, PAUSE,
};
using std::chrono::system_clock;
class Smf {
private:
const char * filepath;
Note * notes;
Note * endpoint;
Note * current;
SmfStatus status;
system_clock::time_point startTime;
RtMidiOut * midi;
public:
Smf(const char * filepath, RtMidiOut * midi);
bool start();
bool play();
bool pause();
bool stop();
bool seek(int milliseconds);
};
smf.cpp
#include "smf.hpp"
#include "RtMidi.h"
#include "SmfParser.hpp"
#include "midi.hpp"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <smf.h>
#include <thread>
#include <unistd.h>
std::ostream &operator<<(std::ostream &Str, const Note ¬e) {
if (note.message[0] == 144) {
printf("timing %d[msec], note %d on, velocity = %d", note.time, note.message[1], note.message[2]);
} else if (note.message[0] == 128) {
printf("timing %d[msec], note %d off", note.time, note.message[1]);
} else {
printf("unknown note; %d %d %d %d", note.time, note.message[0], note.message[1], note.message[2]);
}
return Str;
}
bool compare_event(const ch_event &left, const ch_event &right) {
if (left.acumulate_time == right.acumulate_time) {
if (left.event_type == right.event_type) {
return left.par1 < right.par2;
}
return left.event_type < right.event_type;
}
return left.acumulate_time < right.acumulate_time;
}
Smf::Smf(const char *_filepath, RtMidiOut * _midi) {
midi = _midi;
filepath = _filepath;
std::cout << "parse: " << filepath << std::endl;
SmfParser smf = SmfParser(filepath);
smf.printcredits();
smf.parse(0);
smf.abstract();
std::cout << std::endl << "ticks per beat: " << smf.getTicks_per_beat() << std::endl;
int noteCount = 0;
for (auto x : smf.events)
if (x.event_type == 9 || x.event_type == 8) noteCount++;
std::sort(smf.events.begin(), smf.events.end(), compare_event);
notes = new Note[noteCount];
std::cout << "notes: " << notes << std::endl;
int tempo = smf.getBpm();
int tickPerBeat = smf.getTicks_per_beat();
float k = 60000.0f / (tempo * tickPerBeat);
std::cout << "tempo " << tempo << ", tpb " << tickPerBeat << ", k=" << k << std::endl;
int count = 0;
for (auto x : smf.events) {
if (x.event_type == 9) {
notes[count].time = (int)(x.acumulate_time * k);
notes[count].message[0] = 144;
notes[count].message[1] = x.par1;
notes[count].message[2] = x.par2;
count++;
} else if (x.event_type == 8) {
notes[count].time = (int)(x.acumulate_time * k);
notes[count].message[0] = 128;
notes[count].message[1] = x.par1;
notes[count].message[2] = 0;
count++;
}
}
endpoint = notes + count;
status = SmfStatus::STOP;
startTime = std::chrono::system_clock::now();
std::cout << "count: " << count << std::endl;
}
bool Smf::start() {
if (status != SmfStatus::STOP) return false;
std::thread thr([&](RtMidiOut *midi, Note *begin, Note *end) {
current = begin;
int pausedTime = -1;
while (status != SmfStatus::STOP) {
int acumulate = std::chrono::duration_cast<std::chrono::milliseconds> (
std::chrono::system_clock::now() - startTime
).count();
if (status == SmfStatus::PAUSE) {
// Pause直後は、現在の経過時間を保存する
if (pausedTime < 0) pausedTime = acumulate;
usleep(5000);
continue;
} else {
// Play直後は、Pauseしていた時間を再生開始時間に継ぎ足す
if (pausedTime >= 0) {
startTime += std::chrono::milliseconds(acumulate - pausedTime);
acumulate = pausedTime;
pausedTime = -1;
}
}
while (current < end) {
if (current->time <= acumulate) {
if (status == SmfStatus::PLAY) {
midi->sendMessage(current->message, 3);
printf("send: %d %d %d\n", current->message[0], current->message[1], current->message[2]);
}
current++;
} else {
break;
}
}
usleep(1500);
}
std::cout << "Thread destroy" << std::endl;
if (status != SmfStatus::STOP) std::cout << "Unknown reason for stopped" << std::endl;
return;
}, midi, notes, endpoint);
thr.detach();
status = SmfStatus::PAUSE;
return true;
}
bool Smf::play() {
if (status == SmfStatus::STOP) Smf::start();
status = SmfStatus::PLAY;
return true;
}
bool Smf::pause() {
if (status == SmfStatus::STOP) return false;
status = SmfStatus::PAUSE;
return true;
}
bool Smf::stop() {
if (status == SmfStatus::STOP) return false;
status = SmfStatus::STOP;
return true;
}
bool Smf::seek(int _milliseconds) {
startTime = std::chrono::system_clock::now() - std::chrono::milliseconds(_milliseconds);
current = notes;
while (current < endpoint) {
if (current->time >= _milliseconds) return true;
current++;
}
return false;
}
このクラスを使って、実際にSMFを再生します。
play.cpp
#include "RtMidi.h"
#include "midi.hpp"
#include "smf.hpp"
#include <algorithm>
#include <cstdlib>
#include <iostream>
int main() {
MidiInterface *port = new MidiInterface("Player");
// 後述のFluidsynthと接続する指定をします。
RtMidiOut *midiout = (RtMidiOut *)port->connect("FLUID", MidiDirection::OUT);
if (midiout == NULL) {
std::cout << "Not found target port name" << std::endl;
exit(1);
}
// 再生用ファイルは各自で用意
Smf *smf = new Smf("foobar.mid", midiout);
// start で再生用スレッドが準備されます。
smf->start();
while (true) {
std::cout << "\r> ";
fflush(stdout);
int c = std::cin.get();
if (c == 'p') {
// 再生を開始します
smf->play();
} else if (c == 's') {
// 一時停止
smf->pause();
} else if (c == 'r') {
// 最初から再生する
smf->seek(0);
} else if (c == 'q' || std::cin.eof()) {
break;
}
}
delete midiout;
delete port;
return 0;
}
実際に再生するまえに、Raspberry Piの再生機器設定を行います。
またMIDI再生インターフェースとしてFluidsynthを実行します。
$ amixer cset numid=3 1
numid=3,iface=MIXER,name='PCM Playback Route'
; type=INTEGER,access=rw------,values=1,min=0,max=2,step=0
: values=1
$ fluidsynth -a alsa -g 6 /usr/share/sounds/sf2/FluidR3_GM.sf2
FluidSynth version 1.1.6
Copyright (C) 2000-2012 Peter Hanappe and others.
Distributed under the LGPL license.
SoundFont(R) is a registered trademark of E-mu Systems, Inc.
fluidsynth: warning: Failed to pin the sample data to RAM; swapping is possible.
fluidsynth: warning: Requested a period size of 64, got 256 instead
fluidsynth: warning: Failed to set thread to high priority
fluidsynth: warning: Failed to set thread to high priority
Type 'help' for help topics.
>
Fluidsynthを実行している状態で別のコンソールを立ち上げ、先ほど再生用バイナリを実行します。
$ ./play.o
parse: foobar.mid
smf_parser library v1.01
Created by Joan Quintana Compte (joanillo)
Licensed under GPL v.3
ABSTRACT:
---------
SMF: foobar.mid
Format type: 1
Time division: 0 (ticks_per_beat)
Number of tracks: 18
ticks_per_beat: 480
Time Signature: 4 / 4
Tempo: 185
Number of bars: 104.000000
ticks per beat: 480
notes: 0xfc8d40
tempo 185, tpb 480, k=0.675676
count: 2138
> p
> send: 144 37 51
send: 144 56 58
send: 144 59 58
send: 144 63 58
...
前 | 次 |
---|---|
SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む(プロトタイプ編) | Raspberry Piに外部スイッチを扱うドライバを実装する |