DAW悪戦苦闘記

DAWやMIDIを通じてDTMを楽しむ記録。MIDI検定試験にもチャレンジするなり。

PythonでSMFを操作する (6) 移調

前回の続き(注: かなりマニアックです)。操作要領はほとんど同じである。

daw-jones.hatenablog.com

ノート番号と移調

MIDIでは音程をすべて半音単位のノート番号で管理しているので、移調・転調については各メッセージのノート番号を一斉に同一インターバルで加減すればよい。

たとえば1オクターブ下げたい場合、これは12半音下げることになるので、全メッセージのノート番号を12だけ減らせばよい。

ノート番号属性の修正

デルタタイムやチャネル同様に、MIDIメッセージ(ノート・オンまたはノート・オフ)はノート番号属性を持つので、この値を加減算して修正する。 

# ノート番号をnだけ加える関数
def transpose(track, n):
    for msg in track:
        if msg.type in ['note_on', 'note_off']:
            msg.note += n 

# 上記関数を呼び出して1オクターブ(12半音)下げる
transpose(track1, -12)

比較のために、修正前のメッセージ(ノート・オンまたはノート・オフの冒頭一部)と移調修正後のそれを下に例示しておく。

f:id:daw_jones:20170722213934p:plain

f:id:daw_jones:20170722214031p:plain

PythonでSMFを操作する (5) MIDIチャネル変更

前回の続き(注: かなりマニアックです)。今回は既存メッセージのMIDIチャネルを変更するが、要領は前回のデルタタイム修正とほぼ同じである。

daw-jones.hatenablog.com

Studio One で書き出したSMFの大きな問題

Studio One (Prime版)で Presence XT を使ってMIDI打ち込み再生した楽曲をSMFに書き出した場合、すべてのトラックのMIDIチャネルが"0"のままとなる*1

どうやら Presence XT は Kontakt のようなマルチチャネル出力に対応していないようなので、音色(概ねトラックに等しい)毎に出力MIDIチャネルを変える設定はできないと見受ける(下記チュートリアル参考)。

sleepfreaks-dtm.com

一括でチャネル値を変えるには

基本的な考え方は、デルタタイムの変更と同じで、対象トラックを構成する全メッセージの中でチャネル属性を持ったものにつき、そのチャネルの値を所望の値に置き換えるだけである。下例では、track1中のチャネルを"10"(Domino表記上はチャネル11)に変更している。

# チャネル変更のための関数
def modify_channel(track, channel):
    for msg in track:
        if hasattr(msg, "channel"):
            msg.channel = channel

# 上記関数を呼び出してチャネル修正
modify_channel(track1, 10)

実行結果は、当然ながら下記の通りとなる(冒頭メッセージのみ例示)。

f:id:daw_jones:20170722204811p:plain

*1:SMFのデータ上はゼロ・ベースの0〜15レンジ。Dominoに読み込むとチャネル1となる。

PythonでSMFを操作する (4) デルタタイム修正

前回記事の続き(注: かなりマニアックです)。今回は、前回未解決であった最初のノートオン・メッセージのデルタタイム、すなわち発音タイミングをシフトして修正する。

daw-jones.hatenablog.com

データ挿入に伴うズレ

途中挿入したセットアップ・データのメッセージ総数と送信経過時間を求める。以下のような簡単な関数を定義してみる。なお、各メッセージのデルタタイムは、そのtime属性*1を参照して引っ張り出せる。下記プログラム例では、msg.timeがそれに該当する。

# セットアップ・データのメッセージ総数と経過時間を求める関数
def setup_time(setup_msg):
    n = len(setup_msg)
    delta_time = sum([msg.time for msg in setup_msg])
    return (n, delta_time)

# 実際に計算してみると
nmsg, time_to_shift = setup_time(setup_msg)
print(nmsg, time_to_shift)

