エコーを使う

DSPのエコーを設定して、音に残響を付けます。

エコーの仕組み

エコーは、DSPの出力の一部をAPUのRAMにためておき、一定の時間(ディレイ)のあとで、もう一度出力に混ぜる機能です。
ためた音をフィルタに通してからもう一度ためる(フィードバック)ので、音が何度も繰り返しながら小さくなっていきます。

項目レジスタ内容
ディレイの長さEDL16ミリ秒単位(0~15)。バッファの大きさは EDL × 2KB
バッファの場所ESAバッファの先頭のページ(アドレスの上位8ビット)
フィードバックの量EFB符号付き8ビット。大きいほど長く残る
エコーの音量EVOLL・EVOLR左右の音量(符号付き8ビット)
エコーをかけるチャネルEONチャネルごとのビット
フィルタFIR0~FIR78つの係数(符号付き8ビット)
バッファへの書き込みFLGのビット51で書き込みを禁止する

バッファはAPUのRAMの中に取られ、エコーを使っている間、DSPが書き換え続けます。
プログラムや波形サンプルと重ならない場所に置いてください。EDLが15なら30KB(64KBのRAMのほぼ半分)を使います。

設定の手順

FLGの説明のとおり、次の順に設定します。

  1. FLGのビット5をセットして、バッファへの書き込みを禁止する。
  2. EDLとESAを設定する。
  3. バッファの全体を0で埋める。
  4. フィルタ・フィードバック・音量・EONを設定する。
  5. FLGのビット5をクリアして、書き込みを許可する。

バッファを0で埋めずに書き込みを許可すると、RAMに残っていた値が雑音として鳴ります。
また、EDLを変えても、新しい長さがすぐに使われるとは限りません。
手順2から手順5までの間は、EDLの最大の長さ(240ミリ秒)程度待つようにしておくと安全です。

; エコーを設定する(バッファは $C000~$DFFF、ディレイ64ミリ秒)
; 作業用: $20・$21
SetupEcho:
    MOV A, #$6C
    MOV Y, #$20
    MOVW $F2, YA    ; FLG: バッファへの書き込みを禁止する
    MOV A, #$7D
    MOV Y, #$04
    MOVW $F2, YA    ; EDL: 4 × 16ミリ秒(バッファ 8KB)
    MOV A, #$6D
    MOV Y, #$C0
    MOVW $F2, YA    ; ESA: バッファの先頭 = $C000

    ; バッファ($C000~$DFFF)を0で埋める
    MOV $20, #$00
    MOV $21, #$C0
    MOV A, #$00
    MOV Y, A
.clear
    MOV ($20)+Y, A
    INC Y
    BNE .clear
    INC $21
    CMP $21, #$E0
    BNE .clear

    ; フィルタ: 8つの係数をすべて $08 にする(合計 $40 で、全体の大きさは変わらない)
    MOV A, #$0F     ; FIR0
.fir
    MOV Y, #$08
    MOVW $F2, YA
    CLRC
    ADC A, #$10     ; FIR1~FIR7 は $1F, $2F, …, $7F
    BPL .fir

    MOV A, #$0D
    MOV Y, #$40
    MOVW $F2, YA    ; EFB: フィードバック = 0.5倍
    MOV A, #$2C
    MOV Y, #$30
    MOVW $F2, YA    ; EVOLL
    MOV A, #$3C
    MOVW $F2, YA    ; EVOLR(Yは$30のまま)
    MOV A, #$4D
    MOV Y, #$01
    MOVW $F2, YA    ; EON: チャネル0にエコーをかける

    ; 少し待ってから書き込みを許可する(タイマー0で約250ミリ秒)
    MOV $FA, #80    ; 10ミリ秒ごと
    MOV $F1, #$01   ; タイマー0を有効にする
    MOV A, $FD      ; カウンタを読んで0にしておく
    MOV X, #25
.wait
    MOV A, $FD
    BEQ .wait
    DEC X
    BNE .wait

    MOV A, #$6C
    MOV Y, #$00
    MOVW $F2, YA    ; FLG: 書き込みを許可する(リセット・ミュートも解除)
    RET

FIRの係数のレジスタは、FIR0が$0F、FIR1が$1F、…、FIR7が$7Fです。
$10ずつ増やし、$8Fになったところ(最上位ビットが1になる)でループを終えています。

タイマーの使い方はAPUのタイマーで間隔を作るを参照してください。
CONTROL($F1)にこの値を書き込むと、IPL ROMも見えなくなります。

フィルタとフィードバック

フィルタの係数は、ためた音のうち、どの周波数の成分をどれだけ残すかを決めます。
上の例のように、すべての係数を同じ値にすると、繰り返すたびに音がこもっていく(高い音が弱くなる)エコーになります。
係数の合計が$40(64)を超えると、繰り返すたびに音が大きくなることがあるので注意してください。

フィードバックを大きくすると、エコーが長く残ります。
フィルタと合わせて、繰り返すたびに音が大きくなるような設定にすると、音が止まらなくなります(ハウリング)。

エコーのあり・なしの比較

矩形波を0.3秒鳴らしてキーオフしたときの音の大きさです(エコーありは上のSetupEchoの設定)。
エコーなしではキーオフですぐに消えますが、エコーありでは64ミリ秒ごとに音が繰り返し、だんだん小さくなっていきます。
鳴っている間も、エコーの分だけ音が大きくなっています。エミュレーターで鳴らした音の振幅を測ったものです。

コードから構造を読み取る

  • DSPの$7D(EDL)・$6D(ESA)を設定したあと、広い範囲のRAMを0で埋めるループがあれば、エコーのバッファの準備です。ESAの値 × $100 から EDL × 2KB が、エコーに使われている(ほかのデータを置けない)領域です。
  • $0F・$1F・…・$7Fに続けて値を書き込んでいれば、FIRフィルタの係数の設定です。
  • サウンドドライバを解析するときは、エコーのバッファの位置と大きさを先に確かめておくと、残りのRAMのどこにデータを置けるかが分かります。

参照

音を鳴らす
FLG レジスタ
EDL レジスタ
ESA レジスタ
EFB レジスタ
FIRx レジスタ
EON レジスタ