Code Monkey home page Code Monkey logo

flmml-on-html5's Introduction

FlMML on HTML5

npm version Join the chat at https://gitter.im/argentum384/flmml-on-html5

The porting of FlMML, MML player which runs on Flash Player, to HTML5.


Flash上でMMLを演奏するFlMMLをHTML5環境上に移植したものです。

v2.0.0 からオーディオファイル出力にも対応しました。

デモはこちら
Demo page

※v1.x 系で付属していたプレイヤーUIは v2.0.0 で削除されました

対応ブラウザ

  • Chrome
  • Chrome for Android
  • FireFox
  • FireFox for Android
  • Microsoft Edge
  • Opera
  • Opera Mobile
  • Safari (14.1 以降)
  • Safari on iOS (iOS 14.5 以降)
  • Android Browser (Android 5.x 以降)

使い方

シーケンサの詳細な仕様はwikiをご覧下さい。

js ファイルを直接読み込む

  1. Releases から flmml-on-html5.js , flmml-on-html5.worker.js のそれぞれをダウンロード
  2. flmml-on-html5.js のみ <script> タグで読み込む
  3. 再生開始の契機となるイベント発火の前にクリックされるボタン/プレイヤー等 DOM 要素の CSS セレクタを FlMML.prepare(playerSelector) の引数に指定し実行する
    ※実行しなくとも問題ない場合もありますが実行することを推奨します。詳細は wiki を参照
  4. new FlMML() の引数に flmml-on-html5.worker.js のパスを指定
    flmml-on-html5.worker.js をサイトと異なるドメインに配置した場合は crossOriginWorker オプションを有効にして下さい。詳細は wiki を参照

例:
ディレクトリ構成

somedir
├js
│├flmml-on-html5.js
│└flmml-on-html5.worker.js
└index.html

index.html

...
<script src="./js/flmml-on-html5.js"></script>
<script>
    FlMML.prepare("#play");
    const flmml = new FlMML({ workerURL: "./js/flmml-on-html5.worker.js" });
    function onClick() {
        flmml.play("L8 O5CDEFGAB<C");
    }
</script>
...
<button id="play" onclick="onClick()">Play</button>
...

npm パッケージをインストール

  1. インストール
    npm の場合:
    npm i -D flmml-on-html5
    
    yarn の場合:
    yarn add -D flmml-on-html5
    
  2. インストール後 ./node_modules/flmml-on-html5/dist/flmml-on-html5.worker.js をお好みの場所にコピー
  3. 再生開始の契機となるイベント発火の前にクリックされるボタン/プレイヤー等 DOM 要素の CSS セレクタを FlMML.prepare(playerSelector) の引数に指定し実行する
  4. new FlMML() の引数にコピーした flmml-on-html5.worker.js のパスを指定

例:

import { FlMML } from "flmml-on-html5";
...
FlMML.prepare(somePlayerSelectors);
const flmml = new FlMML({ workerURL: someWorkerURL });
...

For Developers

開発環境構築

  • Node.js, yarn 導入済の環境で
  • git clone 後リポジトリのルートディレクトリに移動し
yarn install
yarn start

謝辞

FlMML作者のおー氏をはじめとしたFlMMLのコミッターの皆様、ならびに FlMML on HTML5 の不具合を報告頂いたユーザーの皆様、そのほか FlMML / FlMML on HTML5 の発展に関わるすべての方々に感謝します。

MP3 ファイル出力に lamejs を利用しています (外部のスクリプトとして読み込むためソースコードは同梱していません) 。

flmml-on-html5's People

Contributors

argentum384 avatar gitter-badger avatar kosh04 avatar t-hazawa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

flmml-on-html5's Issues

サンプリングレート変換を Web Audio に任せる

シーケンサ側のサンプリングレート 44100 Hz と音声デバイスのサンプリングレートが異なる場合は Worker 側でレート変換処理を行っていたが、Safari 14.1 以降で AudioContext コンストラクタのオプション指定 に対応したことにより Web Audio 側での変換が可能になったため、 Worker 側の変換処理をやめ Web Audio 側で変換させるように変更する。

AudioWorklet 導入

概要

音声再生に利用している ScriptProcessorNode はすでに deprecated なので、代替機能である AudioWorklet に差し替える。

