SMF (Standard Midi Format)を解析し、MIDIインターフェースに流し込む（クラス編）

この記事は　**[Raspberry PiとMIDIインターフェースでデバイス開発をする](https://qiita.com/ixsiid/items/d126b1d6a073373c2143)** のシリーズ記事です。

### シリーズ目次
1. [Raspberry PiとMIDIインターフェースでデバイス開発をする](https://qiita.com/ixsiid/items/d126b1d6a073373c2143)
2. [RtMidiライブラリを使って、MIDIインターフェースを作成する](https://qiita.com/ixsiid/items/dfd093732d05260ed174)
3. [SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む（プロトタイプ編）](https://qiita.com/ixsiid/items/e3633bc0a594dc5fb972)
4. ***SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む（クラス編）***
5. [Raspberry Piに外部スイッチを扱うドライバを実装する](https://qiita.com/ixsiid/items/6389e71da5cb15ff7a98)


前回の記事をクラス化します。


```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 &note) {
	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を実行します。

```bash
$ 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を実行している状態で別のコンソールを立ち上げ、先ほど再生用バイナリを実行します。

```bash
$ ./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インターフェースに流し込む（プロトタイプ編）](https://qiita.com/ixsiid/items/e3633bc0a594dc5fb972)|[Raspberry Piに外部スイッチを扱うドライバを実装する](https://qiita.com/ixsiid/items/6389e71da5cb15ff7a98)|