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

commit cd18bb1ea8a9027b37b300b689f48f361662ac56
parent 4806cc3ba68d9e4911249ef50c13f396dee6ecb3
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sun,  4 Dec 2022 19:46:03 +0100

update OSS version

Diffstat:
MCMakeLists.txt | 22++++++----------------
Mbase.cmake | 3+++
Abuild_android.bat | 5+++++
Abuild_android_abi.bat | 11+++++++++++
Abuild_linux_wsl.sh | 3+++
Mbuild_win64.bat | 2+-
Msource/jucePlugin/PluginProcessor.cpp | 29+++++++++++++++++------------
Msource/jucePlugin/PluginProcessor.h | 9+++++++--
Msource/jucePlugin/VirusController.cpp | 5+----
Msource/jucePlugin/VirusController.h | 2--
Msource/jucePlugin/ui3/PatchBrowser.cpp | 25++++++++++++++-----------
Msource/jucePlugin/ui3/PatchBrowser.h | 4++--
Msource/jucePlugin/ui3/VirusEditor.cpp | 17++++++++++++-----
Msource/jucePluginLib/parameterdescriptions.cpp | 8+++++++-
Msource/portaudio/CMakeLists.txt | 6++++--
Msource/portmidi/pm_common/CMakeLists.txt | 4+++-
Msource/portmidi/pm_dylib/CMakeLists.txt | 4+++-
Msource/synthLib/CMakeLists.txt | 5+++++
Asource/synthLib/audioTypes.h | 16++++++++++++++++
Msource/synthLib/audiobuffer.cpp | 14++++++++++++--
Msource/synthLib/audiobuffer.h | 12+++++++++---
Asource/synthLib/configFile.cpp | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/configFile.h | 20++++++++++++++++++++
Msource/synthLib/device.cpp | 57+++++++++------------------------------------------------
Msource/synthLib/device.h | 26+++++++-------------------
Asource/synthLib/midiBufferParser.cpp | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/midiBufferParser.h | 26++++++++++++++++++++++++++
Msource/synthLib/plugin.cpp | 21+++++++++++----------
Msource/synthLib/plugin.h | 2+-
Msource/synthLib/resampler.cpp | 22++++++++++++----------
Msource/synthLib/resampler.h | 10+++++-----
Msource/synthLib/resamplerInOut.cpp | 59++++++++++++++++++++++++++++++-----------------------------
Msource/synthLib/resamplerInOut.h | 11++++++-----
Asource/synthLib/wavReader.cpp | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/wavReader.h | 34++++++++++++++++++++++++++++++++++
Asource/synthLib/wavTypes.h | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/synthLib/wavWriter.cpp | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/synthLib/wavWriter.h | 128++++++++++++++++++++++++++++++-------------------------------------------------
Asource/virusConsoleLib/CMakeLists.txt | 17+++++++++++++++++
Asource/virusConsoleLib/audioProcessor.cpp | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/audioProcessor.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/consoleApp.cpp | 364+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/consoleApp.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/esaiListener.cpp | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/esaiListener.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/esaiListenerToCallback.cpp | 12++++++++++++
Asource/virusConsoleLib/esaiListenerToCallback.h | 15+++++++++++++++
Asource/virusConsoleLib/esaiListenerToFile.cpp | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusConsoleLib/esaiListenerToFile.h | 23+++++++++++++++++++++++
Asource/virusIntegrationTest/CMakeLists.txt | 16++++++++++++++++
Asource/virusIntegrationTest/integrationTest.cpp | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusIntegrationTest/integrationTest.h | 35+++++++++++++++++++++++++++++++++++
Msource/virusLib/CMakeLists.txt | 8+++++++-
Msource/virusLib/demoplayback.cpp | 130++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msource/virusLib/demoplayback.h | 25+++++++++++++++++++------
Msource/virusLib/device.cpp | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msource/virusLib/device.h | 26+++++++++++++++++++-------
Asource/virusLib/dspSingle.cpp | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusLib/dspSingle.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asource/virusLib/hdi08TxParser.cpp | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusLib/hdi08TxParser.h | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/virusLib/microcontroller.cpp | 412+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msource/virusLib/microcontroller.h | 47++++++++++++++++++++++++++++++++---------------
Msource/virusLib/microcontrollerTypes.h | 18+++++++++++-------
Dsource/virusLib/midiOutParser.cpp | 56--------------------------------------------------------
Dsource/virusLib/midiOutParser.h | 18------------------
Msource/virusLib/romfile.cpp | 144+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msource/virusLib/romfile.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Asource/virusLib/utils.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusTestConsole/CMakeLists.txt | 26++++++++++++++++++++++++++
Msource/virusTestConsole/virusTestConsole.cpp | 311+++++++++++--------------------------------------------------------------------
Mtemp/cmake_win64/gearmulator.sln.DotSettings | 4++++
72 files changed, 3359 insertions(+), 910 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -10,12 +10,15 @@ project(gearmulator VERSION 1.2.20) include(base.cmake) set(ASMJIT_STATIC TRUE) +set(ASMJIT_NO_INSTALL TRUE) +set(BUILD_SHARED_LIBS OFF) option(${PROJECT_NAME}_BUILD_JUCEPLUGIN "Build Juce plugin" on) add_subdirectory(source/dsp56300/source) add_subdirectory(source/synthLib) add_subdirectory(source/virusLib) +add_subdirectory(source/virusConsoleLib) add_subdirectory(source/libresample) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/source/vstsdk2.4.2/public.sdk/source/vst2.x/audioeffect.h) @@ -33,23 +36,10 @@ if(${PROJECT_NAME}_BUILD_JUCEPLUGIN) add_subdirectory(source/jucePlugin) endif() -# ----------------- Test Console +# ----------------- Console Programs -add_executable(virusTestConsole) -target_sources(virusTestConsole PRIVATE source/virusTestConsole/virusTestConsole.cpp) -target_link_libraries(virusTestConsole PUBLIC virusLib) - -if(UNIX AND NOT APPLE) - target_link_libraries(virusTestConsole PUBLIC -static-libgcc -static-libstdc++) -endif() - -install(TARGETS virusTestConsole DESTINATION . COMPONENT testConsole) - -if(MSVC) - install(DIRECTORY deploy/win/ DESTINATION . COMPONENT testConsole) -else() - install(DIRECTORY deploy/linux/ DESTINATION . COMPONENT testConsole) -endif() +add_subdirectory(source/virusTestConsole) +add_subdirectory(source/virusIntegrationTest) # ----------------- CPack diff --git a/base.cmake b/base.cmake @@ -18,6 +18,7 @@ if(MSVC) # /Oi Enable Intrinsic Functions # /Ot Favor Fast Code + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /GS- /fp:fast /Oy /GT /GL /Zi /Oi /Ot") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /GS- /fp:fast /Oy /GT /GL /Zi /Oi /Ot") set(ARCHITECTURE ${CMAKE_VS_PLATFORM_NAME}) @@ -52,6 +53,7 @@ elseif(APPLE) "-framework OpenGL" "-framework QuartzCore" ) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -funroll-loops -Ofast -flto") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -funroll-loops -Ofast -flto") else() message("CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) @@ -59,6 +61,7 @@ else() if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES arm AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES aarch64) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse") endif() + set(CMAKE_C_FLAGS_RELEASE "-Ofast") set(CMAKE_CXX_FLAGS_RELEASE "-Ofast") set(CMAKE_CXX_FLAGS "-Ofast") execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE) diff --git a/build_android.bat b/build_android.bat @@ -0,0 +1,5 @@ +REM call build_android_abi.bat armeabi-v7a +REM call build_android_abi.bat "armeabi-v7a with NEON" +call build_android_abi.bat arm64-v8a +REM call build_android_abi.bat x86 +REM call build_android_abi.bat x86_64 diff --git a/build_android_abi.bat b/build_android_abi.bat @@ -0,0 +1,11 @@ +set ABI=%~1 +set OUTDIR=temp\cmake_android_%ABI% + +set ARGS=-DANDROID_ARM_NEON=TRUE -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_MAKE_PROGRAM=%ANDROID_NDK%/prebuilt/windows-x86_64/bin/make.exe -DANDROID_NATIVE_API_LEVEL=21 -B "%OUTDIR%" -G "MinGW Makefiles" -Dgearmulator_BUILD_JUCEPLUGIN=OFF + +cmake %ARGS% "-DANDROID_ABI=%ABI%" + +IF %ERRORLEVEL% NEQ 0 exit /B 1 +pushd %OUTDIR% +cmake --build . --config Release +popd diff --git a/build_linux_wsl.sh b/build_linux_wsl.sh @@ -0,0 +1,3 @@ +cmake . -B ./temp/cmake_linux_wsl -Dgearmulator_BUILD_JUCEPLUGIN=OFF -DCMAKE_BUILD_TYPE=Release +cd ./temp/cmake_linux_wsl +cmake --build . --config Release diff --git a/build_win64.bat b/build_win64.bat @@ -5,7 +5,7 @@ IF %ERRORLEVEL% NEQ 0 ( exit /B 2 ) pushd %outdir% -cmake --build . --config Release +cmake --build . --config Release -j 4 IF %ERRORLEVEL% NEQ 0 ( popd exit /B 2 diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp @@ -11,8 +11,16 @@ AudioPluginAudioProcessor::AudioPluginAudioProcessor() : AudioProcessor(BusesProperties() .withInput("Input", juce::AudioChannelSet::stereo(), true) - .withOutput("Output", juce::AudioChannelSet::stereo(), true)), - MidiInputCallback(), m_romName(synthLib::findROM()), m_device(synthLib::findROM()), m_plugin(&m_device) + .withOutput("Output", juce::AudioChannelSet::stereo(), true) +#if JucePlugin_IsSynth + .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) + .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) +#endif + ), + MidiInputCallback(), + m_romName(virusLib::ROMFile::findROM()), + m_rom(m_romName), + m_device(m_rom), m_plugin(&m_device) { getController(); // init controller m_clockTempoParam = getController().getParameterIndexByName(Virus::g_paramClockTempo); @@ -111,8 +119,8 @@ bool AudioPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layou if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) return false; - // This checks if the input layout matches the output layout - if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) + // This checks if the input is stereo + if (layouts.getMainInputChannelSet() != juce::AudioChannelSet::stereo()) return false; return true; @@ -143,17 +151,14 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, // Alternatively, you can process the samples with the channels // interleaved by keeping the same state. - const float* inputs[8] = {}; - float* outputs[8] = {}; + synthLib::TAudioInputs inputs{}; + synthLib::TAudioOutputs outputs{}; for (int channel = 0; channel < totalNumInputChannels; ++channel) - { - const float* in = buffer.getReadPointer(channel); - float* out = buffer.getWritePointer(channel); + inputs[channel] = buffer.getReadPointer(channel); - inputs[channel] = in; - outputs[channel] = out; - } + for (int channel = 0; channel < totalNumOutputChannels; ++channel) + outputs[channel] = buffer.getWritePointer(channel); for(const auto metadata : midiMessages) { diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h @@ -63,6 +63,10 @@ public: std::string getRomName() { return juce::File(juce::String(m_romName)).getFileNameWithoutExtension().toStdString(); } + virusLib::ROMFile::Model getModel() const + { + return m_rom.getModel(); + } synthLib::Plugin& getPlugin() { return m_plugin; @@ -78,11 +82,12 @@ private: void setState(const void *_data, size_t _sizeInBytes); //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) + std::string m_romName; + virusLib::ROMFile m_rom; virusLib::Device m_device; synthLib::Plugin m_plugin; std::vector<synthLib::SMidiEvent> m_midiOut; - std::string m_romName; uint32_t m_clockTempoParam = 0xffffffff; }; diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -161,10 +161,7 @@ namespace Virus ignored */ } - virusLib::VirusModel Controller::getVirusModel() const - { - return m_singles[2][0].name == "Taurus JS" ? virusLib::VirusModel::B : virusLib::VirusModel::C; - } + juce::StringArray Controller::getSinglePresetNames(virusLib::BankNumber _bank) const { if (_bank == virusLib::BankNumber::EditBuffer) diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -81,8 +81,6 @@ namespace Virus juce::Value* getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex); - virusLib::VirusModel getVirusModel() const; - juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; std::string getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const; std::string getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const; diff --git a/source/jucePlugin/ui3/PatchBrowser.cpp b/source/jucePlugin/ui3/PatchBrowser.cpp @@ -3,6 +3,7 @@ #include "VirusEditor.h" #include "../../virusLib/microcontrollerTypes.h" +#include "../../virusLib/microcontroller.h" #include "../VirusController.h" #include "juce_cryptography/hashing/juce_MD5.h" @@ -15,15 +16,9 @@ const juce::Array<juce::String> ModelList = {"A","B","C","TI"}; namespace genericVirusUI { - virusLib::VirusModel guessVersion(const uint8_t v) + virusLib::PresetVersion guessVersion(const uint8_t v) { - if (v < 5) - return virusLib::A; - if (v == 6) - return virusLib::B; - if (v == 7) - return virusLib::C; - return virusLib::TI; + return virusLib::Microcontroller::getPresetVersion(v); } static PatchBrowser* s_lastPatchBrowser = nullptr; @@ -288,9 +283,17 @@ namespace genericVirusUI text = rowElement.unison == 0 ? " " : String(rowElement.unison + 1); else if (columnId == Columns::ST) text = rowElement.transpose != 64 ? String(rowElement.transpose - 64) : " "; - else if (columnId == Columns::VER) { - if (rowElement.model < ModelList.size()) - text = ModelList[rowElement.model]; + else if (columnId == Columns::VER) + { + switch (rowElement.model) + { + case virusLib::A: text = "A"; break; + case virusLib::B: text = "B"; break; + case virusLib::C: text = "C"; break; + case virusLib::D: text = "TI"; break; + case virusLib::D2: text = "TI2"; break; + default: text = "?"; break; + } } g.drawText(text, 2, 0, width - 4, height, Justification::centredLeft, true); // [6] g.setColour(m_patchList.getLookAndFeel().findColour(ListBox::backgroundColourId)); diff --git a/source/jucePlugin/ui3/PatchBrowser.h b/source/jucePlugin/ui3/PatchBrowser.h @@ -11,7 +11,7 @@ namespace Virus namespace virusLib { - enum VirusModel : uint8_t; + enum PresetVersion : uint8_t; } namespace genericVirusUI @@ -25,7 +25,7 @@ namespace genericVirusUI std::string category1; std::string category2; std::vector<uint8_t> sysex; - virusLib::VirusModel model; + virusLib::PresetVersion model; uint8_t unison = 0; uint8_t transpose = 0; uint8_t arpMode = 0; diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp @@ -384,14 +384,21 @@ namespace genericVirusUI if(!m_deviceModel) return; + const auto& presets = getController().getSinglePresets(); + const auto& data = presets.front().front().data; + if(data.empty()) + return; + std::string m; - switch(getController().getVirusModel()) + switch(virusLib::Microcontroller::getPresetVersion(data.front())) { - case virusLib::A: m = "A"; break; - case virusLib::B: m = "B"; break; - case virusLib::C: m = "C"; break; - case virusLib::TI: m = "TI"; break; + case virusLib::A: m = "A"; break; + case virusLib::B: m = "B"; break; + case virusLib::C: m = "C"; break; + case virusLib::D: m = "TI"; break; + case virusLib::D2: m = "TI2"; break; + default: m = "?"; break; } m_deviceModel->setText(m, juce::dontSendNotification); diff --git a/source/jucePluginLib/parameterdescriptions.cpp b/source/jucePluginLib/parameterdescriptions.cpp @@ -321,7 +321,13 @@ namespace pluginLib parseMidiPackets(errors, midipackets); auto res = errors.str(); - assert(res.empty()); + + if(!res.empty()) + { + LOG("ParameterDescription parsing issues:\n" << res); + assert(false); + } + return res; } diff --git a/source/portaudio/CMakeLists.txt b/source/portaudio/CMakeLists.txt @@ -316,8 +316,10 @@ ELSE() ENDIF() - SET(PA_PKGCONFIG_LDFLAGS "${PA_PKGCONFIG_LDFLAGS} -lm -lpthread") - SET(PA_LIBRARY_DEPENDENCIES ${PA_LIBRARY_DEPENDENCIES} m pthread) + IF(NOT ANDROID) + SET(PA_PKGCONFIG_LDFLAGS "${PA_PKGCONFIG_LDFLAGS} -lm -lpthread") + SET(PA_LIBRARY_DEPENDENCIES ${PA_LIBRARY_DEPENDENCIES} m pthread) + ENDIF() ENDIF() diff --git a/source/portmidi/pm_common/CMakeLists.txt b/source/portmidi/pm_common/CMakeLists.txt @@ -57,7 +57,9 @@ if(UNIX) prepend_path(LIBSRC ../pm_linux/ ${LINUXSRC}) list(APPEND LIBSRC ../porttime/ptlinux) - set(PM_NEEDED_LIBS pthread asound) + if(NOT ANDROID) + set(PM_NEEDED_LIBS pthread asound) + endif() endif(APPLE) else(UNIX) if(WIN32) diff --git a/source/portmidi/pm_dylib/CMakeLists.txt b/source/portmidi/pm_dylib/CMakeLists.txt @@ -88,7 +88,9 @@ if(UNIX) prepend_path(LIBSRC ../pm_linux/ ${LINUXSRC}) list(APPEND LIBSRC ../porttime/ptlinux) - set(PM_NEEDED_LIBS pthread asound) + if(NOT ANDROID) + set(PM_NEEDED_LIBS pthread asound) + endif() endif(APPLE) else(UNIX) if(WIN32) diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -5,8 +5,11 @@ add_library(synthLib STATIC) set(SOURCES audiobuffer.cpp audiobuffer.h + audioTypes.h + configFile.cpp configFile.h device.cpp device.h deviceTypes.h + midiBufferParser.cpp midiBufferParser.h midiToSysex.cpp midiToSysex.h midiTypes.h os.cpp os.h @@ -14,6 +17,8 @@ set(SOURCES resampler.cpp resampler.h resamplerInOut.cpp resamplerInOut.h sysexToMidi.cpp sysexToMidi.h + wavReader.cpp wavReader.h + wavTypes.h wavWriter.cpp wavWriter.h ) diff --git a/source/synthLib/audioTypes.h b/source/synthLib/audioTypes.h @@ -0,0 +1,16 @@ +#pragma once + +#include <array> + +#include "dsp56kEmu/types.h" + +namespace synthLib +{ + template<typename T> using TAudioInputsT = std::array<const T*,4>; + template<typename T> using TAudioOutputsT = std::array<T*,12>; + + using TAudioInputs = TAudioInputsT<float>; + using TAudioOutputs = TAudioOutputsT<float>; + using TAudioInputsInt = TAudioInputsT<dsp56k::TWord>; + using TAudioOutputsInt = TAudioOutputsT<dsp56k::TWord>; +} diff --git a/source/synthLib/audiobuffer.cpp b/source/synthLib/audiobuffer.cpp @@ -57,6 +57,14 @@ namespace synthLib } } + void AudioBuffer::append(const TAudioInputs& _data, size_t _size) + { + const auto c = std::min(m_data.size(), _data.size()); + + for(size_t i=0; i<c; ++i) + append(m_data[i], _data[i], _size); + } + void AudioBuffer::remove(const size_t _count) { for(size_t c=0; c<m_data.size(); ++c) @@ -68,16 +76,18 @@ namespace synthLib } } - void AudioBuffer::fillPointers(float** _pointers, size_t _offset) + void AudioBuffer::fillPointers(TAudioOutputs& _pointers, size_t _offset) { for(size_t c=0; c<m_data.size(); ++c) _pointers[c] = &m_data[c][_offset]; } - void AudioBuffer::fillPointers(const float** _pointers, size_t _offset) const + void AudioBuffer::fillPointers(TAudioInputs& _pointers, size_t _offset) const { for(size_t c=0; c<m_data.size(); ++c) _pointers[c] = &m_data[c][_offset]; + for(size_t c=m_data.size(); c<_pointers.size(); ++c) + _pointers[c] = nullptr; } size_t AudioBuffer::size() const diff --git a/source/synthLib/audiobuffer.h b/source/synthLib/audiobuffer.h @@ -2,6 +2,8 @@ #include <cstddef> #include <vector> +#include "audioTypes.h" + namespace synthLib { class AudioBuffer @@ -14,11 +16,12 @@ namespace synthLib void resize(size_t _capacity); void append(const TBuffer& _data); void append(const float** _data, size_t _size); + void append(const TAudioInputs& _data, size_t _size); void remove(size_t _count); - void fillPointers(float** _pointers, size_t _offset = 0); - void fillPointers(const float** _pointers, size_t _offset = 0) const; + void fillPointers(TAudioOutputs& _pointers, size_t _offset = 0); + void fillPointers(TAudioInputs& _pointers, size_t _offset = 0) const; size_t size() const; void ensureSize(size_t _size) @@ -28,7 +31,10 @@ namespace synthLib } void insertZeroes(size_t _size); - const std::vector<float>& getChannel(const size_t _channel) { return m_data[_channel]; } + + const std::vector<float>& getChannel(const size_t _channel) const { return m_data[_channel]; } + float* getChannel(const size_t _channel) { return &m_data[_channel].front(); } + bool empty() const { return size() == 0; } AudioBuffer(size_t _channelCount = 2, size_t _capacity = 1024); diff --git a/source/synthLib/configFile.cpp b/source/synthLib/configFile.cpp @@ -0,0 +1,61 @@ +#include "configFile.h" + +#include <fstream> + +#include "dsp56kEmu/logging.h" + +namespace synthLib +{ + static bool needsTrim(char _c) + { + switch (_c) + { + case ' ': + case '\r': + case '\n': + case '\t': + return true; + default: + return false; + } + } + + static std::string trim(std::string _s) + { + while (!_s.empty() && needsTrim(_s.front())) + _s = _s.substr(1); + while (!_s.empty() && needsTrim(_s.back())) + _s = _s.substr(0, _s.size() - 1); + return _s; + } + + ConfigFile::ConfigFile(const char* _filename) + { + std::ifstream file(_filename, std::ios::in); + + if (!file.is_open()) + throw std::runtime_error("Failed to open config file " + std::string(_filename)); + + std::string line; + + while(std::getline(file, line)) + { + if(line.empty()) + continue; + + // // comment? + if (line.front() == '#' || line.front() == ';') + continue; + + const auto posEq = line.find('='); + + if (posEq == std::string::npos) + continue; + + const auto key = trim(line.substr(0, posEq)); + const auto val = trim(line.substr(posEq + 1)); + + m_values.emplace_back(std::make_pair(key, val)); + } + } +} +\ No newline at end of file diff --git a/source/synthLib/configFile.h b/source/synthLib/configFile.h @@ -0,0 +1,20 @@ +#pragma once + +#include <string> +#include <vector> + +namespace synthLib +{ + class ConfigFile + { + public: + explicit ConfigFile(const char* _filename); + + const std::vector<std::pair<std::string, std::string>>& getValues() const + { + return m_values; + } + private: + std::vector<std::pair<std::string, std::string>> m_values; + }; +} diff --git a/source/synthLib/device.cpp b/source/synthLib/device.cpp @@ -1,5 +1,6 @@ #include "device.h" +#include "audioTypes.h" #include "../dsp56300/source/dsp56kEmu/dsp.h" #include "../dsp56300/source/dsp56kEmu/memory.h" @@ -7,44 +8,8 @@ using namespace dsp56k; namespace synthLib { - Device::Device(uint32_t _memorySize, uint32_t _externalMemStartAddress) - { - const size_t g_requiredMemSize = alignedSize<DSP>() + alignedSize<Memory>() + _memorySize * MemArea_COUNT * sizeof(uint32_t); - - m_buffer.resize(alignedSize(g_requiredMemSize)); - - auto* buf = &m_buffer[0]; - buf = alignedAddress(buf); - - auto* bufDSP = buf; - auto* bufMem = bufDSP + alignedSize<DSP>(); - auto* bufBuf = bufMem + alignedSize<Memory>(); - - m_periph.getEsai().setCallback([this](Audio* _audio) - { - onAudioWritten(); - }, 0, 1); - - m_memory = new (bufMem)Memory(m_memoryValidator, _memorySize, reinterpret_cast<TWord*>(bufBuf)); - m_dsp = new (buf)DSP(*m_memory, &m_periph, &m_periph); - - if(_externalMemStartAddress) - m_memory->setExternalMemory(_externalMemStartAddress, true); - } - - Device::~Device() - { - m_dspThread.reset(); - - if(m_dsp) - { - m_dsp->~DSP(); - m_memory->~Memory(); - - m_dsp = nullptr; - m_memory = nullptr; - } - } + Device::Device() = default; + Device::~Device() = default; void Device::dummyProcess(const uint32_t _numSamples) { @@ -52,26 +17,27 @@ namespace synthLib buf.resize(_numSamples); const auto ptr = &buf[0]; - const float* in[2] = {ptr, ptr}; - float* out[2] = {ptr, ptr}; + TAudioInputs in = {ptr, ptr, nullptr, nullptr};//, nullptr, nullptr, nullptr, nullptr}; + TAudioOutputs out = {ptr, ptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; std::vector<SMidiEvent> midi; process(in, out, _numSamples, midi, midi); } - void Device::process(const float** _inputs, float** _outputs, const size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut) + void Device::process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, const size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut) { for (const auto& ev : _midiIn) sendMidi(ev, _midiOut); - m_periph.getEsai().processAudioInterleaved(_inputs, _outputs, _size, 2, 2, m_extraLatency); + processAudio(_inputs, _outputs, _size); + readMidiOut(_midiOut); } void Device::setExtraLatencySamples(const uint32_t _size) { - const uint32_t maxLatency = static_cast<uint32_t>(getPeriph().getEsai().getAudioInputs()[0].capacity()) >> 1; + constexpr auto maxLatency = Audio::RingBufferSize >> 1; m_extraLatency = std::min(_size, maxLatency); @@ -82,9 +48,4 @@ namespace synthLib LOG("Warning, limited requested latency " << _size << " to maximum value " << maxLatency << ", audio will be out of sync!"); } } - - void Device::startDSPThread() - { - m_dspThread.reset(new DSPThread(*m_dsp)); - } } diff --git a/source/synthLib/device.h b/source/synthLib/device.h @@ -2,6 +2,7 @@ #include <cstdint> +#include "audioTypes.h" #include "deviceTypes.h" #include "../synthLib/midiTypes.h" @@ -13,9 +14,9 @@ namespace synthLib class Device { public: - Device(uint32_t _memorySize, uint32_t _externalMemStartAddress); + Device(); virtual ~Device(); - virtual void process(const float** _inputs, float** _outputs, size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut); + virtual void process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut); void setExtraLatencySamples(uint32_t _size); uint32_t getExtraLatencySamples() const { return m_extraLatency; } @@ -23,35 +24,22 @@ namespace synthLib virtual uint32_t getInternalLatencyMidiToOutput() const { return 0; } virtual uint32_t getInternalLatencyInputToOutput() const { return 0; } - void startDSPThread(); - virtual float getSamplerate() const = 0; virtual bool isValid() const = 0; virtual bool getState(std::vector<uint8_t>& _state, StateType _type) = 0; virtual bool setState(const std::vector<uint8_t>& _state, StateType _type) = 0; + virtual uint32_t getChannelCountIn() = 0; + virtual uint32_t getChannelCountOut() = 0; + protected: virtual void readMidiOut(std::vector<SMidiEvent>& _midiOut) = 0; + virtual void processAudio(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, size_t _samples) = 0; virtual bool sendMidi(const SMidiEvent& _ev, std::vector<SMidiEvent>& _response) = 0; - virtual void onAudioWritten() {} void dummyProcess(uint32_t _numSamples); - - dsp56k::HDI08& getHDI08() { return m_periph.getHDI08(); } - dsp56k::Peripherals56362& getPeriph() { return m_periph; } - dsp56k::DSP& getDSP() { return *m_dsp; } private: - std::vector<uint8_t> m_buffer; - - dsp56k::DefaultMemoryValidator m_memoryValidator; - dsp56k::Peripherals56362 m_periph; - - dsp56k::Memory* m_memory = nullptr; - dsp56k::DSP* m_dsp = nullptr; - dsp56k::Jit* m_jit = nullptr; - - std::unique_ptr<dsp56k::DSPThread> m_dspThread; std::vector<SMidiEvent> m_midiIn; uint32_t m_extraLatency = 0; }; diff --git a/source/synthLib/midiBufferParser.cpp b/source/synthLib/midiBufferParser.cpp @@ -0,0 +1,98 @@ +#include "midiBufferParser.h" + +#include "midiTypes.h" + +namespace synthLib +{ + void MidiBufferParser::write(const std::vector<uint8_t>& _data) + { + if(_data.empty()) + return; + + for (const auto d : _data) + { + if(d == synthLib::M_STARTOFSYSEX) + { + flushSysex(); + m_sysex = true; + m_sysexBuffer.push_back(d); + continue; + } + + if(m_sysex) + { + if(d == synthLib::M_ENDOFSYSEX) + { + flushSysex(); + continue; + } + if(d < 0x80) + { + m_sysexBuffer.push_back(d); + continue; + } + if(d >= 0xf0) + { + // system realtime intercepting sysex + m_midiEvents.emplace_back(d); + continue; + } + + flushSysex(); // aborted sysex + } + + if(m_pendingEventLen == 0) + m_pendingEvent.a = d; + else if(m_pendingEventLen == 1) + m_pendingEvent.b = d; + else if(m_pendingEventLen == 2) + m_pendingEvent.c = d; + + ++m_pendingEventLen; + + if(m_pendingEventLen == 1) + { + if((m_pendingEvent.a & 0xf0) == 0xf0) + flushEvent(); + } + else if(m_pendingEventLen == 2) + { + if(m_pendingEvent.a == synthLib::M_QUARTERFRAME || (m_pendingEvent.a & 0xf0) == synthLib::M_AFTERTOUCH) + flushEvent(); + } + else if(m_pendingEventLen == 3) + { + flushEvent(); + } + } + } + + void MidiBufferParser::getEvents(std::vector<synthLib::SMidiEvent>& _events) + { + std::swap(m_midiEvents, _events); + m_midiEvents.clear(); + } + + void MidiBufferParser::flushSysex() + { + m_sysex = false; + + if(m_sysexBuffer.empty()) + return; + + synthLib::SMidiEvent ev; + ev.sysex.swap(m_sysexBuffer); + + if(ev.sysex.back() != synthLib::M_ENDOFSYSEX) + ev.sysex.push_back(synthLib::M_ENDOFSYSEX); + + m_midiEvents.push_back(ev); + m_sysexBuffer.clear(); + } + + void MidiBufferParser::flushEvent() + { + m_midiEvents.push_back(m_pendingEvent); + m_pendingEventLen = 0; + } +} +\ No newline at end of file diff --git a/source/synthLib/midiBufferParser.h b/source/synthLib/midiBufferParser.h @@ -0,0 +1,26 @@ +#pragma once + +#include <cstdint> +#include <vector> + +#include "../synthLib/midiTypes.h" + +namespace synthLib +{ + class MidiBufferParser + { + public: + void write(const std::vector<uint8_t>& _data); + void getEvents(std::vector<synthLib::SMidiEvent>& _events); + + private: + void flushSysex(); + void flushEvent(); + + std::vector<synthLib::SMidiEvent> m_midiEvents; + std::vector<uint8_t> m_sysexBuffer; + synthLib::SMidiEvent m_pendingEvent; + uint32_t m_pendingEventLen = 0; + bool m_sysex = false; + }; +} diff --git a/source/synthLib/plugin.cpp b/source/synthLib/plugin.cpp @@ -17,7 +17,7 @@ namespace synthLib { constexpr uint8_t g_stateVersion = 1; - Plugin::Plugin(Device* _device) : m_device(_device) + Plugin::Plugin(Device* _device) : m_resampler(_device->getChannelCountIn(), _device->getChannelCountOut()), m_device(_device) { m_resampler.setDeviceSamplerate(_device->getSamplerate()); } @@ -43,7 +43,7 @@ namespace synthLib updateDeviceLatency(); } - void Plugin::process(const float** _inputs, float** _outputs, size_t _count, const float _bpm, const float _ppqPos, const bool _isPlaying) + void Plugin::process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, size_t _count, const float _bpm, const float _ppqPos, const bool _isPlaying) { if(!m_device->isValid()) return; @@ -51,13 +51,14 @@ namespace synthLib setFlushDenormalsToZero(); - const float* inputs[8] {}; - float* outputs[8] {}; + TAudioInputs inputs(_inputs); + TAudioOutputs outputs(_outputs); - inputs[0] = _inputs && _inputs[0] ? _inputs[0] : getDummyBuffer(_count); - inputs[1] = _inputs && _inputs[1] ? _inputs[1] : getDummyBuffer(_count); - outputs[0] = _outputs && _outputs[0] ? _outputs[0] : getDummyBuffer(_count); - outputs[1] = _outputs && _outputs[1] ? _outputs[1] : getDummyBuffer(_count); + for(size_t i=0; i<inputs.size(); ++i) + inputs[i] = _inputs[i] ? _inputs[i] : getDummyBuffer(_count); + + for(size_t i=0; i<outputs.size(); ++i) + outputs[i] = _outputs[i] ? _outputs[i] : getDummyBuffer(_count); std::lock_guard lock(m_lock); @@ -65,9 +66,9 @@ namespace synthLib processMidiClock(_bpm, _ppqPos, _isPlaying, _count); m_resampler.process(inputs, outputs, m_midiIn, m_midiOut, static_cast<uint32_t>(_count), - [&](const float** _in, float** _out, size_t _c, const ResamplerInOut::TMidiVec& _midiIn, ResamplerInOut::TMidiVec& _midiOut) + [&](const TAudioInputs& _ins, const TAudioOutputs& _outs, size_t _c, const ResamplerInOut::TMidiVec& _midiIn, ResamplerInOut::TMidiVec& _midiOut) { - m_device->process(_in, _out, _c, _midiIn, _midiOut); + m_device->process(_ins, _outs, _c, _midiIn, _midiOut); }); m_midiIn.clear(); diff --git a/source/synthLib/plugin.h b/source/synthLib/plugin.h @@ -26,7 +26,7 @@ namespace synthLib uint32_t getLatencyMidiToOutput() const; uint32_t getLatencyInputToOutput() const; - void process(const float** _inputs, float** _outputs, size_t _count, float _bpm, float _ppqPos, bool _isPlaying); + void process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, size_t _count, float _bpm, float _ppqPos, bool _isPlaying); void getMidiOut(std::vector<SMidiEvent>& _midiOut); bool isValid() const; diff --git a/source/synthLib/resampler.cpp b/source/synthLib/resampler.cpp @@ -13,6 +13,7 @@ synthLib::Resampler::Resampler(const float _samplerateIn, const float _samplerat , m_samplerateOut(_samplerateOut) , m_factorInToOut(_samplerateIn / _samplerateOut) , m_factorOutToIn(_samplerateOut / _samplerateIn) + , m_outputPtrs({}) { } @@ -21,8 +22,10 @@ synthLib::Resampler::~Resampler() destroyResamplers(); } -uint32_t synthLib::Resampler::process(float** _output, const uint32_t _numChannels, const uint32_t _numSamples, bool _allowLessOutput, const TProcessFunc& _processFunc) +uint32_t synthLib::Resampler::process(TAudioOutputs& _output, const uint32_t _numChannels, const uint32_t _numSamples, bool _allowLessOutput, const TProcessFunc& _processFunc) { + assert(_numChannels <= m_outputPtrs.size()); + setChannelCount(_numChannels); if (getSamplerateIn() == getSamplerateOut()) @@ -36,12 +39,10 @@ uint32_t synthLib::Resampler::process(float** _output, const uint32_t _numChanne while (remaining > 0) { - m_outputPtrs.resize(_numChannels); - for (uint32_t i = 0; i < _numChannels; ++i) m_outputPtrs[i] = &_output[i][index]; - const uint32_t outBufferUsed = processResample(&m_outputPtrs[0], _numChannels, remaining, _processFunc); + const uint32_t outBufferUsed = processResample(m_outputPtrs, _numChannels, remaining, _processFunc); index += outBufferUsed; remaining -= outBufferUsed; @@ -55,7 +56,7 @@ uint32_t synthLib::Resampler::process(float** _output, const uint32_t _numChanne return index; } -uint32_t synthLib::Resampler::processResample(float ** _output, const uint32_t _numChannels, const uint32_t _numSamples, const TProcessFunc& _processFunc) +uint32_t synthLib::Resampler::processResample(const TAudioOutputs& _output, const uint32_t _numChannels, const uint32_t _numSamples, const TProcessFunc& _processFunc) { const uint32_t inputLen = std::max(1, dsp56k::round_int(static_cast<float>(_numSamples) * m_factorInToOut)); @@ -63,8 +64,9 @@ uint32_t synthLib::Resampler::processResample(float ** _output, const uint32_t _ if (availableInputLen < inputLen) { - float* tempBuffers[8]; - + TAudioOutputs tempBuffers; + tempBuffers.fill(nullptr); + for (uint32_t i = 0; i < _numChannels; ++i) { m_tempOutput[i].resize(inputLen, 0.0f); @@ -81,12 +83,12 @@ uint32_t synthLib::Resampler::processResample(float ** _output, const uint32_t _ { float* output = _output[i]; - outBufferUsed = resample_process(m_resamplerOut[i], m_factorOutToIn, &m_tempOutput[i][0], inputLen, 0, &inBufferUsed, output, _numSamples); + outBufferUsed = resample_process(m_resamplerOut[i], m_factorOutToIn, &m_tempOutput[i][0], static_cast<int>(inputLen), 0, &inBufferUsed, output, static_cast<int>(_numSamples)); if (static_cast<uint32_t>(inBufferUsed) < inputLen) { // LOG("inBufferUsed " << inBufferUsed << " inputLen " << inputLen); - const size_t remaining = inputLen - inBufferUsed; + const auto remaining = inputLen - inBufferUsed; m_tempOutput[i].erase(m_tempOutput[i].begin() + remaining, m_tempOutput[i].end()); } @@ -101,7 +103,7 @@ uint32_t synthLib::Resampler::processResample(float ** _output, const uint32_t _ void synthLib::Resampler::destroyResamplers() { - for (auto& resampler : m_resamplerOut) + for (const auto& resampler : m_resamplerOut) resample_close(resampler); m_resamplerOut.clear(); } diff --git a/source/synthLib/resampler.h b/source/synthLib/resampler.h @@ -14,7 +14,7 @@ namespace synthLib class Resampler { public: - using TProcessFunc = std::function<void(float**, uint32_t)>; + using TProcessFunc = std::function<void(TAudioOutputs&, uint32_t)>; Resampler(float _samplerateIn, float _samplerateOut); Resampler(const Resampler&) = delete; @@ -22,18 +22,18 @@ namespace synthLib uint32_t process(AudioBuffer& _output, size_t _outputOffset, uint32_t _numChannels, uint32_t _numSamples, bool _allowLessOutput, const TProcessFunc& _processFunc) { - float* buffers[8]; + TAudioOutputs buffers; _output.fillPointers(buffers, _outputOffset); return process(buffers, _numChannels, _numSamples, _allowLessOutput, _processFunc); } - uint32_t process(float** _output, uint32_t _numChannels, uint32_t _numSamples, bool _allowLessOutput, const TProcessFunc& _processFunc); + uint32_t process(TAudioOutputs& _output, uint32_t _numChannels, uint32_t _numSamples, bool _allowLessOutput, const TProcessFunc& _processFunc); float getSamplerateIn() const { return m_samplerateIn; } float getSamplerateOut() const { return m_samplerateOut; } private: - uint32_t processResample(float** _output, uint32_t _numChannels, uint32_t _numSamples, const TProcessFunc& _processFunc); + uint32_t processResample(const TAudioOutputs& _output, uint32_t _numChannels, uint32_t _numSamples, const TProcessFunc& _processFunc); void destroyResamplers(); void setChannelCount(uint32_t _numChannels); @@ -45,6 +45,6 @@ namespace synthLib std::vector<void*> m_resamplerOut; std::vector< std::vector<float> > m_tempOutput; - std::vector< float* > m_outputPtrs; + TAudioOutputs m_outputPtrs; }; } diff --git a/source/synthLib/resamplerInOut.cpp b/source/synthLib/resamplerInOut.cpp @@ -11,7 +11,13 @@ using namespace dsp56k; namespace synthLib { - constexpr uint32_t g_channelCount = 2; + ResamplerInOut::ResamplerInOut(uint32_t _channelCountIn, uint32_t _channelCountOut) + : m_channelCountIn(_channelCountIn) + , m_channelCountOut(_channelCountOut) + , m_scaledInput(_channelCountIn) + , m_input(_channelCountIn) + { + } void ResamplerInOut::setDeviceSamplerate(float _samplerate) { @@ -39,27 +45,27 @@ namespace synthLib m_out.reset(new Resampler(m_samplerateDevice, m_samplerateHost)); m_in.reset(new Resampler(m_samplerateHost, m_samplerateDevice)); - m_scaledInputSize = 8; - m_scaledInput.resize(m_scaledInputSize); - + m_scaledInputSize = 0; m_inputLatency = 0; m_outputLatency = 0; // prewarm to calculate latency - std::array<std::vector<float>, g_channelCount> data; + std::array<std::vector<float>, 12> data; - std::array<const float*, g_channelCount> ins{}; - std::array<float*, g_channelCount> outs{}; + TAudioInputs ins; + TAudioOutputs outs; for(size_t i=0; i<data.size(); ++i) - { data[i].resize(512, 0); - ins[i] = &data[i][0]; - outs[i] = &data[i][0]; - } + + for(size_t i=0; i<ins.size(); ++i) + ins[i] = i >= data.size() ? nullptr : &data[i][0]; + + for(size_t i=0; i<outs.size(); ++i) + outs[i] = i >= data.size() ? nullptr : &data[i][0]; TMidiVec midiIn, midiOut; - process(&ins[0], &outs[0], TMidiVec(), midiOut, static_cast<uint32_t>(data[0].size()), [&](const float**, float**, size_t, const TMidiVec&, TMidiVec&) + process(ins, outs, TMidiVec(), midiOut, static_cast<uint32_t>(data[0].size()), [&](const TAudioInputs&, const TAudioOutputs&, size_t, const TMidiVec&, TMidiVec&) { }); } @@ -102,7 +108,7 @@ namespace synthLib } } - void ResamplerInOut::process(const float** _inputs, float** _outputs, const TMidiVec& _midiIn, TMidiVec& _midiOut, uint32_t _numSamples, const TProcessFunc& _processFunc) + void ResamplerInOut::process(const TAudioInputs& _inputs, TAudioOutputs& _outputs, const TMidiVec& _midiIn, TMidiVec& _midiOut, const uint32_t _numSamples, const TProcessFunc& _processFunc) { if(!m_in || !m_out) return; @@ -122,22 +128,13 @@ namespace synthLib m_input.append(_inputs, _numSamples); - // resample input to buffer scaledInput - uint32_t scaledSamples; - - // input jitter - if(m_scaledInputSize > 1) - scaledSamples = static_cast<uint32_t>(floor_int(static_cast<float>(_numSamples) * devDivHost)); - else - scaledSamples = static_cast<uint32_t>(ceil_int(static_cast<float>(_numSamples) * devDivHost)); - - m_scaledInputSize += m_in->process(m_scaledInput, m_scaledInputSize, g_channelCount, scaledSamples, false, [&](float** _data, uint32_t _numRequestedSamples) + auto feedInput = [&](TAudioOutputs& _data, uint32_t _numRequestedSamples) { const auto offset = _numRequestedSamples > m_input.size() ? _numRequestedSamples - m_input.size() : 0; if(offset) { // resampler prewarming, wants more data than we have - for(size_t c=0; c<g_channelCount; ++c) + for(size_t c=0; c<m_channelCountIn; ++c) { memset(_data[c], 0, sizeof(float) * offset); _data[c] += offset; @@ -148,7 +145,7 @@ namespace synthLib if(count) { - for(size_t c=0; c<g_channelCount; ++c) + for(size_t c=0; c<m_channelCountIn; ++c) memcpy(_data[c], &m_input.getChannel(c)[0], sizeof(float) * count); m_input.remove(count); @@ -159,14 +156,16 @@ namespace synthLib { LOG("Resampler input latency " << m_inputLatency << " samples"); } - }); + }; - const auto outputSize = m_out->process(_outputs, g_channelCount, _numSamples, false, [&](float** _outs, uint32_t _numProcessedSamples) + auto feedOutput = [&](const TAudioOutputs& _outs, const uint32_t _numProcessedSamples) { + m_scaledInputSize += m_in->process(m_scaledInput, m_scaledInputSize, m_channelCountIn, _numProcessedSamples, false, feedInput); + clampMidiEvents(m_processedMidiIn, m_midiIn, 0, _numProcessedSamples-1); m_midiIn.clear(); - const float* inputs[g_channelCount]; + TAudioInputs inputs; if(_numProcessedSamples > m_scaledInputSize) { // resampler prewarming, wants more data than we have @@ -180,7 +179,9 @@ namespace synthLib _processFunc(inputs, _outs, _numProcessedSamples, m_processedMidiIn, m_midiOut); m_scaledInput.remove(_numProcessedSamples); m_scaledInputSize -= _numProcessedSamples; - }); + }; + + const auto outputSize = m_out->process(_outputs, m_channelCountOut, _numSamples, false, feedOutput); scaleMidiEvents(_midiOut, m_midiOut, hostDivDev); m_midiOut.clear(); diff --git a/source/synthLib/resamplerInOut.h b/source/synthLib/resamplerInOut.h @@ -12,14 +12,14 @@ namespace synthLib { public: using TMidiVec = std::vector<SMidiEvent>; - using TProcessFunc = std::function<void(const float**, float**, size_t, const TMidiVec&, TMidiVec&)>; + using TProcessFunc = std::function<void(const TAudioInputs&, const TAudioOutputs&, size_t, const TMidiVec&, TMidiVec&)>; - ResamplerInOut() = default; + ResamplerInOut(uint32_t _channelCountIn, uint32_t _channelCountOut); void setDeviceSamplerate(float _samplerate); void setHostSamplerate(float _samplerate); - void process(const float** _inputs, float** _outputs, const TMidiVec& _midiIn, TMidiVec& _midiOut, uint32_t _numSamples, const TProcessFunc& _processFunc); + void process(const TAudioInputs& _inputs, TAudioOutputs& _outputs, const TMidiVec& _midiIn, TMidiVec& _midiOut, uint32_t _numSamples, const TProcessFunc& _processFunc); uint32_t getOutputLatency() const { return m_outputLatency; } uint32_t getInputLatency() const { return m_inputLatency; } @@ -30,6 +30,9 @@ namespace synthLib static void clampMidiEvents(TMidiVec& _dst, const TMidiVec& _src, uint32_t _offsetMin, uint32_t _offsetMax); static void extractMidiEvents(TMidiVec& _dst, const TMidiVec& _src, uint32_t _offsetMin, uint32_t _offsetMax); + const uint32_t m_channelCountIn; + const uint32_t m_channelCountOut; + std::unique_ptr<Resampler> m_out = nullptr; std::unique_ptr<Resampler> m_in = nullptr; @@ -38,8 +41,6 @@ namespace synthLib AudioBuffer m_scaledInput; AudioBuffer m_input; - AudioBuffer m_scaledOutput; - AudioBuffer m_output; size_t m_scaledInputSize = 0; diff --git a/source/synthLib/wavReader.cpp b/source/synthLib/wavReader.cpp @@ -0,0 +1,174 @@ +#include "wavReader.h" + +#include <cstring> +#include <map> +#include <cassert> + +#include "wavTypes.h" + +#include "dsp56kEmu/logging.h" + +namespace synthLib +{ +bool WavReader::load(Data& _data, std::vector<CuePoint>* _cuePoints, const uint8_t* _buffer, size_t _bufferSize) +{ +// const unsigned int totalFileLength = _bufferSize; + + size_t bufferPos = 0; + + SWaveFormatHeader& header = *(SWaveFormatHeader*)&_buffer[0]; + bufferPos += sizeof(header); + + if( memcmp( header.str_wave, "WAVE", 4 ) != 0 ) + return false; + + if( memcmp( header.str_riff, "RIFF", 4 ) != 0 ) + return false; + + std::map<uint32_t, SWaveFormatChunkCuePoint> cuePoints; + std::map<uint32_t, std::string> labels; + + size_t dataChunkOffset = 0; + size_t dataChunkSize = 0; + size_t formatChunkOffset = 0; + + while( bufferPos < (_bufferSize - sizeof(SWaveFormatChunkInfo)) ) + { + SWaveFormatChunkInfo& chunkInfo = *(SWaveFormatChunkInfo*)&_buffer[bufferPos]; + bufferPos += sizeof(chunkInfo); + + if (memcmp(chunkInfo.chunkName, "fmt ", 4) == 0) + { + formatChunkOffset = bufferPos; + } + else if (memcmp(chunkInfo.chunkName, "data", 4) == 0) + { + dataChunkOffset = bufferPos; + dataChunkSize = chunkInfo.chunkSize; + } + else if (memcmp(chunkInfo.chunkName, "cue ", 4) == 0) + { + SWaveFormatChunkCue& chunkCue = *(SWaveFormatChunkCue*)&_buffer[bufferPos]; + + size_t offset = bufferPos + sizeof(chunkCue); + + for (size_t i = 0; i < chunkCue.cuePointCount; ++i) + { + SWaveFormatChunkCuePoint& point = *(SWaveFormatChunkCuePoint*)&_buffer[offset]; + + if (cuePoints.find(point.cueId) == cuePoints.end()) + cuePoints.insert(std::make_pair(point.cueId, point)); + else + LOG("Warning: duplicated cue point " << point.cueId << " found"); + + offset += sizeof(point); + } + } + else if (_cuePoints && (memcmp(chunkInfo.chunkName, "list", 4) == 0 || memcmp(chunkInfo.chunkName, "LIST", 4) == 0)) + { + SWaveFormatChunkList& adtl = *(SWaveFormatChunkList*)&_buffer[bufferPos]; + + size_t offset = bufferPos + sizeof(adtl); + + while (offset < (bufferPos + chunkInfo.chunkSize)) + { + SWaveFormatChunkInfo& subChunkInfo = *(SWaveFormatChunkInfo*)&_buffer[offset]; + + offset += sizeof(subChunkInfo); + + if (memcmp(subChunkInfo.chunkName, "labl", 4) == 0 || memcmp(subChunkInfo.chunkName, "note", 4) == 0) + { + SWaveFormatChunkLabel& chunkLabel = *(SWaveFormatChunkLabel*)&_buffer[offset]; + std::vector<char> name; + name.resize(subChunkInfo.chunkSize- sizeof(chunkLabel)); + ::memcpy(&name[0], &_buffer[offset + sizeof(chunkLabel)], name.size()); + + while (!name.empty() && name.back() == 0) + name.pop_back(); + + std::string nameString; + nameString.insert(nameString.begin(), name.begin(), name.end()); + + if (!nameString.empty()) + { + if (labels.find(chunkLabel.cuePointId) == labels.end()) + { + labels.insert(std::make_pair(chunkLabel.cuePointId, nameString)); + } + else + { + LOG("Warning: duplicated cue label " << nameString << " found for cue id " << chunkLabel.cuePointId);; + } + } + } + offset += (subChunkInfo.chunkSize + 1) & ~1; + } + } + + bufferPos += (chunkInfo.chunkSize + 1) & ~1; + } + + if (dataChunkOffset == 0) + { + LOG("Failed to find wave data in file"); + return false; + } + + if (formatChunkOffset == 0) + { + LOG("Failed to find format information in file"); + return false; + } + + SWaveFormatChunkFormat& fmt = *(SWaveFormatChunkFormat*)&_buffer[formatChunkOffset]; + bufferPos += sizeof(fmt); + + if( fmt.wave_type != eFormat_PCM && fmt.wave_type != eFormat_IEEE_FLOAT ) + return false; // Not PCM or float data + + _data.samplerate = fmt.sample_rate; + + bufferPos = dataChunkOffset; + + const uint32_t numBytes = static_cast<uint32_t>(dataChunkSize); + const uint32_t numSamples = (numBytes << 3) / fmt.bits_per_sample; + + int numChannels = fmt.num_channels; + + _data.data = &_buffer[bufferPos]; + _data.dataByteSize = numBytes; + _data.bitsPerSample = fmt.bits_per_sample; + _data.channels = fmt.num_channels; + _data.isFloat = fmt.wave_type == eFormat_IEEE_FLOAT; + + if (_cuePoints && !cuePoints.empty()) + { + // So this is a bit wierd, according to the doc at https://sites.google.com/site/musicgapi/technical-documents/wav-file-format + // The sampleOffset should be in bytes for uncompressed data and the position should be 0 if there is no plst chunk + // Adobe Audition writes both sampleOffset and position and they are both in samples, even though or wav file + // is uncompressed (32bit float though) and there is no plst chunk so for now treat it that way for float data + + _cuePoints->clear(); + for (auto it = cuePoints.begin(); it != cuePoints.end(); ++it) + { + const SWaveFormatChunkCuePoint& src = it->second; + CuePoint dst; + + if (src.playOrderPosition > 0 || src.playOrderPosition == src.sampleOffset) + dst.sampleOffset = src.playOrderPosition; + else + dst.sampleOffset = src.sampleOffset * numSamples / (fmt.bits_per_sample >> 3); + + const auto itName = labels.find(it->first); + + if (itName != labels.end()) + dst.name = itName->second; + + _cuePoints->push_back(dst); + } + } + + return true; +} + +} diff --git a/source/synthLib/wavReader.h b/source/synthLib/wavReader.h @@ -0,0 +1,34 @@ +#pragma once + +#include <vector> +#include <string> + +namespace synthLib +{ + struct CuePoint + { + size_t sampleOffset; + std::string name; + + bool operator < (const CuePoint& _other) const + { + return sampleOffset < _other.sampleOffset; + } + }; + + struct Data + { + const void* data = nullptr; + size_t dataByteSize = 0; + uint32_t bitsPerSample = 0; + uint32_t channels = 0; + uint32_t samplerate = 0; + bool isFloat = false; + }; + + class WavReader + { + public: + static bool load(Data& _data, std::vector<CuePoint>* _cuePoints, const uint8_t* _buffer, size_t _bufferSize); + }; +}; diff --git a/source/synthLib/wavTypes.h b/source/synthLib/wavTypes.h @@ -0,0 +1,109 @@ +#pragma once + +#include <cstdint> + +namespace synthLib +{ +#ifdef _MSC_VER + // TODO: gcc and others +#pragma pack(push, 1) +#endif + + struct SWaveFormatHeader + { + uint8_t str_riff[4]; // ASCII string "RIFF" + uint32_t file_size; // File size - 8 + uint8_t str_wave[4]; // ASCII string "WAVE" + }; + + struct SWaveFormatChunkInfo + { + uint8_t chunkName[4]; // chunk name + uint32_t chunkSize; // chunk length + }; + + struct SWaveFormatChunkFormat // "fmt ", size = 16 (0x10) + { + uint16_t wave_type; // PCM = 1 + uint16_t num_channels; // number of channels + uint32_t sample_rate; // sample rate + uint32_t bytes_per_sec; // bytes per sample (it's second, isn't it?!) + uint16_t block_alignment; // bytes, that must be sent at a single time + uint16_t bits_per_sample; // bits per sample + }; + + struct SWaveFormatChunkCue + { + uint32_t cuePointCount; // number of cue points in list + }; + + struct SWaveFormatChunkCuePoint + { + uint32_t cueId; // unique identification value + uint32_t playOrderPosition; // play order position + int8_t dataChunkId[4]; // RIFF ID of corresponding data chunk + uint32_t chunkStart; // byte offset of data chunk* + uint32_t blockStart; // byte offset to sample of First Channel + uint32_t sampleOffset; // byte offset to sample byte of First Channel + }; + + struct SWaveFormatChunkLabel + { + uint32_t cuePointId; // unique identification value + }; + + struct SWaveFormatChunkList + { + uint8_t typeId[4]; // "adtl" (0x6164746C) => associated data list + }; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + + enum EWaveFormat + { + eFormat_PCM = 0x0001, + eFormat_MS_ADPCM = 0x0002, + eFormat_IEEE_FLOAT = 0x0003, + eFormat_IBM_CVSD = 0x0005, + eFormat_ALAW = 0x0006, + eFormat_MULAW = 0x0007, + eFormat_OKI_ADPCM = 0x0010, + eFormat_DVI_IMA_ADPCM = 0x0011, + eFormat_MEDIASPACE_ADPCM = 0x0012, + eFormat_SIERRA_ADPCM = 0x0013, + eFormat_G723_ADPCM = 0x0014, + eFormat_DIGISTD = 0x0015, + eFormat_DIGIFIX = 0x0016, + eFormat_DIALOGIC_OKI_ADPCM = 0x0017, + eFormat_YAMAHA_ADPCM = 0x0020, + eFormat_SONARC = 0x0021, + eFormat_DSPGROUP_TRUESPEECH = 0x0022, + eFormat_ECHOSC1 = 0x0023, + eFormat_AUDIOFILE_AF36 = 0x0024, + eFormat_APTX = 0x0025, + eFormat_AUDIOFILE_AF10 = 0x0026, + eFormat_DOLBY_AC2 = 0x0030, + eFormat_GSM610 = 0x0031, + eFormat_ANTEX_ADPCME = 0x0033, + eFormat_CONTROL_RES_VQLPC = 0x0034, + eFormat_CONTROL_RES_VQLPC_2 = 0x0035, + eFormat_DIGIADPCM = 0x0036, + eFormat_CONTROL_RES_CR10 = 0x0037, + eFormat_NMS_VBXADPCM = 0x0038, + eFormat_CS_IMAADPCM = 0x0039, + eFormat_G721_ADPCM = 0x0040, + eFormat_MPEG = 0x0050, + eFormat_Xbox_ADPCM = 0x0069, + eFormat_CREATIVE_ADPCM = 0x0200, + eFormat_CREATIVE_FASTSPEECH8 = 0x0202, + eFormat_CREATIVE_FASTSPEECH10 = 0x0203, + eFormat_FM_TOWNS_SND = 0x0300, + eFormat_OLIGSM = 0x1000, + eFormat_OLIADPCM = 0x1001, + eFormat_OLICELP = 0x1002, + eFormat_OLISBC = 0x1003, + eFormat_OLIOPR = 0x1004 + }; +} diff --git a/source/synthLib/wavWriter.cpp b/source/synthLib/wavWriter.cpp @@ -4,6 +4,11 @@ #include <map> #include <cassert> +#include <chrono> +#include <mutex> +#include <thread> + +#include "dsp56kEmu/types.h" namespace synthLib { @@ -96,4 +101,111 @@ namespace synthLib return true; } + + void WavWriter::writeWord(std::vector<uint8_t>& _dst, dsp56k::TWord _word) + { + const auto d = reinterpret_cast<const uint8_t*>(&_word); + _dst.push_back(d[0]); + _dst.push_back(d[1]); + _dst.push_back(d[2]); + } + + AsyncWriter::AsyncWriter(std::string _filename, uint32_t _samplerate, bool _measureSilence) + : m_filename(std::move(_filename)) + , m_samplerate(_samplerate) + , m_measureSilence(_measureSilence) + { + m_thread.reset(new std::thread([&]() + { + threadWriteFunc(); + })); + } + + AsyncWriter::~AsyncWriter() + { + m_finished = true; + + if(m_thread) + { + m_thread->join(); + m_thread.reset(); + } + } + + void AsyncWriter::append(const std::function<void(std::vector<dsp56k::TWord>&)>& _func) + { + std::lock_guard lock(m_writeMutex); + _func(m_stereoOutput); + } + + void AsyncWriter::threadWriteFunc() + { + synthLib::WavWriter writer; + + std::vector<dsp56k::TWord> m_wordBuffer; + std::vector<uint8_t> m_byteBuffer; + m_byteBuffer.reserve(m_wordBuffer.capacity() * 3); + + bool foundNonSilence = false; + + while(true) + { + { + std::lock_guard lock(m_writeMutex); + std::swap(m_wordBuffer, m_stereoOutput); + } + + if(m_wordBuffer.empty() && m_byteBuffer.empty() && m_finished) + break; + + if(!m_wordBuffer.empty()) + { + for (const dsp56k::TWord w : m_wordBuffer) + WavWriter::writeWord(m_byteBuffer, w); + + if(m_measureSilence) + { + bool isSilence = true; + + for (const dsp56k::TWord w : m_wordBuffer) + { + constexpr dsp56k::TWord silenceThreshold = 0x1ff; + const bool silence = w < silenceThreshold || w >= (0xffffff - silenceThreshold); + if(!silence) + { + isSilence = false; + break; + } + } + + if(foundNonSilence && isSilence) + { + m_silenceDuration += static_cast<uint32_t>(m_wordBuffer.size() >> 1); + } + else if(!isSilence) + { + m_silenceDuration = 0; + foundNonSilence = true; + } + } + + m_wordBuffer.clear(); + } + + if(!m_byteBuffer.empty()) + { + if(writer.write(m_filename, 24, false, 2, static_cast<int>(m_samplerate), &m_byteBuffer[0], m_byteBuffer.size())) + { + m_byteBuffer.clear(); + } + else if(m_finished) + { + LOG("Unable to write data to file " << m_filename << " but termination requested, file is missing data"); + break; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } } diff --git a/source/synthLib/wavWriter.h b/source/synthLib/wavWriter.h @@ -1,89 +1,19 @@ #pragma once -#include <cstdint> +#include <functional> +#include <memory> +#include <mutex> + +#include "wavTypes.h" + #include <vector> #include <string> +#include <thread> + +#include "dsp56kEmu/types.h" namespace synthLib { -#ifdef _MSC_VER -// TODO: gcc and others -#pragma pack(push, 1) -#endif - - struct SWaveFormatHeader - { - uint8_t str_riff[4]; // ASCII string "RIFF" - uint32_t file_size; // File size - 8 - uint8_t str_wave[4]; // ASCII string "WAVE" - }; - - struct SWaveFormatChunkInfo - { - uint8_t chunkName[4]; // chunk name - uint32_t chunkSize; // chunk length - }; - - struct SWaveFormatChunkFormat // "fmt ", size = 16 (0x10) - { - uint16_t wave_type; // PCM = 1 - uint16_t num_channels; // number of channels - uint32_t sample_rate; // sample rate - uint32_t bytes_per_sec; // bytes per sample (it's second, isn't it?!) - uint16_t block_alignment; // bytes, that must be sent at a single time - uint16_t bits_per_sample; // bits per sample - }; - -#ifdef _MSC_VER -#pragma pack(pop) -#endif - - enum EWaveFormat - { - eFormat_PCM = 0x0001, - eFormat_MS_ADPCM = 0x0002, - eFormat_IEEE_FLOAT = 0x0003, - eFormat_IBM_CVSD = 0x0005, - eFormat_ALAW = 0x0006, - eFormat_MULAW = 0x0007, - eFormat_OKI_ADPCM = 0x0010, - eFormat_DVI_IMA_ADPCM = 0x0011, - eFormat_MEDIASPACE_ADPCM = 0x0012, - eFormat_SIERRA_ADPCM = 0x0013, - eFormat_G723_ADPCM = 0x0014, - eFormat_DIGISTD = 0x0015, - eFormat_DIGIFIX = 0x0016, - eFormat_DIALOGIC_OKI_ADPCM = 0x0017, - eFormat_YAMAHA_ADPCM = 0x0020, - eFormat_SONARC = 0x0021, - eFormat_DSPGROUP_TRUESPEECH = 0x0022, - eFormat_ECHOSC1 = 0x0023, - eFormat_AUDIOFILE_AF36 = 0x0024, - eFormat_APTX = 0x0025, - eFormat_AUDIOFILE_AF10 = 0x0026, - eFormat_DOLBY_AC2 = 0x0030, - eFormat_GSM610 = 0x0031, - eFormat_ANTEX_ADPCME = 0x0033, - eFormat_CONTROL_RES_VQLPC = 0x0034, - eFormat_CONTROL_RES_VQLPC_2 = 0x0035, - eFormat_DIGIADPCM = 0x0036, - eFormat_CONTROL_RES_CR10 = 0x0037, - eFormat_NMS_VBXADPCM = 0x0038, - eFormat_CS_IMAADPCM = 0x0039, - eFormat_G721_ADPCM = 0x0040, - eFormat_MPEG = 0x0050, - eFormat_Xbox_ADPCM = 0x0069, - eFormat_CREATIVE_ADPCM = 0x0200, - eFormat_CREATIVE_FASTSPEECH8 = 0x0202, - eFormat_CREATIVE_FASTSPEECH10 = 0x0203, - eFormat_FM_TOWNS_SND = 0x0300, - eFormat_OLIGSM = 0x1000, - eFormat_OLIADPCM = 0x1001, - eFormat_OLICELP = 0x1002, - eFormat_OLISBC = 0x1003, - eFormat_OLIOPR = 0x1004 - }; - class WavWriter { public: @@ -94,7 +24,47 @@ namespace synthLib { return write(_filename, _bitsPerSample, isFloat, _channelCount, _samplerate, &_data[0], sizeof(T) * _data.size()); } + + static void writeWord(std::vector<uint8_t>& _dst, dsp56k::TWord _word); + private: size_t m_existingDataSize = 0; }; + + class AsyncWriter + { + public: + AsyncWriter(std::string _filename, uint32_t _samplerate, bool _measureSilence = false); + ~AsyncWriter(); + + void append(const std::function<void(std::vector<dsp56k::TWord>&)>& _func); + + void setFinished() + { + m_finished = true; + } + + bool isFinished() const + { + return m_finished; + } + + uint32_t getSilenceDuration() const + { + return m_silenceDuration; + } + + private: + void threadWriteFunc(); + + const std::string m_filename; + const uint32_t m_samplerate; + const bool m_measureSilence; + + bool m_finished = false; + std::unique_ptr<std::thread> m_thread; + uint32_t m_silenceDuration = 0; + std::mutex m_writeMutex; + std::vector<dsp56k::TWord> m_stereoOutput; + }; }; diff --git a/source/virusConsoleLib/CMakeLists.txt b/source/virusConsoleLib/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) +project(virusConsoleLib) + +add_library(virusConsoleLib STATIC) + +set(SOURCES + audioProcessor.cpp audioProcessor.h + esaiListener.cpp esaiListener.h + esaiListenerToCallback.cpp esaiListenerToCallback.h + esaiListenerToFile.cpp esaiListenerToFile.h + consoleApp.cpp consoleApp.h +) + +target_sources(virusConsoleLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(virusConsoleLib PUBLIC virusLib) diff --git a/source/virusConsoleLib/audioProcessor.cpp b/source/virusConsoleLib/audioProcessor.cpp @@ -0,0 +1,91 @@ +#include "audioProcessor.h" + +#include <vector> + +#include "esaiListenerToFile.h" +#include "dsp56kEmu/types.h" + +#include "../synthLib/wavWriter.h" + +#include "../virusLib/dspSingle.h" + +AudioProcessor::AudioProcessor(uint32_t _samplerate, std::string _outputFilename, bool _terminateOnSilence, uint32_t _maxSamplecount, virusLib::DspSingle* _dsp1, virusLib::DspSingle* _dsp2) +: m_samplerate(_samplerate) +, m_outputFilname(std::move(_outputFilename)) +, m_terminateOnSilence(_terminateOnSilence) +, m_maxSampleCount(_maxSamplecount) +, m_dsp1(_dsp1) +, m_dsp2(_dsp2) +, m_writer(m_outputFilname, _samplerate, _terminateOnSilence) +{ + m_outputBuffers.resize(2); + m_inputBuffers.resize(2); +} + +AudioProcessor::~AudioProcessor() = default; + +void AudioProcessor::processBlock(const uint32_t _blockSize) +{ + if(m_outputBuffers[0].size() < _blockSize) + { + for(size_t i=0; i<m_outputBuffers.size(); ++i) + { + m_outputBuffers[i].resize(_blockSize); + m_inputBuffers[i].resize(_blockSize); + + m_inputs[i] = &m_inputBuffers[i][0]; + m_outputs[i] = &m_outputBuffers[i][0]; + } + + m_stereoOutput.reserve(m_outputBuffers.size() * 2); + } + + const bool terminateOnSilence = m_terminateOnSilence; + + auto sampleCount = static_cast<uint32_t>(m_inputBuffers[0].size()); + + if(terminateOnSilence && m_writer.getSilenceDuration() >= m_samplerate * 5) + { + m_writer.setFinished(); + return; + } + + if(m_maxSampleCount && m_processedSampleCount >= m_maxSampleCount) + { + m_writer.setFinished(); + return; + } + + if(m_maxSampleCount && m_maxSampleCount - m_processedSampleCount < sampleCount) + { + sampleCount = m_maxSampleCount - m_processedSampleCount; + } + + // reduce thread contention by waiting for the DSP to do some work first. + // If we don't, the ESAI writeTX function will lock/unlock a mutex to inform the waiting thread (us) + // that there is new audio data available. This costs more than 5% of performance +/* const auto& esai = m_dsp1->getPeriphX().getEsai(); + + while(esai.getAudioInputs().size() > (_blockSize>>1)) + std::this_thread::yield(); +*/ + m_dsp1->processAudio(m_inputs, m_outputs, sampleCount, _blockSize); + + m_processedSampleCount += sampleCount; + + { + m_writer.append([&](std::vector<dsp56k::TWord>& _dst) + { + _dst.reserve(m_stereoOutput.size() + sampleCount * 2); + + for(size_t iSrc=0; iSrc<sampleCount; ++iSrc) + { + _dst.push_back(m_outputs[0][iSrc]); + _dst.push_back(m_outputs[1][iSrc]); + } + }); + } + + if(m_maxSampleCount && m_processedSampleCount >= m_maxSampleCount) + m_writer.setFinished(); +} diff --git a/source/virusConsoleLib/audioProcessor.h b/source/virusConsoleLib/audioProcessor.h @@ -0,0 +1,47 @@ +#pragma once + +#include <cstdint> +#include <string> +#include <vector> + +#include "dsp56kEmu/types.h" + +#include "../synthLib/audioTypes.h" +#include "../synthLib/wavWriter.h" + +namespace virusLib +{ + class DspSingle; +} + +class AudioProcessor +{ +public: + AudioProcessor(uint32_t _samplerate, std::string _outputFilename, bool _terminateOnSilence, uint32_t _maxSamplecount, virusLib::DspSingle* _dsp1, virusLib::DspSingle* _dsp2); + ~AudioProcessor(); + + void processBlock(uint32_t _blockSize); + + bool finished() const { return m_writer.isFinished(); } + +private: + // constant data + const uint32_t m_samplerate; + const std::string m_outputFilname; + const bool m_terminateOnSilence; + const uint32_t m_maxSampleCount; + virusLib::DspSingle* const m_dsp1; + virusLib::DspSingle* const m_dsp2; + + // runtime data + synthLib::TAudioInputsInt m_inputs{}; + synthLib::TAudioOutputsInt m_outputs{}; + + std::vector<std::vector<dsp56k::TWord>> m_outputBuffers; + std::vector<std::vector<dsp56k::TWord>> m_inputBuffers; + std::vector<dsp56k::TWord> m_stereoOutput; + + uint32_t m_processedSampleCount = 0; + + synthLib::AsyncWriter m_writer; +}; diff --git a/source/virusConsoleLib/consoleApp.cpp b/source/virusConsoleLib/consoleApp.cpp @@ -0,0 +1,364 @@ +#include "consoleApp.h" + +#include <iostream> + +#include "audioProcessor.h" +#include "esaiListenerToFile.h" + +#include "../virusLib/device.h" + +namespace virusLib +{ + class Device; +} + +using namespace virusLib; +using namespace synthLib; + +class EsaiListener; + +ConsoleApp::ConsoleApp(const std::string& _romFile) +: m_romName(_romFile) +, m_rom(_romFile) +, m_preset({}) +{ + if (!m_rom.isValid()) + { + 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; + return; + } + + virusLib::DspSingle* dsp1 = nullptr; + virusLib::Device::createDspInstances(dsp1, m_dsp2, m_rom); + m_dsp1.reset(dsp1); + + uc.reset(new Microcontroller(m_dsp1->getHDI08(), m_rom)); + if(m_dsp2) + uc->addHDI08(m_dsp2->getHDI08()); +} + +ConsoleApp::~ConsoleApp() +{ + m_demo.reset(); + uc.reset(); + m_dsp1.reset(); + m_dsp2 = nullptr; +} + +bool ConsoleApp::isValid() const +{ + return m_rom.isValid(); +} + +void ConsoleApp::waitReturn() +{ + std::cin.ignore(); +} + +std::thread ConsoleApp::bootDSP(const bool _createDebugger) const +{ + auto loader = virusLib::Device::bootDSP(*m_dsp1, m_rom, _createDebugger); + + if(m_dsp2) + { + auto loader2 = virusLib::Device::bootDSP(*m_dsp2, m_rom, false); + loader2.join(); + } + + return loader; +} + +dsp56k::IPeripherals& ConsoleApp::getYPeripherals() const +{ + return m_dsp1->getPeriphNop(); +} + +void ConsoleApp::loadSingle(int b, int p) +{ + if(m_rom.getSingle(b, p, m_preset)) + { + std::cout << "Loaded Single " << ROMFile::getSingleName(m_preset) << std::endl; + } +} + +bool ConsoleApp::loadSingle(const std::string& _preset) +{ + auto isDigit = true; + for (size_t i = 0; i < _preset.size(); ++i) + { + if (!isdigit(_preset[i])) + { + isDigit = false; + break; + } + } + + if (isDigit) + { + int preset = atoi(_preset.c_str()); + const int bank = preset / m_rom.getPresetsPerBank(); + preset -= bank * m_rom.getPresetsPerBank(); + loadSingle(bank, preset); + return true; + } + + for (uint32_t b = 0; b < 26; ++b) + { + for (uint32_t p = 0; p < m_rom.getPresetsPerBank(); ++p) + { + Microcontroller::TPreset data; + m_rom.getSingle(b, p, data); + + const std::string name = ROMFile::getSingleName(data); + if (name.empty()) + { + return false; + } + if (name == _preset) + { + loadSingle(b, p); + return true; + } + } + } + return false; +} + +bool ConsoleApp::loadDemo(const std::string& _filename) +{ + m_demo.reset(new DemoPlayback(*uc)); + + if(m_demo->loadFile(_filename)) + { + std::cout << "Loaded demo song from file " << _filename << std::endl; + return true; + } + + m_demo.reset(); + return false; +} + +bool ConsoleApp::loadInternalDemo() +{ + if(m_rom.getDemoData().empty()) + return false; + + m_demo.reset(new DemoPlayback(*uc)); + + if(m_demo->loadBinData(m_rom.getDemoData())) + { + std::cout << "Loaded internal demo from ROM " << m_romName << std::endl; + return true; + } + m_demo.reset(); + return false; +} + +std::string ConsoleApp::getSingleName() const +{ + return ROMFile::getSingleName(m_preset); +} + +std::string ConsoleApp::getSingleNameAsFilename() const +{ + auto audioFilename = m_demo ? "factorydemo" : getSingleName(); + + for (size_t i = 0; i < audioFilename.size(); ++i) + { + if (audioFilename[i] == ' ') + audioFilename[i] = '_'; + } + return "virusEmu_" + audioFilename + ".wav"; +} + +void ConsoleApp::audioCallback(uint32_t audioCallbackCount) +{ + uc->process(1); // FIXME wrong value + + constexpr uint8_t baseChannel = 0; + + switch (audioCallbackCount) + { + case 1: + LOG("Sending Init Control Commands"); + uc->sendInitControlCommands(); + break; + case 256: + if(!m_demo) + { + LOG("Sending Preset"); +#if 0 + uc->writeSingle(BankNumber::EditBuffer, 0, m_preset); // cmdline + v.getSingle(1, 6, m_preset); // Anubis MS + uc->writeSingle(BankNumber::EditBuffer, 1, m_preset); + v.getSingle(1, 10, m_preset); // Impact MS + uc->writeSingle(BankNumber::EditBuffer, 2, m_preset); + v.getSingle(1, 3, m_preset); // Impact MS + uc->writeSingle(BankNumber::EditBuffer, 3, m_preset); +#elif 0 + uc->writeSingle(BankNumber::EditBuffer, 0, m_preset); // cmdline + uc->writeSingle(BankNumber::EditBuffer, 1, m_preset); + v.getSingle(1, 6, m_preset); // Anubis MS + uc->writeSingle(BankNumber::EditBuffer, 2, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 3, m_preset); + v.getSingle(10, 56, m_preset); // Impact MS + uc->writeSingle(BankNumber::EditBuffer, 4, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 5, m_preset); +#else + uc->writeSingle(BankNumber::EditBuffer, virusLib::SINGLE, m_preset); +#endif +/* uc->writeSingle(BankNumber::EditBuffer, 3, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 4, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 5, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 6, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 7, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 8, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 9, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 10, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 11, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 12, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 13, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 14, m_preset); + uc->writeSingle(BankNumber::EditBuffer, 15, m_preset); +*/ } + break; + case 512: + if(!m_demo) + { + LOG("Sending Note On"); +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 36, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 48, 0x5f)); // Note On + for(uint8_t i=0; i<1; ++i) + uc->sendMIDI(SMidiEvent(0x90 + i, 60, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 60, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 63, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 67, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 72, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 75, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0x90 + baseChannel, 79, 0x5f)); // Note On +// uc->sendMIDI(SMidiEvent(0xb0, 1, 0)); // Modwheel 0 + uc->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + } + break; +/* case 8000: + LOG("Sending 2nd Note On"); + uc->sendMIDI(SMidiEvent(0x90, 67, 0x7f)); // Note On + uc->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + break; + case 16000: + LOG("Sending 3rd Note On"); + uc->sendMIDI(SMidiEvent(0x90, 63, 0x7f)); // Note On + uc->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + break; +*/ + } +#if 0 + static uint8_t cycle = 0; + + static uint8_t channel = 0; + static int totalNoteCount = 1; + + if(audioCallbackCount >= 1024 && (audioCallbackCount & 2047) == 0) + { + static uint8_t note = 127; + if(note >= 96) + { + note = 24; + + switch(cycle) + { + case 0: note += 0; break; + case 1: note += 3; break; + case 2: note += 7; break; + case 3: note += 10; break; + case 4: note += 5; break; + case 5: note += 2; break; + } + ++cycle; + if(cycle == 6) + cycle = 0; + } + if(cycle < 7) + { + totalNoteCount++; + LOG("Sending Note On for note " << static_cast<int>(note) << ", total notes " << totalNoteCount); + uc->sendMIDI(SMidiEvent(0x90 + baseChannel + channel, note, 0x5f)); // Note On + channel++; + if(channel >= 6) +// if(channel >= 16) + channel = 0; + uc->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + +// if(totalNoteCount >= 40) +// dsp.enableTrace(static_cast<dsp56k::DSP::TraceMode>(dsp56k::DSP::Ops | dsp56k::DSP::Regs | dsp56k::DSP::StackIndent)); + } + note += 12; + } +#endif + if(m_demo && audioCallbackCount >= 256) + m_demo->process(1); +} + +void ConsoleApp::run(const std::string& _audioOutputFilename, uint32_t _maxSampleCount/* = 0*/, bool _createDebugger/* = false*/) +{ + assert(!_audioOutputFilename.empty()); +// dsp.enableTrace((DSP::TraceMode)(DSP::Ops | DSP::Regs | DSP::StackIndent)); + + constexpr uint32_t blockSize = 64; + + constexpr uint32_t notifyThreshold = ((blockSize<<1) - 4); + + uint32_t callbackCount = 0; + dsp56k::Semaphore sem(1); + + auto& esai = m_dsp1->getPeriphX().getEsai(); + int32_t notifyTimeout = 0; + + esai.setCallback([&](dsp56k::Audio*) + { + // Reduce thread contention by waiting until we have nearly enough audio output data available. + // The DSP thread needs to lock & unlock a mutex to inform the waiting thread (us) that data is + // available if the output ring buffer was completely drained. We can omit this by ensuring that + // the output buffer never becomes completely empty. + const auto sizeReached = esai.getAudioOutputs().size() >= notifyThreshold; + + --notifyTimeout; + +// LOG("Size " << esai.getAudioOutputs().size() << ", size reached " << (sizeReached ? "true" : "false") << ", notify " << (notify ? "true" : "false")); + if(notifyTimeout <= 0 && sizeReached) + { + notifyTimeout = static_cast<int>(notifyThreshold); + sem.notify(); + } + + callbackCount++; + if((callbackCount & 0x07) == 0) + audioCallback(callbackCount>>3); + }, 0); + + bootDSP(_createDebugger).join(); + + /* + const std::string romFile = m_romName; + auto& mem = m_dsp1->getMemory(); + + mem.saveAsText((romFile + "_X.txt").c_str(), dsp56k::MemArea_X, 0, mem.size()); + mem.saveAsText((romFile + "_Y.txt").c_str(), dsp56k::MemArea_Y, 0, mem.size()); + mem.save((romFile + "_P.bin").c_str(), dsp56k::MemArea_P); + mem.saveAssembly((romFile + "_P.asm").c_str(), 0, mem.size(), true, false, m_dsp1->getDSP().getPeriph(0), m_dsp1->getDSP().getPeriph(1)); + */ + + std::vector<synthLib::SMidiEvent> midiEvents; + + AudioProcessor proc(m_rom.getSamplerate(), _audioOutputFilename, m_demo != nullptr, _maxSampleCount, m_dsp1.get(), m_dsp2); + + while(!proc.finished()) + { + sem.wait(); + proc.processBlock(blockSize); + uc->processHdi08Tx(midiEvents); + midiEvents.clear(); + } + + esai.setCallback(nullptr,0); +} diff --git a/source/virusConsoleLib/consoleApp.h b/source/virusConsoleLib/consoleApp.h @@ -0,0 +1,51 @@ +#pragma once +#include <string> + +#include "esaiListener.h" +#include "esaiListenerToCallback.h" +#include "dsp56kEmu/memory.h" +#include "dsp56kEmu/dsp.h" + +#include "../virusLib/romfile.h" +#include "../virusLib/microcontroller.h" +#include "../virusLib/demoplayback.h" +#include "../virusLib/dspSingle.h" + +class ConsoleApp +{ +public: + ConsoleApp(const std::string& _romFile); + ~ConsoleApp(); + + bool isValid() const; + + void loadSingle(int b, int p); + bool loadSingle(const std::string& _preset); + + bool loadDemo(const std::string& _filename); + bool loadInternalDemo(); + + std::string getSingleName() const; + std::string getSingleNameAsFilename() const; + + static void waitReturn(); + + void run(const std::string& _audioOutputFilename, uint32_t _maxSampleCount = 0, bool _createDebugger = false); + + const virusLib::ROMFile& getRom() const { return m_rom; } + +private: + + std::thread bootDSP(bool _createDebugger) const; + dsp56k::IPeripherals& getYPeripherals() const; + void audioCallback(uint32_t audioCallbackCount); + + const std::string m_romName; + virusLib::ROMFile m_rom; + std::unique_ptr<virusLib::DspSingle> m_dsp1; + virusLib::DspSingle* m_dsp2 = nullptr; + std::unique_ptr<virusLib::Microcontroller> uc; + std::unique_ptr<virusLib::DemoPlayback> m_demo; + + virusLib::Microcontroller::TPreset m_preset; +}; diff --git a/source/virusConsoleLib/esaiListener.cpp b/source/virusConsoleLib/esaiListener.cpp @@ -0,0 +1,128 @@ +#include "esaiListener.h" + +#include "dsp56kEmu/esai.h" + +using namespace dsp56k; + +#ifdef _DEBUG +size_t g_writeBlockSize = 8192; +#else +size_t g_writeBlockSize = 65536; +#endif + +EsaiListener::EsaiListener(dsp56k::Esai& _esai, const uint8_t _outChannels, const uint8_t _inChannels, TCallback _callback) + : m_outChannels(_outChannels) + , m_inChannels(_inChannels) + , m_nextWriteSize(g_writeBlockSize) + , m_callback(std::move(_callback)) +{ + if (_outChannels & 32) ++m_outChannelCount; + if (_outChannels & 16) ++m_outChannelCount; + if (_outChannels & 8) ++m_outChannelCount; + if (_outChannels & 4) ++m_outChannelCount; + if (_outChannels & 2) ++m_outChannelCount; + if (_outChannels & 1) ++m_outChannelCount; + + _esai.setCallback([&](Audio* _audio) + { + onAudioCallback(_audio); + }, 4); + + _esai.writeEmptyAudioIn(4); +} + +void EsaiListener::setMaxSamplecount(uint32_t _max) +{ + m_maxSampleCount = _max; +} + +void EsaiListener::onAudioCallback(dsp56k::Audio* _audio) +{ + constexpr size_t sampleCount = 4; + constexpr size_t channelsIn = 8; + constexpr size_t channelsOut = 12; + + TWord inputData[channelsIn][sampleCount] = { {0,0,0,0}, {0,0,0,0},{0,0,0,0}, {0,0,0,0},{0,0,0,0}, {0,0,0,0},{0,0,0,0}, {0,0,0,0} }; + const TWord* audioIn[channelsIn] = { + (m_inChannels & 0x01) ? inputData[0] : nullptr, + (m_inChannels & 0x01) ? inputData[1] : nullptr, + (m_inChannels & 0x02) ? inputData[2] : nullptr, + (m_inChannels & 0x02) ? inputData[3] : nullptr, + (m_inChannels & 0x04) ? inputData[4] : nullptr, + (m_inChannels & 0x04) ? inputData[5] : nullptr, + (m_inChannels & 0x08) ? inputData[6] : nullptr, + (m_inChannels & 0x08) ? inputData[7] : nullptr, + }; + TWord outputData[channelsOut][sampleCount] = { {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0} }; + TWord* audioOut[channelsOut] = { + (m_outChannels & 0x01) ? outputData[0] : nullptr, + (m_outChannels & 0x01) ? outputData[1] : nullptr, + (m_outChannels & 0x02) ? outputData[2] : nullptr, + (m_outChannels & 0x02) ? outputData[3] : nullptr, + (m_outChannels & 0x04) ? outputData[4] : nullptr, + (m_outChannels & 0x04) ? outputData[5] : nullptr, + (m_outChannels & 0x08) ? outputData[6] : nullptr, + (m_outChannels & 0x08) ? outputData[7] : nullptr, + (m_outChannels & 0x10) ? outputData[8] : nullptr, + (m_outChannels & 0x10) ? outputData[9] : nullptr, + (m_outChannels & 0x20) ? outputData[10] : nullptr, + (m_outChannels & 0x20) ? outputData[11] : nullptr + }; + + m_counter++; + if ((m_counter & 0x1fff) == 0) + { + LOG("Deliver Audio"); + } + + _audio->processAudioInterleaved(audioIn, audioOut, sampleCount); + + if (limitReached()) + return; + + if (!m_audioData.capacity()) + { + for (int c = 0; c < channelsOut; ++c) + { + for (int i = 0; i < sampleCount; ++i) + { + if (audioOut[c] && audioOut[c][i]) + { + m_audioData.reserve(2048); + break; + } + } + } + + if(m_audioData.capacity()) + onBeginDeliverAudioData(); + } + + if (m_audioData.capacity()) + { + for (int i = 0; i < sampleCount; ++i) + { + for (int c = 0; c < channelsOut; ++c) + { + if(audioOut[c] && !limitReached()) + { + m_audioData.push_back(audioOut[c][i]); + ++m_processedSampleCount; + } + } + } + + if (m_audioData.size() >= m_nextWriteSize || limitReached()) + { + if (onDeliverAudioData(m_audioData)) + { + m_audioData.clear(); + m_nextWriteSize = g_writeBlockSize; + } + else + m_nextWriteSize += g_writeBlockSize; + } + } + + m_callback(this, m_counter); +} diff --git a/source/virusConsoleLib/esaiListener.h b/source/virusConsoleLib/esaiListener.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../synthLib/wavWriter.h" + +#include "dsp56kEmu/types.h" + +#include <cstdint> +#include <functional> +#include <vector> + +namespace dsp56k +{ + class Audio; + class Esai; +} + +class EsaiListener +{ +public: + using TCallback = std::function<void(EsaiListener*, uint32_t)>; + + EsaiListener(dsp56k::Esai& _esai, uint8_t _outChannels, uint8_t _inChannels, TCallback _callback); + virtual ~EsaiListener() = default; + + void setMaxSamplecount(uint32_t _max); + + bool limitReached() const + { + return m_maxSampleCount && m_processedSampleCount >= m_maxSampleCount; + } + + uint8_t getChannelCount() const { return m_outChannelCount; } + +private: + virtual bool onDeliverAudioData(const std::vector<dsp56k::TWord>& _audioData) = 0; + virtual void onBeginDeliverAudioData() {}; + + void onAudioCallback(dsp56k::Audio* _audio); + + const uint8_t m_outChannels; + const uint8_t m_inChannels; + uint8_t m_outChannelCount = 0; + + std::vector<dsp56k::TWord> m_audioData; + int m_counter = 0; + size_t m_nextWriteSize; + const TCallback m_callback; + uint32_t m_maxSampleCount = 0; + uint32_t m_processedSampleCount = 0; +}; diff --git a/source/virusConsoleLib/esaiListenerToCallback.cpp b/source/virusConsoleLib/esaiListenerToCallback.cpp @@ -0,0 +1,12 @@ +#include "esaiListenerToCallback.h" + +EsaiListenerToCallback::EsaiListenerToCallback(dsp56k::Esai& _esai, uint8_t _outChannels, uint8_t _inChannels, TCallback _countCallback, TDataCallback _dataCallback) +: EsaiListener(_esai, _outChannels, _inChannels, std::move(_countCallback)) +, m_callback(std::move(_dataCallback)) +{ +} + +bool EsaiListenerToCallback::onDeliverAudioData(const std::vector<dsp56k::TWord>& _audioData) +{ + return m_callback(_audioData); +} diff --git a/source/virusConsoleLib/esaiListenerToCallback.h b/source/virusConsoleLib/esaiListenerToCallback.h @@ -0,0 +1,15 @@ +#pragma once +#include "esaiListener.h" + +class EsaiListenerToCallback : public EsaiListener +{ +public: + using TDataCallback = std::function<bool(const std::vector<dsp56k::TWord>&)>; + + EsaiListenerToCallback(dsp56k::Esai& _esai, uint8_t _outChannels, uint8_t _inChannels, TCallback _countCallback, TDataCallback _dataCallback); + +private: + bool onDeliverAudioData(const std::vector<dsp56k::TWord>& _audioData) override; + + const TDataCallback m_callback; +}; diff --git a/source/virusConsoleLib/esaiListenerToFile.cpp b/source/virusConsoleLib/esaiListenerToFile.cpp @@ -0,0 +1,60 @@ +#include "esaiListenerToFile.h" + +#include "dsp56kEmu/logging.h" + +EsaiListenerToFile::EsaiListenerToFile(dsp56k::Esai& _esai, uint8_t _outChannels, uint8_t _inChannels, TCallback _callback, uint32_t _samplerate, std::string _audioFilename) + : EsaiListener(_esai, _outChannels, _inChannels, std::move(_callback)) + , m_samplerate(_samplerate) + , m_audioFilename(std::move(_audioFilename)) +{ + for(size_t i=0; i<m_audioFilenames.size(); ++i) + { + m_audioFilenames[i] = m_audioFilename; + if(i) + m_audioFilenames[i] = static_cast<char>('0' + i) + m_audioFilenames[i]; + + m_audioDatas[i].reserve(65536); + } +} + +bool EsaiListenerToFile::onDeliverAudioData(const std::vector<dsp56k::TWord>& _audioData) +{ + if (m_audioFilename.empty()) + return true; + + if(getChannelCount() == 1) + { + for(size_t s=0; s<_audioData.size(); ++s) + writeWord(0, _audioData[s]); + } + else + { + for(size_t s=0; s<_audioData.size();) + { + for(uint8_t c=0; c<getChannelCount(); ++c) + { + for(size_t i=0; i<2; ++i, ++s) + { + writeWord(c, _audioData[s]); + } + } + } + } + + for(size_t i=0; i<getChannelCount(); ++i) + { + if(m_writers[i].write(m_audioFilenames[i], 24, false, 2, m_samplerate, m_audioDatas[i])) + m_audioDatas[i].clear(); + } + return true; +} + +void EsaiListenerToFile::onBeginDeliverAudioData() +{ + LOG("Begin writing audio to file " << m_audioFilename); +} + +void EsaiListenerToFile::writeWord(const uint8_t _channel, const dsp56k::TWord _word) +{ + synthLib::WavWriter::writeWord(m_audioDatas[_channel], _word); +} diff --git a/source/virusConsoleLib/esaiListenerToFile.h b/source/virusConsoleLib/esaiListenerToFile.h @@ -0,0 +1,23 @@ +#pragma once +#include <array> + +#include "esaiListener.h" + +class EsaiListenerToFile : public EsaiListener +{ +public: + EsaiListenerToFile(dsp56k::Esai& _esai, uint8_t _outChannels, uint8_t _inChannels, TCallback _callback, uint32_t _samplerate, std::string _audioFilename); + +private: + bool onDeliverAudioData(const std::vector<dsp56k::TWord>& _audioData) override; + void onBeginDeliverAudioData() override; + + void writeWord(uint8_t _channel, dsp56k::TWord _word); + + std::array<synthLib::WavWriter,3> m_writers; + const uint32_t m_samplerate; + const std::string m_audioFilename; + std::vector<uint8_t> m_audioData; + std::array<std::string,3> m_audioFilenames; + std::array<std::vector<uint8_t>,3> m_audioDatas; +}; diff --git a/source/virusIntegrationTest/CMakeLists.txt b/source/virusIntegrationTest/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.10) + +project(virusIntegrationTest) + +add_executable(virusIntegrationTest) + +set(SOURCES + integrationTest.cpp integrationTest.h + ../dsp56300/source/disassemble/commandline.cpp + ../dsp56300/source/disassemble/commandline.h +) + +target_sources(virusIntegrationTest PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(virusIntegrationTest PUBLIC virusConsoleLib) diff --git a/source/virusIntegrationTest/integrationTest.cpp b/source/virusIntegrationTest/integrationTest.cpp @@ -0,0 +1,316 @@ +#include <iostream> + +#include "integrationTest.h" + +#include <fstream> +#include <utility> + +#include "../virusConsoleLib/consoleApp.h" + +#include "../dsp56300/source/dsp56kEmu/jitunittests.h" +#include "../dsp56300/source/disassemble/commandline.h" + +#include "../synthLib/wavReader.h" +#include "../synthLib/os.h" + +namespace synthLib +{ + class WavReader; +} + +int main(int _argc, char* _argv[]) +{ + if constexpr (true) + { + try + { + puts("Running Unit Tests..."); +// dsp56k::InterpreterUnitTests tests; + dsp56k::JitUnittests jitTests(false); + puts("Unit Tests finished."); + } + catch (const std::string& _err) + { + std::cout << "Unit test failed: " << _err << std::endl; + return -1; + } + } + + try + { + const CommandLine cmd(_argc, _argv); + + if(cmd.contains("rom") && cmd.contains("preset")) + { + const auto romFile = cmd.get("rom"); + const auto preset = cmd.get("preset"); + + IntegrationTest test(cmd, romFile, preset, std::string()); + return test.run(); + } + if(cmd.contains("folder")) + { + std::vector<std::string> subfolders; + synthLib::getDirectoryEntries(subfolders, cmd.get("folder")); + + if(subfolders.empty()) + { + std::cout << "Nothing found for testing in folder " << cmd.get("folder") << std::endl; + return -1; + } + + for (auto& subfolder : subfolders) + { + if(subfolder.find("/.") != std::string::npos) + continue; + + std::vector<std::string> files; + synthLib::getDirectoryEntries(files, subfolder); + + std::string romFile; + std::string presetsFile; + + if(files.empty()) + { + std::cout << "Directory " << subfolder << " doesn't contain any files" << std::endl; + return -1; + } + + for (auto& file : files) + { + if(synthLib::hasExtension(file, ".txt")) + presetsFile = file; + if(synthLib::hasExtension(file, ".bin")) + romFile = file; + } + + if(romFile.empty()) + { + std::cout << "Failed to find ROM in folder " << subfolder << std::endl; + return -1; + } + if(presetsFile.empty()) + { + std::cout << "Failed to find presets file in folder " << subfolder << std::endl; + return -1; + } + + if(romFile.find("firmware") != std::string::npos) + { + auto* hFile = fopen(romFile.c_str(), "rb"); + size_t size = 0; + if(hFile) + { + fseek(hFile, 0, SEEK_END); + size = ftell(hFile); + fclose(hFile); + } + if(size > virusLib::ROMFile::getRomSizeModelABC()) + { + std::cout << "Ignoring TI verification tests, TI is not supported" << std::endl; + continue; + } + } + + std::vector<std::string> presets; + + std::ifstream ss; + ss.open(presetsFile.c_str(), std::ios::in); + + if(!ss.is_open()) + { + std::cout << "Failed to open presets file " << presetsFile << std::endl; + return -1; + } + + std::string line; + + while(std::getline(ss, line)) + { + while(!line.empty() && line.find_last_of("\r\n") != std::string::npos) + line = line.substr(0, line.size()-1); + if(!line.empty()) + presets.push_back(line); + } + + ss.close(); + + if(presets.empty()) + { + std::cout << "Presets file " << presetsFile << " is empty" << std::endl; + return -1; + } + + for (auto& preset : presets) + { + IntegrationTest test(cmd, romFile, preset, subfolder + '/'); + if(test.run() != 0) + return -1; + } + } + + return 0; + } + + std::cout << "invalid command line arguments" << std::endl; + return -1; + } + catch(const std::runtime_error& _err) + { + std::cout << _err.what() << std::endl; + return -1; + } +} + +IntegrationTest::IntegrationTest(const CommandLine& _commandLine, std::string _romFile, std::string _presetName, std::string _outputFolder) + : m_cmd(_commandLine) + , m_romFile(std::move(_romFile)) + , m_presetName(std::move(_presetName)) + , m_outputFolder(std::move(_outputFolder)) + , m_app(m_romFile) +{ +} + +int IntegrationTest::run() +{ + if (!m_app.isValid()) + { + std::cout << "Failed to load ROM " << m_romFile << ", make sure that the ROM file is valid" << std::endl; + return -1; + } + + if (!m_app.loadSingle(m_presetName)) + { + std::cout << "Failed to find preset '" << m_presetName << "', make sure to use a ROM that contains it" << std::endl; + return -1; + } + + const int lengthSeconds = m_cmd.contains("length") ? m_cmd.getInt("length") : 0; + if(lengthSeconds > 0) + { + // create reference file + return runCreate(lengthSeconds); + } + + const auto referenceFile = m_app.getSingleNameAsFilename(); + + if (!loadAudioFile(m_referenceFile, m_outputFolder + referenceFile)) + return -1; + + return runCompare(); +} + +bool IntegrationTest::loadAudioFile(File& _dst, const std::string& _filename) const +{ + const auto hFile = fopen(_filename.c_str(), "rb"); + if (!hFile) + { + std::cout << "Failed to load wav file " << _filename << " for comparison" << std::endl; + return false; + } + fseek(hFile, 0, SEEK_END); + const size_t size = ftell(hFile); + _dst.file.resize(size); + fseek(hFile, 0, SEEK_SET); + if (fread(&_dst.file.front(), 1, size, hFile) != size) + { + std::cout << "Failed to read data from file " << _filename << std::endl; + fclose(hFile); + return false; + } + fclose(hFile); + + _dst.data.data = nullptr; + + if (!synthLib::WavReader::load(_dst.data, nullptr, &_dst.file.front(), _dst.file.size())) + { + std::cout << "Failed to interpret file " << _filename << " as wave data, make sure that the file is a valid 24 bit stereo wav file" << std::endl; + return false; + } + + if(_dst.data.samplerate != m_app.getRom().getSamplerate()) + { + std::cout << "Wave file " << _filename << " does not have the correct samplerate, expected " << m_app.getRom().getSamplerate() << " but got " << _dst.data.samplerate << " instead" << std::endl; + return false; + } + + if (_dst.data.bitsPerSample != 24 || _dst.data.channels != 2 || _dst.data.isFloat) + { + std::cout << "Wave file " << _filename << " has an invalid format, expected 24 bit / 2 channels but got " << _dst.data.bitsPerSample << " bit / " << _dst.data.channels << " channels" << std::endl; + return false; + } + return true; +} + +int IntegrationTest::runCompare() +{ + const auto sampleCount = m_referenceFile.data.dataByteSize * 8 / m_referenceFile.data.bitsPerSample / 2; + + std::vector<uint8_t> temp; + + File compareFile; + const auto res = createAudioFile(compareFile, "compare_", static_cast<uint32_t>(sampleCount)); + if(res) + return res; + + auto* ptrA = static_cast<const uint8_t*>(compareFile.data.data); + auto* ptrB = static_cast<const uint8_t*>(m_referenceFile.data.data); + + for(uint32_t i=0; i<sampleCount; ++i) + { + const uint32_t a = (static_cast<uint32_t>(ptrA[0]) << 24) || (static_cast<uint32_t>(ptrA[1]) << 16) || ptrA[2]; + const uint32_t b = (static_cast<uint32_t>(ptrB[0]) << 24) || (static_cast<uint32_t>(ptrB[1]) << 16) || ptrB[2]; + + if(b != a) + { + std::cout << "Test failed, audio output is not identical to reference file, difference starting at frame " << (i>>1) << std::endl; + return -2; + } + + ptrA += 3; + ptrB += 3; + } + + std::cout << "Test succeeded, compared " << sampleCount << " samples" << std::endl; + return 0; +} + +int IntegrationTest::runCreate(const int _lengthSeconds) +{ + const auto sampleCount = m_app.getRom().getSamplerate() * _lengthSeconds * 2; + + File file; + return createAudioFile(file, "", sampleCount); +} + +int IntegrationTest::createAudioFile(File& _dst, const std::string& _prefix, const uint32_t _sampleCount) +{ + const auto filename = m_outputFolder + _prefix + m_app.getSingleNameAsFilename(); + + auto* hFile = fopen(filename.c_str(), "wb"); + + if(!hFile) + { + std::cout << "Failed to create output file " << filename << std::endl; + return -1; + } + + fclose(hFile); + + m_app.run(filename, _sampleCount); + + if(!loadAudioFile(_dst, filename)) + { + std::cout << "Failed to open written file " << filename << " for verification" << std::endl; + return -1; + } + + const auto sampleCount = _dst.data.dataByteSize * 8 / _dst.data.bitsPerSample / 2; + + if(sampleCount != _sampleCount) + { + std::cout << "Verification of written file failed, expected " << _sampleCount << " samples but file only has " << sampleCount << " samples" << std::endl; + return -1; + } + return 0; +} diff --git a/source/virusIntegrationTest/integrationTest.h b/source/virusIntegrationTest/integrationTest.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../virusConsoleLib/consoleApp.h" + +#include "../synthLib/wavReader.h" + +class CommandLine; + +class IntegrationTest +{ +public: + explicit IntegrationTest(const CommandLine& _commandLine, std::string _romFile, std::string _presetName, std::string _outputFolder); + + int run(); + +private: + struct File + { + std::vector<uint8_t> file; + synthLib::Data data; + }; + + bool loadAudioFile(File& _dst, const std::string& _filename) const; + int runCompare(); + int runCreate(int _lengthSeconds); + int createAudioFile(File& _dst, const std::string& _prefix, uint32_t _sampleCount); + + const CommandLine& m_cmd; + const std::string m_romFile; + const std::string m_presetName; + const std::string m_outputFolder; + ConsoleApp m_app; + + File m_referenceFile; +}; diff --git a/source/virusLib/CMakeLists.txt b/source/virusLib/CMakeLists.txt @@ -7,13 +7,19 @@ set(SOURCES demopacketvalidator.cpp demopacketvalidator.h demoplayback.cpp demoplayback.h device.cpp device.h - midiOutParser.cpp midiOutParser.h + dspSingle.cpp dspSingle.h + hdi08TxParser.cpp hdi08TxParser.h romfile.cpp romfile.h microcontroller.cpp microcontroller.h microcontrollerTypes.cpp microcontrollerTypes.h + utils.h ) target_sources(virusLib PRIVATE ${SOURCES}) source_group("source" FILES ${SOURCES}) target_link_libraries(virusLib PUBLIC synthLib) + +if(DSP56300_DEBUGGER) + target_link_libraries(virusLib PUBLIC dsp56kDebugger) +endif() diff --git a/source/virusLib/demoplayback.cpp b/source/virusLib/demoplayback.cpp @@ -20,7 +20,7 @@ namespace virusLib constexpr auto g_timeScale_C = 57; // C OS 6.6 constexpr auto g_timeScale_A = 54; // A OS 2.8 - bool DemoPlayback::loadMidi(const std::string& _filename) + bool DemoPlayback::loadFile(const std::string& _filename) { if(synthLib::hasExtension(_filename, ".bin")) { @@ -172,7 +172,7 @@ namespace virusLib return e; } - DemoPlayback::Event DemoPlayback::parseSysex(const uint8_t* _data, const uint32_t _count) + DemoPlayback::Event DemoPlayback::parseSysex(const uint8_t* _data, const uint32_t _count) const { Event e; @@ -182,6 +182,62 @@ namespace virusLib // Only seen for single and multi patches for now e.data.resize(_count); memcpy(&e.data.front(), _data, _count); + +#if 0 // demo presets extraction + if(_count - 6 >= ROMFile::getSinglePresetSize()) + { + int foo=0; + ROMFile::TPreset data; + memcpy(&data[0], &e.data[6], ROMFile::getSinglePresetSize()); + + const auto isMulti = _data[3] == 0x11; + const uint8_t program = _data[4]; + + const auto name = isMulti ? ROMFile::getMultiName(data) : ROMFile::getSingleName(data); + + std::vector<synthLib::SMidiEvent> responses; + + // use the uc to generate our sysex header + if(isMulti) + { + m_mc.sendSysex({0xf0, 0x00, 0x20, 0x33, 0x01, OMNI_DEVICE_ID, 0x31, 0x01, 0x00, 0xf7}, responses, synthLib::MidiEventSourceEditor); + } + else + { + m_mc.sendSysex({0xf0, 0x00, 0x20, 0x33, 0x01, OMNI_DEVICE_ID, 0x30, 0x01, program, 0xf7}, responses, synthLib::MidiEventSourceEditor); + } + + auto& s = responses.front().sysex; + memcpy(&s[9], &data[0], data.size()); + + // checksum needs to be updated + s.pop_back(); + Microcontroller::calcChecksum(s, 5); + s.push_back(0xf7); + + std::stringstream ss; + ss << "demo_preset_" << (isMulti ? "multi" : "single") << '_' << std::setfill('0') << std::setw(2) << std::to_string(program) << '_' << name << ".syx"; + + auto filename = ss.str(); + for(auto& f : filename) + { + switch (f) + { + case '?': + case '@': + case ';': + case ':': + case '/': + case '\\': + f = '_'; + break; + } + } + FILE* hFile = fopen(filename.c_str(), "wb"); + fwrite(&s.front(), 1, s.size(), hFile); + fclose(hFile); + } +#endif e.type = EventType::RawSerial; } else @@ -196,6 +252,9 @@ namespace virusLib void DemoPlayback::process(const uint32_t _samples) { + if(m_stop) + return; + if(m_currentEvent == 0 && m_remainingDelay == 0) { // switch to multi mode when playback starts @@ -206,20 +265,53 @@ namespace virusLib return; } - if(m_currentEvent >= m_events.size()) + if(m_currentEvent >= getEventCount()) return; - m_remainingDelay -= _samples; - while(m_remainingDelay <= 0 && m_currentEvent < m_events.size()) + m_remainingDelay -= static_cast<int32_t>(_samples); + + while(m_remainingDelay <= 0) { - const auto& e = m_events[m_currentEvent]; - if(!processEvent(e)) + if(!processEvent(m_currentEvent)) return; + + m_remainingDelay = static_cast<int32_t>(static_cast<float>(getEventDelay(m_currentEvent)) * m_timeScale); + ++m_currentEvent; - m_remainingDelay = e.delay * m_timeScale; + + if(m_currentEvent >= getEventCount()) + { + stop(); + break; + } } } + void DemoPlayback::writeRawData(const std::vector<uint8_t>& _data) const + { + std::vector<dsp56k::TWord> dspWords; + + for(size_t i=0; i<_data.size(); i += 3) + { + dsp56k::TWord d = static_cast<dsp56k::TWord>(_data[i]) << 16; + if(i+1 < _data.size()) + d |= static_cast<dsp56k::TWord>(_data[i+1]) << 8; + if(i+2 < _data.size()) + d |= static_cast<dsp56k::TWord>(_data[i+2]); + dspWords.push_back(d); + } + + m_mc.writeHostBitsWithWait(0,1); + m_mc.m_hdi08.writeRX(dspWords); + } + + void DemoPlayback::stop() + { + m_stop = true; + LOG("Demo Playback end reached"); + std::cout << "Demo song has ended." << std::endl; + } + bool DemoPlayback::processEvent(const Event& _event) const { switch (_event.type) @@ -227,7 +319,7 @@ namespace virusLib case EventType::MidiSysex: { std::vector<synthLib::SMidiEvent> responses; - m_mc.sendSysex(_event.data, false, responses, synthLib::MidiEventSourcePlugin); + m_mc.sendSysex(_event.data, responses, synthLib::MidiEventSourcePlugin); } break; case EventType::Midi: @@ -245,25 +337,7 @@ namespace virusLib } break; case EventType::RawSerial: - { - if(m_mc.needsToWaitForHostBits(0,1)) - return false; - - std::vector<dsp56k::TWord> dspWords; - - for(size_t i=0; i<_event.data.size(); i += 3) - { - dsp56k::TWord d = static_cast<dsp56k::TWord>(_event.data[i]) << 16; - if(i+1 < _event.data.size()) - d |= static_cast<dsp56k::TWord>(_event.data[i+1]) << 8; - if(i+2 < _event.data.size()) - d |= static_cast<dsp56k::TWord>(_event.data[i+2]); - dspWords.push_back(d); - } - - m_mc.writeHostBitsWithWait(0,1); - m_mc.m_hdi08.writeRX(dspWords); - } + writeRawData(_event.data); break; } return true; diff --git a/source/virusLib/demoplayback.h b/source/virusLib/demoplayback.h @@ -11,12 +11,18 @@ namespace virusLib { public: DemoPlayback(Microcontroller& _mc) : m_mc(_mc) {} + virtual ~DemoPlayback() = default; - bool loadMidi(const std::string& _filename); - bool loadBinData(const std::vector<uint8_t>& _data); + virtual bool loadFile(const std::string& _filename); + virtual bool loadBinData(const std::vector<uint8_t>& _data); + + virtual void process(uint32_t _samples); + + protected: + void writeRawData(const std::vector<uint8_t>& _data) const; + void setTimeScale(const float _timeScale) { m_timeScale = _timeScale; } + void stop(); - void process(uint32_t _samples); - private: enum class EventType { @@ -32,19 +38,26 @@ namespace virusLib uint8_t delay = 0; }; - static Event parseSysex(const uint8_t* _data, uint32_t _count); + Event parseSysex(const uint8_t* _data, uint32_t _count) const; Event parseMidi(const uint8_t* _data); bool parseData(const std::vector<uint8_t>& _data); bool processEvent(const Event& _event) const; + virtual size_t getEventCount() const { return m_events.size(); } + virtual uint32_t getEventDelay(const size_t _index) const { return m_events[_index].delay; } + virtual bool processEvent(const size_t _index) { return processEvent(m_events[_index]); } + + protected: Microcontroller& m_mc; + private: std::vector<Event> m_events; int32_t m_remainingDelay = 0; uint32_t m_currentEvent = 0; - uint32_t m_timeScale = 54; + float m_timeScale = 54.0f; + bool m_stop = false; }; } diff --git a/source/virusLib/device.cpp b/source/virusLib/device.cpp @@ -1,33 +1,56 @@ #include "device.h" + +#include "dspSingle.h" #include "romfile.h" namespace virusLib { - // 128k words beginning at $20000 - constexpr dsp56k::TWord g_externalMemStart = 0x020000; - constexpr dsp56k::TWord g_memorySize = 0x040000; - - Device::Device(const std::string& _romFileName) - : synthLib::Device(g_memorySize, g_externalMemStart) - , m_rom(_romFileName) - , m_syx(getHDI08(), m_rom) + Device::Device(const ROMFile& _rom, const bool _createDebugger/* = false*/) + : synthLib::Device() + , m_rom(_rom) { if(!m_rom.isValid()) return; - auto loader = m_rom.bootDSP(getDSP(), getPeriph()); + DspSingle* dsp1; + createDspInstances(dsp1, m_dsp2, m_rom); + m_dsp.reset(dsp1); + + m_dsp->getPeriphX().getEsai().setCallback([this](dsp56k::Audio*) + { + onAudioWritten(); + }, 0); + + m_mc.reset(new Microcontroller(m_dsp->getHDI08(), _rom)); + + if(m_dsp2) + m_mc->addHDI08(m_dsp2->getHDI08()); - startDSPThread(); + auto loader = bootDSP(*m_dsp, m_rom, _createDebugger); + + if(m_dsp2) + { + auto loader2 = bootDSP(*m_dsp2, m_rom, false); + loader2.join(); + } loader.join(); - dummyProcess(8); + while(!m_mc->dspHasBooted()) + dummyProcess(8); - m_syx.sendInitControlCommands(); + m_mc->sendInitControlCommands(); dummyProcess(8); - m_syx.createDefaultState(); + m_mc->createDefaultState(); + } + + Device::~Device() + { + m_dsp->getPeriphX().getEsai().setCallback(nullptr,0); + m_mc.reset(); + m_dsp.reset(); } float Device::getSamplerate() const @@ -40,22 +63,21 @@ namespace virusLib return m_rom.isValid(); } - void Device::process(const float** _inputs, float** _outputs, size_t _size, const std::vector<synthLib::SMidiEvent>& _midiIn, std::vector<synthLib::SMidiEvent>& _midiOut) + 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) { synthLib::Device::process(_inputs, _outputs, _size, _midiIn, _midiOut); - m_syx.process(_size); m_numSamplesProcessed += static_cast<uint32_t>(_size); } - bool Device::getState(std::vector<uint8_t>& _state, synthLib::StateType _type) + bool Device::getState(std::vector<uint8_t>& _state, const synthLib::StateType _type) { - return m_syx.getState(_state, _type); + return m_mc->getState(_state, _type); } bool Device::setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) { - return m_syx.setState(_state, _type); + return m_mc->setState(_state, _type); } uint32_t Device::getInternalLatencyMidiToOutput() const @@ -71,6 +93,26 @@ namespace virusLib return 384; } + uint32_t Device::getChannelCountIn() + { + return 2; + } + + uint32_t Device::getChannelCountOut() + { + return 6; + } + + void Device::createDspInstances(DspSingle*& _dspA, DspSingle*& _dspB, const ROMFile& _rom) + { + _dspA = new DspSingle(0x040000, false); + + configureDSP(*_dspA, _rom); + + if(_dspB) + configureDSP(*_dspB, _rom); + } + bool Device::sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) { if(_ev.sysex.empty()) @@ -78,12 +120,12 @@ namespace virusLib // LOG("MIDI: " << std::hex << (int)_ev.a << " " << (int)_ev.b << " " << (int)_ev.c); auto ev = _ev; ev.offset += m_numSamplesProcessed + getExtraLatencySamples(); - return m_syx.sendMIDI(ev, true); + return m_mc->sendMIDI(ev); } std::vector<synthLib::SMidiEvent> responses; - if(!m_syx.sendSysex(_ev.sysex, true, responses, _ev.source)) + if(!m_mc->sendSysex(_ev.sysex, responses, _ev.source)) return false; for (const auto& response : responses) @@ -94,21 +136,63 @@ namespace virusLib void Device::readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) { - while(getHDI08().hasTX()) + m_mc->processHdi08Tx(_midiOut); + } + + void Device::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) + { + constexpr auto maxBlockSize = dsp56k::Audio::RingBufferSize>>2; + + auto inputs(_inputs); + auto outputs(_outputs); + + while(_samples > maxBlockSize) { - if(m_midiOutParser.append(getHDI08().readTX())) + m_dsp->processAudio(inputs, outputs, maxBlockSize, getExtraLatencySamples()); + + _samples -= maxBlockSize; + + for (auto& input : inputs) + { + if(input) + input += maxBlockSize; + } + + for (auto& output : outputs) { - const auto midi = m_midiOutParser.getMidiData(); - _midiOut.insert(_midiOut.end(), midi.begin(), midi.end()); - m_midiOutParser.clearMidiData(); + if(output) + output += maxBlockSize; } } + + m_dsp->processAudio(inputs, outputs, _samples, getExtraLatencySamples()); } void Device::onAudioWritten() { + m_mc->process(1); + m_numSamplesWritten += 1; - m_syx.sendPendingMidiEvents(m_numSamplesWritten >> 1); + m_mc->sendPendingMidiEvents(m_numSamplesWritten >> 1); + } + + void Device::configureDSP(DspSingle& _dsp, const ROMFile& _rom) + { + auto& jit = _dsp.getJIT(); + auto conf = jit.getConfig(); + + conf.aguSupportBitreverse = false; + conf.aguSupportMultipleWrapModulo = false; + conf.dynamicPeripheralAddressing = false; + + jit.setConfig(conf); + } + + std::thread Device::bootDSP(DspSingle& _dsp, const ROMFile& _rom, const bool _createDebugger) + { + auto res = _rom.bootDSP(_dsp.getDSP(), _dsp.getPeriphX()); + _dsp.startDSPThread(_createDebugger); + return res; } } diff --git a/source/virusLib/device.h b/source/virusLib/device.h @@ -1,9 +1,9 @@ #pragma once +#include "dspSingle.h" #include "../synthLib/midiTypes.h" #include "../synthLib/device.h" -#include "midiOutParser.h" #include "romfile.h" #include "microcontroller.h" @@ -12,12 +12,13 @@ namespace virusLib class Device final : public synthLib::Device { public: - Device(const std::string& _romFileName); + Device(const ROMFile& _rom, bool _createDebugger = false); + ~Device() override; float getSamplerate() const override; bool isValid() const override; - void process(const float** _inputs, float** _outputs, size_t _size, const std::vector<synthLib::SMidiEvent>& _midiIn, std::vector<synthLib::SMidiEvent>& _midiOut) override; + void process(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _size, const std::vector<synthLib::SMidiEvent>& _midiIn, std::vector<synthLib::SMidiEvent>& _midiOut) override; bool getState(std::vector<uint8_t>& _state, synthLib::StateType _type) override; bool setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) override; @@ -25,14 +26,25 @@ namespace virusLib uint32_t getInternalLatencyMidiToOutput() const override; uint32_t getInternalLatencyInputToOutput() const override; + uint32_t getChannelCountIn() override; + uint32_t getChannelCountOut() override; + + static void createDspInstances(DspSingle*& _dspA, DspSingle*& _dspB, const ROMFile& _rom); + static std::thread bootDSP(DspSingle& _dsp, const ROMFile& _rom, bool _createDebugger); + private: bool sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) override; void readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) override; - void onAudioWritten() override; + void processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) override; + void onAudioWritten(); + static void configureDSP(DspSingle& _dsp, const ROMFile& _rom); + + const ROMFile& m_rom; + + std::unique_ptr<DspSingle> m_dsp; + DspSingle* m_dsp2 = nullptr; + std::unique_ptr<Microcontroller> m_mc; - ROMFile m_rom; - Microcontroller m_syx; - MidiOutParser m_midiOutParser; uint32_t m_numSamplesWritten = 0; uint32_t m_numSamplesProcessed = 0; }; diff --git a/source/virusLib/dspSingle.cpp b/source/virusLib/dspSingle.cpp @@ -0,0 +1,79 @@ +#include "dspSingle.h" + +#if DSP56300_DEBUGGER +#include "dsp56kDebugger/debugger.h" +#endif + +namespace virusLib +{ + constexpr dsp56k::TWord g_externalMemStart = 0x020000; + + DspSingle::DspSingle(uint32_t _memorySize, bool _use56367Peripherals/* = false*/, const char* _name/* = nullptr*/) : m_name(_name ? _name : std::string()), m_periphX(&m_periphY) + { + const size_t requiredMemSize = + dsp56k::alignedSize<dsp56k::DSP>() + + dsp56k::alignedSize<dsp56k::Memory>() + + dsp56k::Memory::calcMemSize(_memorySize, g_externalMemStart) * sizeof(uint32_t); + + m_buffer.resize(dsp56k::alignedSize(requiredMemSize)); + + auto* buf = &m_buffer[0]; + buf = dsp56k::alignedAddress(buf); + + auto* bufDSPClass = buf; + auto* bufMemClass = bufDSPClass + dsp56k::alignedSize<dsp56k::DSP>(); + auto* bufMemSpace = bufMemClass + dsp56k::alignedSize<dsp56k::Memory>(); + + m_memory = new (bufMemClass)dsp56k::Memory(m_memoryValidator, _memorySize, _memorySize, g_externalMemStart, reinterpret_cast<dsp56k::TWord*>(bufMemSpace)); + + dsp56k::IPeripherals* periphY = &m_periphNop; + + if (_use56367Peripherals) + periphY = &m_periphY; + + m_dsp = new (buf)dsp56k::DSP(*m_memory, &m_periphX, periphY); + m_jit = &m_dsp->getJit(); + } + + DspSingle::~DspSingle() + { + m_dspThread.reset(); + + if(m_dsp) + { + m_dsp->~DSP(); + m_memory->~Memory(); + + m_dsp = nullptr; + m_memory = nullptr; + } + } + + void DspSingle::startDSPThread(const bool _createDebugger) + { +#if DSP56300_DEBUGGER + const auto debugger = _createDebugger ? std::make_shared<dsp56kDebugger::Debugger>(*m_dsp) : std::shared_ptr<dsp56kDebugger::Debugger>(); +#else + const auto debugger = std::shared_ptr<dsp56k::DebuggerInterface>(); +#endif + + m_dspThread.reset(new dsp56k::DSPThread(*m_dsp, m_name.empty() ? nullptr : m_name.c_str(), debugger)); + } + + template<typename T> void processAudio(DspSingle& _dsp, const synthLib::TAudioInputsT<T>& _inputs, const synthLib::TAudioOutputsT<T>& _outputs, const size_t _samples, uint32_t _latency) + { + const T* inputs[] = {_inputs[1], _inputs[0], nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; + T* outputs[] = {_outputs[1], _outputs[0], _outputs[3], _outputs[2], _outputs[5], _outputs[4], nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; + + _dsp.getPeriphX().getEsai().processAudioInterleaved(inputs, outputs, static_cast<uint32_t>(_samples), _latency); + } + void DspSingle::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, const size_t _samples, uint32_t _latency) + { + virusLib::processAudio(*this, _inputs, _outputs, _samples, _latency); + } + + void DspSingle::processAudio(const synthLib::TAudioInputsInt& _inputs, const synthLib::TAudioOutputsInt& _outputs, const size_t _samples, uint32_t _latency) + { + virusLib::processAudio(*this, _inputs, _outputs, _samples, _latency); + } +} diff --git a/source/virusLib/dspSingle.h b/source/virusLib/dspSingle.h @@ -0,0 +1,43 @@ +#pragma once + +#include "dsp56kEmu/dspthread.h" + +#include "../synthLib/audioTypes.h" + +namespace virusLib +{ + class DspSingle + { + public: + DspSingle(uint32_t _memorySize, bool _use56367Peripherals = false, const char* _name = nullptr); + virtual ~DspSingle(); + + dsp56k::HDI08& getHDI08() { return m_periphX.getHDI08(); } + dsp56k::Peripherals56362& getPeriphX() { return m_periphX; } + dsp56k::Peripherals56367& getPeriphY() { return m_periphY; } + dsp56k::PeripheralsNop& getPeriphNop() { return m_periphNop; } + dsp56k::DSP& getDSP() const { return *m_dsp; } + dsp56k::Jit& getJIT() const { return *m_jit; } + dsp56k::Memory& getMemory() const {return *m_memory; } + + void startDSPThread(bool _createDebugger); + + virtual void processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples, uint32_t _latency); + virtual void processAudio(const synthLib::TAudioInputsInt& _inputs, const synthLib::TAudioOutputsInt& _outputs, size_t _samples, uint32_t _latency); + + private: + const std::string m_name; + std::vector<uint8_t> m_buffer; + + dsp56k::DefaultMemoryValidator m_memoryValidator; + dsp56k::Peripherals56367 m_periphY; + dsp56k::Peripherals56362 m_periphX; + dsp56k::PeripheralsNop m_periphNop; + + dsp56k::Memory* m_memory = nullptr; + dsp56k::DSP* m_dsp = nullptr; + dsp56k::Jit* m_jit = nullptr; + + std::unique_ptr<dsp56k::DSPThread> m_dspThread; + }; +} diff --git a/source/virusLib/hdi08TxParser.cpp b/source/virusLib/hdi08TxParser.cpp @@ -0,0 +1,192 @@ +#include "hdi08TxParser.h" + +#include <chrono> +#include <sstream> +#include <thread> + +#include "microcontroller.h" +#include "romfile.h" + +#include "../dsp56300/source/dsp56kEmu/logging.h" + +namespace virusLib +{ + const std::vector<dsp56k::TWord> g_knownPatterns[] = + { + {0xf40000, 0x7f0000, 0x000000, 0xf70000} // sent after DSP has booted + }; + + bool Hdi08TxParser::append(const dsp56k::TWord _data) + { +// LOG("HDI08 TX: " << HEX(_data)); + + const auto byte = static_cast<uint8_t>(_data >> 16); + + switch (m_state) + { + case State::Default: + if(_data == 0xf4f4f4) + { + m_remainingPresetBytes = 0; + m_state = State::Default; + LOG("Finished receiving preset, no upgrade needed"); + } + else if(_data == 0xf50000) + { + m_state = State::StatusReport; + m_remainingStatusBytes = m_mc.getROM().getModel() == ROMFile::Model::ABC ? 1 : 2; + } + else if(_data == 0xf400f4) + { + m_state = State::Preset; + LOG("Begin receiving upgraded preset"); + + if(m_remainingPresetBytes == 0) + { + m_remainingPresetBytes = std::numeric_limits<uint32_t>::max(); + LOG("No one requested a preset upgrade, assuming preset size based on first word (version number)"); + } + } + else if((_data & 0xff0000) == 0xf00000) + { + LOG("Begin reading sysex"); + m_state = State::Sysex; + m_sysexData.push_back(byte); + } + else + { + size_t i=0; + bool matched = false; + + m_nonPatternWords.emplace_back(_data); + + for (const auto& pattern : g_knownPatterns) + { + auto& pos = m_patternPositions[i]; + + if(pattern[pos] == _data) + { + matched = true; + + ++pos; + if(pos == std::size(pattern)) + { +// LOG("Matched pattern " << i); + const auto p = static_cast<PatternType>(i); + + switch (p) + { + case PatternType::DspBoot: + m_dspHasBooted = true; + LOG("DSP boot completed"); + break; + default: + m_matchedPatterns.push_back(p); + break; + } + + pos = 0; + m_nonPatternWords.clear(); + } + } + else + { + pos = 0; + } + + ++i; + } + + if(!matched) + { + std::stringstream s; + for (const auto& w : m_nonPatternWords) + s << HEX(w) << ' '; + LOG("Unknown DSP words: " << s.str()); + m_nonPatternWords.clear(); + } + } + break; + case State::Sysex: + { + if(_data & 0xffff) + { + LOG("Abort reading sysex, received invalid midi byte " << HEX(_data)); + m_state = State::Default; + m_midiData.clear(); + return append(_data); + } + + m_sysexData.push_back(byte); + + if(byte == 0xf7) + { + LOG("End reading sysex"); + + m_state = State::Default; + + std::stringstream s; + for (const auto b : m_sysexData) + s << HEXN(b, 2); + + LOG("Received sysex: " << s.str()); + + synthLib::SMidiEvent ev; + std::swap(ev.sysex, m_sysexData); + m_midiData.emplace_back(ev); + + return true; + } + } + break; + case State::Preset: + { + if(m_remainingPresetBytes == std::numeric_limits<uint32_t>::max()) + { + const auto version = byte; + + switch (version) + { + case 1: + case 2: + m_remainingPresetBytes = m_mc.getROM().getMultiPresetSize(); + break; + default: + m_remainingPresetBytes = m_mc.getROM().getSinglePresetSize(); + break; + } + LOG("Preset size for version code " << static_cast<int>(version) << " is " << m_remainingPresetBytes); + } + + uint32_t shift = 16; + uint32_t i=0; + while(m_remainingPresetBytes > 0 && i < 3) + { + m_presetData.push_back((_data >> shift) & 0xff); + shift -= 8; + --m_remainingPresetBytes; + ++i; + } + + if(m_remainingPresetBytes == 0) + { + LOG("Succesfully received preset"); + m_state = State::Default; + } + } + break; + case State::StatusReport: + m_dspStatus.push_back(_data); + if(--m_remainingStatusBytes == 0) + m_state = State::Default; + break; + } + + return false; + } + + void Hdi08TxParser::waitForPreset(uint32_t _byteCount) + { + m_remainingPresetBytes = _byteCount; + } +} diff --git a/source/virusLib/hdi08TxParser.h b/source/virusLib/hdi08TxParser.h @@ -0,0 +1,70 @@ +#pragma once + +#include <array> + +#include "../synthLib/midiTypes.h" +#include "../dsp56300/source/dsp56kEmu/types.h" + +namespace virusLib +{ + class Microcontroller; + + class Hdi08TxParser + { + public: + enum class State + { + Default, + Sysex, + Preset, + StatusReport + }; + + enum class PatternType + { + DspBoot, + + Count + }; + + Hdi08TxParser(Microcontroller& _mc) : m_mc(_mc) + { + m_patternPositions.fill(0); + } + + bool append(dsp56k::TWord _data); + const std::vector<synthLib::SMidiEvent>& getMidiData() const { return m_midiData; } + void clearMidiData() { m_midiData.clear(); } + void waitForPreset(uint32_t _byteCount); + + bool waitingForPreset() const + { + return m_remainingPresetBytes != 0; + } + + bool hasDspBooted() const + { + return m_dspHasBooted; + } + + private: + Microcontroller& m_mc; + + std::vector<synthLib::SMidiEvent> m_midiData; + std::vector<uint8_t> m_sysexData; + std::vector<uint8_t> m_presetData; + std::vector<dsp56k::TWord> m_dspStatus; + + uint32_t m_remainingPresetBytes = 0; + uint32_t m_remainingStatusBytes = 0; + + State m_state = State::Default; + + std::vector<PatternType> m_matchedPatterns; + std::vector<dsp56k::TWord> m_nonPatternWords; + + std::array<size_t, static_cast<size_t>(PatternType::Count)> m_patternPositions = {}; + + bool m_dspHasBooted = false; + }; +} diff --git a/source/virusLib/microcontroller.cpp b/source/virusLib/microcontroller.cpp @@ -13,8 +13,9 @@ using namespace synthLib; constexpr virusLib::PlayMode g_defaultPlayMode = virusLib::PlayModeSingle; constexpr uint32_t g_sysexPresetHeaderSize = 9; +constexpr uint32_t g_sysexPresetFooterSize = 2; // checksum, f7 + constexpr uint32_t g_singleRamBankCount = 2; -constexpr uint32_t g_presetsPerBank = 128; constexpr uint8_t g_pageA[] = {0x05, 0x0A, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, @@ -34,17 +35,21 @@ constexpr uint8_t g_pageC_global[] = {45, 63, 64, 65, 66, 67, 68, 69, 70, 85, 86, 87, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 105, 106, 110, 111, 112, 113, 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 127}; -constexpr uint8_t g_pageC_multi[] = {5,6,7,8,9,10,11,12,13,14,22,31,32,33,34,35,36,37,38,39,40,41,72,73,74,75,77,78}; -constexpr uint8_t g_pageC_multiPart[] = {31,32,33,34,35,36,37,38,39,40,41,72,73,74,75,77,78}; namespace virusLib { -Microcontroller::Microcontroller(HDI08& _hdi08, ROMFile& _romFile) : m_hdi08(_hdi08), m_rom(_romFile) + +Microcontroller::Microcontroller(HDI08& _hdi08, const ROMFile& _romFile) : m_rom(_romFile) { if(!_romFile.isValid()) return; - m_globalSettings.fill(0); + m_hdi08.addHDI08(_hdi08); + + m_hdi08TxParsers.reserve(2); + m_hdi08TxParsers.emplace_back(*this); + + m_globalSettings.fill(0xffffffff); for(size_t i=0; i<m_multis.size(); ++i) m_rom.getMulti(0, m_multis[i]); @@ -54,13 +59,13 @@ Microcontroller::Microcontroller(HDI08& _hdi08, ROMFile& _romFile) : m_hdi08(_hd bool failed = false; // read all singles from ROM and copy first ROM banks to RAM banks - for(uint32_t b=0; b<8 && !failed; ++b) + for(uint32_t b=0; b<26 && !failed; ++b) { std::vector<TPreset> singles; const auto bank = b >= g_singleRamBankCount ? b - g_singleRamBankCount : b; - for(uint32_t p=0; p<g_presetsPerBank; ++p) + for(uint32_t p=0; p<m_rom.getPresetsPerBank(); ++p) { TPreset single; m_rom.getSingle(bank, p, single); @@ -78,20 +83,33 @@ Microcontroller::Microcontroller(HDI08& _hdi08, ROMFile& _romFile) : m_hdi08(_hd m_singles.emplace_back(std::move(singles)); } - m_singleEditBuffer = m_singles[0][0]; + if(!m_singles.empty()) + { + const auto& singles = m_singles[0]; - for(auto i=0; i<static_cast<int>(m_singleEditBuffers.size()); ++i) - m_singleEditBuffers[i] = m_singles[0][i]; + if(!singles.empty()) + { + m_singleEditBuffer = singles[0]; + + for(auto i=0; i<static_cast<int>(std::min(singles.size(), m_singleEditBuffers.size())); ++i) + m_singleEditBuffers[i] = singles[i]; + } + } } void Microcontroller::sendInitControlCommands() { + writeHostBitsWithWait(0, 1); + + LOG("Sending Init Control Commands"); + sendControlCommand(MIDI_CLOCK_RX, 0x1); // Enable MIDI clock receive sendControlCommand(GLOBAL_CHANNEL, 0x0); // Set global midi channel to 0 sendControlCommand(MIDI_CONTROL_LOW_PAGE, 0x1); // Enable midi CC to edit parameters on page A sendControlCommand(MIDI_CONTROL_HIGH_PAGE, 0x1); // Enable poly pressure to edit parameters on page B sendControlCommand(MASTER_VOLUME, 127); // Set master volume to maximum sendControlCommand(MASTER_TUNE, 64); // Set master tune to 0 + sendControlCommand(DEVICE_ID, OMNI_DEVICE_ID); // Set device ID to Omni } void Microcontroller::createDefaultState() @@ -104,31 +122,16 @@ void Microcontroller::createDefaultState() loadMulti(0, m_multiEditBuffer); } -bool Microcontroller::needsToWaitForHostBits(char flag1, char flag2) const -{ - const int target = (flag1?1:0)|(flag2?2:0); - const int hsr = m_hdi08.readStatusRegister(); - if (((hsr>>3)&3)==target) - return false; - return m_hdi08.hasDataToSend(); -} - -void Microcontroller::writeHostBitsWithWait(const char flag1, const char flag2) const +void Microcontroller::writeHostBitsWithWait(const uint8_t flag0, const uint8_t flag1) { - std::lock_guard lock(m_mutex); - - const int hsr=m_hdi08.readStatusRegister(); - const int target=(flag1?1:0)|(flag2?2:0); - if (((hsr>>3)&3)==target) return; - waitUntilBufferEmpty(); - m_hdi08.setHostFlags(flag1, flag2); + m_hdi08.writeHostFlags(flag0, flag1); } bool Microcontroller::sendPreset(const uint8_t program, const std::vector<TWord>& preset, const bool isMulti) { std::lock_guard lock(m_mutex); - if(m_loadingState || m_hdi08.hasDataToSend() || needsToWaitForHostBits(0,1)) + if(m_loadingState || waitingForPresetReceiveConfirmation()) { // if we write a multi or a multi mode single, remove a pending single for single mode // If we write a single-mode single, remove all multi-related pending writes @@ -168,36 +171,40 @@ bool Microcontroller::sendPreset(const uint8_t program, const std::vector<TWord> m_hdi08.writeRX(preset); + LOG("Send to DSP: " << (isMulti ? "Multi" : "Single") << " to program " << static_cast<int>(program)); + + for (auto& parser : m_hdi08TxParsers) + parser.waitForPreset(isMulti ? m_rom.getMultiPresetSize() : m_rom.getSinglePresetSize()); + return true; } void Microcontroller::sendControlCommand(const ControlCommand _command, const uint8_t _value) { - send(PAGE_C, 0x0, _command, _value); + send(globalSettingsPage(), 0x0, _command, _value); } -bool Microcontroller::send(const Page _page, const uint8_t _part, const uint8_t _param, const uint8_t _value, bool cancelIfFull/* = false*/) +bool Microcontroller::send(const Page _page, const uint8_t _part, const uint8_t _param, const uint8_t _value) { std::lock_guard lock(m_mutex); - waitUntilReady(); - - if(cancelIfFull && needsToWaitForHostBits(0,1)) - return false; writeHostBitsWithWait(0,1); + TWord buf[] = {0xf4f400, 0x0}; buf[0] = buf[0] | _page; buf[1] = (_part << 16) | (_param << 8) | _value; m_hdi08.writeRX(buf, 2); - if(_page == PAGE_C) +// LOG("Send command, page " << (int)_page << ", part " << (int)_part << ", param " << (int)_param << ", value " << (int)_value); + + if(_page == globalSettingsPage()) { m_globalSettings[_param] = _value; } return true; } -bool Microcontroller::sendMIDI(const SMidiEvent& _ev, bool cancelIfFull/* = false*/) +bool Microcontroller::sendMIDI(const SMidiEvent& _ev) { const uint8_t channel = _ev.a & 0x0f; const uint8_t status = _ev.a & 0xf0; @@ -240,7 +247,7 @@ bool Microcontroller::sendMIDI(const SMidiEvent& _ev, bool cancelIfFull/* = fals return true; } -bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelIfFull, std::vector<SMidiEvent>& _responses, const MidiEventSource _source) +bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, std::vector<SMidiEvent>& _responses, const MidiEventSource _source) { if (_data.size() < 7) return true; // invalid sysex or not directed to us @@ -285,18 +292,24 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI response.push_back(toMidiByte(_bank)); response.push_back(_program); - for(const auto value : _dump) - { - response.push_back(value); - } + const auto size = _type == DUMP_SINGLE ? m_rom.getSinglePresetSize() : m_rom.getMultiPresetSize(); - // checksum - uint8_t cs = 0; + const auto modelABCsize = ROMFile::getSinglePresetSize(); - for(size_t i=5; i<response.size(); ++i) - cs += response[i]; + for(size_t i=0; i<modelABCsize; ++i) + response.push_back(_dump[i]); - response.push_back(cs & 0x7f); + // checksum for ABC models comes after 256 bytes of preset data + response.push_back(calcChecksum(response, 5)); + + if (size > modelABCsize) + { + for (size_t i = modelABCsize; i < size; ++i) + response.push_back(_dump[i]); + + // Second checksum for D model: That checksum is to be calculated over the whole preset data, including the ABC checksum + response.push_back(calcChecksum(response, 5)); + } response.push_back(M_ENDOFSYSEX); @@ -343,7 +356,7 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI if(_bank == BankNumber::A) { // eat this, host, whoever you are. 128 multi packets - for(uint8_t i=0; i<g_presetsPerBank; ++i) + for(uint8_t i=0; i<m_rom.getPresetsPerBank(); ++i) { TPreset data; const auto res = requestMulti(_bank, i, data); @@ -360,10 +373,10 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI buildResponseHeader(ev); - response.push_back(PARAM_CHANGE_C); + response.push_back(globalSettingsPage()); response.push_back(0); // part = 0 response.push_back(_param); - response.push_back(m_globalSettings[_param]); + response.push_back(static_cast<uint8_t>(m_globalSettings[_param])); response.push_back(M_ENDOFSYSEX); _responses.emplace_back(std::move(ev)); @@ -371,8 +384,11 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI auto buildGlobalResponses = [&]() { - for (const auto globalParam : g_pageC_global) - buildGlobalResponse(globalParam); + for (uint32_t i=0; i<m_globalSettings.size(); ++i) + { + if(m_globalSettings[i] <= 0xff) + buildGlobalResponse(static_cast<uint8_t>(i)); + } }; auto buildTotalResponse = [&]() @@ -406,7 +422,7 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI TPreset _dump, _multi; const auto res = requestSingle(BankNumber::EditBuffer, _part, _dump); const auto resm = requestMulti(BankNumber::EditBuffer, 0, _multi); - const uint8_t channel = _part == SINGLE ? m_globalSettings[GLOBAL_CHANNEL] : _multi[static_cast<size_t>(MD_PART_MIDI_CHANNEL) + _part]; + const uint8_t channel = _part == SINGLE ? static_cast<uint8_t>(m_globalSettings[GLOBAL_CHANNEL]) : _multi[static_cast<size_t>(MD_PART_MIDI_CHANNEL) + _part]; for (const auto cc : g_pageA) { SMidiEvent ev; @@ -435,18 +451,19 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI const auto bank = fromMidiByte(_data[7]); const uint8_t program = _data[8]; LOG("Received Single dump, Bank " << (int)toMidiByte(bank) << ", program " << (int)program); - TPreset dump; - std::copy_n(_data.data() + g_sysexPresetHeaderSize, dump.size(), dump.begin()); - return writeSingle(bank, program, dump); + TPreset preset; + preset.fill(0); + std::copy_n(_data.data() + g_sysexPresetHeaderSize, std::min(preset.size(), _data.size() - g_sysexPresetHeaderSize - g_sysexPresetFooterSize), preset.begin()); + return writeSingle(bank, program, preset); } case DUMP_MULTI: { const auto bank = fromMidiByte(_data[7]); const uint8_t program = _data[8]; LOG("Received Multi dump, Bank " << (int)toMidiByte(bank) << ", program " << (int)program); - TPreset dump; - std::copy_n(_data.data() + g_sysexPresetHeaderSize, dump.size(), dump.begin()); - return writeMulti(bank, program, dump); + TPreset preset; + std::copy_n(_data.data() + g_sysexPresetHeaderSize, std::min(preset.size(), _data.size() - g_sysexPresetHeaderSize - g_sysexPresetFooterSize), preset.begin()); + return writeMulti(bank, program, preset); } case REQUEST_SINGLE: { @@ -492,16 +509,45 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI case REQUEST_ARRANGEMENT: buildArrangementResponse(); break; - case PARAM_CHANGE_A: - case PARAM_CHANGE_B: - case PARAM_CHANGE_C: + case PAGE_A: + case PAGE_B: + case PAGE_C: { const auto page = static_cast<Page>(cmd); - const auto part = _data[7]; + if(!isPageSupported(page)) + break; + + auto part = _data[7]; const auto param = _data[8]; const auto value = _data[9]; + if(page == globalSettingsPage() && param == PLAY_MODE) + { + const auto playMode = value; + + send(page, part, param, value); + + switch(playMode) + { + case PlayModeSingle: + { + LOG("Switch to Single mode"); + return writeSingle(BankNumber::EditBuffer, SINGLE, m_singleEditBuffer); + } + case PlayModeMultiSingle: + case PlayModeMulti: + { + writeMulti(BankNumber::EditBuffer, 0, m_multiEditBuffer); + for(uint8_t i=0; i<16; ++i) + writeSingle(BankNumber::EditBuffer, i, m_singleEditBuffers[i]); + return true; + } + default: + return true; + } + } + if(page == PAGE_C || (page == PAGE_B && param == CLOCK_TEMPO)) { applyToMultiEditBuffer(part, param, value); @@ -510,29 +556,6 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI switch(command) { - case PLAY_MODE: - { - const auto playMode = value; - - switch(playMode) - { - case PlayModeSingle: - { - LOG("Switch to Single mode"); - return writeSingle(BankNumber::EditBuffer, SINGLE, m_singleEditBuffer); - } - case PlayModeMultiSingle: - case PlayModeMulti: - { - writeMulti(BankNumber::EditBuffer, 0, m_multiEditBuffer); - for(uint8_t i=0; i<16; ++i) - writeSingle(BankNumber::EditBuffer, i, m_singleEditBuffers[i]); - return true; - } - default: - return true; - } - } case PART_BANK_SELECT: return partBankSelect(part, value, false); case PART_BANK_CHANGE: @@ -545,8 +568,6 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI return multiProgramChange(value); } return true; - default: - break; } } else @@ -572,7 +593,7 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI _responses.push_back(ev); } - return send(page, part, param, value, _cancelIfFull); + return send(page, part, param, value); } default: LOG("Unknown sysex command " << HEXN(cmd, 2)); @@ -581,28 +602,34 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI return true; } -void Microcontroller::waitUntilBufferEmpty() const +std::vector<TWord> Microcontroller::presetToDSPWords(const TPreset& _preset, const bool _isMulti) const { - while (m_hdi08.hasDataToSend()) - { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::this_thread::yield(); - } -} + const auto targetByteSize = _isMulti ? m_rom.getMultiPresetSize() : m_rom.getSinglePresetSize(); + const auto sourceByteSize = _isMulti ? ROMFile::getMultiPresetSize() : ROMFile::getSinglePresetSize(); -std::vector<TWord> Microcontroller::presetToDSPWords(const TPreset& _preset) -{ - int idx = 0; + const auto sourceWordSize = (sourceByteSize + 2) / 3; + const auto targetWordSize = (targetByteSize + 2) / 3; std::vector<TWord> preset; - preset.resize(0x56); + preset.resize(targetWordSize, 0); - for (int i = 0; i < 0x56; i++) + size_t idx = 0; + for (size_t i = 0; i < sourceWordSize && i < targetWordSize; i++) { - if (i == 0x55) - preset[i] = _preset[idx] << 16; - else + if (i == (sourceWordSize - 1)) + { + if (idx < sourceByteSize) + preset[i] = _preset[idx] << 16; + if ((idx + 1) < sourceByteSize) + preset[i] |= _preset[idx + 1] << 8; + if ((idx + 2) < sourceByteSize) + preset[i] |= _preset[idx + 2]; + } + else if (i < sourceWordSize) + { preset[i] = ((_preset[idx] << 16) | (_preset[idx + 1] << 8) | _preset[idx + 2]); + } + idx += 3; } @@ -628,15 +655,6 @@ bool Microcontroller::getSingle(BankNumber _bank, uint32_t _preset, TPreset& _re return true; } -void Microcontroller::waitUntilReady() const -{ - while (!bittest(m_hdi08.readControlRegister(), HDI08::HCR_HRIE)) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - std::this_thread::yield(); - } -} - bool Microcontroller::requestMulti(BankNumber _bank, uint8_t _program, TPreset& _data) const { if (_bank == BankNumber::EditBuffer) @@ -693,13 +711,13 @@ bool Microcontroller::writeSingle(BankNumber _bank, uint8_t _program, const TPre else m_singleEditBuffers[_program % m_singleEditBuffers.size()] = _data; - LOG("Loading Single " << ROMFile::getSingleName(_data) << " to part " << (int)_program); + LOG("Loading Single " << ROMFile::getSingleName(_data) << " to part " << static_cast<int>(_program)); if(_program == SINGLE) m_globalSettings[PLAY_MODE] = PlayModeSingle; // Send to DSP - return sendPreset(_program, presetToDSPWords(_data), false); + return sendPreset(_program, presetToDSPWords(_data, false), false); } bool Microcontroller::writeMulti(BankNumber _bank, uint8_t _program, const TPreset& _data) @@ -723,7 +741,7 @@ bool Microcontroller::writeMulti(BankNumber _bank, uint8_t _program, const TPres m_globalSettings[PLAY_MODE] = PlayModeMulti; // Convert array of uint8_t to vector of 24bit TWord - return sendPreset(_program, presetToDSPWords(_data), true); + return sendPreset(_program, presetToDSPWords(_data, true), true); } bool Microcontroller::partBankSelect(const uint8_t _part, const uint8_t _value, const bool _immediatelySelectSingle) @@ -803,9 +821,14 @@ bool Microcontroller::loadMultiSingle(uint8_t _part, const TPreset& _multi) void Microcontroller::process(size_t _size) { + m_hdi08.exec(); + std::lock_guard lock(m_mutex); - if(!m_pendingPresetWrites.empty() && !m_hdi08.hasDataToSend()) + if(m_pendingPresetWrites.empty() || !m_hdi08.rxEmpty() || waitingForPresetReceiveConfirmation()) + return; + + if(!m_pendingPresetWrites.empty()) { const auto preset = m_pendingPresetWrites.front(); m_pendingPresetWrites.pop_front(); @@ -816,14 +839,14 @@ void Microcontroller::process(size_t _size) bool Microcontroller::getState(std::vector<unsigned char>& _state, const StateType _type) { - const auto deviceId = m_globalSettings[DEVICE_ID]; + const uint8_t deviceId = static_cast<uint8_t>(m_globalSettings[DEVICE_ID]); std::vector<SMidiEvent> responses; if(_type == StateTypeGlobal) - sendSysex({M_STARTOFSYSEX, 0x00, 0x20, 0x33, 0x01, deviceId, REQUEST_TOTAL, M_ENDOFSYSEX}, false, responses, MidiEventSourcePlugin); + sendSysex({M_STARTOFSYSEX, 0x00, 0x20, 0x33, 0x01, deviceId, REQUEST_TOTAL, M_ENDOFSYSEX}, responses, MidiEventSourcePlugin); - sendSysex({M_STARTOFSYSEX, 0x00, 0x20, 0x33, 0x01, deviceId, REQUEST_ARRANGEMENT, M_ENDOFSYSEX}, false, responses, MidiEventSourcePlugin); + sendSysex({M_STARTOFSYSEX, 0x00, 0x20, 0x33, 0x01, deviceId, REQUEST_ARRANGEMENT, M_ENDOFSYSEX}, responses, MidiEventSourcePlugin); if(responses.empty()) return false; @@ -871,7 +894,7 @@ bool Microcontroller::setState(const std::vector<unsigned char>& _state, const S for (const auto& event : events) { - sendSysex(event.sysex, false, unusedResponses, MidiEventSourcePlugin); + sendSysex(event.sysex, unusedResponses, MidiEventSourcePlugin); unusedResponses.clear(); } @@ -880,37 +903,38 @@ bool Microcontroller::setState(const std::vector<unsigned char>& _state, const S return true; } -bool Microcontroller::sendMIDItoDSP(uint8_t _a, uint8_t _b, uint8_t _c, bool cancelIfFull) const +bool Microcontroller::sendMIDItoDSP(uint8_t _a, const uint8_t _b, const uint8_t _c) { std::lock_guard lock(m_mutex); - if(cancelIfFull && (needsToWaitForHostBits(1,1) || m_hdi08.dataRXFull())) - return false; - writeHostBitsWithWait(1,1); + writeHostBitsWithWait(1, 1); - switch (_a) + auto sendMIDItoDSP = [this](const uint8_t _midiByte) { - case M_TIMINGCLOCK: - case M_START: - case M_CONTINUE: - case M_STOP: - { - const auto temp = static_cast<TWord>(_a) << 16; - m_hdi08.writeRX(&temp, 1); - } - break; - default: - { - TWord buf[3] = { static_cast<TWord>(_a) << 16, static_cast<TWord>(_b) << 16, static_cast<TWord>(_c) << 16 }; - m_hdi08.writeRX(buf, 3); - } - break; + const TWord word = static_cast<TWord>(_midiByte) << 16; + m_hdi08.writeRX(&word, 1); + }; + + const auto command = (_a & 0xf0); + + if(command == 0xf0) + { + // single-byte status message + sendMIDItoDSP(_a); + } + else + { + sendMIDItoDSP(_a); + sendMIDItoDSP(_b); + + if(command != M_AFTERTOUCH) + sendMIDItoDSP(_c); } return true; } -void Microcontroller::sendPendingMidiEvents(uint32_t _maxOffset) +void Microcontroller::sendPendingMidiEvents(const uint32_t _maxOffset) { auto size = m_pendingMidiEvents.size(); @@ -921,7 +945,7 @@ void Microcontroller::sendPendingMidiEvents(uint32_t _maxOffset) { const auto& ev = m_pendingMidiEvents.front(); - if(!sendMIDItoDSP(ev.a,ev.b,ev.c, true)) + if(!sendMIDItoDSP(ev.a,ev.b,ev.c)) break; m_pendingMidiEvents.pop_front(); @@ -929,6 +953,68 @@ void Microcontroller::sendPendingMidiEvents(uint32_t _maxOffset) } } +void Microcontroller::addHDI08(dsp56k::HDI08& _hdi08) +{ + m_hdi08.addHDI08(_hdi08); + m_hdi08TxParsers.emplace_back(*this); +} + +void Microcontroller::processHdi08Tx(std::vector<synthLib::SMidiEvent>& _midiEvents) +{ + std::lock_guard lock(m_mutex); + + for(size_t i=0; i<m_hdi08.size(); ++i) + { + auto* hdi08 = m_hdi08.get(i); + auto& parser = m_hdi08TxParsers[i]; + + while(hdi08->hasTX()) + { + if(parser.append(hdi08->readTX())) + { + const auto midi = parser.getMidiData(); + if(i == 0) + _midiEvents.insert(_midiEvents.end(), midi.begin(), midi.end()); + parser.clearMidiData(); + } + } + } +} + +PresetVersion Microcontroller::getPresetVersion(const TPreset& _preset) +{ + return getPresetVersion(_preset[0]); +} + +PresetVersion Microcontroller::getPresetVersion(const uint8_t v) +{ + if(v >= D2) return D2; + if(v >= D) return D; + if(v >= C) return C; + if(v >= B) return B; + return A; +} + +uint8_t Microcontroller::calcChecksum(const std::vector<uint8_t>& _data, const size_t _offset) +{ + uint8_t cs = 0; + + for (size_t i = _offset; i < _data.size(); ++i) + cs += _data[i]; + + return cs & 0x7f; +} + +bool Microcontroller::dspHasBooted() const +{ + for (const auto &p : m_hdi08TxParsers) + { + if(!p.hasDspBooted()) + return false; + } + return true; +} + void Microcontroller::applyToSingleEditBuffer(const Page _page, const uint8_t _part, const uint8_t _param, const uint8_t _value) { if(_part == SINGLE) @@ -937,11 +1023,20 @@ void Microcontroller::applyToSingleEditBuffer(const Page _page, const uint8_t _p applyToSingleEditBuffer(m_singleEditBuffers[_part], _page, _param, _value); } -void Microcontroller::applyToSingleEditBuffer(TPreset& _single, const Page _page, const uint8_t _param, const uint8_t _value) +void Microcontroller::applyToSingleEditBuffer(TPreset& _single, const Page _page, const uint8_t _param, const uint8_t _value) const { - // The manual does not have a Single dump specification, therefore I assume that its a 1:1 mapping of pages A and B + constexpr uint32_t paramsPerPage = 128; + + uint32_t offset; + switch(_page) + { + default: + case PAGE_A: offset = 0; break; + case PAGE_B: offset = 1; break; + } - const uint32_t offset = (_page - PAGE_A) * g_presetsPerBank + _param; + offset *= paramsPerPage; + offset += _param; if(offset >= _single.size()) return; @@ -960,4 +1055,31 @@ void Microcontroller::applyToMultiEditBuffer(const uint8_t _part, const uint8_t } } +Page Microcontroller::globalSettingsPage() const +{ + return PAGE_C; +} + +bool Microcontroller::isPageSupported(Page _page) const +{ + switch (_page) + { + case PAGE_A: + case PAGE_B: + case PAGE_C: + return true; + default: + return false; + } +} + +bool Microcontroller::waitingForPresetReceiveConfirmation() const +{ + for (const auto& parser : m_hdi08TxParsers) + { + if(parser.waitingForPreset()) + return true; + } + return false; +} } diff --git a/source/virusLib/microcontroller.h b/source/virusLib/microcontroller.h @@ -2,6 +2,7 @@ #include "../dsp56300/source/dsp56kEmu/dsp.h" #include "../dsp56300/source/dsp56kEmu/hdi08.h" +#include "../dsp56300/source/dsp56kEmu/hdi08queue.h" #include "romfile.h" @@ -11,6 +12,7 @@ #include <list> #include <mutex> +#include "hdi08TxParser.h" #include "microcontrollerTypes.h" namespace virusLib @@ -23,10 +25,10 @@ class Microcontroller public: using TPreset = ROMFile::TPreset; - explicit Microcontroller(dsp56k::HDI08& hdi08, ROMFile& romFile); + explicit Microcontroller(dsp56k::HDI08& hdi08, const ROMFile& romFile); - bool sendMIDI(const synthLib::SMidiEvent& _ev, bool cancelIfFull = false); - bool sendSysex(const std::vector<uint8_t>& _data, bool _cancelIfFull, std::vector<synthLib::SMidiEvent>& _responses, synthLib::MidiEventSource _source); + bool sendMIDI(const synthLib::SMidiEvent& _ev); + bool sendSysex(const std::vector<uint8_t>& _data, std::vector<synthLib::SMidiEvent>& _responses, synthLib::MidiEventSource _source); bool writeSingle(BankNumber _bank, uint8_t _program, const TPreset& _data); bool writeMulti(BankNumber _bank, uint8_t _program, const TPreset& _data); @@ -41,19 +43,29 @@ public: bool getState(std::vector<unsigned char>& _state, synthLib::StateType _type); bool setState(const std::vector<unsigned char>& _state, synthLib::StateType _type); - bool sendMIDItoDSP(uint8_t _a, uint8_t _b, uint8_t _c, bool cancelIfFull) const; + bool sendMIDItoDSP(uint8_t _a, const uint8_t _b, const uint8_t _c); void sendPendingMidiEvents(uint32_t _maxOffset); + void addHDI08(dsp56k::HDI08& _hdi08); + + void processHdi08Tx(std::vector<synthLib::SMidiEvent>& _midiEvents); + + static PresetVersion getPresetVersion(const TPreset& _preset); + static PresetVersion getPresetVersion(uint8_t _versionCode); + + static uint8_t calcChecksum(const std::vector<uint8_t>& _data, const size_t _offset); + + bool dspHasBooted() const; + + const ROMFile& getROM() const { return m_rom; } + private: - bool send(Page page, uint8_t part, uint8_t param, uint8_t value, bool cancelIfFull = false); + bool send(Page page, uint8_t part, uint8_t param, uint8_t value); void sendControlCommand(ControlCommand command, uint8_t value); bool sendPreset(uint8_t program, const std::vector<dsp56k::TWord>& preset, bool isMulti = false); - bool needsToWaitForHostBits(char flag1,char flag2) const; - void writeHostBitsWithWait(char flag1,char flag2) const; - void waitUntilReady() const; - void waitUntilBufferEmpty() const; - static std::vector<dsp56k::TWord> presetToDSPWords(const TPreset& _preset); + void writeHostBitsWithWait(uint8_t flag0, uint8_t flag1); + std::vector<dsp56k::TWord> presetToDSPWords(const TPreset& _preset, bool _isMulti) const; bool getSingle(BankNumber _bank, uint32_t _preset, TPreset& _result) const; bool partBankSelect(uint8_t _part, uint8_t _value, bool _immediatelySelectSingle); @@ -64,16 +76,21 @@ private: bool loadMultiSingle(uint8_t _part, const TPreset& _multi); void applyToSingleEditBuffer(Page _page, uint8_t _part, uint8_t _param, uint8_t _value); - static void applyToSingleEditBuffer(TPreset& _single, Page _page, uint8_t _param, uint8_t _value); + void applyToSingleEditBuffer(TPreset& _single, Page _page, uint8_t _param, uint8_t _value) const; void applyToMultiEditBuffer(uint8_t _part, uint8_t _param, uint8_t _value); - - dsp56k::HDI08& m_hdi08; - ROMFile& m_rom; + Page globalSettingsPage() const; + bool isPageSupported(Page _page) const; + bool waitingForPresetReceiveConfirmation() const; + + dsp56k::HDI08Queue m_hdi08; + std::vector<Hdi08TxParser> m_hdi08TxParsers; + + const ROMFile& m_rom; std::array<TPreset,128> m_multis; TPreset m_multiEditBuffer; - std::array<uint8_t, 256> m_globalSettings; + std::array<uint32_t, 256> m_globalSettings; std::vector<std::vector<TPreset>> m_singles; // Multi mode diff --git a/source/virusLib/microcontrollerTypes.h b/source/virusLib/microcontrollerTypes.h @@ -20,6 +20,7 @@ namespace virusLib PARAM_CHANGE_A = 0x70, PARAM_CHANGE_B = 0x71, PARAM_CHANGE_C = 0x72, + PARAM_CHANGE_D = 0x73, }; enum ControlCommand : uint8_t @@ -83,8 +84,8 @@ namespace virusLib MIDI_ARPEGGIATOR_SEND = 96, MULTI_PROGRAM_CHANGE = 105, MIDI_CLOCK_RX = 106, - EFFECT_SEND = 113, // actually in bank A UNK6d = 109, + EFFECT_SEND = 113, // actually in bank A PLAY_MODE = 122, PART_NUMBER = 123, GLOBAL_CHANNEL = 124, @@ -143,7 +144,7 @@ namespace virusLib MD_PART_PROG_CHANGE_ENABLE }; - enum Page + enum Page : uint8_t { PAGE_A = 0x70, PAGE_B = 0x71, @@ -177,13 +178,16 @@ namespace virusLib H, Count }; - enum VirusModel : uint8_t + + enum PresetVersion : uint8_t { - A, - B, - C, - TI + A = 0x00, // 0-4 = OS 1 & OS 2, 5 = OS 3 + B = 0x06, // OS 4 + C = 0x07, // OS 6 + D = 0x08, + D2 = 0x0C, }; + uint8_t toMidiByte(BankNumber _bank); BankNumber fromMidiByte(uint8_t _byte); uint32_t toArrayIndex(BankNumber _bank); diff --git a/source/virusLib/midiOutParser.cpp b/source/virusLib/midiOutParser.cpp @@ -1,56 +0,0 @@ -#include "midiOutParser.h" - -#include <chrono> -#include <sstream> -#include <thread> - -#include "../dsp56300/source/dsp56kEmu/logging.h" - -namespace virusLib -{ - bool MidiOutParser::append(const dsp56k::TWord word) - { - // Only support for single byte responses atm - if ((word & 0xff00ffff) != 0) - { - LOG("Unexpected MIDI data: 0x" << HEX(word)); - return false; - } - - uint8_t buf = (word & 0x00ff0000) >> 16; - - // Check for sequence start 0xf0 - if (!m_data.empty()) - { - if (buf == 0 || buf == 0xf5) - return false; - if (buf != 0xf0) { - LOG("Unexpected MIDI bytes: 0x" << HEXN(buf, 2)); - return false; - } - } - - m_data.push_back(buf); - - // End of midi command, show it - if (buf == 0xf7) - { - std::ostringstream stringStream; - for (size_t i = 0; i < m_data.size(); i++) - { - //printf("tmp: 0x%x\n", midi[i]); - stringStream << HEXN(m_data[i], 2); - } - LOG("SYSEX RESPONSE: 0x" << stringStream.str()); - - synthLib::SMidiEvent ev; - std::swap(ev.sysex, m_data); - m_midiData.push_back(ev); - return true; - } - - //LOG("BUF=0x"<< HEX(buf)); - - return false; - } -} diff --git a/source/virusLib/midiOutParser.h b/source/virusLib/midiOutParser.h @@ -1,18 +0,0 @@ -#pragma once - -#include "../synthLib/midiTypes.h" -#include "../dsp56300/source/dsp56kEmu/types.h" - -namespace virusLib -{ - class MidiOutParser - { - public: - bool append(dsp56k::TWord _data); - const std::vector<synthLib::SMidiEvent>& getMidiData() const { return m_midiData; } - void clearMidiData() { m_midiData.clear(); } - private: - std::vector<synthLib::SMidiEvent> m_midiData; - std::vector<uint8_t> m_data; - }; -} diff --git a/source/virusLib/romfile.cpp b/source/virusLib/romfile.cpp @@ -1,24 +1,63 @@ #include <cassert> #include <fstream> +#include <algorithm> #include "romfile.h" +#include "utils.h" #include "../dsp56300/source/dsp56kEmu/dsp.h" #include "../dsp56300/source/dsp56kEmu/logging.h" +#include "../synthLib/os.h" + +#include <cstring> // memcpy + #ifdef _WIN32 +#define NOMINMAX #include <Windows.h> #endif namespace virusLib { -ROMFile::ROMFile(const std::string& _path) : m_path(_path) +void ROMFile::dumpToBin(const std::vector<dsp56k::TWord>& _data, const std::string& _filename) +{ + FILE* hFile = fopen(_filename.c_str(), "wb"); + + for(size_t i=0; i<_data.size(); ++i) + { + const auto d = _data[i]; + const auto hsb = (d >> 16) & 0xff; + const auto msb = (d >> 8) & 0xff; + const auto lsb = d & 0xff; + fwrite(&hsb, 1, 1, hFile); + fwrite(&msb, 1, 1, hFile); + fwrite(&lsb, 1, 1, hFile); + } + fclose(hFile); +} + +ROMFile::ROMFile(const std::string& _path) : m_file(_path) { LOG("Init access virus"); - auto chunks = get_dsp_chunks(); + // Open file + LOG("Loading ROM at " << m_file); + std::ifstream file(this->m_file, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + LOG("Failed to load ROM at '" << m_file << "'"); +#ifdef _WIN32 + const auto errorMessage = std::string("Failed to load ROM file. Make sure it is put next to the plugin and ends with .bin"); + ::MessageBoxA(nullptr, errorMessage.c_str(), "ROM not found", MB_OK); +#endif + return; + } + + std::istream *dsp = &file; + + const auto chunks = readChunks(*dsp); + file.close(); - if(chunks.empty()) + if (chunks.empty()) return; bootRom.size = chunks[0].items[0]; @@ -40,56 +79,66 @@ ROMFile::ROMFile(const std::string& _path) : m_path(_path) i = 0; } +// dumpToBin(bootRom.data, _path + "_bootloader.bin"); +// dumpToBin(commandStream, _path + "_commandstream.bin"); + + printf("ROM File: %s\n", _path.c_str()); printf("Program BootROM size = 0x%x\n", bootRom.size); printf("Program BootROM offset = 0x%x\n", bootRom.offset); - printf("Program BootROM len = 0x%x\n", static_cast<uint32_t>(bootRom.data.size())); - printf("Program Commands len = 0x%x\n", static_cast<uint32_t>(commandStream.size())); + printf("Program CommandStream size = 0x%x\n", static_cast<uint32_t>(commandStream.size())); } -std::vector<ROMFile::Chunk> ROMFile::get_dsp_chunks() const +std::string ROMFile::findROM() { - uint32_t offset = 0x18000; + return synthLib::findROM(getRomSizeModelABC()); +} - // Open file - std::ifstream file(this->m_path, std::ios::binary | std::ios::ate); +std::vector<ROMFile::Chunk> ROMFile::readChunks(std::istream& _file) +{ + _file.seekg(0, std::ios_base::end); + const auto fileSize = _file.tellg(); - if(!file.is_open()) + uint32_t offset = 0x18000; + int lastChunkId = 4; + + if (fileSize == 1024 * 512) { - LOG("Failed to load ROM at '" << m_path << "'"); -#ifdef _WIN32 - const std::string errorMessage = std::string("Failed to load ROM file. Make sure it is put next to the plugin and ends with .bin"); - ::MessageBoxA(nullptr, errorMessage.c_str(), "ROM not found", MB_OK); -#endif + // ABC + m_model = Model::ABC; + } + else + { + LOG("Invalid ROM, unexpected filesize") return {}; } - LOG("Loading ROM at " << m_path); - std::vector<Chunk> chunks; - chunks.reserve(5); + chunks.reserve(lastChunkId + 1); - // Read all the chunks, hardcoded to 4 for convenience - for (int i = 0; i <= 4; i++) + // Read all the chunks + for (int i = 0; i <= lastChunkId; i++) { - file.seekg(offset); + _file.seekg(offset); // Read buffer Chunk chunk; //file.read(reinterpret_cast<char *>(&chunk->chunk_id), 1); - file.read(reinterpret_cast<char*>(&chunk.chunk_id), 1); - file.read(reinterpret_cast<char*>(&chunk.size1), 1); - file.read(reinterpret_cast<char*>(&chunk.size2), 1); + _file.read(reinterpret_cast<char*>(&chunk.chunk_id), 1); + _file.read(reinterpret_cast<char*>(&chunk.size1), 1); + _file.read(reinterpret_cast<char*>(&chunk.size2), 1); - assert(chunk.chunk_id == 4 - i); + if(i == 0 && chunk.chunk_id == 3 && lastChunkId == 4) // Virus A has one chunk less + lastChunkId = 3; + + assert(chunk.chunk_id == lastChunkId - i); // Format uses a special kind of size where the first byte should be decreased by 1 const uint16_t len = ((chunk.size1 - 1) << 8) | chunk.size2; - uint8_t buf[3]; - for (uint32_t j = 0; j < len; j++) { - file.read(reinterpret_cast<char*>(buf), 3); + uint8_t buf[3]; + _file.read(reinterpret_cast<char*>(buf), 3); chunk.items.emplace_back((buf[0] << 16) | (buf[1] << 8) | buf[2]); } @@ -98,18 +147,16 @@ std::vector<ROMFile::Chunk> ROMFile::get_dsp_chunks() const offset += 0x8000; } - file.close(); - return chunks; } -std::thread ROMFile::bootDSP(dsp56k::DSP& dsp, dsp56k::Peripherals56362& periph) +std::thread ROMFile::bootDSP(dsp56k::DSP& dsp, dsp56k::Peripherals56362& periph) const { // Load BootROM in DSP memory for (uint32_t i=0; i<bootRom.data.size(); i++) dsp.memory().set(dsp56k::MemArea_P, bootRom.offset + i, bootRom.data[i]); -// dsp.memory().saveAssembly("Virus_BootROM.asm", bootRom.offset, bootRom.size, false, false, &periph); +// dsp.memory().saveAssembly((m_file + "_BootROM.asm").c_str(), bootRom.offset, bootRom.size, false, false, &periph); // Attach command stream std::thread feedCommandStream([&]() @@ -122,46 +169,35 @@ std::thread ROMFile::bootDSP(dsp56k::DSP& dsp, dsp56k::Peripherals56362& periph) return feedCommandStream; } -bool ROMFile::getSingle(int bank, int presetNumber, TPreset& _out) const +bool ROMFile::getSingle(const int _bank, const int _presetNumber, TPreset& _out) const { - const uint32_t offset = 0x50000 + (bank * 0x8000) + (presetNumber * 0x100); - - if(!getPreset(offset, _out)) - return false; - - std::stringstream ss; - ss << "Loading Single: Bank " << static_cast<char>('A' + bank) << " " << std::setfill('0') << std::setw(3) << presetNumber << " [" << getSingleName(_out) << "]"; + const uint32_t offset = 0x50000 + (_bank * 0x8000) + (_presetNumber * getSinglePresetSize()); - const std::string msg(ss.str()); - - LOG(msg); - puts(msg.c_str()); - - return true; + return getPreset(offset, _out); } bool ROMFile::getMulti(const int _presetNumber, TPreset& _out) const { - // Open file - return getPreset(0x48000 + (_presetNumber * 256), _out); + return getPreset(0x48000 + (_presetNumber * getMultiPresetSize()), _out); } bool ROMFile::getPreset(const uint32_t _offset, TPreset& _out) const { // Open file - std::ifstream file(this->m_path, std::ios::binary | std::ios::ate); + std::ifstream file(this->m_file, std::ios::binary | std::ios::ate); if(!file.is_open()) { - LOG("Failed to open ROM file " << m_path) + LOG("Failed to open ROM file " << m_file) return false; } file.seekg(_offset); if(file.tellg() != _offset) return false; - file.read(reinterpret_cast<char *>(_out.data()), 256); + file.read(reinterpret_cast<char *>(_out.data()), getSinglePresetSize()); file.close(); return true; } + std::string ROMFile::getSingleName(const TPreset& _preset) { return getPresetName(_preset, 240, 249); @@ -172,7 +208,7 @@ std::string ROMFile::getMultiName(const TPreset& _preset) return getPresetName(_preset, 4, 13); } -std::string ROMFile::getPresetName(const TPreset& _preset, uint32_t _first, uint32_t _last) +std::string ROMFile::getPresetName(const TPreset& _preset, const uint32_t _first, const uint32_t _last) { std::string name; @@ -180,10 +216,10 @@ std::string ROMFile::getPresetName(const TPreset& _preset, uint32_t _first, uint for (uint32_t i = _first; i <= _last; i++) { - const char c = _preset[i]; + const auto c = _preset[i]; if(c < 32 || c > 127) break; - name.push_back(c); + name.push_back(static_cast<char>(c)); } return name; diff --git a/source/virusLib/romfile.h b/source/virusLib/romfile.h @@ -3,6 +3,8 @@ #include <thread> #include <vector> +#include "dsp56kEmu/types.h" + namespace dsp56k { class Peripherals56362; @@ -29,28 +31,80 @@ public: std::vector<uint32_t> data; }; - using TPreset = std::array<uint8_t, 256>; + enum class Model + { + Invalid = -1, + ABC, + Snow, + TI + }; + + using TPreset = std::array<uint8_t, 512>; + + static void dumpToBin(const std::vector<dsp56k::TWord>& _data, const std::string& _filename); explicit ROMFile(const std::string& _path); bool getMulti(int _presetNumber, TPreset& _out) const; - bool getSingle(int bank, int presetNumber, TPreset& _out) const; + bool getSingle(int _bank, int _presetNumber, TPreset& _out) const; bool getPreset(uint32_t _offset, TPreset& _out) const; - + static std::string getSingleName(const TPreset& _preset); static std::string getMultiName(const TPreset& _preset); static std::string getPresetName(const TPreset& _preset, uint32_t _first, uint32_t _last); - std::thread bootDSP(dsp56k::DSP& dsp, dsp56k::Peripherals56362& periph); + std::thread bootDSP(dsp56k::DSP& dsp, dsp56k::Peripherals56362& periph) const; bool isValid() const { return bootRom.size > 0; } + Model getModel() const { return m_model; } + + uint32_t getSamplerate() const + { + return 12000000 / 256; + } + + static uint32_t getMultiPresetSize() + { + return 256; + } + + static uint32_t getSinglePresetSize() + { + return 256; + } + + static uint8_t getSinglesPerBank() + { + return 128; + } + + static constexpr uint32_t getRomSizeModelABC() + { + return 1024 * 512; + } + + uint8_t getPresetsPerBank() const + { + return 128; + } + + static std::string findROM(); + + const std::vector<uint8_t>& getDemoData() const { return m_demoData; } + private: - std::vector<Chunk> get_dsp_chunks() const; + std::vector<Chunk> readChunks(std::istream& _file); BootRom bootRom; std::vector<uint32_t> commandStream; - const std::string m_path; + const std::string m_file; + Model m_model = Model::Invalid; + + std::vector<TPreset> m_singles; + std::vector<TPreset> m_multis; + std::vector<uint8_t> m_demoData; }; + } diff --git a/source/virusLib/utils.h b/source/virusLib/utils.h @@ -0,0 +1,55 @@ +#pragma once + +#include <cstdint> +#include <iostream> +#include <vector> + + +namespace virusLib +{ + static uint16_t swap16(const uint32_t _val) + { + return (_val & 0xff) << 8 | (_val & 0xff00) >> 8; + } + + static uint32_t swap32(const uint32_t _val) + { + return ((_val & 0xff) << 24) | ((_val & 0xff00) << 8) | ((_val & 0xff0000) >> 8) | (_val >> 24); + } + + struct membuf : std::streambuf + { + explicit membuf(const std::vector<char>& base) + { + char* p(const_cast<char*>(base.data())); + this->setg(p, p, p + base.size()); + } + + protected: + pos_type seekpos(pos_type sp, std::ios_base::openmode which) override + { + return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which); + } + + pos_type seekoff(off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode which = std::ios_base::in) override + { + if (dir == std::ios_base::cur) + gbump(static_cast<int>(off)); + else if (dir == std::ios_base::end) + setg(eback(), egptr() + off, egptr()); + else if (dir == std::ios_base::beg) + setg(eback(), eback() + off, egptr()); + return gptr() - eback(); + } + }; + + struct imemstream : virtual membuf, std::istream + { + explicit imemstream(const std::vector<char>& base) + : membuf(base), std::istream(static_cast<std::streambuf*>(this)) + { + } + }; +} diff --git a/source/virusTestConsole/CMakeLists.txt b/source/virusTestConsole/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) + +project(virusTestConsole) + +add_executable(virusTestConsole) + +set(SOURCES + virusTestConsole.cpp +) + +target_sources(virusTestConsole PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(virusTestConsole PUBLIC virusConsoleLib) + +if(UNIX AND NOT APPLE) + target_link_libraries(virusTestConsole PUBLIC -static-libgcc -static-libstdc++) +endif() + +install(TARGETS virusTestConsole DESTINATION . COMPONENT testConsole) + +if(MSVC) + install(DIRECTORY ${CMAKE_SOURCE_DIR}/deploy/win/ DESTINATION . COMPONENT testConsole) +else() + install(DIRECTORY ${CMAKE_SOURCE_DIR}/deploy/linux/ DESTINATION . COMPONENT testConsole) +endif() diff --git a/source/virusTestConsole/virusTestConsole.cpp b/source/virusTestConsole/virusTestConsole.cpp @@ -1,323 +1,94 @@ #include <iostream> -#include <vector> -#include "../dsp56300/source/dsp56kEmu/dsp.h" -#include "../dsp56300/source/dsp56kEmu/dspthread.h" -#include "../dsp56300/source/dsp56kEmu/jitunittests.h" -#include "../dsp56300/source/dsp56kEmu/unittests.h" +#include "../virusConsoleLib/consoleApp.h" -#include "../synthLib/wavWriter.h" -#include "../synthLib/os.h" +#include "dsp56kEmu/jitunittests.h" +#include "dsp56kEmu/interpreterunittests.h" -#include "../virusLib/romfile.h" -#include "../virusLib/microcontroller.h" -#include "../virusLib/midiOutParser.h" -#include "../virusLib/demoplayback.h" +#include "../synthLib/os.h" using namespace dsp56k; using namespace virusLib; using namespace synthLib; -std::vector<uint8_t> audioData; -std::string audioFilename; - -WavWriter writer; -#if _DEBUG -size_t g_writeBlockSize = 8192; -#else -size_t g_writeBlockSize = 65536; -#endif -size_t g_nextWriteSize = g_writeBlockSize; - -Microcontroller::TPreset preset; -Microcontroller* microcontroller = nullptr; - -std::unique_ptr<DemoPlayback> demo; - -size_t audioCallbackCount = 0; - -void writeWord(const TWord _word) -{ - const auto d = reinterpret_cast<const uint8_t*>(&_word); - audioData.push_back(d[0]); - audioData.push_back(d[1]); - audioData.push_back(d[2]); -} - -void audioCallback(dsp56k::Audio* audio) -{ - switch (audioCallbackCount) - { - case 8: - LOG("Sending Init Control Commands"); - microcontroller->sendInitControlCommands(); - break; - case 64: - if(!demo) - { - LOG("Sending Preset"); - microcontroller->writeSingle(BankNumber::EditBuffer, SINGLE, preset); - } - break; - case 128: - if(!demo) - { - LOG("Sending Note On"); - microcontroller->sendMIDI(SMidiEvent(0x90, 60, 0x7f)); // Note On - microcontroller->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); - } - break; - } - - if(audioCallbackCount > 128 && demo) - demo->process(1); - - ++audioCallbackCount; - - static int ctr=0; - constexpr size_t sampleCount = 4; - constexpr size_t channelsIn = 2; - constexpr size_t channelsOut = 2; - - TWord inputData[channelsIn][sampleCount] = {{0,0,0,0}, {0,0,0,0}}; - const TWord* audioIn [channelsIn ] = {inputData[0], inputData[1] }; - TWord outputData[channelsOut][sampleCount] ={{0, 0, 0, 0}, {0, 0, 0, 0}}; - TWord* audioOut[channelsOut] = {outputData[0], outputData[1]}; - - - ctr++; - if((ctr & 0x1fff) == 0) - { - LOG("Deliver Audio"); - } - - audio->processAudioInterleaved(audioIn, audioOut, sampleCount, channelsIn, channelsOut); - - if(!audioData.capacity()) - { - for(int c=0; c<channelsOut; ++c) - { - for(int i=0; i<sampleCount; ++i) - { - if(audioOut[c][i]) - { - audioData.reserve(2048); - } - } - } - - if(audioData.capacity()) - { - if(demo) - { - audioFilename = "virusEmu_demo.wav"; - } - else - { - audioFilename = ROMFile::getSingleName(preset); - - for(size_t i=0; i<audioFilename.size(); ++i) - { - if(audioFilename[i] == ' ') - audioFilename[i] = '_'; - } - audioFilename = "virusEmu_" + audioFilename + ".wav"; - } - LOG("Begin writing audio to file " << audioFilename); - } - } - - if(audioData.capacity()) - { - for(int i=0; i<sampleCount; ++i) - { - for(int c=0; c<2; ++c) - writeWord(audioOut[c][i]); - } - - if(audioData.size() >= g_nextWriteSize) - { - if(writer.write(audioFilename, 24, false, 2, 12000000/256, audioData)) - { - audioData.clear(); - g_nextWriteSize = g_writeBlockSize; - } - else - g_nextWriteSize += g_writeBlockSize; - } - } -} - -void loadSingle(ROMFile& r, int b, int p) -{ - r.getSingle(b, p, preset); -} - -bool loadSingle(ROMFile& r, const std::string& _preset) -{ - auto isDigit = true; - for(size_t i=0; i<_preset.size(); ++i) - { - if(!isdigit(_preset[i])) - { - isDigit = false; - break; - } - } - - if(isDigit) - { - int preset = atoi(_preset.c_str()); - const int bank = preset / 128; - preset -= bank * 128; - loadSingle(r, bank, preset); - return true; - } - - for(uint32_t b=0; b<8; ++b) - { - for(uint32_t p=0; p<128; ++p) - { - std::array<uint8_t, 256> data; - r.getSingle(b, p, data); - - const std::string name = ROMFile::getSingleName(data); - if(name.empty()) - { - return false; - } - if(name == _preset) - { - loadSingle(r, b, p); - return true; - } - } - } - return false; -} - -auto waitReturn = []() -{ - std::cin.ignore(); -}; - int main(int _argc, char* _argv[]) { - if(true) + if constexpr(true) { try { puts("Running Unit Tests..."); - // UnitTests tests; +// InterpreterUnitTests tests; JitUnittests jitTests; - // return 0; puts("Unit Tests finished."); } catch(const std::string& _err) { std::cout << "Unit test failed: " << _err << std::endl; - waitReturn(); + ConsoleApp::waitReturn(); return -1; } } - // Create the DSP with peripherals - constexpr TWord g_memorySize = 0x040000; // 128k words beginning at 0x20000 - const DefaultMemoryValidator memoryMap; - Memory memory(memoryMap, g_memorySize); - memory.setExternalMemory(0x020000, true); - Peripherals56362 periph; - DSP dsp(memory, &periph, &periph); - - periph.getEsai().setCallback(audioCallback,4,1); - periph.getEsai().writeEmptyAudioIn(4, 2); + const auto romFile = findROM(0); - const auto romFile = findROM(); if(romFile.empty()) { std::cout << "Unable to find ROM. Place a ROM file with .bin extension next to this program." << std::endl; - waitReturn(); + ConsoleApp::waitReturn(); return -1; } - ROMFile v(romFile); - auto loader = v.bootDSP(dsp, periph); - Microcontroller uc(periph.getHDI08(), v); - microcontroller = &uc; + std::unique_ptr<ConsoleApp> app; + app.reset(new ConsoleApp(romFile)); - if(_argc > 1) + if(!app->isValid()) { - const std::string name(_argv[1]); + std::cout << "ROM file " << romFile << " couldn't be loaded. Make sure tha the ROM file is valid" << std::endl; + ConsoleApp::waitReturn(); + return -1; + } - if(hasExtension(name, ".mid")) + if(_argc > 1) + { + const std::string name = _argv[1]; + if(hasExtension(name, ".mid") || hasExtension(name, ".bin")) { - // try to load demo - demo.reset(new DemoPlayback(uc)); - if(!demo->loadMidi(name)) + if(!app->loadDemo(name)) { - demo.reset(); + std::cout << "Failed to load demo from '" << name << "', make sure that the file contains a demo" << std::endl; + ConsoleApp::waitReturn(); + return -1; } } - if(!demo && !loadSingle(v, name)) + else if(name == "demo") + { + if(!app->loadInternalDemo()) + { + std::cout << "Failed to load internal demo, the ROM might not contain any demo song" << std::endl; + ConsoleApp::waitReturn(); + return -1; + } + } + else if(!app->loadSingle(name)) { std::cout << "Failed to find preset '" << _argv[1] << "', make sure to use a ROM that contains it" << std::endl; - waitReturn(); + ConsoleApp::waitReturn(); return -1; } } else { -// loadSingle(v, 3, 0x65); // SmoothBsBC -// loadSingle(v, 0, 12); // CommerseSV -// loadSingle(v, 0, 23); // Digedi_JS -// loadSingle(v, 0, 69); // Mystique -// loadSingle(v, 1, 4); // Backing -// loadSingle(v, 0, 50); // Hoppin' SV -// loadSingle(v, 0, 28); // Etheral SV -// loadSingle(v, 1, 75); // Oscar1 HS -// loadSingle(v, 0, 93); // RepeaterJS -// loadSingle(v, 0,126); -// loadSingle(v, 3,101); -// loadSingle(v, 0,5); -// loadSingle(v, 3, 56); // Impact MS -// loadSingle(v, 3, 73); // NiceArp JS - loadSingle(v, 0, 51); // IndiArp BC -// loadSingle(v, 0, 103); // SilkArp SV -// loadSingle(v, 3, 15); // BellaArpJS -// loadSingle(v, 3, 35); // EnglArp JS -// loadSingle(v, 3, 93); // Rhy-Arp JS -// loadSingle(v, 3, 119); // WalkaArpJS -// loadSingle(v, 0, 126); // Init + if(!app->loadInternalDemo()) + app->loadSingle(0, 0); } - // Load preset - dsp.enableTrace((DSP::TraceMode)(DSP::Ops | DSP::Regs | DSP::StackIndent)); + const std::string audioFilename = app->getSingleNameAsFilename(); - DSPThread dspThread(dsp); + app->run(audioFilename); - MidiOutParser midiOut; - - std::thread midiThread([&]() - { - while(true) - { - const auto word = periph.getHDI08().readTX(); - midiOut.append(word); - } - }); - - // queue for HDI08 - loader.join(); - - // dump memory to files -// memory.saveAsText((romFile + "_X.txt").c_str(), MemArea_X, 0, memory.size()); -// memory.saveAsText((romFile + "_Y.txt").c_str(), MemArea_Y, 0, memory.size()); -// memory.saveAssembly((romFile + "_P.asm").c_str(), 0, memory.size(), true, false, &periph); - - while (true) - { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + std::cout << "Program ended. Press key to exit." << std::endl; + ConsoleApp::waitReturn(); return 0; } diff --git a/temp/cmake_win64/gearmulator.sln.DotSettings b/temp/cmake_win64/gearmulator.sln.DotSettings @@ -1,4 +1,6 @@ <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue">DO_NOT_SHOW</s:String> + <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue">ON_SINGLE_LINE</s:String> <s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue">False</s:Boolean> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=AAR/@EntryIndexedValue">AAR</s:String> @@ -8,12 +10,14 @@ <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=FX/@EntryIndexedValue">FX</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=LA/@EntryIndexedValue">LA</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=LC/@EntryIndexedValue">LC</s:String> + <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=LCD/@EntryIndexedValue">LCD</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=PC/@EntryIndexedValue">PC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=ROM/@EntryIndexedValue">ROM</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SP/@EntryIndexedValue">SP</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SR/@EntryIndexedValue">SR</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SSH/@EntryIndexedValue">SSH</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String> + <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=TI/@EntryIndexedValue">TI</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Classes_0020and_0020structs/@EntryIndexedValue">&lt;NamingElement Priority="1"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="__interface" /&gt;&lt;type Name="class" /&gt;&lt;type Name="struct" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Class_0020and_0020struct_0020fields/@EntryIndexedValue">&lt;NamingElement Priority="11"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="class field" /&gt;&lt;type Name="struct field" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="m_" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Class_0020and_0020struct_0020methods/@EntryIndexedValue">&lt;NamingElement Priority="10"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="member function" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String>