NNGSAとのRMS比較をやる。NNGSAが低いのなら後段でSAかませたい。 残差の振幅分布を見ると、やっぱNGSAの方が良い。0付近へよくデータが集まる。 RMSEもNGSAのほうが良かった。。。

うーん、変だ。確かにSLAのときも全く同様の現象を見たんだけど。 実験ではNGSAとても悪かったはずなんだがなあ。。。

グダグダしてのんびりとしている。根本的な改善策がないか、過去のブログを見直してみる。

気になったのはTTAのエンコード部分。再帰的ライスを使っているのは知っていたが、さらに、指数移動平均によって平均を更新していることが分かった。

// aiki: 1つの残差出力
__forceinline void tta_encoder_put_value(TTA_adapt *rice, TTAint32 value) {
      TTAuint32 k, unary, outval;

  // aiki: 符号付き整数を符号なし整数に変換(非負数は奇, 負数は偶)
      outval = ENC(value);

      // encode Rice unsigned
      k = rice->k0;

  // aiki: 指数移動平均式で平均を更新: sum0 <- (15/16) * sum0 + outval
      rice->sum0 += outval - (rice->sum0 >> 4);
  // aiki: shift_16[x] は 2^(4 + x) = 16 * 2^(x) を計算するテーブル.
      if (rice->k0 > 0 && rice->sum0 < shift_16[rice->k0])
    // aiki: k0 > 0 && sum0 < 16 * 2^(k0) ならば k0 が大きいので減らす
              rice->k0--;
      else if (rice->sum0 > shift_16[rice->k0 + 1])
    // aiki: sum0 > 32 * 2^(k0) ならば k0 が小さいので増やす
              rice->k0++;

      if (outval >= bit_shift[k]) {
    // aiki: 出力値が 2^(k0) 以上ならば
    // aiki: 出力値を引く: outval -= 2^(k0)
              outval -= bit_shift[k];
              k = rice->k1;

    // aiki: 指数移動平均式で平均を更新: sum1 <- (15/16) * sum1 + outval
              rice->sum1 += outval - (rice->sum1 >> 4);
              if (rice->k1 > 0 && rice->sum1 < shift_16[rice->k1])
      // aiki: k1 > 0 && sum1 < 16 * 2^(k1) ならば k1 が大きいので減らす
                      rice->k1--;
              else if (rice->sum1 > shift_16[rice->k1 + 1])
      // aiki: sum0 > 32 * 2^(k1) ならば k1 が小さいので増やす
                      rice->k1++;

    // aiki: unaryの計算
              unary = 1 + (outval >> k);
      } else { // aiki: elseをブロックで囲った
    // aiki: 出力値が 2^(k0) より小さければ、unaryは0
    unary = 0;
  }

  // aiki: static変数の補足
  // aiki: enc_fifo_bcount: static変数。キャッシュ内のビットカウント。
  // aiki: enc_fifo_bcache: static変数。キャッシュ内のビット。

      // put unary
  // aiki: unaryの連続した1を出力する
      do {
    // aiki: キャッシュ内に1バイト以上データがあるなら1バイト未満になるまで出力
              while (enc_fifo_bcount >= 8) {
                      write_byte(enc_fifo_bcache);
                      enc_fifo_bcache >>= 8;
                      enc_fifo_bcount -= 8;
              }

              if (unary > 23) {
      // aiki: bit_mask[x]は下位x[bit]に1が立ったマスク
      // aiki: unaryが24以上ならば, 23bit分をマスクで一気に書き出す
      // aiki: 24が特別になっているのは, これに7bit以上のデータがあるとオーバーランするから
                      enc_fifo_bcache |= bit_mask[23] << enc_fifo_bcount;
                      enc_fifo_bcount += 23;
                      unary -= 23;
              } else {
      // aiki: マスクを使って一気に書き出す
                      enc_fifo_bcache |= bit_mask[unary] << enc_fifo_bcount;
                      enc_fifo_bcount += unary + 1;
                      unary = 0;
              }
      } while (unary);

      // put binary
  // aiki: キャッシュ内に1バイト以上データがあるなら1バイト未満になるまで出力
      while (enc_fifo_bcount >= 8) {
              write_byte(enc_fifo_bcache);
              enc_fifo_bcache >>= 8;
              enc_fifo_bcount -= 8;
      }

      if (k) {
    // aiki: Rice符号の剰余部出力: k(k0 or k1)が1以上であれば、outvalの下位k[bit]を出力
              enc_fifo_bcache |= (outval & bit_mask[k]) << enc_fifo_bcount;
              enc_fifo_bcount += k;
      }
} // tta_encoder_put_value

