//
// Generated by dtk
// Translation Unit: JASTrack.cpp
//

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

#include "JSystem/JAudio/JASTrack.h"
#include "JSystem/JAudio/JASBankMgr.h"
#include "JSystem/JAudio/JASCalc.h"
#include "JSystem/JAudio/JASCallback.h"
#include "JSystem/JAudio/JASChannel.h"
#include "JSystem/JAudio/JASChGlobal.h"
#include "JSystem/JAudio/JASPlayer.h"
#include "JSystem/JAudio/JASSystemHeap.h"
#include "JSystem/JAudio/JASRate.h"
#include "JSystem/JKernel/JKRSolidHeap.h"
#include "JSystem/JUtility/JUTAssert.h"

/* 80280960-80280A34       .text __ct__Q28JASystem6TTrackFv */
JASystem::TTrack::TTrack() {
    mParent = NULL;
    field_0x364 = 0.0f;
    field_0x368 = 0.0f;
    field_0x36c = 0;
    mUpdateFlags = 0;
    field_0x374 = 0;
    field_0x376 = 0x78;
    field_0x378 = 0x78;
    mTranspose = 0;
    field_0x37b = 0;
    mPauseStatus = 0;
    mVolumeMode = 0;
    field_0x37e = 0;
    mIsPaused = 0;
    field_0x386 = 0;
    field_0x387 = 0;
    field_0x388 = 0;
    field_0x389 = 0;
    mChannelUpdater.init();
    for (int i = 0; i < 12; i++) {
        field_0x304[i] = Player::sAdsTable[i];
    }
    Calc::bzero(&mTimedParam, sizeof(TimedParam_));
}

/* 80280A34-80280A80       .text __ct__Q38JASystem6TTrack11TimedParam_Fv */
JASystem::TTrack::TimedParam_::TimedParam_() {}

/* 80280A80-80280C10       .text init__Q28JASystem6TTrackFv */
void JASystem::TTrack::init() {
    mSeqCtrl.init();
    mTrackPort.init();
    mIntrMgr.init();
    mNoteMgr.init();
    mChannelUpdater.initAllocChannel(0);
    initTimed();
    mRegisterParam.init();
    mOscRoute[0] = 0x0f;
    field_0x2cc[0] = Player::sEnvelopeDef;
    mOscRoute[1] = 0x0f;
    field_0x2cc[1] = Player::sEnvelopeDef;
    mParent = NULL;
    for (int i = 0; i < 16; i++) {
        mChildren[i] = NULL;
    }
    if (mOuterParam) {
        mOuterParam->initExtBuffer();
    }
    field_0x364 = 0.0f;
    field_0x368 = 1.0f;
    field_0x36c = 0;
    mVibrate.init();
    mUpdateFlags = 0;
    field_0x374 = 0;
    field_0x376 = 0x78;
    field_0x378 = 0x30;
    updateTempo();
    mTranspose = 0;
    field_0x37b = 0;
    mPauseStatus = 10;
    mVolumeMode = 0;
    field_0x37e = 0;
    for (int i = 0; i < 3; i++) {
        mCalcTypes[i] = 0;
        mParentCalcTypes[i] = 0;
        mChannelUpdater.mCalcTypes[i] = 0x0d;
    }
    mIsPaused = 0;
    field_0x386 = 0;
    field_0x387 = 1;
    field_0x388 = 0;
}

/* 80280C10-80280D0C       .text inherit__Q28JASystem6TTrackFv */
void JASystem::TTrack::inherit() {
    if (!mParent) {
        return;
    }
    field_0x376 = mParent->field_0x376;
    field_0x388 = 0;
    field_0x368 = mParent->field_0x368;
    field_0x378 = mParent->field_0x378;
    field_0x387 = mParent->field_0x387;
    mIsPaused = mParent->mIsPaused;
    mPauseStatus = mParent->mPauseStatus;
    mVolumeMode = mParent->mVolumeMode;
    field_0x386 = mParent->field_0x386;
    if (field_0x37b & 2) {
        return;
    }
    mRegisterParam.inherit(mParent->mRegisterParam);
    for (int i = 0; i < 3; i++) {
        mCalcTypes[i] = mParent->mCalcTypes[i];
        mParentCalcTypes[i] = mParent->mParentCalcTypes[i];
        mChannelUpdater.mCalcTypes[i] = mParent->mChannelUpdater.mCalcTypes[i];
    }
}

u16 (*JASystem::TTrack::sCallBackFunc)(JASystem::TTrack*, u16);
JASystem::TSeqParser JASystem::TTrack::sParser;
JASystem::TTrack* JASystem::TTrack::sFreeList;

/* 80280D0C-80280F80       .text mainProc__Q28JASystem6TTrackFv */
s8 JASystem::TTrack::mainProc() {
    int r31 = 0;
    if (field_0x388 && mParent) {
        f32 tmp = field_0x376;
        tmp /= mParent->field_0x376;
        if (tmp > 1.0f) {
            tmp = 1.0f;
        }
        field_0x364 += tmp;
        if (field_0x364 < 1.0f) {
            return 0;
        }
        field_0x364 -= 1.0f;
    }
    if (mParent && mChannelUpdater.field_0x0) {
        TChannel* channel = mChannelUpdater.getListHead(0);
        if (channel) {
            mParent->mChannelUpdater.addListHead(channel, 0);
            channel->field_0x4 = &mParent->mChannelUpdater;
            mChannelUpdater.field_0x0--;
            mParent->mChannelUpdater.field_0x0++;
        }
    }
    mIntrMgr.request(REQUEST_UNK_7);
    mIntrMgr.timerProcess();
    tryInterrupt();
    if (mIsPaused == 0 || (mPauseStatus & 2) == 0) {
        // TODO: figure out how to remove gotos. maybe inlines.
        if (mSeqCtrl.getWait() == -1) {
            if (checkNoteStop(0)) {
                mSeqCtrl.wait(0);
            } else {
                goto lab_19c;
            }
        }
        if (mSeqCtrl.getWait() > 0) {
            if (mSeqCtrl.waitCountDown()) {
                mNoteMgr.endProcess();
            } else {
                goto lab_19c;
            }
        }
        r31 = sParser.parseSeq(this);
lab_19c:
        updateTimedParam();
    }
    updateSeq(0, false);
    if (r31 < 0) {
        return -1;
    }
    for (int i = 0; i < 16; i++) {
        TTrack* child = mChildren[i];
        if (child && child->field_0x37e) {
            JUT_ASSERT(276, this == child->mParent);
            if (child->mainProc() == -1) {
                child->close();
                mChildren[i] = NULL;
            }
        }
    }
    return 0;
}

/* 80280F80-80280FA8       .text setInterrupt__Q28JASystem6TTrackFUs */
void JASystem::TTrack::setInterrupt(u16 param_1) {
    mIntrMgr.request(param_1);
}

