JXSから外部.vert / .frag などのシェーダーを読み込む #jitter #n4m #maxmsp

Jitter & GLSLで遊ぶのを効率化したい

昨日に引き続きMax8のシェーダーで遊んでいるんですが、JXSはXMLの中にGLSLを書くような形でVisual Studio Code のコード補完やらフォーマッターなどで扱いにくかったので、JXSと.vert / .fragを分離して扱う方法を模索しました。

※この記事はMax中級者以上向きにざっくり書いてるので分かりやすくはないです。

JXSから外部シェーダー(.vert / .frag)は読める

Cycling74のforumなどを探してみるとJXSから外部シェーダー読めるよと書いてあります。具体的な書き方は見つけられなかったんですが多分こんな感じという書き方をしたらいけました。
  <program name="vp" type="vertex" source="default.vert" />
  <program name="fp" type="fragment" source="default.frag" />

これで.vert / .frag をMax Projectのotherフォルダなどパスが通ってる所に置いておけば読めます。

シェーダーを切り替えたい ⇨ JXSの動的生成(ご参考)

ではシェーダーを色々書いて遊ぼうとして .frag を量産したとしても、上記のJXS上のファイル名をいちいち変更するのはめんどくさいです。XMLに詳しくないのですが動的に変数を与えて書き換えるという事は出来なさそうな・・・?

という訳でJXS自体を動的に生成すれば良いと思いつき、こういう時に役立つ node.scriptオブジェクトことNode.js先生にお願いしました。MaxにNode.jsくっついて本当に良かった・・・

node.js / node for max のJXS生成サンプルコード

const fs = require('fs');
const MaxAPI = require('max-api');

const generateJXS = (vertexShaderFilename, fragmentShaderFilename, outputJxsFilename) => {
  const jxsTemplate = `
  <jittershader name="default">
    <description>Default Slab </description>
    <param name="scale" type="float" default="1.0" />
    <param name="tex0" type="int" default="0" />
    <param name="modelViewProjectionMatrix" type="mat4" state="MODELVIEW_PROJECTION_MATRIX" />
    <param name="textureMatrix0" type="mat4" state="TEXTURE0_MATRIX" />
    <param name="position" type="vec3" state="POSITION" />
    <param name="texcoord" type="vec2" state="TEXCOORD" />
    <param name="u_resolution" type="vec2" state="TEXDIM0"/>
    <param name="color" type="vec4" state="COLOR" />
    <param name="u_time" type="float"/>
    <language name="glsl" version="1.5">
      <bind param="scale" program="fp" />
      <bind param="tex0" program="fp" />
      <bind param="modelViewProjectionMatrix" program="vp" />
      <bind param="textureMatrix0" program="vp" />
      <bind param="position" program="vp" />
      <bind param="texcoord" program="vp" />
      <bind param="color" program="vp" />
      <bind param="u_resolution" program="fp"/>
      <bind param="u_time" program="fp"/>
      <program name="vp" type="vertex" source="${vertexShaderFilename}" />
      <program name="fp" type="fragment" source="${fragmentShaderFilename}" />
    </language>
  </jittershader>
  `;

  fs.writeFileSync(outputJxsFilename, jxsTemplate.trim());
  MaxAPI.post(`JXS file '${outputJxsFilename}' generated with '${vertexShaderFilename}' and '${fragmentShaderFilename}'.`);
  MaxAPI.outlet('read',outputJxsFilename);
};

MaxAPI.addHandler("generate_jxs", (vertexShaderFilename, fragmentShaderFilename, outputJxsFilename) => {
  generateJXS(vertexShaderFilename, fragmentShaderFilename, outputJxsFilename);
});

.vert / .frag などを引数で渡して generate_jxs 関数をMaxからコールすれば、アウトレットから今つくった.jxsファイルを出力します。後段に jit.gl.slab を接続しておけば自動的にシェーダーが切り替わるという訳です。

folderオブジェクトでVSCodeで書いた.frag名を取得し umenuにセット、umenuからの出力を利用したりすればシェーダー切り替えを簡略化する事は難しくないでしょう。ただしparam など独自変数をjit.gl.slabへ伝えたい場合はparam / bind / uniform などの書き換えが必要ですので、こんな方法もあるんかなという程度あくまで JXS / node.scriptのサンプルとしてご利用ください。上手くつくればShaderToyのコードなんかも動くと思います。

default.vert(ご参考)

#version 410

in vec3 position;
in vec2 texcoord;
in vec4 color;

out jit_PerVertex {
    vec2 texcoord;
    vec4 color;
} jit_out;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 textureMatrix0;


