Develop and Download Open Source Software

Browse Subversion Repository

Contents of /tags/version_0_6_2/memo.txt

Parent Directory Parent Directory | Revision Log Revision Log


Revision 30 - (show annotations) (download)
Thu Aug 16 07:42:41 2012 UTC (11 years, 7 months ago) by toshinagata1964
File MIME type: text/plain
File size: 73122 byte(s)
version 0.6.2
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.

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26