/* 80280FA8-80281004       .text tryInterrupt__Q28JASystem6TTrackFv */
bool JASystem::TTrack::tryInterrupt() {
    if (mSeqCtrl.mPreviousFilePtr) {
        return false;
    }
    void* var1 = mIntrMgr.checkIntr();
    if (var1 == NULL) {
        return false;
    }
    return mSeqCtrl.callIntr(var1);
}

/* 80281004-8028100C       .text assignExtBuffer__Q28JASystem6TTrackFPQ38JASystem6TTrack11TOuterParam */
void JASystem::TTrack::assignExtBuffer(TOuterParam* param_1) {
    mOuterParam = param_1;
}

/* 8028100C-80281050       .text releaseChannelAll__Q28JASystem6TTrackFv */
void JASystem::TTrack::releaseChannelAll() {
    if (mParent) {
        mParent->mChannelUpdater.receiveAllChannels(&mChannelUpdater);
    } else {
        TGlobalChannel::releaseAll(&mChannelUpdater);
    }
}

/* 80281050-80281088       .text flushAll__Q28JASystem6TTrackFv */
void JASystem::TTrack::flushAll() {
    mChannelUpdater.stopAll();
    mChannelUpdater.stopAllRelease();
}

/* 80281088-80281138       .text moveFreeChannel__Q28JASystem6TTrackFPQ28JASystem11TChannelMgrPQ28JASystem11TChannelMgri */
int JASystem::TTrack::moveFreeChannel(TChannelMgr* param_1, TChannelMgr* param_2, int param_3) {
    if (param_3 < 0) {
        u32 r31 = param_1->field_0x0;
        param_2->receiveAllChannels(param_1);
        return r31;
    }
    for (int i = 0; i < param_3; i++) {
        if (!param_1->field_0x0) {
            break;
        }
        TChannel* channel = param_1->getListHead(0);
        if (!channel) {
            break;
        }
        param_1->field_0x0--;
        param_2->addListHead(channel, 0);
        param_2->field_0x0++;
    }
    return 0;
}

/* 80281138-802811DC       .text initTimed__Q28JASystem6TTrackFv */
void JASystem::TTrack::initTimed() {
    for (u8 i = 0; i < 18; i++) {
        mTimedParam.mMoveParams[i].mMoveTime = 0.0f;
        mTimedParam.mMoveParams[i].mCurrentValue = 1.0f;
        mTimedParam.mMoveParams[i].mTargetValue = 1.0f;
    }
    mTimedParam.mMoveParams[1].mCurrentValue = 0.0f;
    mTimedParam.mMoveParams[1].mTargetValue = 0.0f;
    mTimedParam.mMoveParams[3].mCurrentValue = 0.5f;
    mTimedParam.mMoveParams[3].mTargetValue = 0.5f;
    mTimedParam.mMoveParams[16].mCurrentValue = 0.5f;
    mTimedParam.mMoveParams[16].mTargetValue = 0.5f;
    mTimedParam.mMoveParams[17].mCurrentValue = 0.0f;
    mTimedParam.mMoveParams[17].mTargetValue = 0.0f;
    mTimedParam.mMoveParams[2].mCurrentValue = 0.0f;
    mTimedParam.mMoveParams[2].mTargetValue = 0.0f;
    mTimedParam.mMoveParams[4].mCurrentValue = 0.0f;
    mTimedParam.mMoveParams[4].mTargetValue = 0.0f;
    for (u8 i = 1; i < 4; i++) {
        mTimedParam.mMoveParams[i + 12].mCurrentValue = 0.0f;
        mTimedParam.mMoveParams[i + 12].mTargetValue = 0.0f;
    }
    mTimedParam.mMoveParams[5].mCurrentValue = 0.0f;
    mTimedParam.mMoveParams[5].mTargetValue = 0.0f;
}

static void dummy() {
    OSReport("i >= 0");
    OSReport("i < 3");
}

/* 802811DC-80281258       .text connectBus__Q28JASystem6TTrackFii */
void JASystem::TTrack::connectBus(int line, int param_2) {
    JUT_ASSERT(486, line < (6));
    mChannelUpdater.field_0x4e[line] = param_2;
}

/* 80281258-802814AC       .text noteOn__Q28JASystem6TTrackFUclllUl */
int JASystem::TTrack::noteOn(u8 param_1, s32 param_2, s32 param_3, s32 param_4, u32 param_5) {
    if (field_0x386 && (mPauseStatus & 0x40)) {
        return -1;
    }
    noteOff(param_1, 0);
    TChannelMgr* r31 = &mChannelUpdater;
    TTrack* parent = mParent;
    while (r31->field_0x0 == 0 || r31->field_0x8 == 0) {
        if (!parent) {
            r31 = &mChannelUpdater;
            break;
        }
        r31 = &parent->mChannelUpdater;
        parent = parent->mParent;
    }
    if (field_0x37b == 4) {
        JUT_ASSERT(527, mParent != NULL);
        if (r31 != &mParent->mChannelUpdater) {
            if (moveFreeChannel(r31, &mParent->mChannelUpdater, 1) != 1) {
                OSReport("in Player (NOTE-MODE) ... ボイス借用に失敗しました！！ (%d)\n", r31->field_0x0);
            }
            r31 = &mParent->mChannelUpdater;
        }
    } else if (r31 != &mChannelUpdater) {
        if (moveFreeChannel(r31, &mChannelUpdater, 1) != 1) {
            OSReport("in Player ボイス借用に失敗しました！！ (%d)\n", r31->field_0x0);
        }
        r31 = &mChannelUpdater;
    }
    u8 bankNum = mRegisterParam.getBankNumber();
    u8 physNum = BankMgr::getPhysicalNumber(bankNum);
    u8 progNum = mRegisterParam.getProgramNumber();
    TChannel* channel = BankMgr::noteOn(r31, physNum, progNum, param_2, param_3, param_4);
    if (!channel) {
        return -1;
    }
    mNoteMgr.setChannel(param_1, channel);
    channel->field_0xe8 = param_5;
    channel->setPanPower(mRegisterParam.getPanPowerBank(), mRegisterParam.getPanPowerExt(), mRegisterParam.getPanPowerOsc(), 0.0f);
    overwriteOsc(channel);
    if (field_0x374) {
        channel->directReleaseOsc(0, field_0x374);
    }
    return 0;
}

/* 802814AC-802815DC       .text overwriteOsc__Q28JASystem6TTrackFPQ28JASystem8TChannel */
void JASystem::TTrack::overwriteOsc(TChannel* param_1) {
    u32 r28;
    for (int i = 0; i < 2; i++) {
        u32 var1 = mOscRoute[i];
        if (var1 == 0x0f) {
            continue;
        }
        r28 = var1 & 3;
        if (var1 & 8) {
            if (!param_1->isOsc(r28)) {
                JUT_WARN(593, "%s", "cannot copy osc");
                continue;
            }
            param_1->copyOsc(r28, &field_0x2cc[i]);
        } else if (var1 & 4) {
            s16* var4 = field_0x2cc[i].rel_table;
            if (!param_1->isOsc(r28)) {
                JUT_WARN(603, "%s", "cannot copy osc");
                continue;
            }
            param_1->copyOsc(r28, &field_0x2cc[i]);
            field_0x2cc[i].rel_table = var4;
        }
        param_1->overwriteOsc(r28, &field_0x2cc[i]);
    }
}