実際の計算結果は、15メッセージ (nmsg) かつ600ティック (time_to_shift) 分の長さとなる。したがって、冒頭から18 (= 3*2 + 15) メッセージ目に来る最初のノートオン・メッセージ*3につき、そのデルタタイムを600ティックだけ減らす必要がある。

該当メッセージのデルタタイム修正

該当メッセージのデルタタイム値を書き換えるには、そのメッセージ・オブジェクトのtime属性値を直接修正してしまえばよい。たとえば、以下の1行で事足りる。

# 該当メッセージのデルタタイム修正
track1[3 + nmsg].time -= time_to_shift

修正後のtrack1冒頭は下例の通りとなり、タイミングが修正されたことがわかる。

f:id:daw_jones:20170716173156p:plain

このように、メッセージ・オブジェクトの属性値を修正すれば、元からあるMIDIデータを色々と自由自在に変更修正可能で、MIDIチャネルや移調なども同様のやり方で簡単に編集できる。これらについては次回以降述べる。

*1:タイムと言いつつ値はティック単位である。ティックあたりの時間分解能(μs)は、メタトラックのテンポ設定値で定義される。

*2:元からあったメタメッセージ2件に、前々回に追加挿入したSysExメッセージ1件、計3件。

*3:本例ではたまたまノートオンだが、一般的にはコントロール・チェンジやピッチベンド・チェンジなどの可能性がある。

DominoでSMF保存した場合の注意点

今になって気づいたのだが、Domino上で各トラックの音色を選択設定した後にSMF保存すると、Domino独自の余計なバンクセレクトMSB/LSBメッセージが挿入されてしまう。

MIDI検定実技では、2級と1級を問わず、レギュレーション違反で減点対象になる可能性があるので、残さずに削除しておくべきと思われる。

現象

Dominoで音色設定すると、SMFに余分なバンクセレクトMSB/LSBメッセージ2行が書き足されてしまう。DominoからSMFとして書き出したものを再度Dominoに読み込んでみると確認できる(下図参照)。

f:id:daw_jones:20170717140505p:plain

対策 (1)

この不要メッセージは手動で削除した上で、SMFとして再保存する。多少手間が掛かってしまうが、三度再読み込みして不要メッセージが消去されたかどうか最終確認した方がよいだろう。2級2次試験の楽曲はトラック数が少ないので、このような手動対応でも全然問題ないと思う。

なお、この余計なバンクセレクトMSB/LSBがなくても指定音色(プログラム・チェンジ)によるDominoでの再生は可能である。

対策 (2)

2級にせよ1級にせよ、プログラム・チェンジを含めたシステム・セットアップデータはDominoで再生確認する前に、別途Dominoを使わずに追加する。すなわち、Studio One からベースとなるMIDIデータを書き出したSMFに、Pythonによるバッチ処理でセットアップデータを丸ごと書き込んでしまう。この場合は、Dominoで音色を選択操作する必要が一切ないため、Domino独自のバンクセレクト・メッセージが書き足されずに済む。

トラック数が非常に多い1級楽曲に関しては、このやり方でないと対応が難しいと思う。

確認作業

最終成果物としてのSMFは、再度Dominoに読み込んで、上記のような不要なメッセージが残存していないかどうか検証した方が安心である。念には念を入れるのであれば、さらにPythonのmidoパッケージを使い、SMFの各トラック冒頭部分を目視チェックするとより確実であろう(下記記事参照)*1

daw-jones.hatenablog.com

*1:むろんこれはPythonプログラミングができる人限定。MIDIのイベントリストを閲覧チェックできるDAWであればそれで十分であろう。

PythonでSMFを操作する (3) セットアップデータ挿入

前回の続き(注: かなりマニアックです)。前準備は済んでいるものとする。

daw-jones.hatenablog.com

セットアップデータの生成

セットアップ用のデータは、コントロール・チェンジとプログラム・チェンジを組み合わせた一連のメッセージ・リストとして定義できる。

