gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 1eb0591775bfe9bfc47d643c536008c77708fd7c
parent e6387b5128e16f7900689863ac03ea67f2eae784
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Thu, 25 Jul 2024 20:38:39 +0200

create new library for misc hardware emulations

Diffstat:
Msource/CMakeLists.txt | 10++++------
Asource/hardwareLib/CMakeLists.txt | 20++++++++++++++++++++
Asource/hardwareLib/am29f.cpp | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/am29f.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2c.cpp | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2c.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2cFlash.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2cFlash.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/sciMidi.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/sciMidi.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msource/mqLib/mqhardware.h | 7+++----
Msource/mqLib/mqmc.cpp | 2+-
Msource/mqLib/mqmc.h | 5+++--
Msource/nord/n2x/n2xLib/CMakeLists.txt | 5++---
Dsource/nord/n2x/n2xLib/i2c.cpp | 164-------------------------------------------------------------------------------
Dsource/nord/n2x/n2xLib/i2c.h | 63---------------------------------------------------------------
Asource/nord/n2x/n2xLib/n2xflash.cpp | 0
Asource/nord/n2x/n2xLib/n2xflash.h | 10++++++++++
Dsource/nord/n2x/n2xLib/n2xi2cflash.cpp | 102-------------------------------------------------------------------------------
Dsource/nord/n2x/n2xLib/n2xi2cflash.h | 52----------------------------------------------------
Msource/nord/n2x/n2xLib/n2xmc.cpp | 2+-
Msource/nord/n2x/n2xLib/n2xmc.h | 11++++++-----
Msource/wLib/CMakeLists.txt | 4+---
Dsource/wLib/am29f.cpp | 140-------------------------------------------------------------------------------
Dsource/wLib/am29f.h | 68--------------------------------------------------------------------
Msource/wLib/wHardware.cpp | 3++-
Msource/wLib/wHardware.h | 9++++++---
Dsource/wLib/wMidi.cpp | 101-------------------------------------------------------------------------------
Dsource/wLib/wMidi.h | 46----------------------------------------------
Msource/xtLib/xtHardware.h | 9++++-----
Msource/xtLib/xtUc.h | 4++--
31 files changed, 813 insertions(+), 772 deletions(-)

diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt @@ -42,12 +42,10 @@ if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) endif() # ----------------- dependencies -if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA OR ${CMAKE_PROJECT_NAME}_SYNTH_XENIA OR ${CMAKE_PROJECT_NAME}_SYNTH_N2X) - add_subdirectory(mc68k) - if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA OR ${CMAKE_PROJECT_NAME}_SYNTH_XENIA) - add_subdirectory(wLib) - endif() -endif() + +add_subdirectory(mc68k EXCLUDE_FROM_ALL) +add_subdirectory(hardwareLib EXCLUDE_FROM_ALL) +add_subdirectory(wLib EXCLUDE_FROM_ALL) # ----------------- Synths Osirus/OsTIrus diff --git a/source/hardwareLib/CMakeLists.txt b/source/hardwareLib/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hardwareLib) + +add_library(hardwareLib STATIC) + +set(SOURCES + am29f.cpp am29f.h + i2c.cpp i2c.h + i2cFlash.cpp i2cFlash.h + sciMidi.cpp sciMidi.h +) + +target_sources(hardwareLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(hardwareLib PUBLIC 68kEmu dsp56kEmu synthLib) + +set_property(TARGET hardwareLib PROPERTY FOLDER "Gearmulator") + +target_include_directories(hardwareLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/hardwareLib/am29f.cpp b/source/hardwareLib/am29f.cpp @@ -0,0 +1,138 @@ +#include "am29f.h" + +#include <cassert> + +#include "mc68k/logging.h" +#include "mc68k/mc68k.h" + +namespace hwLib +{ + Am29f::Am29f(uint8_t* _buffer, const size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr): m_buffer(_buffer), m_size(_size), m_useWriteEnable(_useWriteEnable), m_bitreverseCmdAddr(_bitreversedCmdAddr) + { + auto br = [&](uint16_t x) + { + return m_bitreverseCmdAddr ? static_cast<uint16_t>(bitreverse(x) >> 4) : x; + }; + + // Chip Erase + m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x10}}}); + + // Sector Erase + m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}}}); + + // Program + m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0xA0}}}); + } + + void Am29f::write(const uint32_t _addr, const uint16_t _data) + { + const auto reset = [this]() + { + m_currentBusCycle = 0; + m_currentCommand = -1; + }; + + if(!writeEnabled()) + { + reset(); + return; + } + + bool anyMatch = false; + + const auto d = _data & 0xff; + + for (size_t i=0; i<m_commands.size(); ++i) + { + auto& cycles = m_commands[i].cycles; + + if(m_currentBusCycle < cycles.size()) + { + const auto& c = cycles[m_currentBusCycle]; + + if(c.addr == _addr && c.data == d) + { + anyMatch = true; + + if(m_currentBusCycle == cycles.size() - 1) + m_currentCommand = static_cast<int32_t>(i); + } + } + } + + if(!anyMatch) + { + if(m_currentCommand >= 0) + { + const auto c = static_cast<CommandType>(m_currentCommand); + + execCommand(c, _addr, _data); + } + + reset(); + } + else + { + ++m_currentBusCycle; + } + } + + void Am29f::execCommand(const CommandType _command, uint32_t _addr, const uint16_t _data) + { + switch (_command) + { + case CommandType::ChipErase: + assert(false); + break; + case CommandType::SectorErase: + { + size_t sectorSizekB = 0; + switch (_addr) + { + case 0x00000: sectorSizekB = 16; break; + case 0x04000: + case 0x06000: sectorSizekB = 8; break; + case 0x08000: sectorSizekB = 32; break; + case 0x10000: + case 0x20000: + case 0x30000: + case 0x40000: + case 0x50000: + case 0x60000: + case 0x70000: sectorSizekB = 64; break; + case 0x78000: + case 0x7A000: + case 0x7C000: + // mq sends erase commands for a flash with top boot block even though a chip with bottom boot block is installed + _addr = 0x70000; + sectorSizekB = 64; + break; + default: + MCLOG("Unable to erase sector at " << MCHEX(_addr) << ", out of bounds!"); + return; + } + + MCLOG("Erasing Sector at " << MCHEX(_addr) << ", size " << MCHEX(1024 * sectorSizekB)); + for(size_t i = _addr; i< _addr + sectorSizekB * 1024; ++i) + m_buffer[i] = 0xff; + } + break; + case CommandType::Program: + { + if(_addr >= m_size) + return; + MCLOG("Programming word at " << MCHEX(_addr) << ", value " << MCHEXN(_data, 4)); + const auto old = mc68k::Mc68k::readW(m_buffer, _addr); + // "A bit cannot be programmed from a 0 back to a 1" + const auto v = _data & old; + mc68k::Mc68k::writeW(m_buffer, _addr, v); + // assert(v == _data); + break; + } + case CommandType::Invalid: + default: + assert(false); + break; + } + } +} +\ No newline at end of file diff --git a/source/hardwareLib/am29f.h b/source/hardwareLib/am29f.h @@ -0,0 +1,67 @@ +#pragma once + +#include <cstdint> +#include <cstddef> +#include <vector> + +namespace hwLib +{ + class Am29f + { + public: + struct BusCycle + { + uint16_t addr; + uint8_t data; + }; + + struct Command + { + std::vector<BusCycle> cycles; + }; + + enum class CommandType + { + Invalid = -1, + ChipErase, + SectorErase, + Program, + }; + + explicit Am29f(uint8_t* _buffer, size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr); + + void writeEnable(bool _writeEnable) + { + m_writeEnable = _writeEnable; + } + + void write(uint32_t _addr, uint16_t _data); + + private: + bool writeEnabled() const + { + return !m_useWriteEnable || m_writeEnable; + } + + static constexpr uint16_t bitreverse(uint16_t x) + { + x = ((x & 0xaaaau) >> 1) | static_cast<uint16_t>((x & 0x5555u) << 1); + x = ((x & 0xccccu) >> 2) | static_cast<uint16_t>((x & 0x3333u) << 2); + x = ((x & 0xf0f0u) >> 4) | static_cast<uint16_t>((x & 0x0f0fu) << 4); + + return ((x & 0xff00) >> 8) | static_cast<uint16_t>((x & 0x00ff) << 8); + } + + void execCommand(CommandType _command, uint32_t _addr, uint16_t _data); + + uint8_t* m_buffer; + const size_t m_size; + const bool m_useWriteEnable; + const bool m_bitreverseCmdAddr; + + std::vector<Command> m_commands; + bool m_writeEnable = false; + uint32_t m_currentBusCycle = 0; + int32_t m_currentCommand = -1; + }; +} +\ No newline at end of file diff --git a/source/hardwareLib/i2c.cpp b/source/hardwareLib/i2c.cpp @@ -0,0 +1,164 @@ +#include "i2c.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +namespace hwLib +{ + void I2c::masterWrite(const bool _sda, const bool _scl) + { + if(_sda != m_sda && _scl != m_scl) + { + assert(false && "only one pin should flip"); + return; + } + + if(_sda != m_sda) + { + m_sda = _sda; + sdaFlip(_sda); + } + else if(_scl != m_scl) + { + m_scl = _scl; + sclFlip(_scl); + } + } + + std::optional<bool> I2c::masterRead(const bool _scl) + { + if(_scl == m_scl) + return {}; + + if(m_state != State::Start) + return {}; + + m_scl = _scl; + + if(_scl) + { + if(m_nextBit == BitAck) + { + m_nextBit = Bit7; + return m_ackBit; // this was returned already in onAck() + } + + if(m_nextBit >= Bit0 && m_nextBit <= Bit7) + { + if(m_nextBit == Bit7) + m_byte = onReadByte(); + + auto res = m_byte & (1<<m_nextBit); + --m_nextBit; + return res; + } + } + + return {}; + } + + std::optional<bool> I2c::setSdaWrite(const bool _write) + { + if(m_sdaWrite == _write) + return {}; + + m_sdaWrite = _write; + + if(m_state != State::Start) + return {}; + + if(!m_sdaWrite) + { + if(m_nextBit == BitAck) + { + const auto ackBit = onAck(); + if(ackBit) + { + m_ackBit = *ackBit; + } + return ackBit; + } + } + return {}; + } + + void I2c::onStateChanged(const State _state) + { + LOG("state: " << (_state == State::Start ? "start" : "stop")); + + switch (_state) + { + case State::Stop: + m_nextBit = BitInvalid; + break; + case State::Start: + m_nextBit = Bit7; + m_byte = 0; + break; + } + } + + void I2c::onStartCondition() + { + setState(State::Start); + } + + void I2c::onStopCondition() + { + setState(State::Stop); + } + + void I2c::onByteWritten() + { + LOG("Got byte " << HEXN(byte(), 2)); + } + + void I2c::sdaFlip(const bool _sda) + { + if(m_scl) + { + if(!_sda) + onStartCondition(); + else + onStopCondition(); + } + } + + void I2c::sclFlip(const bool _scl) + { + if(_scl && m_state == State::Start) + { + if(m_nextBit >= Bit0 && m_nextBit <= Bit7) + { + LOG("next bit " << static_cast<int>(m_nextBit) << " = " << m_sda); + + if(m_nextBit == Bit7) + m_byte = 0; + + // data input + if(m_sda) + m_byte |= (1<<m_nextBit); + + --m_nextBit; + + if(m_nextBit < 0) + { + onByteWritten(); + } + } + else if(m_nextBit == BitAck) + { + m_nextBit = 7; + } + } + } + + void I2c::setState(const State _state) + { +/* if(m_state == _state) + return; +*/ m_state = _state; + onStateChanged(_state); + } +} diff --git a/source/hardwareLib/i2c.h b/source/hardwareLib/i2c.h @@ -0,0 +1,63 @@ +#pragma once + +#include <cstdint> +#include <optional> + +namespace hwLib +{ + class I2c + { + public: + enum class State + { + Stop, + Start, + }; + enum BitPos : int8_t + { + Bit7 = 7, + Bit6 = 6, + Bit5 = 5, + Bit4 = 4, + Bit3 = 3, + Bit2 = 2, + Bit1 = 1, + Bit0 = 0, + BitAck = -1, + BitInvalid = -2, + }; + + I2c() = default; + + virtual ~I2c() = default; + + void masterWrite(bool _sda, bool _scl); + virtual std::optional<bool> masterRead(bool _scl); + std::optional<bool> setSdaWrite(bool _write); + + bool sda() const { return m_sda; } + bool scl() const { return m_scl; } + uint8_t byte() const { return m_byte; } + + protected: + virtual void onStateChanged(State _state); + virtual void onStartCondition(); + virtual void onStopCondition(); + virtual void onByteWritten(); + virtual std::optional<bool> onAck() { return {}; } + virtual uint8_t onReadByte() { return 0; } + + private: + void sdaFlip(bool _sda); + void sclFlip(bool _scl); + void setState(State _state); + + bool m_sdaWrite = true; // true = write + bool m_sda = false; + bool m_scl = false; + State m_state = State::Stop; + int8_t m_nextBit = BitInvalid; + uint8_t m_byte; + bool m_ackBit = true; + }; +} diff --git a/source/hardwareLib/i2cFlash.cpp b/source/hardwareLib/i2cFlash.cpp @@ -0,0 +1,101 @@ +#include "i2cFlash.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +#include "synthLib/os.h" + +namespace hwLib +{ + void I2cFlash::saveAs(const std::string& _filename) const + { + synthLib::writeFile(_filename, m_data); + } + + void I2cFlash::onStartCondition() + { + m_state = State::ReadDeviceSelect; + I2c::onStartCondition(); + } + + void I2cFlash::onStopCondition() + { + I2c::onStopCondition(); + } + + void I2cFlash::onByteWritten() + { + I2c::onByteWritten(); + + switch (m_state) + { + case State::ReadDeviceSelect: + m_deviceSelect = byte(); + m_state = State::AckDeviceSelect; + break; + case State::ReadAddressMSB: + m_address = byte() << 8; + m_state = State::AckAddressMSB; + break; + case State::ReadAddressLSB: + m_address |= byte(); + m_state = State::AckAddressLSB; + break; + case State::ReadWriteData: + writeByte(byte()); + break; + default: + assert(false && "invalid state"); + break; + } + } + + std::optional<bool> I2cFlash::onAck() + { + switch (m_state) + { + case State::AckDeviceSelect: + m_state = State::ReadAddressMSB; + return false; + case State::AckAddressMSB: + m_state = State::ReadAddressLSB; + return false; + case State::AckAddressLSB: + m_state = State::ReadWriteData; + return false; + case State::ReadWriteData: + return false; + default: + assert(false && "invalid state"); + return true; + } + } + + uint8_t I2cFlash::onReadByte() + { + assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); + assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Read); + const auto res = m_data[m_address]; + + LOG("I2C op read from " << HEXN(m_address,4) << ", res " << HEXN(res,2)); + advanceAddress(); + return res; + } + + void I2cFlash::writeByte(uint8_t _byte) + { + assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); + assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Write); + + m_data[m_address] = _byte; + + LOG("I2C op write to " << HEXN(m_address,4) << ", val " << HEXN(_byte,2)); + advanceAddress(); + } + + void I2cFlash::advanceAddress() + { + m_address = (m_address & 0xFF80) | ((m_address + 1) & 0x7F); + } +} diff --git a/source/hardwareLib/i2cFlash.h b/source/hardwareLib/i2cFlash.h @@ -0,0 +1,66 @@ +#pragma once + +#include <array> +#include <optional> +#include <string> + +#include "i2c.h" + +namespace hwLib +{ + // M24512 64kx 8 i2c flash chip emulation + class I2cFlash : public I2c + { + public: + using Data = std::array<uint8_t, 0x10000>; + + I2cFlash() + { + m_data.fill(0xff); + } + + explicit I2cFlash(const Data& _data) : m_data(_data) + { + } + + void saveAs(const std::string& _filename) const; + + private: + enum class State + { + ReadDeviceSelect, AckDeviceSelect, + ReadAddressMSB, AckAddressMSB, + ReadAddressLSB, AckAddressLSB, + ReadWriteData, + }; + + enum DeviceSelectMask + { + Area = 0b1111'000'0, + ChipEnable = 0b0000'111'0, + Rw = 0b0000'000'1, + }; + + enum DeviceSelectValues + { + AreaMemory = 0b1010'000'0, + AreaId = 0b1011'000'0, + Read = 0b0000'000'1, + Write = 0b0000'000'0, + }; + + void onStartCondition() override; + void onStopCondition() override; + void onByteWritten() override; + std::optional<bool> onAck() override; + uint8_t onReadByte() override; + void writeByte(uint8_t _byte); + void advanceAddress(); + + State m_state = State::ReadDeviceSelect; + uint8_t m_deviceSelect = 0; + uint64_t m_address = 0; + + Data m_data; + }; +} diff --git a/source/hardwareLib/sciMidi.cpp b/source/hardwareLib/sciMidi.cpp @@ -0,0 +1,101 @@ +#include "sciMidi.h" + +#include <deque> + +#include "mc68k/qsm.h" + +namespace hwLib +{ + // pause 0.1 seconds for a sysex size of 500, delay is calculated for other sysex sizes accordingly + static constexpr float g_sysexSendDelaySeconds = 0.1f; + static constexpr uint32_t g_sysexSendDelaySize = 500; + + SciMidi::SciMidi(mc68k::Qsm& _qsm) : m_qsm(_qsm) + { + } + + void SciMidi::process(const uint32_t _numSamples) + { + std::unique_lock lock(m_mutex); + + if(m_readingSysex) + return; + + auto remainingSamples = _numSamples; + + while(!m_pendingSysexBuffers.empty()) + { + if(m_remainingSysexDelay > 0) + { + const auto sub = std::min(m_remainingSysexDelay, remainingSamples); + remainingSamples -= sub; + + m_remainingSysexDelay -= sub; + } + + if(m_remainingSysexDelay) + break; + + const auto& msg = m_pendingSysexBuffers.front(); + + for (const auto b : msg) + m_qsm.writeSciRX(b); + + m_remainingSysexDelay = static_cast<uint32_t>(static_cast<float>(msg.size()) * 44100.0f * g_sysexSendDelaySeconds / static_cast<float>(g_sysexSendDelaySize)); + + m_pendingSysexBuffers.pop_front(); + } + } + + void SciMidi::writeMidi(const uint8_t _byte) + { + std::unique_lock lock(m_mutex); + + if(_byte == 0xf0) + { + m_writingSysex = true; + } + + if(m_writingSysex) + { + m_pendingSysexMessage.push_back(_byte); + } + else + { + m_qsm.writeSciRX(_byte); + } + + if (_byte == 0xf7) + { + m_writingSysex = false; + + if (!m_pendingSysexMessage.empty()) + m_pendingSysexBuffers.push_back(std::move(m_pendingSysexMessage)); + + m_pendingSysexMessage.clear(); + } + } + + void SciMidi::readTransmitBuffer(std::vector<uint8_t>& _result) + { + std::deque<uint16_t> midiData; + m_qsm.readSciTX(midiData); + if (midiData.empty()) + return; + + _result.clear(); + _result.reserve(midiData.size()); + + for (const auto data : midiData) + { + const uint8_t d = data & 0xff; + + if(d == 0xf0) + m_readingSysex = true; + else if(d == 0xf7) + m_readingSysex = false; + + _result.push_back(d); + } + } +} diff --git a/source/hardwareLib/sciMidi.h b/source/hardwareLib/sciMidi.h @@ -0,0 +1,46 @@ +#pragma once + +#include <deque> +#include <vector> +#include <cstdint> +#include <mutex> + +namespace mc68k +{ + class Qsm; +} + +namespace hwLib +{ + class SciMidi + { + public: + explicit SciMidi(mc68k::Qsm& _qsm); + + void process(uint32_t _numSamples); + + void writeMidi(uint8_t _byte); + void writeMidi(const std::initializer_list<uint8_t>& _bytes) + { + for (const uint8_t byte : _bytes) + writeMidi(byte); + } + void writeMidi(const std::vector<uint8_t>& _bytes) + { + for (const uint8_t byte : _bytes) + writeMidi(byte); + } + void readTransmitBuffer(std::vector<uint8_t>& _result); + + private: + mc68k::Qsm& m_qsm; + + bool m_readingSysex = false; + bool m_writingSysex = false; + uint32_t m_remainingSysexDelay = 0; + + std::deque< std::vector<uint8_t> > m_pendingSysexBuffers; + std::vector<uint8_t> m_pendingSysexMessage; + std::mutex m_mutex; + }; +} diff --git a/source/mqLib/mqhardware.h b/source/mqLib/mqhardware.h @@ -10,9 +10,8 @@ #include "dsp56kEmu/dspthread.h" -#include "synthLib/midiTypes.h" +#include "hardwareLib/sciMidi.h" -#include "wLib/wMidi.h" #include "wLib/wHardware.h" namespace mqLib @@ -54,7 +53,7 @@ namespace mqLib void initVoiceExpansion(); - wLib::Midi& getMidi() override + hwLib::SciMidi& getMidi() override { return m_midi; } @@ -79,6 +78,6 @@ namespace mqLib TAudioOutputs m_audioOutputs; std::array<MqDsp,g_dspCount> m_dsps; - wLib::Midi m_midi; + hwLib::SciMidi m_midi; }; } diff --git a/source/mqLib/mqmc.cpp b/source/mqLib/mqmc.cpp @@ -33,7 +33,7 @@ namespace mqLib m_romRuntimeData.resize(ROM::size()); memcpy(m_romRuntimeData.data(), m_rom.getData(), ROM::size()); - m_flash.reset(new wLib::Am29f(m_romRuntimeData.data(), m_romRuntimeData.size(), false, true)); + m_flash.reset(new hwLib::Am29f(m_romRuntimeData.data(), m_romRuntimeData.size(), false, true)); m_memory.resize(g_memorySize, 0); diff --git a/source/mqLib/mqmc.h b/source/mqLib/mqmc.h @@ -6,7 +6,8 @@ #include "buttons.h" #include "lcd.h" #include "leds.h" -#include "wLib/am29f.h" + +#include "hardwareLib/am29f.h" #include "mc68k/mc68k.h" #include "mc68k/hdi08periph.h" @@ -66,7 +67,7 @@ namespace mqLib const ROM& m_rom; std::vector<uint8_t> m_romRuntimeData; - std::unique_ptr<wLib::Am29f> m_flash; + std::unique_ptr<hwLib::Am29f> m_flash; LCD m_lcd; Buttons m_buttons; Leds m_leds; diff --git a/source/nord/n2x/n2xLib/CMakeLists.txt b/source/nord/n2x/n2xLib/CMakeLists.txt @@ -5,13 +5,12 @@ project(n2xLib) add_library(n2xLib STATIC) set(SOURCES - i2c.cpp i2c.h n2xdevice.cpp n2xdevice.h n2xdsp.cpp n2xdsp.h + n2xflash.cpp n2xflash.h n2xfrontpanel.cpp n2xfrontpanel.h n2xhardware.cpp n2xhardware.h n2xhdi08.cpp n2xhdi08.h - n2xi2cflash.cpp n2xi2cflash.h n2xkeyboard.cpp n2xkeyboard.h n2xmc.cpp n2xmc.h n2xmiditypes.h @@ -23,7 +22,7 @@ set(SOURCES target_sources(n2xLib PRIVATE ${SOURCES}) source_group("source" FILES ${SOURCES}) -target_link_libraries(n2xLib PUBLIC synthLib 68kEmu wLib) +target_link_libraries(n2xLib PUBLIC synthLib 68kEmu hardwareLib) if(DSP56300_DEBUGGER) target_link_libraries(n2xLib PUBLIC dsp56kDebugger) diff --git a/source/nord/n2x/n2xLib/i2c.cpp b/source/nord/n2x/n2xLib/i2c.cpp @@ -1,164 +0,0 @@ -#include "i2c.h" - -#include <cassert> - -#include "dsp56kEmu/logging.h" - -namespace n2x -{ - void I2c::masterWrite(const bool _sda, const bool _scl) - { - if(_sda != m_sda && _scl != m_scl) - { - assert(false && "only one pin should flip"); - return; - } - - if(_sda != m_sda) - { - m_sda = _sda; - sdaFlip(_sda); - } - else if(_scl != m_scl) - { - m_scl = _scl; - sclFlip(_scl); - } - } - - std::optional<bool> I2c::masterRead(const bool _scl) - { - if(_scl == m_scl) - return {}; - - if(m_state != State::Start) - return {}; - - m_scl = _scl; - - if(_scl) - { - if(m_nextBit == BitAck) - { - m_nextBit = Bit7; - return m_ackBit; // this was returned already in onAck() - } - - if(m_nextBit >= Bit0 && m_nextBit <= Bit7) - { - if(m_nextBit == Bit7) - m_byte = onReadByte(); - - auto res = m_byte & (1<<m_nextBit); - --m_nextBit; - return res; - } - } - - return {}; - } - - std::optional<bool> I2c::setSdaWrite(const bool _write) - { - if(m_sdaWrite == _write) - return {}; - - m_sdaWrite = _write; - - if(m_state != State::Start) - return {}; - - if(!m_sdaWrite) - { - if(m_nextBit == BitAck) - { - const auto ackBit = onAck(); - if(ackBit) - { - m_ackBit = *ackBit; - } - return ackBit; - } - } - return {}; - } - - void I2c::onStateChanged(const State _state) - { - LOG("state: " << (_state == State::Start ? "start" : "stop")); - - switch (_state) - { - case State::Stop: - m_nextBit = BitInvalid; - break; - case State::Start: - m_nextBit = Bit7; - m_byte = 0; - break; - } - } - - void I2c::onStartCondition() - { - setState(State::Start); - } - - void I2c::onStopCondition() - { - setState(State::Stop); - } - - void I2c::onByteWritten() - { - LOG("Got byte " << HEXN(byte(), 2)); - } - - void I2c::sdaFlip(const bool _sda) - { - if(m_scl) - { - if(!_sda) - onStartCondition(); - else - onStopCondition(); - } - } - - void I2c::sclFlip(const bool _scl) - { - if(_scl && m_state == State::Start) - { - if(m_nextBit >= Bit0 && m_nextBit <= Bit7) - { - LOG("next bit " << static_cast<int>(m_nextBit) << " = " << m_sda); - - if(m_nextBit == Bit7) - m_byte = 0; - - // data input - if(m_sda) - m_byte |= (1<<m_nextBit); - - --m_nextBit; - - if(m_nextBit < 0) - { - onByteWritten(); - } - } - else if(m_nextBit == BitAck) - { - m_nextBit = 7; - } - } - } - - void I2c::setState(const State _state) - { -/* if(m_state == _state) - return; -*/ m_state = _state; - onStateChanged(_state); - } -} diff --git a/source/nord/n2x/n2xLib/i2c.h b/source/nord/n2x/n2xLib/i2c.h @@ -1,63 +0,0 @@ -#pragma once - -#include <cstdint> -#include <optional> - -namespace n2x -{ - class I2c - { - public: - enum class State - { - Stop, - Start, - }; - enum BitPos : int8_t - { - Bit7 = 7, - Bit6 = 6, - Bit5 = 5, - Bit4 = 4, - Bit3 = 3, - Bit2 = 2, - Bit1 = 1, - Bit0 = 0, - BitAck = -1, - BitInvalid = -2, - }; - - I2c() = default; - - virtual ~I2c() = default; - - void masterWrite(bool _sda, bool _scl); - virtual std::optional<bool> masterRead(bool _scl); - std::optional<bool> setSdaWrite(bool _write); - - bool sda() const { return m_sda; } - bool scl() const { return m_scl; } - uint8_t byte() const { return m_byte; } - - protected: - virtual void onStateChanged(State _state); - virtual void onStartCondition(); - virtual void onStopCondition(); - virtual void onByteWritten(); - virtual std::optional<bool> onAck() { return {}; } - virtual uint8_t onReadByte() { return 0; } - - private: - void sdaFlip(bool _sda); - void sclFlip(bool _scl); - void setState(State _state); - - bool m_sdaWrite = true; // true = write - bool m_sda = false; - bool m_scl = false; - State m_state = State::Stop; - int8_t m_nextBit = BitInvalid; - uint8_t m_byte; - bool m_ackBit = true; - }; -} diff --git a/source/nord/n2x/n2xLib/n2xflash.cpp b/source/nord/n2x/n2xLib/n2xflash.cpp diff --git a/source/nord/n2x/n2xLib/n2xflash.h b/source/nord/n2x/n2xLib/n2xflash.h @@ -0,0 +1,10 @@ +#pragma once + +#include "hardwareLib/i2cFlash.h" + +namespace n2x +{ + class Flash : public hwLib::I2cFlash + { + }; +} diff --git a/source/nord/n2x/n2xLib/n2xi2cflash.cpp b/source/nord/n2x/n2xLib/n2xi2cflash.cpp @@ -1,102 +0,0 @@ -#include "n2xi2cflash.h" - -#include <cassert> - -#include "dsp56kEmu/logging.h" - -namespace n2x -{ - template<> RomData<g_flashSize>::RomData(); - - I2cFlash::I2cFlash() - { - if(!isValid()) - data().fill(0xff); - } - - void I2cFlash::onStartCondition() - { - m_state = State::ReadDeviceSelect; - I2c::onStartCondition(); - } - - void I2cFlash::onStopCondition() - { - I2c::onStopCondition(); - } - - void I2cFlash::onByteWritten() - { - I2c::onByteWritten(); - - switch (m_state) - { - case State::ReadDeviceSelect: - m_deviceSelect = byte(); - m_state = State::AckDeviceSelect; - break; - case State::ReadAddressMSB: - m_address = byte() << 8; - m_state = State::AckAddressMSB; - break; - case State::ReadAddressLSB: - m_address |= byte(); - m_state = State::AckAddressLSB; - break; - case State::ReadWriteData: - writeByte(byte()); - break; - default: - assert(false && "invalid state"); - break; - } - } - - std::optional<bool> I2cFlash::onAck() - { - switch (m_state) - { - case State::AckDeviceSelect: - m_state = State::ReadAddressMSB; - return false; - case State::AckAddressMSB: - m_state = State::ReadAddressLSB; - return false; - case State::AckAddressLSB: - m_state = State::ReadWriteData; - return false; - case State::ReadWriteData: - return false; - default: - assert(false && "invalid state"); - return true; - } - } - - uint8_t I2cFlash::onReadByte() - { - assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); - assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Read); - const auto res = data()[m_address]; - - LOG("I2C op read from " << HEXN(m_address,4) << ", res " << HEXN(res,2)); - advanceAddress(); - return res; - } - - void I2cFlash::writeByte(uint8_t _byte) - { - assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); - assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Write); - - data()[m_address] = _byte; - - LOG("I2C op write to " << HEXN(m_address,4) << ", val " << HEXN(_byte,2)); - advanceAddress(); - } - - void I2cFlash::advanceAddress() - { - m_address = (m_address & 0xFF80) | ((m_address + 1) & 0x7F); - } -} diff --git a/source/nord/n2x/n2xLib/n2xi2cflash.h b/source/nord/n2x/n2xLib/n2xi2cflash.h @@ -1,52 +0,0 @@ -#pragma once - -#include <optional> - -#include "i2c.h" -#include "n2xromdata.h" -#include "n2xtypes.h" - -namespace n2x -{ - class I2cFlash : public I2c, public RomData<g_flashSize> - { - public: - I2cFlash(); - - private: - enum class State - { - ReadDeviceSelect, AckDeviceSelect, - ReadAddressMSB, AckAddressMSB, - ReadAddressLSB, AckAddressLSB, - ReadWriteData, - }; - - enum DeviceSelectMask - { - Area = 0b1111'000'0, - ChipEnable = 0b0000'111'0, - Rw = 0b0000'000'1, - }; - - enum DeviceSelectValues - { - AreaMemory = 0b1010'000'0, - AreaId = 0b1011'000'0, - Read = 0b0000'000'1, - Write = 0b0000'000'0, - }; - - void onStartCondition() override; - void onStopCondition() override; - void onByteWritten() override; - std::optional<bool> onAck() override; - uint8_t onReadByte() override; - void writeByte(uint8_t _byte); - void advanceAddress(); - - State m_state = State::ReadDeviceSelect; - uint8_t m_deviceSelect = 0; - uint64_t m_address = 0; - }; -} diff --git a/source/nord/n2x/n2xLib/n2xmc.cpp b/source/nord/n2x/n2xLib/n2xmc.cpp @@ -4,7 +4,7 @@ #include "n2xdsp.h" #include "n2xrom.h" -#include "synthLib/midiTypes.h" + #include "synthLib/os.h" namespace n2x diff --git a/source/nord/n2x/n2xLib/n2xmc.h b/source/nord/n2x/n2xLib/n2xmc.h @@ -2,11 +2,12 @@ #include "n2xfrontpanel.h" #include "n2xhdi08.h" -#include "n2xi2cflash.h" +#include "n2xflash.h" #include "n2xtypes.h" -#include "mc68k/hdi08.h" + #include "mc68k/mc68k.h" -#include "wLib/wMidi.h" + +#include "hardwareLib/sciMidi.h" namespace n2x { @@ -36,7 +37,7 @@ namespace n2x std::array<uint8_t, g_romSize> m_rom; std::array<uint8_t, g_ramSize> m_ram; - I2cFlash m_flash; + Flash m_flash; Hdi08DspA m_hdi08A; Hdi08DspB m_hdi08B; @@ -45,7 +46,7 @@ namespace n2x FrontPanel m_panel; uint32_t m_prevPC; - wLib::Midi m_midi; + hwLib::SciMidi m_midi; uint64_t m_totalCycles = 0; bool m_hasSentMidi = false; }; diff --git a/source/wLib/CMakeLists.txt b/source/wLib/CMakeLists.txt @@ -5,14 +5,12 @@ project(wLib) add_library(wLib STATIC) set(SOURCES - am29f.cpp am29f.h dspBootCode.h lcd.cpp lcd.h lcdfonts.cpp lcdfonts.h wDevice.cpp wDevice.h wDsp.cpp wDsp.h wHardware.cpp wHardware.h - wMidi.cpp wMidi.h wMidiTypes.h wPlugin.cpp wPlugin.h wRom.cpp wRom.h @@ -22,6 +20,6 @@ set(SOURCES target_sources(wLib PRIVATE ${SOURCES}) source_group("source" FILES ${SOURCES}) -target_link_libraries(wLib PUBLIC synthLib 68kEmu) +target_link_libraries(wLib PUBLIC synthLib 68kEmu hardwareLib) target_include_directories(wLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/wLib/am29f.cpp b/source/wLib/am29f.cpp @@ -1,139 +0,0 @@ -#include "am29f.h" - -#include <cassert> - -#include "mc68k/logging.h" -#include "mc68k/mc68k.h" - -namespace wLib -{ - Am29f::Am29f(uint8_t* _buffer, const size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr): m_buffer(_buffer), m_size(_size), m_useWriteEnable(_useWriteEnable), m_bitreverseCmdAddr(_bitreversedCmdAddr) - { - auto br = [&](uint16_t x) - { - return m_bitreverseCmdAddr ? static_cast<uint16_t>(bitreverse(x) >> 4) : x; - }; - - // Chip Erase - m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x10}}}); - - // Sector Erase - m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}}}); - - // Program - m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0xA0}}}); - } - - void Am29f::write(const uint32_t _addr, const uint16_t _data) - { - const auto reset = [this]() - { - m_currentBusCycle = 0; - m_currentCommand = -1; - }; - - if(!writeEnabled()) - { - reset(); - return; - } - - bool anyMatch = false; - - const auto d = _data & 0xff; - - for (size_t i=0; i<m_commands.size(); ++i) - { - auto& cycles = m_commands[i].cycles; - - if(m_currentBusCycle < cycles.size()) - { - const auto& c = cycles[m_currentBusCycle]; - - if(c.addr == _addr && c.data == d) - { - anyMatch = true; - - if(m_currentBusCycle == cycles.size() - 1) - m_currentCommand = static_cast<int32_t>(i); - } - } - } - - if(!anyMatch) - { - if(m_currentCommand >= 0) - { - const auto c = static_cast<CommandType>(m_currentCommand); - - execCommand(c, _addr, _data); - } - - reset(); - } - else - { - ++m_currentBusCycle; - } - } - - void Am29f::execCommand(const CommandType _command, uint32_t _addr, const uint16_t _data) - { - switch (_command) - { - case CommandType::ChipErase: - assert(false); - break; - case CommandType::SectorErase: - { - size_t sectorSizekB = 0; - switch (_addr) - { - case 0x00000: sectorSizekB = 16; break; - case 0x04000: - case 0x06000: sectorSizekB = 8; break; - case 0x08000: sectorSizekB = 32; break; - case 0x10000: - case 0x20000: - case 0x30000: - case 0x40000: - case 0x50000: - case 0x60000: - case 0x70000: sectorSizekB = 64; break; - case 0x78000: - case 0x7A000: - case 0x7C000: - // mq sends erase commands for a flash with top boot block even though a chip with bottom boot block is installed - _addr = 0x70000; - sectorSizekB = 64; - break; - default: - MCLOG("Unable to erase sector at " << MCHEX(_addr) << ", out of bounds!"); - return; - } - - MCLOG("Erasing Sector at " << MCHEX(_addr) << ", size " << MCHEX(1024 * sectorSizekB)); - for(size_t i = _addr; i< _addr + sectorSizekB * 1024; ++i) - m_buffer[i] = 0xff; - } - break; - case CommandType::Program: - { - if(_addr >= m_size) - return; - MCLOG("Programming word at " << MCHEX(_addr) << ", value " << MCHEXN(_data, 4)); - const auto old = mc68k::Mc68k::readW(m_buffer, _addr); - // "A bit cannot be programmed from a 0 back to a 1" - const auto v = _data & old; - mc68k::Mc68k::writeW(m_buffer, _addr, v); - // assert(v == _data); - break; - } - case CommandType::Invalid: - default: - assert(false); - break; - } - } - -} -\ No newline at end of file diff --git a/source/wLib/am29f.h b/source/wLib/am29f.h @@ -1,67 +0,0 @@ -#pragma once - -#include <cstdint> -#include <cstddef> -#include <vector> - -namespace wLib -{ - class Am29f - { - public: - struct BusCycle - { - uint16_t addr; - uint8_t data; - }; - - struct Command - { - std::vector<BusCycle> cycles; - }; - - enum class CommandType - { - Invalid = -1, - ChipErase, - SectorErase, - Program, - }; - - explicit Am29f(uint8_t* _buffer, size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr); - - void writeEnable(bool _writeEnable) - { - m_writeEnable = _writeEnable; - } - - void write(uint32_t _addr, uint16_t _data); - - private: - bool writeEnabled() const - { - return !m_useWriteEnable || m_writeEnable; - } - - static constexpr uint16_t bitreverse(uint16_t x) - { - x = ((x & 0xaaaau) >> 1) | static_cast<uint16_t>((x & 0x5555u) << 1); - x = ((x & 0xccccu) >> 2) | static_cast<uint16_t>((x & 0x3333u) << 2); - x = ((x & 0xf0f0u) >> 4) | static_cast<uint16_t>((x & 0x0f0fu) << 4); - - return ((x & 0xff00) >> 8) | static_cast<uint16_t>((x & 0x00ff) << 8); - } - - void execCommand(CommandType _command, uint32_t _addr, uint16_t _data); - - uint8_t* m_buffer; - const size_t m_size; - const bool m_useWriteEnable; - const bool m_bitreverseCmdAddr; - - std::vector<Command> m_commands; - bool m_writeEnable = false; - uint32_t m_currentBusCycle = 0; - int32_t m_currentCommand = -1; - }; -} -\ No newline at end of file diff --git a/source/wLib/wHardware.cpp b/source/wLib/wHardware.cpp @@ -1,10 +1,11 @@ #include "wHardware.h" -#include "wMidi.h" #include "dsp56kEmu/audio.h" #include "synthLib/midiBufferParser.h" +#include "hardwareLib/sciMidi.h" + #include "mc68k/mc68k.h" namespace wLib diff --git a/source/wLib/wHardware.h b/source/wLib/wHardware.h @@ -8,6 +8,11 @@ #include "synthLib/midiTypes.h" +namespace hwLib +{ + class SciMidi; +} + namespace mc68k { class Mc68k; @@ -20,15 +25,13 @@ namespace dsp56k namespace wLib { - class Midi; - class Hardware { public: Hardware(const double& _samplerate); virtual ~Hardware() = default; - virtual Midi& getMidi() = 0; + virtual hwLib::SciMidi& getMidi() = 0; virtual mc68k::Mc68k& getUc() = 0; void haltDSP(); diff --git a/source/wLib/wMidi.cpp b/source/wLib/wMidi.cpp @@ -1,101 +0,0 @@ -#include "wMidi.h" - -#include <deque> - -#include "mc68k/qsm.h" - -namespace wLib -{ - // pause 0.1 seconds for a sysex size of 500, delay is calculated for other sysex sizes accordingly - static constexpr float g_sysexSendDelaySeconds = 0.1f; - static constexpr uint32_t g_sysexSendDelaySize = 500; - - Midi::Midi(mc68k::Qsm& _qsm) : m_qsm(_qsm) - { - } - - void Midi::process(const uint32_t _numSamples) - { - std::unique_lock lock(m_mutex); - - if(m_readingSysex) - return; - - auto remainingSamples = _numSamples; - - while(!m_pendingSysexBuffers.empty()) - { - if(m_remainingSysexDelay > 0) - { - const auto sub = std::min(m_remainingSysexDelay, remainingSamples); - remainingSamples -= sub; - - m_remainingSysexDelay -= sub; - } - - if(m_remainingSysexDelay) - break; - - const auto& msg = m_pendingSysexBuffers.front(); - - for (const auto b : msg) - m_qsm.writeSciRX(b); - - m_remainingSysexDelay = static_cast<uint32_t>(static_cast<float>(msg.size()) * 44100.0f * g_sysexSendDelaySeconds / static_cast<float>(g_sysexSendDelaySize)); - - m_pendingSysexBuffers.pop_front(); - } - } - - void Midi::writeMidi(const uint8_t _byte) - { - std::unique_lock lock(m_mutex); - - if(_byte == 0xf0) - { - m_writingSysex = true; - } - - if(m_writingSysex) - { - m_pendingSysexMessage.push_back(_byte); - } - else - { - m_qsm.writeSciRX(_byte); - } - - if (_byte == 0xf7) - { - m_writingSysex = false; - - if (!m_pendingSysexMessage.empty()) - m_pendingSysexBuffers.push_back(std::move(m_pendingSysexMessage)); - - m_pendingSysexMessage.clear(); - } - } - - void Midi::readTransmitBuffer(std::vector<uint8_t>& _result) - { - std::deque<uint16_t> midiData; - m_qsm.readSciTX(midiData); - if (midiData.empty()) - return; - - _result.clear(); - _result.reserve(midiData.size()); - - for (const auto data : midiData) - { - const uint8_t d = data & 0xff; - - if(d == 0xf0) - m_readingSysex = true; - else if(d == 0xf7) - m_readingSysex = false; - - _result.push_back(d); - } - } -} diff --git a/source/wLib/wMidi.h b/source/wLib/wMidi.h @@ -1,46 +0,0 @@ -#pragma once - -#include <deque> -#include <vector> -#include <cstdint> -#include <mutex> - -namespace mc68k -{ - class Qsm; -} - -namespace wLib -{ - class Midi - { - public: - explicit Midi(mc68k::Qsm& _qsm); - - void process(uint32_t _numSamples); - - void writeMidi(uint8_t _byte); - void writeMidi(const std::initializer_list<uint8_t>& _bytes) - { - for (const uint8_t byte : _bytes) - writeMidi(byte); - } - void writeMidi(const std::vector<uint8_t>& _bytes) - { - for (const uint8_t byte : _bytes) - writeMidi(byte); - } - void readTransmitBuffer(std::vector<uint8_t>& _result); - - private: - mc68k::Qsm& m_qsm; - - bool m_readingSysex = false; - bool m_writingSysex = false; - uint32_t m_remainingSysexDelay = 0; - - std::deque< std::vector<uint8_t> > m_pendingSysexBuffers; - std::vector<uint8_t> m_pendingSysexMessage; - std::mutex m_mutex; - }; -} diff --git a/source/xtLib/xtHardware.h b/source/xtLib/xtHardware.h @@ -1,14 +1,13 @@ #pragma once -#include <string> - #include "xtDSP.h" #include "xtRom.h" #include "xtUc.h" #include "dsp56kEmu/dspthread.h" -#include "wLib/wMidi.h" +#include "hardwareLib/sciMidi.h" + #include "wLib/wHardware.h" namespace xt @@ -50,7 +49,7 @@ namespace xt void initVoiceExpansion(); - wLib::Midi& getMidi() override + hwLib::SciMidi& getMidi() override { return m_midi; } @@ -70,6 +69,6 @@ namespace xt TAudioInputs m_audioInputs; TAudioOutputs m_audioOutputs; std::array<DSP,g_dspCount> m_dsps; - wLib::Midi m_midi; + hwLib::SciMidi m_midi; }; } diff --git a/source/xtLib/xtUc.h b/source/xtLib/xtUc.h @@ -9,7 +9,7 @@ #include "mc68k/mc68k.h" #include "mc68k/hdi08periph.h" -#include "wLib/am29f.h" +#include "hardwareLib/am29f.h" namespace xt { @@ -51,7 +51,7 @@ namespace xt std::array<uint8_t, g_romSize> m_romRuntimeData; xtHdi08A m_hdiA; - wLib::Am29f m_flash; + hwLib::Am29f m_flash; Pic m_pic; Lcd m_lcd;