/* 802815DC-8028165C       .text noteOff__Q28JASystem6TTrackFUcUs */
bool JASystem::TTrack::noteOff(u8 param_1, u16 param_2) {
    TChannel* channel = mNoteMgr.getChannel(param_1);
    if (!channel) {
        return false;
    }
    if (param_2 == 0) {
        channel->stop(0);
    } else {
        channel->stop(param_2);
    }
    mNoteMgr.releaseChannel(param_1);
    return true;
}

/* 8028165C-802816C4       .text gateOn__Q28JASystem6TTrackFUclll */
int JASystem::TTrack::gateOn(u8 param_1, s32 param_2, s32 param_3, s32 param_4) {
    TChannel* channel = mNoteMgr.getChannel(param_1);
    if (!channel) {
        return -1;
    }
    BankMgr::gateOn(channel, param_2, param_3, param_4);
    return 0;
}

/* 802816C4-80281708       .text checkNoteStop__Q28JASystem6TTrackFl */
bool JASystem::TTrack::checkNoteStop(s32 param_1) {
    TChannel* channel = mNoteMgr.getChannel(param_1);
    if (channel == NULL) {
        return true;
    }
    if (channel->field_0x1 == 0xff) {
        return true;
    }
    return false;
}

/* 80281708-802817E4       .text oscSetupFull__Q28JASystem6TTrackFUcUlUl */
void JASystem::TTrack::oscSetupFull(u8 param_1, u32 param_2, u32 param_3) {
    bool r8 = (param_1 & 0x10) ? true : false;
    u8 r7 = param_1 & 0x0f;
    bool r0 = (param_1 & 0x80) ? true : false;
    bool r9 = (param_1 & 0x40) ? true : false;
    bool r10 = (param_1 & 0x20) ? true : false;
    if (r0) {
        field_0x2cc[r8] = Player::sEnvelopeDef;
        field_0x2cc[r8].field_0x0 = r7;
        switch (r7) {
            case 1:
                field_0x2cc[r8].field_0x14 = 1.0f;
                break;
        }
    }
    if (r9) {
        if (param_2 == 0) {
            field_0x2cc[r8].table = NULL;
        }
        field_0x2cc[r8].table = (s16*)(mSeqCtrl.mRawFilePtr + param_2);
    }
    if (!r10) {
        return;
    }
    if (param_3 == 0) {
        field_0x2cc[r8].rel_table = Player::sRelTable;
    }
    field_0x2cc[r8].rel_table = (s16*)(mSeqCtrl.mRawFilePtr + param_3);
}

/* 802817E4-80281850       .text oscSetupSimpleEnv__Q28JASystem6TTrackFUcUl */
void JASystem::TTrack::oscSetupSimpleEnv(u8 param_1, u32 param_2) {
    switch (param_1) {
    case 0:
        field_0x2cc[0] = Player::sEnvelopeDef;
        field_0x2cc[0].table = (s16*)(mSeqCtrl.mRawFilePtr + param_2);
        break;
    case 1:
        field_0x2cc[0].rel_table = (s16*)(mSeqCtrl.mRawFilePtr + param_2);
        break;
    }
}

/* 80281850-80281900       .text updateOscParam__Q28JASystem6TTrackFif */
void JASystem::TTrack::updateOscParam(int param_1, f32 param_2) {
    switch (param_1) {
    case 6:
        field_0x2cc[0].field_0x10 = param_2;
        break;
    case 7:
        field_0x2cc[0].field_0x4 = param_2;
        break;
    case 8:
        field_0x2cc[0].field_0x14 = param_2;
        break;
    case 9:
        field_0x2cc[1].field_0x10 = param_2;
        break;
    case 10:
        field_0x2cc[1].field_0x4 = param_2;
        break;
    case 11:
        field_0x2cc[1].field_0x14 = param_2;
        break;
    default:
        JUT_ASSERT(753, false);
        break;
    }
}

/* 80281900-802819D0       .text oscSetupSimple__Q28JASystem6TTrackFUc */
void JASystem::TTrack::oscSetupSimple(u8 param_1) {
    switch (param_1) {
    case 0:
        field_0x2cc[1] = Player::sVibratoDef;
        break;
    case 1:
        field_0x2cc[0] = Player::sTremoroDef;
        break;
    case 2:
        field_0x2cc[1] = Player::sTremoroDef;
        break;
    }
}

/* 802819D0-80281AA4       .text updateTimedParam__Q28JASystem6TTrackFv */
void JASystem::TTrack::updateTimedParam() {
    for (s32 i = 0; i < TIMED_PARAMS; i++) {
        MoveParam_& param = mTimedParam.mMoveParams[i];

        if (param.mMoveTime > 0.0f) {
            param.mCurrentValue += param.mMoveAmount;
            param.mMoveTime -= 1.0f;
            if (i <= 5 || i >= 11) {
                mUpdateFlags |= (1 << i);
            } else {
                updateOscParam(i, param.mCurrentValue);
            }
        }
    }
    mUpdateFlags |= 2;
}

