musicLine(iOS)についての進捗。
MIDIプレイヤーで曲を再生する部分について報告します。

実装状況
| カテゴリ | タイトル | 補足 | |
| View | |||
|
Composition UI配置
ガイド表示 | 再生バー | 曲を再生している時に、再生位置をバーで表示 | |
| 再生ボタン | 曲を再生/停止を監視して、状態を変更、再生準備が遅い時はインジケーターを表示 | ||
| 再生速度の表示 | 曲の再生速度を表示(iOS独自) | ||
| テスト音のガイド | 鳴っている音階に色をつける | ||
|
Community UI配置
| |||
| Composition | |||
| MIDI | コンバーター | CompositionからMIDIのデータ構造へ変換 | |
| 曲の再生 | 画面左からMIDI再生、Compositionのデータ構造から曲の再生、再生位置の同期 | ||
| 曲の再生(最初から) | 再生ボタン長押しで最初から再生 | ||
| 曲の再生(位置指定) | スクロール領域をダブルタップでその位置から再生(iOS独自) | ||
| 再生速度の変更 | ドラッグで曲の再生速度を変更(iOS独自) | ||
| 再生処理の最適化 | 曲に変更がない時はキャッシュを使う、変更通知とキャッシュの管理 | ||
| MIDI | |||
|
Common MIDI共通
| データ構造 | MIDIフォーマットへ出力できる構造 | |
| コントローラー | 曲の読み込みや再生、再生位置の監視や音源の変更などMIDIの扱いを管理する | ||
| サンプラー | ピアノや音符生成時に単体のMidiイベントから音を鳴らす | ||
|
Service サービスモデル
| MidiPlayer | MIDIを再生する | |
| MidiReader | MIDIファイルの読み込み | ||
| MidiWriter | MIDIファイル出力 | ||
| Commnity | |||
| Common コミュニティ
共通 | データ構造 | カテゴリ等の曲情報、いいねやお気に入り等のリスポンスを保持 | |
曲の再生やピアノ音を鳴らす処理について大まかな実装は完了しました。またコミュニティの実装にも着手しました。
全進捗マップを表示
| カテゴリ | タイトル | 補足 | |
| View | |||
|
Composition UI配置
ガイド表示 | ピアノ | ||
| スクロールエリア | |||
| 小節番号 | |||
| 小節線 | |||
| 分割線 | 拡大率によって間隔を変化 | ||
| フレーズ | フレーズがないところはフレーズ作成ボタンを表示 | ||
| ツールボタン | 選択中のツールはツール色にハイライトする | ||
| メロディーライン | ペンツールでの音符作成時の入力線 | ||
| メロディー音符 | 音符の先頭にチョボをつける | ||
| リズム音符 | ベース音符のみ下に表示 | ||
| サンプルデータ | 手入力したサンプルデータの音符を配置 | ||
| 連符 | 連符は数字で表示、拡大率によって省略 | ||
| ツールガイド(ペン) | 長さ編集しているリズム音符、伸ばした時の削る範囲 | ||
| ツールガイド(指) | 移動中の音符影、矩形選択の枠 | ||
| ツールガイド(消しゴム) | 削除範囲、和音のみの削除範囲 | ||
| ツールガイド(フレーズ) | フレーズ移動・伸縮のバー、選択範囲テキスト(iOS独自) | ||
| フレーズボタン | フレーズボタンとサブボタンを配置、ツールに応じてアイコンを変更、画面拡大率に応じて縮小 | ||
| フレーズタブ | ツールに応じてアイコンを変更、拡大率に応じて縮小、スクロール状態に応じて移動 | ||
| 音符描画の高速化 | 音符が多くても描画できる速さへ最適化 | ||
| 音符の編集状態 | 選択や変更した時に枠線の色変更 | ||
| 音符音階 | メロディ音符に音階表示 | ||
| 再生バー | 曲を再生している時に、再生位置をバーで表示 | ||
| 再生ボタン | 曲を再生/停止を監視して、状態を変更、再生準備が遅い時はインジケーターを表示 | ||
| 再生速度の表示 | 曲の再生速度を表示(iOS独自) | ||
| テスト音のガイド | 鳴っている音階に色をつける | ||
|
Community UI配置
| |||
|
Dialog ダイアログ
| フレーズ | フレーズの作成・設定・挿入、フレーズの長さやリピート回数の設定 | |
| Composition | |||
|
Common 作曲共通
| データ構造 | 座標とサイズ、状態を保持する。レンダラーやコライダー、通知等のロジック | |
| コンバーター | Domainのデータ構造へ変換・逆変換 | ||
| 通知機構 | Modelに変更があった時に、Viewへ通知して再描画。変更状況を監視してキャッシュ | ||
| FingerTool 指ツール
音符の編集 | データ構造 | フレーズの音符を取得、基点と移動ベクトルを保持して移動する | |
| 音符の移動 | 和音や連符は上下のみに制御 | ||
| 音符の影 | 移動中に操作できているかわかりやすいように(iOS独自) | ||
| 内外判定 | 音符の移動をフレーズ内に留める | ||
| 衝突判定 | 移動する音符が他の音符に重なったときの挙動 | ||
| タップ選択 | タップした音符の子音符も選択・解除 | ||
| 矩形選択 | 囲った音符を選択 | ||
| 音符の入れ替え | リズム音符のスライドで重なった音符と入れ替える(iOS独自) | ||
| 和音の削除 | 和音を移動した時に重なる時に削除する | ||
| 連符に切り替え | リズム音符タップで切り替え | ||
| PenTool ペンツール
音符の作成 | データ構造 | 音符を作成・分割・統合、フレーズへ音符を追加する | |
| 音符の作成 | タップで音符を作成。分割線に合わせて長さを決定 | ||
| 音符列の作成 | スワイプに沿って複数の音符を作成。音階変わる時に音符分割 | ||
| 有無判定 | タップやスワイプした縦ラインに既に音符があるか判定 | ||
| 音符の移動 | 音符が既にある場合は、作成ではなく移動。一定距離進むと移動終了 | ||
| 音符の分割 | リズム音符をタップした時、音符を分割、メロディ線に沿ってY位置を動かす | ||
| 音符の統合 | 2つのリズム音符の間をタップした場合、音符を統合 | ||
| 音符の伸縮 | リズム音符を左右にスワイプすることで音符の長さを伸縮、始点で分割なし(仕様変更) | ||
| 音符の消滅 | 音符の伸縮をした時に長さが0になると削除する(iOS独自) | ||
| 領域差演算 | 音符伸縮で他の音符に重なる時は差演算して他の音符長さを削る | ||
| サンプリング補間 | 素早くスワイプしても音符が移動できるようにサンプリング間を補間する | ||
| 細かい音符 | 分割線内の細かな音符を移動する挙動 | ||
| EraserTool 消しゴムツール
音符の削除 | データ構造 | 和音と連符の認識、フレーズから音符を削除する | |
| 音符の削除 | タップで音符を削除 | ||
| 和音・連符の削除 | 和音・連符をタップで和音・連符のみを削除(iOS独自) | ||
| 音符種別判定 | タップした音符が和音・連符・ルート音符なのか判定する | ||
| 音符列の削除 | スワイプで指定する削除範囲内の複数の音符を削除。 | ||
| 和音列の削除 | リズム音符をスワイプで複数の和音のみを削除。(iOS独自) | ||
| 休符に切り替え | リズム音符タップで休符に切り替える | ||
| PhraseTool フレーズツール
フレーズの編集 | データ構造 | トラックのフレーズを取得・追加、削除、状態 | |
| フレーズの作成・挿入 | ダイアログで長さとリピートを設定 | ||
| フレーズの選択 | 2点タップで範囲選択、範囲ガイドタップで選択解除(仕様変更) | ||
| フレーズの移動 | 左右スワイプで移動、移動は選択フレーズとスワイプ中のフレーズを含む | ||
| フレーズの伸縮 | サブ編集領域をスワイプした時にフレーズ長さを伸縮、長さ0で削除(iOS独自) | ||
| フレーズの貼り付け | 選択フレーズをコピー・ペースト、サブボタンかフレーズタブタップで挿入、選択解除 | ||
| 周囲有無判定 | 作成する小節の近くにフレーズがあるかの判定、設定できる長さやリピート回数の制御 | ||
| フレーズボタン・タブ | ツールに応じてフレーズボタン、タブの処理を変更 | ||
| フレーズの結合 | 連続する2フレーズの接する小節線をタップで結合(iOS独自) | ||
| フレーズの分割 | フレーズをタップで分割(iOS独自) | ||
| StampTool スタンプツール
モチーフの編集 | |||
| Transform 画面移動
座標変換 | 画面を上下移動 | ピアノのスワイプにより | |
| 画面を左右移動 | スクロールエリアのスワイプにより | ||
| 画面を拡大・縮小 | ピンチアウト・インにより基点を画面中心に設定する | ||
| 画面を拡大・縮小(軸指定) | 長押しからのドラッグ。ピアノでX軸方向、スクロールエリアでY軸方向 | ||
| MIDI | コンバーター | CompositionからMIDIのデータ構造へ変換 | |
| 曲の再生 | 画面左からMIDI再生、Compositionのデータ構造から曲の再生、再生位置の同期 | ||
| 曲の再生(最初から) | 再生ボタン長押しで最初から再生 | ||
| 曲の再生(位置指定) | スクロール領域をダブルタップでその位置から再生(iOS独自) | ||
| 再生速度の変更 | ドラッグで曲の再生速度を変更(iOS独自) | ||
| 再生処理の最適化 | 曲に変更がない時はキャッシュを使う、変更通知とキャッシュの管理 | ||
| MIDI | |||
|
Common MIDI共通
| データ構造 | MIDIフォーマットへ出力できる構造 | |
| コントローラー | 曲の読み込みや再生、再生位置の監視や音源の変更などMIDIの扱いを管理する | ||
| サンプラー | ピアノや音符生成時に単体のMidiイベントから音を鳴らす | ||
|
Service サービスモデル
| MidiPlayer | MIDIを再生する | |
| MidiReader | MIDIファイルの読み込み | ||
| MidiWriter | MIDIファイル出力 | ||
| Commnity | |||
| Common コミュニティ
共通 | データ構造 | カテゴリ等の曲情報、いいねやお気に入り等のリスポンスを保持 | |
| Domain | |||
|
データ構造 Json形式 | Melody | MelodyTrack, *MelodyPhrase, NoteContainer, NoteBlock, Note | |
| Drum | DrumTrack, *DrumPhrase, BeatContainer, Beat | ||
| 共通化 | Original, Repeat, Syncの3種のPhraseをジェネリッククラスとプロトコルで抽象化 | ||
|
Service サービスモデル
| SongReder | Jsonファイルを読み込み、Domainのデータへ変換 | |
| SongWriter | DomainのデータからJsonファイルへ書き出し | ||
| Common | |||
|
Service サービスモデル
| リポジトリ | 保存データを管理 | |
実装の詳細
MIDIのデータ構造
MIDIを再生するためのモデルを作りました。

