gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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 }