commit cd18bb1ea8a9027b37b300b689f48f361662ac56
parent 4806cc3ba68d9e4911249ef50c13f396dee6ecb3
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sun, 4 Dec 2022 19:46:03 +0100
update OSS version
Diffstat:
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"><NamingElement Priority="1"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Class_0020and_0020struct_0020fields/@EntryIndexedValue"><NamingElement Priority="11"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="m_" Suffix="" Style="aaBb" /></NamingElement></s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Class_0020and_0020struct_0020methods/@EntryIndexedValue"><NamingElement Priority="10"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></NamingElement></s:String>