自分のやってることに近いことが分かっているのでMonkey's Audioの実装を見てみよう。まず、NewPredictor.cppの予測処理。

// aiki: サンプルnA, nBを圧縮。ステレオ信号ならnA, nB両方にデータが入る
// aiki: 片方のチャンネルが無音の場合はnBは0になる。
// aiki: nA, nBの呼び分けは CAPECompressCore::EncodeFrame にて行われる
int64 CPredictorCompressNormal::CompressValue(int64 nA, int64 nB)
{
    // roll the buffers if necessary
    if (m_nCurrentIndex == WINDOW_BLOCKS)
    {
        m_rbPrediction.Roll(); m_rbAdapt.Roll();
        m_nCurrentIndex = 0;
    }

    // stage 1: simple, non-adaptive order 1 prediction
    // aiki: 固定係数フィルタによる予測。これはTTAと全く同じ(!)プリエンファシスフィルタ
    nA = m_Stage1FilterA.Compress(nA);
    nB = m_Stage1FilterB.Compress(nB);

    // stage 2: adaptive offset filter(s)
    // aiki: 適応フィルタによる予測。コードを見る限り4係数
    m_rbPrediction[0] = nA;
    m_rbPrediction[-2] = m_rbPrediction[-1] - m_rbPrediction[-2];

    m_rbPrediction[-5] = nB;
    m_rbPrediction[-6] = m_rbPrediction[-5] - m_rbPrediction[-6];

    int64 * paryM = &m_aryM[8];

    int64 nPredictionA = (m_rbPrediction[-1] * paryM[0]) + (m_rbPrediction[-2] * paryM[-1]) + (m_rbPrediction[-3] * paryM[-2]) + (m_rbPrediction[-4] * paryM[-3]);
    int64 nPredictionB = (m_rbPrediction[-5] * paryM[-4]) + (m_rbPrediction[-6] * paryM[-5]) + (m_rbPrediction[-7] * paryM[-6]) + (m_rbPrediction[-8] * paryM[-7]) + (m_rbPrediction[-9] * paryM[-8]);

    int64 nOutput = nA - ((nPredictionA + (nPredictionB >> 1)) >> 10);

    // adapt
    // aiki: 出力の符号を取る
    // aiki: ((x >> 30) & 2) - 1 は符号関数の符号を反転したもの
    // aiki: (x >> 30)で0(>0), -1(<0)となり, (x >> 30) & 2で0(>0), 2(<0),
    // aiki: ((x >> 30) & 2) - 1 によって-1(>0), 1(<0)
    m_rbAdapt[0] = (m_rbPrediction[-1]) ? ((m_rbPrediction[-1] >> 30) & 2) - 1 : 0;
    m_rbAdapt[-1] = (m_rbPrediction[-2]) ? ((m_rbPrediction[-2] >> 30) & 2) - 1 : 0;
    m_rbAdapt[-4] = (m_rbPrediction[-5]) ? ((m_rbPrediction[-5] >> 30) & 2) - 1 : 0;
    m_rbAdapt[-5] = (m_rbPrediction[-6]) ? ((m_rbPrediction[-6] >> 30) & 2) - 1 : 0;

    // aiki: 係数の更新。残差符号によって更新符号が変わる。
    // aiki: すなわちSign-Algorithmで出力フィードバックしている。
    if (nOutput > 0)
    {
        int64 * pM = &paryM[-8]; int64 * pAdapt = &m_rbAdapt[-8];
        EXPAND_9_TIMES(*pM++ -= *pAdapt++;)
    }
    else if (nOutput < 0)
    {
        int64 * pM = &paryM[-8]; int64 * pAdapt = &m_rbAdapt[-8];
        EXPAND_9_TIMES(*pM++ += *pAdapt++;)
    }

    // stage 3: NNFilters
    // aiki: CNNフィルタによる予測。圧縮オプションにより使うフィルタ段数が異なる
    if (m_pNNFilter)
    {
        nOutput = m_pNNFilter->Compress(nOutput);

        if (m_pNNFilter1)
        {
            nOutput = m_pNNFilter1->Compress(nOutput);

            if (m_pNNFilter2)
                nOutput = m_pNNFilter2->Compress(nOutput);
        }
    }

    m_rbPrediction.IncrementFast(); m_rbAdapt.IncrementFast();
    m_nCurrentIndex++;

    return nOutput;
}

注目のCNNの圧縮(予測)処理は以下。NNFilter.cpp にある

