# LMML [![](https://img.shields.io/crates/v/lmml?color=blue&label=lmml)](https://crates.io/crates/lmml) [![](https://img.shields.io/crates/v/lmml-parser?color=blue&label=lmml-parser)](https://crates.io/crates/lmml-parser) [![](https://img.shields.io/crates/v/lmml-cli?color=blue&label=lmml-cli)](https://crates.io/crates/lmml-cli) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/yuma140902/lmml/test.yml)]() LMML (**L**MML **M**usic **M**acro **L**anguage) はMMLの方言です。 ## 演奏例 https://github.com/yuma140902/lmml/assets/23431077/dfad8777-ade5-4591-8804-a3968a2e14ea ``` ; Bad Apple!! feat. nomico t320 l4 @0 v15 cd e.r8ag e.r8 edc cd e.r8dc c cd e.r8ag e.r8 edc cd e.r8dc c.r8 d.r8e.r8 @1 v10 cd e.r8ag e.r8 edc cd e.r8dc c cd e.r8ag e.r8 edc cd e.r8dc c.r8 d.r8e.r8 @3 v25 gaed e.r8de gaed e.r8 dedc c de egga ede.r8 dega ede.r8 dedc c de @4 v30 egga ede.r8 dega ede.r8 dedc c de egga ede.r8 dega ede.r8 ab>c @0 v15 egga ede.r8 dega ede.r8 dedc c de egga ede.r8 dega ede.r8 dedc c de egga ede.r8 dega ede.r8 dedc c de egga ede.r8 dega ede.r8 ab>c`コマンド オクターブの値を1増やします。 ### `<`コマンド オクターブの値を1減らします。 ### `N`コマンド MIDIのノート番号(0~127)によって音符を指定します。 ※LMMLはMIDIに依存していませんが、内部的にMIDI互換のフォーマットで音符を扱っています。 ### `V`コマンド 音の大きさをセットします。初期値は20で、大きいほど音が大きくなります。100が0dB、0が-∞ dBに対応します。 ### `T`コマンド テンポをセットします。値は1分間に四分音符が鳴る回数を表します。初期値は120です。 ### `@`コマンド 0~4の数字で波形を設定します。デフォルト値は0です。 | 数字 | 波形 | |------|------------| | 0 | ノコギリ波 | | 1 | 矩形波 | | 2 | パルス波 | | 3 | 三角波 | | 4 | 正弦波 | ### `:`コマンド LMMLには0~15の16個のチャンネルがあり、これらを同時に演奏することができます。 `:0`のように書くと、それより後のコマンドはチャンネル0に対して作用します。 #### 例 https://github.com/yuma140902/lmml/assets/23431077/6f76011c-c638-4741-a067-14986da3178d ``` ; 少女さとり ~ 3rd eye (東方地霊殿より) ; (最初の2小節のみ) :0 @3 v20 t60 o4 :1 @3 v20 t60 o3 :2 @3 v30 t60 o2 :3 @4 v50 t60 o2 :4 @4 v20 t60 o4 :5 @4 v40 t60 o1 :0 l8 c+16d16f+ df+gc+gf+2 :1 l2 f+1 gf+ :2 l2 f+1 gf+ :3 l32 crrrrrrr rrrrrrrr [d+g]rrrrr[d+g]r rrrr[d+g]rrr <<[bg+f]rrrrrrr rrrr[bg+f]rrr >[g+f]> :4 l2 [bf+]1[bg][bf+] :5 l2 [bf+]1[bg][bf+] ``` ## LMML言語の文法 ``` := * := | | | | | | | | | | | := ? ? ? := 'C' | 'D' | 'E' | 'F' | 'G' | 'A' | 'B' | 'c' | 'd' | 'e' | 'f' | 'g' | 'a' | 'b' := '+' | '-' := 'R' ? ? | 'r' ? ? := '[' ( ?)+ ']' ? ? := '.' := 'N' | 'n' := 'O' | 'o' := 'L' ? | 'l' ? := 'V' | 'v' := 'T' | 't' := '@' := ':' := '>' := '<' := + := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ``` 基本的に大文字小文字を区別しません。また、空白や改行は無視されます。 `;`から始まる行はコメントとして無視されます。 ## LMML言語の細かい仕様 ### 長さの指定について 音符コマンド、休符コマンド、`L`コマンドで数字や`.`を省略した場合の挙動は表のようになります。予想する挙動が人によって異なると思われる部分は太字で示してあります。 | 最後の`L`コマンドの数字 | `.`有無 | 音符・休符コマンドの数字 | `.`有無 | 長さ | |-------------------------|---------|--------------------------|---------|-----------------| | `L`コマンド無し | N/A | 省略 | 無 | 四分音符 | | `L`コマンド無し | N/A | 省略 | 有 | 付点四分音符 | | `L`コマンド無し | N/A | n | 無 | n分音符 | | `L`コマンド無し | N/A | n | 有 | 付点n分音符 | | m | 無 | 省略 | 無 | m分音符 | | m | 無 | 省略 | 有 | 付点m分音符 | | m | 無 | n | 無 | n分音符 | | m | 無 | n | 有 | 付点n分音符 | | m | **有** | 省略 | **無** | **付点m分音符** | | m | 有 | 省略 | 有 | 付点m分音符 | | m | **有** | n | **無** | **n分音符** | | m | 有 | n | 有 | 付点n分音符 | ### 音の高さについて 音符コマンドを周波数に変換する処理は2段階で行われます。 まず以下のようにしてノート番号と呼ばれる内部形式に変換します。$`n`$は音符コマンドの種類、$`m`$はシャープ・フラットなどの指定、$`o`$は現在のオクターブです。 ``` base = if n == C then 0 if n == D then 2 if n == E then 4 if n == F then 5 if n == G then 7 if n == A then 9 if n == B then 11 modifier = if m == '+' then 1 if m == '-' then -1 else 0 N = base + modifier + ((o + 1) * 12) ``` 次にノート番号$`N`$を周波数$`f`$ [Hz]に変換します。 ```math f = 440 \times 2 ^ { \frac{N - 69}{12} } ``` ### 音の減衰について 1つの音は鳴った瞬間に最も音量が大きく、徐々に小さくなっていきます。具体的には`V`コマンドで指定した音量を$`v_0`$、音の長さを$`T`$、音が鳴り始めてからの経過時間を$`t`$とすると、$`t`$における音量$`v(t)`$は ```math v(t) = \frac{v_0 (T-t)}{T} ``` です。 減衰の仕方を変える方法は現在のところありません。 ### 数値の限界について #### オクターブの値 上限・下限ともにありませんが、-1から9程度の範囲で使用することを想定しています。 なお、`O`コマンドでは非負の値しか指定できないため、負のオクターブを指定したい場合は`<`コマンドを使用してください。 #### ボリュームの値 下限は0、上限はありません。ただし100が0dBに対応するため、それより大きい値を指定すると音割れが発生する場合があります。0のときは音が全く出ません。 #### `N`コマンドの値 下限は0、上限はありません。ただし0から127の範囲の値を想定しています。 #### テンポ 下限は1、上限はありません。 #### チャンネル番号 下限は0、上限は15です。 ## LMML実装の細かい仕様 ### 精度について 波形合成は44.1kHz、32bit-floatで行っています。 ## 各クレート ### `lmml`クレート LMMLのASTやタイムラインのデータ構造と波形合成処理を含むクレートです。 ### `lmml-parser`クレート LMMLのパーサーです。 ```rust use lmml::{LmmlAst, LmmlCommand, NoteChar, NoteModifier}; use lmml_parser::parse_lmml; pub fn main() { let ast = parse_lmml("t80 c+ d e8. r8").unwrap(); assert_eq!(ast, LmmlAst(vec![ LmmlCommand::SetTempo(80), LmmlCommand::Note { note: NoteChar::C, modifier: NoteModifier::Sharp, length: None, is_tied: false, }, LmmlCommand::Note { note: NoteChar::D, modifier: NoteModifier::Natural, length: None, is_tied: false, }, LmmlCommand::Note { note: NoteChar::E, modifier: NoteModifier::Natural, length: Some(8), is_tied: true, }, Lmmlcommand::Rest { length: Some(8) is_tied: false, }, ])); } ``` ### `lmml-cli`クレート LMMLを対話的に演奏したり他の形式に変換するためのコマンドラインツールです。 #### インストール方法 ```sh cargo install lmml-cli ``` #### 使用方法 ファイルを演奏する。 ```sh lmml load ファイル ``` 対話モードを起動する。 ```sh lmml repl ```