C++を1行も書かずにRustとWeb GUIで商用オーディオプラグインを構築する
こんにちは。siy1121です。
先日のADC Japan 2026において「C++を1行も書かずにRustとWeb GUIで商用オーディオプラグインを構築する」というテーマで登壇しました[1]。
発表内では、WRAC Stack (WebView + Rust Audio + CLAP) という概念を提唱し、NovoNotesでこのWRAC Stackを採用した理由、実用化するにあたって必要だった作業などを解説しました。
この記事は、その発表を文章形式にリライト・追記したものです。
オリジナルのスライドはこちらからご覧いただけます。
本記事の要約
- オーディオプラグイン開発では一般に C++ / CMake / JUCE という技術スタックが支持されている
- NovoNotesも上記スタックを採用していたが、我々のチーム体制と上記スタックの相性が悪く、大きな負担となっていた
- NovoNotesチームの体制に適した新しい技術スタックを模索した結果、WebView, Rust, CLAP を活用した技術スタックに落ち着いた
- 同じ課題を感じている開発者の参考になると考え、WRAC Stack として公開した
本記事を読むのに必要な前提知識
本記事は、NovoNotesのプラグイン開発の事例として、WRAC Stackを紹介するものです。
何かしらの方法でオーディオプラグインを開発した経験があるとスムーズに読み進めることができます。
WRAC Stack
本記事では、近年NovoNotesがオーディオプラグイン開発に採用している技術スタックである WRAC Stack を紹介します。
名前の由来は、WebView + Rust Audio + CLAP から来ており、既に15,000人以上のユーザー様にWRAC Stack製プラグインを利用いただいています。

WRAC Stackでは、DSPやプラグイン本体をRustで実装し、GUIをWebView上のWeb技術で構築します。更にCLAPをVST3やAUなどへ展開するための中間表現として扱う点が特徴です。
プラグイン開発の業界標準
WRAC Stackの詳細に入る前に、オーディオプラグイン開発における最も一般的な手法について確認します。
商用オーディオプラグイン開発にあたっては、以下の要件が発生します。
- リアルタイム要件
- クロスプラットフォーム対応
- 様々なプラグインフォーマットへの対応
これらの要件を満たす最も一般的な選択肢となっているのは
- C++
- CMake
- JUCE
の組み合わせです。
なぜNovoNotesは別の方法を模索したか
我々のチーム体制と、C++ / CMake / JUCE スタックの相性が悪く、大きな負担となっていたのが理由です。
説明にあたって、NovoNotesチームの特徴を知っていただく必要があります。
- 3~5人程度の小さなチームである
- 他に本業を持っているパートタイムエンジニアが多い
- メンバーの経験も様々であり、得意とする技術領域が異なる
C++との相性
C++でメモリ安全性やスレッド安全性をチームとして担保するには、ローレベルなメモリ管理等について一定水準以上の理解をチーム全体で共有する必要がありました。
我々のチームは専門性が異なるメンバーで構成されていることもあり、統一された品質で安全なコードを実装・保守するコストがかかっていました。
CMakeとの相性
CMakeは高機能ではありますが、相応のセットアップコスト、メンテナンスコストがかかります。
これは小規模でパートタイムエンジニアを擁するNovoNotesチームにとって、実際に大きな負担となっていました。
JUCEとの相性
JUCEは素晴らしいフレームワークですが、特にライセンス面でNovoNotesと相性が悪い部分がありました。
- ほとんどがパートタイムエンジニアで構成されるNovoNotesチームにとって
「1エンジニア1ライセンス制」がどうしても割高になってしまう - 我々のような小規模事業者にとって、ビジネスレベルでロックインされることが
固定コスト・将来のリスクの観点から許容が難しかった
これらの理由から、NovoNotesはC++ / CMake / JUCE 以外の別の選択肢を模索することになりました。
Rustという選択肢
代替案を模索するにあたって、まずはC++の代わりとなるプログラミング言語を考えます。
ここでは、C++の代替としてよく名前が挙がるRustを採用できるかどうか考えてみましょう。
商用プラグイン開発には以下の要件が必要でした。
- リアルタイム要件
- クロスプラットフォーム対応
- 様々なプラグインフォーマットへの対応
Rustはこれらの要件のうち、リアルタイム要件、クロスプラットフォーム対応を満たすことが可能だと考えられます。
次に、C++ / CMakeで発生していた問題が解消されているのかを確認します。
- 専門性が異なるメンバーで構成されているため、C++で統一された品質で安全なコードを実装・保守するコストが高かった
- → 所有権・借用・型システムによって、C++で発生しがちなメモリ安全性やデータ競合に関する多くの問題をコンパイル時に検出できるため、C++より安全なコードを書くコストが少なくて済む
- 小規模でパートタイムエンジニアを擁するNovoNotesチームにとって、CMakeの運用コストが負担だった
- → ビルドシステムとパッケージマネージャとして簡単に扱えるcargoが存在している
このように、C++ / CMakeで発生していた多くの問題が解消していることが分かります。
このことから、後はRustで様々なプラグインフォーマットに対応できれば、代替案として機能すると考えられます。
Rustを用いたプラグイン開発は可能か?
オーディオプラグイン開発で鬼門となるのが、複数のプラグインフォーマットに対応する必要がある点です。
VST / Audio Units / AAX など、OSやDAWによって異なるフォーマットを採用していることから、これらのフォーマットに対応しなくてはなりません。
そういった意味で、一つのコードベースで全てのフォーマットに対応できるJUCEは非常に重宝されてきました。しかし、今回はJUCEを使わないという選択をしました。
1つのコードベースで複数のフォーマットに対応させるためには
- プラグインフォーマットはC / C++ / Objective-C で触ることが前提のため、Rustバインディングが必要
- 1つのコードベースを実現するために抽象化レイヤーが必要
と、我々のような小さなチームでやり切るには難しい膨大な作業が必要になります。
この問題を解決するために、我々は「CLAP」というプラグインフォーマットを活用することにしました。[2]
CLAPとは
CLAPとは、u-he, Bitwig が開発した新しいプラグインフォーマットであり、VSTやAU、AAXと同じ技術レイヤーに属します。
特徴として以下が挙げられます。
- C言語で API が定義されていてポータビリティが高い
- コアの仕様がシンプル/軽量。拡張を組み合わせて機能を提供する思想
- オープンソースでライセンスの観点からも使いやすい
CLAP-First Development