/* 80281AA4-80281E90       .text updateTrackAll__Q28JASystem6TTrackFv */
void JASystem::TTrack::updateTrackAll() {
    if (field_0x37b == 4) {
        return;
    }

    f32 pitch;
    f32 vol;
    f32 pan;
    f32 fx_vol;
    f32 dolby;

    f32 pan_weight = (f32)mRegisterParam.getPanPowerParent() / 0x7FFF;
    mChannelUpdater.field_0x68 = mRegisterParam.field_0x1a | 0x10000;
    mChannelUpdater.field_0x6c = 0;

    f32 delayF = mTimedParam.mMoveParams[TIMED_Unk17].mCurrentValue * 128.0f;
    s8 delay;
    s8 unk = 0;
    OSf32tos8(&delayF, &delay);
    if (delay < 0) {
        unk = -delay;
        delay = 0;
    }

    mChannelUpdater.field_0x60 = 16;
    mChannelUpdater.field_0x5a[0] = unk;
    mChannelUpdater.field_0x5a[1] = delay;

    vol = mTimedParam.mMoveParams[TIMED_Volume].mCurrentValue;
    if (mVolumeMode == 0) {
        vol *= vol;
    }
    if (field_0x386) {
        vol = 0.0f;
    }

    f32 cent = Player::pitchToCent(mTimedParam.mMoveParams[TIMED_Pitch].mCurrentValue,
        mRegisterParam.field_0xe);
    pitch = cent * mVibrate.getValue();

    pan = mTimedParam.mMoveParams[TIMED_Pan].mCurrentValue;
    fx_vol = mTimedParam.mMoveParams[TIMED_Fxmix].mCurrentValue;
    dolby = mTimedParam.mMoveParams[TIMED_Dolby].mCurrentValue;

    if (mOuterParam) {
        if (mOuterParam->checkOuterSwitch(OUTERPARAM_Volume)) {
            vol *= mOuterParam->getVolume();
        }
        if (mOuterParam->checkOuterSwitch(OUTERPARAM_Pitch)) {
            pitch *= mOuterParam->getPitch();
        }
        if (mOuterParam->checkOuterSwitch(OUTERPARAM_Fxmix)) {
            fx_vol = panCalc(fx_vol, mOuterParam->getFxVol(), pan_weight, mCalcTypes[1]);
        }
        if (mOuterParam->checkOuterSwitch(OUTERPARAM_Dolby)) {
            dolby = panCalc(dolby, mOuterParam->getDolby(), pan_weight, mCalcTypes[2]);
        }
        if (mOuterParam->checkOuterSwitch(OUTERPARAM_Pan)) {
            pan = panCalc(pan, mOuterParam->getPan(), pan_weight, mCalcTypes[0]);
        }
    }

    if (mParent == NULL || (field_0x37b & 1)) {
        mChannelUpdater.field_0x18 = vol;
        mChannelUpdater.field_0x1c = pitch;
        mChannelUpdater.field_0x20 = pan;
        mChannelUpdater.field_0x24 = fx_vol;
        mChannelUpdater.field_0x28 = dolby;
    } else {
        pan_weight = (f32)mRegisterParam.getPanPowerTrack() / 0x7FFF;

        mChannelUpdater.field_0x18 = mParent->mChannelUpdater.field_0x18 * vol;
        mChannelUpdater.field_0x1c = mParent->mChannelUpdater.field_0x1c * pitch;
        mChannelUpdater.field_0x20 = panCalc(
            pan, mParent->mChannelUpdater.field_0x20, pan_weight, mParentCalcTypes[0]
        );
        mChannelUpdater.field_0x24 = panCalc(
            fx_vol, mParent->mChannelUpdater.field_0x24, pan_weight, mParentCalcTypes[1]
        );
        mChannelUpdater.field_0x28 = panCalc(
            dolby, mParent->mChannelUpdater.field_0x28, pan_weight, mParentCalcTypes[2]
        );

        if (mOuterParam && mOuterParam->checkOuterSwitch(OUTERPARAM_FIR8Filter)) {
            for (u8 i = 0; i < 8; i++) {
                mChannelUpdater.field_0x2c[i] = mOuterParam->getIntFirFilter(i);
            }
            mChannelUpdater.field_0x61 = 8;
        }

        for (u8 i = 0; i < 4; i++) {
            mChannelUpdater.field_0x3c[i] = mTimedParam.mMoveParams[TIMED_IIR_Unk0 + i].mCurrentValue * 0x7FFF;
        }

        mChannelUpdater.field_0x61 |= 0x20;
        mChannelUpdater.field_0x4c = mTimedParam.mMoveParams[TIMED_Unk5].mCurrentValue * 0x7FFF;
    }
}

/* 80281E90-80282364       .text updateTrack__Q28JASystem6TTrackFUl */
void JASystem::TTrack::updateTrack(u32 flags) {
    if (field_0x37b == 4) {
        return;
    }

    f32 pitch;
    f32 vol;
    f32 pan;
    f32 fx_vol;
    f32 dolby;

    f32 pan_weight = (f32)mRegisterParam.getPanPowerParent() / 0x7FFF;
    if (flags & OUTERPARAM_Unk18) {
        f32 unkF = mTimedParam.mMoveParams[TIMED_Unk17].mCurrentValue * 128.0f;
        s8 unk = 0;
        s8 delay;
        OSf32tos8(&unkF, &delay);
        if (delay < 0) {
            unk = -delay;
            delay = 0;
        }

        mChannelUpdater.field_0x5a[0] = unk;
        mChannelUpdater.field_0x5a[1] = delay;
    }

    if (flags & OUTERPARAM_Tempo && mParent == NULL) {
        updateTempo();
    }

    pitch = 1.0f;

    u32 set_volume = flags & OUTERPARAM_Volume;
    if (set_volume) {
        vol = mTimedParam.mMoveParams[TIMED_Volume].mCurrentValue;
        if (mVolumeMode == 0) {
            vol *= vol;
        }
        if (field_0x386) {
            vol = 0.0f;
        }
        if (mOuterParam && mOuterParam->checkOuterSwitch(OUTERPARAM_Volume)) {
            vol *= mOuterParam->getVolume();
        }
        if (mIsPaused && (mPauseStatus & 1)) {
            vol *= mTimedParam.mMoveParams[TIMED_Unk16].mCurrentValue;
        }
    }

    u32 set_pitch = flags & OUTERPARAM_Pitch;
    if (set_pitch) {
        pitch = Player::pitchToCent(mTimedParam.mMoveParams[TIMED_Pitch].mCurrentValue,
            mRegisterParam.field_0xe);
        pitch *= mVibrate.getValue();
        if (mOuterParam && mOuterParam->checkOuterSwitch(OUTERPARAM_Pitch)) {
            pitch *= mOuterParam->getPitch();
        }
    }

    u32 set_pan = flags & OUTERPARAM_Pan;
    if (set_pan) {
        pan = mTimedParam.mMoveParams[TIMED_Pan].mCurrentValue;
        if (mOuterParam && mOuterParam->checkOuterSwitch(OUTERPARAM_Pan)) {
            pan = panCalc(pan, mOuterParam->getPan(), pan_weight, mCalcTypes[0]);
        }
    }


    u32 set_fxvol = flags & OUTERPARAM_Fxmix;
    if (set_fxvol) {
        fx_vol = mTimedParam.mMoveParams[TIMED_Fxmix].mCurrentValue;
        if (mOuterParam && mOuterParam->checkOuterSwitch(OUTERPARAM_Fxmix)) {
            fx_vol = panCalc(fx_vol, mOuterParam->getFxVol(), pan_weight, mCalcTypes[1]);
        }
    }


    u32 set_dolby = flags & OUTERPARAM_Dolby;
    if (set_dolby) {
        dolby = mTimedParam.mMoveParams[TIMED_Dolby].mCurrentValue;
        if (mOuterParam && mOuterParam->checkOuterSwitch(OUTERPARAM_Dolby)) {
            dolby = panCalc(dolby, mOuterParam->getDolby(), pan_weight, mCalcTypes[2]);
        }
    }

    if (flags & OUTERPARAM_IIRFilter) {
        for (u8 i = 0; i < 4; i++) {
            mChannelUpdater.field_0x3c[i] = mTimedParam.mMoveParams[TIMED_IIR_Unk0 + i].mCurrentValue * 0x7FFF;
        }
        mChannelUpdater.field_0x61 |= 0x20;
    }

    if (mOuterParam && (flags & OUTERPARAM_FIR8Filter) && mOuterParam->checkOuterSwitch(OUTERPARAM_FIR8Filter)) {
        for (u8 i = 0; i < 8; i++) {
            mChannelUpdater.field_0x2c[i] = mOuterParam->getIntFirFilter(i);
        }
        mChannelUpdater.field_0x61 = (mChannelUpdater.field_0x61 & 0x20) + 8;
    }

    if (flags & OUTERPARAM_Unk6) {
        mChannelUpdater.field_0x4c = mTimedParam.mMoveParams[TIMED_Unk5].mCurrentValue * 0x7FFF;
    }

    if (mParent == NULL || (field_0x37b & 1)) {
        if (set_volume) {
            mChannelUpdater.field_0x18 = vol;
        }
        if (set_pitch) {
            mChannelUpdater.field_0x1c = pitch;
        }
        if (set_pan) {
            mChannelUpdater.field_0x20 = pan;
        }
        if (set_fxvol) {
            mChannelUpdater.field_0x24 = fx_vol;
        }
        if (set_dolby) {
            mChannelUpdater.field_0x28 = dolby;
        }
    } else {
        pan_weight = (f32)mRegisterParam.getPanPowerTrack() / 0x7FFF;
        if (set_volume) {
            mChannelUpdater.field_0x18 = mParent->mChannelUpdater.field_0x18 * vol;
        }
        if (set_pitch) {
            mChannelUpdater.field_0x1c = mParent->mChannelUpdater.field_0x1c * pitch;
        }
        if (set_pan) {
            mChannelUpdater.field_0x20 = panCalc(
                pan, mParent->mChannelUpdater.field_0x20, pan_weight, mParentCalcTypes[0]
            );
        }
        if (set_fxvol) {
            mChannelUpdater.field_0x24 = panCalc(
                fx_vol, mParent->mChannelUpdater.field_0x24, pan_weight, mParentCalcTypes[1]
            );
        }
        if (set_dolby) {
            mChannelUpdater.field_0x28 = panCalc(
                dolby, mParent->mChannelUpdater.field_0x28, pan_weight, mParentCalcTypes[2]
            );
        }
    }
}

