メインコンテンツまでスキップ

チュートリアルMIDIデータの作成

📚 Source Page

このチュートリアルではMidiMessageクラスはMIDIデータを表現するために使われます。このクラスはMidiBufferクラスもMIDIメッセージのバッファを扱うために導入されている。

レベル初心者

プラットフォームWindows , macOS , Linux , iOS , Android

クラス: MidiMessage,MidiBuffer,Time,Timer

スタート

注記

このチュートリアルは、あなたがMIDI一般に精通していることを前提としています。また、JUCEのボタンやスライダーの使い方にも慣れているはずです(Tutorial: The Slider classそしてTutorial: Listeners and Broadcasters).

このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

デモプロジェクトでは、MIDIチャンネル10にMIDIメッセージを作成するための4つのボタンを用意しています。バスドラム、スネアドラム、クローズドハイハット、オープンハイハットです。また、ボリュームコントローラーメッセージ(連続コントローラー7)を作成するスライダーもあります。インターフェイスは次のスクリーンショットのとおりです。

ドラムパッドボタンとボリュームスライダー
ドラムパッドボタンとボリュームスライダー

右側のパネルには、生成された MIDI メッセージのリストがタイムスタンプ(アプリケーショ ンの起動時刻からの相対時間)とともに表示されます。

注記

アプリケーションはMIDIデータを送信したり、音を出したりせず、MIDIデータを表示するだけです。

MidiMessageクラス

このチュートリアルでは、いくつかのMIDIメッセージタイプを作成するのに必要なコードを説明します。また、ほとんどのMIDIメッセージタイプを解析するコードも含まれています。一般的にMidiMessageクラスにはstaticを作成するためのメンバー関数です。MidiMessageオブジェクト(例えばMidiMessage::noteOn()関数があります)。また、以下のような問い合わせやアクセスのためのメンバー関数もあります。MidiMessageオブジェクト(例えばMidiMessage::isNoteOn()そしてMidiMessage::getNoteNumber()関数)。

MidiMessageオブジェクトの作成

をご覧ください。public static member functionsのためにMidiMessageクラスを作成します。これには、異なるタイプのMIDIメッセージを作成するためのすべての関数がリストされています。またMidiMessage個々のバイトや生データからオブジェクトを作成するが、これらはマストは MIDI 仕様に従った有効な MIDI メッセージです。(無効なMidiMessageオブジェクト)

注記

MidiMessageオブジェクトは通常、ローカル変数またはメンバ変数として格納され、値で渡されるべきである。

ノートオン・メッセージを作成するにはMidiMessage::noteOn()関数を使用します。これにはMIDIチャンネル(番号1 .. 16)、ノート番号(0 .. 127)、速度(as auint8価値0 .. 127).あるいは、速度は次のように表すこともできる。floatに変換される。0 .. 127内部的に(最も近い整数に四捨五入)。

注記

ベロシティがゼロのノートオンは、実際にはノートオフのメッセージである。1 .. 127(を中心としたノートオンの最小浮動小数点速度となる)。0.004f).またMidiMessage::noteOff()ノートオフ・メッセージを特別に作成するための機能で、ノートオフ・ベロシティも指定できます(一部のシンセサイザーで認識されます)。

デモ・プロジェクトでは、ベロシティが100で、どのボタンがクリックされたかによって異なるノート番号のノートオン・メッセージを作成します:

    void setNoteNumber (int noteNumber)
{
auto message = juce::MidiMessage::noteOn (midiChannel, noteNumber, (juce::uint8) 100);
message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToList (message);
}

に注目してほしい。noteNumberの値はボタンによって設定され、setNoteNumber()関数に渡されます。また100に対するuint8型になります。このようにしないと、コンパイラが、どのバージョンのMidiMessage::noteOn()関数が呼び出されるべきである。

のタイムスタンプを設定する。MidiMessageはオプションだが、イベントが生成されたり受信されたりした時刻を記録するのに非常に便利である。デフォルトのタイムスタンプはゼロで、タイムスタンプの時間単位は定義されていません。一般的に、どの時間単位を使うかはアプリケーション次第です。この単純なケースでは、秒を単位としています。Time::getMillisecondCounterHiRes()関数を使用し、0.001を乗算する(その時点からの相対時間となるように、アプリケーションの起動時間を差し引く)。

ボリューム・スライダーは、コンティニュアス・コントローラー(CC)メッセージの作成に使用されます。CC7はボリューム・コントロール変更メッセージです:

        volumeSlider.onValueChange = [this]
{
auto message = juce::MidiMessage::controllerEvent (midiChannel, 7, (int) volumeSlider.getValue());
message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToList (message);
};

MidiMessageオブジェクトの解析

私たちのaddMessageToList()関数はタイムスタンプとMIDIメッセージを解析し、インターフェースのメッセージリストに表示できるようにします:

    void addMessageToList (const juce::MidiMessage& message)
{
auto time = message.getTimeStamp();

auto hours = ((int) (time / 3600.0)) % 24;
auto minutes = ((int) (time / 60.0)) % 60;
auto seconds = ((int) time) % 60;
auto millis = ((int) (time * 1000.0)) % 1000;

auto timecode = juce::String::formatted ("%02d:%02d:%02d.%03d",
hours,
minutes,
seconds,
millis);

logMessage (timecode + " - " + getMidiMessageDescription (message));
}

