Propeller日記 (4)
長嶋 洋一
Propeller日記(1)
2008年3月26日(水)
さて、昨日の続きである。 VocalTractオブジェクトの中のstartメソッドの、 以下の初期化の部分はまさに、サウンド出力の「肝」だったのである。 これをたまたま見落としていたのは偶然とはいえ、凡ミスであった。 まぁ、そのお陰でPropellerのアセンブラにだいぶ慣れてきたので、 損な回り道ということでもないだろう。ctrb_ := $18000000 + pos_pin & $3F ctrb_ += $04000000 + (neg_pin & $3F) << 9Propellerマニュアル のCRTBのところを見ると、PropellerのCog内のカウンタ/タイマ関係について、 以下のように詳細に書かれていた。 オーディオ出力はシリアルなのだから、絶対にカウンタ/タイマを使っている筈なのである。 まず、CRTA/CRTBのspin解説として
とあり、どんなふうに応用できるかとして、以下をズラリと並べている。
このプログラムでCRTBに設定しているデータのフォーマットは以下であった。
初期化ルーチンでCTRBに転送されるデータをstartメソッドで初期化しているところを確認すると、
ctrb_ := $18000000 + pos_pin & $3F ctrb_ += $04000000 + (neg_pin & $3F) << 9ということは、最上位1バイトについて言えば、0001 1000 + 0000 0100 = 0001 1100となる。 bit31は無視なので、bit30..26の5ビットのCTRMODEは00111である。 2バイト目はゼロなので、bit25.23の3ビットのPLLDIVは000である。 「pos_pin & $3F」の意味は、将来的な64ビットのPropellerのためにマスクしているだけなので、 これは単にpos_pinであり、これがbit5..0に置かれる。 neg_pinは同様に9ビット左シフトした位置のbit14..9に置かれる。 それぞれのピンの意味は以下である。
従って、以下のPLLDIVは000なので、「VCO / 128」が出力されることになる。
CTRMODEについての解説は以下であり
ここでのCTRMODEは00111ということで、以下の表から「DUTY differential」モードであることが判明した。 posとnegの2本のステレオ出力となっているが、この記述によれば、論理的に反転しているだけのように見える。 そこで、ステレオで鳴らしていたサウンドの、片方のチャンネルの信号をひっくり返して(交換して)みたところ、 ステレオの中央にびったりと定位した。 元のサウンドは、なんとなくステレオっぽく拡がっていたのだが、 これは「モノラル信号を片チャンネルから、その反転信号を反対チャンネルから出す」という、 まさに「擬似ステレオ」そのままであった。
ここでいよいよ、Webでダウンロードしていた このマニュアル の出番となった。 Propellerの各Cogsが持つ、強力であるというカウンタを理解する必要がでてきた。 その全体のブロック図は以下のようなものである。
ちょっと複雑なのはmulti-purposeであるからなので、今回のDUTY differentialモードの解説を見ると、 以下のようなブロックだけでいいことが判った。
このD/A出力は今後も色々に使うことが予想されるので、ここでマニュアルの説明をきちんと整理することにした。
Duty Cycle modes of operationModes %00110 (DUTY single-ended) and %00111 (DUTY differential) on the surface appear similar to the NCO/PWM modes of operation; however the waveforms they produce are very different. The output of the Duty Cycle modes is the carry output of the PHSA register, whenever the PHSA register overflows (wraps around from $FFFF_FFFF to $0000_0000) the APIN is set to 1. The block diagram for this mode of operation is shown in Figure 9. If FRQA is $0000_0001, the carry bit of PHSA will be 1 only once every 2^32 (4,294,967,296) cycles. At an 80MHz system clock, this will occur approximately once every 54 seconds. Similarly if FRQA is $FFFF_FFFF, the carry bit of PHSA will be 0 only once every 2^32 cycles.
以下が、上の解説に対応した出力例である。
そして、以下のように、オーディオ出力のD/Aに使う、という例の解説があった。 ・・・これでだいぶ、先が見えてきた。 そこでまず、この段階でのexp016.spinとAudioOut03.spinを
と、VocalTrackと同様に動作するバージョンとして保管した。 そして、exp016.spinをコピーしてリネームしたexp017.spinを作り、 またAudioOut03.spinをコピーしてリネームしたAudioOut04.spinを作った。
- メイン exp016.spin
- 音声合成・サウンド出力 AudioOut03.spin
そしてまず、音声合成のセグメントの演算で、 13種類のパラメータを次のセグメントに向けて時間的に補間していることが判ったので、 この部分をカットしてみた。 これによって、音声ではなくなって単なるフォルマント持続音が出ることになるが、 プログラムは13パラメータの数値演算とD/A出力だけになる。
上のようなMax/MSP側のパラメータ設定ソフトを作り、 対応して出来たPropellerソフト等は以下である。
これで、持続音に対して、音声合成の13パラメータをリアルタイムに個々に変化させることができた。 AudioOut04.spinの冒頭部分は、以下のようにスッキリした。
- メイン exp017.spin
- サウンド出力 AudioOut04.spin
- Max/MSPパッチ MIDItest03.mxt
VAR long cog, tract, zzzz4, zzzz5, attenuation, zzzz6 '6 longs ...must long dira_, zzzz1, zzzz2, ctrb_, zzzz3, cnt_ '6 longs ...be long frames[4] 'many longs ...contiguous PUB start(parameters, pos_pin, neg_pin) dira_ |= (|< pos_pin + |< neg_pin) ctrb_ := $1C000000 + (pos_pin & $3F) + ((neg_pin & $3F) << 9) tract := parameters cnt_ := clkfreq / 20_000 return cog := cognew(@entry, @attenuation) PUB change bytemove(@frames[0] + 3, tract, 13) DAT entry org call #initial jmp #loop retadd long :wait :wait jmpret retadd,#loop mov f_ptr,par add f_ptr,#4*8+3 movd :set2,#par_curr mov f_cnt,#13 :set jmpret retadd,#loop rdbyte t1,f_ptr shl t1,#24 :set2 mov par_curr,t1 add :set2,d0 add f_ptr,#1 djnz f_cnt,#:set jmp #:wait ' ##### Vocal Tract Job ##### loop waitcnt cnt_value,cnt_ticks 'wait for sample period rdlong t1,par 'perform master attenuation sar x,t1 mov t1,x 'update duty cycle output for pin driving add t1,h80000000 mov frqb,t1 mov t1,par 'update sample receiver in main memory add t1,#1*4 wrlong x,t1 :White_noise_source test lfsr,lfsr_taps wc 'iterate lfsr three times rcl lfsr,#1 ・・・・・・・・ jmp retadd 'run segment of frame handler, return to loop呼び出し側の親オブジェクトから、 13種類のパラメータのいずれかを変更した場合に呼ばれるメソッドをchangeとした。 これに対応して上のように、常時、13個のパラメータをPropeller共有RAMの領域からCog内に転送して、 24ビット左シフトしながら全て、内部演算用のpar_currに置いている。 ここは以前はセグメント補間の処理で複雑だったところである。
また、「Vocal Tract Job」の冒頭、 サンプリングクロックに対応した「waitcnt cnt_value,cnt_ticks」に続く以下の部分が、 カウンタに可変Dutyデータを書き込む、焦点の部分であることが明確になった。 なかなか見つからなかったが、ここにたった1箇所だけ、FRQBがあったのである。
この部分は、変数「x」が、カウンタに書き込まれるPWMデータとして前回の処理から受け継がれる。 まず、rdlong t1,par 'perform master attenuation sar x,t1 mov t1,x 'update duty cycle output for pin driving add t1,h80000000 mov frqb,t1 mov t1,par 'update sample receiver in main memory add t1,#1*4 wrlong x,t1
の部分では、「rdlong t1,par」で、音量制御(データをシフトして小さくする)パラメータをt1に格納し、 「sar x,t1」で、MSBのサインビットを残したまま、t1ビットだけデータを右シフトして小さくする。 これを再びt1に戻す。 ところが、既にmaster attenuationという概念は省略してしまったので、この3行のうち、 最初の2行は不要となり、カットした。 そしてrdlong t1,par 'perform master attenuation sar x,t1 mov t1,x 'update duty cycle output for pin driving
の部分、実はここしか残らないのであるが、 t1に$80000000を加算している。 これは「サインビットの反転」に他ならない。 PWMというのは、ディジタル的に反転する時間がサンプル値となるので、 このように、サンプリング周期ごとに符号を反転させ、それからの時間幅としてアナログ値を得るのであろう。 そしてmov t1,x 'update duty cycle output for pin driving add t1,h80000000 mov frqb,t1
は、親メソッドから「現在のサンプルポイント」の問い合わせに返すための値なので、 これも省略した。 ・・・ということで、この肝心のD/A出力の部分はちょっと拍子抜けだが、mov t1,par 'update sample receiver in main memory add t1,#1*4 wrlong x,t1' ##### Vocal Tract Job ##### loop waitcnt cnt_value,cnt_ticks 'wait for sample period mov t1,x 'update duty cycle output for pin driving add t1,h80000000 mov frqb,t1 :White_noise_source test lfsr,lfsr_taps wc 'iterate lfsr three times rcl lfsr,#1 ・・・・・・・・ jmp retadd 'run segment of frame handler, return to loopだけになってしまった。 これでいよいよ、このプログラムで解読していないのは、 音声合成アルゴリズムの数値演算とCORDICの部分だけとなった。 しかし、これはたぶん、理論をそのまま記述しているだけなので、 必要になったらいつでも確認できる。 ここで知りたかったのは「Propellerでのディジタルオーディオ」、 サウンドの出力の部分なのである。 そこで、サンプリング周期ごとの処理だけを残して、 まったくのオリジナルとして、同じメカニズムでサウンドを出力するプログラム、 というように、目標を修正することにした。
2008年3月27日(木)
Propellerマニュアルにあった「DUTY differentialモード」によるサウンド出力を実験するために、 まずexp017.spinをコピーしてリネームしたexp018.spinを作り、 またAudioOut04.spinをコピーしてリネームしたAudioOut05.spinを作った。 今度はAudioOut05.spinの大部分はカットされ、exp018.spinもほぼ全面改訂となる。 パラメータはMax/MSPからMIDI経由でいくらでも渡せるので、 Propellerの部分は最小限にする計画である。まず、 exp018.spin の冒頭の部分は、以下のようにこれまでとまったく同じである。
{{ exp018.spin }} CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 OBJ Num : "Numbers" TV : "TV_Terminal" midiIn : "MidiIn03" midiOut : "MidiOut01" v : "AudioOut05"そして、exp018.spinのMainの部分は以下のように、 とりあえずMIDIから7ビットのデータを1つだけPropellerに送るようにした。
PUB Main | dummy, para Num.Init TV.Start(12) TV.Str(string("Audio_output_Cog =")) dummy := v.start(10, 11) TV.Str(Num.ToStr(dummy, Num#DEC)) TV.Str(string(", MIDI_input_Cog =")) dummy := midiIn.start(7) TV.Str(Num.ToStr(dummy, Num#DEC)) TV.Str(string(", MIDI_output_Cog =")) dummy := midiOut.start(6) TV.Str(Num.ToStr(dummy, Num#DEC)) repeat dummy := midiIn.event if dummy <> -1 midiOut.fifoset(dummy) TV.Str(Num.ToStr(dummy, Num#HEX7)) if dummy == $903064 elseif dummy == $903000 elseif (dummy & $FFFF00) == $B00000 para := dummy & $00007F v.change(para) elseif (dummy & $FFFF00) == $B00100AudioOut05.spin の方は、まず冒頭のspinの部分は以下のように簡単にした。 累算値の初期値は、とりあえず$01としてみた。
VAR long speed, dira_, ctrb_, cnt_ PUB start(pos_pin, neg_pin) dira_ |= (|< pos_pin + |< neg_pin) ctrb_ := $1C000000 + (pos_pin & $3F) + ((neg_pin & $3F) << 9) speed := $01 cnt_ := clkfreq / 20_000 return cognew(@entry, @speed) PUB change(parameter) speed := parameterパラメータは、親オブジェクトから「v.change(para)」と送ったものを、 changeメソッドで受けて、これを内部変数speedに格納している。 そしてアセンブラ領域は、以下で全てである。
DAT entry org call #initial jmp #loop retadd long :wait :wait jmpret retadd,#loop jmp #:wait loop waitcnt cnt_value,cnt_ticks mov t1,x add t1,h80000000 mov frqb,t1 mov t2,par rdlong t1,t2 shl t1,#24 add x,t1 jmp retadd initial mov reserves,#0 add initial,d0 djnz clear_cnt,#initial mov t1,par add t1,#4 rdlong dira,t1 add t1,#4 rdlong ctrb,t1 add t1,#4 rdlong cnt_ticks,t1 mov cnt_value,cnt add cnt_value,cnt_ticks initial_ret ret h80000000 long $80000000 d0 long $00000200 clear_cnt long $1F0 - reserves reserves cnt_ticks res 1 cnt_value res 1 x res 1 t1 res 1 t2 res 1Propellerツールでこのプログラムをコンパイルしてダウンロード・実行させると、 シンセサイザーで馴染みのある「鋸歯状波」のサウンドが鳴った。 MIDIからパラメータを変化させると、そのピッチが大幅に変化する。 これで、とりあえずのオーディオ出力のスタート地点に立ったことになる。
せっかくなので波形を見てみようと、数年ぶりにWindows95のノートを発掘して、 秋月電子で買ったオシロを繋いで、このアナログ出力波形を見てみることにした。 バッテリが寿命で再リセットとかが必要だったが、無事に動いて、 オシロのスクリーンショットをサーバに転送できた。 波形は以下のようなものである。
プローブが電圧を1/10にしているので、ちゃんとp-pで2.5Vぐらいは出ているようだ。 もう片方のチャンネルからは、もちろん極性反転した信号が出ていた。 以下が、この実験の様子である。 Propellerの小さなボードが、4台のパソコンに囲まれている風景である。
ここで見ている波形は、サンプリング周期ごとに、単純にパラメータを累算した$00000000から$FFFFFFFFまでの値が、 FRQBに書き込まれているので、このような鋸歯状波となるわけである。 Propellerの内部ROMには、サインテーブルが格納されているというので、 次の目標は、ピッチを変えて、さらに音量を変えて、サイン波をオーディオ出力することにした。
まず音量については、VocalTract.spinの中に、attenuationの処理があったのを参考にして、 0倍、1/2倍、1/4倍、・・・と7ビット(1/128倍まで)の単純シフトとする事にした。 送りのexp018.spinのMainの該当部分を以下のように、 コントロールチンジの次の番号で0-7の値をPropellerに送るようにした。
repeat dummy := midiIn.event if dummy <> -1 midiOut.fifoset(dummy) TV.Str(Num.ToStr(dummy, Num#HEX7)) if dummy == $903064 elseif dummy == $903000 elseif (dummy & $FFFF00) == $B00000 para := dummy & $00007F v.change1(para) elseif (dummy & $FFFF00) == $B00100 para := dummy & $000007 v.change2(para)受けるAudioOut05.spinの方は、変数volumeを新設して、
VAR long speed, dira_, ctrb_, cnt_, volume PUB start(pos_pin, neg_pin) dira_ |= (|< pos_pin + |< neg_pin) ctrb_ := $1C000000 + (pos_pin & $3F) + ((neg_pin & $3F) << 9) speed := $01 volume := 0 cnt_ := clkfreq / 44_100 return cognew(@entry, @speed) PUB change1(parameter) speed := parameter PUB change2(parameter) volume := parameter & %0111 DAT entry org call #initial jmp #loop retadd long :wait :wait jmpret retadd,#loop jmp #:wait loop waitcnt cnt_value,cnt_ticks mov y,x mov t1,par add t1,#4*4 rdlong t2,t1 and t2,#%0111 sar x,t2 add x,h80000000 mov frqb,x mov x,y mov t2,par rdlong t1,t2 shl t1,#24 add x,t1 jmp retaddと、累算値xをいったんyに格納して、D/A出力するところでだけ、 SAR命令によって、渡されたビット数だけ右シフトした。 この命令はSHR命令と違って、MSBの符号ビットはそのままにしてくれるので、 これだけで簡単にアッテネータの部分が実現できた。
Propellerのサインテーブルは当然のことながら1/4周期分しか格納されていないので、 位相によって読み出しと極性を調整する必要がある。 ところでVocalTract.spinの中では、「sine」という部分で以下のようにPropeller内部のテーブルを参照していて、 どうも位相と極性も調整しているっぽいので、この部分を真似てみることにした。
sine shr t2,#32-13 'get 13-bit angle test t2,h00001000 wz 'get sine quadrant 3|4 into nz test t2,h00000800 wc 'get sine quadrant 2|4 into c negc t2,t2 'if sine quadrant 2|4, negate table offset or t2,h00007000 'insert sine table base address >> 1 shl t2,#1 'shift left to get final word address rdword t2,t2 'read sine word from table negnz t2,t2 'if quadrant 3|4, negate word shl t2,#15 'msb-justify resultPropellerマニュアルによれば、メインメモリ中のサインテーブルは以下のように2049「ワード」、 すなわち16ビット幅である。 アドレスは$E000-$F001である。 1/4周期なので、最初のゼロ値から1/4周期で、終端の最大値まで持つために端数が出るわけである。
上のルーチンでは、位相t2はフルスケール13ビットであり、
sine shr t2,#32-13 'get 13-bit angleで位相累算値t2を13ビットのフルスケールに収めて、
test t2,h00001000 wz 'get sine quadrant 3|4 into nzによってそのMSBを見て、後半の周期(3/4 四半期)であればZフラグを立て、
test t2,h00000800 wc 'get sine quadrant 2|4 into cによって、2/4 四半期であればCフラグを立て、
negc t2,t2 'if sine quadrant 2|4, negate table offsetによって、このキャリーでt2を反転することで逆位相で読み出す。そして
or t2,h00007000 'insert sine table base address >> 1 shl t2,#1 'shift left to get final word address rdword t2,t2 'read sine word from tableによってテーブルのアドレスオフセットに調整して、 データは1ワードなのでアドレスを2倍してから実際のサインテーブル値をt2に読み込み、
negnz t2,t2 'if quadrant 3|4, negate word shl t2,#15 'msb-justify resultによって最後にzフラグで極性を反転し、 結果を32ビットのlong長に戻している。
この部分を自前のプログラムに組み込んでしばし実験して、 とりあえずサインが出る、という以下のプログラムが出来た。
波形は以下のように、綺麗なサイン波となった。
- メイン exp019.spin
- サウンド出力 AudioOut06.spin
44.1KHzに変更したサンプリング周期ごとに処理しているのは、以下のような簡単な仕事である。
以下が、プログラム中の該当する部分である。
- サンプル値xをyに保管
- アッテネータ値で出力xをレベルダウン
- xをFRQBに書き込むことでPWMオーディオ出力
- 波形読み出しの位相累算値phaseを累算
- これでサインテーブルを読み出す
- 保管していたサンプル値yをxに戻す
- サインの値をxに加算
loop waitcnt cnt_value,cnt_ticks mov y,x mov t1,par add t1,#4*4 rdlong t2,t1 and t2,#%0111 sar x,t2 add x,h80000000 mov frqb,x mov t1,par rdlong t2,t1 add phase,t2 mov t2,phase shr t2,#32-13 'get 13-bit angle test t2,h00001000 wz 'get sine quadrant 3|4 into nz test t2,h00000800 wc 'get sine quadrant 2|4 into c negc t2,t2 'if sine quadrant 2|4, negate table offset or t2,h00007000 'insert sine table base address >> 1 shl t2,#1 'shift left to get final word address rdword t1,t2 'read sine word from table negnz t1,t1 'if quadrant 3|4, negate word shl t1,#7 mov x,y add x,t1 jmp retaddこれでとりあえず「サイン」が出たが、まだ「オシレータ」と呼べるものではない。 実際には、Propellerのピン出力にRCフィルタが入っているので、 周波数特性としてはかなりのLPFがかかって、周波数ごとにレベルはかなり変化する。 これは仕方ないとして、もう少し、オシレータになるような改良を加えていこう。
2008年3月28日(金)
サイン波が出たということは、波形テーブルを参照すれば、周期波形については、あとはどんな信号も生成できる。 サンプリングをCDと同じ44.1KHzにしたこともあり、ここで波形のピッチの部分をきちんと整理しよう。かつて 作るサウンドエレクトロニクス で、AKI-H8で同じように信号生成からMIDI楽器にまでシステムを進化させた時には、 12等分平均率に基づくMIDI対応のピッチごとに、AKI-H8のROMにデータテーブルを持った。 しかしPropellerでは、共有ROMもCog内RAMも容量に限りがあるので、 そのような潤沢なデータテーブルを無駄に確保するような戦略は採用できない。
その一方で、クロック80MHzのCogの処理能力はかなりのものなので、 同じMIDIの演奏情報の時間的トラフィックに対して、おそらくソフトウェア的になんとかしても、 そこそこは間に合う筈である。 経験的な直感から、ここでは1オクターブ分の位相累算値をテーブルとして持ち、 MIDIノートナンバに対応したピッチの処理は、リアルタイムにTopオブジェクトのCogが行うことにした。 まずは机上の設計としてデータを検討し、本当にそのピッチでサウンドが鳴るか、検証してみよう。
位相累算値のデータが半分になれば、波形のアクセスの時間が2倍かかって、 生成されるサウンドのピッチはオクターブ下がる。 そこで、最高音域の1オクターブ分の12個の位相累算値データがあれば、 それより低い場合には、オクターブ分だけデータを右ビットシフトすればよい。 1個のデータから平均率の12音を生成するには、「12乗根の2」の高精度乗除演算が必要なので、 これは採用せず、12個を愚直に持つことにする。 MIDIについて から、
------------------------------------------------------- Octave|| Note Numbers # || C | C# | D | D# | E | F | F# | G | G# | A | A# | B ------------------------------------------------------- 0 || 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 1 || 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 2 || 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 3 || 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 4 || 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 5 || 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 6 || 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 7 || 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 8 || 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 9 || 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 10 || 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------とあるので、MIDIノートナンバの最高音域では、 「Note 116 = G#9」から「Note 127 = G10」までを持てばよい。 ピッチの基準は「A4 = 440Hz」を使うことにして、ここでは簡単のために、 MIDI規約には存在しないが、形式的にあと全音だけ上にNoteを129まで延長して、 「Note 129 = A10」をまず計算する。 これはA4の6オクターブ上なので、ぴったり440 * 64 = 28160Hzである。
サンプリング周波数が44.1KHzということで、その1周期の時間は22675.736μsecほどである。 A10の28.160KHzの周期は35511.363μsecほどである。 A10のピッチはサンプリング定理の22.05KHzを越えているが、 累算値のデータは小数点以下の端数なので、理論的には計算できる。 求めるA10のための累算値データを x とすれば、32ビット(1 long)フル、 つまり$100000000を累算していると44.1KHzになるのに対して、 データ x を同じサンプリングで累算すると28.160KHzになるので、 比例関係から
$100000000 : 44.1 = x : 28.160 となり、以下のGoogle電卓の計算によって、「x = 2742546010」という整数値を得た。
この数値を元に、同様にGoogle電卓で「12乗根の2」を y として計算すると、
y = 2^(1 / 12) = 1.05946309 となるので、MIDI最高音のG10の場合には、同様にGoogle電卓で「x / y / y」を計算して、
G10 : (2742546010 / 1.05946309) / 1.05946309 = 2443330740 となった。 ここからは、出て来た結果をさらにyで割れば半音下のデータとなるので、 同様にGoogle電卓で計算して、以下を得た。
F#10 : 2443330740 / 1.05946309 = 2306197130
F10 : 2306197130 / 1.05946309 = 2176760240
E10 : 2176760240 / 1.05946309 = 2054588080
Eb10 : 2054588080 / 1.05946309 = 1939272920
D10 : 1939272920 / 1.05946309 = 1830429900
C#10 : 1830429900 / 1.05946309 = 1727695770
C10 : 1727695770 / 1.05946309 = 1630727660
B9 : 1630727660 / 1.05946309 = 1539201960
Bb9 : 1539201960 / 1.05946309 = 1452813200
A9 : 1452813200 / 1.05946309 = 1371273070
Ab9 : 1371273070 / 1.05946309 = 1294309430
ちなみに確認のために、最後のデータをさらにyで割って2倍してみると
(G9) : 1294309430 / 1.05946309 * 2 = 2434330860 となり、これはG10の2443330740とかなり近いもので、誤差の蓄積は心配ないオーダである。 このデータを親オブジェクトの側に定数データとして持ち、 MIDIノートナンバに対応してオクターブをシフトして、 まったく変更していないAudioOut06.spinに対してspeedパラメータとして渡すようにしたのが、 以下の「exp020.spin」プログラムである。
{{ exp020.spin }} CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 DAT f_number long 1294309430,1371273070,1452813200,1539201960 long 1630727660,1727695770,1830429900,1939272920 long 2054588080,2176760240,2306197130,2443330740 OBJ Num : "Numbers" TV : "TV_Terminal" midiIn : "MidiIn03" midiOut : "MidiOut01" v : "AudioOut06" PUB Main | dummy, para, p1 Num.Init TV.Start(12) TV.Str(string("Audio_output_Cog =")) dummy := v.start(10, 11) TV.Str(Num.ToStr(dummy, Num#DEC)) TV.Str(string(", MIDI_input_Cog =")) dummy := midiIn.start(7) TV.Str(Num.ToStr(dummy, Num#DEC)) TV.Str(string(", MIDI_output_Cog =")) dummy := midiOut.start(6) TV.Str(Num.ToStr(dummy, Num#DEC)) repeat dummy := midiIn.event if dummy <> -1 midiOut.fifoset(dummy) TV.Str(Num.ToStr(dummy, Num#HEX7)) if (dummy & $FF00FF) == $900064 v.change2(0) p1 := (dummy & $007F00) >> 8 para := f_number[ (p1+4) // 12 ] >> ( (127-p1) / 12 ) v.change1(para) elseif (dummy & $FF00FF) == $900000 v.change2(7) elseif (dummy & $FFFF00) == $B00000 para := dummy & $000007 v.change2(para)このプログラムは、 以下のように作ったMax/MSPパッチ「MIDItest04.mxt」 によって、Mac側のソフトシンセのピアノ音と、Propellerの生成音とでユニゾンの確認ができた。 MIDIノートオンで「アッテネータ = 0」で発音し、MIDIノートオフで「アッテネータ = 7」として、 擬似的に消音しているだけの簡単なものである。
プログラムの中でちょっとトリッキーなのは
para := f_number[ (p1+4) // 12 ] >> ( (127-p1) / 12 ) であるが、オクターブ分、12個並べた配列のインデックスの部分には、 12個ごとに繰り返すので、剰余演算子「//」を使った。 普通は「%」を使うが、Propellerではバイナリ数値のために使うので、 ここはちょっとした注意点である。 あとは、オクターブが低いほどたくさん右シフトしているだけであり、 ここでは普通の除算(余りは捨てる)「/」を使っている。
これで、このプログラムの走るPropellerボードに、 電子ピアノなどをMIDIで繋いで、簡単な楽器としてスタンドアロンで使う・・・ というようなアプリケーションはかなり身近になった。 当面、あまりPropellerでこちらの方面に深入りするつもりはないので、 また鉾先を変えていくことにしよう。
2008年3月30日(日)
Propellerで目指すところに関係して、 このページ を改訂した。 これまでのインスタレーションは、基本的にというシステム構成をしていた。 AKI-H8で作ったインターフェース、I-Cubeなど、 そしてGAINERも、この第2/第4項目のインターフェースである。 Propellerについても、ここまでの実験で、この領域に十分に参加できる可能性は確信した。
- 外界からの入力を検出するセンサ
- センサからホストへのインターフェース
- ホストPC(Max/MSP/jitter, Flash, Processing等)
- ホストから(広義)ディスプレイへのインターフェース
- 外界に反応を返す(広義の)ディスプレイ
しかしさらに、もしインターフェースの部分の情報処理能力が高いのであれば、 第3項目まで含めて、つまりパソコンを不要としてシステムを構成できる。 Propellerには、この可能性を強く感じる。 このページ のインスタレーションの中にはごく少数、その発想でAKI-H8だけで実現したものもあるが、 まだまだホストPCは全盛である。 とりあえずAKI-H8やGAINERやBasicStampやPICに加わることから始めるとして、 目標はそこで終わらず、より高いところを目指したい。
さて、明日から2日間は学科会議合宿でPropellerと遊ぶことも出来ないので、 今日はちょっとだけの改造をすることにした。 これまで作ってきたPropellerソフトウェアでは、 ビデオ出力の部分は、Parallax社の提供したオブジェクトをそのまま、 ソフトウェア部品として利用してきた。 しかし、せっかくなら不要な部分を削ったり、機能を追加してオリジナル化してみたい。 そこで、OBJモジュールで参照されている
について、コピーしてオリジナル名としたものをカレントディレクトリに置いて、 実験することにした。後ろの2つは、「TV_Terminal.spin」から参照されているものである。
- Numbers.spin
- TV_Terminal.spin
- TV.spin
- Graphics.spin
親オブジェクトの方は、exp020.spinをコピーしてexp021.spinとリネームし、 まずは「MIDIを受けてサイン波形を生成する」というSPECは変更せず、 子オブジェクト・孫オブジェクトの改訂だけを行うことにした。 対象となるのは、
である。 まずは、簡単に完結していそうな「E_Numbers01.spin」を調べた。
- E_Numbers01.spin
- E_TV_Terminal01.spin
- E_TV01.spin
- E_Graphics01.spin
最初に、各オブジェクトのソースをリネームしただけの全プログラムをコンパイルして、 「Object Info」によって、以下のメモリ使用量のスクリーンショットを撮った。 不要な機能を削減したとして、どのくらい「効く」のかをテストするためである。
「E_Numbers01.spin」は、テキスト/ストリングの処理メソッドをごっそり集めたオブジェクトである。 しかし、基本的にビデオ出力のデバッグ画面に、10進あるいは16進でデータを表示する以外の機能については、 それほど使う予定もないので、カットすることにした。 削除したメソッドは以下の2つである。
さらに、使用しないモードの表示フォーマット用データをばっさりと切ってみたが、 以下のように、プログラムが56longs減っただけで、ほとんど影響が無かった。
- PUB FromStr(StrAddr, Format): Num | Idx, N, Val, Char, Base, GChar, IChar, Field
- PRI InBaseRange(Char, Base): Value
続いて、親オブジェクトから呼ばれているもう一つの「TV_Terminal.spin」を調べた。 この中には、テキスト表示のメソッドと、10進、16進、バイナリ、の3種類で数値を表示するメソッドがあったが、 数値表示はE_Numbers01.spinのNum.ToStrメソッドを使ってテキスト表示しているので、 数値表示の部分をカットすることにした。 削除したメソッドは、ビデオ表示のstopを入れて以下の4つである。 しかし以下のように、プログラムが37longs減っただけで、ほとんど影響が無かった。
- PUB stop
- PUB dec(value) | i
- PUB hex(value, digits)
- PUB bin(value, digits)
RAMに広大なグラフィックメモリを確保しているのもここであるが、 とりあえずまずはここは手を付けていない。 続いて、「E_TV01.spin」を調べた。 トップオブジェクトの初期化では、MIDIの入出力と、オーディオについては、 初期化の際にCog番号が返ってきて、それをビデオ表示した。 ところが、ビデオだけは「TV_Terminal.spin」の中で単に「TV.Start」を呼ぶだけで、 何も戻ってこなかった。 これは、そのさらに下の(子の子で孫メソッド)、「E_TV01.spin」の中のstartメソッドを呼んでいたからである。 そこで、この孫メソッドからの返り値としてCog番号を受け取り、 さらに子メソッドとして返すようにして、トップで無事に全ての初期化のCog番号が表示された。
「E_TV01.spin」は機能として、ビデオ出力の「NTSC」「PAL」の両方に対応している。 まずPALを使うことはないのでここをカットしようとしたが、 かなりトリッキーに両方に対応したコードとなっていたので、これは断念した。 Propellerの設計思想として、「NTSC用」「PAL用」などと冗長に両方のプログラムを用意して、 ビデオモードに対応してそのいずれかにジャンプする、などというのはダサイのだろう。 まだまだ、プログラムROMが潤沢にある旧来型のマイコンの発想だったことを反省した。
続いて、「E_Graphics01.spin」を調べた。 これは凄いオブジェクトであり、専用に1つのCogを使って、グラフィックドライバを構成していた。 「TV_Terminal.spin」からは、上記のtv.startと、こちらのためのgr.startの2つのNEWCOGを発令していた。 そこで、この両方から起動Cog番号を返して、 TV_Terminal.spinでは片方を10倍して返して、トップオブジェクトでデコードして、両方を表示するようにした。 カットしたのはstopメソッドぐらいなので、以下のようにちょっとサイズが増えた。
ここまでで、システムを構成しているPropellerソースは以下の8本である。 Cog IDやMIDI入力の情報をリアルタイムにビデオ出力し、 MIDIを送受信し、そのMIDIノートナンバに対応したピッチのサイン波形をオーディオ出力する、 という仕事を、6個のCogsによって実行している。 時分割でも割り込みでもなく各Cogsは自分の仕事を並列処理するシステムである。 これによって、以下のように、ビデオモニタからは、メイン(Cog ID=0)以外の、 全てのCogsの番号が表示されるようにできた。
- メイン exp021.spin
- サウンド出力 AudioOut06.spin
- MIDI入力 MidiIn03.spin
- MIDI出力 MidiOut01.spin
- 数値データ変換 E_Numbers01.spin
- ビデオターミナル出力 E_TV_Terminal01.spin
- ビデオ出力 E_TV01.spin
- グラフィックドライバ E_Graphics01.spin
グラフィックドライバ・オブジェクトの「E_Graphics01.spin」を眺めてみると、 かなり強力なグラフィックメソッドを完備していることが判明した。 これは、ビデオ・インスタレーションにはもってこいである。 ただし現状では、これはトップから見て孫メソッドなので、いちいち「TV_Terminal.spin」を介して制御する必要がある。 初期設定のCog番号を返す方法も、これが原因でちょっとダサいものになっている。 そこで、「E_TV01.spin」と「E_Graphics01.spin」を、 「TV_Terminal.spin」と同じ階層に並べられないか、実験してみた。 これが可能となれば、トップオブジェクトから直接に、グラフィックドライバのメソッドを利用できる筈である。
そして、メインとグラフィクス関係の3つ、の計4本のソースをコピーしてリネームし、 同じ動作を行うかチェックしながら改造した結果、結論として「出来ない」ことが判明した。 具体的には、まず親オブジェクト「exp022.spin」の該当部分は以下である。
{{ exp022.spin }} ・・・・・・・・ OBJ midiIn : "MidiIn03" midiOut : "MidiOut01" audio : "AudioOut06" Num : "E_Numbers02" TV : "E_TV_Terminal02" gr : "E_Graphics02" PUB Main | dummy, para, p1 Num.Init { Num.Init MUST be called before first object use. } para := TV.Start1(12) dummy := TV.Start2 TV.out(0) { display home } TV.Str(string("TV_port_Cog =")) TV.Str(Num.ToStr(para, Num#DEC)) TV.Str(string(", Graphic_driver_Cog =")) TV.Str(Num.ToStr(dummy, Num#DEC)) ・・・・・・・・ repeat dummy := midiIn.event if dummy <> -1 midiOut.fifoset(dummy) TV.Str(Num.ToStr(dummy, Num#HEX7)) if (dummy & $FF00FF) == $900064 ・・・・・・・・ elseif (dummy & $FFFF00) == $B00100 para := dummy & $000003 TV.gr_test(para) ' gr.color(para+1)ここで、子オブジェクト「E_TV_Terminal01.spin」の該当部分は以下である。
OBJ tv : "E_TV02" gr : "E_Graphics02" PUB gr_test(para) gr.color(para+1) PUB start1(basepin) : status '' basepin = first of three pins on a 4-pin boundary (0, 4, 8...) to have '' 1.1k, 560, and 270 ohm resistors connected and summed to form the 1V, '' 75 ohm DAC for baseband video bitmap_base := (@bitmap + $3F) & $7FC0 repeat x from 0 to x_tiles - 1 repeat y from 0 to y_tiles - 1 screen[y * x_tiles + x] := bitmap_base >> 6 + y + x * y_tiles tvparams_pins := (basepin & $38) << 1 | (basepin & 4 == 4) & %0101 longmove(@tv_status, @tvparams, paramcount) tv_screen := @screen tv_colors := @color_schemes status := tv.start(@tv_status) PUB start2 : status status := gr.start gr.setup(x_tiles, y_tiles, 0, y_screen, bitmap_base) gr.textmode(x_scale, y_scale, x_spacing, 0) gr.width(width)また、孫オブジェクト「E_Graphics02.spin」の該当部分は以下である。
親オブジェクトの「exp022.spin」では、OBJブロックで、子オブジェクトだけでなく、 孫オブジェクトも(強制的に)定義している。 そして、このプログラムを実行すると、MIDIコントロールチェンジ0番のデータとして、 子メソッド「TV.gr_test(para)」にパラメータpara(0-3) が渡される。 これは孫メソッド「color(c)」にパラメータcとして渡されるので、 システムとしての動作はこれまでと同じながら、以下のように表示の色だけが変わった。 これは予想される正しい動作である。PUB color(c) '' Set pixel color to two-bit pattern '' c - color code in bits[1..0] setcommand(_color, @colors[c & 3]) 'set colorところで、上記のプログラムで親オブジェクトの最後の行で「'」でコメントアウトしている、 「gr.color(para+1)」のコメントを外して、続けて実行してみると、ビデオ出力がまったく動かなくなった。 MIDI入出力とサウンド生成はそのまま動いているのに、ビデオ表示が止まってしまうのである。 これは、孫オブジェクトに強制的にメッセージを送ったのが、どこかのパラメータを異常に書き換えたためと思われる。 結局、親・子・孫という3階層を2階層にする、という試みは、このままで簡単には実現しない事が判った。 もちろん、「E_TV_Terminal01.spin」の内容を全て親に移動させれば出来るのは当然だが、 それではトップがかなり煩雑になるので、それも避けたい。
・・・結論として、グラフィックドライバは「E_TV_Terminal01.spin」を経由して叩く、ということにした。 それでも、これまで完全なブラックボックスだったライブラリの中を、 ざっと眺めただけとはいえ、オリジナル・オブジェクト化したのは、今後に向けては収穫である。 ビデオドライバのPropellerアセンブラでは、どこかで見たようなジャンプテーブルがあったし、 グラフィックドライバのPropellerアセンブラでは、どこかで見たような座標計算のための数値演算があった。 当然のことだが、これらのソフトウェア部品を有効に使い回すことで、 Propellerのライブラリは効率良く、そして強固に出来上がっているのだった。
2008年4月1日(火)
ちょっとだけPropellerが見えてきた数日前から、 「トランジスタ技術」の編集部の人とやりとりしていたが、 およそ以下のようなことで記事を書く、ということになった。 CQ出版に原稿を書くのはだいぶ久しぶりのことだなぁ。*記事概要 テーマ:Propellerを使ったxxxxの製作 予定ページ数:8ページ程度 予定掲載号:トランジスタ技術 2008年8月号 原稿締め切り日:6/15(要相談) 文字数(目安):約10000字程度 図表点数(目安):12点程度こうなると、いよいよ何かターゲットを決めて、具体的に作っていくことになる。 ちょっと燃えてきたぞ。
手元には40ピンDIPのPropellerチップが10個あるだけなので、 さっそく、ネット経由で部品を注文した。 ところが、シリアルEEPROMの 24LC256 については、この商社は、8ピンDIPを取り扱っていなくて、以下のMSOPというパッケージしかない。
ピン間は0.65ミリである。DIPの0.1インチの2.54mmとはだいぶ違う。 とりあえず、変換基板とレジストも注文してみた。 果たして、うまくハンダ付けできるだろうか。 2008年4月2日(水)
昨日の夕方、17:55にネットで注文したのに、もう今日の昼過ぎには部品が届いた。 さすが「電子部品のアスクル」、ちょっと高いけれども便利な商社である。さて問題は、シリアルEEPROMの0.65ミリというMSOPパッケージと、それをDIPに変換する変換基板である。
出して並べてみると・・・これは小さい(^_^;)。 鼻息で吹き飛んで床に落としたら、もう絶対に発見できそうもない。 これを付けられるだろうか。
とりあえず、道具はいつものハンダ一式。 ただし必殺のツール、「フラックス」だけが頼りである。
そして、息を止めて集中すること数分間。 さすがにフラックスは素晴らしく、最初の挑戦からアッサリと綺麗にハンダ付け出来た(^_^)。 1枚の変換基板に多数のEEPROMを並べるのは次回にするとして、今回は変換基板をカットしてみた。 もちろん、残りの部分にもあと1個は取り付けできる。
さて、それでは製作を進めよう・・・と部品を並べていて、5MHzの水晶振動子を注文し忘れていたことに気付いた。 仕方ないのでネットで注文して、これ以降の製作は、水晶が届く明日にすることにした。
ハードを離れて、Propellerツールに向かい、あらためてグラフィクス関係のライブラリを捜してみると、
という一群の入ったディレクトリを発見した。 こういう場合には「demo」の名前がトップだ、ということで叩いてみると、 なんとビデオモニタにカラーグラフィックのアニメーションが現れた。 テキスト情報表示もいいけれど、これも欲しかったのである。 そして、PS2マウス対応だというMouse.spinはとりあえず不要であり、 TV.spinとGraphics.spinについては、ここまでのPropellerライブラリの傾向から、 おそらく既に調べたものと同一では、という気がした。 そこでとりあえず、
- Graphics_Demo.spin
- Mouse.spin
- TV.spin
- Graphics.spin
となるように整理してみた。 これはかなり簡単な作業であり、やはりE_TV02.spinとE_Graphics02.spinは、 TV.spinとGraphics.spinにそのまま置換できた。 以下はそのムービーである。
- ここまでの最新のexp022.spinはそのままに保留しておく
- Graphics_Demo.spinをコピーしてexp023.spinとリネームして改造
- exp023.spinからの参照はE_TV02.spinとE_Graphics02.spinとするように変更
- E_TV02.spinとE_Graphics02.spinは変更しない
- マウス処理をカット/コメントアウトする
- それでもグラフィックのデモが同様に行われる
そして、画面内では、文字列として定義した「Propeller」というテキストのグラフィック表示も実現していることから、 16進表示についてソフトウェア的に盛り込めば、exp022.spinから呼ばれていたE_TV_Terminal02.spinナシに、 直接にE_TV02.spinとE_Graphics02.spinを参照できることがわかった。
そこで、これまでに制作したMIDI関係とサウンド(サイン波)生成の機能のあるexp022.spinを、 ここでのexp023.spinに統合していくことを目指した。 MIDIの16進表示については、E_Numbers01.spinを使わないで表示することになる。
まずはMIDIの16進表示ナシで、単純に両者のプログラムを合体させたのが、 以下のexp024.spinである。 これで同様のグラフィックをライブ生成しながら、 MIDI入力・MIDI出力・オーディオ生成も同時に実行している。
{{ exp024.spin }} CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 _stack = ($3000 + $3000 + 100) >> 2 'accomodate display memory and stack x_tiles = 16 y_tiles = 12 paramcount = 14 bitmap_base = $2000 display_base = $5000 lines = 5 thickness = 2 VAR long tv_status '0/1/2 = off/visible/invisible read-only long tv_enable '0/? = off/on write-only long tv_pins '%ppmmm = pins write-only long tv_mode '%ccinp = chroma,interlace,ntsc/pal,swap write-only long tv_screen 'pointer to screen (words) write-only long tv_colors 'pointer to colors (longs) write-only long tv_hc 'horizontal cells write-only long tv_vc 'vertical cells write-only long tv_hx 'horizontal cell expansion write-only long tv_vx 'vertical cell expansion write-only long tv_ho 'horizontal offset write-only long tv_vo 'vertical offset write-only long tv_broadcast 'broadcast frequency (Hz) write-only long tv_auralcog 'aural fm cog write-only word screen[x_tiles * y_tiles] long colors[64] byte x[lines] byte y[lines] byte xs[lines] byte ys[lines] OBJ midiIn : "MidiIn03" midiOut : "MidiOut01" audio : "AudioOut06" tv : "E_TV02" gr : "E_Graphics02" PUB start | i, j, k, kk, dx, dy, pp, pq, rr, numx, numchr, dummy, para, p1 'start tv longmove(@tv_status, @tvparams, paramcount) tv_screen := @screen tv_colors := @colors tv.start(@tv_status) 'init colors repeat i from 0 to 63 colors[i] := $00001010 * (i+4) & $F + $2B060C02 'init tile screen repeat dx from 0 to tv_hc - 1 repeat dy from 0 to tv_vc - 1 screen[dy * tv_hc + dx] := display_base >> 6 + dy + dx * tv_vc + ((dy & $3F) << 10) 'init bouncing lines i := 1001 j := 123123 k := 8776434 repeat i from 0 to lines - 1 x[i] := ?j // 64 y[i] := k? // 48 repeat until xs[i] := k? ~> 29 repeat until ys[i] := ?j ~> 29 'start and setup graphics gr.start gr.setup(16, 12, 128, 96, bitmap_base) 'start MIDI/audio midiIn.start(7) midiOut.start(6) audio.start(10, 11) repeat 'clear bitmap gr.clear 'draw spinning triangles gr.colorwidth(3,0) repeat i from 1 to 8 gr.vec(0, 0, (k & $7F) << 3 + i << 5, k << 6 + i << 8, @vecdef) 'draw expanding pixel halo gr.colorwidth(1,k) gr.arc(0,0,80,30,-k<<5,$2000/9,9,0) 'step bouncing lines repeat i from 0 to lines - 1 if ||~x[i] > 60 -xs[i] if ||~y[i] > 40 -ys[i] x[i] += xs[i] y[i] += ys[i] 'draw bouncing lines gr.colorwidth(1,thickness) gr.plot(~x[0], ~y[0]) repeat i from 1 to lines - 1 gr.line(~x[i],~y[i]) gr.line(~x[0], ~y[0]) 'draw spinning stars and revolving crosshairs and dogs gr.colorwidth(2,0) repeat i from 0 to 7 gr.vecarc(80,50,30,30,-(i<<10+k<<6),$40,-(k<<7),@vecdef2) gr.pixarc(-80,-40,30,30,i<<10+k<<6,0,@pixdef2) gr.pixarc(-80,-40,20,20,-(i<<10+k<<6),0,@pixdef) 'draw small box with text gr.colorwidth(1,14) gr.box(60,-80,60,16) gr.textmode(1,1,6,5) gr.colorwidth(2,0) gr.text(90,-72,@pchip) 'draw incrementing digit if not ++numx & 7 numchr++ if numchr < "0" or numchr > "9" numchr := "0" gr.textmode(8,8,6,5) gr.colorwidth(1,8) gr.text(-90,50,@numchr) 'copy bitmap to display gr.copy(display_base) 'increment counter that makes everything change k++ dummy := midiIn.event if dummy <> -1 midiOut.fifoset(dummy) if (dummy & $FF00FF) == $900064 audio.change2(0) p1 := (dummy & $007F00) >> 8 para := f_number[ (p1+4) // 12 ] >> ( (127-p1) / 12 ) audio.change1(para) elseif (dummy & $FF00FF) == $900000 audio.change2(7) elseif (dummy & $FFFF00) == $B00000 para := dummy & $000007 audio.change2(para) elseif (dummy & $FFFF00) == $B00100 para := dummy & $000003 DAT f_number long 1294309430,1371273070,1452813200,1539201960 long 1630727660,1727695770,1830429900,1939272920 long 2054588080,2176760240,2306197130,2443330740 tvparams long 0 'status long 1 'enable long %001_0101 'pins long %0000 'mode long 0 'screen long 0 'colors long x_tiles 'hc long y_tiles 'vc long 10 'hx long 1 'vx long 0 'ho long 0 'vo long 0 'broadcast long 0 'auralcog vecdef word $4000+$2000/3*0 'triangle word 50 word $8000+$2000/3*1+1 word 50 word $8000+$2000/3*2-1 word 50 word $8000+$2000/3*0 word 50 word 0 vecdef2 word $4000+$2000/12*0 'star word 50 word $8000+$2000/12*1 word 20 word $8000+$2000/12*2 word 50 word $8000+$2000/12*3 word 20 word $8000+$2000/12*4 word 50 word $8000+$2000/12*5 word 20 word $8000+$2000/12*6 word 50 word $8000+$2000/12*7 word 20 word $8000+$2000/12*8 word 50 word $8000+$2000/12*9 word 20 word $8000+$2000/12*10 word 50 word $8000+$2000/12*11 word 20 word $8000+$2000/12*0 word 50 word 0 pixdef word 'crosshair byte 2,7,3,3 word %%00333000,%%00000000 word %%03020300,%%00000000 word %%30020030,%%00000000 word %%32222230,%%00000000 word %%30020030,%%02000000 word %%03020300,%%22200000 word %%00333000,%%02000000 pixdef2 word 'dog byte 1,4,0,3 word %%20000022 word %%02222222 word %%02222200 word %%02000200 pchip byte "Propeller",0 'textとりあえずここまで動けば、グラフィクスのパラメータをサウンドと同時に制御することで、 まさにマルチメディア・インスタレーションの入口に立ったことになる。 明日からが楽しみになってきた。
2008年4月3日(木)
新学期の準備に追われているうちに午後になり、昨日注文していた水晶も届いた。 とりあえず、オリジナルのPropeller回路が動くかどうかまでは、作って実験してみよう。 まずは電源である。 マルツの小型スイッチングACアダプタの「+5V 2A」を使って、 +5Vラインは3端子レギュレータでなく直接、この出力が使えないか、試してみることにした。 +5Vを受けて3.3Vにしているのは、LM3940である。 パスコンは、参考回路図には「1000μF」とあったが、たまたま手元に「1F」という強烈なのがあったので、 これを+5Vと+3.3Vのラインに入れて、積層セラミックコンデンサの105をここにパラってみた。とりあえず電圧はちゃんと出たので、Propellerマニュアルにあった、 以下の回路図に従って、シリアルEEPROMとPropellerクリップを接続してみた。
いつもは小さく感じるリセットスイッチが大きく感じるのは不思議だが、こんなカンジになった。
ここでとりあえず、パソコンPropeller Toolに移動して、 これまで使っていたPropeller Demo BoardからUSBケーブルを外して、 この試作基板に接続されたPropellerクリップに繋いでみた。 すると、WindowsはこちらにのPropellerクリップも、自動的に新しいハードウェアとして認識した。 そしてPropeller Toolのハードウェア認識メニューを走らせると、 以下のように、これまでのCOM4から新しいCOM5となって、無事に認識した。
Propeller Toolから、これまでに作ったプログラムをPropellerのRAMに転送するのも成功、 そしてVerifyチェックまである「EEPROMに転送」の実行もエラーなく成功した。 無事に、昨日の微細ハンダ付けのシリアルEEPROMは稼動しているようである。 あとはMIDI、オーディオ、ビデオのハードを作ればいいが、ちょっとビデオについては検討の余地があるので、 今日はここまで、とした。 自分でハンダ付けしたハードウェアが動くというのは、出来合いのDemoボードが当然のように動くのとはまた違って、 嬉しいものだ。
2008年4月4日(金)
トラ技の記事の製作例にするシステムについて考えてみた。 編集部の希望として、というのがあった。 またこちらからのメイルの中に
- Propellerの特長(処理能力)活かした製作記事
- 並列処理をハードウェアで行えることががPropellerの特長のひとつ
→そのような処理対象を製作テーマにできれば- あまり肩肘張った記事では読者から敬遠される
- 遊び心も多少含めて
若いエンジニアに「発想の転換CPU」「温故知新CPU」として紹介したい と書いたところ、 「記事中にコラムを設けて,1000字程度で触れるか、本文中にさりげなく含めて」 と、こちらも了解をもらった。 これを受けて編集部に提案したのは、
というテーマである。 AKI-H8やGAINERを使って、ホストPCにMax/MSP/jitterやFLASHやprocessingを使えば、 かなりのことが出来るのは当然として、 ここでは是非、「パソコン無しでソコソコの仕事をする」システムとしてみたい。 Propellerの8つのCogsがそれぞれの仕事をテキパキとこなして、 外界との関係性のアルゴリズムをspinでサクサクと作れる、 Propeller Toolによって、現場でもデバッグや改訂が出来る、 というあたりをアピールするのが目標である。
- 製作例として「インスタレーション」ということでいきたい
- 科学館などで体験できる、「不思議なビデオアート装置」みたいなイメージ
まだ「マイク入力」のサンプルプログラムと回路の実験をしていないが、 アナログの入力には1つのCogと2本の入出力ピンが必要なのは分かっている。 センサの入力はアナログであればこれと同等だし、 クロックタイプのセンサであれば、MIDI入力と同様にソフトウェアでモニタするので、 入力の1ピンだけで十分である。
これらをまとめて、とりあえず現状でのシステム構成要素として、 Propellerシステムを構成する機能ブロックを以下のように整理してみた。 これらを取捨選択してシステムを設計する、ということになる。
このような各要素に対して、Propeller全体としての制約条件は
- メイン処理
- Cog(0)を使用する
- プログラム用に入出力ピン4本を使用する
(P30/31 - ホスト、P28/29 - シリアルEEPROM)- ディジタル入力(スイッチ等)は直接にポートをモニタできる
- ディジタル出力(LED/リレー等)は直接にポートをドライブできる
- ビデオ出力のフレームレートごとの変化を担当
- MIDI入力FIFOをモニタしてイベントがあれば判定処理
- MIDI出力したい情報はMIDI出力FIFOに積んでおくだけ
- MIDI入力
- RS232Cなどシリアル入力でも同等
- 1個のCogを使用する
- 入力ピン1本を使用する
- 外付回路はフォトカプラとTR1個
- ソフトウェア的にMIDI信号をモニタして受信
- MIDIイベントFIFOに積んでMainがモニタする
- MIDI出力
- RS232Cなどシリアル出力でも同等
- 1個のCogを使用する
- 出力ピン1本を使用する
- 外付回路は74HC05を1個
- ソフトウェア的にMIDI信号を上げ下げする
- Mainが積んだMIDI送信FIFOから出力する
- サウンド出力
- 1個のCogを使用する
- 出力ピン2本を使用する→ステレオ(逆相)出力
- 外付回路はRCフィルタのみ
- Cog内部カウンタをPWMモードで使用する
- サンプリングは44.1KHz楽勝
- ソフトウェアでサウンド生成(シンセサイズ)できる
- 高精度のサイン波テーブル内蔵
- ビデオ出力(NTSC)
- 2個のCogを使用する(グラフィックドライバ/ビデオ信号スキャン)
- 出力ピン3本を使用する
- 出力ピンは [4n] [4n+1] [4n+2] の連続した3本であること
- 外付回路は3本の抵抗のみ→ミックスしてビデオ信号化
- ビデオチップもフレームメモリも不要で、ソフトウェアでビデオを生成
- ビデオ生成の詳細の解析はまだ(^_^;)
- アナログ入力
- 1個のCogを使用する
- 入力ピン2本を使用する
- 外付回路はRCのみ、外部A/Dは不要
- アナログ入力の詳細の解析はまだ(^_^;)
ということになる。 これまでに制作したインスタレーションなどの例であれば、 AKI-H8やI-CubeやGAINERの置き換え用途としては
- Cogは全部で8個まで
- 入出力ピンは全部で32本まで
ということになる。 これだと、センサ入力として1つのCogで複数のチャンネルを管理するのを別にして、 アナログ入力は5種類、ないし6種類の異なったタイプ(サンプリング周期、データ形態、 ソフトウェア後処理、等)を混在できそうである。
- MainのCog(0)
- MIDI入力
- MIDI出力
- アナログ入力(センサ用)
AKI-H8やI-CubeやGAINERでは困難な、 「パソコン無しのスタンドアロン」「ビデオ出力」「ステレオオーディオ出力」の機能を盛り込むとなると、 Propellerシステムの選択肢は俄然、増えてくることになる。 ここでは、欲張った場合にはピン数やCog数の制約から、 1つのPropellerで実現する制限が出て来るだろう。 ただしこれは、他の組み込みマイコンではちょっと手が届かない、贅沢な制約である。 例えば、
というようなシステムであれば、Cogは8個全部、 そしてセンサ入力に多数を使用しても、ピン数はなんとかなりそうである。 これをパソコン無しに実現し、マイコンシステムで悩む「割り込み」「マルチタスクモニタ」等が不要、 ということであれば、まさにPropellerの実力は鮮明になるだろう。 そこで今回のシステムとしては、このあたりを目指すことにした。
- MainのCog(0)
- (MIDI入力)(センサ用/デバッグ用)
- ビデオ出力[1]
- ビデオ出力[2]
- ディジタル入力(センサ用)
- アナログ入力(センサ用)
- ステレオオーディオ出力(リアルタイム・シンセサイザ)
システムの方針が決まったので、試作基板の製作を続けることにした。 最終的には使わないとしても、せっかくオリジナル外付回路を作ったので、 MIDI INとMIDI OUTも製作し、目玉としてビデオ出力回路を2チャンネル、搭載することにした。 同時に2系統の異なる映像信号を生成するシステムというのは、パソコンでもなかなか大変だが、 Propellerであればもう1つのCogを映像生成に指定するだけの筈である。 オーディオはヘッドホンアンプでなく、ステレオミニジャックからのライン出力である。
まず最初に、Propeller Demo Boardのブレッドボード領域に製作した、 以下のMIDI IN回路とMIDI OUT回路とを、この新しい試作基板の上に作ってみた。
基板上ではこんなものである。
まずここまで作ったところで、Propeller Demo Boardと取り替えて、動作確認した。 ポートのピンの位置については、オーディオも含めて、以下のように定義した。
これに合わせて、ビデオも含めてポート(ピン)番号の定義がこれまでのPropeller Demo Boardと違ってくるので、 Topオブジェクトのファイル名については、新しい試作基板のものをまったく新しいものに変更した。 該当する変更部分は以下となる。
- P30/31 - ホスト接続用
- P28/29 - シリアルEEPROM用
- P27 - MIDI Input
- P26 - MIDI Output
- P24/25 - Audio出力(ステレオ)
OBJ midiIn : "MidiIn03" midiOut : "MidiOut01" v : "AudioOut06" PUB Main | dummy, para, p1 TV.Str(string("Audio_output_Cog =")) dummy := v.start(24,25) TV.Str(Num.ToStr(dummy, Num#DEC)) TV.Str(string(", MIDI_input_Cog =")) dummy := midiIn.start(27) TV.Str(Num.ToStr(dummy, Num#DEC)) TV.Str(string(", MIDI_output_Cog =")) dummy := midiOut.start(26) TV.Str(Num.ToStr(dummy, Num#DEC))そして、以下のように無事にここまでは動いた。 まだビデオ出力は無いので、Max/MSPからMIDIを出して、 それがPropellerのソフトスルーでMIDIとして戻ることで確認した。
これに続いて、オーディオと、2系統のビデオ出力の回路を増設した。 オーディオは、とりあえずPropellerの出力から4.7KΩで受けて、GNDへは0.1μF、出力へは10μFで出した。 マニュアルとかのドキュメントによって、だいぶ数値のばらつきがあり、抵抗では10KΩと220Ωの両方の記事があった。 ビデオのピンの位置については、以下のように定義した。 片方をPropeller Demo Boardと同じにして、他のポートを8ビット単位で空けておくためである。
1.1KΩの抵抗が無かったので2.2KΩを並列にして、基板上ではこんなものである。
- P8/9/10 - ビデオ出力(A)
- P12/13/14 - ビデオ出力(B) - Propeller Demo Boardと同じ
ビデオ出力の片方をPropeller Demo Boardと同じにしたので、 同じプログラムで、とりあえずビデオ出力(B)は簡単に出てくれた。
あとはビデオ出力の基準(base)ピン番号を変えればビデオ出力(A)が出る・・・と思ったところで、ひっかかった。 Topオブジェクトからは、初期化の時に、明示的にビデオ出力のピン番号を指定していないのである。 ここで、リネームしたTopプログラムの proto001.spin と、ビデオ出力のために参照している E_TV02.spin との中から、関係していそうな部分を調べてみた。
proto001.spinの中で関係しそうなところは、
VAR long tv_status, tv_enable, tv_pins, tv_mode, tv_screen long tv_colors, tv_hc, tv_vc, tv_hx, tv_vx, tv_ho, tv_vo long tv_broadcast, tv_auralcog word screen[x_tiles * y_tiles] long colors[64] byte x[lines], y[lines], xs[lines], ys[lines] PUB start | i, j, k, kk, dx, dy, pp, pq, rr, numx, numchr, dummy, para, p1, thickness 'start tv longmove(@tv_status, @tvparams, paramcount) tv_screen := @screen tv_colors := @colors tv.start(@tv_status) ・・・・・・・・ DAT ・・・・・・・・ tvparams long 0 'status long 1 'enable long %001_0101 'pins long %0000 'mode long 0 'screen long 0 'colors long x_tiles 'hc long y_tiles 'vc long 10 'hx long 1 'vx long 0 'ho long 0 'vo long 0 'broadcast long 0 'auralcogあたりである。 初期化のtv.start(@tv_status)で渡すパラメータのポインタtvparamsには、 「pins」として「%001_0101」という謎の数値があった。 Propeller Demo BoardではP12-14がビデオ出力なので、 「12」すなわち「%1100」という文字列があれば、と期待したが、これは外れた。
そこでE_TV02.spinの中で関係しそうなところを調べると、
・・・・・・・・ ''VAR 'TV parameters - 14 contiguous longs '' long tv_status '0/1/2 = off/invisible/visible read-only '' long tv_enable '0/non-0 = off/on write-only '' long tv_pins '%pppmmmm = pin group, pin group mode write-only '' long tv_mode '%tccip = tile,chroma,interlace,ntsc/pal write-only '' long tv_screen 'pointer to screen (words) write-only '' long tv_colors 'pointer to colors (longs) write-only '' long tv_ht 'horizontal tiles write-only '' long tv_vt 'vertical tiles write-only '' long tv_hx 'horizontal tile expansion write-only '' long tv_vx 'vertical tile expansion write-only '' long tv_ho 'horizontal offset write-only '' long tv_vo 'vertical offset write-only '' long tv_broadcast 'broadcast frequency (Hz) write-only '' long tv_auralcog 'aural fm cog write-only ''The preceding VAR section may be copied into your code. ''After setting variables, do start(@tv_status) to start driver. ''All parameters are reloaded each superframe, allowing you to make live ''changes. To minimize flicker, correlate changes with tv_status. ''Experimentation may be required to optimize some parameters. ''Parameter descriptions: ・・・・・・・・ '' tv_pins '' '' bits 6..4 select pin group: '' %000: pins 7..0 '' %001: pins 15..8 '' %010: pins 23..16 '' %011: pins 31..24 '' %100: pins 39..32 '' %101: pins 47..40 '' %110: pins 55..48 '' %111: pins 63..56 '' '' bits 3..0 select pin group mode: '' %0000: %0000_0111 - baseband '' %0001: %0000_0111 - broadcast '' %0010: %0000_1111 - baseband + chroma '' %0011: %0000_1111 - broadcast + aural '' %0100: %0111_0000 broadcast - '' %0101: %0111_0000 baseband - '' %0110: %1111_0000 broadcast + aural - '' %0111: %1111_0000 baseband + chroma - '' %1000: %0111_0111 broadcast baseband '' %1001: %0111_0111 baseband broadcast '' %1010: %0111_1111 broadcast baseband + chroma '' %1011: %0111_1111 baseband broadcast + aural '' %1100: %1111_0111 broadcast + aural baseband '' %1101: %1111_0111 baseband + chroma broadcast '' %1110: %1111_1111 broadcast + aural baseband + chroma '' %1111: %1111_1111 baseband + chroma broadcast + aural '' ----------------------------------------------------------- '' active pins top nibble bottom nibble '' '' the baseband signal nibble is arranged as: '' bit 3: chroma signal for s-video (attach via 560-ohm resistor) '' bits 2..0: baseband video (sum 270/560/1100-ohm resistors to form 75-ohm 1V signal) '' '' the broadcast signal nibble is arranged as: '' bit 3: aural subcarrier (sum 560-ohm resistor into network below) '' bits 2..0: visual carrier (sum 270/560/1100-ohm resistors to form 75-ohm 1V signal)これによれば、Propeller Demo BoardのP12-14では、 「%001_0101」というのは、ppp=%001、mmmm=%0101、ということになる。 確かに「pins 15..8」のグループなので、おそらくモードは「baseband」なのだろうと推定した。 さて問題は、ビデオ出力(A)では、同じ「pins 15..8」のグループで、上位4ビットでなくて、 下位41ビットに移動したいのである。 つまり、ppp=%001は同じ筈であり、mmmmをどこにするか、という事になる。
ここで、Propellerの データシート の中から、以下の部分を思い出した。
mmmmのビット3については、chroma signalやaural subcarrierのビットでここでは関係ないので、 同じグループの下位で「baseband」にするには、mmmm=0000であろう、と判断して、 proto001.spinのDAT領域の該当部分を「%001_0000」としてみた。 すると、一発でもう片方のビデオモニタから映像が出て、あっさり解決してしまった。 やはり、苦労してPropellerアセンブラのソースを読んできた甲斐があった。
ここで結論として、とりあえずproto001.spinのDAT領域の該当部分に設定する値は、
ということになった。 ただし、これでは排他的にしかビデオ信号が出ない。 PropellerのCogとして2系統を並列動作させる必要がある。 ちょっとトライした感じでは、Propeller Toolから「Run Time Error」が出て、単純に タスクをダブルで呼び出す・・・というカンジでは行かないようである。 いよいよ、難しいところに入ってきたのだろうか。
- ビデオ出力(A) - P8..10 = [001_0000]
- ビデオ出力(B) - P12..14=[001_0101]
Propeller日記(5) へ
「日記」シリーズ の記録