AudioWorklet では一度に送信するオーディオバッファのサイズが 128 サンプルで固定なので、シーケンサ側で作成したバッファがより長い場合でもバッファ長の違いを吸収する仕組みを考える。

workerURL が cross-origin な場合も読み込めるようにする

概要

コンストラクタ new FlMML() に指定する flmml-on-html5.worker.js のパスは Web Worker の仕様上同一オリジンである必要があったが、 CDN などクロスオリジンでの利用例が見受けられるため、オプションを付けることでクロスオリジンのURLも指定可能とする。

利用例

コンストラクタのオプションで crossOriginWorkertrue にした場合のみ、 Worker スクリプト読み込みに inline worker + importScript() を挟み同一オリジン制約を回避する。

想定利用例

new FlMML({
    workerURL: "https://example.com/flmml-on-html5.worker.js",
    crossOriginWorker: true
});

iOS14.5以上で 他アプリのバックグラウンド再生が止まってしまう問題を解消するPull Requestを出そうとしています

ご挨拶

  • はじめまして、flmml-on-html5 を使わせて頂いている t_hazawa と申します。素晴らしいプロダクトを公開していただき、誠にありがとうございます。
  • Pull Request を送信しようと考えています。

問題 と Pull Request について

問題の背景

  • iOS14.5 以上において、AudioContext が生成されたタイミングで、他アプリのバックグラウンド再生を停止させる挙動となったようです。

問題点

  • このため、 flmml-on-html5 を読み込んだ段階で、iOS14.5以上では、他のアプリのバックグラウンド再生が停止されてしまっていました。
  • サイト訪問者からすると、サイトを開いただけで他アプリのバックグラウンド再生が止まってしまう、という問題点がありました。
    • サイト内のFlMMLプレイヤーで再生を開始したタイミングで、他アプリのバックグラウンド再生が止まるのは問題なさそうです。

動的インポートではダメそう

  • なお、 ES2020 からは「動的インポート」がありますが、 https://qiita.com/pentamania/items/2c568a9ec52148bbfd08 に示されている理由(※)で、動的インポートでは、この問題点を解決することはできませんでした。
    • ※: ユーザーイベントでも、途中で非同期処理(=動的インポート) を挟むとアンロックできないという点です。

私が出そうとしているPull Request の内容

  • そこで、私は、プレイヤーで再生する直前に AudioContext を生成するように flmml-on-html5 に修正を加えました。

  • 修正後には、iOSで上記の問題がなくなり、Windows10, Mac, iPhone, Android の主要ブラウザで問題なく動作をさせられました。

  • この問題は一般性があるかと思い、Pull Requestを送信しようと考えています。

    • 上記の手直ししたい箇所があるため、一週間後頃に提出できるかと思います。
    • 作業がかぶらないように事前に連絡させて頂きました。
  • Pull Request 送信後、もしよかったらレビューや取り込みをして頂けますと幸いです。


以上となります。どうぞよろしくお願いいたします。

モジュールFMのLFO周波数がおかしい

モジュールFMのLFO周波数が想定の8倍になってしまっている (PM/AM両方) 。

原因は元のFlMML (ActionScript) では0で初期化される

private lfo_step_: number;

が初期化されずundefinedのまま
this.lfo_step_++;
if ((this.lfo_step_ & 7) === 0) {
this.lfo_count_ += this.lfo_count_diff_;
}

でundefinedまたはNaNをインクリメントし続けずっとNaNのままとなり、if文の条件式が常に真になっているため。

AudioContext 生成と Web Audio API アンロックの契機変更

概要

#32 の続きの対応。

具体的には、元々の実装では、「JS読み込み時にAudioContextを生成し、画面のどこかをクリック時にアンロック」されていたのを、 「HTML上で プレイヤーを表すDOMに 専用のクラスをつけ、そのクラスがクリックされたときに 生成&アンロック」を行うようにしました。

