#include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JASChannel.h" #if TARGET_PC #include "dusk/audio/DuskDsp.hpp" #endif #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASCalc.h" #include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JSupport/JSupport.h" #include "JSystem/JMath/JMATrigonometric.h" #include "JSystem/JGeometry.h" OSMessageQueue JASChannel::sBankDisposeMsgQ; OSMessage JASChannel::sBankDisposeMsg[16]; OSMessage JASChannel::sBankDisposeList[16]; int JASChannel::sBankDisposeListSize; JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mStatus(STATUS_STOP), mDspCh(NULL), mCallback(i_callback), mCallbackData(i_callbackData), mUpdateTimer(0), mBankDisposeID(NULL), mKey(0), mVelocity(0x7f), mKeySweep(0.0f), mKeySweepTarget(0.0f), mKeySweepCount(0), mSkipSamples(0) { field_0xdc.mChannelType = 0; field_0x104 = 0; mMixConfig[0].whole = 0x150; mMixConfig[1].whole = 0x210; mMixConfig[2].whole = 0x352; mMixConfig[3].whole = 0x412; mMixConfig[4].whole = 0; mMixConfig[5].whole = 0; mPriority = 0x13f; mPauseFlag = false; } JASChannel::~JASChannel() { if (mDspCh != NULL) { JUT_WARN(62, "%s","~JASChannel:: mDspCh != NULL"); mDspCh->drop(); } if (mCallback != NULL) { mCallback(CB_STOP, this, NULL, mCallbackData); } } int JASChannel::play() { JASDSPChannel* channel = JASDSPChannel::alloc(JSULoByte(mPriority), dspUpdateCallback, this); if (channel == NULL) { JKR_DELETE(this); return 0; } mDspCh = channel; channel->start(); mStatus = STATUS_PLAY; return 1; } int JASChannel::playForce() { JASDSPChannel* channel = JASDSPChannel::allocForce(JSULoByte(mPriority), dspUpdateCallback, this); if (channel == NULL) { JKR_DELETE(this); return 0; } mDspCh = channel; channel->start(); mStatus = STATUS_PLAY; return 1; } void JASChannel::release(u16 i_directRelease) { if (mStatus == STATUS_PLAY) { if (i_directRelease != 0) { setDirectRelease(i_directRelease); } for (u32 i = 0; i < 2; i++) { if (mOscillators[i].isValid()) { mOscillators[i].release(); } } JUT_ASSERT(135, mDspCh); mDspCh->setPriority(JSUHiByte(mPriority)); mStatus = STATUS_RELEASE; } } void JASChannel::setOscInit(u32 oscnum, JASOscillator::Data const* i_data) { JUT_ASSERT(147, oscnum < OSC_NUM); mOscillators[oscnum].initStart(i_data); } void JASChannel::setMixConfig(u32 bus, u16 i_config) { JUT_ASSERT(153, bus < BUSOUT_CPUCH); mMixConfig[bus].whole = i_config; } f32 JASChannel::calcEffect(JASChannel::PanVector const* i_vector) { return i_vector->mSound + i_vector->mEffect + i_vector->mChannel; } f32 JASChannel::calcPan(JASChannel::PanVector const* i_vector) { return 0.5f + (i_vector->mSound - 0.5f) + (i_vector->mEffect - 0.5f) + (i_vector->mChannel - 0.5f); } void JASChannel::effectOsc(u32 oscnum, JASOscillator::EffectParams* i_params) { JUT_ASSERT(176, oscnum < OSC_NUM); f32 value = mOscillators[oscnum].getValue(); switch (mOscillators[oscnum].getTarget()) { case JASOscillator::TARGET_PITCH: i_params->mPitch *= value; break; case JASOscillator::TARGET_VOLUME: i_params->mVolume *= value; break; case JASOscillator::TARGET_PAN: value -= 0.5; i_params->mPan += value; break; case JASOscillator::TARGET_FXMIX: i_params->mFxMix += value; break; case JASOscillator::TARGET_DOLBY: i_params->mDolby += value; break; case JASOscillator::TARGET_5: i_params->_14 *= value; break; case JASOscillator::TARGET_6: i_params->_18 *= value; break; default: JUT_WARN(205, "%s", "Invalid osc target"); } } void JASChannel::setKeySweepTarget(s32 i_target, u32 i_count) { if (i_count == 0) { mKeySweep = i_target; } else { mKeySweep = 0.0f; mKeySweepTarget = i_target; } mKeySweepCount = i_count; } void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVolume, JASOscillator::EffectParams const& i_params) { PanVector pan_vector, fxmix_vector, dolby_vector; pan_vector.mSound = mSoundParams.mPan; pan_vector.mChannel = mParams.mPan; pan_vector.mEffect = i_params.mPan; fxmix_vector.mSound = mSoundParams.mFxMix; fxmix_vector.mChannel = mParams.mFxMix; fxmix_vector.mEffect = i_params.mFxMix; dolby_vector.mSound = mSoundParams.mDolby; dolby_vector.mChannel = mParams.mDolby; dolby_vector.mEffect = i_params.mDolby; f32 pan = 0.5f; f32 dolby = 0.0f; #if TARGET_PC u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode(); #else u32 effectiveOutputMode = JASDriver::getOutputMode(); #endif switch (effectiveOutputMode) { case JAS_OUTPUT_MONO: break; case JAS_OUTPUT_STEREO: pan = calcPan(&pan_vector); break; case JAS_OUTPUT_SURROUND: pan = calcPan(&pan_vector); dolby = calcEffect(&dolby_vector); break; } f32 fxmix = calcEffect(&fxmix_vector); f32 volume = mVelocity / 127.0f; volume = volume * volume; volume = mSoundParams.mVolume * i_params.mVolume * mParams.mVolume * (i_params._18 * mTremolo.getValue() + 1.0f) * volume; if (volume < 0.0f) { volume = 0.0f; } pan = JASCalc::clamp01(pan); fxmix = JASCalc::clamp01(fxmix); dolby = JASCalc::clamp01(dolby); if (isDolbyMode()) { updateAutoMixer(i_channel, volume, pan, fxmix, dolby); } else { updateMixer(volume, pan, fxmix, dolby, i_mixerVolume); } } s32 JASChannel::dspUpdateCallback(u32 i_type, JASDsp::TChannel* i_channel, void* i_this) { JASChannel* _this = static_cast(i_this); switch (i_type) { case JASDSPChannel::CB_PLAY: return _this->updateDSPChannel(i_channel); case JASDSPChannel::CB_START: return _this->initialUpdateDSPChannel(i_channel); case JASDSPChannel::CB_STOP: case JASDSPChannel::CB_DROP: _this->mDspCh->free(); _this->mDspCh = NULL; JKR_DELETE(_this); return -1; default: JUT_WARN(323, "Unexpected JASDSPChannel::UpdateStatus %d", i_type); } return 0; } s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { if (isDolbyMode()) { i_channel->initAutoMixer(); } if (mCallback != NULL) { mCallback(CB_START, this, i_channel, mCallbackData); } if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; JKR_DELETE(this); return -1; } if (checkBankDispose()) { JUT_WARN_DEVICE(357, 2, "%s","Lost bank data while playing"); mDspCh->free(); mDspCh = NULL; JKR_DELETE(this); return -1; } switch (field_0xdc.mChannelType) { case 0: i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); break; case 2: i_channel->setOscInfo(mOscillatorSomething); break; } for (u8 i = 0; i < DSP_OUTPUT_CHANNELS; i++) { MixConfig mix_config = mMixConfig[i]; u32 output_mode = JASDriver::getOutputMode(); if (output_mode == JAS_OUTPUT_MONO) { switch (mix_config.parts.upper) { case 8: mix_config.parts.upper = 11; break; case 9: mix_config.parts.upper = 2; break; } } else if (output_mode == JAS_OUTPUT_STEREO && mix_config.parts.upper == 8) { mix_config.parts.upper = 11; } i_channel->setBusConnect(i, mix_config.parts.upper); } JASOscillator::EffectParams effect_params; for (u32 i = 0; i < 2; i++) { if (mOscillators[i].isValid()) { mOscillators[i].update(); effectOsc(i, &effect_params); } } mVibrate.resetCounter(); mTremolo.resetCounter(); u16 mixer_volume[DSP_OUTPUT_CHANNELS]; updateEffectorParam(i_channel, mixer_volume, effect_params); for (u8 i = 0; i < DSP_OUTPUT_CHANNELS; i++) { i_channel->setMixerInitVolume(i, mixer_volume[i]); } f32 pitch = JASCalc::pow2(mParams.field_0x8 + (mKey + mKeySweep) / 12.0f + effect_params._14 * mVibrate.getValue()); pitch = mSoundParams.mPitch * effect_params.mPitch * pitch * mParams.mPitch * 4096.0f; if (pitch < 0.0f) { pitch = 0.0f; } i_channel->setPitch(pitch); i_channel->setPauseFlag(mPauseFlag); i_channel->field_0x066 = 0; return 0; } s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { JUT_ASSERT(444, mStatus == STATUS_PLAY || mStatus == STATUS_RELEASE); JUT_ASSERT(445, mDspCh); if (mCallback != NULL) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; JKR_DELETE(this); return -1; } if (checkBankDispose()) { JUT_WARN_DEVICE(467, 2, "%s", "Lost bank data while playing"); mDspCh->free(); mDspCh = NULL; JKR_DELETE(this); return -1; } i_channel->setPauseFlag(mPauseFlag); JASOscillator::EffectParams effect_params; if (mPauseFlag) { if (mOscillators[0].isRelease()) { mDspCh->free(); mDspCh = NULL; JKR_DELETE(this); return -1; } } else { f32 inc = 32028.5f / JASDriver::getDacRate(); mVibrate.incCounter(inc); mTremolo.incCounter(inc); if (mUpdateTimer != 0) { mUpdateTimer--; if (mUpdateTimer == 0 && mCallback != NULL) { mCallback(CB_TIMER, this, i_channel, mCallbackData); } } inc = 48000.0f / JASDriver::getDacRate(); for (u32 i = 0; i < 2; i++) { if (mOscillators[i].isValid()) { mOscillators[i].incCounter(inc); effectOsc(i, &effect_params); if (i == 0 && mOscillators[i].isStop()) { mDspCh->free(); mDspCh = NULL; JKR_DELETE(this); return -1; } } } } u16 mixer_volume[6]; updateEffectorParam(i_channel, mixer_volume, effect_params); for (u8 i = 0; i < 6; i++) { i_channel->setMixerVolume(i, mixer_volume[i]); } f32 pitch = JASCalc::pow2(mParams.field_0x8 + (mKey + mKeySweep) / 12.0f + effect_params._14 * mVibrate.getValue()); pitch = mSoundParams.mPitch * effect_params.mPitch * pitch * mParams.mPitch * 4096.0f; if (pitch < 0.0f) { pitch = 0.0f; } i_channel->setPitch(pitch); if (!mPauseFlag && mKeySweepCount != 0) { mKeySweep += (mKeySweepTarget - mKeySweep) / mKeySweepCount; mKeySweepCount--; } return 0; } void JASChannel::updateAutoMixer(JASDsp::TChannel* i_channel, f32 volume, f32 pan, f32 fxmix, f32 dolby) { if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { volume *= 0.707f; } volume = JASCalc::clamp01(volume); u16 dspVolume = volume * JASDriver::getChannelLevel_dsp(); u8 dspPan = pan * 127.5f; u8 dspDolby = dolby * 127.5f; u8 dspFxMix = fxmix * 127.5f; i_channel->setAutoMixer(dspVolume, dspPan, dspDolby, dspFxMix, 0); } void JASChannel::updateMixer(f32 i_volume, f32 i_pan, f32 i_fxmix, f32 i_dolby, u16* i_volumeOut) { for (u32 i = 0; i < 6; i++) { f32 volume = i_volume; MixConfig config = mMixConfig[i]; if (config.parts.upper == 0) { i_volumeOut[i] = 0; } else { f32 scale; if (config.parts.lower0 != 0) { switch (config.parts.lower0) { case 1: scale = i_pan; break; case 2: scale = i_fxmix; break; case 3: scale = i_dolby; break; case 5: scale = 1.0f - i_pan; break; case 6: scale = 1.0f - i_fxmix; break; case 7: scale = 1.0f - i_dolby; break; } switch (config.parts.lower0) { case 2: case 6: volume *= scale; break; default: if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { volume *= scale; } else { volume *= JMASinRadian(scale * JGeometry::TUtil::PI() * 0.5f); } break; } } if (config.parts.lower1 != 0) { switch (config.parts.lower1) { case 1: scale = i_pan; break; case 2: scale = i_fxmix; break; case 3: scale = i_dolby; break; case 5: scale = 1.0f - i_pan; break; case 6: scale = 1.0f - i_fxmix; break; case 7: scale = 1.0f - i_dolby; break; } switch (config.parts.lower1) { case 3: case 7: volume *= JMASinRadian((scale * 0.34776f + 0.32612f) * JGeometry::TUtil::PI() * 0.5f); break; case 2: case 6: volume *= scale; break; default: // 0, 1, 4, 5, 8-15 if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { volume *= scale; } else { volume *= JMASinRadian(scale * JGeometry::TUtil::PI() * 0.5f); } break; } } volume = JASCalc::clamp01(volume); i_volumeOut[i] = volume * JASDriver::getChannelLevel_dsp(); } } } void JASChannel::free() { JUT_ASSERT(661, mStatus == STATUS_RELEASE || mStatus == STATUS_STOP); mCallback = NULL; mCallbackData = NULL; } void JASChannel::initBankDisposeMsgQueue() { OSInitMessageQueue(&sBankDisposeMsgQ, sBankDisposeMsg, 0x10); sBankDisposeListSize = 0; } void JASChannel::receiveBankDisposeMsg() { OSMessage msg; for (sBankDisposeListSize = 0; OSReceiveMessage(&sBankDisposeMsgQ, &msg, OS_MESSAGE_NOBLOCK); sBankDisposeListSize++) { sBankDisposeList[sBankDisposeListSize] = msg; } } bool JASChannel::checkBankDispose() const { if (mBankDisposeID == NULL) { return false; } for (int i = 0; i < sBankDisposeListSize; i++) { if (mBankDisposeID == sBankDisposeList[i]) { return true; } } return false; }