/* 80282364-802824C0       .text updateTempo__Q28JASystem6TTrackFv */
void JASystem::TTrack::updateTempo() {
    if (mParent == NULL) {
        field_0x368 = (f32) field_0x378;
        field_0x368 *= field_0x376;
        field_0x368 /= Kernel::getDacRate();
        field_0x368 *= (4.0f / 3.0f);
        if (mOuterParam != NULL && mOuterParam->checkOuterSwitch(OUTERPARAM_Tempo)) {
            field_0x368 *= mOuterParam->getTempo();
        }
    } else {
        field_0x368 = mParent->field_0x368;
        field_0x378 = mParent->field_0x378;
    }

    for (s32 i = 0; i < MAX_CHILDREN; i++) {
        TTrack* child = mChildren[i];
        if (child && child->field_0x37e) {
            JUT_ASSERT(1159, this == child->mParent);
            child->updateTempo();
        }
    }
}

/* 802824C0-802825A0       .text updateSeq__Q28JASystem6TTrackFUlb */
void JASystem::TTrack::updateSeq(u32 flags, bool update_children) {
    u32 new_flags = flags | mUpdateFlags;
    if (mOuterParam) {
        new_flags |= mOuterParam->getOuterUpdate();
        mOuterParam->setOuterUpdate(0);
    }

    mVibrate.incCounter();
    mUpdateFlags = 0;

    if (new_flags != 0) {
        updateTrack(new_flags);
    }

    for (s32 i = 0; i < MAX_CHILDREN; i++) {
        TTrack* child = mChildren[i];
        if (child && child->field_0x37e) {
            if (update_children) {
                child->updateSeq(new_flags, update_children);
            } else {
                child->mUpdateFlags |= new_flags;
            }
        }
    }
}

/* 802825A0-8028265C       .text seqTimeToDspTime__Q28JASystem6TTrackFlUc */
int JASystem::TTrack::seqTimeToDspTime(s32 param_1, u8 param_2) {
    f32 f1 = ((f32)param_1 * (f32)param_2) / 100.0f;
    if (field_0x387) {
        f1 /= field_0x368;
    } else {
        f1 = 120.0f * f1;
        f1 /= field_0x378;
        if (Kernel::getOutputRate() == 0) {
            f1 = (f1 * Kernel::getSubFrames()) / 10.0f;
        }
    }
    return f1;
}

/* 8028265C-8028278C       .text setParam__Q28JASystem6TTrackFifi */
void JASystem::TTrack::setParam(int target, f32 target_value, int move_time) {
    JUT_ASSERT(1236, target >= 0);
    JUT_ASSERT(1237, target < TIMED_PARAMS);

    MoveParam_& param = mTimedParam.mMoveParams[target];
    param.mTargetValue = target_value;
    if (move_time <= 0) {
        param.mCurrentValue = param.mTargetValue;
        param.mMoveAmount = 0.0f;
        param.mMoveTime = 1.0f;
    } else {
        param.mMoveAmount = (param.mTargetValue - param.mCurrentValue) / move_time;
        param.mMoveTime = move_time;
    }
}

/* 8028278C-802827F0       .text setSeqData__Q28JASystem6TTrackFPUcli */
bool JASystem::TTrack::setSeqData(u8* param_1, s32, int) {
    init();
    field_0x37b = 3;
    mSeqCtrl.start(param_1, 0);
    updateTrackAll();
    field_0x37e = 2;
    return true;
}

/* 802827F0-802828A8       .text startSeq__Q28JASystem6TTrackFv */
bool JASystem::TTrack::startSeq() {
    switch (field_0x37e) {
        case 0:
            // Not ready
            OSReport("in Player StartSeq:   準備ができていません\n");
            return false;
        case 1:
            // Already started
            OSReport("in Player StartSeq:   既に動作中です\n");
            return false;
        case 3:
            // Stopping
            OSReport("in Player StartSeq: 停止中です\n");
            return false;
        case 2:
            field_0x37e = 1;
            break;
    }

    Kernel::registerSubFrameCallback(rootCallback, this);
    return true;
}

/* 802828A8-80282908       .text stopSeq__Q28JASystem6TTrackFv */
bool JASystem::TTrack::stopSeq() {
    switch (field_0x37e) {
        case 0:
            break;
        case 2:
            field_0x37e = 0;
            if (field_0x389 != 0 && this != NULL) {
                delete this;
            }
            break;
        default:
            field_0x37e = 3;
            break;
    }
    return true;
}

/* 80282908-80282944       .text stopSeqMain__Q28JASystem6TTrackFv */
void JASystem::TTrack::stopSeqMain() {
    updateSeq(0, 1);
    close();
}

/* 80282944-802829DC       .text noteOffAll__Q28JASystem6TTrackFv */
void JASystem::TTrack::noteOffAll() {
    if (mParent == NULL) {
        for (u8 i = 0; i < 8; i++) {
            noteOff(i, 10);
        }
    } else {
        for (u8 i = 0; i < 8; i++) {
            noteOff(i, 0);
        }
    }
    mNoteMgr.init();
}