の対応で未考慮だった以下の点を対応する。

  • プレイヤーの DOM 要素の CSS セレクタを指定できるようにする
    • click イベントが発生する前に FlMML.prepare(playerSelectors) で対象の DOM 要素を指定させるようにする
    • あるいは flmml-on-html5.ts ソースコード内の PLAYER_SELECTORS に CSS セレクタを指定してビルドした場合は、上記 FlMML.prepare(playerSelectors) を実行することなく表題の生成 & アンロックが行えるものとする
    • (参考)
  • Worker 側の初期化 ( MsgType.BOOT 送信) がスクリプト読み込み時だったのを AudioContext 生成後に変更する
    ( AudioContext.sampleRate がシーケンサに渡せなかったため )

コンストラクタの引数形式変更

概要

new FlMML() の引数は workerURL のみだったが、今後の拡張性を鑑みオブジェクトでの指定も可能とする。
また、併せて bufferSize, bufferMultiple, infoInterval の指定を可能とする。

変更点

  • 引数に以下プロパティを持つオブジェクトを指定した場合、以下を反映する。いずれも省略可
    • workerURL : 従来通りの Worker スクリプトの URL
    • bufferSize : バッファサイズの指定 ※省略時 8192
    • bufferMultiple : { バックバッファ総サイズ / バッファサイズ } の倍率指定 ※省略時 32
    • infoInterval : setInfoInterval() で指定する間隔の初期値指定 ※省略時 125
  • 引数に文字列を指定した場合従来通り workerURL のみ指定した扱いとする

参考: v2.x の仕様wiki
https://github.com/argentum384/flmml-on-html5/wiki/v2.x

COM_ 系の定数をpublic化

flmmlonhtml5-raw.js, Messenger.ts で定義されているCOM_BOOTなどのMainスレッド - Workerスレッド通信用の定数はローカル変数として定義されているが、外部のFlMMLonHTML5を利用する機能から参照できず不便であるため、publicなstaticフィールド(相当)に修正する。
(恐らく当時はminify後jsファイルのサイズ削減を狙ったものと思われます)

オーディオファイル出力機能追加

概要

FlMML on HTML5 を利用するツールの中にはシーケンサの出力を wav, mp3 などのオーディオファイルに出力できるよう改変しているものが見受けられる。
ただ、かなり大がかりな改変を各々のツール作成者に強いることになってしまっているので、本分であるMML再生機能への影響が小さく、またソースコードサイズが大きくなりすぎない範囲で類似のオーディオファイル出力機能を追加する。

仕様

wav ファイル、mp3 ファイルの出力を可能とする。
出力後ファイルを加工することを考慮し、直接ブラウザにダウンロードさせず、ファイルのバイナリデータを取得するようにする。

  • 共通
    • flmml.export***(mml, ...) の形式のメソッドを実行して出力開始
      • 戻り値は Promise<ArrayBuffer[]> とし、出力が完了したら resolve され ArrayBuffer[] として出力ファイルのバイナリデータを取得できる
        • ArrayBuffer[] はそのまま new Blob(...) の第一引数に指定できる
      • 出力中シーケンサは通常再生と異なり、いわばオーディオファイル出力モードで動作する
        • Web Audio は一切操作しない
        • シーケンサで作成した音声バッファも AudioWorkletProcessor には送らず、代わりに同一 Worker 内オーディオファイル出力用の処理に回される
        • 現在の処理位置は通常再生と同様 flmml.getNowMSec() 等で取得可
        • 一時停止 (flmml.pause()) は無効とする
        • 出力中 flmml.stop() を実行すると出力処理を中断し、戻り値の Promise は reject される 中断時点までの分だけ出力して処理完了する
        • 出力中 flmml.play(mml) を実行した場合、 出力中の処理が中断, reject される 中断時点までの分だけ出力して処理完了する。その後通常再生が始まる
        • 出力中 flmml.export***(mml, ...) を再度実行した場合、後から実行した方は reject される
    • チャンネル数とサンプリングレートは FlMML シーケンサ部と同じ 2ch, 44100 Hz で固定
  • wav ファイル
    • flmml.exportWav(mml) で出力
    • ビット深度は 16 ビットで固定
  • mp3 ファイル
    • flmml.exportMp3(mml, bitrate) で出力
    • ビットレート bitrate は指定可。省略時は 192 (kbps) とする
    • エンコーダに lamejs を利用
      • mp3ファイル出力を利用する場合のみ new FlMML(...) のオプションで lamejs のスクリプトのURLを指定させ、 importScripts(...) で読み込むようにする

再生またはオーディオファイル出力が停止してしまうことがある