// aiki: 1つの残差を復号
__forceinline TTAint32 tta_decoder_get_value(TTA_adapt *rice) {
      TTAuint32 k, level, tmp;
      TTAint32 value = 0;

      // decode Rice unsigned
  // aiki: unary部を復号し連続した1の数をvalueに
  // aiki: バイト単位の読み出し
      if (!(dec_fifo_bcache ^ bit_mask[dec_fifo_bcount])) {
              value += dec_fifo_bcount;
              dec_fifo_bcache = read_byte();
              dec_fifo_bcount = 8;
              while (dec_fifo_bcache == 0xff) {
                      value += 8;
                      dec_fifo_bcache = read_byte();
              }
      }

  // aiki: 残ったビット分のカウント
      while (dec_fifo_bcache & 1) {
              value++;
              dec_fifo_bcache >>= 1;
              dec_fifo_bcount--;
      }
      dec_fifo_bcache >>= 1;
      dec_fifo_bcount--;

  // aiki: valueが0ならlevelは0でk0, それ以上ならlevelは1でk1をつかう
      if (value) {
              level = 1;
              k = rice->k1;
              value--;
      } else {
              level = 0;
              k = rice->k0;
      }

  // aiki: Rice符号の剰余部取得: k(k0 or k1)が1以上であれば、outvalの下位k[bit]を出力
      if (k) {
    // aiki: キャッシュがk[bit]以上になるまでバイト単位で読み込み
              while (dec_fifo_bcount < k) {
                      tmp = read_byte();
                      dec_fifo_bcache |= tmp << dec_fifo_bcount;
                      dec_fifo_bcount += 8;
              }
    // aiki: Rice符号の取得値の確定。右辺のvalueはunaryと同等。
              value = (value << k) + (dec_fifo_bcache & bit_mask[k]);
    // aiki: k[bit]読み込み後のキャッシュ更新
              dec_fifo_bcache >>= k;
              dec_fifo_bcount -= k;
              dec_fifo_bcache &= bit_mask[dec_fifo_bcount];
      }

      if (level) {
    // aiki: 指数移動平均式で平均を更新: sum1 <- (15/16) * sum1 + outval
              rice->sum1 += value - (rice->sum1 >> 4);
              if (rice->k1 > 0 && rice->sum1 < shift_16[rice->k1])
      // aiki: k1 > 0 && sum1 < 16 * 2^(k1) ならば k1 が大きいので減らす
                      rice->k1--;
              else if (rice->sum1 > shift_16[rice->k1 + 1])
      // aiki: k1 > 0 && sum1 < 16 * 2^(k1) ならば k1 が大きいので減らす
                      rice->k1++;
    // aiki: 出力値を足す: outval += 2^(k0)
              value += bit_shift[rice->k0];
      }

  // aiki: 指数移動平均式で平均を更新: sum0 <- (15/16) * sum0 + outval
      rice->sum0 += value - (rice->sum0 >> 4);
      if (rice->k0 > 0 && rice->sum0 < shift_16[rice->k0])
    // aiki: k0 > 0 && sum0 < 16 * 2^(k0) ならば k0 が大きいので減らす
              rice->k0--;
      else if (rice->sum0 > shift_16[rice->k0 + 1])
    // aiki: sum0 > 32 * 2^(k0) ならば k0 が小さいので増やす
      rice->k0++;

  // aiki: 符号なし整数を符号付き整数に変換
      value = DEC(value);

      return value;
} // tta_decoder_get_value

ビット出力がよく洗練されている... static変数にしているのはむしろ扱いやすくしているのかもしれない。やっぱ俺はまだまだだ...

単独のフィルタでは伸び悩みを感じてきたので、奥の手たるカスケードを召喚した。組み合わせとしてはNGSA->SA, SA->NGSA, NGSA->NGSA, SA->SAがあったが、NGSA->SAが最もよく、フィルタ次数とブロックサイズ設定よってはwavpack(-hh)を超える結果を出したので、採用する。

良くなる理由は、カスケード接続が一般によい手段であることが指摘されている他、NGSAで相関除去が強く動くからではないかと想像している。

コード整理。NNGSAを廃止。1サンプル単位で予測できるように実装する。 static 関数とするのでインライン展開が効くはず。整理していたらすべてin-placeでやりきれそうに見えた。やってしまっていいか、寝てからやってしまおう。

AR係数次数0にするとSAになるわけだが、性能が良かったりする。。。。。もう少し検証必須。 また、SAは次数小さくしたほうが良い結果が出る。4とか、NGSAの半分とか。

係数の観察もしたい。丸めの影響も見たいところ。

TODO:

  • 全部in-placeにしてバッファ1つでやりきるように整理
  • SAのフィルタ次数策定