#include "JSystem/JSystem.h" // IWYU pragma: keep

#include "JSystem/JAudio2/JASTrack.h"
#include "JSystem/JAudio2/JASCriticalSection.h"
#include "JSystem/JAudio2/JASDriverIF.h"
#include "JSystem/JAudio2/JASBank.h"
#include "JSystem/JAudio2/JASAiCtrl.h"
#include "JSystem/JAudio2/JASDSPInterface.h"

JASTrack::JASTrack() : mDefaultChannelMgr(this), mChannelMgrCount(1), mStatus(0) {
    mChannelMgrs[0] = &mDefaultChannelMgr;
    for (int i = 1; i < 4; i++) {
        mChannelMgrs[i] = NULL;
    }
    init();
}

DUSK_GAME_DATA JASDefaultBankTable JASTrack::sDefaultBankTable;

DUSK_GAME_DATA JASTrack::TList JASTrack::sTrackList;

// NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations
JASTrack::~JASTrack() {
    JUT_ASSERT(70, mStatus != STATUS_RUN);
    for (int i = 1; i < 4; i++) {
        JKR_DELETE(mChannelMgrs[i]);
    }
}

// NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations
void JASTrack::setChannelMgrCount(u32 count) {
    JUT_ASSERT(79, mStatus != STATUS_RUN);
    JUT_ASSERT(80, count >= 1 && count <= CHANNEL_MGR_MAX);
    mChannelMgrCount = 1;
    for (int i = 1; i < count; i++) {
        if (mChannelMgrs[i] == NULL) {
            mChannelMgrs[i] = JKR_NEW TChannelMgr(this);
            if (mChannelMgrs[i] == NULL) {
                JUT_WARN(87, "%s", "Not enough JASTrackChannelMgr\n");
                return;
            }
        }
        mChannelMgrCount++;
    }
    for (u32 i = mChannelMgrCount; i < 4; i++) {
        if (mChannelMgrs[i] != NULL) {
            JKR_DELETE(mChannelMgrs[i]);
            mChannelMgrs[i] = NULL;
        }
    }
}

DUSK_GAME_DATA JASOscillator::Point const JASTrack::sAdsTable[4] = {
    {0, 0, 0x7fff},
    {0, 0, 0x7fff},
    {0, 0, 0},
    {0xe, 0, 0},
};

DUSK_GAME_DATA JASOscillator::Data const JASTrack::sEnvOsc = {0, 1.0f, NULL, NULL, 1.0f, 0.0f};

DUSK_GAME_DATA JASOscillator::Data const JASTrack::sPitchEnvOsc = {1, 1.0f, NULL, NULL, 1.0f, 0.0f};

// NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations
void JASTrack::init() {
    JUT_ASSERT(104, mStatus == STATUS_FREE || mStatus == STATUS_STOPPED);
    mSeqCtrl.init();
    mTrackPort.init();
    initTimed();
    mRegisterParam.init();
    mOscParam[0] = sEnvOsc;
    mOscParam[1] = sPitchEnvOsc;
    for (int i = 0; i < 4; i++) {
        mOscPoint[i] = sAdsTable[i];
    }
    mParent = NULL;
    for (int i = 0; i < 16; i++) {
        mChildren[i] = NULL;
    }
    mChannelMgrs[0]->init();
    mChannelMgrCount = 1;
    for (int i = 1; i < 4; i++) {
        if (mChannelMgrs[i] != NULL) {
            JKR_DELETE(mChannelMgrs[i]);
            mChannelMgrs[i] = NULL;
        }
    }
    mBankTable = &sDefaultBankTable;
    field_0x1d8 = 1.0f;
    field_0x1dc = 1.0f;
    mVibDepth = 0.0f;
    mVibPitch = 1.0f;
    mTremDepth = 0.0f;
    mTremPitch = 1.0f;
    mVibDelay = 0;
    mTremDelay = 0;
    mPanPower = 1.0f;
    mDirectRelease = 0;
    mSkipSample = 0;
    mTempo = 120;
    mTimebase = 0x30;
    mTempoRate = 1.0f;
    updateTempo();
    mTranspose = 0;
    field_0x22b = 60;
    mBankNumber = 0;
    mProgNumber = 0xf0;
    mBendSense = 0xc;
    mNoteOnPrio = 0x40;
    mReleasePrio = 0;
    mGateRate = 100;
    mMixConfig[0] = 0x150;
    mMixConfig[1] = 0x210;
    mMixConfig[2] = 0x352;
    mMixConfig[3] = 0x412;
    mMixConfig[4] = 0;
    mMixConfig[5] = 0;
    for (int i = 0; i < 8; i++) {
        mFIR[i] = 0;
    }
    mFIR[0] = 0x7fff;
    for (int i = 0; i < 8; i++) {
        mIIR[i] = 0;
    }
    mIIR[0] = 0x7fff;
    mFilterMode = 0;
    mFlags.pause = false;
    mFlags.mute = false;
    mFlags.flag2 = true;
    mFlags.flag5 = true;
    mFlags.autoDelete = false;
    mFlags.flag4 = false;
    mFlags.flag6 = false;
    mStatus = STATUS_FREE;
}