様々なプラグインフォーマットに対応するために、同じ技術レイヤーに属するCLAPを活用するというのは不思議に聞こえるかもしれません。
今回、我々はCLAPをエンドユーザーに配布するためのフォーマットとしてではなく、様々なフォーマットに対応させるための中間表現として活用します。
具体的には以下のような工程で開発を行います
- CLAPの仕様に則ってプラグインを開発する
- 別のフォーマットに変換する
clap-sys
まずはclap-sysを使用してRustでCLAPフォーマットに則ったオーディオプラグインを開発します。
clap-sysはCLAP APIに対するRustの低レイヤーなbindingであり、Rust のコンパイラが保証する安全性のもとでコードを書けるようにするには自前でアダプターレイヤーを用意する必要がありますが、clap-wrapperを活用する前提においては都合が良かったのでこちらを採用しました。[3]
clap-wrapper
CLAPフォーマットで実装されたプラグインは、VST3 / AU等の他の配布するプラグインフォーマットに変換する必要があります。
clap-wrapperはCLAPをVST3 / AU / AAX 等で使えるようにするラッパーです。
clap-wrapperは、CLAPプラグインを動的ライブラリとしてラップすることも可能ですが、静的ライブラリとしてビルドしたCLAPのコードをラップすることも可能で、実行時に発生しがちな動的ライブラリ周辺の問題を回避することもできます。
コード例
Rust + CLAP でDSP処理を書く際の雰囲気をお伝えするためにコード例を示します。
基本的に MyPluginProcessor 等の任意のstructに Processor traitを実装することで、DSP処理本体であるprocess(&mut self, context: ProcessContext<'_>)が呼び出されるようになります。
ProcessContext引数には処理対象となるオーディオバッファやMIDIイベント、オートメーションパラメータ等が含まれ、これをもとにDSP処理を行います。
impl Processor for MyPluginProcessor {
fn process(&mut self, context: ProcessContext<'_>)
-> PluginResult<ProcessStatus>
{
// Write your dsp here!
// context contains the audio buffer and
// the input events for this block.
Ok(ProcessStatus::ContinueIfNotQuiet)
}
}
オーディオプラグインにおけるGUI開発
プラグイン開発のもう一つの大きなテーマはGUIです。
プラグイン自体はDSPに関するパラメータをホストに公開しているので、プラグイン独自のGUIの実装は必須ではありませんが、より良いUXを提供するために多くのプラグインが独自のGUIを提供しています。
JUCEであればGUIの機能も含まれますが、今回はJUCEを使わない選択をしているので自身でGUIを実装する方法を考える必要があります。
RustでGUIを実装する上での選択肢
まず思い浮かぶのはRust NativeなGUIライブラリ(egui, iced)などです。
しかし、これらのライブラリを採用するにはいくつかの面で不確実性がありました。
- プラグインGUIは、一般的なスタンドアロンのGUIでは発生しないOSやDAWの相性による問題が発生しうる
- 我々が必要とする機能・表現力を十分にサポートしているかがすぐには分からない
一方、Rust NativeなGUIライブラリ以外で思い浮かぶのがWebViewです。
WebViewは不確実性という観点で見ると優秀であることが分かります。
- JUCE8を始めとした多くの採用事例により、プラグインGUI環境でも動作することが分かっている
- Web技術を採用したUIはかなり多く、我々が必要とする機能・表現力を十分にサポートしている確信がある
ただし、WebViewはリソースを多く消費する傾向にあります。これを踏まえてオーディオプラグインのGUIとして適しているか、という議論は当然あります。
そのうえで、我々は以下の理由でWebViewを採用可能であると判断しました。
- WebViewはレンダリング処理がホスト側のオーディオ処理スレッドとは独立して動作するため、DSPのリアルタイム処理とGUI描画を明確に分離して設計できる。そのうえで、WebViewとの通信頻度やメインスレッド上で行う処理を適切に制御すれば、多少性能に影響しても実用上問題ない水準に抑えられると判断した
- WebViewの主なリソース消費はGUI表示中に発生する。プラグインのGUIウィンドウを同時に大量表示するという使い方をしない限り、リソースの消費は限定的である。我々のプロダクトではこのような使われ方は想定していないため問題ないと判断した
- NovoNotesが開発するプラグインの性質上、3D表現等を活用したリッチなGUIの実装が必要になるケースが多い。WebViewを採用することで得られる開発効率によってリソース消費をある程度正当化できると判断した
WebView + Rust + AudioPlugin を実現する上で必要なピース
WebView + Rust + AudioPlugin を実現する上で必要な要件を整理しましょう。
1. ホストから指定された場所にWebViewを配置する
まず、プラグインホストから指定されるWindowにWebViewを配置する必要があります。
Window参照の仕組みやWebViewの実装はOSにより異なるため、サポートしたいOS全てに対応できる仕組みが必要になります。
2. WebViewからRust側へメッセージを送信する
プラグインのUIとして使用する以上、WebView側にDSPの制御パラメータを操作するためのノブやフェーダーを配置することになります。もう少し一般化すると「WebViewから任意のメッセージをRust側に送信できる必要がある」と言えます。
3. RustからWebViewへメッセージを送信する
DSPに関わる情報をWebView側にリアルタイムで表示したいことがあります。例えばコンプレッサーであれば、入出力のゲインやゲインリダクションといった値をリアルタイムに表示することが該当します。これも一般化すると「Rust側からWebViewへメッセージを送信できる必要がある」と言えます。
これらの要件を満たすために我々は今回2つのcrateを実装しました。
プラグイン内でWebViewを扱う wxp crate
wxpは、Rustオーディオプラグイン環境でWebViewを扱うためのcrateで、win/mac/linuxに対応しています。
wxpは「wry」という既存のWebView crateをオーディオプラグイン向けにラップし、機能を追加したものです。
wryはRustとWebViewを使ってアプリケーションを開発するフレームワークでおなじみのTauri内部で使用されているcrateで、同じチームが開発を行っています。
wxpはwryに以下の機能を追加したものです。
- wryが想定していないオーディオプラグイン独自のウィンドウライフサイクルに対応する追加コードが含まれる
- wryには含まれていない高レベルなWebView↔Rust間コミュニケーションAPIを実装している
- 同じくwryを使用しているTauriのAPIを参考にしている
コード例
WebView初期化の例
use std::rc::Rc;
use wxp::{WebContext, WxpCommandHandler, WxpWebViewBuilder};
let mut web_context =
WebContext::new(std::env::temp_dir().join("my-plugin"));
let handler = Rc::new(WxpCommandHandler::new());
// `webview` must be kept alive while the UI is shown.
let webview = WxpWebViewBuilder::new(&mut web_context)
.with_command_handler(handler)
.with_serve_zip("wxp-plugin", FRONTEND_ZIP)
.build_as_child(&window)?;
ホスト管理下のメインスレッドに処理を投入するための run_loop crate
run_loop crateは、オーディオプラグインGUI環境において、任意の処理をメインスレッド上で動作させるためのcrateです。
オーディオプラグイン開発に限らず、GUIを扱うアプリケーションでは、しばしば特定の処理をメインスレッド(≒UIスレッド)で動かしたいことがあります。これは「UIに関連する処理(書き換えや更新等)はメインスレッド上で行う」という制約があるからで、基本的にUIフレームワークの機能として特定の処理をメインスレッドで動作させるAPIが提供されています。
一方、今回のようなオーディオプラグインの場合は一部事情が異なります。
まず、UIに関連する処理以外(ホストアプリケーションとのやり取り等)でもメインスレッドで動作させたい処理が発生する場合があること。
そして最も大きな違いとして、メインスレッドをホスト側が管理しており、プラグイン側からホストが管理するメインスレッドに処理を割り込ませるのにひと手間必要な点が挙げられます。
よって、オーディオプラグイン開発においては、メインスレッドに任意の処理を割り込ませる機構を自前で用意する必要があり、そのためにrun_loop crateが開発された、という経緯があります。
(JUCEではjuce::MessageManagerがこれに相当します)
WebView ↔ Rustのコミュニケーションを始め、WebViewに関する操作はメインスレッド上で行う必要があるため、wxp crateを活用するためにはrun_loopが必須という構図があります。
例えば、リアルタイムオーディオのデータをWebViewに定期的に投げる際などには、run_loopを使用して定期的にメインスレッド上でWebViewにipcをトリガーすることになります。
コード例
10秒後にメインスレッドで任意の処理を行う例
use std::time::Duration;
let run_loop = RunLoop::current();
// Schedule execution after 10 seconds
let handle = run_loop.schedule(Duration::from_secs(10), || {
println!("10 seconds have passed");
});
WRAC Stackにおける開発者体験
ここまでで、WRAC Stackが出来上がった経緯と、その要素技術について説明してきました。
最後にC++/CMake/JUCEと比較した、WRAC Stackにおける開発者体験についても紹介します。
WRAC Stackは、プラグイン側にRust、GUI側にWeb技術と別々の技術を採用するため、それぞれの領域で開発者体験に特徴があります。
Rust
Rustでは、所有権・借用・型システムによって、C++で発生しがちなメモリ安全性やデータ競合に関する多くの問題をコンパイル時に検出できます。もちろんFFIやリアルタイムオーディオ特有の制約にはC++と同様引き続き注意が必要ですが、例えば assert_no_alloc crateを活用すればリアルタイムスレッドで意図しないメモリ確保が発生した場合でもランタイム時に検知できるなど、安全性をより高める工夫は可能です。
さらにビルドシステム・パッケージマネージャとしてcargoが使用できるため、CMakeと比較して特にライブラリの管理が容易です。
WebView
Webの開発エコシステムの恩恵をフルに受けることができます。
例えば、C++ではUIの調整を行うとリビルドが必要ですが、Webではホットリロードが容易に実現できるため、プラグインを立ち上げたままUIの実装・調整が可能です。
また、ブラウザ内蔵の開発者ツールも利用可能なため、UIのデバッグやJavaScriptのデバッグも容易です。
WRAC Stackが引き受けたコストと制約
C++ / CMake / JUCE の技術スタックを選ばなかったことで受けずに済んだコスト・リスクがある一方、選ばなかったことで別のコスト・制約を引き受けたことにも言及しておきます。
もちろん我々のチームでは総合的に勘案してWRAC Stackのメリットの方が大きいと判断していますが、WRAC Stackを検討されている方の判断の助けになればと思い記載します。
技術としての成熟度
WRAC Stack及びコアとなるCLAPを中間表現として活用する技術は、商用オーディオプラグイン開発で採用可能な一定の水準に達していると考えますが、JUCEと比べるとエコシステムが小さいです。例えば、clap-wrapperにおけるAAX, AUv3サポートは2026年7月に開始されたばかりのため、事業上AAXが必須な場合はWRAC Stackの採用には検証が必要でしょう。
また、DSPを始めとして、JUCEにはプラグイン開発に活用できる様々な資産が存在しますが、Rustではそれらすべてが既存のcrateでカバーされている保証はないため、必要があれば自前で実装する必要があります。
メンテナンスコスト
JUCEを採用しないことで受けずに済んだコスト・リスクがある一方、
- clap-sys利用に対して必要なアダプタの実装・保守
- CLAP + WebViewの繋ぎこみにあたって開発した2つのcrateの保守
など、自前でメンテナンスをする必要があるコードが増えたのも確かです。
NovoNotesとしては、自身でコントロールできないJUCEライセンスのリスクを、「自前でメンテナンスするコードが増える」という比較的自身でコントロールしやすいリスクに付け替えたという認識であり、保守するコードが多少増えてもメリットが大きいと判断しています。
OSによってWebViewの挙動が異なる部分がある
WebViewはOSが提供するものを使用しているため、CEFのようにバイナリサイズが膨らむ心配はない一方、OSごとにブラウザエンジンが異なるためそのまま挙動の変化として現れる場合があります。特にmacOSのWKWebView(WebKit)では注意が必要です。
Demo
登壇時には、WRAC Stackを使った開発の様子を実際に触りながらお伝えしました。
同等の内容の録画データを公開しましたので以下からご覧いただけます。
- cargoにより、新しいパッケージの追加が容易であることを示すデモ
- リッチな3DスペクトログラムアナライザーのUIを、調整・デバッグするデモ
- MIDI入力を活用してシンセサイザーを動作させるデモ
まとめ
「WRAC Stack」はNovoNotesのチーム特性に合わせた技術スタックを模索した結果生まれました。
- C++の代わりにRustを採用した
- 我々のチームは専門領域が異なるメンバーで構成されており、チーム全体で安全なC++コードを運用する上で課題があった
- Rustは、C++で発生しがちなメモリ安全性やデータ競合に関する多くの問題をコンパイル時に検出できるため、よりチームにフィットしていた
- CMakeより運用コストが少ないcargoを採用できたこともメリットだった
- GUIにWeb技術を採用した
- Rust NativeなGUIライブラリを使う選択肢もあったが、実績の観点からWebViewを採用した
- NovoNotesでは空間オーディオを始めリッチな GUI を必要とするシーンが多いため、膨大なWeb資産やエコシステムが活用できる点でも評価が高い
- JUCEの代わりにCLAPを中心に据えた
- 特にパートタイムエンジニアが多い我々のチームでは、ライセンス面で相性が悪かった
- 小規模事業者でビジネスレベルでロックインされることはリスクだった
- CLAP-firstな開発とすることで、コアの依存がPermissive Licenseとなり、リスクを回避することができた[4]
WRAC StackがNovoNotesと同じような課題を持っているチームの新たな選択肢となれば幸いです。
WRAC Stackに興味を持ってくださった方へ
NovoNotesはWRAC Stackに関するレポジトリをGitHubに公開しています。
WRAC Stackは商用開発で採用できる程度には成熟してきましたが、まだ改善・成長の余地が大きくある技術スタックだと考えています。ご意見、ご感想お待ちしております。
Example
WRAC Stackでどのようなプラグインを開発できるのか気になった方は、こちらのExampleをご覧ください
Template
WRAC Stackで実際にプラグインを作ってみたい方はこちらからお試しください
各種Crate
WRAC Stackを実現するにあたって、独自に実装する必要があったcrateはこちらにまとまっています
おまけ:LLM時代におけるWRAC Stack
WRAC Stackへの移行を決めた時点では意図していなかったことですが、結果的にWRAC StackはLLMを活用した開発と相性が良い技術スタックになったと考えています。本記事で挙げたNovoNotesチームにおける課題に対して「LLMを活用すれば C++ / CMake / JUCE のままでも良かったのではないか」という仮説も考えられますが、この観点についても我々の考えを述べておきます。
LLMを活用した開発は、「生成し、フィードバックを得て、修正する」というループとして表現できると考えます。コードの生成自体が高速化された結果、開発全体の生産性は、生成されたものに対してどれだけ短い時間で有用なフィードバックを返せるかに依存します。
そしてフィードバックの担い手には二種類あります。
コンパイラやテストのように機械が返せるものと、操作感や手触りのような人間の判断が必要なものです。機械で静的に検査・フィードバックできる領域を増やし、人間にしか判断できない部分に人間の時間を集中させられれば、より良い開発サイクルが実現できます。
この観点でWRAC Stackの各要素を見直すと、それぞれがLLMとの相性の良さにつながっていることが分かります。
Rust
前述の通り、Rustはメモリ安全性やデータ競合に関する多くの誤りをコンパイル時に検出できます。これはLLMが生成したコードにも同様に働き、この種の誤りは生成ループの内側でコンパイルエラーとして検出できます。
オーディオプラグインは、DAWごとのスレッドモデルやウィンドウライフサイクルの差異など、手元で再現しにくい条件で不具合が発現しやすい領域です。
誤りを実行して初めて発見するのではなく、実行前に機械的なフィードバックとして受け取れることは効率的なLLM開発において有利になります。
WebGUI
UI開発は操作感や手触りという依然として人間の判断が必要な部分が存在する領域です。
WRAC Stackではホットリロードにより、プラグインを立ち上げたままコードの変更を反映できるため、LLMの変更に対してすぐに人間がフィードバックを返すことが可能になります。
これは人間のフィードバックを得るために毎回リビルドする必要がある環境と比較して大きなアドバンテージです。
以上を踏まえ「LLMを活用すれば C++ / CMake / JUCE のままでも良かったのではないか」という仮説に対して、我々は2つの理由からWRAC Stackに乗り換えた価値があると判断しています。
1つ目は、そもそもLLMではライセンス問題は解決できないということ。
2つ目は「メンバーの専門領域の多様性」から来るコーディングに関する局所的な問題をLLMで対症療法的に解決しようとするよりは、LLMを活用した開発サイクルそのものを前提に最適化するアプローチが取れたほうがより本質的であり、WRAC Stackはそのアプローチと相性が良いことが挙げられます。
結果論的ではありますが、WRAC Stackと既存スタックとの比較の一助となれば幸いです。
登壇者・NovoNotesの紹介
Kosuke Yuasa
- NovoNotesの立ち上げからDSP開発を5年間にわたりリードする
- wxp, run_loop 共同メンテナー
- 前職のクリプトン・フューチャー・メディアではC++プログラマとして「初音ミク」等のオーディオアプリケーション開発に従事
- 現在は株式会社LabBaseにてRustやTypeScriptを用いたWebサービス開発を行う
- 『C++ポケットリファレンス』(技術評論社) 共著
Sota Ichikawa
- NovoNotesのプラグイン開発に約4年にわたり参画
- wxp, run_loop 共同メンテナー
- 現在は株式会社ディー・エヌ・エー(DeNA)に所属し、深層学習を活用したマルチメディア分野の研究開発を行う
- 学生時代よりWeb領域で幅広く開発経験を積み、大学院では音源分離をはじめとする音響信号処理の研究に従事した
NovoNotes
- 鈴木理が2020年に立ち上げたオーディオプラグインベンダー
- 現在8つのプロダクトを提供しており、約3万人のユーザーを擁する
- 空間オーディオ系を強みとしている

-
「C++を1行も書かず」にとは、「自分たちが書く・保守するコードにC++が含まれない」という意味であり、バイナリの生成に一切のC++コードによる成果物を使っていないわけではない。C++を使わないという技術的な挑戦をしているわけではなく、自分たちが保守するコードをRustで統一したい、という要求からくるものである。 ↩︎
-
「nih-plug」はAU未サポートのため採用せず ↩︎
-
高レベルなラッパーとして「clack」が知られるがclap-wrapperとの相性の観点から採用しなかった。clackはclapのスレッドモデルを型で厳密に表現するが、clap-wrapperはclapのスレッドモデルを厳密に再現できないため、実態と乖離する部分が出たため。 ↩︎
-
より正確には、自身でコントロールできないJUCEライセンスのリスクを、「自前でメンテナンスするコードが増える」という比較的自身でコントロールしやすいリスクに付け替えた、と表現できる ↩︎
オーディオプラグインブランド NovoNotes の開発チーム公式ブログです。 一緒に開発する仲間を募集しています! novo-notes.com/jobs
Discussion