p5jsとSonicPiをつなぐデスクトップアプリ

# はじめに
前回、<a href="https://qiita.com/mathrax-s/items/79c1170301a5faa08867">p5jsとElectron Fiddleでデスクトップアプリをつくりました</a>。そもそものきっかけは、p5jsとSonicPiをつなぐツールをつくろうと思ったからでした。そのアプリができたのでこちらにまとめます。

SonicPiにはOSC（OpenSoundControl）というネットワーク通信機能があります。別のPCから遠隔操作もできるのですが、同じPCの異なるソフト間でも使えて、たとえば「Processingでつくったヴィジュアルにあわせて、SonicPiでつくった本格的なサウンドを同期させる」など連携することができます。ProcessingだとoscP5というライブラリでできますが、Processingをインストールしてもらったり、Processingでアプリ化してもデータサイズが大きくなってしまいます。

そんなとき、SonicPiの掲示板「in_thread」で、こちらのエントリーをみつけました。
https://in-thread.sonic-pi.net/t/live-coding-with-machine-learning-magenta-js/4462

個人的にちょっと気になっている、Googleの音楽系の人工知能ライブラリ「Magenta」を、p5jsがGUIを担当してSonicPiが音を担当し演奏しています。面白そうなので、オンラインワークショップ（プログラミングの経験がない人向け）の参考にしようと思ったのですが、p5jsとSonicPiをつなぐ（OSC通信を仲介してくれる）プログラムを、コマンドライン操作でインストールして実行する、という流れになっていて、なんとかシンプルにできないかなぁと思いました。

後ほどこのプログラム「bridge.js」をほぼそのまま使わせていただきますが、上記の掲示板でもリンクされていた「p5js-osc」の中にあります。
https://github.com/genekogan/p5js-osc

今回は、この「bridge.js」をデータサイズの小さいデスクトップアプリ（といっても190メガくらい）にして、「このアプリをダウンロードしてダブルクリックで起動してください」という感じで、p5jsとSonicPiがOSCメッセージをやりとりできるようにしたいと思います。

なお、SonicPiはv3.2.2です。SonicPiのv3.1の場合は、OSCのポート番号が違ったり、OSCメッセージが微妙に異なるので、以下の手順では動きません。v3.2以降をつかってください。

---
# 手順

## Electron Fiddleを起動する
![スクリーンショット 2020-10-28 15.53.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/20f74844-a833-a7e1-8069-1536c2de998b.png)


## 「bridge.js」を入手
![スクリーンショット 2020-10-28 16.01.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/725a1419-c5ac-3372-8395-95667bb62993.png)
https://github.com/genekogan/p5js-osc
こちらから「p5js-osc」をダウンロードすると、その中に「bridge.js」があります。

## 「renderer.js」にコピー＆ペースト
![スクリーンショット 2020-10-28 16.32.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/3b737e52-8fee-7638-49ec-591af3dce5af.png)
「bridge.js」をテキストエディタで開き、Electron Fiddleの「renderer.js」にコピー&ペーストします。

## 「RUN」ボタンで実行
![スクリーンショット 2020-10-28 16.36.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/56b1b7e2-0483-b3ab-44d7-345745665f90.png)
これだけで機能は満たしているので、最低限の作業はこれでおわりです。しかし「bridge.js」の「oscServer.kill();」と「oscClient.kill();」でエラーが出ていたので、少しだけ修正します。「RUN」して起動しただけではエラーは起きませんが、実際にp5jsとSonicPiで通信確認するときに発生しました。エラーを確認するためには「main.js」を編集し、デバッグツールを有効にしてます。

```main.js
  // Open the DevTools.
  mainWindow.webContents.openDevTools()
```
![スクリーンショット 2020-10-28 16.39.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/733e661c-93c2-aa4e-35b6-336c7122f65f.png)


## 動作を確認する
![スクリーンショット 2020-10-28 16.49.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/d5eec75c-23c0-6a1a-ca7f-5e0044e96672.png)
デバッグツールを有効にして「RUN」した直後の画面です。この「RUN」したままの状態で、SonicPiを起動し、さらにブラウザで<a href="https://www.openprocessing.org/sketch/992900">サンプルのp5js</a>も同時に実行します。3つを同時に実行している状態ですね。混乱しないように注意しましょう。

![スクリーンショット 2020-10-28 17.07.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/a16cec7b-3d4f-4518-9205-1f44245d4e01.png)

OpenProcessingの画面で、マウスを動かすと円がでてきます。クリックするとSonicPiにOSCメッセージを送るので、何度かクリックしてみて、SonicPiの画面を確認してみてください。