前回同様に、midoパッケージのMessageメソッドで個々のメッセージ・オブジェクトを生成し、それをリストとして繋げればよい。下例では、リスト化した一連のメッセージを変数setup_msgに格納している*1。中身は、協会テンプレでお馴染みのセットアップ・データそのままであるが*2、説明せずともほとんど自明だと思う。

# セットアップ用データのメッセージ・リスト定義
setup_msg = [Message('control_change', channel=0, control=0, value=121, time=480),
             Message('control_change', channel=0, control=32, value=0, time=10),
             Message('control_change', channel=0, control=0, value=0, time=10),
             Message('control_change', channel=0, control=32, value=4, time=0),
             Message('program_change', channel=0, program=68, time=0),
             Message('control_change', channel=0, control=7, value=102, time=10),
             Message('control_change', channel=0, control=10, value=64, time=10),
             Message('control_change', channel=0, control=11, value=127, time=10),
             Message('control_change', channel=0, control=91, value=58, time=10),
             Message('control_change', channel=0, control=93, value=0, time=10),
             Message('control_change', channel=0, control=101, value=0, time=10),
             Message('control_change', channel=0, control=100, value=0, time=10),
             Message('control_change', channel=0, control=6, value=2, time=10),
             Message('control_change', channel=0, control=101, value=127, time=10),
             Message('control_change', channel=0, control=100, value=127, time=10)]

該当トラックへのセットアップデータの挿入

あとは上で作ったメッセージの塊をトラック・オブジェクト(メッセージのリスト)の所定の位置に挿入するだけである。前回のSysExメッセージ挿入後であれば、メタメッセージ2件とあわせて、先頭から3件飛ばした位置に差し込むことになる。これは単なるリスト操作に過ぎない。

# セットアップ・データを所定位置に挿入する
track1[3:3] = setup_msg

挿入後のトラック冒頭は以下のような結果となる。

f:id:daw_jones:20170716161637p:plain

実はこのセットアップ・データの挿入に伴って一番最初のノートオン・メッセージのデルタタイムを修正する必要がある。なぜなら、セットアップ・データの送信時間だけタイミングがズレてしまうからである。これについては、次回詳述する。

*1:実際にはプログラム・チェンジなどの値は各トラックに応じて変更する。なお、プログラム・チェンジの値はゼロ・ベース(0〜127のレンジ)に変換する必要がある。

*2:本例はメロディ・トラック用のもの。リズム用トラックの場合は、一番先頭のバンクセレクトMSBの値を"120"とし、末尾5行のベンド・レンジ設定部分を省く。また本例では、DominoでSMF保存した際に追加された余計なバンクセレクトMSB/LSB(頭から3行目および4行目)を付けたままだが、本来は不要である。これについては後日補足する。

PythonでSMFを操作する (2) SysEx挿入

前回記事の続き(注: かなりマニアックです)。今回はトラック1の1小節目冒頭に所定のSysExメッセージを挿入してみる。

daw-jones.hatenablog.com

下準備

midoパッケージのインポートと、Studio One から書き出したサンプルのSMFファイルをmid変数に読み込むところまでやっておく。

from mido import MidiFile, Message

# 各トラック毎の全メッセージを表示する
def dump_track(track_obj):
    for msg in track_obj:
        print(msg)

# 全トラックの全メッセージをトラック毎に表示する
def dump_smf(midi_obj):
    for i, track in enumerate(midi_obj.tracks):
        print(f"Track {i}: {track.name}")
        dump_track(track)

# Studio One から書き出したSMFを mid に読み込む
mid = MidiFile('smf_studio1.mid')

対象トラックの抽出

SysExメッセージはトラック1に挿入したいと思うので、SMFオブジェクト(上例ではmid変数に格納)を構成するトラック・リストより、トラック1を抜き出して操作することにする*1

mid.tracksの中身を見れば、構成トラックは以下の通りに7件あるが、このうち最初の要素はメタイベント用のトラックなので、トラック1は2番目の要素("Oboe"というトラック名が付いている)ということになる。