void JASTrack::initTimed() {
    mMoveParam.params.volume.mValue = 1.0f;
    mMoveParam.params.pitch.mValue = 0.0f;
    mMoveParam.params.pan.mValue = 0.5f;
    mMoveParam.params.fxmix.mValue = 0.0f;
    mMoveParam.params.dolby.mValue = 0.0f;
    mMoveParam.params.distFilter.mValue = 0.0f;
    for (int i = 0; i < 6; i++) {
        mMoveParam.array[i].mCount = 0;
        mMoveParam.array[i].mTarget = mMoveParam.array[i].mValue;
    }
}

void JASTrack::inherit(JASTrack const& i_parent) {
    mFlags.flag2 = i_parent.mFlags.flag2;
    mBankNumber = i_parent.mBankNumber;
    mProgNumber = i_parent.mProgNumber;
    mNoteOnPrio = i_parent.mNoteOnPrio;
    mReleasePrio = i_parent.mReleasePrio;
    mSkipSample = i_parent.mSkipSample;
    for (int i = 0; i < 8; i++) {
        mFIR[i] = i_parent.mFIR[i];
    }
    for (int i = 0; i < 8; i++) {
        mIIR[i] = i_parent.mIIR[i];
    }
    mFilterMode = i_parent.mFilterMode;
    for (int i = 0; i < 6; i++) {
        mMixConfig[i] = i_parent.mMixConfig[i];
    }
}

void JASTrack::assignExtBuffer(u32 index, JASSoundParams* i_soundParams) {
    JUT_ASSERT(249, index < mChannelMgrCount);
    mChannelMgrs[index]->mSoundParams = i_soundParams;
}

void JASTrack::setSeqData(void* base, u32 offset) {
    JUT_ASSERT(257, mStatus == STATUS_FREE);
    mSeqCtrl.start(base, offset);
}

void JASTrack::startSeq() {
    JUT_ASSERT(265, mParent == 0);
    JUT_ASSERT(266, mStatus == STATUS_FREE);
    JASCriticalSection critical_section;
    sTrackList.append(this);
    mStatus = STATUS_RUN;
}

void JASTrack::stopSeq() {
    JASCriticalSection critical_section;
    mFlags.flag6 = 1;
}

void JASTrack::start() {
    JUT_ASSERT(289, mParent != NULL);
    JUT_ASSERT(290, mStatus == STATUS_FREE);
    mStatus = STATUS_RUN;
}

void JASTrack::close() {
    for (int i = 0; i < 16; i++) {
        JASTrack* child = mChildren[i];
        if (child != NULL) {
            child->close();
            if (child->mFlags.autoDelete) {
                JKR_DELETE(child);
                mChildren[i] = NULL;
            }
        }
    }
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        if (mChannelMgrs[i] != NULL) {
            mChannelMgrs[i]->releaseAll();
        }
    }
    mParent = NULL;
    mStatus = STATUS_STOPPED;
}

bool JASTrack::connectChild(u32 track_no, JASTrack* i_child) {
    JUT_ASSERT(326, track_no < MAX_CHILDREN);
    if (mChildren[track_no] != NULL) {
        return false;
    }
    i_child->mParent = this;
    mChildren[track_no] = i_child;
    return true;
}

void JASTrack::closeChild(u32 track_no) {
    JUT_ASSERT(340, track_no < MAX_CHILDREN);
    JASTrack* child = mChildren[track_no];
    if (child != NULL) {
        getRootTrack()->updateSeq(false, 1.0f);
        child->close();
        if (child->mFlags.autoDelete) {
            JKR_DELETE(child);
            mChildren[track_no] = NULL;
        }
    }
}

// NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations
JASTrack* JASTrack::openChild(u32 trk_no) {
    JUT_ASSERT(358, trk_no < MAX_CHILDREN);
    JASTrack* child = mChildren[trk_no];
    if (child != NULL) {
        switch (child->mStatus) {
        case STATUS_FREE:
            break;
        case STATUS_RUN:
            getRootTrack()->updateSeq(false, 1.0f);
            child->close();
            // no break
        case STATUS_STOPPED:
            bool auto_delete = child->mFlags.autoDelete;
            child->init();
            child->mFlags.autoDelete = auto_delete;
            mChildren[trk_no] = NULL;
            connectChild(trk_no, child);
            break;
        }
    } else {
        child = JKR_NEW JASTrack();
        if (child == NULL) {
            JUT_WARN(388, "%s", "Not enough JASTrack\n");
            return NULL;
        }
        child->mFlags.autoDelete = true;
        connectChild(trk_no, child);
    }
    child->setChannelMgrCount(mChannelMgrCount);
    child->inherit(*this);
    return child;
}

void JASTrack::connectBus(int line, int param_1) {
    JUT_ASSERT(404, line < JASChannel::BUSOUT_CPUCH);
    mMixConfig[line] = param_1;
}

f32 JASTrack::getVolume() const { return mMoveParam.params.volume.mValue; }
f32 JASTrack::getPitch() const { return mMoveParam.params.pitch.mValue; }
f32 JASTrack::getPan() const { return mMoveParam.params.pan.mValue; }
f32 JASTrack::getFxmix() const { return mMoveParam.params.fxmix.mValue; }
f32 JASTrack::getDolby() const { return mMoveParam.params.dolby.mValue; }

void JASTrack::setLatestKey(u8 param_0) {
    field_0x22b = param_0;
    field_0x22b += getTransposeTotal();
}

JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 param_1, u32 param_2,
                                   u32 i_updateTimer) {
    JASBank* bank = NULL;
    if (mBankTable != NULL) {
        bank = mBankTable->getBank(mBankNumber);
    }
    JASChannel* channel = JASBank::noteOn(bank, mProgNumber, param_1, param_2,
                                          mNoteOnPrio | mReleasePrio << 8,
                                          channelUpdateCallback, i_channelMgr);
    if (channel == NULL) {
        return NULL;
    }
    channel->setUpdateTimer(i_updateTimer);
    channel->setSkipSamples(mSkipSample);
    channel->setVibrateDelay(mVibDelay);
    channel->setTremoloDelay(mTremDelay);
    channel->setParams(i_channelMgr->mParams);
    for (u32 i = 0; i < 6; i++) {
        channel->setMixConfig(i, mMixConfig[i]);
    }
    overwriteOsc(channel);
    if (mDirectRelease != 0) {
        channel->setDirectRelease(mDirectRelease);
    }
    return channel;
}

int JASTrack::noteOn(u32 noteid, u32 param_1, u32 param_2) {
    JUT_ASSERT(486, noteid != 0 && noteid < TChannelMgr::CHANNEL_MAX);
    if (isMute()) {
        return 0;
    }
    int ret = 1;
    param_1 += getTransposeTotal();
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        if (mChannelMgrs[i] != NULL) {
            mChannelMgrs[i]->noteOff(noteid, 0);
            JASChannel* channel = channelStart(mChannelMgrs[i], param_1, param_2, 0);
            if (channel == NULL) {
                ret = 0;
            }
            mChannelMgrs[i]->mChannels[noteid] = channel;
        }
    }
    return ret;
}