/* 802829DC-80282A90       .text close__Q28JASystem6TTrackFv */
int JASystem::TTrack::close() {
    if (field_0x37e == 0) {
        return 0;
    }

    noteOffAll();
    field_0x37e = 0;

    for (s32 i = 0; i < MAX_CHILDREN; i++) {
        if (mChildren[i]) {
            mChildren[i]->close();
            mChildren[i] = NULL;
        }
    }

    field_0x386 = 0;
    releaseChannelAll();
    if (field_0x389 != 0 && this != NULL) {
        delete this;
    }
    return 0;
}

/* 80282A90-80282B44       .text muteTrack__Q28JASystem6TTrackFb */
void JASystem::TTrack::muteTrack(bool mute) {
    field_0x386 = mute;
    mUpdateFlags |= 1;

    if (field_0x386 && (mPauseStatus & 0x20)) {
        for (u8 i = 0; i < 8; i++) {
            noteOff(i, 10);
        }
    }

    for (s32 i = 0; i < MAX_CHILDREN; i++) {
        if (mChildren[i]) {
            mChildren[i]->muteTrack(mute);
        }
    }
}

/* 80282B44-80282B84       .text start__Q28JASystem6TTrackFPvUl */
bool JASystem::TTrack::start(void* param_1, u32 param_2) {
    mSeqCtrl.start(param_1, param_2);
    field_0x37e = 1;
    updateTrackAll();
    return false;
}

/* 80282B84-80282CE8       .text openChild__Q28JASystem6TTrackFUcUc */
JASystem::TTrack* JASystem::TTrack::openChild(u8 trk_no, u8 param_2) {
    JUT_ASSERT(1448, trk_no < MAX_CHILDREN);

    if (mChildren[(int)trk_no]) {
        mChildren[(int)trk_no]->close();
        mChildren[(int)trk_no] = NULL;
    }

    TTrack* new_track = new TTrack();

    if (new_track == NULL) {
        // Not enough JASTracks.
        JUT_WARN(1459, "%s", "JASTrackが足りません。\n");
        return NULL;
    }

    new_track->init();
    new_track->field_0x389 = 1;
    new_track->mParent = this;
    new_track->field_0x37b = param_2;

    u32 top_nibble = field_0x36c & 0xf0000000;
    if (top_nibble >= 0x70000000) {
        // "JASTrack:openTrack: The hierarchy exceeds 8 levels and an invalid track ID is generated.\n"
        OSReport("JASTrack:openTrack: 階層が8段階を超えてしまい、不正なトラックIDが生成されました\n");
    }
    new_track->field_0x36c = (top_nibble + 0x10000000) |
                             (((field_0x36c) << 4 | ((u32) trk_no)) & 0x0FFFFFFF);
    mChildren[(int)trk_no] = new_track;
    new_track->inherit();
    return new_track;
}

/* 80282CE8-80282D80       .text loadTbl__Q28JASystem6TTrackFUlUlUl */
int JASystem::TTrack::loadTbl(u32 param_1, u32 param_2, u32 param_3) {
    u32 param_2_times_three;

    switch (param_3) {
    case 4:
        return mSeqCtrl.mRawFilePtr[param_1 + param_2];
    case 5:
        return mSeqCtrl.get16(param_1 + param_2 * 2);
    case 6:
        // Doing (param_2 * 3) causes a multiply instruction instead of a shift
        // and add.
        param_2_times_three = param_2;
        param_2_times_three += param_2 * 2;
        return mSeqCtrl.get24(param_1 + param_2_times_three);
    case 7:
        return mSeqCtrl.get32(param_1 + param_2 * 4);
    case 8:
        return mSeqCtrl.get32(param_1 + param_2);
    }
}

/* 80282D80-80282DC0       .text exchangeRegisterValue__Q28JASystem6TTrackFUc */
int JASystem::TTrack::exchangeRegisterValue(u8 target) {
    if (target < 0x40) {
        return readReg32(target);
    }

    u8 index = target - 0x40;
    return mTrackPort.mValue[index];
}

/* 80282DC0-80282ED4       .text readReg32__Q28JASystem6TTrackFUc */
u32 JASystem::TTrack::readReg32(u8 target) {
    u32 result;
    switch(target) {
        case 0x28:
        case 0x29:
        case 0x2a:
        case 0x2b:
            result = mRegisterParam.getAddress(target - 0x28);
            break;
        case 0x23:
            result = readReg16(0x4) << 16;
            result |= readReg16(0x5);
            break;
        default:
            result = readReg16(target);
            break;
    }
    return result;
}

/* 80282ED4-802830AC       .text readReg16__Q28JASystem6TTrackFUc */
u16 JASystem::TTrack::readReg16(u8 target) {
    JUT_ASSERT(1553, target != TRegisterParam::PARAM_REG_XY);
    JUT_ASSERT(1554, target < TRegisterParam::PARAM_REG_AR0 || target > TRegisterParam::PARAM_REG_AR3);

    int i;
    u16 result;
    switch (target) {
        case 0x20:
            result = mRegisterParam.getBankNumber();
            break;
        case 0x21:
            result = mRegisterParam.getProgramNumber();
            break;
        case 0x22:
            result = readReg16(0) << 8;
            result |= readReg16(1);
            break;
        case 0x2C:
            result = 0;
            for (i = MAX_CHILDREN - 1; i >= 0; i--) {
                result <<= 1;
                if (mChildren[i] && mChildren[i]->field_0x37e != 0) {
                    result |= 1;
                }
            }
            break;
        case 0x2D:
            result = 0;
            for (i = 7; i >= 0; i--) {
                result <<= 1;
                result |= checkNoteStop(i);
            }
            break;
        case 0x30:
            result = mSeqCtrl.getLoopCount();
            break;
        default:
            result = mRegisterParam.field_0x0[target];
            break;
    }
    return result;
}

/* 802830AC-80283164       .text writeRegDirect__Q28JASystem6TTrackFUcUs */
void JASystem::TTrack::writeRegDirect(u8 target, u16 value) {
    u16 r4;
    u8 val_u8;

    switch (target) {
        case 0:
        case 1:
        case 2:
            val_u8 = value & 0xFF;
            value = val_u8;
            r4 = Player::extend8to16(val_u8);
            break;
        case 0x20:
        case 0x21:
            return;
        case 0x22:
            writeRegDirect(0, value >> 8);
            target = 1;
            r4 = value;
            value = value & 0xFF;
            break;
        default:
            r4 = value;
            break;
    }
    mRegisterParam.field_0x0[target] = value;
    mRegisterParam.setFlag(r4);
}