MidiControllerで曲の読み込みや再生を管理します。
再生する際は、まず再生する曲MidiSongをchangeSong()メソッドで指定してMidiStreamを作成します。MidiStreamは曲情報や再生状況を保持してます。続いてplay()メソッドによりMidiPlayerにMidiStreamを渡すことで再生します。
なお、MIDIファイルから再生したい場合はMidiReaderを使ってファイルをMidiSongモデルに読み込みます。
なぜCompositionとMIDIにモジュールを分ける?
理由は可読性を高めるためです。
実装したコードを後から見直しても分かりやすくするためです。
例えば、musicLineではSongモデルといっても、作曲中やコミュニティ閲覧中なのか、再生するための曲なのかと状況によって曲の概念(曲に付属する情報)が変わります。
もちろんモジュールを一緒にして曲の情報を片っ端から付けまくることはできますが、ある状況では不必要な情報が付いている状態となり可読性が低下します。
つまり状況を混ぜるほど複雑になり、どのメソッド・プロパティを使えばいいかわからなくなる危険性があります。
特に後々の機能拡張や不具合修正を考えると、コードはシンプルな状態にして可読性を高く保つことで効率的な開発ができますね。
この辺りの話に興味がある方はこちらの記事がおすすめ♪
little-hands.hatenablog.com
データ構造の変換(Composition → MIDI)
作曲モデルからMIDIモデルのデータ構造に変換できるように変換モデルSongConverterを作りました。
MIDI再生をするときは、MIDIモデルへ変換して再生します。

