FM音源チップチューンの音色を共有するサービスを作った
先日メガドライブやPC-88/98などのレトロゲーム風の曲で使われるFM音源の音色を投稿・共有できるOPNShareというサービスを公開しました.
この記事ではOPNShareについて簡単な紹介と使用した技術について説明します.
リポジトリはこちらです.
サービスの特徴
OPNShareではユーザー登録するとOPN系FM音源 (OPN/OPNA/OPNB/OPN2) 向けの音色データを投稿することができます.音色データはPMD, FMPなど80~90年代の作曲に使われたMMLによるテキスト形式によるもの,またはDefleMask, Furnaceなど近年のPCでの使用を想定したミュージックトラッカーのバイナリ形式によるものに対応しています.
OPNShareではユーザーが投稿した音色データを誰でもダウンロードすることができます.音色は先述のMML形式のテキストやトラッカー向けバイナリ形式のデータとしてダウンロードできます.またユーザー登録していれば,気に入った音色に「いいね」をつけることができます.
OPNShareではAPIを公開しています.APIでは投稿された音色データのリストや音色パラメーターを取得することができます.APIの仕様はリポジトリのopenapi.yamlにまとめています.
開発の動機
FM音源の音色は合成原理の複雑さやパラメーターの多さ(OPN系だと40個以上!)による制御の難しさなどから,自分で音を作るには少し難易度が高いです.そのため多くのひとは音源ドライバー付属のプリセットデータやネット上で公開されている音色データを使用することが多いと感じています.私が開発していたBambooTrackerというトラッカーの音色データも,デモファイルとして同梱しているものやトラッカーの公式Discordサーバーにポストされているものがあります.
これらの音色データは個人単位でデータを公開しているものが多く,様々な場所にデータが点在しているのが現状です.このためユーザーが使いたい音色があるとき,様々な場所を巡って音色を探す必要があります.場所によっては検索エンジンで出てこない場所があったり,インターネットアーカイブを辿らないと見つからないところもあり,容易に音色を探すことができません.
作曲するにあたって音色を気軽に手に入れることができ,かつ誰でも音色を共有する場所をつくるため,このサービスを開発しました.
機能と実装について
サービスの基本構成
フロントエンド・API管理・ロジック処理にはNext.jsを使用し,認証情報・データの管理にはFirebaseを使用しました.これらは日本語の資料が多く,Webアプリ開発初心者向けの解説記事も多く存在しており,開発時に不明な点があればすぐに情報を見つけられるため採用しました.
アプリ構成図
今回のアプリでは初めて外部のバックエンドプラットフォームであるFirebaseを利用しました.Firebaseで提供されているサービスのうち,Firebase Authenticationをユーザーの認証情報の管理に,Firestoreをユーザー情報と音色情報の管理に利用しました.NoSQLであるFirestoreではオブジェクトのままデータを保持できるため,アプリで扱う音色データをオブジェクトのまま扱えるのが便利でした.Firebaseはいくつかの簡単な関数を呼び出すだけで認証管理やDB管理を行うことができ,簡単なチュートリアルに従って実装するだけで動く機能が実装できたので開発体験が非常に良かったです.
アプリのホスティングにはVercelを利用しました.Next.jsの開発元であるVercel社の提供するサービスであり,後述のv0も合わせたデプロイ環境の構築が容易にできることを理由に採用しました.
UIコンポーネントはshadcn/uiを,スタイリングにはTailwind CSSを使用しています.これらはv0を用いてデザインしたものを基本的に使用しています.
そのほか毎回コミット時にロジック部分のテストをGitHub Actionsで実行することで,Vercelの自動デプロイ機能を合わせてCI/CDを実現させています.
仕様の策定
開発を始める前に何を作るか,何を提供するか,何で作るか,どう作るかをChatGPTと議論して決めました.私はネット上の不特定多数が利用することを想定したアプリを作成したことがなく,いわゆる開発や実装の定石パターンには詳しくありません.そこで自分のアイディアを共有しつつ,他のサービスの例を出しながらどのような機能や画面遷移が必要か,どのようなライブラリ構成でアプリを構築するかなどをChatGPTと相談しつつ,仕様を策定しました.
ユーザーインターフェース
ChatGPTとの議論で決まった内容を仕様書にまとめたのち,Vercelが提供しているv0を使って画面レイアウトやコンポーネントの作成,コンポーネントのふるまいの大枠を決定しました.
コンポーネントへのユーザーアクションやリンクアドレス,開発途中での仕様変更に伴うユーザーインターフェースの修正は一部Claudeの協力を得ながら基本手作業で修正していきました.
ゼロからサイトを構築するとなるとおそらく今の倍以上は工数がかかったと思うので,AIを活用して開発効率を高めるのはとてもいい手だと思いました.
テキストデータパーサー
テキスト形式の音色データからの音色パラメーターの読み取り処理では,以前の記事で紹介したnearleyによる字句解析・構文解析を行っています.
もともとはコメントや空行もnearleyで構文を定義して扱えるようにしたかったのですが,文法の定義がうまくできなかったこともあり,文字列の置換・削除などテキスト整形をしてから解析を行うようにしました.パーサーの作成は今回の実装で一番苦労した場所でかなり無理やり感が否めないので,もう一度構文の定義から実装し直したほうがいいかもしれません.
API
API機能はNext.jsのAPIルーティング機能を使用して実現しています.クライアントからのリソース取得のリクエストに対して,APIサーバーがFirestoreから取得したドキュメントを整形しレスポンスを返すフローになっています.
APIの実装は初めにOpenAPIで仕様をまとめたのちに,Zodでリクエストのパラメーターとレスポンスのボディのスキーマを定義してから実装しました.データの検証はクライアント-APIサーバー間およびAPIサーバー-Firestore間のリクエスト,レスポンス両方に対して実施し,ロジック部分ではデータの型安全性を担保したうえで処理を行うようにしました.
今後の開発
今後の開発については以下3点への対応を検討しています.
- 音色プレビュー機能の追加
- API呼び出し・キャッシュ処理の効率化
- ロジック処理のマイクロサービス化
音色を探すときテキスト情報だけでは音色のイメージは伝わりません.そこでどんな音色かをすぐ確認できるように,鍵盤を押すとその音色で音が鳴るキーボードを音色ページに表示したいと考えています.FM音源エミュレーターはC言語で書かれたコードが公開されているため,こちらのコードをWASM化してブラウザーで利用できるようにしつつ,AudioWorkletによるリアルタイム音声生成で音声プレビューを行うことを想定しています.AudioWorkletによるリアルタイム音声生成は以前試したことがあるので,この時の記録をもとに実装を進めたいです.
今回は外部サービス連携やホスティングサービスへのデプロイなどを初めて実装・対応したものが多く,かなり設計が危うい部分や冗長な部分があると感じています.特にデータベースサーバーとのやり取りは呼び出しが冗長な箇所が多く,今後Webアプリへのアクセス数が増加した際に負荷がかかる恐れがあります.サーバー負荷削減・リクエスト数削減のため,リクエストの方法の見直しやキャッシュの設定を検討したいです.
また現在Next.jsのフロントエンド内で実行しているロジック部分は,今後機能が拡張されると処理が肥大化していきます.フロントエンド部・ロジック部と処理の責務を明確に分離するため,個々のロジック処理をサービスとして分割するマイクロサービス化も場合によっては対応していきたいです.
Discussion