/* 80283164-802836FC       .text writeRegParam__Q28JASystem6TTrackFUc */
void JASystem::TTrack::writeRegParam(u8 param) {
    u8 curr_file_byte;
    u8 bVar0;
    u8 bVar1;

    u32 iVar1;
    u32 iVar2;
    u32 tbl_param_3;
    u32 tbl_param_1;

    s16 sVar0;
    s16 sVar1;
    u32 extended_sVar1;
    u32 tbl_return;

    switch (param & 0xF) {
        case 11:
            iVar1 = 0;
            iVar2 = 11;
            break;
        case 10:
            curr_file_byte = *mSeqCtrl.mCurrentFilePtr++;
            iVar1 = curr_file_byte & 0xC;
            tbl_param_3 = (curr_file_byte >> 4) + 4;
            iVar2 = 10;
            break;
        case 9:
            curr_file_byte = *mSeqCtrl.mCurrentFilePtr++;
            iVar1 = curr_file_byte & 0xC;
            iVar2 = curr_file_byte & 0xF0;
            if (iVar1 == 8) {
                iVar1 = 16;
            }
            break;
        default:
            iVar1 = param & 0xC;
            iVar2 = param & 0x3;
            break;
    }

    bVar0 = *mSeqCtrl.mCurrentFilePtr++;
    if (iVar2 == 10) {
        u8 next_file_byte = *mSeqCtrl.mCurrentFilePtr++;
        tbl_param_1 = readReg32(next_file_byte);
    }

    switch (iVar1) {
        case 0:
            bVar1 = *mSeqCtrl.mCurrentFilePtr++;
            sVar0 = readReg16(bVar1);
            break;
        case 4:
            sVar0 = *mSeqCtrl.mCurrentFilePtr++;
            break;
        case 12:
            sVar0 = mSeqCtrl.read16();
            break;
        case 8: {
            u32 byte = (u32) *mSeqCtrl.mCurrentFilePtr++;
            if (byte & 0x80) {
                sVar0 = byte << 8;
            } else {
                sVar0 = (byte << 8) | (byte << 1);
            }
            break;
        }
        case 16:
            sVar0 = -1;
            break;
    }

    sVar1 = readReg16(bVar0);
    extended_sVar1 = sVar1;

    switch (iVar2) {
        case 0x0:
            break;
        case 0x1:
            if (iVar1 == 4) {
                sVar0 = Player::extend8to16(sVar0);
            }
            sVar0 += sVar1;
            break;
        case 0x2:
            extended_sVar1 *= sVar0;
            writeRegDirect(4, extended_sVar1 >> 16);
            writeRegDirect(5, extended_sVar1 & 0xFFFF);
            return;
        case 0x3:
            mRegisterParam.setFlag(sVar1 - sVar0);
            return;
        case 0xB:
            sVar0 = extended_sVar1 - (u32)sVar0;
            break;
        case 0x10:
            if (iVar1 == 4) {
                sVar0 = Player::extend8to16(sVar0);
            }
            if (sVar0 < 0) {
                sVar0 = (u16)sVar1 >> -sVar0;
            } else {
                sVar0 = (u16)sVar1 << sVar0;
            }
            break;
        case 0x20:
            if (iVar1 == 4) {
                sVar0 = Player::extend8to16(sVar0);
            }
            if (sVar0 < 0) {
                sVar0 = sVar1 >> -sVar0;
            } else {
                sVar0 = sVar1 << sVar0;
            }
            break;
        case 0x30:
            sVar0 &= sVar1;
            break;
        case 0x40:
            sVar0 |= sVar1;
            break;
        case 0x50:
            sVar0 ^= sVar1;
            break;
        case 0x60:
            sVar0 = -sVar1;
            break;
        case 0x90:
            tbl_return = (u32)Player::getRandomS32();
            sVar0 = tbl_return % (u16)sVar0;
            break;
        case 0xA:
            tbl_return = loadTbl(tbl_param_1, sVar0, tbl_param_3);
            sVar0 = tbl_return & 0xFFFF;
            break;
    }

    // Bug: reg_flags is uninitialized in several of these cases.
    u16 reg_flags;
    switch (bVar0) {
        case 0:
        case 1:
        case 2: {
            u8 val3 = sVar0 & 0xFF;
            sVar0 = val3;
            reg_flags = Player::extend8to16(val3);
            break;
        }
        case 0x21: {
            u8 bank = mRegisterParam.getBankNumber();
            sVar0 = (bank << 8) | (sVar0 & 0xFF);
            bVar0 = 6;
            break;
        }
        case 0x20:
            sVar0 = mRegisterParam.getProgramNumber() | ((s16)sVar0 << 8);
            bVar0 = 6;
            break;
        case 0x2E:
            sVar0 = (mRegisterParam.field_0x1a & 0xFF00) | (sVar0 & 0xFF);
            bVar0 = 13;
            break;
        case 0x2F: {
            s16 tmp = (mRegisterParam.field_0x1a & 0xFF);
            sVar0 = tmp | (sVar0 << 8);
            bVar0 = 13;
            break;
        }
        case 0x22:
            writeRegDirect(0, sVar0 >> 8);
            bVar0 = 1;
            sVar0 = sVar0 & 0xFF;
            reg_flags = sVar0;
            break;
        case 0x28:
        case 0x29:
        case 0x2a:
        case 0x2b:
            mRegisterParam.setAddress(bVar0 - 0x28, tbl_return);
            return;
        default:
            reg_flags = sVar0;
            break;
    }

    mRegisterParam.field_0x0[bVar0] = sVar0;
    mRegisterParam.setFlag(reg_flags);

    switch (bVar0) {
        case 6:
            for (s32 i = 0; i < (s32)ARRAY_SIZE(mOscRoute); i++) {
                mOscRoute[i] = 0xF;
            }
            break;
        case 7:
            mUpdateFlags |= OUTERPARAM_Pitch;
            break;
        case 13:
            mChannelUpdater.field_0x68 = mRegisterParam.field_0x1a | 0x10000;
            mChannelUpdater.field_0x6c = 0;
            break;
    }
}

/* 802836FC-80283720       .text readSelfPort__Q28JASystem6TTrackFi */
u16 JASystem::TTrack::readSelfPort(int param_1) {
    return mTrackPort.readImport(param_1);
}

/* 80283720-80283744       .text writeSelfPort__Q28JASystem6TTrackFiUs */
void JASystem::TTrack::writeSelfPort(int param_1, u16 param_2) {
    mTrackPort.writeExport(param_1, param_2);
}

/* 80283744-802837AC       .text writePortAppDirect__Q28JASystem6TTrackFUlUs */
int JASystem::TTrack::writePortAppDirect(u32 port, u16 value) {
    mTrackPort.writeImport(port, value);
    if (port == 0 || port == 1) {
        mIntrMgr.request(port == 0 ? REQUEST_UNK_3 : REQUEST_UNK_4);
    }
    return 1;
}

/* 802837AC-802837E4       .text readPortAppDirect__Q28JASystem6TTrackFUlPUs */
int JASystem::TTrack::readPortAppDirect(u32 port, u16* read_ptr) {
    *read_ptr = mTrackPort.readExport(port);
    return 1;
}

/* 802837E4-8028381C       .text routeTrack__Q28JASystem6TTrackFUl */
JASystem::TTrack* JASystem::TTrack::routeTrack(u32 route) {
    TTrack* owning_track = this;

    u32 depth = route >> 28;
    for (u32 i = 0; i < depth; i++) {
        owning_track = owning_track->mChildren[route & 0xF];
        if (owning_track == NULL) {
            return NULL;
        }
        route >>= 4;
    }
    return owning_track;
}

