n2xhardware.cpp (9727B)
1 #include "n2xhardware.h" 2 3 #include "n2xromloader.h" 4 #include "dsp56kEmu/threadtools.h" 5 #include "synthLib/deviceException.h" 6 7 namespace n2x 8 { 9 constexpr uint32_t g_syncEsaiFrameRate = 16; 10 constexpr uint32_t g_syncHaltDspEsaiThreshold = 32; 11 12 static_assert((g_syncEsaiFrameRate & (g_syncEsaiFrameRate - 1)) == 0, "esai frame sync rate must be power of two"); 13 static_assert(g_syncHaltDspEsaiThreshold >= g_syncEsaiFrameRate * 2, "esai DSP halt threshold must be greater than two times the sync rate"); 14 15 Rom initRom(const std::vector<uint8_t>& _romData, const std::string& _romName) 16 { 17 if(_romData.empty()) 18 return RomLoader::findROM(); 19 Rom rom(_romData, _romName); 20 if(rom.isValid()) 21 return rom; 22 return RomLoader::findROM(); 23 } 24 25 Hardware::Hardware(const std::vector<uint8_t>& _romData, const std::string& _romName) 26 : m_rom(initRom(_romData, _romName)) 27 , m_uc(*this, m_rom) 28 , m_dspA(*this, m_uc.getHdi08A(), 0) 29 , m_dspB(*this, m_uc.getHdi08B(), 1) 30 , m_samplerateInv(1.0 / g_samplerate) 31 , m_semDspAtoB(2) 32 { 33 if(!m_rom.isValid()) 34 throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "No firmware found, expected firmware .bin with a size of " + std::to_string(Rom::MySize) + " bytes"); 35 36 m_dspA.getPeriph().getEsai().setCallback([this](dsp56k::Audio*){ onEsaiCallbackA(); }, 0); 37 m_dspB.getPeriph().getEsai().setCallback([this](dsp56k::Audio*){ onEsaiCallbackB(); }, 0); 38 39 m_ucThread.reset(new std::thread([this] 40 { 41 ucThreadFunc(); 42 })); 43 44 while(!m_bootFinished) 45 processAudio(8,8); 46 m_midiOffsetCounter = 0; 47 } 48 49 Hardware::~Hardware() 50 { 51 m_destroy = true; 52 53 while(m_destroy) 54 processAudio(8,64); 55 56 m_dspA.terminate(); 57 m_dspB.terminate(); 58 59 m_esaiFrameIndex = 0; 60 m_esaiLatency = 0; 61 62 while(!m_dspA.getDSPThread().runThread() || !m_dspB.getDSPThread().runThread()) 63 { 64 // DSP A waits for space to push to DSP B 65 m_semDspAtoB.notify(); 66 67 if(m_dspB.getPeriph().getEsai().getAudioInputs().full()) 68 m_dspB.getPeriph().getEsai().getAudioInputs().pop_front(); 69 70 // DSP B waits for ESAI rate limiting and for DSP A to provide audio data 71 m_haltDSPSem.notify(999999); 72 if(m_dspA.getPeriph().getEsai().getAudioOutputs().empty()) 73 m_dspA.getPeriph().getEsai().getAudioOutputs().push_back({}); 74 } 75 76 m_ucThread->join(); 77 } 78 79 bool Hardware::isValid() const 80 { 81 return m_rom.isValid(); 82 } 83 84 void Hardware::processUC() 85 { 86 if(m_remainingUcCycles <= 0) 87 syncUCtoDSP(); 88 89 const auto deltaCycles = m_uc.exec(); 90 91 if(m_esaiFrameIndex > 0) 92 m_remainingUcCycles -= static_cast<int64_t>(deltaCycles); 93 } 94 95 void Hardware::processAudio(uint32_t _frames, const uint32_t _latency) 96 { 97 getMidi().process(_frames); 98 99 ensureBufferSize(_frames); 100 101 dsp56k::TWord* outputs[12]{nullptr}; 102 outputs[1] = &m_audioOutputs[0].front(); 103 outputs[0] = &m_audioOutputs[1].front(); 104 outputs[3] = &m_audioOutputs[2].front(); 105 outputs[2] = &m_audioOutputs[3].front(); 106 outputs[4] = m_dummyOutput.data(); 107 outputs[5] = m_dummyOutput.data(); 108 outputs[6] = m_dummyOutput.data(); 109 outputs[7] = m_dummyOutput.data(); 110 outputs[8] = m_dummyOutput.data(); 111 outputs[9] = m_dummyOutput.data(); 112 outputs[10] = m_dummyOutput.data(); 113 outputs[11] = m_dummyOutput.data(); 114 115 auto& esaiB = m_dspB.getPeriph().getEsai(); 116 117 // LOG("B out " << esaiB.getAudioOutputs().size() << ", A out " << esaiA.getAudioOutputs().size() << ", B in " << esaiB.getAudioInputs().size()); 118 119 while (_frames) 120 { 121 const auto processCount = std::min(_frames, static_cast<uint32_t>(64)); 122 _frames -= processCount; 123 124 advanceSamples(processCount, _latency); 125 126 const auto requiredSize = processCount > 8 ? processCount - 8 : 0; 127 128 if(esaiB.getAudioOutputs().size() < requiredSize) 129 { 130 // reduce thread contention by waiting for output buffer to be full enough to let us grab the data without entering the read mutex too often 131 132 std::unique_lock uLock(m_requestedFramesAvailableMutex); 133 m_requestedFrames = requiredSize; 134 m_requestedFramesAvailableCv.wait(uLock, [&]() 135 { 136 if(esaiB.getAudioOutputs().size() < requiredSize) 137 return false; 138 m_requestedFrames = 0; 139 return true; 140 }); 141 } 142 143 // read output of DSP B to regular audio output 144 esaiB.processAudioOutputInterleaved(outputs, processCount); 145 146 outputs[0] += processCount; 147 outputs[1] += processCount; 148 outputs[2] += processCount; 149 outputs[3] += processCount; 150 } 151 } 152 153 void Hardware::processAudio(const synthLib::TAudioOutputs& _outputs, const uint32_t _frames, const uint32_t _latency) 154 { 155 processAudio(_frames, _latency); 156 157 for(size_t i=0; i<_frames; ++i) 158 { 159 _outputs[0][i] = dsp56k::dsp2sample<float>(m_audioOutputs[0][i]); 160 _outputs[1][i] = dsp56k::dsp2sample<float>(m_audioOutputs[1][i]); 161 _outputs[2][i] = dsp56k::dsp2sample<float>(m_audioOutputs[2][i]); 162 _outputs[3][i] = dsp56k::dsp2sample<float>(m_audioOutputs[3][i]); 163 } 164 } 165 166 bool Hardware::sendMidi(const synthLib::SMidiEvent& _ev) 167 { 168 m_midiIn.push_back(_ev); 169 return true; 170 } 171 172 void Hardware::notifyBootFinished() 173 { 174 m_bootFinished = true; 175 } 176 177 void Hardware::ensureBufferSize(const uint32_t _frames) 178 { 179 if(m_dummyInput.size() >= _frames) 180 return; 181 182 m_dummyInput.resize(_frames, 0); 183 m_dummyOutput.resize(_frames, 0); 184 185 for (auto& audioOutput : m_audioOutputs) 186 audioOutput.resize(_frames, 0); 187 188 m_dspAtoBBuffer.resize(_frames * 4); 189 } 190 191 void Hardware::onEsaiCallbackA() 192 { 193 // forward DSP A output to DSP B input 194 const auto out = m_dspA.getPeriph().getEsai().getAudioOutputs().pop_front(); 195 196 dsp56k::Audio::RxFrame in; 197 in.resize(out.size()); 198 199 in[0] = dsp56k::Audio::RxSlot{out[0][0]}; 200 in[1] = dsp56k::Audio::RxSlot{out[1][0]}; 201 in[2] = dsp56k::Audio::RxSlot{out[2][0]}; 202 in[3] = dsp56k::Audio::RxSlot{out[3][0]}; 203 204 m_dspB.getPeriph().getEsai().getAudioInputs().push_back(in); 205 206 m_semDspAtoB.wait(); 207 } 208 209 void Hardware::processMidiInput() 210 { 211 ++m_midiOffsetCounter; 212 213 while(!m_midiIn.empty()) 214 { 215 const auto& e = m_midiIn.front(); 216 217 if(e.offset > m_midiOffsetCounter) 218 break; 219 220 getMidi().write(e); 221 m_midiIn.pop_front(); 222 } 223 } 224 225 void Hardware::onEsaiCallbackB() 226 { 227 m_semDspAtoB.notify(); 228 229 ++m_esaiFrameIndex; 230 231 processMidiInput(); 232 233 if((m_esaiFrameIndex & (g_syncEsaiFrameRate-1)) == 0) 234 m_esaiFrameAddedCv.notify_one(); 235 236 m_requestedFramesAvailableMutex.lock(); 237 238 if(m_requestedFrames && m_dspB.getPeriph().getEsai().getAudioOutputs().size() >= m_requestedFrames) 239 { 240 m_requestedFramesAvailableMutex.unlock(); 241 m_requestedFramesAvailableCv.notify_one(); 242 } 243 else 244 { 245 m_requestedFramesAvailableMutex.unlock(); 246 } 247 248 m_haltDSPSem.wait(1); 249 } 250 251 void Hardware::syncUCtoDSP() 252 { 253 assert(m_remainingUcCycles <= 0); 254 255 // we can only use ESAI to clock the uc once it has been enabled 256 if(m_esaiFrameIndex <= 0) 257 return; 258 259 if(m_esaiFrameIndex == m_lastEsaiFrameIndex) 260 { 261 resumeDSPs(); 262 std::unique_lock uLock(m_esaiFrameAddedMutex); 263 m_esaiFrameAddedCv.wait(uLock, [this]{return m_esaiFrameIndex > m_lastEsaiFrameIndex;}); 264 } 265 266 const auto esaiFrameIndex = m_esaiFrameIndex; 267 const auto esaiDelta = esaiFrameIndex - m_lastEsaiFrameIndex; 268 269 const auto ucClock = m_uc.getSim().getSystemClockHz(); 270 const double ucCyclesPerFrame = static_cast<double>(ucClock) * m_samplerateInv; 271 272 // if the UC consumed more cycles than it was allowed to, remove them from remaining cycles 273 m_remainingUcCyclesD += static_cast<double>(m_remainingUcCycles); 274 275 // add cycles for the ESAI time that has passed 276 m_remainingUcCyclesD += ucCyclesPerFrame * static_cast<double>(esaiDelta); 277 278 // set new remaining cycle count 279 m_remainingUcCycles = static_cast<int64_t>(m_remainingUcCyclesD); 280 281 // and consume them 282 m_remainingUcCyclesD -= static_cast<double>(m_remainingUcCycles); 283 284 if(esaiDelta > g_syncHaltDspEsaiThreshold) 285 haltDSPs(); 286 287 m_lastEsaiFrameIndex = esaiFrameIndex; 288 } 289 290 void Hardware::ucThreadFunc() 291 { 292 dsp56k::ThreadTools::setCurrentThreadName("MC68331"); 293 dsp56k::ThreadTools::setCurrentThreadPriority(dsp56k::ThreadPriority::Highest); 294 295 while(!m_destroy) 296 { 297 processUC(); 298 processUC(); 299 processUC(); 300 processUC(); 301 processUC(); 302 processUC(); 303 processUC(); 304 processUC(); 305 } 306 resumeDSPs(); 307 m_destroy = false; 308 } 309 310 void Hardware::advanceSamples(const uint32_t _samples, const uint32_t _latency) 311 { 312 // if the latency was higher first but now is lower, we might report < 0 samples. In this case we 313 // cannot notify but have to wait for another sample block until we can notify again 314 315 const auto latencyDiff = static_cast<int>(_latency) - static_cast<int>(m_esaiLatency); 316 m_esaiLatency = _latency; 317 318 const auto notifyCount = static_cast<int>(_samples) + latencyDiff + m_dspNotifyCorrection; 319 320 if (notifyCount > 0) 321 { 322 m_haltDSPSem.notify(notifyCount); 323 m_dspNotifyCorrection = 0; 324 } 325 else 326 { 327 m_dspNotifyCorrection = notifyCount; 328 } 329 } 330 331 void Hardware::haltDSPs() 332 { 333 if(m_dspHalted) 334 return; 335 m_dspHalted = true; 336 // LOG("Halt"); 337 m_dspA.getHaltDSP().haltDSP(); 338 m_dspB.getHaltDSP().haltDSP(); 339 } 340 341 void Hardware::resumeDSPs() 342 { 343 if(!m_dspHalted) 344 return; 345 m_dspHalted = false; 346 // LOG("Resume"); 347 m_dspA.getHaltDSP().resumeDSP(); 348 m_dspB.getHaltDSP().resumeDSP(); 349 } 350 351 bool Hardware::getButtonState(const ButtonType _type) const 352 { 353 return m_uc.getFrontPanel().getButtonState(_type); 354 } 355 356 void Hardware::setButtonState(const ButtonType _type, const bool _pressed) 357 { 358 m_uc.getFrontPanel().setButtonState(_type, _pressed); 359 } 360 361 uint8_t Hardware::getKnobPosition(KnobType _knob) const 362 { 363 return m_uc.getFrontPanel().getKnobPosition(_knob); 364 } 365 366 void Hardware::setKnobPosition(KnobType _knob, uint8_t _value) 367 { 368 return m_uc.getFrontPanel().setKnobPosition(_knob, _value); 369 } 370 }