Increments × cyma (Ateam Inc.) Advent Calendar 2020 の25日目は
Increments株式会社の @ktroutner が担当します。
最近は大学時代に授業でやったプロジェクトをもう一度見てコードを整理したりしています。その中で面白かったプロジェクトをひとつここで紹介したいと思います。
serpentという言語でスケジューリングを利用してMIDIメッセージを送信する、という内容です。今回は簡単なドラムマシンを作りたいと思いますが自由に拡張するところがありますので色々試してみると面白いです。(最後にいくつか紹介します。)
serpentとは
serpentはコンピュータミュージックアプリケーションなどリアルタイムシステムのために作成された言語です。(pythonに似ているのでpythonの経験がある方はなんとなく分かるかもしれません。)
メジャーな言語ではないためドキュメンテーションが少ないですが、ここにあるドキュメンテーションを基にここで説明していきます。
インストール
-
このサイトから最新のバージョンをダウンロードします。
-
ファイルをホームディレクトリに移動する
-
バイナリファイルを
bin
ディレクトリに移動するmkdir ~/bin cd ~/serpent mv serpent64 wxserpent64.app ~/bin
-
wxserpent64
を実行するスクリプトを作成する#!/bin/sh echo "running wxserpent64 with arguments $*" /Users/ktroutner/bin/wxserpent64.app/Contents/MacOS/wxserpent64 $*
-
スクリプトを実行可能にする
chmod +x ~/bin/wxserpent64
```
5. PATH
を設定する
`SERPENTPATH`は`export`しないと起動できない
```
PATH=$PATH:/Users/ktroutner/bin
export SERPENTPATH="/Users/ktroutner/serpent/lib:/Users/ktroutner/serpent/programs:/Users/ktroutner/serpent/wxslib
```
ここまでできたら、wxserpent64
のインタプリタを実行できるはずです。
wxserpent64 wxs_test.srp
参考: https://cs.cmu.edu/~music/serpent/doc/installation.htm
スケジューリングについて
以下のように好きなタイミングで処理をスケジューリングできます。
こちらのサンプルコードは1秒に1回メッセージを出力します。
require "debug"
require "sched"
def activity(i):
display "activity", i, sched_rtime, time_get()
sched_cause(1, nil, 'activity', i+1)
sched_init()
sched_cause(1, nil, 'activity', 101)
sched_run()
-
sched_init
でスケジュールを初期化する -
sched_cause(delay, object, callback, params)
-
delay
: 何秒後にスケジュールするか -
object
: メソッドを呼び出すオブジェクト -
callback
: 呼び出すメソッド -
params
: 呼び出すメソッドに渡すパラメータ
-
参考:https://cs.cmu.edu/~music/serpent/doc/scheduling.htm
MIDIメッセージを送信する
serpentが提供しているPortMidiのAPIを使ってMIDIメッセージを送信します。
midi_create()
でMIDIのストリームを作成して、midi_open_output
でオープンします。
midi_open_output(midi, devno, buffer_size, latency)
-
midi
:midi_create
で作成したMIDIストリーム -
devno
: MIDIのアウトプットデバイス番号(midi_out_default()
でデフォルトを取得できる) -
buffer_size
: バッファーサイズ -
latency
: レイテンシー(ミリ秒)
このストリームを使ってmidi_write
でメッセージを送信します。
midi_write(midi, time, msg)
-
midi
:midi_create
で作成したMIDIストリーム -
time
: タイムスタンプ(今回特に気にしないので0
を渡す) -
msg
: MIDIメッセージ- MIDIメッセージの構成についてはこちら
参考:https://cs.cmu.edu/~music/serpent/doc/serpent-midi.htm
ドラムマシンの実装
ここで流れだけを説明しますが、全てのコードはGitHubにあります。
https://github.com/ktroutner/drum-machine
初期化
MIDIのセットアップを行います。
mo = midi_create()
result = midi_open_output(mo, MIDI_ID, 100, 0)
if (result != 0)
print "midi_open_returns "; result
return
スケジュールの設定も行います。
sched_init()
rtsched.time_offset = time_get()
vtsched.set_bps(tempo/60.0)
ノートの再生
drum
メソッドの中で、現在時刻のノートを midi_note_on
で再生して、停止する midi_note_off
をスケジュールします。
次時刻でまた drum
の呼び出しもスケジュールします。
def midi_note_on(midi, chan, key, loud)
chan = chan & 15
midi_write(midi, 0, chr(midi_status_noteon + chan) + chr(key) + chr(loud))
//play the current notes, schedule them to be turned off, and schedule
//notes for next beat
def drum(id, i, n)
if id != player_id
return
p = int(subseq(sequence, i, i+1)) - 1
notes = patterns[p]
for note in notes[n]
midi_note_on(mo, 9, note, 100)
sched_cause(intrvl_sec, nil, 'midi_note_on', mo, 9, note, 0)
if n == len(notes)-1
if i < len(sequence)-1
sched_cause(intrvl_sec, nil, 'drum', player_id, i+2, 0)
else
return //nothing more to do
else
sched_cause(intrvl_sec, nil, 'drum', player_id, i, n+1)
そして、再生ループを初期化後に開始します。
vtsched.cause(0.2, nil, 'drum', [player_id, 0, 0])
wxs_timer_start(2, 'timer_callback')
再生の制御
音声を再生するだけではなく、再生中にピッチベンドやテンポの変更などのエフェクトも実装できます。今回ははテンポを変更するエフェクトを実装してみます。
def set_tempo(obj, x)
vtsched.set_bps(x/60.0)
serpentが簡単なUIも提供していますのでこちらを使って再生中に自由にテンポの変更を行えるようにします。
tempo_slider = Labeled_slider(0, "Tempo", 5, 30, 250, 20,
70, 10, 300, tempo, 'linear')
tempo_slider.method = 'set_tempo'
ビートファイルについて
どのノートをどのタイミングで再生するかはビートファイルで指定します。なお、同じパターンを簡単に繰り返しできるように作っています。
1 1 1 1 2 2 2 2
120
46xxxxxxxx
38 x x
36x x
42xxxxxxxx
38 x
35x x
1行目はパターンを再生順番です。(各パターンは下の方で定義しています。)
2行目ではデフォルトのテンポを指定します。
3行目に空行を入れて、以降は各パターンの定義です。
パターンのフォーマットとしては、1行ごとにノートナンバーとそのノートを再生するビートを指定します。(1文字は八分音符です。「x」は再生する、空白は再生しない、というふうに扱います。)
パターンの間に空行を入れますので、上のサンプルで以下の2パターンを定義することになります。
パターン1
46xxxxxxxx
38 x x
36x x
パターン2
42xxxxxxxx
38 x
35x x
ドラムマシンを実行する
上のプログラムは実行できますが、まだ音は出ない状態になります。
実際に音を出すためにPCでMIDIのドライバーとデバイスを設定する必要があります。
MIDIバスの設定
Macの場合、Applications/Utilities
にある「Audio MIDI設定」から設定できます。
ウインドウ > MIDIスタジオを表示
でMIDIスタジオを開きます。
IACドライバー
をダブルクリックして、「装置はオンライン」にチェックを入れます。
MIDIデバイスの設定
ここまでできたらMIDIメッセージは受信できるので、次はメッセージを音として出力するシンセサイザーを作成します。
シンセサイザーはなんでもいいですが、Macの場合はとりあえず無料で使えるGarageBandが良いでしょう。
GarageBandを開いて「トラックのタイプを選択」で「ソフトウェア音源」を選択します。
ドラムマシンを想定しているので「Electronic Drum Kit」から「After Party」を選択してみます。
ドラム以外ももちろん使えますので好きなものを試してみると面白いかと思います。(SynthesizerのBassは個人的に好きでした。)シンセサイザーによってノートが変わる場合もありますのでビートファイルの修正も必要かもしれません。
次に試してみると面白いこと
- TouchOSCを使ってスマホで操作できるようにする
- ビートファイルでもっと細かいタイミングを可能にする
- pythonで実装する
- スケジューリング: sched
- MIDIメッセージ送信:python-rtmidi OR mido
Increments × cyma (Ateam Inc.) Advent Calendar 2020 は以上となります! それでは良いお年を!Happy New Year!