LoginSignup
5
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-28

はじめに

前回、p5jsとElectron Fiddleでデスクトップアプリをつくりました。そもそものきっかけは、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

「bridge.js」を入手

スクリーンショット 2020-10-28 16.01.51.png
https://github.com/genekogan/p5js-osc
こちらから「p5js-osc」をダウンロードすると、その中に「bridge.js」があります。

「renderer.js」にコピー&ペースト

スクリーンショット 2020-10-28 16.32.47.png
「bridge.js」をテキストエディタで開き、Electron Fiddleの「renderer.js」にコピー&ペーストします。

「RUN」ボタンで実行

スクリーンショット 2020-10-28 16.36.39.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

動作を確認する

スクリーンショット 2020-10-28 16.49.42.png
デバッグツールを有効にして「RUN」した直後の画面です。この「RUN」したままの状態で、SonicPiを起動し、さらにブラウザでサンプルのp5jsも同時に実行します。3つを同時に実行している状態ですね。混乱しないように注意しましょう。

スクリーンショット 2020-10-28 17.07.45.png

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

スクリーンショット 2020-10-28 17.08.04.png
[Cue」ログに「/osc:127.0.0.1:XXXXX/fromP5 [72]」のように表示されていれば、正常に動作しています(XXXXXは毎回変わる数値)。「/fromP5」と「72」は、OpenProcessingのp5jsから送っています。
スクリーンショット 2020-10-28 17.08.59.png
もしうまくいかないときは、SonicPiの「prefs」の「入出力」で「OSCサーバーを有効にする」のチェックを確認してください。

うまくいっているように見えますが、何度かOpenProcessingを実行し直したりすると、Electronのアプリにエラーがでてきました。
スクリーンショット 2020-10-28 17.08.19.png
「×」マークをクリックするとエラー情報をみることができます。「△」マークの警告は常に出るようですので、気にしなくてよさそうです。
スクリーンショット 2020-10-28 16.46.18.png
「oscServer.kill is not a function」とメッセージがでています。いろいろやってると「oscClient.kill is not a function」も出てきました。どうやら「kill」というファンクションはないのでエラーになっているようです。

エラーの修正

スクリーンショット 2020-10-28 17.22.20.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
ウィンドウサイズとキャンバス位置は「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

スクリーンショット 2020-10-28 18.50.31.png
Electron Fiddleのメニューから「Tasks」>「Package Fiddle...」を選ぶと、書き出されます。

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