int64 CNNFilter::Compress(int64 nInput)
{
    if (m_nBitdepth == 16)
    {
        // convert the input to a short and store it
        // aiki: short(たぶん16bit)幅に丸め込む。範囲外の値は0x7FFF(>0), 0x8000(<0)に飽和する (NNFilter.h)
        m_rbInput16[0] = GetSaturatedShortFromInt(nInput);

        // figure a dot product
        // aiki: 内積実行(指定された次数のフィルタを畳み込む)。
        int64 nDotProduct;
        #ifdef ENABLE_AVX_ASSEMBLY
            if (this->useAVX2())
                nDotProduct = CalculateDotProductAVXx16(&m_rbInput16[-m_nOrder], &m_paryM16[0], m_nOrder);
            else
        #endif
        #ifdef ENABLE_SSE_ASSEMBLY
            if (this->useSSE2())
               nDotProduct = CalculateDotProductSSEx16(&m_rbInput16[-m_nOrder], &m_paryM16[0], m_nOrder);
            else
        #endif
        nDotProduct = CalculateDotProductx16(&m_rbInput16[-m_nOrder], &m_paryM16[0], m_nOrder);

        // calculate the output
        // aiki: 残差計算。シフト量 m_nShift はコンストラクタで指定
        int64 nOutput = int64(nInput) - int64((int64(nDotProduct) + (int64(1) << (int64(m_nShift) - 1))) >> int64(m_nShift));

        // adapt
        // aiki: 適応。nOutputの符号を見て更新方向を決め、m_rbDeltaM16だけ増減する。Sign-Algorithmやな。
        #ifdef ENABLE_AVX_ASSEMBLY
            if (this->useAVX2())
                AdaptAVXx16(&m_paryM16[0], &m_rbDeltaM16[-m_nOrder], nOutput, m_nOrder);
            else
        #endif
        #ifdef ENABLE_SSE_ASSEMBLY
            if (this->useSSE2())
                AdaptSSEx16(&m_paryM16[0], &m_rbDeltaM16[-m_nOrder], nOutput, m_nOrder);
            else
        #endif
                Adaptx16(&m_paryM16[0], &m_rbDeltaM16[-m_nOrder], nOutput, m_nOrder);

        int64 nTempABS = llabs(nInput);

        // aiki: 入力の絶対値に応じて更新量の更新。入力を量子化。Sign-Algorithmだけど入力を荒くしている。
        if (nTempABS > (m_nRunningAverage * 3))
            m_rbDeltaM16[0] = ((nInput >> 25) & 64) - 32;
        else if (nTempABS > (m_nRunningAverage * 4) / 3)
            m_rbDeltaM16[0] = ((nInput >> 26) & 32) - 16;
        else if (nTempABS > 0)
            m_rbDeltaM16[0] = ((nInput >> 27) & 16) - 8;
        else
            m_rbDeltaM16[0] = 0;

        // aiki: 入力の絶対値の移動平均値の更新。
        m_nRunningAverage += (nTempABS - m_nRunningAverage) / 16;

        m_rbDeltaM16[-1] >>= 1;
        m_rbDeltaM16[-2] >>= 1;
        m_rbDeltaM16[-8] >>= 1;

        // increment and roll if necessary
        m_rbInput16.IncrementSafe();
        m_rbDeltaM16.IncrementSafe();

        return nOutput;
    }
    else // m_nBitdepth == 32
    {
        // aiki: (snip) ビット幅32の場合の処理。
    }
}

大雑把に見ると、層ごとにサンプルごとに適応している。たしかにこれなら圧縮率は高いだろう。

CNNの設定は以下で決まる。これも NewPredictor.cpp にある。CNNFilterのコンストラクタの第一引数は次数。第二引数はシフト量。

CPredictorCompressNormal::CPredictorCompressNormal(intn nCompressionLevel, intn nBitsPerSample)
    : IPredictorCompress(nCompressionLevel)
{
    if (nCompressionLevel == MAC_COMPRESSION_LEVEL_FAST)
    {
        m_pNNFilter = NULL;
        m_pNNFilter1 = NULL;
        m_pNNFilter2 = NULL;
    }
    else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_NORMAL)
    {
        m_pNNFilter = new CNNFilter(16, 11, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
        m_pNNFilter1 = NULL;
        m_pNNFilter2 = NULL;
    }
    else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_HIGH)
    {
        m_pNNFilter = new CNNFilter(64, 11, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
        m_pNNFilter1 = NULL;
        m_pNNFilter2 = NULL;
    }
    else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_EXTRA_HIGH)
    {
        m_pNNFilter = new CNNFilter(256, 13, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
        m_pNNFilter1 = new CNNFilter(32, 10, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
        m_pNNFilter2 = NULL;
    }
    else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_INSANE)
    {
        m_pNNFilter = new CNNFilter(1024 + 256, 15, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
        m_pNNFilter1 = new CNNFilter(256, 13, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
        m_pNNFilter2 = new CNNFilter(16, 11, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
    }
    else
    {
        throw(1);
    }
}

Extra High以上では多層構成になっている。 Extra Highでは一段目は256次数、二段目は32次数。二段目の方により弱いフィルタを噛ましている。INSANE頭おかしい…(小声) 層を増やすたびに次数が急激に減り過ぎでは、という印象。 他にも圧縮オプションに応じてフレームサイズが変わっていた。(CAPECompressCreate::Start 関数)

// initialize (creates the base classes)
m_nSamplesPerFrame = 73728;
if (nCompressionLevel == MAC_COMPRESSION_LEVEL_EXTRA_HIGH)
    m_nSamplesPerFrame *= 4;
else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_INSANE)
    m_nSamplesPerFrame *= 16;

クソデカフレームサイズだな。

だいたい理解したと思いこんでいる。不足があればもっと読み込んでみる。