の調査をしたところオーディオファイルに限らなさそうなので別に起票

原因

前提として FlMML (Flash版も) は出力音声データを保持する2つの表裏バッファを持ち、表のバッファの再生中、裏のバッファに表の後に続く音声を先んじて書き込み、表の再生が終わったら今度は裏を再生して表に書き込んで…… というように表裏の役割を切り替えながら再生と音声書き出しを行っている。

このバッファの裏表を切り替える時点で現在の面のバッファへの書き込み完了直後の状態だと、最終的には然るべき分岐に入れず後続の処理が止まってしまう様子。

レンダリングが重いMMLで起こりやすいが、ブラウザ/MML/その他端末環境などによってたまたま起こり得る事象とみられ、特定の状況/環境でしか再現できなかったのはそのためと思われる。
また #56 はオーディオファイル出力だったが通常の再生でも発生する可能性がある。

対処

条件分岐の修正で対処できそうなので対応予定

オーディオファイル出力が途中から進まなくなってしまうことがある

レンダリング時の負荷が大きいMMLをオーディオファイルとして出力しようとすると、出力処理が途中から進まなくなる現象が発生することを確認しました。
当方の環境では、mml/21290mml/21291などで当該現象の発生を確認しました。
なお、処理が進まなくなった状態でflmml.stop()を実行した場合は、処理が進まなくなるまでの分のオーディオファイルが出力されます。
この現象の発生原因の調査、および不具合修正をお願いしたく思います。
お手数をおかけしますが、何卒よろしくお願いいたします。

【告知】ユーザ名とリポジトリ名を変更します

自分のユーザ名とリポジトリ名の変更を行います。

  • 旧: carborane3 / FlMMLonHTML5
  • 新: argentum384 / flmml-on-html5

変更作業は 11/9(金) に行う予定です。

以下影響のあるところについて行う対応と (git cloneされている方向けの) お願いになります。

Git

旧リポジトリへのアクセスは新しい方にリダイレクトされるようなので、リモートリポジトリURLの変更 (cloneしたローカルリポジトリなど) は後からでも問題ないはずです。とはいえ、一応お手すきの際に変更をお願いします。

GitHub Pages

こちらはリダイレクトされないので、GitHub Pagesへのリンクのある README.md は要修正となります。変更作業の中で修正します。

ユーザ名とリポジトリ名の記述箇所について

LICENSE, flmmlonhtml5.js, flmmlonhtml5-raw.js のCopyright表記を新しいユーザ名とリポジトリ名に修正します。


関係者の皆様にはご迷惑おかけしますが、かねてより強く変えたい思いがありましたので、何卒ご容赦下さい。
考慮漏れや不備あればご指摘頂けると幸いです。宜しくお願いします。

一部の音色がAudioContextのサンプリングレートに依存している

以下の音色またはフィルタは、ノイズ周期や周波数特性がAudioContextのサンプリングレートに依存しており、可聴域を十分カバーする範囲においてもレートが異なると聴感上音の違いがはっきり聞き取れます (48000Hzと96000Hzで比較) 。

  • ホワイトノイズ @4
  • FC長周期ノイズ @7
  • FC短周期ノイズ @8
  • GB長周期ノイズ @11
  • GB短周期ノイズ @12
  • フォルマントフィルタ @'A', @'E', @'I', @'O', @'U'

他にも、上記ほど顕著ではないものの折り返しノイズの現れ方が違ったり、各種波形メモリの音色が波形によってはやや異なる場合があります。
AudioContextのサンプリングレートによるこれらの差異ができるだけ小さくなるよう対応します。

対応は、FlMML (Flash版) に準拠し、全ての波形生成/合成をレート44100Hzで行うようにし、全トラック合成後にAudioContextのサンプリングレートにリサンプリングしてからWeb Audio APIに渡すといった変更を想定しています。

Web Audioアンロック処理のリスナー削除ができていない

#11 でWeb Audioアンロック処理をclickイベントのキャプチャフェーズで行うようにしたため、同イベントのリスナー削除の際 removeEventListener の第3引数 (useCapture) をtrueとすべきだが、対応が漏れている。
実害は無いようだが余計にイベントが発火するので修正する。

window.removeEventListener("click", onClick);