f:id:daw_jones:20170716133626p:plain

したがって、トラック1を以下のように抜き出して変数代入する(リストのインデックスはゼロベースなので、2ではなくて1)。

# トラック1を抜き出して、変数"track1"に代入する
track1 = mid.tracks[1]

このトラックの中身は以下のようになっている(11行目以降は省略)。このメッセージ・リストの2番目の要素の直後(メタメッセージの直後)にSysExメッセージを挿入する。

f:id:daw_jones:20170716134356p:plain

メッセージ・オブジェクトの生成

メッセージ・データの作成は非常に簡単で、たとえばGM2音源使用オンのためのSysExメッセージは、以下の1行で済む*2。データ値は10進数のタプルで与える。ここは協会テンプレSMFを上記と同じくmidoパッケージを使って覗いてみた結果をそのままコピーすればよい。他セットアップ用のデータも同様である(次回以降詳述)。

# SysExメッセージの作成
msg = Message('sysex', data=(126, 127, 9, 3))

本メッセージをprintすれば、以下のように中身を表示確認できる。

f:id:daw_jones:20170716140156p:plain

なお、midoパッケージにおける各種MIDIメッセージの仕様については、公式ドキュメントを参照されたい。

メッセージの挿入

トラック・オブジェクトはリストのサブクラスで、要はメッセージのリスト構造になっているので、リスト操作のメソッドが適用可能である。したがって、上記メッセージの挿入は、そのものずばりinsertメソッドで入れてしまえばできあがりである。

# 上記SysExメッセージを2番目の要素の後ろに挿入する
track1.insert(2, msg)

挿入後のトラック1の中身は、以下のようになった(冒頭5行)。

f:id:daw_jones:20170716141707p:plain

*1:元のSMFオブジェクトmidを直接修正してもよいが、ここでは分かりやすくするために該当のトラック・オブジェクトを切り出して編集操作する。[ここからはマニアックな話] Pythonではリストなどのミュータブルなオブジェクトを変数に代入しても、それはオリジナルに対する参照になるため、実は変数track1の中身を操作すると元のmid.tracks[1]にも反映される。「Pythonのリストで参照渡しでないコピーを作成する話」などを参考。

*2:ここではデルタタイムはデフォルトのゼロのままでよい。

小岩井さんのMIDI検定1級合格体験記事

今朝上げた記事の補足。

daw-jones.hatenablog.com

たまたま同タイミングで、小岩井ことりさんのMIDI検定1級合格関連の大変興味深いインタビュー記事が「DTMステーション」に掲載されていたので参考まで。トップ合格であると同時に、2次審査では出題者の外山和彦先生よりS判定を貰っているとはまったく驚嘆するほかない成果である。

www.dtmstation.com

 

記事中でかなり驚いたのは、MIDIデータを2度打ち込んで比較照合することによりミスを炙り出したというくだり。昨年の課題曲は音数少なめだったからこそできた芸当かもしれず。私にはこのアプローチは正直しんどいと思う(無理っす)。

あと楽器音色に応じて複数音源を使い分けておられる点は、案の定というか、私が昨日書いた所感と共通しているところである。個人的に気になったのは、ホルン(ゲシュトップフト奏法含む)はシンセ音色なのだろうか、という点。

ミックスとエフェクト処理は凝りまくってる感じで、ここはもうツールからなにから私は足元にも及ばないな、と僭越ながら感心することしきり。ただ、ステレオ・エンハンサーを使い過ぎでは、と怪訝に思い、小岩井さん自身が制作されたオーディオ(これは結構貴重です)を聴いたら、やっぱり中央がぽっかり抜けてて左右振り過ぎな感は否めなかった。しかし、ハープとかストリングズの音色は結構印象的。またEQやフィルター等駆使して不要な音域を積極的にカットする処理は私も見習いたい。