/* 8028381C-80283870       .text writePortApp__Q28JASystem6TTrackFUlUs */
int JASystem::TTrack::writePortApp(u32 route, u16 value) {
    TTrack* track = routeTrack(route);
    if (track == NULL) {
        return 0;
    }

    return track->writePortAppDirect((route >> 16) & 0xFF, value);
}

/* 80283870-802838C4       .text readPortApp__Q28JASystem6TTrackFUlPUs */
int JASystem::TTrack::readPortApp(u32 route, u16* value_out) {
    TTrack* track = routeTrack(route);
    if (track == NULL) {
        return 0;
    }

    return track->readPortAppDirect((route >> 16) & 0xFF, value_out);
}

/* 802838C4-80283A64       .text pause__Q28JASystem6TTrackFbb */
void JASystem::TTrack::pause(bool pause, bool pause_children) {
    mIsPaused = pause;

    if (pause) {
        if (mPauseStatus & 1) {
            mUpdateFlags |= 1;
        }

        if (mPauseStatus & 0x4) {
            for (u8 i = 0; i < 8; i++) {
                noteOff(i, 10);
            }
        }

        if (mPauseStatus & 0x8) {
            for (s32 i = 0; i < 8; i++) {
                TChannel* channel = mNoteMgr.getChannel(i);
                if (channel) {
                    channel->setPauseFlag(1);
                }
            }
        }
    } else {
        mUpdateFlags |= 1;
        for (s32 i = 0; i < 8; i++) {
            TChannel* channel = mNoteMgr.getChannel(i);
            if (channel) {
                channel->setPauseFlag(0);
            }
        }
    }

    mIntrMgr.request(pause ? REQUEST_UNK_0 : REQUEST_UNK_1);

    if (pause_children) {
        for (s32 i = 0; i < MAX_CHILDREN; i++) {
            TTrack* child = mChildren[i];
            if (child && child->field_0x37e) {
                JUT_ASSERT(2109, this == child->mParent);
                child->pause(pause, true);
            }
        }
    }
}

/* 80283A64-80283AB4       .text getTranspose__Q28JASystem6TTrackCFv */
int JASystem::TTrack::getTranspose() const {
    if (mParent != NULL) {
        return mParent->getTranspose() + mTranspose;
    }
    return mTranspose;
}

/* 80283AB4-80283AF0       .text setTempo__Q28JASystem6TTrackFUs */
void JASystem::TTrack::setTempo(u16 tempo) {
    field_0x376 = tempo;
    if (mParent == NULL) {
        updateTempo();
    } else {
        field_0x388 = 1;
    }
}

/* 80283AF0-80283B20       .text setTimebase__Q28JASystem6TTrackFUs */
void JASystem::TTrack::setTimebase(u16 timeBase) {
    field_0x378 = timeBase;
    if (mParent == NULL) {
        updateTempo();
    }
}

/* 80283B20-80283BBC       .text panCalc__Q28JASystem6TTrackFfffUc */
f32 JASystem::TTrack::panCalc(f32 a, f32 b, f32 weight, u8 type) {
    switch (type) {
        case 0:
            return a;
        case 1:
            return b;
        case 2:
            return a * (1.0f - weight) + (b * weight);
    }
    JUT_ASSERT(2150, false);
    return 0.0f;
}

/* 80283BBC-80283C9C       .text rootCallback__Q28JASystem6TTrackFPv */
s32 JASystem::TTrack::rootCallback(void* user_data) {
    TTrack* track = (TTrack*)user_data;

    if (track == NULL) {
        return -1;
    }
    if (track->field_0x37e == 0) {
        return -1;
    }
    if (track->field_0x37e == 3) {
        track->stopSeqMain();
        return -1;
    }

    track->field_0x364 += track->field_0x368;
    if (track->field_0x364 < 1.0f) {
        track->updateSeq(0, true);
    } else {
        while (track->field_0x364 >= 1.0f) {
            track->field_0x364 -= 1.0f;
            if (track->mainProc() == -1) {
                track->stopSeqMain();
                return -1;
            }
        }
    }
    return 0;
}

/* 80283C9C-80283CE8       .text registerSeqCallback__Q28JASystem6TTrackFPFPQ28JASystem6TTrackUs_Us */
void JASystem::TTrack::registerSeqCallback(u16 (*param_1)(TTrack*, u16)) {
    if (sCallBackFunc) {
        OSReport("in Player ... overwrite callback\n");
    }
    sCallBackFunc = param_1;
}

/* 80283CE8-80283E9C       .text newMemPool__Q28JASystem6TTrackFi */
void JASystem::TTrack::newMemPool(int param_1) {
    TTrack* runner = (TTrack*)new (JASDram, 0) u8[sizeof(TTrack)];
    JUT_ASSERT(2218, runner);
    TOuterParam* outer = new (JASDram, 0) TOuterParam();
    JUT_ASSERT(2220, outer);
    runner->assignExtBuffer(outer);
    sFreeList = runner;
    for (int i = 1; i < param_1; i++) {
        runner->next = (TTrack*)new (JASDram, 0) u8[sizeof(TTrack)];
        JUT_ASSERT(2228, runner->next);
        TOuterParam* outer = new (JASDram, 0) TOuterParam();
        JUT_ASSERT(2230, outer);
        runner->next->assignExtBuffer(outer);
        runner = runner->next;
    }
    runner->next = NULL;
}

/* 80283E9C-80283ECC       .text __ct__Q28JASystem8TVibrateFv */
JASystem::TVibrate::TVibrate() {
    init();
}

/* 80283ECC-80283EE4       .text init__Q28JASystem8TVibrateFv */
void JASystem::TVibrate::init() {
    mPitch = 1.0f / 18.0f;
    mDepth = 0.0f;
    field_0x0 = 0.0f;
}

/* 80283EE4-80283F18       .text incCounter__Q28JASystem8TVibrateFv */
void JASystem::TVibrate::incCounter() {
    field_0x0 += mPitch;
    if (!(field_0x0 >= 4.0)) {
        return;
    }
    field_0x0 -= 4.0f;
}

/* 80283F18-80283FD0       .text getValue__Q28JASystem8TVibrateCFv */
f32 JASystem::TVibrate::getValue() const {
    if (mDepth == 0.0f) {
        return 1.0f;
    }

    f32 value = field_0x0;
    bool flag = false;

    if (value < 2.0) {
        if (!(value < 1.0)) {
            value = 2.0f - value;
        }
    } else {
        flag = true;
        if (value < 3.0) {
            value = value - 2.0f;
        } else {
            value = 4.0f - value;
        }
    }

    f32 sin_val = Calc::sinfT(value);
    if (flag) {
        sin_val = -sin_val;
    }
    return Player::pitchToCent(sin_val * mDepth, 12.0f);
}