![スクリーンショット 2020-10-28 17.08.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/76aa6deb-963b-a538-1d86-aa9eb161d469.png)
[Cue」ログに「/osc:127.0.0.1:XXXXX/fromP5 [72]」のように表示されていれば、正常に動作しています（XXXXXは毎回変わる数値）。「/fromP5」と「72」は、OpenProcessingのp5jsから送っています。
![スクリーンショット 2020-10-28 17.08.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/18e8e78b-a4b7-c902-c4ae-f59006f73c46.png)
もしうまくいかないときは、SonicPiの「prefs」の「入出力」で「OSCサーバーを有効にする」のチェックを確認してください。

うまくいっているように見えますが、何度かOpenProcessingを実行し直したりすると、Electronのアプリにエラーがでてきました。
![スクリーンショット 2020-10-28 17.08.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/7749a753-d528-bfc5-5929-fa7d4891244e.png)
「×」マークをクリックするとエラー情報をみることができます。「△」マークの警告は常に出るようですので、気にしなくてよさそうです。
![スクリーンショット 2020-10-28 16.46.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/426e2f16-3e2b-372d-3a0d-538857060b20.png)
「oscServer.kill is not a function」とメッセージがでています。いろいろやってると「oscClient.kill is not a function」も出てきました。どうやら「kill」というファンクションはないのでエラーになっているようです。


## エラーの修正
![スクリーンショット 2020-10-28 17.22.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/3122be91-0b77-2d52-6754-d1fffe23c829.png)
Electron Fiddleの「renderer.js」に戻り、killを使わないように書き換えました（JavaScriptは詳しくないので、これでいいのかな？？。自信がないです。よい方法があれば教えてください）。

再び「RUN」して、動作確認してみてください。


## SonicPiから音を出す
SonicPiでOSCメッセージを受信したら音を鳴らしたい場合は、以下のようなプログラムをSonicPiで「run」します。「run」したまま待機するイメージです。音がしないときは「stop」して、待機していない状態かもしれませんので再びSonicPiで「run」しましょう。

※ SonicPi v3.2.2のプログラムです（v3.1では動かないです）。

```
live_loop :loop_name do
  
  use_real_time
  
  n = sync "/osc*/fromP5"
  
  play n
  
end
```

## 整える
次は「renderer.js」にクレジットをいれたり、「HelloWorld」を消したり、ウィンドウサイズとか色などを整えていきます。画面の色はCSSで書いてもいいと思いますが、せっかくなのでp5jsで書いてみました。お好みでどうぞ。

![スクリーンショット 2020-10-28 18.34.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/3416e87e-9aef-f96c-9a92-5c93c1e10d49.png)
ウィンドウサイズとキャンバス位置は「main.js」と「styles.css」で、ウィドウタイトルは「index.html」で、画面の色は「renderer.js」の「p5js」で設定しました。


```renderer.js
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.

// --------------------------------------------------------------- //
// This script is from the "bridge.js" and modified by mathrax -s.
// original : https://github.com/genekogan/p5js-osc
// --------------------------------------------------------------- //
var osc = require('node-osc');
var io = require('socket.io')(8081);


var oscServer, oscClient;

var isConnected = false;

io.sockets.on('connection', function (socket) {
	console.log('connection');
	socket.on("config", function (obj) {
		isConnected = true;
    	oscServer = new osc.Server(obj.server.port, obj.server.host);
	    oscClient = new osc.Client(obj.client.host, obj.client.port);
	    oscClient.send('/status', socket.sessionId + ' connected');
		oscServer.on('message', function(msg, rinfo) {
			socket.emit("message", msg);
		});
		socket.emit("connected", 1);
	});
 	socket.on("message", function (obj) {
		oscClient.send.apply(oscClient, obj);
  	});
	socket.on('disconnect', function(){
		if (isConnected) {
			// oscServer.kill();
			// oscClient.kill();
			oscServer = null;
			oscClient = null;
		}
  	});
});
// end of "brigde.js" script.
// --------------------------------------------------------------- //


// The script from here written by mathrax-s
// --------------------------------------------------------------- //
const p5 = require('p5');

const sketch = function (p) {
  // setup
  p.setup = () => {
    canvas = p.createCanvas(p.windowWidth, p.windowHeight);
  }

  // draw
  p.draw = () => {
    p.background(50,100,100);
    p.fill(255);
    p.textAlign(p.CENTER);
    p.text("OSC Bridge", p.windowWidth / 2, p.windowHeight / 2);
  }
};
const app = new p5(sketch);
// end of mathrax-s's script.
// --------------------------------------------------------------- //


```

```main.js
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')

function createWindow () {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 300,
    height: 300,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', function () {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
```

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>OSC Bridge</title>
    <link rel="stylesheet" type="text/css" href="./styles.css">
  </head>
  <body>
    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>
```

```styles.css
body{
    padding: 0;
    margin: 0;
}

canvas{
    vertical-align: top;
}
```

## デスクトップアプリに書き出す
![スクリーンショット 2020-10-28 18.51.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/e00d0b59-f471-05e7-e2e0-ddefffca4897.png)

![スクリーンショット 2020-10-28 18.50.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668579/9f0cc6b1-d252-00d3-8529-3636c3503d2e.png)
Electron Fiddleのメニューから「Tasks」>「Package Fiddle...」を選ぶと、書き出されます。