| 1 |
2007.3.28. |
| 2 |
Alchemusica のデバイス管理について。 |
| 3 |
内部的にはある整数値でデバイスを特定するのがよい。ただし、ユーザーからはこの整数値は見えず、あくまでもデバイス名での管理となる。いくつかの問題が存在する。 |
| 4 |
(1) CoreMIDI の UniqueID とこの整数値との対応はどうするか。 |
| 5 |
(2) CoreMIDI のデバイス名は Alchemusica とは関わり無く変更されることがある。そのアップデートをどうするか。 |
| 6 |
(3) 逆に、CoreMIDI で同じ名前を持つデバイスで異なる UniqueID を持つことがあり得る。その対応をどうつけるか。 |
| 7 |
(4) CoreMIDI の与り知らないデバイスが MIDI ファイルで指定されることがあり得る。そのときはどうするか。 |
| 8 |
(5) Alchemusica でユーザーが任意のデバイス名を指定することができる。そのときはどうするか。 |
| 9 |
|
| 10 |
2007.4.1. |
| 11 |
グラフィックウィンドウのオートスクロールについて。 |
| 12 |
GraphicClientView, GraphicRulerView が複数あり、すべての GraphicClientView の横軸と、対応する Client/Ruler の組の縦軸は揃っていなくてはならない。これをどう処理するか? |
| 13 |
(1) 横軸の変更は、次の時に起きる。 |
| 14 |
(a) 編集によってシーケンスの長さが変わった。 |
| 15 |
(b) 横スクロールバーが操作された。 |
| 16 |
(c) どれかの Client 中でマウスドラッグによる横スクロールが起きた。 |
| 17 |
(d) 演奏中にタイムインジケータの位置が画面をはみ出した。 |
| 18 |
(e) ウィンドウの大きさが変更された。 |
| 19 |
(f) 横軸のShrink/expand ボタンが押された。 |
| 20 |
(g) どれかの Client 中でマウスドラッグによる拡大/縮小が起きた。 |
| 21 |
(2) 縦軸の変更は、次の時に起きる。 |
| 22 |
(a) 編集によって値の上限が変わった(普通は起きない)。 |
| 23 |
(b) 縦スクロールバーが操作された。 |
| 24 |
(c) Client または Ruler 中でマウスドラッグによる縦スクロールが起きた。 |
| 25 |
(d) ウィンドウの大きさが変更された。 |
| 26 |
(e) 縦軸のShrink/expand ボタンが押された(未実装)。 |
| 27 |
(f) Client または Ruler 中でマウスドラッグによる拡大/縮小が起きた。 |
| 28 |
|
| 29 |
2007.5.10. |
| 30 |
グラフィックウィンドウでの編集について。実装はきわめて不完全。 |
| 31 |
(1) えんぴつモード |
| 32 |
修飾キーなし |
| 33 |
タイムチャート:選択モードと同じ。 |
| 34 |
ピアノロール:音符を書く。クリックなら決まった長さの音符、ドラッグならその長さの音符。ただし、すでにある音符の上でドラッグを開始したときは、開始位置によって音符の長さ・高さ・位置の変更になる。その音符がすでに選択されているときは、選択されている音符すべてに対して同じ処理が行われる。音符以外で選択されているイベントは変化しない。 |
| 35 |
ストリップチャート:クリックなら1イベントの挿入、ドラッグならサブモードメニューに従ってイベントを挿入または値の変更。ただし、すでに存在するストリップの上(ホットスポット上、または横棒上)でドラッグを開始したときは、そのイベントの位置・値が変更される。そのイベントが選択されているときは、選択されている同種のイベントすべてに対して同じ処理が行われる。処理対象以外の選択イベントは変化しない。 |
| 36 |
シフトキー |
| 37 |
タイムチャート:修飾キーなしと同じ。 |
| 38 |
ピアノロール:修飾キーなしと同じだが、現在の選択を解除しない。音符を書いたときは書いた音符が選択に追加され、ドラッグのときはその音符が現在の選択に追加されて一緒にドラッグの対象になる。音符以外で選択されているイベントは変化しない。 |
| 39 |
ストリップチャート:クリックは何もしない。ドラッグなら選択されたイベントのみ値の変更。サブモードメニューが set なら何もしない。 |
| 40 |
コマンドキー |
| 41 |
すべて:ドラッグで範囲拡大。クリック(シフト+コマンド+クリック?)で直前のズーム状態に戻る。 |
| 42 |
(2) 選択モード |
| 43 |
修飾キーなし |
| 44 |
タイムチャート:その位置または範囲を「編集範囲」にする。現在の選択は解除される。 |
| 45 |
ピアノロール、ストリップチャート:四角で囲まれた範囲に入っているイベントを選択する。選択されたイベントをすべて含む範囲が「編集範囲」になる。ただし、すでに存在するイベントの上でクリック/ドラッグしたときはえんぴつモードと同じ処理を行う。 |
| 46 |
シフトキー |
| 47 |
タイムチャート:(ドラッグ)現在の選択を解除せず、新しく選択された範囲を含めて新たな編集範囲とする。 |
| 48 |
ピアノロール、ストリップチャート:現在の選択に対して、四角で囲まれた範囲のイベントを付け加えて選択する(反転?どちらがいい?)編集範囲は、新たに選択されたイベントすべてを含む範囲になる。 |
| 49 |
コマンドキー |
| 50 |
すべて:えんぴつモードと同じ。 |
| 51 |
(3) アイビームモード |
| 52 |
選択モードと基本的には同じだが、時間軸方向のみマウス位置が認識され、高さ方向は全選択となる。 |
| 53 |
|
| 54 |
なお、編集範囲が変更されたときは、演奏開始位置もその位置に変更される。(未実装) |
| 55 |
選択が変更されたときは、直前の選択を覚えておいて戻せるようにする。(未実装) |
| 56 |
|
| 57 |
2007.5.11. ピアノロールはアイビームモードを除いて一応全部実装した(つもり)。 |
| 58 |
2007.5.12. アイビームモードを実装。編集作業は全部実装したはず。編集範囲の変更関係が未整理。 |
| 59 |
|
| 60 |
選択変更時のイベントリストウィンドウ/グラフィックウィンドウの連携がうまくいっていない。どうすべきか? SelectionChanged と EditingRangeChanged の notification がうまく設計されていないらしい。 |
| 61 |
(1) MyDocument の setSelection 等は、自動的に editing range をセット/変更すべきである。 |
| 62 |
(2) MyDocument の setEditingRange は、setSelection でセットされた editing range に関係なく別の editing range をセットするのに使う。 |
| 63 |
(3) グラフィックウィンドウは描画時に selection を参照するため、再描画すれば自動的に正しい selection が描画される。一方、イベントリストウィンドウは明示的に selection をセットしなければならない。(MyTableView で reloadData をオーバーライドするのは乱暴だろうか? 10.3 以降で使える selectRowIndexes:byExtendingSelection: を使えばそう無理でもないのでは。→いやでも、選択が変更されるごとに selection をセットし直す方がまだよさそう。) |
| 64 |
(4) 選択変更の undo とカップルさせて実装したらどうか。 |
| 65 |
(a) MyDocument に NSMutableArray *selectionStack; int selectionStackPointer; NSMutableDictionary *selectionQueue; の3つのメンバを追加する。 |
| 66 |
(b) selectionQueue には、現在のイベントループの中で選択が変更されたトラックについて、トラック番号をキー、直前の選択を値として格納する。ただし、同じトラックで何度も選択が変更された場合は、最初の変更だけが記録される。 |
| 67 |
(c) 同時に、NotificationQueue に selectionWillChange の Notification が格納され、sender ごとに coalesce される。 |
| 68 |
(d) イベントループが終わったら、selectionWillChange の Notification が(1つだけ)発信される。MyDocument は自分でその Notification をつかまえて、selectionQueue を selectionStack に積んで selectionStackPointer を進める。その後、selectionDidChange の Notification を発信する。イベントリストウィンドウはこの Notification をつかまえて selection をアップデートする。その他のウィンドウも表示をアップデートする。 |
| 69 |
(e) editing range は、selection の変更により暗黙に変更されるときと、明示的に変更されるときとがある。これは selectionWillChange のハンドラ(MyDocument だけにある)中で処理することができる。すなわち、selectionQueue の中に editing range のエントリも入れることにし、明示的に editing range が変更されたときはその時点で古い editing range を selectionQueue に入れる。一方、selection だけが変更されるときは editing range は selectionQueue には入らない。selectionWillChange のハンドラでは、editing range エントリの有無によって処理を振り分けることができる。すなわち、editing range エントリがなければ選択から editing range を計算してセットし、古い editing range を selectionQueue に入れる。editing range エントリがすでにあれば再計算は必要ない。 |
| 70 |
(f) イベント編集が行われたときは、selection の undo スタックはクリアされる。このときは、selectionQueue 中に特殊なキー(@"invalid" など)でエントリを作ることでフラグを立てておき、selectionWillChange のハンドラ中でスタッククリアの処理を行う。ただし、編集にともなって新たな selection がセットされている可能性があるので、editing range の再計算は行う必要があるかも知れない。editing range が明示的に変更されておらず、かつどれかのトラックで selection が残っていれば再計算、それ以外は変更なし、という扱いでよさそう。(ただし、イベントを挿入した時にはそのイベントを選択するか、そのイベントが占める範囲を editing range として設定するようにすべきである。)→ちょっと気が変わった。このときは editing range は再計算しないことにする。 |
| 71 |
|
| 72 |
2007.5.16. だいたい実装した。ただし、編集範囲の undo と、演奏開始位置の変更は未実装。演奏開始位置は無条件に変更しない方がいいかも? タイムチャートビューで option+クリックしたときだけ、などでどうか? |
| 73 |
ちょっと変な挙動をすることがある。例えば、「オスティナート」の最初の数音をタイムチャートで選択した後、音符をシフトクリックして選択範囲を変更すると、10.0.0 まで編集範囲が伸びてしまう。コンダクタートラックと関係しているらしいが、詳細不明。 |
| 74 |
複数トラックにまたがった選択があるとき、イベントリストウィンドウで選択を変更したら、その他のトラックの選択は解除すべきだろうか?(グラフィックウィンドウでも同じで、フォーカストラック以外のトラックの選択は解除すべきか否か?)→グラフィックウィンドウでは、表示されているトラックはフォーカスの有無に関わらず選択を解除するが、表示されていないトラックは解除しない。これは混乱を招きそう。新しい選択をするために古い選択を解除するときは、全トラックについて選択を解除すべきだろう。→ 2007.5.17. 実装した。 |
| 75 |
|
| 76 |
|
| 77 |
2007.5.17. |
| 78 |
コピー・ペーストについて考察。ペーストボードは、カスタムデータを保持する(テキストでもエキスポートできた方が便利だが、これはあとで考える)。NSPasteboard に書き込めるのは NSData, plist, NSString だけなので、選択範囲をシリアライズして NSData に書き出してから NSPasteboard に書き込む。 |
| 79 |
シリアライズは SMF を使おうと思っていたが、ちょっと複雑に過ぎるきらいがある。内部データをそのままシリアライズする方が簡単? (SMF の方がコンパクトではあるが。)とにかく、MDSequenceSerialize.c に MDSequenceSerializeWithSelection(), MDSequenceUnserialize() 関数を作成すること。 |
| 80 |
MDSequence の情報:トラック数、各トラックのコピー元におけるトラック番号。 |
| 81 |
MDTrack の情報:トラック名、start tick, last tick。 |
| 82 |
MDEvent の情報:MDEvent データそのもの、Message があるときはその内容。 |
| 83 |
'MDSQ' (Int32), size (Int64), numTracks (Int32), originalTrackNumbers (Int32 * numTracks); |
| 84 |
'MDTR' (Int32), size (Int64), trackName (Int64), startTick (Int32), lastTick (Int32), numEvents (Int32), events (MDEvent * numEvents), data (arbitrary length); ← これをトラック数だけ繰り返す |
| 85 |
'size' は、'MDxx', 'size' フィールドを含めないブロックサイズ。整数のエンディアンはネイティブエンディアンとするが、'MDxx' はこのままの順。char[4] として実装すればよかろう。 |
| 86 |
data へのポインタは、MDTR ブロックの先頭メンバ (trackName) のアドレスとの差として表す。 |
| 87 |
|
| 88 |
いややっぱり SMF をベースにした方がよいか。コンパクトさと、コードが共用できるメリットは大きい。ただし、同じキーのノートが重なっている時にノートオン/オフの対応が一意的に決まらなくなるのを何とかしないといけない。 |
| 89 |
・書き出す時:2つ目のノートの直前に、特殊なメタイベント (duration) を書く。 |
| 90 |
・読みこむ時:ノートオフと対応していないノートオンは "kMDEventInternalNoteOn" にする。今までの実装では、ノートオフを別トラックにまとめてから MDTrackMatchNoteOff() を呼び出していたが、SMF を読みながらノートオフが出るたびにトラックを後ろ向きにスキャンしてノートオンを探すようにする。別トラックが要らなくなるのでメモリ効率はいいだろう。処理速度も大して変わらないのでは?(ほとんどの場合、ノートオンはノートオフのすぐ近くにある)通常のノートオンは duration = 0 の InternalNoteOn とするが、直前に duration メタイベントがあるノートオンはその duration を保持しておく。ノートオンを探す時、duration != 0 の InternalNoteOn については duration もチェックする。 |
| 91 |
→ 2007.5.22. 一応実装した。ただし、まだ MDTrackMatchNoteOffInTrack()(関数名を変えた)は残っている。また、ノートオフトラックを(必要ないのに)アロケートしているので、このあたりは整理が必要。→2007.5.23. 整理した。 |
| 92 |
|
| 93 |
ペーストボード中のデータの内容を簡単に把握できる仕掛けが必要。メタイベントで押し込むことを考えたのだが、ペーストボードのデータタイプとして MySequencePBoardType, MyCatalogPBoardType の2種類を宣言しておき、MySequencePBoardType にシーケンスデータそのもの、MyCatalogPBoardType にデータ内容の概要を入れておけばよさそう。データ内容の概要は、トラック数(コンダクタートラックを含む)、各トラックのオリジナルトラック番号(コンダクター=0)、コピーしたときの編集範囲(シーケンスの長さではない。ノートオフは編集範囲の終了点より先にある可能性があり、この場合編集範囲の終了点はシーケンスの長さよりも短い。)各トラック中のイベント数、MIDIイベント数、オリジナルのトラック名(32文字程度まで)。← UTF8 として不適切な場所で切ってしまいそうなときどうするか?←NSString の rangeOfComposedCharacterSequenceAtIndex: メソッドを使えばよい。まあ、とりあえず後回しでいいか。 |
| 94 |
|
| 95 |
ペーストの時、ペーストボード中のデータのどのトラックをペースト先のどのトラックに挿入するかが問題。わかりやすい方法はあるだろうか? |
| 96 |
|
| 97 |
(1) ペースト先がイベントリストウィンドウの場合。 |
| 98 |
ペーストボード中のデータはコンダクタートラック以外の単一トラックでなければならない。そうでない場合は、警告を出して、どのトラックのデータをペーストするかを選択させる(複数選択可)。 |
| 99 |
ペースト先がコンダクタートラックの場合は、ペーストボード中にコンダクタートラックが含まれてなければならない。それ以外のトラックは単に無視される。 |
| 100 |
|
| 101 |
(2) ペースト先がグラフィックウィンドウの場合。 |
| 102 |
フォーカストラックの数がペーストボード中のトラック数以上でなければならない。ただし、コンダクタートラックは常にコンダクタートラックにペーストされる。(グラフィックウィンドウに含まれているかどうかには関わらない)。 |
| 103 |
「トラックを選択してペースト」を選ぶと、どのトラックにどのトラックをペーストするかを選ぶことができる。 |
| 104 |
|
| 105 |
2007.5.24. |
| 106 |
そもそも、グラフィックウィンドウをメインウィンドウにした方がいいんじゃないか? 左のトラックリストを少し拡張すれば現在のトラックリストウィンドウの機能を含めることができる。その方が直感的だしすっきりする。ペーストの設計がややこしいのも、グラフィックウィンドウに「含まれる」トラックが可変である点が一つ問題になっている。 |
| 107 |
|
| 108 |
大きな変更になりそうなので、現時点でのスナップショットにタグ tag_20070524 をつけておく。 |
| 109 |
|
| 110 |
2007.5.25. |
| 111 |
ColorCell のサブクラス化で問題発生。インスタンス変数 colorValue を追加してバックグラウンドの色を保持するようにしたところ、セルをクリックした時に BAD ACCESS で落ちるようになってしまった。NSZombieEnabled 環境変数を使ってデバッガで追いかけたところ、colorValue が二重リリースされていることがわかった。原因は、セルをクリックした時に内部で NSCell がコピーされるためで、この時にインスタンス変数が単なるポインタとしてコピーされ、retain カウントが1つ足りない状態になっていたためだった。ColorCell で copyWithZone をオーバーライドして colorValue を retain することで解決。http://developer.apple.com/documentation/Cocoa/Conceptual/ControlCell/Tasks/SubclassingNSCell.html に確かに書いてある。ドキュメントは隅々まで読まんといかんのだなあ。 |
| 112 |
|
| 113 |
2007.5.31. |
| 114 |
グラフィックウィンドウ左半分のトラックリストの設計。 |
| 115 |
(1) 今までのトラックウィンドウの機能を盛り込む。全トラックのリストを表示。Editable, hide, record, mute, solo はそのセルをクリックしてトグルするか、またはトラックを選択してヘッダセルをクリックして変更する。Hide のトラックは文字などがすべてグレー表示になる。 |
| 116 |
(2) Editable と hide はここでよいが、mute, solo は出力デバイス/チャンネル指定と隣合っていた方がよい。(mute と solo は両方必要か? 一方だけでもよいのでは?) |
| 117 |
(3) Record は、録音/再生ウィンドウで指定する方がいい。 |
| 118 |
(4) トラックリストの上のスペースに録音・再生ボタンを配置してもいい。これだと全部が一つのウィンドウにまとまるのですっきりする。 |
| 119 |
(5) 録音ボタンを押すと、シートが降りて来てダイアログモードになる、というのはどうだ? これだと自然に録音中の編集を禁止できるし、録音時の複雑な設定(パンチインかカウントインか、リプレースかオーバーダブか、入力デバイスはどれか、録音先はどれか、など)が1つのダイアログに集中できるのはわかりやすいインターフェイスなのではないか。 |
| 120 |
(6) 1つのドキュメントの録音・再生中は他のドキュメントの録音・再生は禁止した方がよい。今まではプレイウィンドウが1つしかなかったから自然にそうなったが、ドキュメントごとに再生ボタンがつくと二重再生をしないように配慮しないといけない。(複数ドキュメントを同時に再生できたらそれはそれで面白いとも言えるが、滅多に使わない機能のために仕様を複雑化させるのはあまり好ましくなかろう。) |
| 121 |
|
| 122 |
2007.6.10. |
| 123 |
録音ボタンはまだ実装していないが、それ以外はだいたい実装できた。Editable はトラック色のセルにえんぴつアイコンで表示、visible, mute はそれぞれ目のアイコン、スピーカーアイコンで表示。solo 機能はなくした。 |
| 124 |
複数ドキュメントの同時再生は今のところ可能。特に禁止する必要もないような気がしてきた。 |
| 125 |
グラフィックウィンドウがメインウィンドウになった。演奏ウィンドウ、トラックリストウィンドウはもはや不要。ただし、起動時にメニューバーだけの状態になってしまうので、オープンダイアログを出すか、新規ドキュメントを開くかしないといけない。これは未実装。(録音ボタンが先だ。) |
| 126 |
|
| 127 |
2007.7.8. |
| 128 |
録音ボタンを実装。一応オーバーダブ/リプレースも実装した。Wait-for-note はダイアログで選択はできるがまだ機能しない。カウントオフも未実装。Stop tick は実装したが、録音停止後再生を継続することができない。→できるようになった。 |
| 129 |
|
| 130 |
リストウィンドウの動作がいろいろおかしい。 |
| 131 |
(1) イベントを1つ選択してリターンキーを押すと編集が開始できるが、この時に仮挿入されるイベントの tick が選択されたイベントの次のイベントの tick になる。これは混乱を招く。 |
| 132 |
(2) end-of-track の行が選択できない。このため、トラック長をマニュアルで編集できない。また、イベントを1つも含まない新規トラックにイベントを挿入できない。これは致命的。 |
| 133 |
(3) 左右カーソルキーは隣のセルに移動するのではなく、セルの中のテキスト編集に通常通り使用できるべきだろう。 |
| 134 |
(4) リターンキーを押すと自動的に新規イベントが挿入されるのも実はちょっと不便。 |
| 135 |
(5) 上下カーソルキーを使うと上下の行に移動できるのはいいのだが、編集モードから抜けてしまうので不便。 |
| 136 |
(6) メニューまたはコマンドキーショートカットで新規イベントの挿入ができるべき。 |
| 137 |
|
| 138 |
(1): 新規イベントは、選択されたイベントの直後に、そのイベントと同じtickで入る。 |
| 139 |
(2): end-of-track 行の選択を許す。ただし、削除はできず、編集は tick 値の変更のみが許される。end-of-track 行が選択されている時に新規イベントを挿入すると、end-of-track の直前に挿入され、end-of-track の tick 値がそれに従って変更される(通常は +1, ノートイベントが挿入された場合はさらに duration 分プラスされる)。 |
| 140 |
(3): 左右カーソルキーは通常のテキスト編集通りに使える。 |
| 141 |
(4): リターンキーは下のセルへの移動、シフト+リターンは上のセルへの移動。コマンド+リターンで新規イベントの挿入。エンターキーで編集モードから抜ける。 |
| 142 |
(5): 上下カーソルキーは、テキストの先頭/末尾への移動。 |
| 143 |
(6): コマンド+I で、現在の編集tickに新規イベントが挿入される。 |
| 144 |
(7): イベントの tick を変更した結果イベントの場所が移動したとき、そのイベントの直後に「未定義イベント」が同一ティックで並んでいるときはそれらのイベントも一緒に移動する。また、tick 確定の操作と同時に上下のセルへ移動した場合、移動前の位置の上下に移動するものとする。 |
| 145 |
|
| 146 |
コマンド+リターンがうまく実装できなかったが、それ以外は一応実装した。しかし、リターンで下のセル、シフト+リターンで上のセルへの移動という仕様は微妙だな。 |
| 147 |
|
| 148 |
2007.7.12. |
| 149 |
グラフィックウィンドウで、myPlayingView, myMainView, myTableView のいずれかにフォーカスリングが書かれるようにした。フォーカスリングが書かれているときはその view が firstResponder。copy/cut/paste メニューコマンドが tableView と mainView とでは違う動作をする予定なので、それを対応付けやすくする。 |
| 150 |
|
| 151 |
2007.7.13. |
| 152 |
さあいよいよコピー/ペーストの実装だ。 |
| 153 |
【コピー】 |
| 154 |
(0) すべてのウィンドウで、現在の編集範囲がペーストボードに記録される。 |
| 155 |
(1) リストウィンドウでは、そのトラックの選択イベントをコピー。 |
| 156 |
(2) グラフィックウィンドウのトラックリストでは、選択されたトラックの全イベントをコピー。 |
| 157 |
(3) グラフィックウィンドウのグラフビューでは、すべてのトラックの選択されたイベントをコピー。 |
| 158 |
(見えないトラックも含む) |
| 159 |
【ペースト】 |
| 160 |
(0) すべてのウィンドウで、ペーストボードの編集範囲の先頭が「現在の編集範囲」の先頭になるようにイベントが移動される。普通のペーストの場合は、ペーストボード中の編集範囲に含まれるイベントが削除される。マージの場合は削除されない。コンダクタートラック専用のイベント(テンポ、拍子、SMPTE、マーカー)は自動的にコンダクタートラックに移動される。また、コンダクタートラックにMIDIイベントを挿入しようとすると警告が出され、ペーストは行われない。 |
| 161 |
(1) リストウィンドウでは、ペーストボードの内容が単一トラックである場合に限りペーストできる。ペーストボードの内容が複数トラックの場合は警告ダイアログが出され、ペーストは行われない。 |
| 162 |
(2) トラックリストでは、トラックが選択されている場合は選択トラックとペーストボード中のトラックを先頭から対応させてペーストが行われる。ペーストボード中のトラックが余る場合は新しいトラックが作成されて、そこにペーストされる。ペーストボード中のトラックが足りない場合は特に警告はされない。(3) グラフィックビューでは、編集可能なトラックとペーストボード中のトラックを先頭から対応させてペーストが行われる。それ以外の仕様は (2) と同じ。 |
| 163 |
|
| 164 |
MySequencePBoardType: NSMutableData にコピーするイベントを SMF として書き出す。 |
| 165 |
MySeqCatalogPBoardType: 以下のデータをバイナリで書き込む。 |
| 166 |
ヘッダ:ヘッダ長 (4 bytes, ヘッダ長自身は含めない), トラック数 (4 bytes), 編集範囲 (4 bytes, 4 bytes) |
| 167 |
各トラック:データ長 (4 bytes, データ長自身は含めない), オリジナルトラック番号 (4 bytes), オリジナルトラック名 (64 bytes), イベント数 (4 bytes), MIDIイベント数 (4 bytes) |
| 168 |
これをトラック数ぶんだけ繰り返す。 |
| 169 |
MDCatalog, MDCatalogTrack : 上のデータ構造に対応する typedef。MDSequence.h で定義。 |
| 170 |
|
| 171 |
2007.7.15. |
| 172 |
一応実装できた。0.5d1 として公開。 |
| 173 |
|
| 174 |
2007.7.15. |
| 175 |
グラフィックウィンドウで、シフト+クリック(ドラッグ)などの動作について。選択モードのとき、シフト+ドラッグは基本的には「現在の選択に新しい選択範囲を加える」という動作をするのだが、Mac OS X の動作としてはこれはコマンド+クリック(ドラッグ)の動作に近く、シフト+ドラッグ(クリック)は「現在の選択と新たなマウス位置で定義される範囲内にあるものをすべて選択する」という動作の方が好ましいのではないか。実は、トラック中の「ほとんど全部のイベント」を選択しようと思って、タイムチャートビューで最初の方をクリックし、最後の方にスクロールしてシフト+クリックしたのだが、何も選択されなかった。 |
| 176 |
これを実装しようと思うと、「現在の選択と新たなマウス位置で定義される範囲」をどう定義するかが大問題となる。ただ、選択範囲を矩形に限定してしまえば、それほど難しくはない。最初のマウスダウンでシフトキーが押されていれば、「その時点での選択範囲」を表す矩形を計算し、その矩形と現在のマウスカーソルの位置から選択すべき範囲の矩形を計算して、その内部を選択範囲とすればよい。これは不可能ではないな。 |
| 177 |
しかし、やはり理解しにくい仕様になりそうだったので、シフト+ドラッグの仕様は現状のままとし、タイムチャートビューでコマンド+ドラッグしたときだけ途中のイベントも一挙に選択するようにした。この方が実用的でわかりやすい仕様だと思う。 |
| 178 |
→実際に使ってみるとどうしても「シフト+クリック」して「あれ、途中は選択されないの? そうか、コマンド+クリックだっけ。」と気づいてやり直すことになってしまう。これはやっぱり改善した方がいいな。あと、矩形選択をしたとき、縦軸方向の選択範囲を rulerView に表示した方がいいかも知れない。 |
| 179 |
|
| 180 |
2007.7.16. |
| 181 |
グラフィックウィンドウでの入力クオンタイズを実装。 |
| 182 |
|
| 183 |
2007.7.16. |
| 184 |
ピアノロールビューでのコピーは「編集中トラックの選択イベント」に限定することにした。これで、常に「コピーされたトラック数」=「編集中トラックの数」となる。複数トラックの選択はわかりにくい操作になりがちなので、どのトラックからイベントがコピーされるかが明示的になったのは良い。 |
| 185 |
|
| 186 |
2007.7.16. |
| 187 |
テーブルビューでの選択変更の動作が鈍いのが気になっている。reloadClientViews を必要以上に呼び出しているようだ。reloadClientViews は非常に重い処理なので、MainView がリドローされる直前に一度だけ呼び出すようにすべき。→あちこちに影響が出そうな変更なので慎重に。→ 2008.1.1. 一応修正した。大部分の reloadClientViews を setNeedsReloadClientViews に変更し、setNeedsReloadClientViews では NSNotificationQueue に notification を NSPostWhenIdle モードでポストするようにした。これで、アイドル状態になった時に一度だけ reloadClientViews が実行される。 |
| 188 |
|
| 189 |
2008.1.6. |
| 190 |
実験的にオーディオ録音を実装してみる。CoreAudio をだいぶ勉強しないといかんな。 |
| 191 |
/Developer/Examples/CoreAudio/Services/AudioFileTools/AudioFileTools.xcodeproj をまず読んでみる。 |
| 192 |
録音は CAAudioFileRecorder.cpp が主にやっている。 |
| 193 |
|
| 194 |
CAAudioFileRecorder::CAAudioFileRecorder() では次の処理をやっている。 |
| 195 |
・FindNextComponent() で、type = kAudioUnitType_Output, subType = kAudioUnitSubType_HALOutput, manufacture = kAudioUnitManufacturer_Apple である Component を見つける。 |
| 196 |
・OpenAComponent() でその Component を開いて mInputUnit に格納。mInputUnit は AudioUnit 型の変数 (AudioUnit * ではないので注意)。 |
| 197 |
・AudioUnitSetProperty() で output を disable, input を enable にする。 |
| 198 |
・デフォルト入力デバイスを取得して、mInputDevice に kAudioOutputUnitProperty_CurrentDevice としてセットする。(ここがよくわからない?)mInputDevice は AudioDeviceID 型。 |
| 199 |
・コールバック関数をセットする。ここではCAAudioFileRecorder::InputProc()。 |
| 200 |
・ハードウェアフォーマットを取得する。CAStreamBasicDescription 型だが、これは AudioStreamBasicDescription のラッパークラス。 |
| 201 |
|
| 202 |
CAAudioFileRecorder::SetFile() |
| 203 |
・ディレクトリ名 (parentFSRef), ファイル名 (filename), ファイルタイプ (AudioFileTypeID filetype), フォーマット (CAStreamBasicDescription), レイアウト (CAAudioChannelLayout) を指定して呼び出す。CAAudioFileWriter::SetFile() でこれらのパラメータを設定するが、IO フォーマットは1フレームのチャンネル数とサンプルレート以外は "canonical" な設定に変更される。(CAStreamBasicDescription::SetCanonical()) 具体的には、non-interleave, linear PCM, 32 bit float など。 |
| 204 |
.... |
| 205 |
|
| 206 |
ちょっと複雑すぎて挫折。RecordAudioToFile の方が簡単なので、こちらから攻めよう。 |
| 207 |
|
| 208 |
録音は main.cpp の StartRecording() で始まる。 |
| 209 |
|
| 210 |
・DCAudioFileRecorder 型の新しいインスタンスを作り、DCAudioFileRecorder::Configure() で初期設定、Start() で録音開始、Stop() で終了。 |
| 211 |
|
| 212 |
・DCAudioFileRecorder::Configure(const FSRef inParentDirectory, const CFStringRef inFileName, AudioStreamBasicDescription *inASBD) |
| 213 |
ConfigureAU() で AudioUnit の初期化、ConfigureOutputFile() で AudioFile の初期化。 |
| 214 |
|
| 215 |
・ConfigureAU() |
| 216 |
|
| 217 |
- AudioOutputUnit を取得。FindNextComponent, OpenComponent を使い、type = output, subtype = HALOutput, manufacturer = Apple の Autio Unit を取得。 |
| 218 |
- AudioOutputUnitのinput を有効、output を無効にする。 |
| 219 |
- AudioHardwareGetProperty でデフォルト入力デバイスを取得。 |
| 220 |
- AU にデフォルト入力デバイスを設定。 |
| 221 |
- コールバック関数を設定。 |
| 222 |
- ハードウェアデバイスフォーマットを取得 |
| 223 |
- フォーマットを修正(エンディアン依存なので注意!) |
| 224 |
- AU の出力フォーマットを指定 |
| 225 |
- AU の I/O バッファのサンプル数を取得 |
| 226 |
- AU を初期化 |
| 227 |
- 自分のオーディオバッファをアロケート (AudioBufferList 型) |
| 228 |
|
| 229 |
・ConfigureOutputFile() |
| 230 |
ExtAudioFile... という関数を使って、ファイルの作成・フォーマット指定・mono->stereo変換・非同期書き込みの初期化を行っている。 |
| 231 |
|
| 232 |
・AudioInputProc() |
| 233 |
AudioUnit のコールバック関数。AudioUnitRender() でオーディオデータを取得して、ExtAudioFileWriteAsync() で書き込んでいる。自前でスレッド管理をやらなくていいので楽! |
| 234 |
|
| 235 |
さらに CAPlayThrough を解析。これは入力と出力のデバイスをそれぞれポップアップリストで表示するが、AudioHardwareGetPropertyInfo/AudioHardwareGetPropertyを使っている。実験してみると、これで Jack Router もちゃんと出てくる。 |
| 236 |
|
| 237 |
Alchemusica での録音機能の実装案。 |
| 238 |
(0) メニューに "Record Audio..." を作る。本当はオーディオトラックに時系列でオーディオイベントを並べるようにすべきだが、とりあえず実験ということで。 |
| 239 |
(1) ファイルが Alchemusica project として保存されていなければ、保存するように指示して終了。プロジェクトディレクトリ中の "Audio" ディレクトリに aiff 形式で保存する。ファイル名は、「録音開始ティック-通し番号.aiff」。 |
| 240 |
(2) ダイアログを出して、オーディオ入力とスルー出力先を指定させる。このとき、ポップアップメニューは CAPlayThrough のコードを参考にする。 |
| 241 |
(3) "Start Recording" ボタンが押されたら、AudioUnit, AudioFile を初期化して、MIDI演奏を開始し、録音を開始する。 |
| 242 |
|
| 243 |
MDAudio_MacOSX.c に録音関係のコードを書いた。(2008.1.6.) |
| 244 |
|
| 245 |
プロジェクトディレクトリ中にファイルを保存すると、プロジェクトを保存した時に上書きされて消えてしまうことに気がついた。録音ダイアログを改良して、ファイルを指定するようにした。(2008.2.13.) |
| 246 |
|
| 247 |
2008.2.20. |
| 248 |
リストウィンドウの使い心地がイマイチ。もう少しなんとかしたい。問題点は: |
| 249 |
(1) 任意のティック位置にイベントを挿入できない。グラフィックウィンドウなどで指定した「現在の編集位置」に挿入できるようにすべき。 |
| 250 |
(2) ティックを編集するとイベントの行位置が変わってしまうのが(やむを得ないとは言え)使いにくい。 |
| 251 |
(3) やっぱり最初に挿入するのは「空」イベントでいいんじゃないか? そのかわり、既存のイベントを複製する "Duplicate" コマンドや、"Insert multiple events..." コマンド(これはダイアログで細かい指定ができるようにする)を用意すれば便利じゃないか。 |
| 252 |
(4) MidiPolish 伝統の「同時イベントをずらす」機能と似ているが、選択されたイベントを指定されたティック間隔で置き直す(他のイベントは動かさない)というコマンドがあってもよさそう。 |
| 253 |
|
| 254 |
2008.2.27. |
| 255 |
新規イベントの挿入を実装し直し。 |
| 256 |
(1) イベントが選択されている時は、そのイベントと同じティック値の新しいイベントを選択されている行の直後に挿入し、そのイベントを編集するモードに入る。ただし、選択されているのが end-of-track の時は、そのティック値のイベントを end-of-track の直前に挿入し、編集モードに入る。 |
| 257 |
(2) イベントが選択されていない時は、「現在の編集位置」の開始ティックに新規イベントを作成し、そのイベントを編集するモードに入る(同じティック位置にイベントが存在する時は、その後ろに挿入される)。現在の編集位置が指定されていない時は、トラックの先頭にティック=0で新規イベントを挿入する。これは「編集位置=1.1.0」と指定した時とは動作が異なる。ティック=0にイベントが存在しているとき、編集位置無指定ならその前に新規イベントが挿入され、編集位置=1.1.0と指定すると既存イベントの後に新規イベントが挿入される。 |
| 258 |
|
| 259 |
2008.2.28. |
| 260 |
イベントの選択表示を実装するために準備中。通常の表示についてはすでに選択表示を考慮した実装がしてあるが、編集時が問題。 |
| 261 |
(1) 新規にイベントを作るとき、現在は「直前に挿入したイベント」のコピーが作られるが、これが非表示のイベントの場合は空イベントを作る。 |
| 262 |
(2) これで、編集を開始した時そのイベントは常に見えているが、kind/code を変更すると非表示のイベントに変わる可能性がある。この時は警告ダイアログを出し、ユーザーが「非表示でもOK」と了解した場合は、そのイベントを確定して非表示にし、あたかも同じ行の同じカラム位置のセルで編集が完了したかのように作業を続行する。 |
| 263 |
(3) ついでに、ティックが変更されて編集中のイベントの行位置が変化したときの動作もきちんと定義する。水平方向に別のセルに移動する時は、同じイベント内で編集を続ける。垂直方向にも移動する時は、行位置が変化した後同じセルの編集状態に一度とどまって、次のアクションで別のセルに移るのがよいのでは? 二度手間になるかな。→いや、垂直方向にも移動する時は、「イベントの位置が変化しなかった場合に移動していたであろうセル」に移動する、という(現在の)仕様でよいみたい。 |
| 264 |
|
| 265 |
2008.3.1. |
| 266 |
イベントの選択条件をどう実装するかだが、イベント編集ウィンドウと同じようなウィンドウを作って、そこでイベントタイプを並べるという考えで進めている。そうなると、イベントタイプの編集はイベントウィンドウと共通のコードが使えた方がよいので、EventKindTextFieldCell というクラスを作って ListWindowController からコードを分離している。ちょっと面倒なのは: |
| 267 |
(1) nib 中で NSTableView の dataCell として EventKindTextFieldCell クラスを指定したとき、どのタイミングでどのイニシャライザが呼ばれるのかよくわからない。EventKindTextFieldCell クラス中で、独立した nib からコンテキストメニューを読み込みたいのだが、どこに書いたらいいのかわからない。仕方がないので、menu メソッドを上書きし、まだ nib を読み込んでいなければそこで読むことにした。あまり美しくないが仕方がない。 |
| 268 |
(2) dataCell に menu を設定しただけでは NSTableView からコンテキストメニューが現れない。NSTableView の menuForEvent: をオーバーライドしないといけない。 |
| 269 |
(3) メニューコマンドのアクションは EventKindTextFieldCell クラスで実装できるが、値を setStringValue: などで設定しても NSTableView に伝わらない。cell の controlView メソッドを使って自分が NSTableView の中にいるかどうか調べ、もしそうなら dataSource の setObjectValue 系メソッドを呼ぶ必要がある。このとき、行・カラム位置の情報を cell は持っていないので、NSCell の menuForEvent:inRect:ofView メソッドをオーバーライドしてマウスボタンが押された位置を覚えておかないといけない。 |
| 270 |
(4) コンテキストメニューを出す際に、その行を選択する必要がある。これは NSTableView の menuForEvent: 中で実装すれば良いようだ(未処理)。 |
| 271 |
|
| 272 |
2008.3.4. |
| 273 |
イベントの選択条件だが、やはり専用のダイアログを整備した方が使いやすそうなので、そうした。EventFilterPanel で、Cocoa binding を使っている。現在、メタイベントとコントロールイベントはテーブルビューに全部のエントリがあって、必要なものをチェックするようになっているが、これはかなり使いにくい。"+", "-" で必要なものだけ並べる方がたぶん使いやすいだろう。 |
| 274 |
|
| 275 |
2008.3.8. |
| 276 |
一応 0.5d3 として公開。一時、オーディオ録音を実装した段階で 0.6 に上げようかと思っていたのだが、以前に書いたロードマップを見ると「ドキュメントを書く」とか恐ろしいことが書いてあって、とても現状で 0.6 として出すのは無理なので、0.5d3 とした。 |
| 277 |
ドキュメントもそうだが、いろいろ細かい編集機能をもう少し充実させたい。実は、そろそろマクロ機能を実装したいなと思ってたりして… |
| 278 |
|
| 279 |
マクロ機能だが、どの言語にするかなあ。SONAR の Cal は lisp ベースのようで(昔の cakewalk のマクロはスタックベースだったな)、まあこういうのも嫌いではないのだが、いわゆるオブジェクト指向と組み合わせようとすると、ちょっと記述方法がごちゃごちゃしてくる。あとカッコがあまり多いのはめんどくさい。Python も言語としてはいいのだが、インデントに依存しているのがめんどくさい。Ruby は組み込みが面倒。やっぱり自前で作らないとしょうがないのかなあ… |
| 280 |
|
| 281 |
MacRuby (http://trac.macosforge.org/projects/ruby/wiki/MacRuby) って面白そう。オブジェクトの実装は Objective-C に任せて、インタプリタ部は Ruby 1.9 に準拠している。このアプローチなら、それほど難しくなく新しい言語が実装できるんじゃないか? |
| 282 |
|
| 283 |
問題になるのは、ランタイムでメソッドを探すこと、および定義すること。-> Objective C のランタイム関数はたくさん公開されているから、なんとかなるんじゃないか。 |
| 284 |
|
| 285 |
2008.3.20. |
| 286 |
いろいろ調べてみたところ、Ruby の組み込みは不可能ではないみたい。「あまり組み込みには向いていない」とあちこちで書かれてはいるが、方法はある。やってみようか? |
| 287 |
|
| 288 |
できあがりのイメージ。たとえば、expression を volume に差し替えるなら、こんな風に書ければよいだろう。 |
| 289 |
|
| 290 |
selectedTracks.each { |n| |
| 291 |
ptr = pointerForTrack(n) |
| 292 |
while (ptr.nextInSelection) |
| 293 |
if ptr.kind == :control && ptr.code == 11 |
| 294 |
ptr.code = 7 |
| 295 |
end |
| 296 |
end |
| 297 |
} |
| 298 |
|
| 299 |
VALUE rb_eval_string_wrap(const char *str, int *state) という関数を使ってスクリプトを実行すれば、無名モジュール中で実行されるのでネームスペースを汚染しない。→まあそれはいいんだけど、上の例だと ptr とかはそのまま残ってしまうのかな? 終わった後ローカル変数がクリアされると嬉しいのだが。細かいところだからとりあえずはいいか。 |
| 300 |
でも本当は、「現在のドキュメント」を self として、selectedTracks などは "self.selectedTracks" と解釈されるべきものだ。もちろんこんな風にすれば解決はするが: |
| 301 |
def newmacro |
| 302 |
selectedTacks.each { |n| |
| 303 |
... |
| 304 |
} |
| 305 |
end |
| 306 |
いちいち名前をつけないといけないのも微妙に面倒くさい。 |
| 307 |
|
| 308 |
instance_eval を使えばいいんだ。「現在のドキュメント」を doc とすると: |
| 309 |
doc.instance_eval(" |
| 310 |
selectedTracks.each { |n| |
| 311 |
... |
| 312 |
} |
| 313 |
") |
| 314 |
で doc = self のコンテキストで実行できる。 |
| 315 |
|
| 316 |
メニューコマンドに現れるプラグイン的なスクリプトと、使い捨てにするマクロを別扱いにするか。 |
| 317 |
プラグインスクリプトは、暗黙の "class MRDocument ... end" の中で実行される。マクロは、暗黙の "def MRDocument.XXXX ... end" の中でいったんメソッドとして定義され、"document.XXXX" で実行される。終了後、メソッド XXXX はすぐに削除される。←いや、直前のマクロが保持されていても別に構わない、だが使い道はないと思われる。 |
| 318 |
|
| 319 |
書き方が違うのもちょっと混乱するな。すべてメソッド定義の形にして、「メニューコマンド名」とメソッド名を関係づけるようにしたらどうか? |
| 320 |
|
| 321 |
書き方(プラグインもドキュメントごとのマクロも同じ): |
| 322 |
def hoge |
| 323 |
... |
| 324 |
end |
| 325 |
register_menu("menu command name", :hoge) |
| 326 |
|
| 327 |
プラグインは "class MRDocument ... end" の中で実行される。一方、ドキュメント付属のマクロは "doc.instance_eval" の中で実行される。後者では、def は特異メソッドの定義として働くはず。 |
| 328 |
もしかして、前者は "MRDocument.instance_eval" で実行しても同じ結果になる? |
| 329 |
|
| 330 |
ちなみに、register_menu は、MRDocument のクラスメソッドと通常のメソッドの両方として定義しておく。クラスメソッドの方は「すべてのドキュメントに対して適応可能なコマンドを登録」、通常メソッドの方は「そのドキュメントのみに対して適応可能なコマンドを登録」という違いがある。 |
| 331 |
|
| 332 |
メニューが選択されたら、MRDocument のインスタンスに対してそのメニューコマンドに対応するメソッド名を問い合わせ、見つかればそのメソッドを呼び出す。 |
| 333 |
|
| 334 |
MRDocument のインスタンスを MyDocument 内に保持する必要があるな(特異メソッドはインスタンスに保持されるため)。これはちょっと嫌らしい感じ。MRDocument のクラス変数として「全ドキュメントを含む配列」を1つ定義しておいたらどうだろう。MRDocument <-> MyDocument の相互変換はそんなにしょっちゅうは起きないはずだからこれでも効率的には十分。それに、MyDocument のコンストラクタで MRDocument オブジェクトを作り、dealloc で解放するようにすればいいのでは。これなら MyDocument の内部に MRDocument へのポインタをキャッシュしておいても特に問題にはならないし。 |
| 335 |
MRDocument が MyDocument を retain していると、MRDocument を破棄しない限り MyDocument は決して release されないことになる。これはちょっと問題だな。Ruby オブジェクトは永久に生き残ってしまう可能性があるから、Ruby オブジェクトから Cocoa オブジェクトを retain するのはなるべく避けないといけない。 |
| 336 |
MRDocument は MyDocument と1:1で対応し、同じ MyDocument に対しては常に同じ MRDocument が使われる。このため、MyDocument のコンストラクタ中で新しい MRDocument オブジェクトを作成して、大域変数 $mr_documents が指す配列に格納する。MyDocument の dealloc では、このオブジェクトが指すポインタを NULL に差し替えて、$mr_documents から除く。 |
| 337 |
|
| 338 |
|
| 339 |
C で実装すべきメソッド一覧。 |
| 340 |
クラス MRDocument |
| 341 |
document(name) すでに開いている name という名前のドキュメントを取得する。name はファイル名であっても構わない。 |
| 342 |
name ドキュメントの名前を取得する。 |
| 343 |
new 新しいドキュメントを作る。 |
| 344 |
open(fname) ファイル名 fname をドキュメントとして開く。 |
| 345 |
save ドキュメントを保存する。 |
| 346 |
save_as(fname, flag) ドキュメントを別名で保存する。flag が true なら、上書きを確認するダイアログを出さない。 |
| 347 |
revert(flag) ドキュメントを最後に保存された状態に戻す。flag が true なら、確認ダイアログを出さない。 |
| 348 |
track(n) n 番目のトラック(n = 0 はコンダクタートラック)を得る。 |
| 349 |
insert_track(track, n) トラック track を n 番目のトラックとして挿入する。 |
| 350 |
delete_track(n) n 番目のトラックを削除する。 |
| 351 |
tick_to_time(tick) tick を絶対時間(秒単位の Float)に変換する。 |
| 352 |
time_to_tick(time) 絶対時間を tick に変換する。 |
| 353 |
tick_to_measure(tick) tick を bar/beat/subtick に変換する。bar/beat は 1 から、subtick は 0 から始まる。3つの整数の配列を返す。 |
| 354 |
measure_to_tick(ary) bar/beat/subtick を tick に変換する。ary は bar, beat, subtick の配列。 |
| 355 |
duration シーケンスの長さを tick 単位で返す。 |
| 356 |
|
| 357 |
クラス MRTrack |
| 358 |
MRTrack は MDTrack へのポインタを保持するが、同時に MyDocument へのポインタも保持する。MyDocument では、registerDocumentTrackInfo/unregisterDocumentTrackInfo クラスメソッドにより、MyDocument/MDTrack/trackNumber の対応付けを管理している。これらのメソッドを用いて MyDocumentTrackInfo 構造体へのポインタを登録しておくと、MyDocument のメソッドでトラックの挿入/削除を行う限り、MyDocument/MDTrack/trackNumber の対応付けは必ず正しくなるように保証されている。 |
| 359 |
MyDocument が nil でない時には、MyDocument の変更を伴う MRTrack の操作は原則的には MyDocument を通して行う。 |
| 360 |
MRTrack は MDTrack を retain し、GC で回収された時に release する。 |
| 361 |
|
| 362 |
initialize(doc, n) MRDocument doc のトラック n に対応する MRTrack オブジェクトを返す。doc == nil (または doc を省略)ならば、新規トラックが作成される。 |
| 363 |
pointer このトラックを指す MRPointer オブジェクトを返す。 |
| 364 |
count トラックに含まれるイベントの総数を返す。 |
| 365 |
count_midi トラックに含まれる MIDI イベントの数。 |
| 366 |
count_sysex トラックに含まれる sysex イベントの数。 |
| 367 |
count_meta トラックに含まれるメタイベントの数。 |
| 368 |
duration トラックのデュレーションを tick 単位で返す。 |
| 369 |
duration= トラックのデュレーションを変更する。既存のイベントの最大ティック値よりも大きな値を指定しなければエラーになる。 |
| 370 |
channel トラックの MIDI 出力チャンネル。 |
| 371 |
channel= トラックの MIDI 出力チャンネルを変更する (1-16 の整数値) |
| 372 |
device トラックの出力デバイス名。 |
| 373 |
device= トラックの出力デバイス名を変更する。存在しないデバイス名を指定しても構わない。 |
| 374 |
name トラック名。 |
| 375 |
name= トラック名を変更する。 |
| 376 |
|
| 377 |
クラス MRPointer |
| 378 |
MRPointer は MDPointer へのポインタを保持するが、同時に MyDocument, MDTrack へのポインタも保持する。(MDPointer 自体 MDTrack へのポインタを持っているので、二重に持つことになる)。MDPointer は retain するが、MyDocument, MDTrack は retain せず、参照するのみ。 |
| 379 |
MyDocument が nil でない時には、MyDocument の変更を伴う MRPointer への操作は原則的には MyDocument を通して行う。 |
| 380 |
|
| 381 |
initialize(doc, n) MRDocument doc のトラック n 中のイベントを指すポインタを作る。doc が nil か、n が負ならば、どのドキュメントにも属しないダミートラックを新たに作成して、それに対するポインタを作る。 |
| 382 |
initialize_copy(ptr) MRPointer ptr と同じトラック・同じ場所を指す新しいポインタを作る。 |
| 383 |
position ポインタの指す位置。-1 は「先頭より前」を指す。 |
| 384 |
position= ポインタを移動させる。代入式の右辺の値を返す (Ruby の仕様) |
| 385 |
move_to ポインタを移動させる。イベントが存在しない場所に移動した時は false, それ以外は true を返す。 |
| 386 |
move_by ポインタを現在の位置からの相対値で移動させる。イベントが存在しない場所に移動した時は false, それ以外は true を返す。 |
| 387 |
count 指しているトラックが含むイベント数。 |
| 388 |
top ポインタを「先頭より前」に移動させる。position=(-1) と同じ。 |
| 389 |
bottom ポインタを「末尾の次」に移動させる。potision=(count) と同じ。 |
| 390 |
next 次の位置に移動させる。トラックの「末尾の次」に達したら偽を、それ以外は真を返す。 |
| 391 |
last 前の位置に移動させる。トラックの「先頭の前」に達したら偽を、それ以外は真を返す。 |
| 392 |
next_in_selection 選択範囲が有効であるなら、選択されている次の位置に移動させる。対応するイベントがなければ現在の位置にとどまり、偽を返す。それ以外は真を返す。 |
| 393 |
last_in_selection 選択範囲が有効であるなら、選択されている前の位置に移動させる。対応するイベントがなければ現在の位置にとどまり、偽を返す。それ以外は真を返す。 |
| 394 |
jump_to_tick(tick) tick より小さくないティック値を持つ最初のイベントの位置に移動する。そのようなイベントが存在しなければ「末尾の次」に移動し、偽を返す |
| 395 |
kind イベントの種類。以下のシンボルで表す。:null, :meta, :tempo, :time_signature, :key, :smpte, :port, :text, :message, :program, :note, :control, :pitch_bend, :channel_pressure, :key_pressure, :sysex, :sysex_cont |
| 396 |
code イベントのコード |
| 397 |
data イベントのデータ。:text は文字列、:sysex, :sysex_cont, :smpte, :time_signature は数値の配列。ptr->data[n] = m という代入を許すかどうかは要検討。 |
| 398 |
duration ノートイベントのデュレーション。 |
| 399 |
on_velocity ノートオンヴェロシティ。 |
| 400 |
off_velocity ノートオフヴェロシティ。 |
| 401 |
|
| 402 |
2008.4.6. |
| 403 |
上記コマンドの一部を実装して、一応下のようなスクリプトが動作するようにはなった。 |
| 404 |
|
| 405 |
# Raise all notes of track 1 by one half-tone |
| 406 |
pt = pointer(1) |
| 407 |
while pt.next |
| 408 |
puts "position = #{pt.position}, kind = #{pt.kind}, code = #{pt.code}" |
| 409 |
if pt.kind == :note |
| 410 |
pt.code += 1 |
| 411 |
end |
| 412 |
end |
| 413 |
|
| 414 |
アンドゥも動作するようにしたのだが、pt.code = X で1つのイベントを変更するたびに MyDocument に変更メッセージを送っていたのでは、アンドゥバッファがえらいことになってしまう。スクリプトを書くほどの処理なのだから、数万個のイベントを一気に変更しようとする可能性もある。せめて、同種の変更はまとめて modifyCodes:ofMultipleEventsAt:inTrack:mode: などで処理すべきだろう。かなりコーディングは面倒だが、MRPointer 中に変更処理のキャッシュを保持し、同じ処理が続く間はキャッシュにためて、あるタイミングで MyDocument にメッセージを送るようにしてみた。一応コンパイルは通って動作はしているようだが、キャッシングが正しく働いているかどうかは未確認。← code と data については確認した。(2008.4.7.) |
| 415 |
|
| 416 |
tick の変更については、modifyTick:ofMultipleEventsAt:inTrack:mode:destinationPositions: は変更する tick が昇順になっていることを要求するのでちょっと厄介。→ MDTrackChangeTick() を修正してこの制限を取り払った。→ イベントの順序が変わったとき undo がうまくいかない。destinationPositions を MDPointSetObject で与えているからいけないので、これは position の配列でないといけない。要修正。結構大変そう。 |
| 417 |
(1) ターゲットのイベントを別トラックに抽出して tick を変更する。 |
| 418 |
(2) destinationPosition == nil の時は: |
| 419 |
(2-1) new2old[] という int の配列を用意し、0..count-1 の整数を入れる(count はターゲットイベントの数)。これを tick の昇順になるようソートする。 |
| 420 |
(2-2) old2new[] という配列を用意し、old2new[new2old[i]] = i となるようにする。 |
| 421 |
(2-3) i = 0..count-1 に対して、ターゲットの new2old[i] 番目の位置を求める。これが undo 時の destinationPositions になる。 |
| 422 |
(2-3) i = 0..count-1 に対して、new2old[i] 番目の古い tick 値を求める。これが undo 時の tick の配列になる。 |
| 423 |
(2-4) new2old[] を利用してイベントの配列をソートする。 |
| 424 |
(2-5) 別トラックをもとのトラックにマージする。マージした位置を示す MDPointSet が undo 時のターゲットイベントを指定する。 |
| 425 |
(3) destinationPosition != nil の時は: |
| 426 |
(3-1) new2old[] という int の配列を用意し、0..count-1 の整数を入れる。これを destinationPosition の昇順になるようにソートする。 |
| 427 |
(3-2) (2-2) と同じ。 |
| 428 |
(3-3) (2-3) と同じ。 |
| 429 |
(3-4) (2-4) と同じ。 |
| 430 |
(3-5) 別トラックをもとのトラックにマージする。マージ先の位置として、destinationPosition の点をすべて含む MDPointSet を指定する。同じ MDPointSet が undo 時のターゲットイベントを指定する。 |
| 431 |
…非常に複雑だが、共通した処理も多いのでなんとかコーディングできるのでは。 |
| 432 |
→ 2007.4.10. 実装してみた。まだ意地悪テストはしていないが、イベントを全部逆順にするスクリプトが正しく動作し、undo も正しいようなので、動いていそうな感じ。 |
| 433 |
|
| 434 |
ついでにイベントリストウィンドウで不具合発見。(2008.04.12.) |
| 435 |
(1) 新規トラックにペーストするとき、編集位置を指定しなければコピーしたのと同じ tick 値のところにペーストされるべきなのに、最初のペーストでは 1.1.0 にペーストされてしまう。一度 undo してもう一度ペーストすると正しくペーストされる。→ MyDocument の init で startEditingRange, endEditingRange を kMDNegativeTick で初期化することで修正。 |
| 436 |
(2) トラック名が空のとき、複数のトラックを開くと見分けがつかない。→直した。 |
| 437 |
(3) トラックを削除したらそのウィンドウは自動的に閉じられるべき。→直した。 |
| 438 |
(4) (これはイベントリストウィンドウに限らない)クリップボードからペースト(マージ)した後は、新しく挿入されたイベントが選択されているべきだろう。→直した。 |
| 439 |
(5) 選択したイベントを削除したあと undo で戻した場合、削除した時点で無選択だが、戻したあとも無選択になる。戻したイベントを選択すべきかどうか? 普通は選択しないみたいなので、それでよい気もするが、ペーストしたイベントが選択されていることを考えると、undo で戻したイベントも選択されていると便利かもしれない。deleteMultipleEventsAt:fromTrack: 中の [[[self undoManager] prepareWithInvocationTarget: self] insertMultipleEvents: trackObj at: pointSet toTrack: trackNo selectInsertedEvents: NO]; の最後の NO を YES に変えれば選択されるようになる。どっちがいいかね? |
| 440 |
|
| 441 |
2008.4.13. |
| 442 |
Ruby スクリプトから、値を対話的に入れるダイアログが開けるようにすること。 |
| 443 |
|
| 444 |
d = MRDialog.new |
| 445 |
n = d.item(type, hash) |
| 446 |
# type: "textfield", "radio", "checkbox", "popup" |
| 447 |
# format: for textfield - "i0:50", "f0.0:1.0", "s20", etc. |
| 448 |
# for radio, popup - an array of strings |
| 449 |
# for checkbox - none |
| 450 |
# If a block is given, it will be executed whenever an action is invoked |
| 451 |
d.enable(n) # enable |
| 452 |
d.disable(n) |
| 453 |
d.set_value(n, value) |
| 454 |
d.value(n) |
| 455 |
d.set_attr(n, key, value[, key2, value2, ...]) |
| 456 |
d.attr(n, key) |
| 457 |
d.run # Returns true if and only if "OK" is pressed |
| 458 |
|
| 459 |
ダイアログは MRDialog 型のオブジェクトで、各アイテムはインスタンス変数 _items に配列要素として格納される(@で始まっていないため Ruby スクリプトからのアクセスは不可)。各アイテムを表すデータの属性は、:type, :title, :format, :block, :x, :y, :width, :height などのキーでアクセスできる。属性の読み出しは attr, セットは set_attr メソッドを使う。 |
| 460 |
|
| 461 |
d.layout(M, N, i11, ..., i1N, i21, ..., i2N, ..., iM1, ..., iMN [, options]) |
| 462 |
空の NSView を作り、i11,...,iMN で表されるアイテムをその NSView のサブビューとし、テーブルレイアウトする。NSView のサイズはすべてのアイテムが収まるように設定される。ダイアログのサイズは、NSView と OK, Cancel ボタンが収まるように変更される(ただし、横幅は最初の値より小さくはならない)。NSView を表す整数を返す。 |
| 463 |
|
| 464 |
こんなのどうだろう。 |
| 465 |
hash = MRDialog.run { |
| 466 |
# 新しい MRDialog を作り、以下のアイテムを定義して、最後にダイアログを実行し、 |
| 467 |
# 終了したら MRDialog を破棄して、OK ボタンが押されたなら編集可能アイテムの値を |
| 468 |
# 含むハッシュを返し、Cancel なら nil を返す。 |
| 469 |
layout(n, m, item(...), ..., item(...)) |
| 470 |
} |
| 471 |
|
| 472 |
リストウィンドウ関係のバグ2点: |
| 473 |
編集位置を 1.2.0 に設定してリストウィンドウで新規イベントを挿入すると、先頭イベントが 1.1.0 に移動してしまうことがある。→これは原因不明。下の件を直しているうちに、いつのまにか再現しなくなった。 |
| 474 |
新規イベントを挿入した直後、ティックをダブルクリックして編集モードにして、タブで右のカラムに移動しても、右カラムの編集モードにならず、そのイベントが選択されていて TableView がフォーカスされていない状態になってしまう。→一応直した。スクリプト対応のため modify track notification 周りを大幅にいじったのだが、その副作用だったらしい。notification をまとめて idle time に送るようにしたのだが、隣のカラムで引き続き編集を続ける時は、編集モードに入る前に notification を送っておかないとうまくいかない。この手の不具合は今後も発見されるかもしれない。 |
| 475 |
|
| 476 |
SMF を読み込んだとき、トラック0に non-meta イベントが入っていてもそのまま通してしまう。これは別トラックに分けるべき。 |
| 477 |
|
| 478 |
2009.8.27. |
| 479 |
すごく久しぶりに手をつけてみた。Molby のソースから Ruby 関連のコードを流用して、Ruby コンソールが動くようになった。 |
| 480 |
インタラプトの実装で悩んでいたのだが、1.8.7 を静的リンクすることにしてみた。これだと、10.4 でも rb_add_event_hook が使える。ただし、拡張ライブラリが使えなくなるが、実害はないだろう。rb_add_event_hook でコールバック関数を登録し、50回に1回インタラプトチェックする。本当は setitimer みたいな機構を使う方がスマートだが、MIDI やオーディオと干渉すると面倒。 |
| 481 |
この実装だと、"while 1; end" という無限ループは止められない。これは Ruby の仕様なので仕方がないが、なんかみっともないな。 |
| 482 |
|
| 483 |
2009.8.29. |
| 484 |
Ruby1.8.7 を再ビルドしてみる。まず、while/until でイベントフックが呼ばれるように修正。 |
| 485 |
|
| 486 |
--- eval_org.c 2009-03-23 18:28:31.000000000 +0900 |
| 487 |
+++ eval.c 2009-08-29 21:00:48.000000000 +0900 |
| 488 |
@@ -3145,6 +3145,9 @@ |
| 489 |
goto while_out; |
| 490 |
do { |
| 491 |
while_redo: |
| 492 |
+ EXEC_EVENT_HOOK(RUBY_EVENT_LINE, node, self, |
| 493 |
+ ruby_frame->last_func, |
| 494 |
+ ruby_frame->last_class); /* 20090829 TN */ |
| 495 |
rb_eval(self, node->nd_body); |
| 496 |
while_next: |
| 497 |
; |
| 498 |
@@ -3180,6 +3183,9 @@ |
| 499 |
goto until_out; |
| 500 |
do { |
| 501 |
until_redo: |
| 502 |
+ EXEC_EVENT_HOOK(RUBY_EVENT_LINE, node, self, |
| 503 |
+ ruby_frame->last_func, |
| 504 |
+ ruby_frame->last_class); /* 20090829 TN */ |
| 505 |
rb_eval(self, node->nd_body); |
| 506 |
until_next: |
| 507 |
; |
| 508 |
|
| 509 |
ついで、次のコマンドで universal binary としてビルド。 |
| 510 |
|
| 511 |
CFLAGS='-isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -arch i386 -arch ppc -O2' ./configure --disable-shared --disable-thread |
| 512 |
|
| 513 |
このままだと、alloca が二重定義の警告の対象になって面倒なので、config.h の以下の行を削除。 |
| 514 |
#define alloca alloca |
| 515 |
|
| 516 |
Xcode 上で、インクルード、ライブラリのディレクトリとして Ruby をビルドしたディレクトリを加え、リンクオプションに -lruby-static を加えれば、めでたく Ruby をスタティックリンクすることができる。 |
| 517 |
|
| 518 |
2009.8.31. |
| 519 |
メニューを整理。スクリプトメニューのコマンドも validate できるようにした。"validate_コマンド名" というメソッドが定義されていれば、それを呼び出して、戻り値が true ならメニューが validate される。定義されていなければこれまでと同様(コマンドが MRDocument の通常メソッドの場合はドキュメントが開いていれば valid, クラスメソッドの場合は常に valid)。ただし、チェックはしていない。 |
| 520 |
【TODO】 |
| 521 |
MRDocument に each_editable_track, each_selected_track を追加する。(2009.9.1. 実装) |
| 522 |
Ruby クラス MRPointSet を実装。(2009.9.7. 実装) |
| 523 |
MRPointer で kind, code, tick, data1, data2, noteon_vel, noteoff_vel, duration, tempo, data3, message, smpte 属性の読み書きを可能にする。→現在は data ですべての属性を扱っているが、もしかしてこの方がいい? duration, velocity, release_velocity 属性を実装しよう。(2009.9.11.実装) |
| 524 |
MRTrack のメソッドとして、shift_tick(delta, pointset), modify_tick(array, pointset), selection, selection=, merge(track[, pointset]), unmerge(pointset), extract(pointset), add(event, ...) を実装。add の引数 event は配列で、[0, 60, 480, 64, 0] (tick, note, duration, on-vel, off-vel), [240, :control, 11, 120] など。 |
| 525 |
(2009.9.12. shift_tick, modify_tick, selection, selection= を実装。10.31. shift_tick, modify_tick はやめた。) |
| 526 |
insert_at(events, pointset) なんてのもあっていいが、あんまり使い道なさそう。merge(MRTrack.new(event, ...), pointset) でいいので、特に実装しなくていいか。 |
| 527 |
shift selection を Ruby で実装。 |
| 528 |
重要なコマンドを Ruby で実装して、Events メニューで定義。 |
| 529 |
Merge コマンド、Delete Selected Tracks, Open Selected Tracks as Event List を正しく validate。 |
| 530 |
deltacount の仕様変更。イベント順序を変えないようにする。 |
| 531 |
GraphicWindowControllers の sortedTrackNumbers, visibleTrackCount を削除する。 |
| 532 |
Time Manager 関係のコールを何とかする。ついに "deprecated" の警告が出るようになってしまった。 |
| 533 |
|
| 534 |
2009.9.14. |
| 535 |
MRPointer のキャッシングは何かと問題がありそうな感じ。pt.code = X などについては直接 MyDocument を変更することにして、かわりに「イベントをまとめて編集する」メソッドを MRTrack で実装したらどうか。 |
| 536 |
modify_tick(pointset, delta) |
| 537 |
modify_tick(pointset, enum) |
| 538 |
modify_tick(pointset) block |pt, i| |
| 539 |
modify_code(pointset, code) |
| 540 |
modify_code(pointset) block |pt, i| |
| 541 |
modify_data(pointset, data) |
| 542 |
modify_data(pointset) block |pt, i| |
| 543 |
modify_velocity(pointset, vel) |
| 544 |
modify_velocity(pointset) block |pt, i| |
| 545 |
modify_release_velocity(pointset, rvel) |
| 546 |
modify_release_velocity(pointset) block |pt, i| |
| 547 |
それぞれ、pointset が nil ならば現在の selection が使われる。 |
| 548 |
(2009.9.14. MRPointer のキャッシングをなくした。メモ:複数の値を渡す yield は rb_yield_values(n, arg1, arg2, ...) と書く。9.15. modify_tick, modify_code を実装。 |
| 549 |
2009.10.31. 方針変更。次項参照。) |
| 550 |
|
| 551 |
2009.9.17. |
| 552 |
イマイチ使いにくい。MRPointSet を MRTrack と関係づけた方がいいかも。MREventSet として、modify_{tick|code|data} は MREventSet のメソッドとして実装する。 |
| 553 |
eventset.modify_tick(delta) |
| 554 |
eventset.modify_tick(array) |
| 555 |
eventset.modify_tick { |pt, i| ... } |
| 556 |
modify_code, modify_data, modify_velocity, modify_release_velocity, modify_duration も同様。 |
| 557 |
eventset.each { |pt| ... } # pointset と仕様が違うが、明らかにこっちの方が使いやすい。collect とかも使える。但し、同じ pt が使い回される点は注意が必要。 |
| 558 |
eventset.reverse_each { |pt| ... } # Enumerable の reverse_each は使えない(同じ pt が使い回されるため) |
| 559 |
eventset.select { |pt| ... } # eventset の中でブロックが真を返すものから成る(新しい)集合を返す |
| 560 |
eventset.reject! { |pt| ... } # eventset の中からブロックが真を返すものを取り除く |
| 561 |
track.eventset # 空のイベントの集合 |
| 562 |
track.eventset(nil) # 空のイベント集合 |
| 563 |
track.eventset(pointset) # 同じ位置のイベントの集合 (eventset の親トラックは track でなくてもかまわない) |
| 564 |
track.eventset { |pt| ... } # ブロックが真を返すイベントの集合 |
| 565 |
track.selection # eventset を返す |
| 566 |
track.selection= # 右辺は単なる pointset でもよい |
| 567 |
(2009.10.31. 上のメソッドを実装。) |
| 568 |
|
| 569 |
2009.9.22. |
| 570 |
インタラプトチェックが思ったより頻繁に呼ばれていることがわかったので、setitimer/sigaction を使って約50ミリ秒ごとに1回呼ぶようにした。これでスクリプトの実行はかなり軽くなった。MIDI/オーディオとの干渉は未確認。 |
| 571 |
|
| 572 |
2009.9.23. |
| 573 |
めちゃくちゃ面倒な不具合発見。rb_newobj がたまに失敗し、BAD ACCESS で落ちてしまう。調べてみると、どうやら GC 中に「ファイナライザ」を定義したオブジェクトは直接回収されず、次の Ruby コンテキストスイッチのタイミングで回収されるようで、GC で「回収した」と思っていたオブジェクトが実はすべてこの手のオブジェクトだった場合、自由セルは残っておらず BAD ACCESS になってしまう、というストーリーらしい。これはかなり困ったが、結局 gc_sweep 中で add_heap() を呼ぶところの条件式に freelist == 0 というのを入れた。一応動いているみたい。 |
| 574 |
|
| 575 |
--- ruby-1.8.7-p160/gc.c 2009-03-27 19:25:23.000000000 +0900 |
| 576 |
+++ ruby-1.8.7-p160-tn/gc.c 2009-09-23 21:16:13.000000000 +0900 |
| 577 |
@@ -1215,7 +1215,7 @@ |
| 578 |
if (malloc_limit < GC_MALLOC_LIMIT) malloc_limit = GC_MALLOC_LIMIT; |
| 579 |
} |
| 580 |
malloc_increase = 0; |
| 581 |
- if (freed < free_min) { |
| 582 |
+ if (freed < free_min || freelist == 0) { |
| 583 |
add_heap(); |
| 584 |
} |
| 585 |
during_gc = 0; |
| 586 |
|
| 587 |
2009.10.31. |
| 588 |
MRDocument -> MRSequence に改名、またユーザーから見えるクラス名から "MR" を取り除いた。クラス名は、Sequence/Track/Pointer/EventSet/PointSet。まあいいんじゃないか。 |
| 589 |
MRSequence.delete_track を書いていて、微妙な問題に気がついた。MyDocument からトラックを削除すると、そのトラックは undo バッファに入るが、Document との関係はいったん解消されるように見える。この場合、次のような問題が起きる。(self == MRSequence オブジェクトとする) |
| 590 |
tr = self.track(1) # track 1 をキープ |
| 591 |
self.delete_track(1) # track 1 を削除、undo バッファに入る |
| 592 |
tr.all_events.modify_tick(1000) # tr を編集。undo バッファ中のトラックが MyDocument の知らない間に変更されてしまう |
| 593 |
|
| 594 |
対策として、-[MyDocument deleteTrackAt:] に手を入れて、削除するトラックがどこかよそで使われている(DocumentTrackInfo が登録されている)ときには MDTrackNewFromTrack() で複製したものを undo バッファに乗せることにした。あまり美しい解決法ではないが、一応動くだろう。 |
| 595 |
|
| 596 |
2009.11.1. |
| 597 |
ちょっとごちゃごちゃになってきたので、ここまでに実装した Ruby コマンドを整理。 |
| 598 |
|
| 599 |
PointSet |
| 600 |
clear, initialize, initialize_copy, length, size, member?, include?, each, [], add, <<, delete, reverse, merge, subtract, union, difference, intersection, sym_difference, convolute, deconvolute, offset, +, |, -, &, ^, range_at, inspect, to_s, [] (class method) |
| 601 |
|
| 602 |
EventSet |
| 603 |
initialize, track, eot_selected, track=, eot_selected=, each, reverse_each, each_with_index, reverse_each_with_index, select, reject!, modify_tick, modify_code, modify_data, modify_velocity, modify_release_velocity, modify_duration |
| 604 |
|
| 605 |
Kernel |
| 606 |
check_interrupt, get_interrupt_flag, set_interrupt_flag, show_progress_panel, hide_progress_panel, set_progress_value, set_progress_message, register_menu, get_global_settings, set_global_settings, execute_script |
| 607 |
|
| 608 |
Sequence |
| 609 |
tick_to_time, time_to_tick, tick_to_measure, measure_to_tick, tick_for_selection, duration, track, number_of_tracks, ntracks, each_track, each_editable_track, each_selected_track, insert_track, delete_track, current (class method) |
| 610 |
|
| 611 |
Track |
| 612 |
initialize, duration, duration=, count, nevents, count_midi, nmidievents, count_sysex, nsysexevents, count_meta, nmetaevents, pointer, event, channel, channel=, device, device=, name, name=, selection, selection=, eventset, all_events, selected?, editable?, each, each_selected, each_in, reverse_each, reverse_each_selected, reverse_each_in, merge, copy, cut, add, << |
| 613 |
|
| 614 |
Pointer |
| 615 |
initialize, initialize_copy, position, position=, move_to, move_by, track, top, bottom, next, last, next_in_selection, last_in_selection, jump_to_tick, kind, kind=, code, code=, data, data=, tick, tick=, duration, duration=, velocity, velocity=, release_velocity, release_velocity=, selected? |
| 616 |
|
| 617 |
RubyDialog |
| 618 |
initialize, run, item, layout, _items, set_attr, attr |
| 619 |
|
| 620 |
2009.11.3. |
| 621 |
オーディオデータの録音をやってみて「あまりの使いにくさに」力が抜けてしまった。オーディオスルーもないし、入力レベル調整もできないし、本当にデータをファイルに書き込んでいるだけ。もうちょいなんとかせんといかんな。とりあえず、 |
| 622 |
・入力信号のレベルを Level Indicator で表示。 |
| 623 |
・入力レベルの調整をボリュームでできるようにする。(全チャンネル共通でよい) |
| 624 |
・Play thru の実装。 |
| 625 |
この3つは最低限だな。 |
| 626 |
|
| 627 |
全般的に Core Audio 周りを設計し直した方がいいのだが、とりあえず: |
| 628 |
・MDPlayerInitMIDIDevices でグローバルな AUGraph を作っていて、ここに AUStereoMixer を使っている(2つの DLS Synth をミックスするため)。シーケンスからのオーディオ出力(未実装)と play thru はここに入れればいい。-> MDAudioInitialize() (MDAudio_MacOSX.c) に移動した。 |
| 629 |
・外部からのオーディオ入力を AUMixer に入れるのはうまくいかない。入力と出力で aggregate device を作ればいいみたいだけど、sampling rate が同じでないといけないらしいし、いろいろ不自由なことが起こりそう。 |
| 630 |
・CAPlayThrough サンプルコードを参考にして、同じようにやればいいはず。基本的には、オーディオ入力のデータを読み込んだら、リングバッファに溜め込む(録音中ならついでにファイルに書き込む)。一方、AUMixer の一段前に AUVariSpeed(?) をかませて、そのコールバックでリングバッファ中のデータをとりこむ。入力と出力は同期していない可能性があるから(変動することもあるから)、どんどん同期がずれることを避けるために varispeed unit を入れる。-> varispeed unit は 10.5 以降だった。これは使わない。サンプリングレートを合わせて、「前のつづき」をひたすら流し込めばいいのか?→リングバッファは必須みたい。HAL はデータを "push" し、mixer などはデータを "pull" する。この間にはバッファが必要。まあ言われてみれば当然か。 |
| 631 |
・素直に CARingBuffer 相当の機能を実装しよう。 |
| 632 |
|
| 633 |
2010.6.12. |
| 634 |
Kontakt 4 を衝動買いしてしまった(汗)。もう少しオーディオ回りを強化しないと。 |
| 635 |
入力レベル調整と表示がまだ実装できてないので、これを何とかする。 |
| 636 |
|
| 637 |
2010.6.18. |
| 638 |
オーディオ録音で原因不明のクラッシュが発生するようになってしまった。オーディオ信号処理がいまいちよく理解できていないので、たぶん何か間違ったことをやっているんだろう。整理してみる。 |
| 639 |
|
| 640 |
【使っている AudioUnit など】 |
| 641 |
名前は gAudio (MDAudio) のメンバ名。 |
| 642 |
AUNode synth, synth2; 内蔵シンセ。 |
| 643 |
AUNode mixer; ミキサー。 |
| 644 |
AUNode varispeed; サンプリング速度調整ユニット。未使用。 |
| 645 |
AUNode output; 出力ユニット。 |
| 646 |
AUGraph graph; 信号処理のための AUGraph。下のように結合されている。 |
| 647 |
synth ──0┐ |
| 648 |
synth2 ──1┼ mixer … output |
| 649 |
2┘ |
| 650 |
AudioUnit mixerUnit, outputUnit; mixer, output の中身。 |
| 651 |
MusicDevice musicDevice, musicDevice2; synth, synth2 の中身。 |
| 652 |
AudioUnit inputUnit; 入力デバイス(マイク、ライン入力、SoundFlower など)。これは graph にはつながらず、コールバック関数 sMDAudioInputProc を使ってリングバッファに信号を送る。 |
| 653 |
mixer の bus 2 には AUNode はつながっておらず、mixer に設定されたコールバック関数 (sMDAudioOutputProc) からリングバッファ経由で信号を取得する。 |
| 654 |
mixer の出力と output の入力もつながっておらず、output 入力のコールバック関数中で AudioUnitRender(mixerUnit, ..., ioData) を呼び出してデータを取得する。同じ関数中で、オーディオ録音時のファイルへの書き出しと、Play Thru フラグへの対応(Play Thru がオフならデータを渡さない)も行う。 |
| 655 |
|
| 656 |
【オーディオフォーマットについて整理】 |
| 657 |
・CoreAudio の内部フォーマットは、32 bit float, non-interleaved。 |
| 658 |
・外部デバイスからの入力は AUHAL を通す。AUHAL は AudioConverter を内蔵しているので、AUHAL の出口のフォーマットを指定することができる。(Cf. Apple Technical Note 2091) |
| 659 |
CHECK_ERR(err, AudioUnitSetProperty(gAudio->inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 660 |
(4番目の引数だが、AUHAL は入力用バスが 0、出力用バスは 1 と決まっている。) |
| 661 |
・StereoMixer の入口・出口はすべて同じフォーマットでないといけない。 |
| 662 |
・output の入口もフォーマット指定ができるみたい(未確認)。 |
| 663 |
・オーディオ録音は ExtAudioFile を使う。これも、内部に AudioConverter を内蔵している。ファイルに書き出すフォーマットは ExtAudioFileCreateNew() で指定する。オーディオ信号は ExtAudioFileWriteAsync() で渡すが、この時のフォーマットは次のように指定する。 |
| 664 |
err = ExtAudioFileSetProperty(gAudio->audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &gAudio->preferredFormat); |
| 665 |
(引数の順序が統一されていないので面倒。コンパイル時にチェックされるので間違えたらすぐに気はつくのだが。) |
| 666 |
|
| 667 |
2010.6.20. |
| 668 |
ローカル svn でバージョン管理を再開。 |
| 669 |
mkdir ~/svnroot |
| 670 |
svnadmin create ~/svnroot/Alchemusica |
| 671 |
svn mkdir ~/svnroot/Alchemusica/trunk -m "Creating trunk" |
| 672 |
svn mkdir ~/svnroot/Alchemusica/tags -m "Creating tags" |
| 673 |
svn mkdir ~/svnroot/Alchemusica/branches -m "Creating branches" |
| 674 |
cd ~/Development/Alchemusica |
| 675 |
(build など、svn レポジトリに登録する必要のないファイルを削除) |
| 676 |
svn import . file:///Users/******/svnroot/Alchemusica/trunk -m "initial import" |
| 677 |
cd ~/Development |
| 678 |
mv Alchemusica was_Alchemusica |
| 679 |
svn co file:///Users/******/svnroot/Alchemusica/trunk Alchemusica |
| 680 |
|
| 681 |
オーディオ処理関係、一応安定したみたい。 |
| 682 |
今後やるべきこと: |
| 683 |
・内蔵シンセ出力もオーディオ信号と同じ扱いにする。 |
| 684 |
・AudioUnit のプラグインを読み込めるように… |
| 685 |
→ 一応現在インストールされている MusicDevice プラグインはリストできるようになった。 |
| 686 |
参考:http://quickdekay.net/Software/Tutorials/Components/ |
| 687 |
それにしても、Classic Mac OS が滅んで幾年月、再び NewHandle() とか HLock() なんて関数を使う機会があるとは思わなかったぜ。そもそも Component Manager 自体が前世紀の遺物だからなあ。 |
| 688 |
・AudioUnit の Custom View を表示できるようにする。 |
| 689 |
Cf. http://developer.apple.com/mac/library/technotes/tn2007/tn2213.html |
| 690 |
うー、Carbon Event…つら過ぎ…なんで今更こんなものとつきあわなきゃならんのか… |
| 691 |
ちなみに、Kontakt は Cocoa View を持っている。Vitous Philharmonik は Carbon View だけ。 |
| 692 |
・Kontakt で試していたところ、View をロードしている途中でバスエラーで落ちる。Cocoa View でも Carbon View でもそうなる。小さなテストコードを書くと再現しない。非常に悩んだが、結局 Xcode のターゲットを新しく作り直すと落ちなくなった。なんなんだ… |
| 693 |
|
| 694 |
2011.3.20. |
| 695 |
内蔵マイク入力などが機能していないのを発見。1つは、AUHAL をアロケートするところで desc.componentFlags, desc.componentFlagsMask をゼロクリアしていなかったためにゴミが残っていたこと。もう1つは、sMDAudioAllocateMyBufferList でバッファをアロケートするときに、バッファサイズ (ip->bufferSizeFrames) をセットしていなかったこと。 |
| 696 |
|
| 697 |
2011.8.22. |
| 698 |
・時間軸拡大の undo がおかしい。直前の状態に戻らず、その前に拡大した直後の状態に戻ってしまう。→修正した |
| 699 |
・ストリップチャートで編集した音を選択するようにしたい。どの音を編集しているか確認したいので。→実装した。シフト+編集の時は現在の選択を変化させない。 |
| 700 |
・ストリップチャートを編集するとき鉛筆モードにするのがわずらわしい。うっかりピアノロールでクリックすると音符が挿入されてしまうのが面倒。(さらに、音符挿入の undo がちょっとおかしい気がする) ピアノロールの鉛筆モードとストリップチャートの描画モードを分けた方がいいかも。鉛筆モードはストリップチャートでは選択モードと同じ、逆にストリップチャートの描画モードはピアノロールでは選択モードと同じ。→ 慣れたら現状でもいけるかなあ… |
| 701 |
|
| 702 |
2011.8.24. |
| 703 |
Sysex の録音が正しくなかった(先頭の f0 が落ちていた)ので修正。 |
| 704 |
|
| 705 |
2011.8.24. |
| 706 |
古い Time Manager 関数を置き換えるための作戦。 |
| 707 |
・1つの MDPlayer ごとに1つの pthread を作るのが簡単。そんなに重くないでしょ。そもそも複数の MDPlayer を同時に立ち上げる状況は少ないだろうし。 |
| 708 |
・開始は pthread_create, 終了は pthread_join/pthread_cancel。一定時間眠るのは nanosleep。nanosleep はキャンセルポイントになっているのかな? pthread_testcancel 入れないとだめかな。→実験してみた。nanosleep はキャンセルポイントになっている模様。ただし、pthread_cancel を使うと、キャンセルポイントを通過した時点でスレッドが破棄されてしまうので、クリーンアップルーチンを定義する必要がある。これはちょっと大げさか。どうせ定期的にコールされるのだから、フラグを立てて終わるのを待つ方がきれいに終了できる。 |
| 709 |
・ついでに、Ruby のインタラプトチェックも pthread を使おう。(マルチスレッドで SIGALRM を使うのは何となく筋が悪い気がするので…) |
| 710 |
・演奏開始時に pthread を作り、同時に MDSequence に mutex を作る(calloc で pthread_mutex_t 型のメモリを確保して、そのポインタを MDSequence 内に保持する)。 |
| 711 |
・メインスレッド:MyDocument から内部データを変更するとき MDSequence をロック。 |
| 712 |
・別スレッド:イベントを送出している間 MDSequence をロック。 |
| 713 |
|
| 714 |
→ 一応実装できたと思う。イベントのない時は MyTimerFunc (旧 MyTimerCallback)が長いインターバルを返すのを失念していて、その間演奏を止められないというバグを作ってしまったが、my_usleep() (nanosleep() のラッパー関数)を細切れにコールして、その合間に player->shouldTerminate フラグをチェックすることで切り抜けた。 |
| 715 |
|
| 716 |
2011.8.27. |
| 717 |
演奏中の画面横スクロールが、モーダルダイアログとかメニューコマンド選択中は止まってしまう。NSTimer が働かないためで、これは仕方がないと思っていたのだが、実は簡単に回避できることを今日知った。 |
| 718 |
timer = [[NSTimer allocWithZone:[self zone]] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.1] interval:0.1 target:self selector:@selector(timerCallback:) userInfo:nil repeats:YES]; |
| 719 |
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; |
| 720 |
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode]; |
| 721 |
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode]; |
| 722 |
こうすれば、モーダルダイアログ中もメニューコマンド選択中も構わずタイマーが発動する。 |
| 723 |
→ これだとちょっとまずいのは、グラフィックウィンドウなどを編集中でも画面が勝手にスクロールしてしまうこと。たぶん、画面スクロールを行うハンドラの中で、「現在の RunLoop mode」と「アクティブなウィンドウ」をチェックすれば切り抜けられるのでは? → RunLoop mode == NSEventTrackingRunLoopMode && アクティブウィンドウチェックで、スクロールバーのドラッグを検出できる。また、ウィンドウ内のマウスドラッグは GraphicWindowController の lastMouseClientView と GraphicClientView の isDragging で検出する。 |
| 724 |
|
| 725 |
演奏中にcommand-Q などで終了すると、ときどきこんなエラーが出る。 |
| 726 |
2011-08-28 01:26:09.040 Alchemusica[9360:813] *** CFMessagePort: bootstrap_register(): failed 1103 (0x44f) 'Service name already exists', port = 0x7e0f, name = 'Apple MIDI Client' |
| 727 |
See /usr/include/servers/bootstrap_defs.h for the error codes. |
| 728 |
いつも出る訳ではない。出現する条件はまだ特定できていない。 |
| 729 |
|
| 730 |
録音時のメトロノームを実装しないと。ダイアログにエントリーだけ作って機能を実装してない。 |
| 731 |
|
| 732 |
メトロノームを実装しようと思って、少し厄介なことに気がついた。内蔵音源でメトロノームを鳴らしながらオーディオを録音すると、メトロノームの音が一緒に録音されてしまう。これを回避するのはわりと面倒だぞ。どうしたものかな… |
| 733 |
(一応 Matrix Mixer を使う、という方法は考えておくこと。Cf: /Developer/Examples/CoreAudio/Services/MatrixMixerTest ) |
| 734 |
内蔵音源のレイテンシーもちゃんと調べておかないといけないな。 |
| 735 |
|
| 736 |
メトロノームを鳴らすアルゴリズムもけっこう難しい。 |
| 737 |
・player->nextMetronomeBar, nextMetronomeBeat をそれぞれ、次にメトロノームのベルを鳴らすティック、普通のカウントを鳴らすティックとする。(ベルは小節の頭)また、次の拍子記号の位置を nextTimeSignature とする。 |
| 738 |
・time signature のデータを d[0..3] とすると、1拍の長さは d[2]*timebase/24、1小節の長さはd[0]*timebase*4/(2^d[1])。 |
| 739 |
・Preroll(tick) した直後。tick の直前にある拍子記号の位置を t, 1拍の長さを beat, 1小節の長さを bar とすると、 |
| 740 |
nextMetronomeBar = t + ceil((tick - t) / bar) * bar |
| 741 |
t0 = (nextMetronomeBar > tick ? nextMetronomeBar - bar : nextMetronomeBar) |
| 742 |
t1 = t0 + ceil((tick - t0) / beat) * beat |
| 743 |
nextMetronomeBeat = (t1 > nextMetronomeBar ? nextMetronomeBar : t1) |
| 744 |
以下,nextMetronomeBeat は beat ずつ増やす。nextMetronomeBar, nextTimeSignature のいずれよりも小さければそのままカウントを鳴らして更新。nextMetronomeBar 以上で nextTimeSignature よりも小さければ、ベルを鳴らして、nextMetronomeBeat = nextMetronomeBar + beat, nextMetronomeBar += bar とする。このとき、nextMetronomeBar が nextTimeSignature よりも大きくなったら、nextMetronomeBar = nextTimeSignature とする。bar/beat はそのまま。ベルを鳴らしたとき、nextMetronomeBar == nextTimeSignature なら、bar/beat を新しい拍子記号に合わせて更新し、nextTimeSignature を次の拍子記号の位置に進める。 |
| 745 |
|
| 746 |
2011.8.31. |
| 747 |
いちおう実装できたと思う。 |
| 748 |
内蔵音源で演奏していると、ときどき音が鳴らなくなる。フリーズすることもある。どうも、ピアノ曲でサステインペダルを踏みっぱなしにしていると起きるようなのだが、詳しい条件は不明。 |
| 749 |
|
| 750 |
2011.9.3. |
| 751 |
sourceforge.jp の svn 管理に移行。 |
| 752 |
svn mkdir svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/trunk -m "Creating trunk" |
| 753 |
svn mkdir svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/tags -m "Creating tags" |
| 754 |
svn mkdir svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/branches -m "Creating branches" |
| 755 |
cd ~/Development/Alchemusica |
| 756 |
(build, Alchemusica.xcode/USERNAME.* など、svn レポジトリに登録する必要のないファイルを削除) |
| 757 |
svn import . svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/trunk -m "initial import" |
| 758 |
cd ~/Development |
| 759 |
mv Alchemusica was_Alchemusica |
| 760 |
svn co svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/trunk Alchemusica |
| 761 |
|
| 762 |
タグの付け方。 |
| 763 |
svn copy svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/trunk svn+ssh://toshinagata1964@svn.sourceforge.jp/svnroot/alchemusica/tags/version_x_y_z -m "version x.y.z" |
| 764 |
|
| 765 |
2012.1.21. |
| 766 |
バージョン 0.6.1 |
| 767 |
クオンタイズを実装。(Events メニュー) |
| 768 |
グラフィックウィンドウで、ライン形状とオペレーションモード (set/add...) のポップアップボタンを常に有効にした。 |
| 769 |
初期設定の保存方法を変更。一部の設定が初期化されてしまうかも。すみません。 |
| 770 |
イベントをシフトするコマンドを実装。(Events メニュー) |
| 771 |
トラックの挿入/削除が演奏に反映されていなかったので修正。 |
| 772 |
ストリップチャートの編集が、矩形選択モードでもできるようにした。 |
| 773 |
メトロノーム設定ウィンドウが正しく動作していなかったので修正。また、クリック音の設定を変更したとき音を鳴らすようにした。 |
| 774 |
"Detele selected time" がトラックにイベントが無いときクラッシュしていたので修正。 |
| 775 |
トラックソロ機能を実装。 |
| 776 |
ポーズボタンが正しくハイライトされていなかったので修正。 |
| 777 |
|
| 778 |
- Quantize selected events menu command is implemented. |
| 779 |
- Ruby dialogs can be bound to the global settings by assigning @bind_global_settings instance variable. |
| 780 |
- Graphic window: the popup buttons for line shape and operation mode (set/add/...) are always enabled. |
| 781 |
- The global settings are again restructured. The settings are now under "settings" entry of the user default. |
| 782 |
- set/get_global_settings command changes feature: the setting is no longer placed under "MDRuby" but directly in the UserDefaults, and the key path is now recognized. |
| 783 |
- Text field in the Ruby dialog causes "OK" as the default action when return key is pressed. |
| 784 |
- Shift selected events command is enabled. |
| 785 |
- Inserting/deleting tracks was not reflected in the MIDI playing. Fixed. |
| 786 |
- Editing strip chart is also possible when rectangle selection mode is enabled. |
| 787 |
- Metronome setting window was not working correctly when the note name is edited. Fixed. Rings metronome bell/click during editing. |
| 788 |
- Deleting selected time causes crash when there are no events in the editing track. Fixed. |
| 789 |
- Solo track capability is implemented. |
| 790 |
- The Pause button was not highlighted correctly. Fixed. |