int JASTrack::gateOn(u32 param_0, u32 i_velocity, f32 i_time, u32 i_flags) {
    bool result = true;
    param_0 += getTransposeTotal();
    if (mGateRate != 100) {
        i_time *= mGateRate / 100.0f;
    }
    u32 uvar2 = seqTimeToDspTime(i_time);
    u32 update_timer = (i_flags & 6) ? 0 : uvar2;
    int uvar7 = i_flags & 1 ? field_0x22b : param_0;
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        TChannelMgr* channel_mgr = mChannelMgrs[i];
        if (channel_mgr != NULL) {
            if (!mFlags.flag4) {
                channel_mgr->noteOff(0, 0);
                if (!isMute()) {
                    channel_mgr->mChannels[0] =
                        channelStart(channel_mgr, uvar7, i_velocity, update_timer);
                }
            } else {
                JASChannel* channel = channel_mgr->mChannels[0];
                if (channel != NULL) {
                    channel->setKey(uvar7 - channel->field_0xdc.mWaveInfo.mBaseKey);
                    channel->setVelocity(i_velocity);
                    channel->setUpdateTimer(update_timer);
                }
            }
            if (i_flags & 1) {
                JASChannel* channel = channel_mgr->mChannels[0];
                if (channel != NULL) {
                    channel->setKeySweepTarget(param_0 - uvar7, uvar2);
                }
            }
        }
    }
    mFlags.flag4 = (i_flags & 2) ? true : false;
    field_0x22b = param_0;
    return result;
}

int JASTrack::noteOff(u32 noteid, u16 param_1) {
    JUT_ASSERT(563, noteid < TChannelMgr::CHANNEL_MAX);
    int ret = 1;
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        if (mChannelMgrs[i] != NULL) {
            if (!mChannelMgrs[i]->noteOff(noteid, param_1)) {
                ret = 0;
            }
        }
    }
    return ret;
}

bool JASTrack::checkNoteStop(u32 noteid) const {
    JUT_ASSERT(581, noteid < TChannelMgr::CHANNEL_MAX);
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        if (mChannelMgrs[i] != NULL) {
            JASChannel* channel = mChannelMgrs[i]->mChannels[noteid];
            if (channel != NULL) {
                return false;
            }
        }
    }
    return true;
}

void JASTrack::overwriteOsc(JASChannel* i_channel) {
    for (u32 i = 0; i < 2; i++) {
        if (mOscParam[i].mTable != NULL) {
            JUT_ASSERT(599, mOscParam[i].rel_table == 0);
            i_channel->setOscInit(i, &mOscParam[i]);
        }
    }
}

void JASTrack::updateTimedParam() {
    for (int i = 0; i < 6; i++) {
        MoveParam_* param = &mMoveParam.array[i];
        if (param->mCount != 0) {
            param->mValue += (param->mTarget - param->mValue) / param->mCount;
            param->mCount--;
        }
    }
}

void JASTrack::updateTrack(f32 param_0) {
    updateTempo();
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        TChannelMgr* channel_mgr = mChannelMgrs[i];
        if (channel_mgr != NULL) {
            f32 volume = mMoveParam.params.volume.mValue;
            volume = volume * volume;
            f32 pitch = 1.0f;
            f32 fvar6 = mMoveParam.params.pitch.mValue * mBendSense * (1.0f / 3.0f);
            f32 pan = (mMoveParam.params.pan.mValue - 0.5f) * mPanPower;
            f32 fxmix = mMoveParam.params.fxmix.mValue;
            f32 dolby = mMoveParam.params.dolby.mValue;
            if (channel_mgr->mSoundParams != NULL) {
                volume *= channel_mgr->mSoundParams->mVolume;
                pitch *= channel_mgr->mSoundParams->mPitch;
                pan += channel_mgr->mSoundParams->mPan - 0.5f;
                fxmix += channel_mgr->mSoundParams->mFxMix;
                dolby += channel_mgr->mSoundParams->mDolby;
            }
            pan *= param_0;
            if (mParent == NULL) {
                channel_mgr->mParams.mVolume = volume;
                channel_mgr->mParams.mPitch = pitch;
                channel_mgr->mParams.mPan = pan;
                channel_mgr->mParams.mFxMix = fxmix;
                channel_mgr->mParams.mDolby = dolby;
                channel_mgr->mParams.field_0x8 = fvar6;
            } else {
                TChannelMgr* parent_channel_mgr = mParent->mChannelMgrs[i];
                if (parent_channel_mgr == NULL) {
                    parent_channel_mgr = mParent->mChannelMgrs[0];
                }
                channel_mgr->mParams.mVolume = parent_channel_mgr->mParams.mVolume * volume;
                channel_mgr->mParams.mPitch = parent_channel_mgr->mParams.mPitch * pitch;
                channel_mgr->mParams.mPan = (parent_channel_mgr->mParams.mPan - 0.5f) + pan;
                channel_mgr->mParams.mFxMix = parent_channel_mgr->mParams.mFxMix + fxmix;
                channel_mgr->mParams.mDolby = parent_channel_mgr->mParams.mDolby + dolby;
                channel_mgr->mParams.field_0x8 = parent_channel_mgr->mParams.field_0x8 + fvar6;
            }
            channel_mgr->mParams.mPan += 0.5f;
        }
    }
}