についてgetMidiMessageDescription()関数は実際にMIDIデータを解析し、人間が読めるメッセージの説明を得ます。

    static juce::String getMidiMessageDescription (const juce::MidiMessage& m)
{
if (m.isNoteOn()) return "Note on " + juce::MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3);
if (m.isNoteOff()) return "Note off " + juce::MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3);
if (m.isProgramChange()) return "Program change " + juce::String (m.getProgramChangeNumber());
if (m.isPitchWheel()) return "Pitch wheel " + juce::String (m.getPitchWheelValue());
if (m.isAftertouch()) return "After touch " + juce::MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + ": " + juce::String (m.getAfterTouchValue());
if (m.isChannelPressure()) return "Channel pressure " + juce::String (m.getChannelPressureValue());
if (m.isAllNotesOff()) return "All notes off";
if (m.isAllSoundOff()) return "All sound off";
if (m.isMetaEvent()) return "Meta event";

if (m.isController())
{
juce::String name (juce::MidiMessage::getControllerName (m.getControllerNumber()));

if (name.isEmpty())
name = "[" + juce::String (m.getControllerNumber()) + "]";

return "Controller " + name + ": " + juce::String (m.getControllerValue());
}

return juce::String::toHexString (m.getRawData(), m.getRawDataSize());
}
注記

同じ機能は、メンバー関数[MidiMessage::getDescription()](https://docs.juce.com/master/classMidiMessage.html#a868d95a096fad999de5ba11f9a2f6340 "例えば "Note On C#3 Veloci... "のように。").ここでは、既製の実装は使用せず、異なるタイプのMIDIメッセージの扱い方を説明するために独自に実装します。

この関数はすべてのタイプのMIDIメッセージを解析しようとします(ここまではノートオンとコントローラーメッセージの作成しか見ていませんが)。のデータにアクセスするお勧めの方法をご覧ください。MidiMessageオブジェクトがある:

  • MIDIメッセージのタイプを決定する("is "で始まる関数のいずれかを使う)。
  • そのタイプのMIDIメッセージにアクセスするための適切な関数を使用します。

この関数の最終行に到達するのは、メッセージがシステム・メッセージの場合だけである(例えば、システム・エクスクルーシブ)。メッセージの生データにアクセスするにはMidiMessage::getRawData()しかし、一般的には、ほとんどの目的で組み込み関数の範囲を使用する方が簡単(そして読みやすい)です。

警告

関数を使ってMidiMessage間違ったタイプのメッセージはエラーになる。例えばMidiMessage::getNoteNumber()関数はMidiMessageオブジェクトがありますが、これはそのメッセージがノート・オン・メッセージかノート・オフ・メッセージかを確認するものではありません。あなたはマストのいずれかの関数でまずチェックする。MidiMessage::isNoteOn(),MidiMessage::isNoteOff()あるいはMidiMessage::isNoteOnOrOff().

エクササイズ

を変更する。getMidiMessageDescription()関数を使用して、ノートオン・メッセージの速度をリストする。どの関数を使うべきかについては、APIリファレンスを確認してください。

MidiBufferクラス

私たちのデモ・アプリケーションの1つの問題は、ノートオフ・メッセージを作成しないことです。私たちはパーカッション・サウンド用のMIDIメッセージを作成しているだけなので、これは大きな問題ではないように思えます。しかし、ノート・オン・メッセージに対応するノート・オフ・メッセージを作成しないのはバッドプラクティスです。スタック注釈)。

のノート・オンの直後にノート・オフを追加すればいい。setNoteNumber()関数である:

auto message = juce::MidiMessage::noteOn (1, noteNumber, (uint8) 100);
message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToList (message);

auto messageOff = juce::MidiMessage::noteOff (message.getChannel(), message.getNoteNumber());
messageOff.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToList (messageOff);
uint8unsigned char uint8A platform-independent 8-bit unsigned integer type.Definition juce_MathsFunctions.h:52

ノートオフメッセージのタイムスタンプを変更することもできますが(例えばノートオンメッセージの0.1秒後)、これはメッセージがリストに投稿されるときには変更されません:

auto message = juce::MidiMessage::noteOn (1, noteNumber, (uint8) 100);
message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToList (message);

auto messageOff = juce::MidiMessage::noteOff (message.getChannel(), message.getNoteNumber());
messageOff.setTimeStamp (message.getTimeStamp() + 0.1);
addMessageToList (messageOff);

についてMidiBufferクラスはMIDIメッセージのバッファをタイムスタンプに基づいて繰り返し処理する関数を提供します。これを説明するために、簡単なスケジューリング・システムをセットアップします。MidiMessage特定のタイムスタンプを持つオブジェクトをMidiBufferオブジェクトを使用します。次にTimerMIDIメッセージが配信される予定になっているかどうかを定期的にチェックするオブジェクト。

警告