SongConverterで作曲モデル(Song Track Phrase Element)をMIDIモデル(MidiSong MidiTrack MidiEvent)に変換します。
モデルを変換してしまえば、あとはMidiControllerにMidiSongを渡すだけで再生できるので、わかりやすいですね。
このように事前に扱いやすいモデルに変換すると、モジュール間のアクセスを少なくなる(疎結合)のが良いです。
曲の再生
UI:再生バーと再生ボタン
再生ボタンをタップすると、画面の左位置の時間から曲が再生されます。なお、再生中は再生バーが表示されます。
曲を再生する処理の流れ

1.曲の編集はCompositionモジュール内で完結しますが、2.曲の再生はMIDIモジュールに再生を委託する必要があります。
曲を再生する場合には、MidiControllerにMidiSongを渡す必要があります。しかし編集はSongを使っているので、そこまで編集した曲SongをMidiSongに変換して渡します。MidiControllerは受け取ったMidiSongからMidiStreamを作成して再生を管理します。MidiStreamと一緒にMidiPlayerに問い合わせることで再生位置を取得します。
テスト音の再生
UI:テスト音のガイド
音で音階を確認できるように音符の作成や移動の時にテスト音を鳴らします。その際に、ピアノとピアノロールで鳴っている音階を光らせます。
あとは音階名も表示させたいですね。
またiOSでは動かす音符が和音の場合、和音で鳴るようになっています。Androidでは短音でしか鳴らないので、和音の鳴り方がいまいちわからないなと思ってました。
ちなみに指ツールで複数音符を同時に動かすときは、スワイプで掴んでいる音符の音が鳴ります。
曲の再生(最初から / 位置指定)
再生ボタン長押しで最初から再生します。
(この時に画面も最初に戻したいですが、まだ未実装です。)
また、スクロールバーをダブルタップでその位置から再生します。(iOS独自)
意外と便利そうなのでこの機能を付けてみましたが、誤動作が気になるところですね。
再生速度の変更
UI:再生速度の表示
再生中に再生ボタンを右にドラッグすると再生速度が早くなります。
こちらもiOS独自の機能ですが、あまり利用用途がないかも?
再生速度の変更は必要ですかあ?
ちなみに左にドラッグすると0.5倍速になります。。。
使う時ないよね〜 (´ㅂ`; )
できそうなのでこの機能を付けてみましたが、こちらも誤動作が気になるところです。
あまり使わなそうだと思ったらリリース前に削除するかもしれないです。(;'-' )
再生処理の最適化
UI:再生ボタン(再生が遅い時)
音符が多くて再生準備に時間がかかる時は再生ボタンにインジケーター(くるくるアイコン)を表示するようにしました。
ちょっとしたことですが、タップできたかどうかがわかりやすいですね。
また、再生後に編集してなければ前回のキャッシュを使うので、2回目以降はすぐに再生できます。
おわりに
MIDIプレイヤーで曲やテスト音を再生する部分について実装しました。
音が出せてようやく作曲アプリ感が出てきましたね✨
次は、使用している楽器で音を変えたり、楽器選択ダイアログあたりについて実装していきたいと思います!