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

consoleApp.cpp (6879B)


      1 #include "consoleApp.h"
      2 
      3 #include <iostream>
      4 
      5 #include "audioProcessor.h"
      6 #include "esaiListenerToFile.h"
      7 
      8 #include "virusLib/device.h"
      9 #include "virusLib/romloader.h"
     10 #include "virusLib/demoplaybackTI.h"
     11 
     12 #include "dsp56kEmu/dsp.h"
     13 
     14 namespace virusLib
     15 {
     16 	class Device;
     17 }
     18 
     19 using namespace virusLib;
     20 using namespace synthLib;
     21 
     22 class EsaiListener;
     23 
     24 namespace
     25 {
     26 	ROMFile findRom(const std::string& _name, const DeviceModel _tiModel)
     27 	{
     28 		auto result = ROMLoader::findROM(_name, _tiModel);
     29 		if(result.isValid())
     30 			return result;
     31 		return ROMLoader::findROM(_name, DeviceModel::ABC);
     32 	}
     33 }
     34 
     35 ConsoleApp::ConsoleApp(const std::string& _romFile, const DeviceModel _tiModel)
     36 : m_romName(_romFile)
     37 , m_rom(findRom(_romFile, _tiModel))
     38 , m_preset({})
     39 {
     40 	if (!m_rom.isValid())
     41 	{
     42 		std::cout << "ROM file " << _romFile << " is not valid and couldn't be loaded. Place a valid ROM file with .bin extension next to this program." << std::endl;
     43 		return;
     44 	}
     45 
     46 	std::cout << "Using ROM " << m_rom.getFilename() << '\n';
     47 
     48 	virusLib::DspSingle* dsp1 = nullptr;
     49 	virusLib::Device::createDspInstances(dsp1, m_dsp2, m_rom, static_cast<float>(m_rom.getSamplerate()));
     50 	m_dsp1.reset(dsp1);
     51 
     52 	m_uc.reset(new Microcontroller(*m_dsp1, m_rom, false));
     53 	if(m_dsp2)
     54 		m_uc->addDSP(*m_dsp2, false);
     55 }
     56 
     57 ConsoleApp::~ConsoleApp()
     58 {
     59 	destroy();
     60 }
     61 
     62 bool ConsoleApp::isValid() const
     63 {
     64 	return m_rom.isValid();
     65 }
     66 
     67 void ConsoleApp::waitReturn()
     68 {
     69 	std::cin.ignore();
     70 }
     71 
     72 void ConsoleApp::bootDSP(const bool _createDebugger) const
     73 {
     74 	virusLib::Device::bootDSPs(m_dsp1.get(), m_dsp2, m_rom, _createDebugger);
     75 }
     76 
     77 dsp56k::IPeripherals& ConsoleApp::getYPeripherals() const
     78 {
     79 	if (m_rom.isTIFamily())
     80 		return m_dsp1->getPeriphY();
     81 
     82 	return m_dsp1->getPeriphNop();
     83 }
     84 
     85 void ConsoleApp::loadSingle(int b, int p)
     86 {
     87 	if(m_rom.getSingle(b, p, m_preset))
     88 	{
     89 		std::cout << "Loaded Single " << ROMFile::getSingleName(m_preset) << std::endl;
     90 	}
     91 }
     92 
     93 bool ConsoleApp::loadSingle(const std::string& _preset)
     94 {
     95 	auto isDigit = true;
     96 	for (size_t i = 0; i < _preset.size(); ++i)
     97 	{
     98 		if (!isdigit(_preset[i]))
     99 		{
    100 			isDigit = false;
    101 			break;
    102 		}
    103 	}
    104 
    105 	if (isDigit)
    106 	{
    107 		int preset = atoi(_preset.c_str());
    108 		const int bank = preset / m_rom.getPresetsPerBank();
    109 		preset -= bank * m_rom.getPresetsPerBank();
    110 		loadSingle(bank, preset);
    111 		return true;
    112 	}
    113 
    114 	for (uint32_t b = 0; b < 26; ++b)
    115 	{
    116 		for (uint32_t p = 0; p < m_rom.getPresetsPerBank(); ++p)
    117 		{
    118 			Microcontroller::TPreset data;
    119 			m_rom.getSingle(b, p, data);
    120 
    121 			const std::string name = ROMFile::getSingleName(data);
    122 			if (name.empty())
    123 			{
    124 				return false;
    125 			}
    126 			if (name == _preset)
    127 			{
    128 				loadSingle(b, p);
    129 				return true;
    130 			}
    131 		}
    132 	}
    133 	return false;
    134 }
    135 
    136 bool ConsoleApp::loadDemo(const std::string& _filename)
    137 {
    138 	m_demo.reset(m_rom.isTIFamily() ? new DemoPlaybackTI(*m_uc) : new DemoPlayback(*m_uc));
    139 
    140 	if(m_demo->loadFile(_filename))
    141 	{
    142 		std::cout << "Loaded demo song from file " << _filename << std::endl;
    143 		return true;
    144 	}
    145 
    146 	m_demo.reset();
    147 	return false;
    148 }
    149 
    150 bool ConsoleApp::loadInternalDemo()
    151 {
    152 	if(m_rom.getDemoData().empty())
    153 		return false;
    154 
    155 	m_demo.reset(m_rom.isTIFamily() ? new DemoPlaybackTI(*m_uc) : new DemoPlayback(*m_uc));
    156 
    157 	if(m_demo->loadBinData(m_rom.getDemoData()))
    158 	{
    159 		std::cout << "Loaded internal demo from ROM " << m_romName << std::endl;
    160 		return true;
    161 	}
    162 	m_demo.reset();
    163 	return false;
    164 }
    165 
    166 std::string ConsoleApp::getSingleName() const
    167 {
    168 	return ROMFile::getSingleName(m_preset);
    169 }
    170 
    171 std::string ConsoleApp::getSingleNameAsFilename() const
    172 {
    173 	auto audioFilename = m_demo ? "factorydemo" : getSingleName();
    174 
    175 	for (size_t i = 0; i < audioFilename.size(); ++i)
    176 	{
    177 		if (audioFilename[i] == ' ')
    178 			audioFilename[i] = '_';
    179 	}
    180 	return "virusEmu_" + audioFilename + ".wav";
    181 }
    182 
    183 void ConsoleApp::audioCallback(const uint32_t _audioCallbackCount)
    184 {
    185 	m_uc->process();
    186 
    187 	switch (_audioCallbackCount)
    188 	{
    189 	case 1:
    190 		m_dsp1->drainESSI1();
    191 		LOG("Sending Init Control Commands");
    192 		m_uc->sendInitControlCommands(127);	// set Master Volume to max
    193 		break;
    194 	case 256:
    195 		m_dsp1->drainESSI1();
    196 		m_dsp1->disableESSI1();
    197 		if(!m_demo)
    198 		{
    199 			LOG("Sending Preset");
    200 			m_uc->writeSingle(BankNumber::EditBuffer, virusLib::SINGLE, m_preset);
    201 		}
    202 		break;
    203 	case 512:
    204 		if(!m_demo)
    205 		{
    206 			LOG("Sending Note On");
    207 			m_uc->sendMIDI(SMidiEvent(MidiEventSource::Host, 0x90, 60, 0x5f));		// Note On
    208 			m_uc->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max());
    209 		}
    210 		break;
    211 	}
    212 
    213 	if(m_demo && _audioCallbackCount >= 256)
    214 		m_demo->process(1);
    215 }
    216 
    217 void ConsoleApp::destroy()
    218 {
    219 	m_demo.reset();
    220 	m_uc.reset();
    221 	m_dsp1.reset();
    222 	m_dsp2 = nullptr;
    223 }
    224 
    225 void ConsoleApp::run(const std::string& _audioOutputFilename, uint32_t _maxSampleCount/* = 0*/, uint32_t _blockSize/* = 64*/, bool _createDebugger/* = false*/, bool _dumpAssembler/* = false*/)
    226 {
    227 	assert(!_audioOutputFilename.empty());
    228 //	dsp.enableTrace((DSP::TraceMode)(DSP::Ops | DSP::Regs | DSP::StackIndent));
    229 
    230 	const uint32_t blockSize = _blockSize;
    231 	const uint32_t notifyThreshold = blockSize > 4 ? blockSize - 4 : 0;
    232 
    233 	uint32_t callbackCount = 0;
    234 	dsp56k::SpscSemaphore sem(1);
    235 
    236 	auto& esai = m_dsp1->getAudio();
    237 	int32_t notifyTimeout = 0;
    238 
    239 	std::vector<synthLib::SMidiEvent> midiEvents;
    240 
    241 	esai.setCallback([&](dsp56k::Audio*)
    242 	{
    243 		// Reduce thread contention by waiting until we have nearly enough audio output data available.
    244 		// The DSP thread needs to lock & unlock a mutex to inform the waiting thread (us) that data is
    245 		// available if the output ring buffer was completely drained. We can omit this by ensuring that
    246 		// the output buffer never becomes completely empty.
    247 		const auto availableSize = esai.getAudioOutputs().size();
    248 		const auto sizeReached = availableSize >= notifyThreshold;
    249 
    250 		--notifyTimeout;
    251 
    252 //		LOG("Size " << esai.getAudioOutputs().size() << ", size reached " << (sizeReached ? "true" : "false") << ", notify " << (notify ? "true" : "false"));
    253 		if(notifyTimeout <= 0 && sizeReached)
    254 		{
    255 			notifyTimeout = static_cast<int>(notifyThreshold);
    256 			sem.notify();
    257 		}
    258 
    259 		callbackCount++;
    260 		if((callbackCount & 0x3) == 0)
    261 		{
    262 			m_uc->readMidiOut(midiEvents);
    263 			audioCallback(callbackCount>>2);
    264 		}
    265 	}, 0);
    266 
    267 	bootDSP(_createDebugger);
    268 
    269 	if(_dumpAssembler)
    270 	{
    271 		const std::string romFile = m_rom.getFilename();
    272 		auto& mem = m_dsp1->getMemory();
    273 
    274 		mem.saveAsText((romFile + "_X.txt").c_str(), dsp56k::MemArea_X, 0, mem.sizeXY());
    275 		mem.saveAsText((romFile + "_Y.txt").c_str(), dsp56k::MemArea_Y, 0, mem.sizeXY());
    276 		mem.save((romFile + "_P.bin").c_str(), dsp56k::MemArea_P);
    277 		mem.saveAssembly((romFile + "_P.asm").c_str(), 0, mem.sizeP(), true, false, m_dsp1->getDSP().getPeriph(0), m_dsp1->getDSP().getPeriph(1));
    278 	}
    279 
    280 	AudioProcessor proc(m_rom.getSamplerate(), _audioOutputFilename, m_demo != nullptr, _maxSampleCount, m_dsp1.get(), m_dsp2);
    281 
    282 	while(!proc.finished())
    283 	{
    284 		sem.wait();
    285 		proc.processBlock(blockSize);
    286 		midiEvents.clear();
    287 	}
    288 
    289 	m_dsp1.reset();
    290 	destroy();
    291 }