についてTimerクラスは高精度のタイミングには適していません。これは、すべての関数呼び出しをメッセージスレッド.より堅牢なタイミングを得るには、別のスレッドを使用する必要があります(ほとんどの場合、オーディオスレッドはレンダリングに適しています)。MidiBufferオブジェクトをオーディオに取り込む)。

メンバーを追加するMainContentComponentクラスである:

    juce::MidiBuffer midiBuffer;        // [1]
double sampleRate = 44100.0; // [2]
int previousSampleNumber = 0; // [3]
  • [1](その)MidiBufferオブジェクトそのものである。
  • [2](その)MidiBufferクラス使用サンプルをMIDIメッセージのタイムスタンプの単位とします。オーディオを生成するわけではありませんが、MIDIメッセージのタイムスタンプとして使うものを選ぶ必要があります。サンプルレート.このメンバを使ってサンプルレートを格納する。(一般的な値なので44,100を使用する)。
  • [3]の中でどのタイムスタンプに到達したかを追跡する必要がある。MidiBuffer.このメンバは、このタイムスタンプをサンプルとして保存するために使用します。

MidiBufferオブジェクトにMIDIメッセージを追加する

MIDIメッセージをメッセージ・リストに直接追加する代わりに、MIDIメッセージをMidiBufferオブジェクトを呼び出します。この関数はMidiBuffer::addEvent()関数である:

    void addMessageToBuffer (const juce::MidiMessage& message)
{
auto timestamp = message.getTimeStamp();
auto sampleNumber = (int) (timestamp * sampleRate);
midiBuffer.addEvent (message, sampleNumber);
}

を修正する。setNoteNumber()関数とSlider::onValueChangeヘルパー・オブジェクトでこの機能を利用することができます。これにより、MIDIメッセージ・イベントを未来にスケジューリングすることができます:

    void setNoteNumber (int noteNumber)
{
auto message = juce::MidiMessage::noteOn (1, noteNumber, (juce::uint8) 100);
message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToBuffer (message);

auto messageOff = juce::MidiMessage::noteOff (message.getChannel(), message.getNoteNumber());
messageOff.setTimeStamp (message.getTimeStamp() + 0.1);
addMessageToBuffer (messageOff);
}
        volumeSlider.onValueChange = [this]
{
auto message = juce::MidiMessage::controllerEvent (10, 7, (int) volumeSlider.getValue());
message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToBuffer (message);
};

MidiBufferオブジェクトの反復処理

バッファからメッセージを読み出すには、タイマーを実装する必要がある。バッファにTimerクラスを基本クラスとする:

class MainContentComponent   : public juce::Component,
private juce::Timer
{

そしてTimer::timerCallback()関数である:

    void timerCallback() override
{
auto currentTime = juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime;
auto currentSampleNumber = (int) (currentTime * sampleRate); // [4]

for (const auto metadata : midiBuffer) // [5]
{
if (metadata.samplePosition > currentSampleNumber) // [6]
break;

auto message = metadata.getMessage();
message.setTimeStamp (metadata.samplePosition / sampleRate); // [7]
addMessageToList (message);
}

midiBuffer.clear (previousSampleNumber, currentSampleNumber - previousSampleNumber); // [8]
previousSampleNumber = currentSampleNumber; // [9]
}
  • [4]現在時刻をサンプル単位で計算します。
  • [5]バッファ内のメッセージを繰り返し処理する。
  • [6]から直近に取得されたMIDIメッセージのタイムスタンプが、そのMIDIメッセージのタイムスタンプと一致しない場合。MidiBufferオブジェクトが未来にある場合は、処理を終了してwhile()走る。
  • [7]のタイムスタンプ。MidiMessageオブジェクトのタイムスタンプは、サンプル数に基づいています。これを秒ベースのタイムスタンプシステムにリセットして、私たちのaddMessageToList()関数を修正する必要はない。
  • [8](その)MidiBuffer::clear()関数はある範囲内のタイムスタンプを持つ MIDI メッセージをバッファからクリアします。この関数は、処理したばかりのメッセージを削除するために使います。
  • [9]この関数が実行された時間を記録しておく。timerCallback()関数が呼び出される。

最後に、タイマーをMainContentComponentビルダー

        setSize (800, 300);
startTimer (1);
}
注記

これらの修正のコードはMidiMessageTutorial_02.hファイルにある。

エクササイズ

クラッシュシンバル(音符番号49)とライドシンバル(音符番号51)のボタンを追加する。パンコントロール用のスライダー(CC10)を追加します。の中に、これら3つのコンポーネントを追加するためのスペースが残されています。resized()関数である。

概要

このチュートリアルではMidiMessageクラスとMidiBufferクラスです。このチュートリアルを読めば、次のことができるようになるでしょう:

  • 作成MidiMessageノートオン、ノートオフ、コンティニュアス・コントローラー(コントロール・チェンジ)など。
  • を解析する。MidiMessageオブジェクトの型を発見し、そこから有用なデータを取得する。
  • MIDIメッセージをMidiBufferオブジェクトがある。
  • でMIDIメッセージを反復処理する。MidiBufferオブジェクトのタイムスタンプに基づく。

こちらも参照