void JASTrack::updateTempo() {
    if (mParent == NULL) {
        field_0x1dc = mTempoRate * (mTimebase * mTempo * (4.0f / 3.0f) / JASDriver::getDacRate());
    } else {
        mTempo = mParent->mTempo;
        mTimebase = mParent->mTimebase;
        field_0x1dc = mParent->field_0x1dc;
    }
}

void JASTrack::updateSeq(bool param_0, f32 param_1) {
    if (!param_0) {
        param_0 = mFlags.flag5;
    }
    mFlags.flag5 = false;
    if (param_0) {
        updateTrack(param_1);
    }
    f32 fvar1 = param_1 * mPanPower;
    for (int i = 0; i < 16; i++) {
        JASTrack* child = mChildren[i];
        if (child != NULL && child->mStatus == STATUS_RUN) {
            child->updateSeq(param_0, fvar1);
        }
    }
}

u32 JASTrack::seqTimeToDspTime(f32 i_seqTime) {
    if (mFlags.flag2) {
        i_seqTime = i_seqTime / field_0x1dc;
    } else {
        i_seqTime = i_seqTime * (120.0f / mTimebase);
        i_seqTime *= JASDriver::getSubFrames() / 10.0f;
    }
    return i_seqTime;
}

void JASTrack::setParam(u32 target, f32 i_target, u32 i_count) {
    JUT_ASSERT(739, target < TIMED_PARAMS);
    MoveParam_* param = &mMoveParam.array[target];
    param->mTarget = i_target;
    if (i_count == 0) {
        param->mValue = param->mTarget;
    }
    param->mCount = i_count;
}

void JASTrack::noteOffAll(u16 param_0) {
    for (u8 i = 0; i < 8; i++) {
        noteOff(i, param_0);
    }
    for (int i = 0; i < 16; i++) {
        JASTrack* child = mChildren[i];
        if (child != NULL && child->mStatus == STATUS_RUN) {
            child->noteOffAll(param_0);
        }
    }
}

void JASTrack::mute(bool i_mute) {
    mFlags.mute = i_mute;
    if (i_mute) {
        noteOffAll(10);
    }
}

void JASTrack::setOscScale(u32 osc_no, f32 i_scale) {
    JUT_ASSERT(792, osc_no < OSC_NUM);
    mOscParam[osc_no].mScale = i_scale;
}

void JASTrack::setOscTable(u32 osc_no, JASOscillator::Point const* i_table) {
    JUT_ASSERT(799, osc_no < OSC_NUM)
    mOscParam[osc_no].mTable = i_table;
    if (osc_no != 0) {
        mOscParam[osc_no].rel_table = NULL;
    }
}

void JASTrack::setOscAdsr(s16 param_0, s16 param_1, s16 param_2, s16 param_3, u16 i_directRelease) {
    mOscParam[0] = sEnvOsc;
    mOscParam[0].mTable = mOscPoint;
    mOscPoint[0].mTime = param_0;
    mOscPoint[1].mTime = param_1;
    mOscPoint[2].mTime = param_2;
    mOscPoint[2].mValue = param_3;
    mDirectRelease = i_directRelease;
}

DUSK_GAME_DATA const u32 JASDsp::FILTER_MODE_IIR = 0x00000020;

void JASTrack::setFIR(s16 const* i_FIR) {
    for (int i = 0; i < 8; i++) {
        mFIR[i] = i_FIR[i];
    }
    mFilterMode &= JASDsp::FILTER_MODE_IIR;
    mFilterMode |= 8;
}

void JASTrack::setIIR(s16 const* i_IIR) {
    for (int i = 0; i < 8; i++) {
        mIIR[i] = i_IIR[i];
    }
    mFilterMode |= JASDsp::FILTER_MODE_IIR;
}

u16 JASTrack::readPortSelf(u32 param_0) {
    return mTrackPort.readImport(param_0);
}

void JASTrack::writePortSelf(u32 param_0, u16 param_1) {
    mTrackPort.writeExport(param_0, param_1);
}

