musicLineアプリ開発日記

作曲を誰でも楽しく簡単に♪

【iOS】作曲画面の進捗報告 7

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

曲再生とテスト音の動作


実装状況
カテゴリタイトル補足
View
Composition
UI配置
ガイド表示
再生バー曲を再生している時に、再生位置をバーで表示
再生ボタン曲を再生/停止を監視して、状態を変更、再生準備が遅い時はインジケーターを表示
再生速度の表示曲の再生速度を表示(iOS独自)
テスト音のガイド鳴っている音階に色をつける
Community
UI配置
Composition
MIDI コンバーターCompositionからMIDIのデータ構造へ変換
曲の再生画面左からMIDI再生、Compositionのデータ構造から曲の再生、再生位置の同期
曲の再生(最初から)再生ボタン長押しで最初から再生 最初から再生されるが、画面は最初に移動しない
曲の再生(位置指定)スクロール領域をダブルタップでその位置から再生(iOS独自)
再生速度の変更ドラッグで曲の再生速度を変更(iOS独自)
再生処理の最適化曲に変更がない時はキャッシュを使う、変更通知とキャッシュの管理
MIDI
Common
MIDI共通
データ構造MIDIフォーマットへ出力できる構造
コントローラー曲の読み込みや再生、再生位置の監視や音源の変更などMIDIの扱いを管理する
サンプラーピアノや音符生成時に単体のMidiイベントから音を鳴らす
Service
サービスモデル
MidiPlayerMIDIを再生する
MidiReaderMIDIファイルの読み込み
MidiWriterMIDIファイル出力
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
サービスモデル
MidiPlayerMIDIを再生する
MidiReaderMIDIファイルの読み込み
MidiWriterMIDIファイル出力
Commnity
Common
コミュニティ
共通
データ構造カテゴリ等の曲情報、いいねやお気に入り等のリスポンスを保持
Domain
データ構造
Json形式
Melody MelodyTrack, *MelodyPhrase, NoteContainer, NoteBlock, Note
Drum DrumTrack, *DrumPhrase, BeatContainer, Beat
通化 Original, Repeat, Syncの3種のPhraseをジェネリッククラスとプロトコルで抽象化
Service
サービスモデル
SongRederJsonファイルを読み込み、Domainのデータへ変換
SongWriterDomainのデータからJsonファイルへ書き出し
Common
Service
サービスモデル
リポジトリ保存データを管理


実装の詳細

MIDIのデータ構造

MIDIを再生するためのモデルを作りました。

MIDIのデータ構造

MidiControllerで曲の読み込みや再生を管理します。
再生する際は、まず再生する曲MidiSongchangeSong()メソッドで指定してMidiStreamを作成します。MidiStreamは曲情報や再生状況を保持してます。続いてplay()メソッドによりMidiPlayerMidiStreamを渡すことで再生します。
なお、MIDIファイルから再生したい場合はMidiReaderを使ってファイルをMidiSongモデルに読み込みます。


なぜCompositionとMIDIにモジュールを分ける? 理由は可読性を高めるためです。
実装したコードを後から見直しても分かりやすくするためです。

例えば、musicLineではSongモデルといっても、作曲中やコミュニティ閲覧中なのか、再生するための曲なのかと状況によって曲の概念(曲に付属する情報)が変わります。
もちろんモジュールを一緒にして曲の情報を片っ端から付けまくることはできますが、ある状況では不必要な情報が付いている状態となり可読性が低下します。

つまり状況を混ぜるほど複雑になり、どのメソッド・プロパティを使えばいいかわからなくなる危険性があります。
特に後々の機能拡張や不具合修正を考えると、コードはシンプルな状態にして可読性を高く保つことで効率的な開発ができますね。

この辺りの話に興味がある方はこちらの記事がおすすめ♪
little-hands.hatenablog.com


データ構造の変換(Composition → MIDI)

作曲モデルからMIDIモデルのデータ構造に変換できるように変換モデルSongConverterを作りました。
MIDI再生をするときは、MIDIモデルへ変換して再生します。

作曲とMIDIのデータ構造

SongConverterで作曲モデル(Song Track Phrase Element)をMIDIモデル(MidiSong MidiTrack MidiEvent)に変換します。
モデルを変換してしまえば、あとはMidiControllerMidiSongを渡すだけで再生できるので、わかりやすいですね。
このように事前に扱いやすいモデルに変換すると、モジュール間のアクセスを少なくなる(疎結合)のが良いです。


曲の再生
UI:再生バーと再生ボタン

再生ボタンをタップすると、画面の左位置の時間から曲が再生されます。なお、再生中は再生バーが表示されます。

曲の再生


曲を再生する処理の流れ

曲を編集して再生する流れ

1.曲の編集はCompositionモジュール内で完結しますが、2.曲の再生MIDIモジュールに再生を委託する必要があります。
曲を再生する場合には、MidiControllerMidiSongを渡す必要があります。しかし編集はSongを使っているので、そこまで編集した曲SongMidiSongに変換して渡します。MidiControllerは受け取ったMidiSongからMidiStreamを作成して再生を管理します。MidiStreamと一緒にMidiPlayerに問い合わせることで再生位置を取得します。


テスト音の再生
UI:テスト音のガイド

音で音階を確認できるように音符の作成や移動の時にテスト音を鳴らします。その際に、ピアノとピアノロールで鳴っている音階を光らせます。
あとは音階名も表示させたいですね。

またiOSでは動かす音符が和音の場合、和音で鳴るようになっています。Androidでは短音でしか鳴らないので、和音の鳴り方がいまいちわからないなと思ってました。

テスト音の再生

ちなみに指ツールで複数音符を同時に動かすときは、スワイプで掴んでいる音符の音が鳴ります。


曲の再生(最初から / 位置指定)

再生ボタン長押しで最初から再生します。
(この時に画面も最初に戻したいですが、まだ未実装です。)

また、スクロールバーをダブルタップでその位置から再生します。(iOS独自)
意外と便利そうなのでこの機能を付けてみましたが、誤動作が気になるところですね。

曲の再生(最初から / 位置指定)


再生速度の変更
UI:再生速度の表示

再生中に再生ボタンを右にドラッグすると再生速度が早くなります。
こちらもiOS独自の機能ですが、あまり利用用途がないかも?

再生速度の変更

再生速度の変更は必要ですかあ?
ちなみに左にドラッグすると0.5倍速になります。。。
使う時ないよね〜 (´ㅂ`; )

できそうなのでこの機能を付けてみましたが、こちらも誤動作が気になるところです。
あまり使わなそうだと思ったらリリース前に削除するかもしれないです。(;'-' )


再生処理の最適化
UI:再生ボタン(再生が遅い時)

音符が多くて再生準備に時間がかかる時は再生ボタンにインジケーター(くるくるアイコン)を表示するようにしました。
ちょっとしたことですが、タップできたかどうかがわかりやすいですね。

再生処理の最適化

また、再生後に編集してなければ前回のキャッシュを使うので、2回目以降はすぐに再生できます。



おわりに

MIDIプレイヤーで曲やテスト音を再生する部分について実装しました。
音が出せてようやく作曲アプリ感が出てきましたね✨

次は、使用している楽器で音を変えたり、楽器選択ダイアログあたりについて実装していきたいと思います!