内容はタイトルのとおりで、最近書いていた以下の記事の流れの続きです。
- ブラウザで OSC を扱う初めの一歩(osc.js、osc-js、p5.js用ライブラリを見てみたりなど)【2021年9月】 - Qiita
- ZIG SIM と Node.js のプログラム(osc.js を利用)との間で UDP による OSC通信を軽く試す(OSC Data Monitor の話も) - Qiita
- osc.js で OSC Data Monitor に OSC over UDP でデータを送る(Node.js の keypress との組み合わせ) - Qiita
Sonic Pi について
Sonic Pi とは?
以下の公式サイトの説明によると、Sonic Pi はプログラムを書いて音作りなどができるオープンソースのソフトになります。
そして、Windows用・Mac用・Raspberry Pi用のものが提供されています。
●Sonic Pi - The Live Coding Music Synth for Everyone
https://sonic-pi.net/
今回、自分は Sonic Pi の Mac版を使って進めていきます。
Sonic Pi で OSC通信を使う(受信をする)
Sonic Pi は別アプリ・プログラムからの通信をトリガーにして音を鳴らすことができます。
プログラム
以下のページを見てみると、OSC での通信をトリガーにシンプルな音を鳴らすプログラムは、以下の簡単なもので実現できるようです。
●yoppa org – OSC通信 – ProcessingでSonic Piをコントロール
https://yoppa.org/fms_music17/8679.html
上記のプログラムをもとに、少し改変をして以下のようなプログラムにしてみました。
live_loop :trigger do
use_real_time
n = sync "/osc*/trigger/synth"
play n
end
元のプログラムの 3行目と 4行目は、それぞれ n = sync "/osc*/trigger/synth"
と play n
という内容に変更しています。
2つ目のほうは音の鳴らし方をシンプルにしただけなのです。1つ目の /osc*/
と変更した部分については、この後に補足を書くことにします。
待ち受けをするポート番号について
「Sonic Pi が待ち受けをするポート番号はどうなるのだろう?」と思ってメニュー等を見てみたら、Mac版の上部のメニューに「入出力 ⇒ Incoming OSC Port」と言う]項目があり、デフォルトで「4560番ポート」が指定されていました。
この後の Node.js のプログラムで必要になるので、念のため確認をしておいてください。
Node.js のプログラムで OSC通信
冒頭でも紹介した以下の記事で使ったプログラムを元に、今回使うプログラムを作ります。
●osc.js で OSC Data Monitor に OSC over UDP でデータを送る(Node.js の keypress との組み合わせ) - Qiita
https://qiita.com/youtoy/items/740686a8d2ffb0c6b8ba
先にプログラムを載せておきます。
プログラムを実行するためには、前回の記事と同様にパッケージのインストールが必要になりますが、今回は keypress も追加した $ npm install osc keypress
というコマンドででパッケージのインストールを行ってください。
const osc = require("osc");
const keypress = require("keypress");
var udpPort = new osc.UDPPort({
localAddress: "0.0.0.0",
localPort: 8001,
remoteAddress: "0.0.0.0",
remotePort: 4560,
metadata: true,
});
udpPort.on("message", function (oscMsg, timeTag, info) {
console.log("An OSC message just arrived!", oscMsg);
console.log("Remote info is: ", info);
});
udpPort.on("ready", function () {
console.log("ready");
keypress(process.stdin);
process.stdin.on("keypress", (ch, key) => {
if ((key && key.ctrl && key.name === "c") || (key && key.name === "q")) {
process.exit();
}
switch (key.name) {
case "e":
sendValue(70, key.name);
break;
case "r":
sendValue(75, key.name);
break;
case "t":
sendValue(80, key.name);
break;
case "y":
sendValue(85, key.name);
break;
default:
break;
}
});
process.stdin.setRawMode(true);
process.stdin.resume();
});
function sendValue(inputValue, inputText) {
udpPort.send({
address: "/trigger/synth",
args: [
{
type: "f",
value: inputValue,
},
],
});
console.log(`key.name: ${inputText}`);
}
udpPort.open();
remotePort: 4560
という部分は、Sonic Pi が OSC over UDP を待ち受けるポート番号で、アプリのメニューから確認した値になります。
上記を実行した後にキーボードの「e、r、t、y」のどれかを押すと、キーに対応した数値が OSC通信で Sonic Pi に送られます。
そして、Sonic Pi は、受けとった数値を使い「play n
」の部分を実行するので、キーが押された際に音が鳴ります。
なお今回は使わないですが、上記には 8001番ポートでの待ち受けの処理も含まれています(※ これも前回の記事と同様)。
Sonic Pi 側のプログラムの補足
上で Sonic Pi のプログラムに関する話を書いた中で、「 n = sync "/osc*/trigger/synth"
」という指定をしていた部分の補足をここで書きます。
これは、Node.js で「 address: "/trigger/synth"
」として数値を送った場合に、Sonic Pi側が受けとる内容に依存して変更したものです。
具体的には、上記の Node.js のプログラムを実行して OSC通信を行うと、Sonic Pi側の「Cue」の部分で以下の内容が表示がされます。
これを見ると、元のプログラムのとおりに /osc/...
と書いていると、 /osc:【IPアドレス】:【ポート番号】/...
という内容になっている部分に合致せず、音が鳴りません。
(当初はこの部分に気づけず、音がならない状態になってしまって、その原因がなかなか分かりませんでした...)
この話は、Sonic Pi で音が鳴らない状態になり、デバッグ用の情報がどこかに出てないか見ていたときに気がつくことができました。
上記の 2つを実際に動作させた時の様子
上記のプログラムを両方実行してからキーボードのキーを押して、Node.js のプログラムから Sonic Pi への OSC通信を行って音を鳴らした時の様子を動画で示します。
プログラムで音作りなどができる Sonic Pi と OSC通信との連携を試しました。
— you (@youtoy) September 23, 2021
Node.js側は「キーの押下 ⇒ OSC で値を送信」、Sonic Pi側は「OSC で値を受信 ⇒ 値に応じた音を鳴らす」という処理です。
Node.js側は、パッケージの osc.js と keypress を使って処理を実装してみました! pic.twitter.com/5SaE6rEKxG
おわりに
今回、Node.js で作ったプログラム(ライブラリに osc.js を利用)から Sonic Pi に OSC で通信をして、キー入力に連動して音を出力する仕組みを動作させることができました。
この後は、Sonic Pi にデータを送るプログラムをいろいろ変えてみたり、Sonic Pi側の音を出すプログラムに手を入れて、音の鳴らし方のバリエーションを増やしていこうと思います。
【追記】 Sonic Pi側のプログラムの別事例
複数で待ち受け
以下の記事から、OSC の通信の待ち受けを 2種類にする場合の例をメモしておきます。
●レーザーハープをつくってみる | hiyLab
https://hiylab.net/20200429-laser-harp/
# レーザー1を遮られた時に送信されるメッセージを受信して音を鳴らす。
live_loop :laser_1 do
use_real_time
sync "/osc/trigger/laser_1"
play 60 # Cの音を鳴らす
end
# レーザー2を遮られた時に送信されるメッセージを受信して音を鳴らす。
live_loop :laser_2 do
use_real_time
sync "/osc/trigger/laser_2"
play 64 # Eの音を鳴らす
end