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

device.cpp (16128B)


      1 #include "device.h"
      2 
      3 #include "dspMultiTI.h"
      4 #include "dspSingleSnow.h"
      5 #include "romfile.h"
      6 
      7 #include "dsp56kEmu/jit.h"
      8 
      9 #include "synthLib/deviceException.h"
     10 #include "synthLib/midiToSysex.h"
     11 
     12 #include <cstring>
     13 
     14 #include "dspMemoryPatches.h"
     15 
     16 namespace virusLib
     17 {
     18 	Device::Device(const synthLib::DeviceCreateParams& _params, const bool _createDebugger/* = false*/)
     19 		: synthLib::Device(_params)
     20 		, m_rom(_params.romData, _params.romName, static_cast<DeviceModel>(_params.customData))
     21 		, m_samplerate(getDeviceSamplerate(_params.preferredSamplerate, _params.hostSamplerate))
     22 	{
     23 		m_frontpanelStateMidiEvent.source = synthLib::MidiEventSource::Internal;
     24 
     25 		DspSingle* dsp1;
     26 		createDspInstances(dsp1, m_dsp2, m_rom, m_samplerate);
     27 		m_dsp.reset(dsp1);
     28 
     29 		m_dsp->getAudio().setCallback([this](dsp56k::Audio*)
     30 		{
     31 			onAudioWritten();
     32 		}, 0);
     33 
     34 		m_mc.reset(new Microcontroller(*m_dsp, m_rom, false));
     35 
     36 		if(m_dsp2)
     37 			m_mc->addDSP(*m_dsp2, true);
     38 
     39 		bootDSPs(m_dsp.get(), m_dsp2, m_rom, _createDebugger);
     40 
     41 //		m_dsp->getMemory().saveAssembly("P.asm", 0, m_dsp->getMemory().sizeP(), true, false, m_dsp->getDSP().getPeriph(0), m_dsp->getDSP().getPeriph(1));
     42 
     43 		switch(m_rom.getModel())
     44 		{
     45 		case DeviceModel::A:
     46 			// The A does not send any event to notify that it has finished booting
     47 			dummyProcess(32);
     48 			m_dsp->disableESSI1();
     49 			break;
     50 		case DeviceModel::B:
     51 			// Rack Classic doesn't send that it has finished booting either, wait a bit for the event but abort if it takes too long
     52 			{
     53 				constexpr auto maxRetries = 256;
     54 				uint32_t r=0;
     55 				while(!m_mc->dspHasBooted() && ++r <= maxRetries)
     56 					dummyProcess(8);
     57 				if(r >= maxRetries)
     58 					LOG("Timed out while waiting for the device to finish booting, expecting that it has booted");
     59 			}
     60 			break;
     61 		default:
     62 			while(!m_mc->dspHasBooted())
     63 				dummyProcess(8);
     64 		}
     65 
     66 		m_mc->sendInitControlCommands();
     67 
     68 		dummyProcess(8);
     69 
     70 		m_mc->createDefaultState();
     71 	}
     72 
     73 	Device::~Device()
     74 	{
     75 		m_dsp->getAudio().setCallback(nullptr,0);
     76 		m_mc.reset();
     77 		m_dsp.reset();
     78 	}
     79 
     80 	void Device::getSupportedSamplerates(std::vector<float>& _dst) const
     81 	{
     82 		switch (m_rom.getModel())
     83 		{
     84 		default:
     85 		case DeviceModel::A:
     86 		case DeviceModel::B:
     87 		case DeviceModel::C:
     88 			_dst.push_back(12000000.0f / 256.0f);
     89 			break;
     90 		case DeviceModel::Snow:
     91 		case DeviceModel::TI:
     92 		case DeviceModel::TI2:
     93 			_dst.push_back(32000.0f);
     94 			_dst.push_back(44100.0f);
     95 			_dst.push_back(48000.0f);
     96 			_dst.push_back(64000.0f);
     97 			_dst.push_back(88200.0f);
     98 			_dst.push_back(96000.0f);
     99 			break;
    100 		}
    101 	}
    102 
    103 	void Device::getPreferredSamplerates(std::vector<float>& _dst) const
    104 	{
    105 		switch (m_rom.getModel())
    106 		{
    107 		default:
    108 		case DeviceModel::ABC:
    109 			getSupportedSamplerates(_dst);
    110 			break;
    111 		case DeviceModel::Snow:
    112 		case DeviceModel::TI:
    113 		case DeviceModel::TI2:
    114 			_dst.push_back(44100.0f);
    115 			_dst.push_back(48000.0f);
    116 			break;
    117 		}
    118 	}
    119 
    120 	float Device::getSamplerate() const
    121 	{
    122 		return m_samplerate;
    123 	}
    124 
    125 	bool Device::setSamplerate(const float _samplerate)
    126 	{
    127 		if(!synthLib::Device::setSamplerate(_samplerate))
    128 			return false;
    129 		m_samplerate = _samplerate;
    130 		configureDSP(*m_dsp, m_rom, m_samplerate);
    131 		if(m_dsp2)
    132 			configureDSP(*m_dsp2, m_rom, m_samplerate);
    133 		m_mc->setSamplerate(_samplerate);
    134 		return true;
    135 	}
    136 
    137 	bool Device::isValid() const
    138 	{
    139 		return m_rom.isValid();
    140 	}
    141 
    142 	void Device::process(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _size, const std::vector<synthLib::SMidiEvent>& _midiIn, std::vector<synthLib::SMidiEvent>& _midiOut)
    143 	{
    144 		m_frontpanelStateDSP.clear();
    145 
    146 		synthLib::Device::process(_inputs, _outputs, _size, _midiIn, _midiOut);
    147 
    148 		if(m_rom.isTIFamily())
    149 		{
    150 			// Apparently this LED model did not want a completely closed PWM
    151 			constexpr auto minimumValue = 0.785339653f;
    152 			constexpr auto maximumValue = 0.999146f;
    153 
    154 			if(m_rom.getModel() == DeviceModel::Snow)
    155 			{
    156 				m_frontpanelStateDSP.m_lfoPhases[0] = 1.f;
    157 				m_frontpanelStateDSP.m_lfoPhases[1] = 1.f;
    158 				m_frontpanelStateDSP.m_lfoPhases[2] = 1.f;
    159 				m_frontpanelStateDSP.m_logo = 1.f;
    160 
    161 				FrontpanelState::updatePhaseFromTimer(m_frontpanelStateDSP.m_bpm, m_dsp->getDSP(), 1, 0.83f, 1.0f);
    162 			}
    163 			else
    164 			{
    165 				m_frontpanelStateDSP.updateLfoPhaseFromTimer(m_dsp->getDSP(), 0, 1, minimumValue, maximumValue);
    166 				m_frontpanelStateDSP.updateLfoPhaseFromTimer(m_dsp->getDSP(), 1, 2, minimumValue, maximumValue);
    167 				m_frontpanelStateDSP.updateLfoPhaseFromTimer(m_dsp->getDSP(), 2, 0, minimumValue, maximumValue);
    168 
    169 				FrontpanelState::updatePhaseFromTimer(m_frontpanelStateDSP.m_logo, m_dsp2->getDSP(), 0, 0.963654f, 1);
    170 			}
    171 		}
    172 		else
    173 		{
    174 			m_frontpanelStateDSP.updateLfoPhaseFromTimer(m_dsp->getDSP(), 0, 2);	// TIMER 1 = ACI = LFO 1 LED
    175 			m_frontpanelStateDSP.updateLfoPhaseFromTimer(m_dsp->getDSP(), 1, 1);	// TIMER 2 = ADO = LFO 2/3 LED
    176 		}
    177 
    178 		m_numSamplesProcessed += static_cast<uint32_t>(_size);
    179 
    180 		m_frontpanelStateDSP.toMidiEvent(m_frontpanelStateMidiEvent);
    181 		_midiOut.push_back(m_frontpanelStateMidiEvent);
    182 	}
    183 
    184 #if !SYNTHLIB_DEMO_MODE
    185 	bool Device::getState(std::vector<uint8_t>& _state, const synthLib::StateType _type)
    186 	{
    187 		return m_mc->getState(_state, _type);
    188 	}
    189 
    190 	bool Device::setState(const std::vector<uint8_t>& _state, synthLib::StateType _type)
    191 	{
    192 		return m_mc->setState(_state, _type);
    193 	}
    194 
    195 	bool Device::setStateFromUnknownCustomData(const std::vector<uint8_t>& _state)
    196 	{
    197 		std::vector<synthLib::SMidiEvent> messages;
    198 
    199 		if(parseTIcontrolPreset(messages, _state))
    200 			return m_mc->setState(messages);
    201 
    202 		std::vector<std::vector<uint8_t>> sysexMessages;
    203 		synthLib::MidiToSysex::splitMultipleSysex(sysexMessages, _state, false);
    204 
    205 		if(sysexMessages.empty())
    206 			return false;
    207 
    208 		for (const auto& sysexMessage : sysexMessages)
    209 			messages.emplace_back().sysex = sysexMessage;
    210 
    211 		return m_mc->setState(messages);
    212 	}
    213 #endif
    214 
    215 	bool Device::find4CC(uint32_t& _offset, const std::vector<uint8_t>& _data, const std::string_view& _4cc)
    216 	{
    217 		if(_data.size() < (_offset + _4cc.size()))
    218 			return false;
    219 
    220 		for(uint32_t i=_offset; i<_data.size() - _4cc.size(); ++i)
    221 		{
    222 			bool valid = true;
    223 			for(size_t j=0; j<_4cc.size(); ++j)
    224 			{
    225 				if(static_cast<char>(_data[i + j]) == _4cc[j])
    226 					continue;
    227 				valid = false;
    228 				break;
    229 			}
    230 			if(valid)
    231 			{
    232 				_offset = i;
    233 				return true;
    234 			}
    235 		}
    236 		return false;
    237 	}
    238 
    239 	bool Device::parseTIcontrolPreset(std::vector<synthLib::SMidiEvent>& _events, const std::vector<uint8_t>& _state)
    240 	{
    241 		if(_state.size() < 8)
    242 			return false;
    243 
    244 		uint32_t readPos = 0;
    245 
    246 		uint32_t numFound = 0;
    247 
    248 		while(readPos < _state.size() - 4)
    249 		{
    250 			if(!find4CC(readPos, _state, "MIDI"))
    251 				break;
    252 
    253 			if(readPos >= _state.size())
    254 				break;
    255 
    256 			auto readLen = [&_state](const size_t _offset) -> uint32_t
    257 			{
    258 				if(_offset + 4 > _state.size())
    259 					return 0;
    260 				const uint32_t o =
    261 					(static_cast<uint32_t>(_state[_offset+0]) << 24) | 
    262 					(static_cast<uint32_t>(_state[_offset+1]) << 16) |
    263 					(static_cast<uint32_t>(_state[_offset+2]) << 8) |
    264 					(static_cast<uint32_t>(_state[_offset+3]));
    265 				return o;
    266 			};
    267 
    268 			auto nextLen = [&readPos, &readLen]() -> uint32_t
    269 			{
    270 				const auto len = readLen(readPos);
    271 				readPos += 4;
    272 				return len;
    273 			};
    274 
    275 			const auto dataLen = nextLen();
    276 
    277 			if(dataLen + readPos > _state.size())
    278 				break;
    279 
    280 			const auto controllerAssignmentsLen = nextLen();
    281 
    282 			readPos += controllerAssignmentsLen;
    283 			
    284 			while(readPos < _state.size())
    285 			{
    286 				const auto midiDataLen = nextLen();
    287 
    288 				if(!midiDataLen)
    289 					break;
    290 
    291 				if((readPos + midiDataLen) > _state.size())
    292 					break;
    293 
    294 				synthLib::SMidiEvent& e = _events.emplace_back();
    295 
    296 				e.sysex.assign(_state.begin() + readPos, _state.begin() + readPos + midiDataLen);
    297 
    298 				if(e.sysex.front() != 0xf0)
    299 				{
    300 					assert(e.sysex.size() <= 3);
    301 					e.a = e.sysex[0];
    302 					if(e.sysex.size() > 1)
    303 						e.b = e.sysex[1];
    304 					if(e.sysex.size() > 2)
    305 						e.c = e.sysex[2];
    306 
    307 					e.sysex.clear();
    308 				}
    309 
    310 				readPos += midiDataLen;
    311 
    312 				if(!e.sysex.empty())
    313 					++numFound;
    314 			}			
    315 		}
    316 
    317 		return numFound > 0;
    318 	}
    319 
    320 	bool Device::parsePowercorePreset(std::vector<std::vector<uint8_t>>& _sysexPresets, const std::vector<uint8_t>& _data)
    321 	{
    322 		uint32_t off = 0;
    323 
    324 		uint32_t numFound = 0;
    325 
    326 		while(off < _data.size() - 4)
    327 		{
    328 			// VST2 fxp/fxb chunk must exist
    329 			if(!find4CC(off, _data, "CcnK"))
    330 				break;
    331 
    332 			off += 4;
    333 
    334 			uint32_t pos;
    335 
    336 			// fxp or fxb?
    337 			if(find4CC(off, _data, "FPCh"))
    338 				pos = off + 0x34;					// fxp
    339 			else if(find4CC(off, _data, "FBCh"))
    340 				pos = off + 0x98;					// fxb
    341 			else
    342 				continue;
    343 
    344 			if(pos >= _data.size())
    345 				break;
    346 
    347 			++pos;	// skip first byte, version?
    348 
    349 			constexpr uint32_t presetSize = 256;			// presets seem to be stored without sysex packaging
    350 			constexpr uint32_t padding = 5;					// five unknown bytes betweeen two presets
    351 
    352 			uint8_t programIndex = 0;
    353 
    354 			while((pos + presetSize) <= static_cast<uint32_t>(_data.size()))
    355 			{
    356 				Microcontroller::TPreset p;
    357 				memcpy(&p.front(), &_data[pos], presetSize);
    358 
    359 				const auto version = Microcontroller::getPresetVersion(p);
    360 				if(version != C)
    361 					break;
    362 				const auto name = ROMFile::getSingleName(p);
    363 				if(name.size() != 10)
    364 					break;
    365 
    366 				// pack into sysex
    367 				std::vector<uint8_t>& sysex = _sysexPresets.emplace_back(std::vector<uint8_t>{0xf0, 0x00, 0x20, 0x33, 0x01, OMNI_DEVICE_ID, 0x10, 0x01, programIndex});
    368 				sysex.insert(sysex.end(), _data.begin() + pos, _data.begin() + pos + presetSize);
    369 				sysex.push_back(Microcontroller::calcChecksum(sysex));
    370 				sysex.push_back(0xf7);
    371 
    372 				++numFound;
    373 
    374 				++programIndex;
    375 				pos += presetSize;
    376 				pos += padding;
    377 			}
    378 			off = pos;
    379 		}
    380 
    381 		return numFound > 0;
    382 	}
    383 
    384 	bool Device::parseVTIBackup(std::vector<std::vector<uint8_t>>& _sysexPresets, const std::vector<uint8_t>& _data)
    385 	{
    386 		if(_data.size() < 512)
    387 			return false;
    388 
    389 		// first 11 bytes are the serial number. Check if they're all ASCII
    390 		for(size_t i=0; i<11; ++i)
    391 		{
    392 			if(_data[i] < 32 || _data[i] > 127)
    393 				return false;
    394 		}
    395 
    396 		constexpr size_t presetSize = sizeof(Microcontroller::TPreset);
    397 		Microcontroller::TPreset preset;
    398 
    399 		constexpr uint32_t maxPresets = (4 + 26) * 128;	// 4x RAM banks, 26x ROM banks, 128 patches per bank
    400 
    401 		uint32_t presetIdx = 0;
    402 
    403 		// presets start at $20
    404 		// They are "raw" presets, i.e. 512 bytes of preset data each
    405 		// The sysex packaging is missing, i.e. the single dump header, the checksums and the sysex terminator
    406 		for(size_t i=0x20; i<_data.size() - presetSize; i += presetSize)
    407 		{
    408 			memcpy(preset.data(), &_data[i], presetSize);
    409 
    410 			const auto name = ROMFile::getSingleName(preset);
    411 
    412 			if(name.empty())
    413 				break;
    414 
    415 			auto& sysex = _sysexPresets.emplace_back(std::vector<uint8_t>{
    416 				0xf0, 0x00, 0x20, 0x33, 0x01, OMNI_DEVICE_ID, DUMP_SINGLE,
    417 				static_cast<uint8_t>((presetIdx >> 7) & 0x7f),
    418 				static_cast<uint8_t>(presetIdx & 0x7f)});
    419 
    420 			sysex.reserve(9 + 256 + 1 + 256 + 2);	// header, 256 preset bytes, 1st checksum, 256 preset bytes, 2nd checksum, EOX
    421 
    422 			for(size_t j=0; j<256; ++j)
    423 				sysex.push_back(preset[j]);
    424 			sysex.push_back(Microcontroller::calcChecksum(sysex));
    425 			for(size_t j=256; j<512; ++j)
    426 				sysex.push_back(preset[j]);
    427 			sysex.push_back(Microcontroller::calcChecksum(sysex));
    428 			sysex.push_back(0xf7);
    429 
    430 			++presetIdx;
    431 
    432 			if(presetIdx == maxPresets)
    433 				break;
    434 		}
    435 
    436 		return true;
    437 	}
    438 
    439 	uint32_t Device::getInternalLatencyMidiToOutput() const
    440 	{
    441 		// Note that this is an average value, midi latency drifts in a range of roughly +/- 61 samples
    442 		constexpr auto latency = 324;
    443 
    444 		if(m_rom.isTIFamily())
    445 			return latency - 108;	// TI seems to have improved a bit
    446 
    447 		return latency;	
    448 	}
    449 
    450 	uint32_t Device::getInternalLatencyInputToOutput() const
    451 	{
    452 		// Measured by using an input init patch. Sent a click to the input and recorded both the input
    453 		// as direct signal plus the Virus output and checking the resulting latency in a wave editor
    454 		return 384;
    455 	}
    456 
    457 	uint32_t Device::getChannelCountIn()
    458 	{
    459 		return 2;
    460 	}
    461 
    462 	uint32_t Device::getChannelCountOut()
    463 	{
    464 		return m_rom.isTIFamily() ? 12 : 6;
    465 	}
    466 
    467 	void Device::createDspInstances(DspSingle*& _dspA, DspSingle*& _dspB, const ROMFile& _rom, const float _samplerate)
    468 	{
    469 		if(_rom.getModel() == DeviceModel::Snow)
    470 		{
    471 			_dspA = new DspSingleSnow();
    472 		}
    473 		else if(_rom.getModel() == DeviceModel::TI || _rom.getModel() == DeviceModel::TI2)
    474 		{
    475 			auto* dsp = new DspMultiTI();
    476 			_dspA = dsp;
    477 			_dspB = &dsp->getDSP2();
    478 		}
    479 		else
    480 		{
    481 			_dspA = new DspSingle(_rom.isTIFamily() ? 0x100000 : 0x040000, _rom.isTIFamily(), nullptr, _rom.getModel() == DeviceModel::A);
    482 		}
    483 
    484 		configureDSP(*_dspA, _rom, _samplerate);
    485 
    486 		if(_dspB)
    487 			configureDSP(*_dspB, _rom, _samplerate);
    488 	}
    489 
    490 	bool Device::sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response)
    491 	{
    492 		if(_ev.sysex.empty())
    493 		{
    494 //			LOG("MIDI: " << std::hex << (int)_ev.a << " " << (int)_ev.b << " " << (int)_ev.c);
    495 			auto ev = _ev;
    496 			ev.offset += m_numSamplesProcessed + getExtraLatencySamples();
    497 			return m_mc->sendMIDI(ev, &m_frontpanelStateDSP);
    498 		}
    499 
    500 		std::vector<synthLib::SMidiEvent> responses;
    501 
    502 		if(!m_mc->sendSysex(_ev.sysex, responses, _ev.source))
    503 			return false;
    504 
    505 		for (const auto& response : responses)
    506 			_response.emplace_back(response);
    507 
    508 		return true;
    509 	}
    510 
    511 	void Device::readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut)
    512 	{
    513 		m_mc->readMidiOut(_midiOut);
    514 	}
    515 
    516 	void Device::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples)
    517 	{
    518 		constexpr auto maxBlockSize = dsp56k::Audio::RingBufferSize>>2;
    519 
    520 		auto inputs(_inputs);
    521 		auto outputs(_outputs);
    522 
    523 		while(_samples > maxBlockSize)
    524 		{
    525 			m_dsp->processAudio(inputs, outputs, maxBlockSize, getExtraLatencySamples());
    526 
    527 			_samples -= maxBlockSize;
    528 
    529 			for (auto& input : inputs)
    530 			{
    531 				if(input)
    532 					input += maxBlockSize;
    533 			}
    534 
    535 			for (auto& output : outputs)
    536 			{
    537 				if(output)
    538 					output += maxBlockSize;
    539 			}
    540 		}
    541 
    542 		m_dsp->processAudio(inputs, outputs, _samples, getExtraLatencySamples());
    543 	}
    544 
    545 	void Device::onAudioWritten()
    546 	{
    547 		m_mc->getMidiQueue(0).onAudioWritten();
    548 		m_mc->process();
    549 	}
    550 
    551 	void Device::configureDSP(DspSingle& _dsp, const ROMFile& _rom, const float _samplerate)
    552 	{
    553 		auto& jit = _dsp.getJIT();
    554 		auto conf = jit.getConfig();
    555 
    556 		if(_rom.isTIFamily())
    557 		{
    558 			auto& clock = _dsp.getPeriphX().getEsaiClock();
    559 
    560 			const auto sr = static_cast<int>(_samplerate);
    561 
    562 			clock.setExternalClockFrequency(std::min(sr, 48000) * 256);
    563 
    564 			if(_rom.getModel() != DeviceModel::Snow)
    565 			{
    566 				clock.setSamplerate(sr * 3);
    567 				clock.setEsaiDivider(&_dsp.getPeriphY().getEsai(), 0);
    568 				clock.setEsaiDivider(&_dsp.getPeriphX().getEsai(), 2);
    569 			}
    570 
    571 			conf.aguSupportBitreverse = true;
    572 			conf.maxDoIterations = 32;
    573 
    574 			clock.setClockSource(dsp56k::EsaiClock::ClockSource::Cycles);
    575 		}
    576 		else
    577 		{
    578 			conf.aguSupportBitreverse = false;
    579 		}
    580 
    581 		jit.setConfig(conf);
    582 	}
    583 
    584 	std::thread Device::bootDSP(DspSingle& _dsp, const ROMFile& _rom, const bool _createDebugger)
    585 	{
    586 		auto res = _rom.bootDSP(_dsp);
    587 		_dsp.startDSPThread(_createDebugger);
    588 		return res;
    589 	}
    590 
    591 	void Device::bootDSPs(DspSingle* _dspA, DspSingle* _dspB, const ROMFile& _rom, bool _createDebugger)
    592 	{
    593 		auto loader = bootDSP(*_dspA, _rom, _createDebugger);
    594 
    595 		if(_dspB)
    596 		{
    597 			auto loader2 = bootDSP(*_dspB, _rom, false);
    598 			loader2.join();
    599 		}
    600 
    601 		loader.join();
    602 
    603 //		applyDspMemoryPatches(_dspA, _dspB, _rom);
    604 	}
    605 
    606 	bool Device::setDspClockPercent(const uint32_t _percent)
    607 	{
    608 		if(!m_dsp)
    609 			return false;
    610 
    611 		bool res = m_dsp->getEsxiClock().setSpeedPercent(_percent);
    612 
    613 		if(m_dsp2)
    614 			res &= m_dsp2->getEsxiClock().setSpeedPercent(_percent);
    615 
    616 		return res;
    617 	}
    618 
    619 	uint32_t Device::getDspClockPercent() const
    620 	{
    621 		return !m_dsp ? 0 : m_dsp->getEsxiClock().getSpeedPercent();
    622 	}
    623 
    624 	uint64_t Device::getDspClockHz() const
    625 	{
    626 		return !m_dsp ? 0 : m_dsp->getEsxiClock().getSpeedInHz();
    627 	}
    628 
    629 	void Device::applyDspMemoryPatches(const DspSingle* _dspA, const DspSingle* _dspB, const ROMFile& _rom)
    630 	{
    631 		DspMemoryPatches::apply(_dspA, _rom.getHash());
    632 		DspMemoryPatches::apply(_dspB, _rom.getHash());
    633 	}
    634 
    635 	void Device::applyDspMemoryPatches() const
    636 	{
    637 		applyDspMemoryPatches(m_dsp.get(), m_dsp2, m_rom);
    638 	}
    639 }