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 }