3
0

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 3 years have passed since last update.

Increments × cyma (Ateam Inc.)Advent Calendar 2020

Day 25

serpentでドラムマシンを作る

Last updated at Posted at 2020-12-24

Increments × cyma (Ateam Inc.) Advent Calendar 2020 の25日目は
Increments株式会社の @ktroutner が担当します。

最近は大学時代に授業でやったプロジェクトをもう一度見てコードを整理したりしています。その中で面白かったプロジェクトをひとつここで紹介したいと思います。

serpentという言語でスケジューリングを利用してMIDIメッセージを送信する、という内容です。今回は簡単なドラムマシンを作りたいと思いますが自由に拡張するところがありますので色々試してみると面白いです。(最後にいくつか紹介します。)

serpentとは

serpentはコンピュータミュージックアプリケーションなどリアルタイムシステムのために作成された言語です。(pythonに似ているのでpythonの経験がある方はなんとなく分かるかもしれません。)

メジャーな言語ではないためドキュメンテーションが少ないですが、ここにあるドキュメンテーションを基にここで説明していきます。

インストール

  1. このサイトから最新のバージョンをダウンロードします。

  2. ファイルをホームディレクトリに移動する

  3. バイナリファイルをbinディレクトリに移動する

    mkdir ~/bin
    cd ~/serpent
    mv serpent64 wxserpent64.app ~/bin
    
  4. wxserpent64を実行するスクリプトを作成する

    #!/bin/sh
    echo "running wxserpent64 with arguments $*"
    /Users/ktroutner/bin/wxserpent64.app/Contents/MacOS/wxserpent64 $*
    
  5. スクリプトを実行可能にする

chmod +x ~/bin/wxserpent64
```
5. PATHを設定する
:warning: `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スタジオを開きます。

image.png

IACドライバーをダブルクリックして、「装置はオンライン」にチェックを入れます。

image.png

MIDIデバイスの設定

ここまでできたらMIDIメッセージは受信できるので、次はメッセージを音として出力するシンセサイザーを作成します。

シンセサイザーはなんでもいいですが、Macの場合はとりあえず無料で使えるGarageBandが良いでしょう。

GarageBandを開いて「トラックのタイプを選択」で「ソフトウェア音源」を選択します。

image.png

ドラムマシンを想定しているので「Electronic Drum Kit」から「After Party」を選択してみます。

image.png

ドラム以外ももちろん使えますので好きなものを試してみると面白いかと思います。(SynthesizerのBassは個人的に好きでした。)シンセサイザーによってノートが変わる場合もありますのでビートファイルの修正も必要かもしれません。

次に試してみると面白いこと

  • TouchOSCを使ってスマホで操作できるようにする
  • ビートファイルでもっと細かいタイミングを可能にする
  • pythonで実装する

Increments × cyma (Ateam Inc.) Advent Calendar 2020 は以上となります! それでは良いお年を!Happy New Year!

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?