void JASTrack::writePort(u32 param_0, u16 param_1) {
    JASSeqCtrl::IntrType intr;
    mTrackPort.writeImport(param_0, param_1);
    if (param_0 == 0 || param_0 == 1) {
        if (param_0 == 0) {
            intr = JASSeqCtrl::INTRTYPE_VALUE_2;
        } else {
            intr = JASSeqCtrl::INTRTYPE_VALUE_3;
        }
        mSeqCtrl.interrupt(intr);
    }
}

u16 JASTrack::readPort(u32 param_0) {
    return mTrackPort.readExport(param_0);
}

void JASTrack::setChannelPauseFlag(bool i_pause) {
    for (u32 i = 0; i < mChannelMgrCount; i++) {
        TChannelMgr* mgr = mChannelMgrs[i];
        if (mgr != NULL) {
            mgr->setPauseFlag(i_pause);
        }
    }
    for (int i = 0; i < 16; i++) {
        JASTrack* child = mChildren[i];
        if (child != NULL) {
            child->setChannelPauseFlag(i_pause);
        }
    }
}

void JASTrack::pause(bool i_pause) {
    if (mFlags.pause != i_pause) {
        mFlags.pause = i_pause;
        setChannelPauseFlag(i_pause);
        mSeqCtrl.interrupt(i_pause ? JASSeqCtrl::INTRTYPE_VALUE_0 : JASSeqCtrl::INTRTYPE_VALUE_1);
    }
}

int JASTrack::getTransposeTotal() const {
    if (mParent != NULL) {
        return mTranspose + mParent->getTransposeTotal();
    } else {
        return mTranspose;
    }
}

bool JASTrack::isMute() const {
    if (mParent != NULL) {
        return mFlags.mute || mParent->isMute();
    } else {
        return mFlags.mute;
    }
}

void JASTrack::setTempo(u16 i_tempo) {
    JUT_ASSERT(961, mParent == 0);
    mTempo = i_tempo;
    mFlags.flag5 = true;
    updateTempo();
}

void JASTrack::setTempoRate(f32 i_tempoRate) {
    JUT_ASSERT(969, mParent == 0);
    mTempoRate = i_tempoRate;
    mFlags.flag5 = true;
    updateTempo();
}

void JASTrack::setTimebase(u16 i_timebase) {
    JUT_ASSERT(977, mParent == 0);
    mTimebase = i_timebase;
    mFlags.flag5 = true;
    updateTempo();
}

void JASTrack::updateChannel(JASChannel* param_0, JASDsp::TChannel* param_1) {
    param_0->setVibrate(mVibDepth, mVibPitch);
    param_0->setTremolo(mTremDepth, mTremPitch);
    if (mFilterMode & 0x20) {
        param_1->setIIRFilterParam(mIIR);
    }
    if (mFilterMode & 0x1f) {
        param_1->setFIR8FilterParam(mFIR);
    }
    param_1->setFilterMode(mFilterMode);
    param_1->setDistFilter(mMoveParam.params.distFilter.mValue * 32767.0f);
}


void JASTrack::channelUpdateCallback(u32 param_0, JASChannel* param_1,
                                     JASDsp::TChannel* param_2, void* param_3) {
    TChannelMgr* channel_mgr = static_cast<TChannelMgr*>(param_3);
    JASTrack* track = channel_mgr->mTrack;
    switch (param_0) {
    case 0:
    case 1:
        param_1->setParams(channel_mgr->mParams);
        track->updateChannel(param_1, param_2);
        break;
    case 3:
        param_1->release(0);
        param_1->free();
        channel_mgr->mChannels[0] = NULL;
        break;
    case 2:
        for (int i = 0; i < 8; i++) {
            if (param_1 == channel_mgr->mChannels[i]) {
                channel_mgr->mChannels[i] = NULL;
                break;
            }
        }
        break;
    }
}

int JASTrack::getChannelCount() const {
    JASCriticalSection cs;
    int count = 0;
    for (int i = 0; i < mChannelMgrCount; i++) {
        if (mChannelMgrs[i]) {
            for (int j = 0; j < 8; j++) {
                if (mChannelMgrs[i]->mChannels[j]) {
                    count++;
                }
            }
        }
    }
    return count;
}

JASTrack* JASTrack::getRootTrack() {
    JASTrack* track = this;
    while (track->mParent != NULL) {
        track = track->mParent;
    }
    return track;
}

