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

mqhardware.cpp (10247B)


      1 #include "mqhardware.h"
      2 
      3 #include "synthLib/midiBufferParser.h"
      4 #include "synthLib/deviceException.h"
      5 
      6 #include <cstring>	// memcpy
      7 
      8 namespace mqLib
      9 {
     10 	Hardware::Hardware(const ROM& _rom)
     11 		: wLib::Hardware(44100)
     12 		, m_rom(_rom)
     13 		, m_uc(m_rom)
     14 #if MQ_VOICE_EXPANSION
     15 		, m_dsps{MqDsp(*this, m_uc.getHdi08A().getHdi08(), 0), MqDsp(*this, m_uc.getHdi08B().getHdi08(), 1) , MqDsp(*this, m_uc.getHdi08C().getHdi08(), 2)}
     16 #else
     17 		, m_dsps{MqDsp(*this, m_uc.getHdi08A().getHdi08(), 0)}
     18 #endif
     19 		, m_midi(m_uc.getQSM(), 44100)
     20 	{
     21 		if(!m_rom.isValid())
     22 			throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing);
     23 
     24 		m_uc.getPortF().setDirectionChangeCallback([&](const mc68k::Port& _port)
     25 		{
     26 			if(_port.getDirection() == 0xff)
     27 				setGlobalDefaultParameters();
     28 		});
     29 	}
     30 
     31 	Hardware::~Hardware()
     32 	{
     33 		m_dsps.front().getPeriph().getEsai().setCallback({}, 0);
     34 	}
     35 
     36 	void Hardware::process()
     37 	{
     38 		processUcCycle();
     39 	}
     40 
     41 	void Hardware::setBootMode(const BootMode _mode)
     42 	{
     43 		auto setButton = [&](const Buttons::ButtonType _type, const bool _pressed = true)
     44 		{
     45 			m_uc.getButtons().setButton(_type, _pressed);
     46 		};
     47 
     48 		switch (_mode)
     49 		{
     50 		case BootMode::Default: 
     51 			setButton(Buttons::ButtonType::Inst1, false);
     52 			setButton(Buttons::ButtonType::Inst3, false);
     53 			setButton(Buttons::ButtonType::Shift, false);
     54 			setButton(Buttons::ButtonType::Global, false);
     55 			setButton(Buttons::ButtonType::Multi, false);
     56 			setButton(Buttons::ButtonType::Play, false);
     57 			break;
     58 		case BootMode::FactoryTest:
     59 			setButton(Buttons::ButtonType::Inst1);
     60 			setButton(Buttons::ButtonType::Global);
     61 			break;
     62 		case BootMode::EraseFlash:
     63 			setButton(Buttons::ButtonType::Inst3);
     64 			setButton(Buttons::ButtonType::Global);
     65 			break;
     66 		case BootMode::WaitForSystemDump:
     67 			setButton(Buttons::ButtonType::Shift);
     68 			setButton(Buttons::ButtonType::Global);
     69 			break;
     70 		case BootMode::DspClockResetAndServiceMode:
     71 			setButton(Buttons::ButtonType::Multi);
     72 			break;
     73 		case BootMode::ServiceMode:
     74 			setButton(Buttons::ButtonType::Global);
     75 			break;
     76 		case BootMode::MemoryGame:
     77 			setButton(Buttons::ButtonType::Global);
     78 			setButton(Buttons::ButtonType::Play);
     79 			break;
     80 		}
     81 	}
     82 
     83 	void Hardware::resetMidiCounter()
     84 	{
     85 		// wait for DSP to enter blocking state
     86 
     87 		const auto& esai = m_dsps.front().getPeriph().getEsai();
     88 
     89 		auto& inputs = esai.getAudioInputs();
     90 		auto& outputs = esai.getAudioOutputs();
     91 
     92 		while(inputs.size() > 2 && !outputs.full())
     93 			std::this_thread::yield();
     94 
     95 		m_midiOffsetCounter = 0;
     96 	}
     97 
     98 	void Hardware::hdiProcessUCtoDSPNMIIrq()
     99 	{
    100 		// QS6 is connected to DSP NMI pin but I've never seen this being triggered
    101 #if SUPPORT_NMI_INTERRUPT
    102 		const uint8_t requestNMI = m_uc.requestDSPinjectNMI();
    103 
    104 		if(m_requestNMI && !requestNMI)
    105 		{
    106 //			LOG("uc request DSP NMI");
    107 			m_dsps.front().hdiSendIrqToDSP(dsp56k::Vba_NMI);
    108 
    109 			m_requestNMI = requestNMI;
    110 		}
    111 #endif
    112 	}
    113 
    114 	void Hardware::initVoiceExpansion()
    115 	{
    116 		if (m_dsps.size() < 3)
    117 		{
    118 			setupEsaiListener();
    119 			return;
    120 		}
    121 		/*
    122 		m_dsps[1].getPeriph().getPortC().hostWrite(0x10);	// set bit 4 of GPIO Port C, vexp DSPs are waiting for this
    123 		m_dsps[2].getPeriph().getPortC().hostWrite(0x10);	// set bit 4 of GPIO Port C, vexp DSPs are waiting for this
    124 
    125 		bool done = false;
    126 
    127 		auto& esaiA = m_dsps[0].getPeriph().getEsai();
    128 		auto& esaiB = m_dsps[1].getPeriph().getEsai();
    129 		auto& esaiC = m_dsps[2].getPeriph().getEsai();
    130 
    131 		esaiA.setCallback([&](dsp56k::Audio*)
    132 		{
    133 		}, 0);
    134 
    135 		esaiB.setCallback([&](dsp56k::Audio*)
    136 		{
    137 		}, 0);
    138 
    139 		esaiC.setCallback([&](dsp56k::Audio*)
    140 		{
    141 		}, 0);
    142 
    143 		while (!m_dsps.front().receivedMagicEsaiPacket())
    144 		{
    145 			// vexp1 only needs the audio input
    146 			esaiB.getAudioInputs().push_back({0});
    147 
    148 			// transfer output from vexp1 to vexp2
    149 			auto out = esaiB.getAudioOutputs().pop_front();
    150 			std::array<dsp56k::TWord, 4> in{ out[0], out[1], out[2], 0};
    151 			esaiC.getAudioInputs().push_back(in);
    152 
    153 			// read output of vexp2 and send to main
    154 			out = esaiC.getAudioOutputs().pop_front();
    155 
    156 			// this should consist of RX0 = audio input, RX1/2 = vexp2 output TX1/TX2
    157 			in = {0, out[1], out[2]};
    158 			esaiA.getAudioInputs().push_back(in);
    159 
    160 			// final output 0,1,2 = audio outs
    161 			out = esaiA.getAudioOutputs().pop_front();
    162 		}
    163 		LOG("Voice Expansion initialization completed");
    164 		setupEsaiListener();
    165 		*/
    166 	}
    167 
    168 	void Hardware::setupEsaiListener()
    169 	{
    170 		auto& esaiA = m_dsps.front().getPeriph().getEsai();
    171 
    172 		esaiA.setCallback([&](dsp56k::Audio*)
    173 		{
    174 			onEsaiCallback(esaiA);
    175 		}, 0);
    176 	}
    177 
    178 	void Hardware::processUcCycle()
    179 	{
    180 		syncUcToDSP();
    181 
    182 		const auto deltaCycles = m_uc.exec();
    183 		if(m_esaiFrameIndex > 0)
    184 			m_remainingUcCycles -= static_cast<int64_t>(deltaCycles);
    185 
    186 		for (auto& dsp : m_dsps)
    187 			dsp.transferHostFlagsUc2Dsdp();
    188 
    189 		hdiProcessUCtoDSPNMIIrq();
    190 
    191 		for (auto& dsp : m_dsps)
    192 			dsp.hdiTransferDSPtoUC();
    193 
    194 		if(m_uc.requestDSPReset())
    195 		{
    196 			for (auto& dsp : m_dsps)
    197 			{
    198 				if(dsp.haveSentTXToDSP())
    199 				{
    200 					m_uc.dumpMemory("DSPreset");
    201 					assert(false && "DSP needs reset even though it got data already. Needs impl");
    202 				}
    203 			}
    204 			m_uc.notifyDSPBooted();
    205 		}
    206 	}
    207 
    208 	void Hardware::setGlobalDefaultParameters()
    209 	{
    210 		m_midi.write({0xf0,0x3e,0x10,0x7f,0x24,0x00,0x07,0x02,0xf7});	// Control Send = SysEx
    211 		m_midi.write({0xf0,0x3e,0x10,0x7f,0x24,0x00,0x08,0x01,0xf7});	// Control Receive = on
    212 		m_bootCompleted = true;
    213 	}
    214 
    215 	void Hardware::processAudio(uint32_t _frames, uint32_t _latency)
    216 	{
    217 		ensureBufferSize(_frames);
    218 
    219 		if(m_esaiFrameIndex == 0)
    220 			return;
    221 
    222 		m_midi.process(_frames);
    223 
    224 		m_processAudio = true;
    225 
    226 		auto& esai = m_dsps.front().getPeriph().getEsai();
    227 
    228 		const dsp56k::TWord* inputs[16]{nullptr};
    229 		dsp56k::TWord* outputs[16]{nullptr};
    230 
    231 		// TODO: Right audio input channel needs to be delayed by one frame
    232 
    233 		inputs[0] = &m_audioInputs[0].front();
    234 		inputs[1] = &m_audioInputs[1].front();
    235 		inputs[2] = m_dummyInput.data();
    236 		inputs[3] = m_dummyInput.data();
    237 		inputs[4] = m_dummyInput.data();
    238 		inputs[5] = m_dummyInput.data();
    239 		inputs[6] = m_dummyInput.data();
    240 		inputs[7] = m_dummyInput.data();
    241 
    242 		outputs[1] = &m_audioOutputs[0].front();
    243 		outputs[0] = &m_audioOutputs[1].front();
    244 		outputs[3] = &m_audioOutputs[2].front();
    245 		outputs[2] = &m_audioOutputs[3].front();
    246 		outputs[5] = &m_audioOutputs[4].front();
    247 		outputs[4] = &m_audioOutputs[5].front();
    248 		outputs[6] = m_dummyOutput.data();
    249 		outputs[7] = m_dummyOutput.data();
    250 		outputs[8] = m_dummyOutput.data();
    251 		outputs[9] = m_dummyOutput.data();
    252 		outputs[10] = m_dummyOutput.data();
    253 		outputs[11] = m_dummyOutput.data();
    254 
    255 		const auto totalFrames = _frames;
    256 
    257 		while (_frames)
    258 		{
    259 			const auto processCount = std::min(_frames, static_cast<uint32_t>(1024));
    260 			_frames -= processCount;
    261 
    262 			if constexpr (g_useVoiceExpansion)
    263 			{
    264 				auto& esaiA = m_dsps[0].getPeriph().getEsai();
    265 				auto& esaiB = m_dsps[1].getPeriph().getEsai();
    266 				auto& esaiC = m_dsps[2].getPeriph().getEsai();
    267 				/*
    268 				const auto tccrA = esaiA.getTccrAsString();	const auto rccrA = esaiA.getRccrAsString();
    269 				const auto tcrA = esaiA.getTcrAsString();	const auto rcrA = esaiA.getRcrAsString();
    270 
    271 				const auto tccrB = esaiB.getTccrAsString();	const auto rccrB = esaiB.getRccrAsString();
    272 				const auto tcrB = esaiB.getTcrAsString();	const auto rcrB = esaiB.getRcrAsString();
    273 
    274 				const auto tccrC = esaiC.getTccrAsString();	const auto rccrC = esaiC.getRccrAsString();
    275 				const auto tcrC = esaiC.getTcrAsString();	const auto rcrC = esaiC.getRcrAsString();
    276 
    277 				LOG("ESAI DSPmain:\n" << tccrA << '\n' << tcrA << '\n' << rccrA << '\n' << rcrA << '\n');
    278 				LOG("ESAI VexpA:\n"   << tccrB << '\n' << tcrB << '\n' << rccrB << '\n' << rcrB << '\n');
    279 				LOG("ESAI VexpB:\n"   << tccrC << '\n' << tcrC << '\n' << rccrC << '\n' << rcrC << '\n');
    280 				*/
    281 				// vexp1 only needs the audio input
    282 				esaiB.processAudioInputInterleaved(inputs, processCount);
    283 
    284 				// transfer output from vexp1 to vexp2
    285 				esaiB.processAudioOutputInterleaved(outputs, processCount);
    286 
    287 				const dsp56k::TWord* in[] = { outputs[0], outputs[1], outputs[2], outputs[3], outputs[4], outputs[5], nullptr, nullptr };
    288 				esaiC.processAudioInputInterleaved(in, processCount);
    289 
    290 				// read output of vexp2 and send to main
    291 				esaiC.processAudioOutputInterleaved(outputs, processCount);
    292 
    293 				// RX1/2 = vexp2 output TX1/TX2
    294 				const dsp56k::TWord* inA[] = { inputs[1], inputs[0], outputs[2], outputs[3], outputs[4], outputs[5], nullptr, nullptr };
    295 				esaiA.processAudioInputInterleaved(inA, processCount);
    296 
    297 				// final output 0,1,2 = audio outs below
    298 			}
    299 			else
    300 			{
    301 				esai.processAudioInputInterleaved(inputs, processCount, _latency);
    302 			}
    303 
    304 			const auto requiredSize = processCount > 8 ? processCount - 8 : 0;
    305 
    306 			if(esai.getAudioOutputs().size() < requiredSize)
    307 			{
    308 				// 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
    309 
    310 				std::unique_lock uLock(m_requestedFramesAvailableMutex);
    311 				m_requestedFrames = requiredSize;
    312 				m_requestedFramesAvailableCv.wait(uLock, [&]()
    313 				{
    314 					if(esai.getAudioOutputs().size() < requiredSize)
    315 						return false;
    316 					m_requestedFrames = 0;
    317 					return true;
    318 				});
    319 			}
    320 
    321 			esai.processAudioOutputInterleaved(outputs, processCount);
    322 
    323 			if constexpr (g_useVoiceExpansion)
    324 			{
    325 				for (uint32_t i = 1; i < 3; ++i)
    326 				{
    327 					auto& e = m_dsps[i].getPeriph().getEsai();
    328 
    329 					dsp56k::TWord* outs[16]{ nullptr };
    330 					if (e.getAudioOutputs().size() >= 512)
    331 						e.processAudioOutputInterleaved(outs, static_cast<uint32_t>(e.getAudioOutputs().size() >> 1));
    332 				}
    333 			}
    334 
    335 			inputs[0] += processCount;
    336 			inputs[1] += processCount;
    337 
    338 			outputs[0] += processCount;
    339 			outputs[1] += processCount;
    340 			outputs[2] += processCount;
    341 			outputs[3] += processCount;
    342 			outputs[4] += processCount;
    343 			outputs[5] += processCount;
    344 		}
    345 
    346 		m_processAudio = false;
    347 	}
    348 
    349 	void Hardware::ensureBufferSize(uint32_t _frames)
    350 	{
    351 		if(m_audioInputs.front().size() < _frames)
    352 		{
    353 			for (auto& input : m_audioInputs)
    354 				input.resize(_frames);
    355 		}
    356 
    357 		if(m_audioOutputs.front().size() < _frames)
    358 		{
    359 			for (auto& output : m_audioOutputs)
    360 				output.resize(_frames);
    361 		}
    362 
    363 		if(m_dummyInput.size() < _frames)
    364 			m_dummyInput.resize(_frames);
    365 		if(m_dummyOutput.size() < _frames)
    366 			m_dummyOutput.resize(_frames);
    367 	}
    368 }