void main(void) {
    gl_Position = modelViewProjectionMatrix*vec4(position, 1);
    jit_out.texcoord = vec2(textureMatrix0*vec4(texcoord, 0., 1.));
    jit_out.color = color;
}

リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング のサンプルコードで試してみた

ちょいちょい勉強しているこちらの書籍 ⇨ リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング の.frag のサンプルコードを利用して切り替えテストしてみました。

無事に.fragをさくさく切り替えるという事ができました。

(余談ですが)#version 330 core のサンプルを #version 410に置換して利用したのですが、他は何も変更しないで動きました。GLSL3以上は後方互換性が担保されているのでしょうか?

Happy Patching!

Node for Max 上で tonal ライブラリを利用して Key Scaler を試作 #max8 #n4m

Midi NoteのPitchを特定のスケールに矯正

cycling’74のN4Mサンプルにも入っていた tonal をnode objectで使ってみたのでメモです。

鍵盤の音を特定のスケール(とりあえずC Major)に矯正するスクリプトとなっております。Node for Maxのご参考にどうぞ。

tonal.js

引用 “tonal is a music theory library. Contains functions to manipulate tonal elements of music (note, intervals, chords, scales, modes, keys). It deals with abstractions (not actual music or sound).

tonal is implemented in Typescript and published as a collection of Javascript npm packages.

It uses a functional programing style: all functions are pure, there is no data mutation, and entities are represented by data structures instead of objects.”

https://github.com/tonaljs/tonal ※導入方法はこちら参照

keyscaler.js


const MAX_API = require("max-api");
const { Scale } = require("@tonaljs/tonal");
const { Key } = require("@tonaljs/tonal");
const { Mode } = require("@tonaljs/tonal");
const { Midi } = require("@tonaljs/tonal");
const { Note } = require("@tonaljs/tonal");
const { Chord } = require("@tonaljs/tonal");

const notes = Scale.get("C major").notes;
console.log("notes:"+notes);

let midiNotes = notes.map((value) => {
    return Note.midi(value+"0")
});
console.log("midiNotes:"+midiNotes);

const handlers = {
    "getScaledPitch" : (pitch) => {

        let returnPitch = pitch;

        while(
            midiNotes.every(value => {
                return ((value % 12) != (returnPitch % 12))
            })   
        ){
            returnPitch++;            
        };

        //MAX_API.post(pitch + "->" + returnPitch);

        MAX_API.outlet(returnPitch);  
    },
    /*
    [maxAPI.MESSAGE_TYPES.BANG] : () =>{
        console.log("bang");
    },
    */
};

MAX_API.addHandlers(handlers);

Node for Max を用いて、定形外のUDPデータを受信 #Max8 #n4m

Maxの生命線UDPで問題が・・・

他のアプリを繋いでくれる updreceive

センシングや3D(Unity / UE / Arduino) 等とのコミュニケーション時に重宝するudprecieve ですが、udpを受信する場合に、メッセージが4bytesに揃っていなかったり長すぎた場合にアトリビュートなどの設定やOSC-routeを利用しても上手く受信できない事があります。

OSC Bad message name string: DataAfterAlignedString: Unreasonably long string Dropping entire message. / OSC packet size not a multiple of 4 byte dropping.

Max内にどうにかする方法としてNode for Maxが利用できます。

Node for Max で UDP receive

node object は、とりあえずHelpからコピペでOK

node.script objectjs をHelpからコピペし、ダブルクリックするとテキストエディターが開くので書き換えます。Maxのテキストエディタはクソなので、VS Code使ってます。

udpreceive.js

const dgram = require('dgram');
const IP = '192.168.1.1';
const PORT = 8888;
const socket = dgram.createSocket('udp4');
socket.on('listening', () => {
    const address = socket.address();
    console.log('UDP socket listening on ' + address.address + ":" + address.port);
});
socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);
	/*
		処理
	*/
});
socket.bind(PORT, IP);

とくに外部ライブラリを入れる事なくできました!JS的に正しい書き方なのかは謎。外部ライブラリのOSC-routeなどを使わなくても、テキスト処理はスクリプト言語の十八番でしょうから楽ちんです。

感想

昔、VJソフトのあのタグをつくるためにMaxのjs objectを触っていた頃は、jsの文法を読んで型なしでプロトタイプ継承とか難しいなと思い、深入りしませんでした。時は経ち最近のjsは今どきな文法が色々あって使いやすくなってそうです。

Node.jsやnpmエコシステムなどでは当たり前の事が、Maxおじさんからすると凄いみたいな事は非常に沢山ありそうです。(Blogを適当に放置してる事から分かるように)今までネットワーク / スクリプト言語は触れないように生きてきましたが、そろそろNode.jsから色々触ってみようかなと思いました。

Reference