int JASTrack::tickProc() {
    if (mFlags.pause) {
        return 0;
    }
    int ret = mSeqCtrl.tickProc(this);
    updateTimedParam();
    mFlags.flag5 = true;
    if (ret < 0) {
        return -1;
    }
    for (int i = 0; i < 16; i++) {
        JASTrack* child = mChildren[i];
        if (child != NULL && child->mStatus == STATUS_RUN) {
            JUT_ASSERT(1090, this == child->mParent);
            ret = child->tickProc();
            if (ret < 0) {
                getRootTrack()->updateSeq(false, 1.0f);
                child->close();
                if (child->mFlags.autoDelete) {
                    JKR_DELETE(child);
                    mChildren[i] = NULL;
                }
            }
        }
    }
    return 0;
}

int JASTrack::seqMain() {
    JUT_ASSERT(1108, mStatus == STATUS_RUN);
    if (mFlags.flag6) {
        updateSeq(true, 1.0f);
        close();
        return -1;
    } else {
        while (field_0x1d8 >= 1.0f) {
            field_0x1d8 -= 1.0f;
            int ret = tickProc();
            if (ret < 0) {
                updateSeq(false, 1.0f);
                close();
                return -1;
            }
        }
        field_0x1d8 += field_0x1dc;
        updateSeq(false, 1.0f);
        return 0;
    }
}

s32 JASTrack::TList::cbSeqMain(void* i_this) {
    JASTrack::TList* list = static_cast<JASTrack::TList*>(i_this);
    list->seqMain();
    return 0;
}

void JASTrack::TList::append(JASTrack* i_track) {
    if (!mCallbackRegistered) {
        if (!JASDriver::registerSubFrameCallback(cbSeqMain, this)) {
            JUT_WARN(1157, "%s", "Failed: registerSubFrameCallback");
            return;
        }
        mCallbackRegistered = true;
    }
    Push_back(i_track);
}

void JASTrack::TList::seqMain() {
#if DEBUG
    iterator it, it2;
    for (it = begin(); it != end(); it = it2) {
        it2 = it;
        ++it2;
        int seqMainRes = it->seqMain();
        if (seqMainRes < 0) {
            Remove(&*it);
            if (it->mFlags.autoDelete) {
                JKR_DELETE(&*it);
            }
        }
    }
#else
    iterator it;
    for (it = begin(); it != end();) {
        // Fakematch: Debug shows that it2 should be declared outside the loop and then this line
        // should use operator=, but that results in a missing load instruction for retail, so we
        // use the copy constructor instead of the assignment operator, which fixes it for some
        // reason. Might be related to the fakematch in the assignment operator?
        iterator it2 = it;
        ++it2;
        int seqMainRes = it->seqMain();
        if (seqMainRes < 0) {
            Remove(&*it);
            if (it->mFlags.autoDelete) {
                JKR_DELETE(&*it);
            }
        }
        it = it2;
    }
#endif
}

JASTrack::TChannelMgr::TChannelMgr(JASTrack* i_track) : mSoundParams(NULL), mTrack(i_track) {
    for (int i = 0; i < 8; i++) {
        mChannels[i] = NULL;
    }
    for (int i = 0; i < 8; i++) {
        field_0x38[i] = 0;
    }
}


void JASTrack::TChannelMgr::init() {
    mSoundParams = NULL;
    mParams.init();
    for (int i = 0; i < 8; i++) {
        mChannels[i] = NULL;
    }
    for (int i = 0; i < 8; i++) {
        field_0x38[i] = 0;
    }
}


void JASTrack::TChannelMgr::releaseAll() {
    for (int i = 0; i < 8; i++) {
        JASChannel* channel = mChannels[i];
        if (channel != NULL) {
            channel->release(0);
            channel->free();
            mChannels[i] = NULL;
        }
    }
}

bool JASTrack::TChannelMgr::noteOff(u32 noteid, u16 param_1) {
    JUT_ASSERT(1224, noteid < CHANNEL_MAX);
    JASChannel* channel = mChannels[noteid];
    if (channel == NULL) {
        return false;
    }
    if (param_1 == 0) {
        channel->release(0);
    } else {
        channel->release(param_1);
    }
    channel->free();
    mChannels[noteid] = NULL;
    return true;
}

void JASTrack::TChannelMgr::setPauseFlag(bool i_pause) {
    for (int i = 0; i < 8; i++) {
        JASChannel* channel = mChannels[i];
        if (channel != NULL) {
            channel->setPauseFlag(i_pause);
        }
    }
}