clickイベントの伝播を途中で止められるとiOSでMMLが再生されない

iOS用のWeb Audioアンロック処理はdocumentのclickイベントに紐づけられているが、以下のようにaddEventListenerの引数 useCapture (参考) を指定していないのでイベント伝播のバブリングフェーズで実行される。

// iOS Safari対策
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("click", function onClick(e) {
var audioCtx = FlMMLonHTML5.audioCtx;
var bufSrcDmy = audioCtx.createBufferSource();
bufSrcDmy.connect(audioCtx.destination);
bufSrcDmy.start(0);
document.removeEventListener("click", onClick);
});
});

このため、同ページ内の何らかのDOM要素のclickイベントで event.stopPropagation()return false (後者はイベントハンドラ内限定) が実行されるような状況では、キャプチャフェーズまたは下位要素のバブリングフェーズの途中でclickイベントの伝播が止まってしまい、Web AudioがアンロックされずMMLの再生が始まらない。
例えばFlMMLonHTML5がロードされているぺージ内で特定の要素 ( <div> でも <button> でも何でも) に対してクリックを無効にする処理が他にある場合などが該当。

対処としては以下2点両方の修正を入れ、他のDOM要素のclickイベントに影響を受けないようにする。

  • 前述のaddEventListenerの引数 useCapturetrue を指定し、キャプチャフェーズでアンロック処理を実行するようにする
  • 紐づけるイベントをdocumentのclickイベントではなく、最上位のDOM要素であるwindowのclickイベントに変更する

getMeta***()でundefined文字列が紛れることがある

例えば、単に#ARTISTだけの行をMML中に入れると、metaArtistにundefinedと出ます。
おそらくMML.findMetaDescNの実装が悪いのではないかと思います。
1104行目、#ARTISTなどの後に内容があるかの確認処理と思われる部分if (mm.length >= 3) {とありますが「mm[2]がundefinedでなければ」という式にすべきではないでしょうか。
findMetaDescVも同じ実装となってますので直すとよいと思います。

よろしくお願いします。

バージョン表記

FlMMLonHTML5.js が外部サイトで利用された場合の対応として、できる限りバージョン番号を明記してほしいです。

JS本体がminifyされた状態であっても、コードコメント中に埋め込む等でFlMMLonHTML5のバージョン番号が明記してあれば「いつの時点のソースコードなのか」が分かるため、何かと安心かと思います。

表記例

/*! FlMMLonHTML5 v1.0 | (c) 2018, carborane3 | BSD-3-Clause | https://github.com/carborane3/FlMMLonHTML5 */

参考リンク

npmパッケージ化

概要

npm にパッケージとして公開できるようにするため全体の構成を見直す。
npm レジストリへの publish はGitタグの push 時に GitHub Actions で行う。
同時にビルドした .js ファイル単体も GitHub の Release Assets にアップロードする。

その他構成見直しに際する変更

ユーザーに影響のある変更

  • リリースする js ファイルの名前を以下の通り変更
    • flmmlonhtml5.js -> flmml-on-html5.js
    • flmmlworker.js -> flmml-on-html5.worker.js
  • プレイヤーUI flmmlplayer.js は需要がなさそうなので削除 ※要望あれば ts で書き直す
  • エントリポイント FlMMLonHTML5 の名前を FlMML に変更。ただし当面の間 FlMMLonHTML5 でも利用可とする

内部的な変更

  • webpack 導入
  • flmmlonhtml5-raw.js をTypeScript化
  • 名前空間 ( flmml ,fmgenAs , messenger ) の撤廃

開発環境をVSCodeに移行

ファイル構成がVS2013での開発向けになっており、TypeScriptのバージョンも1.5で色々と古かったため、開発環境の刷新とソースコードのリファクタリングを行う。

  • VSCodeで開発できるよう必要な設定ファイル追加 & 不要なファイル削除
  • TypeScriptのバージョンを最新のものに追従 (今回は3.1.6)
  • 新バージョンのTypeScriptでコンパイルエラーになった箇所を修正
  • 定数フィールドの宣言に readonly 修飾子 (TypeScript 2.0~) を追加
  • UglifyJSのバージョンを最新のものに追従 (今回は3.4.9)
  • README.mdの開発者向け説明修正

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.