1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-15

この記事は Raspberry PiとMIDIインターフェースでデバイス開発をする のシリーズ記事です。

シリーズ目次

  1. Raspberry PiとMIDIインターフェースでデバイス開発をする
  2. RtMidiライブラリを使って、MIDIインターフェースを作成する
  3. SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む(プロトタイプ編)
  4. SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む(クラス編)
  5. 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 &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を実行します。

$ 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に外部スイッチを扱うドライバを実装する
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?