├── .clang-format ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .gitmodules ├── .vscode ├── c_cpp_properties.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── doc ├── opl-params.md ├── opm-params.md └── sd1-params.md ├── libnotesalad ├── CMakeLists.txt ├── CallbackOPLDevice.cpp ├── CallbackOPLDevice.h ├── CallbackOPMDevice.cpp ├── CallbackOPMDevice.h ├── CallbackSD1Device.cpp ├── CallbackSD1Device.h ├── EmulatorBase.cpp ├── EmulatorBase.h ├── IMIDIManager.cpp ├── IMIDIManager.h ├── OPL2MIDIManager.cpp ├── OPL2MIDIManager.h ├── OPL3MIDIManager.cpp ├── OPL3MIDIManager.h ├── OPLEmulator.cpp ├── OPLEmulator.h ├── OPMEmulator.cpp ├── OPMEmulator.h ├── OPMMIDIManager.cpp ├── OPMMIDIManager.h ├── SD1MIDIManager.cpp ├── SD1MIDIManager.h ├── midi.cpp ├── midi.h ├── opl.cpp ├── opl.h ├── oplsd1.cpp ├── oplsd1.h ├── opm.cpp ├── opm.h ├── resampler │ ├── CMakeLists.txt │ ├── arch.h │ ├── resample.c │ └── speex_resampler.h ├── sd1.cpp └── sd1.h ├── libnotesaladcore ├── CMakeLists.txt ├── library.properties ├── src │ ├── 7BitEncoding.cpp │ ├── 7BitEncoding.h │ ├── BufferIO.cpp │ ├── BufferIO.h │ ├── CMakeLists.txt │ ├── MIDI │ │ ├── BasicVoiceAllocator.h │ │ ├── CMakeLists.txt │ │ ├── GlobalParams.h │ │ ├── IMIDIDriver.h │ │ ├── INoteManager.h │ │ ├── IToneGenerator.h │ │ ├── IVoiceAllocator.h │ │ ├── LFO.cpp │ │ ├── LFO.h │ │ ├── MIDICommon.h │ │ ├── MIDIDriver.h │ │ ├── MIDIParser.cpp │ │ ├── MIDIParser.h │ │ ├── MonoNoteManager.cpp │ │ ├── MonoNoteManager.h │ │ ├── OPL │ │ │ ├── CMakeLists.txt │ │ │ ├── DefaultOPLPatchManager.cpp │ │ │ ├── DefaultOPLPatchManager.h │ │ │ ├── OPL2MIDISystem.cpp │ │ │ ├── OPL2MIDISystem.h │ │ │ ├── OPL2VoiceAllocator.cpp │ │ │ ├── OPL2VoiceAllocator.h │ │ │ ├── OPL3MIDISystem.cpp │ │ │ ├── OPL3MIDISystem.h │ │ │ ├── OPL3VoiceAllocator.cpp │ │ │ ├── OPL3VoiceAllocator.h │ │ │ ├── OPLGMPatchData.cpp │ │ │ ├── OPLGMPatchData.h │ │ │ ├── OPLParamInfo.g.cpp │ │ │ ├── OPLParamInfo.h │ │ │ ├── OPLToneGenerator.cpp │ │ │ └── OPLToneGenerator.h │ │ ├── OPM │ │ │ ├── CMakeLists.txt │ │ │ ├── OPMMIDISystem.cpp │ │ │ ├── OPMMIDISystem.h │ │ │ ├── OPMParamInfo.g.cpp │ │ │ ├── OPMParamInfo.h │ │ │ ├── OPMToneGenerator.cpp │ │ │ └── OPMToneGenerator.h │ │ ├── ParamIDs.h │ │ ├── ParamInfo.g.cpp │ │ ├── ParamInfo.h │ │ ├── Patch.h │ │ ├── PatchManagerBase.h │ │ ├── PatchParams.cpp │ │ ├── PatchParams.h │ │ ├── PatchSerialization.h │ │ ├── PolyNoteManager.h │ │ ├── RAMPatchManager.h │ │ ├── SD1 │ │ │ ├── CMakeLists.txt │ │ │ ├── DefaultSD1PatchManager.cpp │ │ │ ├── DefaultSD1PatchManager.h │ │ │ ├── OPLSD1MIDISystem.cpp │ │ │ ├── OPLSD1MIDISystem.h │ │ │ ├── OPLSD1ToneGenerator.cpp │ │ │ ├── OPLSD1ToneGenerator.h │ │ │ ├── SD1MIDISystem.cpp │ │ │ ├── SD1MIDISystem.h │ │ │ ├── SD1ParamInfo.g.cpp │ │ │ ├── SD1ParamInfo.h │ │ │ ├── SD1Patches.cpp │ │ │ ├── SD1Patches.h │ │ │ ├── SD1ToneGenerator.cpp │ │ │ └── SD1ToneGenerator.h │ │ ├── SysEx.cpp │ │ ├── SysEx.h │ │ ├── TimeSource.h │ │ ├── ToneController.h │ │ ├── VoiceStatus.cpp │ │ └── VoiceStatus.h │ ├── OPL │ │ ├── BufferedOPLDevice.cpp │ │ ├── BufferedOPLDevice.h │ │ ├── CMakeLists.txt │ │ ├── OPLDeviceBase.cpp │ │ ├── OPLDeviceBase.h │ │ ├── OPLReadWriteDeviceBase.cpp │ │ ├── OPLReadWriteDeviceBase.h │ │ ├── OPLRegisterSet.cpp │ │ ├── OPLRegisterSet.h │ │ ├── OPLTone.cpp │ │ ├── OPLTone.h │ │ ├── OPLUtils.cpp │ │ └── OPLUtils.h │ ├── OPM │ │ ├── BufferedOPMDevice.cpp │ │ ├── BufferedOPMDevice.h │ │ ├── CMakeLists.txt │ │ ├── OPMDeviceBase.cpp │ │ ├── OPMDeviceBase.h │ │ ├── OPMRegisterSet.cpp │ │ ├── OPMRegisterSet.h │ │ ├── OPMTone.cpp │ │ ├── OPMTone.h │ │ ├── OPMUtils.cpp │ │ └── OPMUtils.h │ ├── SD1 │ │ ├── CMakeLists.txt │ │ ├── SD1DeviceBase.cpp │ │ ├── SD1DeviceBase.h │ │ ├── SD1OPLAdaptor.cpp │ │ ├── SD1OPLAdaptor.h │ │ ├── SD1Tone.cpp │ │ ├── SD1Tone.h │ │ ├── SD1Utils.cpp │ │ └── SD1Utils.h │ ├── Utils.cpp │ ├── Utils.h │ └── notesaladcore.h └── test │ ├── 7BitEncoding.cpp │ ├── CMakeLists.txt │ ├── MIDI │ ├── IMIDIDriver_test.h │ ├── IToneGenerator_test.h │ ├── MIDIDriver.cpp │ ├── MIDIParser.cpp │ ├── OPL │ │ └── OPLParamInfo.cpp │ ├── OPM │ │ └── OPMParamInfo.cpp │ ├── ParamInfo.h │ ├── PatchSerialization.cpp │ ├── SD1 │ │ └── SD1ParamInfo.cpp │ ├── TestTone.cpp │ └── TestTone.h │ ├── OPL │ └── OPLReadWriteDeviceBase.cpp │ └── SD1 │ └── SD1Utils.cpp ├── python ├── .gitignore ├── .pylintrc ├── notesalad │ ├── __init__.py │ ├── opl.py │ ├── opm.py │ ├── patch.py │ ├── resources │ │ ├── __init__.py │ │ ├── opl.json │ │ ├── opm.json │ │ ├── sd1.json │ │ └── universal.json │ └── sd1.py └── pyproject.toml ├── resources └── params │ ├── opl.json │ ├── opm.json │ ├── sd1.json │ └── universal.json ├── scripts ├── codegen │ └── gen_param_info_cpp.py ├── doc │ └── gen_params_md.py ├── generate_all.sh └── params │ ├── gen_opl.py │ ├── gen_opm.py │ ├── gen_sd1.py │ └── gen_universal.py └── web ├── .gitignore ├── .npmrc ├── .prettierrc ├── LICENSE ├── package-lock.json ├── package.json ├── src ├── Worklet │ ├── Emulators │ │ ├── EmulatorBase.js │ │ ├── OPLEmulator.js │ │ └── OPMEmulator.js │ ├── MIDI │ │ ├── MIDIDriverBase.js │ │ ├── OPL3MIDIDriver.js │ │ └── OPMMIDIDriver.js │ ├── NoteSaladLibrary.js │ ├── Processors │ │ ├── OPLProcessor.js │ │ ├── OPMProcessor.js │ │ └── OPXProcessorBase.js │ └── index.js └── index.js └── worklet.webpack.config.js /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: WebKit 2 | IndentExternBlock: Indent 3 | BreakConstructorInitializers: BeforeColon 4 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 5 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | on: 3 | push: 4 | workflow_dispatch: 5 | inputs: 6 | publish_npm_pkg: 7 | description: "Publish NPM package" 8 | required: true 9 | default: false 10 | jobs: 11 | build-desktop: 12 | name: Build & test desktop 13 | runs-on: ubuntu-20.04 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | - name: Configure 20 | run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. 21 | - name: Build 22 | run: cd build && make -j `nproc --all` 23 | - name: Test 24 | run: cd build && make -j `nproc --all` libnotesaladcore_test && ctest 25 | build-web: 26 | name: Build web 27 | runs-on: ubuntu-20.04 28 | env: 29 | NTSLD_EMSDK_VERSION: 3.1.41 30 | steps: 31 | - name: Check out notesalad 32 | uses: actions/checkout@v3 33 | with: 34 | submodules: recursive 35 | path: notesalad 36 | - name: Check out emsdk 37 | uses: actions/checkout@v3 38 | with: 39 | repository: emscripten-core/emsdk 40 | path: emsdk 41 | - name: Install emsdk 42 | run: cd emsdk && ./emsdk install "$NTSLD_EMSDK_VERSION" 43 | - name: Activate emsdk 44 | run: cd emsdk && ./emsdk activate "$NTSLD_EMSDK_VERSION" 45 | - name: Build npm package 46 | run: source emsdk/emsdk_env.sh && cd notesalad/web && npm install && npm run build 47 | - name: Set up npmrc 48 | run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc 49 | - name: Publish npm package 50 | run: cd notesalad/web && npm publish 51 | if: ${{ github.event.inputs.publish_npm_pkg }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | **/.DS_Store 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ymfm"] 2 | path = libnotesalad/ymfm 3 | url = https://github.com/aaronsgiles/ymfm.git 4 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "macFrameworkPath": [ 10 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" 11 | ], 12 | "compilerPath": "/usr/bin/clang", 13 | "cStandard": "c17", 14 | "cppStandard": "c++98", 15 | "intelliSenseMode": "macos-clang-arm64", 16 | "configurationProvider": "ms-vscode.cmake-tools" 17 | } 18 | ], 19 | "version": 4 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "./pyfm/env/bin/python", 3 | "editor.formatOnSave": true, 4 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 5 | "files.associations": { 6 | "vector": "cpp", 7 | "algorithm": "cpp", 8 | "memory": "cpp", 9 | "bitset": "cpp", 10 | "cstdint": "cpp", 11 | "__hash_table": "cpp", 12 | "functional": "cpp", 13 | "__functional_base": "cpp", 14 | "array": "cpp", 15 | "istream": "cpp", 16 | "locale": "cpp", 17 | "tuple": "cpp", 18 | "utility": "cpp", 19 | "variant": "cpp", 20 | "cstdio": "cpp", 21 | "cstring": "cpp", 22 | "ios": "cpp", 23 | "__bit_reference": "cpp", 24 | "__bits": "cpp", 25 | "__config": "cpp", 26 | "__debug": "cpp", 27 | "__errc": "cpp", 28 | "__locale": "cpp", 29 | "__mutex_base": "cpp", 30 | "__node_handle": "cpp", 31 | "__nullptr": "cpp", 32 | "__split_buffer": "cpp", 33 | "__string": "cpp", 34 | "__threading_support": "cpp", 35 | "__tree": "cpp", 36 | "__tuple": "cpp", 37 | "any": "cpp", 38 | "atomic": "cpp", 39 | "bit": "cpp", 40 | "cctype": "cpp", 41 | "chrono": "cpp", 42 | "clocale": "cpp", 43 | "cmath": "cpp", 44 | "complex": "cpp", 45 | "cstdarg": "cpp", 46 | "cstddef": "cpp", 47 | "cstdlib": "cpp", 48 | "ctime": "cpp", 49 | "cwchar": "cpp", 50 | "cwctype": "cpp", 51 | "deque": "cpp", 52 | "exception": "cpp", 53 | "forward_list": "cpp", 54 | "fstream": "cpp", 55 | "initializer_list": "cpp", 56 | "iomanip": "cpp", 57 | "iosfwd": "cpp", 58 | "iostream": "cpp", 59 | "iterator": "cpp", 60 | "limits": "cpp", 61 | "list": "cpp", 62 | "map": "cpp", 63 | "mutex": "cpp", 64 | "new": "cpp", 65 | "optional": "cpp", 66 | "ostream": "cpp", 67 | "ratio": "cpp", 68 | "set": "cpp", 69 | "sstream": "cpp", 70 | "stack": "cpp", 71 | "stdexcept": "cpp", 72 | "streambuf": "cpp", 73 | "string": "cpp", 74 | "string_view": "cpp", 75 | "system_error": "cpp", 76 | "type_traits": "cpp", 77 | "typeinfo": "cpp", 78 | "unordered_map": "cpp", 79 | "unordered_set": "cpp", 80 | "*.ipp": "cpp" 81 | }, 82 | "cmake.configureOnOpen": true 83 | } 84 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build web", 8 | "type": "shell", 9 | "command": ". emsdk_env.sh && cd web && npm pack" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | set(CMAKE_CXX_STANDARD 11) 4 | 5 | project(libnotesalad) 6 | option(WASM "Compile to WASM" OFF) 7 | 8 | add_compile_options(-Wall -Werror) 9 | 10 | enable_testing() 11 | 12 | add_subdirectory(libnotesaladcore) 13 | add_subdirectory(libnotesalad) 14 | 15 | install( 16 | TARGETS notesalad 17 | RUNTIME 18 | DESTINATION lib 19 | PUBLIC_HEADER 20 | DESTINATION include/notesalad 21 | ) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Dan Fry 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Note Salad 2 | 3 | An embeddable, device independent MIDI implementation in C++ with drivers for the Yamaha OPL (YM3812/YMF262), OPM (YM2151) and SD-1 (YMF825) FM synthesizer chips. 4 | 5 | _If you're looking for a quick and simple way to try out the MIDI implementation for OPL or OPM, check out [Note Salad Editor](https://github.com/danielrfry/notesalad-editor/), which is a browser-based patch editor and software synth that emulates those chips._ 6 | 7 | Note Salad parses a MIDI data stream and drives a hardware or software synthesizer by calling abstract methods to start/stop sounds and set synth parameters on individual monophonic channels. It handles channel allocation and basic patch management, and provides software LFOs and mapping of MIDI controls to synth parameters. 8 | 9 | **This code should be considered experimental**. It's a hobby project that I hadn't originally intended to release, with fun taking priority over code quality or documentation. There will be plenty of rough edges. 10 | 11 | ## Components 12 | 13 | ### In this repository 14 | 15 | - **libnotesaladcore**: the core MIDI implementation. Can be built as an RP2040 or Teensyduino library for embedding, or linked into libnotesalad for use on desktop/web platforms. 16 | - **libnotesalad**: library incorporating **libnotesaladcore** and OPL and OPM emulators (from [ymfm](https://github.com/aaronsgiles/ymfm/)). Targets Linux, macOS and web (via WASM). Allows use of the MIDI implementation without dedicated hardware. 17 | 18 | ### In other repositories 19 | 20 | - **[Note Salad Editor](https://github.com/danielrfry/notesalad-editor/)**: GUI patch editor and software synthesizer web app for OPL, OPM and SD-1. Browser based, requiring no additional hardware or software installation. 21 | - **[Note Salad Tools](https://github.com/danielrfry/notesalad-tools/)**: a collection of tools written in Python for converting, 'disassembling' and editing VGM/DRO files, converting MIDI to VGM, playing or rendering VGM/MID files to WAV etc. using **libnotesalad**. 22 | 23 | ## Building libnotesalad for Linux/macOS 24 | 25 | ### Requirements 26 | 27 | - [CMake](https://cmake.org/) >= 3.14 28 | 29 | ### Building 30 | 31 | Clone the repository and its submodules: 32 | 33 | ``` 34 | git clone --recurse-submodules https://github.com/danielrfry/notesalad.git 35 | ``` 36 | 37 | Generate makefiles using CMake and build: 38 | 39 | ``` 40 | cd notesalad 41 | cmake -DCMAKE_BUILD_TYPE=Release -B build 42 | cd build 43 | make 44 | ``` 45 | 46 | Install: 47 | 48 | ``` 49 | sudo make install 50 | ``` 51 | 52 | ### Running tests 53 | 54 | There aren't many tests, but those that do exist can be built and run as follows: 55 | 56 | ``` 57 | cd build 58 | make libnotesaladcore_test 59 | ctest 60 | ``` 61 | 62 | ## Building libnotesalad for web 63 | 64 | ### Requirements 65 | 66 | - [CMake](https://cmake.org/) >= 3.14 67 | - [Emscripten SDK](https://emscripten.org/) 68 | - [node.js](https://nodejs.org/) - latest LTS version 69 | 70 | ### Building 71 | 72 | Install npm packages: 73 | 74 | ``` 75 | cd web 76 | npm install 77 | ``` 78 | 79 | Build: 80 | 81 | ``` 82 | npm run build 83 | ``` 84 | 85 | ## Embedding libnotesaladcore 86 | 87 | ### In RP2040 projects 88 | 89 | libnotesaladcore attempts to follow the Raspberry Pi Pico SDK convention of packaging code as CMake interface libraries. It can be added to an RP2040 project by directly including the libnotesaladcore subdirectory in your project's `CMakeLists.txt`, for example: 90 | 91 | ``` 92 | add_subdirectory(../notesalad/libnotesaladcore/src notesaladcore) 93 | ``` 94 | 95 | Then, add libnotesaladcore to your project's link step, for example: 96 | 97 | ``` 98 | target_link_libraries(your-project pico_stdlib notesaladcore) 99 | ``` 100 | 101 | ### In Teensyduino projects 102 | 103 | A `library.properties` file is included allowing libnotesaladcore to be used as a Teensyduino library. Arduino targets other than the ARM-based Teensy series may also work but have not been tested. 104 | 105 | I find the most convenient way to use the library with Teensyduino is to create a symbolic link to the directory containing `library.properties` in Teensyduino's `libraries` directory. For example: 106 | 107 | ``` 108 | ln -s ~/src/notesalad/libnotesaladcore ~/Documents/Arduino/libraries/ 109 | ``` 110 | 111 | Replace `~/src/notesalad` with the directory containing your working copy of the Note Salad repository, and `~/Documents/Arduino/libraries` with the location of Teensyduino's `libraries` directory. 112 | -------------------------------------------------------------------------------- /libnotesalad/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_compile_definitions( 2 | OUTSIDE_SPEEX 3 | RANDOM_PREFIX=ntsld 4 | FLOATING_POINT 5 | ) 6 | 7 | set(CMAKE_CXX_STANDARD 14) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | if (WASM) 11 | message(STATUS "Setting compilation target to WASM") 12 | add_executable(notesalad) 13 | set_target_properties(notesalad PROPERTIES OUTPUT_NAME libnotesalad) 14 | set_target_properties(notesalad 15 | PROPERTIES LINK_FLAGS "\ 16 | -O1 \ 17 | -s WASM=1 \ 18 | -s BINARYEN_ASYNC_COMPILATION=0 \ 19 | -s SINGLE_FILE=1 \ 20 | -s ENVIRONMENT='web' \ 21 | -s MODULARIZE=1 \ 22 | -s EXPORT_ES6=1 \ 23 | -s EXPORTED_FUNCTIONS='[ \ 24 | _ntsld_midi_set_recv_cbk, \ 25 | _ntsld_midi_send, \ 26 | _ntsld_midi_set_time, \ 27 | _ntsld_midi_update, \ 28 | _ntsld_midi_reset, \ 29 | _ntsld_midi_delete, \ 30 | _ntsld_opl_emu_getsamples, \ 31 | _ntsld_opl_emu_new, \ 32 | _ntsld_opl_cbkdev_new, \ 33 | _ntsld_opl_delete, \ 34 | _ntsld_opl_reset, \ 35 | _ntsld_opl_write, \ 36 | _ntsld_opl2midi_new, \ 37 | _ntsld_opl3midi_new, \ 38 | _ntsld_oplsd1_new, \ 39 | _ntsld_opm_emu_getsamples, \ 40 | _ntsld_opm_emu_new, \ 41 | _ntsld_opm_cbkdev_new, \ 42 | _ntsld_opm_delete, \ 43 | _ntsld_opm_reset, \ 44 | _ntsld_opm_write, \ 45 | _ntsld_opmmidi_new, \ 46 | _ntsld_sd1_cbkdev_new, \ 47 | _ntsld_sd1_delete, \ 48 | _ntsld_sd1midi_new, \ 49 | _calloc, \ 50 | _free \ 51 | ]' \ 52 | -s EXPORTED_RUNTIME_METHODS='[ \ 53 | getValue, \ 54 | setValue, \ 55 | addFunction, \ 56 | removeFunction \ 57 | ]' \ 58 | -s RESERVED_FUNCTION_POINTERS=20" 59 | ) 60 | else() 61 | add_library(notesalad SHARED) 62 | endif() 63 | 64 | target_sources(notesalad 65 | PRIVATE 66 | CallbackOPLDevice.cpp 67 | CallbackOPLDevice.h 68 | CallbackOPMDevice.cpp 69 | CallbackOPMDevice.h 70 | CallbackSD1Device.cpp 71 | CallbackSD1Device.h 72 | EmulatorBase.cpp 73 | EmulatorBase.h 74 | IMIDIManager.cpp 75 | IMIDIManager.h 76 | OPL2MIDIManager.cpp 77 | OPL2MIDIManager.h 78 | OPL3MIDIManager.cpp 79 | OPL3MIDIManager.h 80 | OPLEmulator.cpp 81 | OPLEmulator.h 82 | OPMEmulator.cpp 83 | OPMEmulator.h 84 | OPMMIDIManager.cpp 85 | OPMMIDIManager.h 86 | SD1MIDIManager.cpp 87 | SD1MIDIManager.h 88 | midi.cpp 89 | midi.h 90 | opl.cpp 91 | opl.h 92 | oplsd1.cpp 93 | oplsd1.h 94 | opm.cpp 95 | opm.h 96 | sd1.cpp 97 | sd1.h 98 | ) 99 | 100 | add_subdirectory(resampler) 101 | 102 | add_library(ymfm STATIC 103 | ymfm/src/ymfm_opl.cpp 104 | ymfm/src/ymfm_pcm.cpp 105 | ymfm/src/ymfm_adpcm.cpp 106 | ymfm/src/ymfm_opm.cpp 107 | ) 108 | target_compile_options(ymfm PRIVATE -Wno-deprecated-declarations -fPIC) 109 | 110 | target_link_libraries(notesalad PUBLIC notesaladcore ymfm) 111 | target_include_directories(notesalad 112 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 113 | ) 114 | -------------------------------------------------------------------------------- /libnotesalad/CallbackOPLDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "CallbackOPLDevice.h" 2 | 3 | CallbackOPLDevice::CallbackOPLDevice(void* context, 4 | OPLWriteCallback writeCallback, 5 | OPLResetCallback resetCallback) 6 | : context(context), writeCallback(writeCallback), resetCallback(resetCallback) 7 | { 8 | } 9 | 10 | CallbackOPLDevice::~CallbackOPLDevice() 11 | { 12 | } 13 | 14 | void CallbackOPLDevice::hardReset() 15 | { 16 | resetCallback(this->context); 17 | } 18 | 19 | void CallbackOPLDevice::write(uint16_t addr, uint8_t data) 20 | { 21 | writeCallback(this->context, addr, data); 22 | } 23 | -------------------------------------------------------------------------------- /libnotesalad/CallbackOPLDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OPL/OPLDeviceBase.h" 4 | 5 | extern "C" { 6 | typedef void (*OPLWriteCallback)(void* context, uint16_t addr, uint8_t data); 7 | typedef void (*OPLResetCallback)(void* context); 8 | } 9 | 10 | class CallbackOPLDevice : public OPLDeviceBase { 11 | public: 12 | void* context; 13 | OPLWriteCallback writeCallback; 14 | OPLResetCallback resetCallback; 15 | CallbackOPLDevice(void* context, OPLWriteCallback writeCallback, 16 | OPLResetCallback resetCallback); 17 | virtual ~CallbackOPLDevice(); 18 | virtual void hardReset(); 19 | virtual void write(uint16_t addr, uint8_t data); 20 | }; 21 | -------------------------------------------------------------------------------- /libnotesalad/CallbackOPMDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "CallbackOPMDevice.h" 2 | 3 | CallbackOPMDevice::CallbackOPMDevice(void* context, 4 | OPMWriteCallback writeCallback, 5 | OPMResetCallback resetCallback) 6 | : context(context), writeCallback(writeCallback), resetCallback(resetCallback) 7 | { 8 | } 9 | 10 | CallbackOPMDevice::~CallbackOPMDevice() 11 | { 12 | } 13 | 14 | void CallbackOPMDevice::hardReset() 15 | { 16 | resetCallback(this->context); 17 | } 18 | 19 | void CallbackOPMDevice::write(uint8_t addr, uint8_t data) 20 | { 21 | writeCallback(this->context, addr, data); 22 | } 23 | -------------------------------------------------------------------------------- /libnotesalad/CallbackOPMDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OPM/OPMDeviceBase.h" 4 | 5 | extern "C" { 6 | typedef void (*OPMWriteCallback)(void* context, uint8_t addr, uint8_t data); 7 | typedef void (*OPMResetCallback)(void* context); 8 | } 9 | 10 | class CallbackOPMDevice : public OPMDeviceBase { 11 | public: 12 | void* context; 13 | OPMWriteCallback writeCallback; 14 | OPMResetCallback resetCallback; 15 | CallbackOPMDevice(void* context, OPMWriteCallback writeCallback, 16 | OPMResetCallback resetCallback); 17 | virtual ~CallbackOPMDevice(); 18 | virtual void hardReset(); 19 | virtual void write(uint8_t addr, uint8_t data); 20 | }; 21 | -------------------------------------------------------------------------------- /libnotesalad/CallbackSD1Device.cpp: -------------------------------------------------------------------------------- 1 | #include "CallbackSD1Device.h" 2 | 3 | CallbackSD1Device::CallbackSD1Device(void* context, SD1WriteCallback writeCbk, 4 | SD1DelayCallback delayCbk) : context(context), 5 | writeCbk(writeCbk), delayCbk(delayCbk) 6 | { 7 | } 8 | 9 | CallbackSD1Device::~CallbackSD1Device() 10 | { 11 | } 12 | 13 | void CallbackSD1Device::delayMicroseconds(uint32_t us) 14 | { 15 | this->delayCbk(context, us); 16 | } 17 | 18 | void CallbackSD1Device::write(const uint8_t* data, const uint16_t len) 19 | { 20 | this->writeCbk(context, data, len); 21 | } 22 | 23 | void CallbackSD1Device::hardReset() 24 | { 25 | } -------------------------------------------------------------------------------- /libnotesalad/CallbackSD1Device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SD1/SD1DeviceBase.h" 4 | 5 | #include 6 | 7 | extern "C" { 8 | typedef void (*SD1WriteCallback)(void* context, const uint8_t* data, 9 | const uint16_t len); 10 | typedef void (*SD1DelayCallback)(void* context, uint32_t microseconds); 11 | } 12 | 13 | class CallbackSD1Device : public SD1DeviceBase { 14 | public: 15 | CallbackSD1Device(void* context, SD1WriteCallback writeCbk, 16 | SD1DelayCallback delayCbk); 17 | virtual ~CallbackSD1Device(); 18 | virtual void delayMicroseconds(uint32_t us); 19 | virtual void write(const uint8_t* data, const uint16_t len); 20 | virtual void hardReset(); 21 | 22 | private: 23 | void* context; 24 | SD1WriteCallback writeCbk; 25 | SD1DelayCallback delayCbk; 26 | }; 27 | -------------------------------------------------------------------------------- /libnotesalad/EmulatorBase.cpp: -------------------------------------------------------------------------------- 1 | #include "EmulatorBase.h" 2 | 3 | EmulatorBase::EmulatorBase(uint32_t nativeSampleRate, uint32_t outputSampleRate) 4 | { 5 | this->nativeSampleRate = nativeSampleRate; 6 | this->outputSampleRate = outputSampleRate; 7 | this->resampler = ntsld_resampler_init(2, nativeSampleRate, outputSampleRate, 5, nullptr); 8 | } 9 | 10 | EmulatorBase::~EmulatorBase() 11 | { 12 | ntsld_resampler_destroy(this->resampler); 13 | } 14 | 15 | uint32_t EmulatorBase::getNativeSampleRate() 16 | { 17 | return this->nativeSampleRate; 18 | } 19 | 20 | void EmulatorBase::getSamples(int16_t* output, int numSamples) 21 | { 22 | if (this->nativeSampleRate == this->outputSampleRate) { 23 | return this->getRawSamples(output, numSamples); 24 | } 25 | 26 | while (numSamples > 0) { 27 | if (this->resampleBufferPos < SAMPLE_BUFFER_SAMPLES) { 28 | uint32_t inLen = SAMPLE_BUFFER_SAMPLES - this->resampleBufferPos; 29 | uint32_t outLen = numSamples; 30 | ntsld_resampler_process_interleaved_int( 31 | this->resampler, 32 | &this->resampleBuffer[this->resampleBufferPos * 2], 33 | &inLen, 34 | output, 35 | &outLen); 36 | output += (outLen * 2); 37 | numSamples -= outLen; 38 | this->resampleBufferPos += inLen; 39 | } else { 40 | this->getRawSamples(&this->resampleBuffer[0], SAMPLE_BUFFER_SAMPLES); 41 | this->resampleBufferPos = 0; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /libnotesalad/EmulatorBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "resampler/speex_resampler.h" 6 | 7 | class EmulatorBase { 8 | public: 9 | EmulatorBase(uint32_t nativeSampleRate, uint32_t outputSampleRate); 10 | virtual ~EmulatorBase(); 11 | uint32_t getNativeSampleRate(); 12 | void getSamples(int16_t* output, int numSamples); 13 | 14 | protected: 15 | virtual void getRawSamples(int16_t* output, int numSamples) = 0; 16 | 17 | private: 18 | static const int SAMPLE_BUFFER_SAMPLES = 50; 19 | static const int SAMPLE_BUFFER_SIZE = SAMPLE_BUFFER_SAMPLES * 2; 20 | 21 | uint32_t nativeSampleRate; 22 | uint32_t outputSampleRate; 23 | SpeexResamplerState* resampler; 24 | int16_t resampleBuffer[SAMPLE_BUFFER_SIZE]; 25 | int resampleBufferPos = SAMPLE_BUFFER_SAMPLES; 26 | }; 27 | -------------------------------------------------------------------------------- /libnotesalad/IMIDIManager.cpp: -------------------------------------------------------------------------------- 1 | #include "IMIDIManager.h" 2 | 3 | IMIDIManager::~IMIDIManager() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /libnotesalad/IMIDIManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IMIDIManager { 7 | public: 8 | virtual void reset() = 0; 9 | virtual void setReceiveCallback(std::function callback) = 0; 10 | virtual void sendMIDI(uint8_t* msgData, uint32_t msgLength) = 0; 11 | virtual void setTime(uint32_t timeMS) = 0; 12 | virtual void update() = 0; 13 | virtual ~IMIDIManager(); 14 | }; 15 | -------------------------------------------------------------------------------- /libnotesalad/OPL2MIDIManager.cpp: -------------------------------------------------------------------------------- 1 | #include "OPL2MIDIManager.h" 2 | 3 | OPL2MIDIManager::OPL2MIDIManager(void* context, OPLDeviceBase* device) 4 | : bufferedDevice(device), patchManager(), 5 | midiSystem(&bufferedDevice, &patchManager), parser(&midiSystem.midiDriver) 6 | { 7 | } 8 | 9 | void OPL2MIDIManager::reset() 10 | { 11 | this->midiSystem.midiDriver.reset(true); 12 | } 13 | 14 | void OPL2MIDIManager::setReceiveCallback(std::function callback) 15 | { 16 | this->midiSystem.midiDriver.onSendMIDI = callback; 17 | } 18 | 19 | void OPL2MIDIManager::sendMIDI(uint8_t* msgData, uint32_t msgLength) 20 | { 21 | this->parser.putBuffer(msgData, msgLength); 22 | } 23 | 24 | void OPL2MIDIManager::setTime(uint32_t timeMS) 25 | { 26 | this->midiSystem.timeSource.timeMS = timeMS; 27 | } 28 | 29 | void OPL2MIDIManager::update() 30 | { 31 | this->midiSystem.midiDriver.update(); 32 | } 33 | -------------------------------------------------------------------------------- /libnotesalad/OPL2MIDIManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CallbackOPLDevice.h" 4 | #include "IMIDIManager.h" 5 | #include "MIDI/MIDIParser.h" 6 | #include "MIDI/OPL/DefaultOPLPatchManager.h" 7 | #include "MIDI/OPL/OPL2MIDISystem.h" 8 | 9 | class OPL2MIDIManager : public IMIDIManager { 10 | public: 11 | OPL2MIDIManager(void* context, OPLDeviceBase* device); 12 | virtual void reset(); 13 | virtual void setReceiveCallback(std::function callback); 14 | virtual void sendMIDI(uint8_t* msgData, uint32_t msgLength); 15 | virtual void setTime(uint32_t timeMS); 16 | virtual void update(); 17 | 18 | private: 19 | BufferedOPLDevice bufferedDevice; 20 | DefaultOPLPatchManager patchManager; 21 | OPL2MIDISystem midiSystem; 22 | MIDIParser parser; 23 | }; 24 | -------------------------------------------------------------------------------- /libnotesalad/OPL3MIDIManager.cpp: -------------------------------------------------------------------------------- 1 | #include "OPL3MIDIManager.h" 2 | 3 | OPL3MIDIManager::OPL3MIDIManager(void* context, OPLDeviceBase* device) 4 | : bufferedDevice(device), gmPatchManager(), patchManager(&gmPatchManager), 5 | midiSystem(&bufferedDevice, &patchManager), parser(&midiSystem.midiDriver) 6 | { 7 | } 8 | 9 | void OPL3MIDIManager::reset() 10 | { 11 | this->midiSystem.midiDriver.reset(true); 12 | } 13 | 14 | void OPL3MIDIManager::setReceiveCallback(std::function callback) 15 | { 16 | this->midiSystem.midiDriver.onSendMIDI = callback; 17 | } 18 | 19 | void OPL3MIDIManager::sendMIDI(uint8_t* msgData, uint32_t msgLength) 20 | { 21 | this->parser.putBuffer(msgData, msgLength); 22 | } 23 | 24 | void OPL3MIDIManager::setTime(uint32_t timeMS) 25 | { 26 | this->midiSystem.timeSource.timeMS = timeMS; 27 | } 28 | 29 | void OPL3MIDIManager::update() 30 | { 31 | this->midiSystem.midiDriver.update(); 32 | } 33 | -------------------------------------------------------------------------------- /libnotesalad/OPL3MIDIManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "CallbackOPLDevice.h" 6 | #include "IMIDIManager.h" 7 | #include "MIDI/MIDIParser.h" 8 | #include "MIDI/OPL/DefaultOPLPatchManager.h" 9 | #include "MIDI/OPL/OPL3MIDISystem.h" 10 | #include "MIDI/RAMPatchManager.h" 11 | 12 | class OPL3MIDIManager : public IMIDIManager { 13 | public: 14 | OPL3MIDIManager(void* context, OPLDeviceBase* device); 15 | virtual void reset(); 16 | virtual void setReceiveCallback(std::function callback); 17 | virtual void sendMIDI(uint8_t* msgData, uint32_t msgLength); 18 | virtual void setTime(uint32_t timeMS); 19 | virtual void update(); 20 | 21 | private: 22 | BufferedOPLDevice bufferedDevice; 23 | DefaultOPLPatchManager gmPatchManager; 24 | RAMPatchManager patchManager; 25 | OPL3MIDISystem midiSystem; 26 | MIDIParser parser; 27 | }; 28 | -------------------------------------------------------------------------------- /libnotesalad/OPLEmulator.cpp: -------------------------------------------------------------------------------- 1 | #include "OPLEmulator.h" 2 | 3 | #include 4 | 5 | #define OPL_NATIVE_SAMPLE_RATE 49716 6 | 7 | OPLEmulator::OPLEmulator(uint32_t outputSampleRate) 8 | : EmulatorBase(OPL_NATIVE_SAMPLE_RATE, outputSampleRate), chipInterface(), chip(chipInterface) 9 | { 10 | chip.reset(); 11 | } 12 | 13 | OPLEmulator::~OPLEmulator() 14 | { 15 | } 16 | 17 | void OPLEmulator::hardReset() 18 | { 19 | chip.reset(); 20 | } 21 | 22 | void OPLEmulator::write(uint16_t addr, uint8_t data) 23 | { 24 | this->writeQueue.push_back(std::make_pair(addr, data)); 25 | } 26 | 27 | void OPLEmulator::getRawSamples(int16_t* output, int numSamples) 28 | { 29 | for (int i = 0; i < numSamples; i++) { 30 | if (!this->writeQueue.empty()) { 31 | auto nextWrite = this->writeQueue.front(); 32 | auto addr = nextWrite.first; 33 | auto data = nextWrite.second; 34 | this->writeQueue.erase(this->writeQueue.begin()); 35 | 36 | if (addr & 0x100) { 37 | chip.write_address_hi(addr & 0xff); 38 | } else { 39 | chip.write_address(addr & 0xff); 40 | } 41 | chip.write_data(data); 42 | } 43 | 44 | ymfm::ymf262::output_data chipOutput; 45 | chip.generate(&chipOutput); 46 | output[i * 2] = chipOutput.data[0]; 47 | output[i * 2 + 1] = chipOutput.data[1]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libnotesalad/OPLEmulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "EmulatorBase.h" 4 | #include "OPL/OPLDeviceBase.h" 5 | #include "ymfm/src/ymfm_opl.h" 6 | 7 | #include 8 | #include 9 | 10 | class OPLEmulator : public EmulatorBase, public OPLDeviceBase { 11 | public: 12 | OPLEmulator(uint32_t outputSampleRate); 13 | virtual ~OPLEmulator(); 14 | virtual void hardReset(); 15 | virtual void write(uint16_t addr, uint8_t data); 16 | 17 | protected: 18 | virtual void getRawSamples(int16_t* output, int numSamples); 19 | 20 | private: 21 | ymfm::ymfm_interface chipInterface; 22 | ymfm::ymf262 chip; 23 | std::vector> writeQueue; 24 | }; 25 | -------------------------------------------------------------------------------- /libnotesalad/OPMEmulator.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMEmulator.h" 2 | 3 | #define OPM_NATIVE_SAMPLE_RATE 55930 4 | 5 | OPMEmulator::OPMEmulator(uint32_t outputSampleRate) 6 | : EmulatorBase(OPM_NATIVE_SAMPLE_RATE, outputSampleRate), chipInterface(), chip(chipInterface) 7 | { 8 | chip.reset(); 9 | } 10 | 11 | OPMEmulator::~OPMEmulator() 12 | { 13 | } 14 | 15 | void OPMEmulator::hardReset() 16 | { 17 | chip.reset(); 18 | } 19 | 20 | void OPMEmulator::write(uint8_t addr, uint8_t data) 21 | { 22 | this->writeQueue.push_back(std::make_pair(addr, data)); 23 | } 24 | 25 | void OPMEmulator::getRawSamples(int16_t* output, int numSamples) 26 | { 27 | for (int i = 0; i < numSamples; i++) { 28 | if (!this->writeQueue.empty()) { 29 | auto nextWrite = this->writeQueue.front(); 30 | auto addr = nextWrite.first; 31 | auto data = nextWrite.second; 32 | this->writeQueue.erase(this->writeQueue.begin()); 33 | 34 | chip.write_address(addr); 35 | chip.write_data(data); 36 | } 37 | ymfm::ym2151::output_data chipOutput; 38 | chip.generate(&chipOutput); 39 | output[i * 2] = chipOutput.data[0]; 40 | output[i * 2 + 1] = chipOutput.data[1]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /libnotesalad/OPMEmulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "EmulatorBase.h" 4 | #include "OPM/OPMDeviceBase.h" 5 | #include "ymfm/src/ymfm_opm.h" 6 | 7 | #include 8 | #include 9 | 10 | class OPMEmulator : public EmulatorBase, public OPMDeviceBase { 11 | public: 12 | OPMEmulator(uint32_t outputSampleRate); 13 | virtual ~OPMEmulator(); 14 | virtual void hardReset(); 15 | virtual void write(uint8_t addr, uint8_t data); 16 | 17 | protected: 18 | virtual void getRawSamples(int16_t* output, int numSamples); 19 | 20 | private: 21 | ymfm::ymfm_interface chipInterface; 22 | ymfm::ym2151 chip; 23 | std::vector> writeQueue; 24 | }; 25 | -------------------------------------------------------------------------------- /libnotesalad/OPMMIDIManager.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMMIDIManager.h" 2 | 3 | OPMMIDIManager::OPMMIDIManager(void* context, OPMDeviceBase* device) 4 | : bufferedDevice(device), patchManager(), 5 | midiSystem(&bufferedDevice, &patchManager), parser(&midiSystem.midiDriver) 6 | { 7 | } 8 | 9 | void OPMMIDIManager::reset() 10 | { 11 | this->midiSystem.midiDriver.reset(true); 12 | } 13 | 14 | void OPMMIDIManager::setReceiveCallback(std::function callback) 15 | { 16 | this->midiSystem.midiDriver.onSendMIDI = callback; 17 | } 18 | 19 | void OPMMIDIManager::sendMIDI(uint8_t* msgData, uint32_t msgLength) 20 | { 21 | this->parser.putBuffer(msgData, msgLength); 22 | } 23 | 24 | void OPMMIDIManager::setTime(uint32_t timeMS) 25 | { 26 | this->midiSystem.timeSource.timeMS = timeMS; 27 | } 28 | 29 | void OPMMIDIManager::update() 30 | { 31 | this->midiSystem.midiDriver.update(); 32 | } 33 | -------------------------------------------------------------------------------- /libnotesalad/OPMMIDIManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "CallbackOPMDevice.h" 6 | #include "IMIDIManager.h" 7 | #include "MIDI/MIDIParser.h" 8 | #include "MIDI/OPM/OPMMIDISystem.h" 9 | 10 | class OPMMIDIManager : public IMIDIManager { 11 | public: 12 | OPMMIDIManager(void* context, OPMDeviceBase* device); 13 | virtual void reset(); 14 | virtual void setReceiveCallback(std::function callback); 15 | virtual void sendMIDI(uint8_t* msgData, uint32_t msgLength); 16 | virtual void setTime(uint32_t timeMS); 17 | virtual void update(); 18 | 19 | private: 20 | BufferedOPMDevice bufferedDevice; 21 | PatchManagerBase patchManager; 22 | OPMMIDISystem midiSystem; 23 | MIDIParser parser; 24 | }; 25 | -------------------------------------------------------------------------------- /libnotesalad/SD1MIDIManager.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1MIDIManager.h" 2 | 3 | SD1MIDIManager::SD1MIDIManager(void* context, SD1DeviceBase* device) 4 | : patchManager(), midiSystem(device, &patchManager), 5 | parser(&midiSystem.midiDriver) 6 | { 7 | } 8 | 9 | void SD1MIDIManager::reset() 10 | { 11 | this->midiSystem.midiDriver.reset(true); 12 | } 13 | 14 | void SD1MIDIManager::setReceiveCallback(std::function callback) 15 | { 16 | this->midiSystem.midiDriver.onSendMIDI = callback; 17 | } 18 | 19 | void SD1MIDIManager::sendMIDI(uint8_t* msgData, uint32_t msgLength) 20 | { 21 | this->parser.putBuffer(msgData, msgLength); 22 | } 23 | 24 | void SD1MIDIManager::setTime(uint32_t timeMS) 25 | { 26 | this->midiSystem.timeSource.timeMS = timeMS; 27 | } 28 | 29 | void SD1MIDIManager::update() 30 | { 31 | this->midiSystem.midiDriver.update(); 32 | } 33 | -------------------------------------------------------------------------------- /libnotesalad/SD1MIDIManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "CallbackSD1Device.h" 6 | #include "IMIDIManager.h" 7 | #include "MIDI/MIDIParser.h" 8 | #include "MIDI/SD1/DefaultSD1PatchManager.h" 9 | #include "MIDI/SD1/SD1MIDISystem.h" 10 | 11 | class SD1MIDIManager : public IMIDIManager { 12 | public: 13 | SD1MIDIManager(void* context, SD1DeviceBase* device); 14 | virtual void reset(); 15 | virtual void setReceiveCallback(std::function callback); 16 | virtual void sendMIDI(uint8_t* msgData, uint32_t msgLength); 17 | virtual void setTime(uint32_t timeMS); 18 | virtual void update(); 19 | 20 | private: 21 | DefaultSD1PatchManager patchManager; 22 | SD1MIDISystem midiSystem; 23 | MIDIParser parser; 24 | }; 25 | -------------------------------------------------------------------------------- /libnotesalad/midi.cpp: -------------------------------------------------------------------------------- 1 | #include "midi.h" 2 | #include "IMIDIManager.h" 3 | 4 | void ntsld_midi_set_recv_cbk(MIDIRef midiRef, void* context, MIDIReceiveCallback callback) 5 | { 6 | if (callback == nullptr) { 7 | ((IMIDIManager*)midiRef)->setReceiveCallback(nullptr); 8 | } else { 9 | ((IMIDIManager*)midiRef)->setReceiveCallback([context, callback](void* msgData, uint32_t msgLength) { 10 | callback(context, (uint8_t*)msgData, msgLength); 11 | }); 12 | } 13 | } 14 | 15 | void ntsld_midi_send(MIDIRef midiRef, uint8_t* msgData, uint32_t msgLength) 16 | { 17 | ((IMIDIManager*)midiRef)->sendMIDI(msgData, msgLength); 18 | } 19 | 20 | void ntsld_midi_set_time(MIDIRef midiRef, uint32_t timeMS) 21 | { 22 | ((IMIDIManager*)midiRef)->setTime(timeMS); 23 | } 24 | 25 | void ntsld_midi_update(MIDIRef midiRef) 26 | { 27 | ((IMIDIManager*)midiRef)->update(); 28 | } 29 | 30 | void ntsld_midi_reset(MIDIRef midiRef) 31 | { 32 | ((IMIDIManager*)midiRef)->reset(); 33 | } 34 | 35 | void ntsld_midi_delete(MIDIRef midiRef) 36 | { 37 | delete ((IMIDIManager*)midiRef); 38 | } 39 | -------------------------------------------------------------------------------- /libnotesalad/midi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern "C" { 6 | typedef void* MIDIRef; 7 | typedef void (*MIDIReceiveCallback)(void* context, uint8_t* msgData, uint32_t msgLength); 8 | 9 | void ntsld_midi_set_recv_cbk(MIDIRef midiRef, void* context, MIDIReceiveCallback callback); 10 | void ntsld_midi_send(MIDIRef midiRef, uint8_t* msgData, uint32_t msgLength); 11 | void ntsld_midi_set_time(MIDIRef midiRef, uint32_t timeMS); 12 | void ntsld_midi_update(MIDIRef midiRef); 13 | void ntsld_midi_reset(MIDIRef midiRef); 14 | void ntsld_midi_delete(MIDIRef midiRef); 15 | } 16 | -------------------------------------------------------------------------------- /libnotesalad/opl.cpp: -------------------------------------------------------------------------------- 1 | #include "opl.h" 2 | #include "OPL2MIDIManager.h" 3 | #include "OPL3MIDIManager.h" 4 | #include "OPLEmulator.h" 5 | 6 | OPLDeviceRef ntsld_opl_emu_new(uint32_t sampleRate) 7 | { 8 | return dynamic_cast(new OPLEmulator(sampleRate)); 9 | } 10 | 11 | OPLDeviceRef ntsld_opl_cbkdev_new(void* context, OPLWriteCallback writeCallback, OPLResetCallback resetCallback) 12 | { 13 | return dynamic_cast(new CallbackOPLDevice(context, writeCallback, resetCallback)); 14 | } 15 | 16 | void ntsld_opl_delete(OPLDeviceRef device) 17 | { 18 | delete ((OPLDeviceBase*)device); 19 | } 20 | 21 | void ntsld_opl_reset(OPLDeviceRef device) 22 | { 23 | ((OPLDeviceBase*)device)->hardReset(); 24 | } 25 | 26 | void ntsld_opl_write(OPLDeviceRef device, uint16_t addr, uint8_t data) 27 | { 28 | ((OPLDeviceBase*)device)->write(addr, data); 29 | } 30 | 31 | void ntsld_opl_emu_getsamples(OPLDeviceRef device, int16_t* output, int numSamples) 32 | { 33 | dynamic_cast((OPLDeviceBase*)device)->getSamples(output, numSamples); 34 | } 35 | 36 | void ntsld_opl_emu_getsamples_offset(OPLDeviceRef device, int16_t* output, int offsetSamples, int numSamples) 37 | { 38 | ntsld_opl_emu_getsamples(device, output + (offsetSamples * 2), numSamples); 39 | } 40 | 41 | OPL2MIDIRef ntsld_opl2midi_new(void* context, OPLDeviceRef device) 42 | { 43 | return dynamic_cast(new OPL2MIDIManager(context, (OPLDeviceBase*)device)); 44 | } 45 | 46 | OPL3MIDIRef ntsld_opl3midi_new(void* context, OPLDeviceRef device) 47 | { 48 | return dynamic_cast(new OPL3MIDIManager(context, (OPLDeviceBase*)device)); 49 | } 50 | -------------------------------------------------------------------------------- /libnotesalad/opl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "CallbackOPLDevice.h" 6 | #include "midi.h" 7 | 8 | extern "C" { 9 | typedef void* OPLDeviceRef; 10 | typedef MIDIRef OPL2MIDIRef; 11 | typedef MIDIRef OPL3MIDIRef; 12 | 13 | OPLDeviceRef ntsld_opl_emu_new(uint32_t sampleRate); 14 | OPLDeviceRef ntsld_opl_cbkdev_new(void* context, OPLWriteCallback writeCallback, OPLResetCallback resetCallback); 15 | 16 | void ntsld_opl_delete(OPLDeviceRef device); 17 | void ntsld_opl_reset(OPLDeviceRef device); 18 | void ntsld_opl_write(OPLDeviceRef device, uint16_t addr, uint8_t data); 19 | 20 | void ntsld_opl_emu_getsamples(OPLDeviceRef device, int16_t* output, int numSamples); 21 | void ntsld_opl_emu_getsamples_offset(OPLDeviceRef device, int16_t* output, int offsetSamples, int numSamples); 22 | 23 | OPL2MIDIRef ntsld_opl2midi_new(void* context, OPLDeviceRef device); 24 | OPL3MIDIRef ntsld_opl3midi_new(void* context, OPLDeviceRef device); 25 | } 26 | -------------------------------------------------------------------------------- /libnotesalad/oplsd1.cpp: -------------------------------------------------------------------------------- 1 | #include "oplsd1.h" 2 | #include "SD1/SD1OPLAdaptor.h" 3 | 4 | OPLDeviceRef ntsld_oplsd1_new(SD1DeviceRef sd1Device) 5 | { 6 | return new SD1OPLAdaptor((SD1DeviceBase*)sd1Device); 7 | } -------------------------------------------------------------------------------- /libnotesalad/oplsd1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "opl.h" 4 | #include "sd1.h" 5 | 6 | extern "C" { 7 | OPLDeviceRef ntsld_oplsd1_new(SD1DeviceRef sd1Device); 8 | } -------------------------------------------------------------------------------- /libnotesalad/opm.cpp: -------------------------------------------------------------------------------- 1 | #include "opm.h" 2 | #include "OPMEmulator.h" 3 | #include "OPMMIDIManager.h" 4 | 5 | OPMDeviceRef ntsld_opm_emu_new(uint32_t sampleRate) 6 | { 7 | return dynamic_cast(new OPMEmulator(sampleRate)); 8 | } 9 | 10 | OPMDeviceRef ntsld_opm_cbkdev_new(void* context, OPMWriteCallback writeCallback, OPMResetCallback resetCallback) 11 | { 12 | return dynamic_cast(new CallbackOPMDevice(context, writeCallback, resetCallback)); 13 | } 14 | 15 | void ntsld_opm_delete(OPMDeviceRef device) 16 | { 17 | delete ((OPMDeviceBase*)device); 18 | } 19 | 20 | void ntsld_opm_reset(OPMDeviceRef device) 21 | { 22 | ((OPMDeviceBase*)device)->hardReset(); 23 | } 24 | 25 | void ntsld_opm_write(OPMDeviceRef device, uint8_t addr, uint8_t data) 26 | { 27 | ((OPMDeviceBase*)device)->write(addr, data); 28 | } 29 | 30 | void ntsld_opm_emu_getsamples(OPMDeviceRef device, int16_t* output, int numSamples) 31 | { 32 | dynamic_cast((OPMDeviceBase*)device)->getSamples(output, numSamples); 33 | } 34 | 35 | void ntsld_opm_emu_getsamples_offset(OPMDeviceRef device, int16_t* output, int offsetSamples, int numSamples) 36 | { 37 | ntsld_opm_emu_getsamples(device, output + (offsetSamples * 2), numSamples); 38 | } 39 | 40 | OPMMIDIRef ntsld_opmmidi_new(void* context, OPMDeviceRef device) 41 | { 42 | return dynamic_cast(new OPMMIDIManager(context, (OPMDeviceBase*)device)); 43 | } 44 | -------------------------------------------------------------------------------- /libnotesalad/opm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "CallbackOPMDevice.h" 6 | #include "midi.h" 7 | 8 | extern "C" { 9 | typedef void* OPMDeviceRef; 10 | typedef MIDIRef OPMMIDIRef; 11 | 12 | OPMDeviceRef ntsld_opm_emu_new(uint32_t sampleRate); 13 | OPMDeviceRef ntsld_opm_cbkdev_new(void* context, OPMWriteCallback writeCallback, OPMResetCallback resetCallback); 14 | 15 | void ntsld_opm_delete(OPMDeviceRef device); 16 | void ntsld_opm_reset(OPMDeviceRef device); 17 | void ntsld_opm_write(OPMDeviceRef device, uint8_t addr, uint8_t data); 18 | 19 | void ntsld_opm_emu_getsamples(OPMDeviceRef device, int16_t* output, int numSamples); 20 | void ntsld_opm_emu_getsamples_offset(OPMDeviceRef device, int16_t* output, int offsetSamples, int numSamples); 21 | 22 | OPMMIDIRef ntsld_opmmidi_new(void* context, OPMDeviceRef device); 23 | } 24 | -------------------------------------------------------------------------------- /libnotesalad/resampler/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesalad 2 | PRIVATE 3 | arch.h 4 | resample.c 5 | speex_resampler.h 6 | ) 7 | -------------------------------------------------------------------------------- /libnotesalad/sd1.cpp: -------------------------------------------------------------------------------- 1 | #include "sd1.h" 2 | #include "SD1MIDIManager.h" 3 | 4 | SD1DeviceRef ntsld_sd1_cbkdev_new(void* context, SD1WriteCallback writeCallback, SD1DelayCallback delayCallback) 5 | { 6 | return new CallbackSD1Device(context, writeCallback, delayCallback); 7 | } 8 | 9 | void ntsld_sd1_delete(SD1DeviceRef device) 10 | { 11 | delete ((SD1DeviceBase*)device); 12 | } 13 | 14 | SD1MIDIRef ntsld_sd1midi_new(void* context, SD1DeviceRef device) 15 | { 16 | return new SD1MIDIManager(context, (SD1DeviceBase*)device); 17 | } 18 | -------------------------------------------------------------------------------- /libnotesalad/sd1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CallbackSD1Device.h" 4 | 5 | extern "C" { 6 | typedef void* SD1DeviceRef; 7 | typedef void* SD1MIDIRef; 8 | 9 | SD1DeviceRef ntsld_sd1_cbkdev_new(void* context, SD1WriteCallback writeCallback, SD1DelayCallback delayCallback); 10 | 11 | void ntsld_sd1_delete(SD1DeviceRef device); 12 | 13 | SD1MIDIRef ntsld_sd1midi_new(void* context, SD1DeviceRef device); 14 | } 15 | -------------------------------------------------------------------------------- /libnotesaladcore/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(src) 2 | add_subdirectory(test) 3 | -------------------------------------------------------------------------------- /libnotesaladcore/library.properties: -------------------------------------------------------------------------------- 1 | name=notesaladcore 2 | version=0.1.0 3 | author=Dan Fry 4 | maintainer=Dan Fry 5 | sentence=notesaladcore 6 | paragraph= 7 | url=https://github.com/danielrfry/notesalad 8 | includes=notesaladcore.h 9 | -------------------------------------------------------------------------------- /libnotesaladcore/src/7BitEncoding.cpp: -------------------------------------------------------------------------------- 1 | #include "7BitEncoding.h" 2 | 3 | #include 4 | 5 | Encoder7Bit::Encoder7Bit(IWriter* baseWriter) : baseWriter(baseWriter), buffer(), msbs(0), bufferPos(0) 6 | { 7 | } 8 | 9 | size_t Encoder7Bit::write(const uint8_t* src, size_t count) 10 | { 11 | size_t bytesWritten = 0; 12 | for (size_t pos = 0; pos < count; pos++) { 13 | msbs = (msbs << 1) | (src[pos] >> 7); 14 | buffer[bufferPos++] = src[pos] & 0x7f; 15 | if (bufferPos >= 7) { 16 | if (!flush()) 17 | break; 18 | } 19 | bytesWritten++; 20 | } 21 | 22 | return bytesWritten; 23 | } 24 | 25 | bool Encoder7Bit::flush() 26 | { 27 | if (bufferPos > 0) { 28 | msbs = msbs << (7 - bufferPos); 29 | if (!baseWriter->write(msbs)) 30 | return false; 31 | if (baseWriter->write(&buffer[0], bufferPos) < bufferPos) 32 | return false; 33 | bufferPos = 0; 34 | msbs = 0; 35 | } 36 | return true; 37 | } 38 | 39 | Decoder7Bit::Decoder7Bit(IReader* baseReader) : baseReader(baseReader), msbs(0), msbsAvailable(0) 40 | { 41 | } 42 | 43 | size_t Decoder7Bit::read(uint8_t* dest, size_t count) 44 | { 45 | for (size_t pos = 0; pos < count; pos++) { 46 | if (msbsAvailable == 0) { 47 | if (!baseReader->read(msbs)) 48 | return pos; 49 | msbsAvailable = 7; 50 | } 51 | 52 | if (!baseReader->read(dest[pos])) 53 | return pos; 54 | msbs = msbs << 1; 55 | dest[pos] |= msbs & 0x80; 56 | msbsAvailable--; 57 | } 58 | 59 | return count; 60 | } 61 | 62 | bool Decoder7Bit::eof() 63 | { 64 | return baseReader->eof(); 65 | } 66 | 67 | bool encodeLEB128(uint16_t v, IWriter* buffer) 68 | { 69 | uint8_t b; 70 | do { 71 | b = v & 0x7f; 72 | v = v >> 7; 73 | if (v != 0) { 74 | b |= 0x80; 75 | } 76 | if (!buffer->write(b)) { 77 | return false; 78 | } 79 | } while (v != 0); 80 | 81 | return true; 82 | } 83 | 84 | bool decodeLEB128(uint16_t& outDecodedValue, IReader* buffer) 85 | { 86 | uint8_t shift = 0; 87 | uint8_t b; 88 | outDecodedValue = 0; 89 | while (buffer->read(b)) { 90 | outDecodedValue |= ((b & 0x7f) << shift); 91 | shift += 7; 92 | if (!(b & 0x80)) { 93 | return true; 94 | } 95 | } 96 | return false; 97 | } 98 | 99 | size_t get7BitEncodedSize(size_t unencodedSize) 100 | { 101 | if (unencodedSize == 0) { 102 | return 0; 103 | } else { 104 | return unencodedSize + ((unencodedSize + 6) / 7); 105 | } 106 | } 107 | 108 | size_t get7BitDecodedSize(size_t encodedSize) 109 | { 110 | if (encodedSize == 0) { 111 | return 0; 112 | } else { 113 | return encodedSize - ((encodedSize + 7) / 8); 114 | } 115 | } 116 | 117 | bool encode7Bit(const uint8_t* input, size_t inputSize, uint8_t* output, size_t outputSize) 118 | { 119 | BufferWriter writer(output, outputSize); 120 | Encoder7Bit encoder(&writer); 121 | return encoder.write(input, inputSize) == inputSize && encoder.flush(); 122 | } 123 | 124 | bool decode7Bit(const uint8_t* input, size_t inputSize, uint8_t* output, size_t outputSize) 125 | { 126 | BufferReader reader(input, inputSize); 127 | Decoder7Bit decoder(&reader); 128 | return decoder.read(output, outputSize) == outputSize; 129 | } 130 | -------------------------------------------------------------------------------- /libnotesaladcore/src/7BitEncoding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BufferIO.h" 4 | 5 | class Encoder7Bit : public IWriter { 6 | public: 7 | Encoder7Bit(IWriter* baseWriter); 8 | virtual size_t write(const uint8_t* src, size_t count) override; 9 | bool flush(); 10 | 11 | private: 12 | IWriter* baseWriter; 13 | uint8_t buffer[7]; 14 | uint8_t msbs; 15 | uint8_t bufferPos; 16 | }; 17 | 18 | class Decoder7Bit : public IReader { 19 | public: 20 | Decoder7Bit(IReader* baseReader); 21 | virtual size_t read(uint8_t* dest, size_t count) override; 22 | virtual bool eof() override; 23 | 24 | private: 25 | IReader* baseReader; 26 | uint8_t msbs; 27 | uint8_t msbsAvailable; 28 | }; 29 | 30 | bool encodeLEB128(uint16_t v, IWriter* buffer); 31 | bool decodeLEB128(uint16_t& outDecodedValue, IReader* buffer); 32 | 33 | size_t get7BitEncodedSize(size_t unencodedSize); 34 | size_t get7BitDecodedSize(size_t encodedSize); 35 | bool encode7Bit(const uint8_t* input, size_t inputSize, uint8_t* output, size_t outputSize); 36 | bool decode7Bit(const uint8_t* input, size_t inputSize, uint8_t* output, size_t outputSize); 37 | -------------------------------------------------------------------------------- /libnotesaladcore/src/BufferIO.cpp: -------------------------------------------------------------------------------- 1 | #include "BufferIO.h" 2 | 3 | #include 4 | #include 5 | 6 | BufferReader::BufferReader(const uint8_t* buffer, size_t size) 7 | : buffer(buffer), size(size), position(0) 8 | { 9 | } 10 | 11 | bool BufferReader::eof() 12 | { 13 | return position >= size; 14 | } 15 | 16 | size_t BufferReader::getSize() 17 | { 18 | return size; 19 | } 20 | 21 | size_t BufferReader::getPosition() 22 | { 23 | return position; 24 | } 25 | 26 | size_t BufferReader::read(uint8_t* dest, size_t count) 27 | { 28 | size_t bytesToRead = std::min(this->size - this->position, count); 29 | memcpy(dest, this->buffer + position, bytesToRead); 30 | this->position += bytesToRead; 31 | return bytesToRead; 32 | } 33 | 34 | size_t BufferReader::seek(size_t pos) 35 | { 36 | return position = std::min(pos, size); 37 | } 38 | 39 | BufferWriter::BufferWriter(uint8_t* buffer, size_t size) 40 | : buffer(buffer), size(size), position(0) 41 | { 42 | } 43 | 44 | size_t BufferWriter::getSize() 45 | { 46 | return size; 47 | } 48 | 49 | size_t BufferWriter::getPosition() 50 | { 51 | return position; 52 | } 53 | 54 | size_t BufferWriter::write(const uint8_t* src, size_t count) 55 | { 56 | size_t bytesToWrite = std::min(this->size - this->position, count); 57 | memcpy(this->buffer + position, src, bytesToWrite); 58 | this->position += bytesToWrite; 59 | return bytesToWrite; 60 | } 61 | 62 | size_t BufferWriter::seek(size_t pos) 63 | { 64 | return position = std::min(pos, size); 65 | } 66 | -------------------------------------------------------------------------------- /libnotesaladcore/src/BufferIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IReader { 7 | public: 8 | virtual size_t read(uint8_t* dest, size_t count) = 0; 9 | virtual bool eof() = 0; 10 | 11 | template 12 | bool read(T& item) 13 | { 14 | return this->read(&item, sizeof(item)) == sizeof(item); 15 | } 16 | }; 17 | 18 | class IWriter { 19 | public: 20 | virtual size_t write(const uint8_t* src, size_t count) = 0; 21 | 22 | template 23 | bool write(T& item) 24 | { 25 | return this->write((const uint8_t*)&item, sizeof(item)) == sizeof(item); 26 | } 27 | bool write(uint8_t b) 28 | { 29 | return this->write(&b, 1); 30 | } 31 | }; 32 | 33 | class BufferReader : public IReader { 34 | public: 35 | const uint8_t* buffer; 36 | BufferReader(const uint8_t* buffer, size_t size); 37 | virtual bool eof() override; 38 | virtual size_t getSize(); 39 | virtual size_t getPosition(); 40 | virtual size_t read(uint8_t* dest, size_t count) override; 41 | virtual size_t seek(size_t pos); 42 | 43 | private: 44 | size_t size; 45 | size_t position; 46 | }; 47 | 48 | class BufferWriter : public IWriter { 49 | public: 50 | uint8_t* buffer; 51 | BufferWriter(uint8_t* buffer, size_t size); 52 | size_t getSize(); 53 | size_t getPosition(); 54 | virtual size_t write(const uint8_t* src, size_t count) override; 55 | size_t seek(size_t pos); 56 | 57 | private: 58 | size_t size; 59 | size_t position; 60 | }; 61 | 62 | template 63 | class ArrayWriter : public BufferWriter { 64 | public: 65 | ArrayWriter() : BufferWriter(nullptr, arraySize), backingStore() 66 | { 67 | buffer = &backingStore[0]; 68 | } 69 | 70 | private: 71 | uint8_t backingStore[arraySize]; 72 | }; 73 | -------------------------------------------------------------------------------- /libnotesaladcore/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(notesaladcore INTERFACE) 2 | target_include_directories(notesaladcore 3 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 4 | ) 5 | 6 | target_sources(notesaladcore 7 | INTERFACE 8 | notesaladcore.h 9 | 7BitEncoding.cpp 10 | 7BitEncoding.h 11 | BufferIO.cpp 12 | BufferIO.h 13 | Utils.cpp 14 | Utils.h 15 | ) 16 | 17 | add_subdirectory(MIDI) 18 | add_subdirectory(OPL) 19 | add_subdirectory(OPM) 20 | add_subdirectory(SD1) 21 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/BasicVoiceAllocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "IVoiceAllocator.h" 8 | 9 | template 10 | class BasicVoiceAllocator : public IVoiceAllocator { 11 | public: 12 | BasicVoiceAllocator() 13 | : voiceQueue(voices) 14 | { 15 | this->reset(); 16 | } 17 | 18 | virtual void reset() 19 | { 20 | voiceQueue.clear(); 21 | for (uint8_t i = 0; i < voices; i++) { 22 | voicePriorities[i] = 0; 23 | voiceStatuses[i] = VoiceStatus(); 24 | voiceQueue.push_back(i); 25 | } 26 | } 27 | 28 | virtual bool reserve(uint8_t& outVoice, const VoiceStatus& status, Tone& tone, uint8_t priority) 29 | { 30 | uint8_t nextVoice = this->voiceQueue[0]; 31 | if (this->voicePriorities[nextVoice] > priority) { 32 | // No available voices at the same or lower priority 33 | return false; 34 | } 35 | this->setPriority(nextVoice, priority); 36 | this->voiceStatuses[nextVoice] = status; 37 | outVoice = nextVoice; 38 | return true; 39 | } 40 | 41 | virtual void setPriority(uint8_t voice, uint8_t newPriority) 42 | { 43 | this->voiceQueue.erase(std::remove(this->voiceQueue.begin(), this->voiceQueue.end(), voice), 44 | this->voiceQueue.end()); 45 | auto insertPos = std::find_if(this->voiceQueue.begin(), this->voiceQueue.end(), [this, newPriority](uint8_t& voiceIndex) { 46 | return this->voicePriorities[voiceIndex] > newPriority; 47 | }); 48 | this->voiceQueue.insert(insertPos, voice); 49 | this->voicePriorities[voice] = newPriority; 50 | } 51 | 52 | virtual uint8_t getPriority(uint8_t voice) 53 | { 54 | return this->voicePriorities[voice]; 55 | } 56 | 57 | virtual const VoiceStatus* getVoiceStatus(uint8_t voice) 58 | { 59 | return &this->voiceStatuses[voice]; 60 | } 61 | 62 | virtual void applyToChannelVoices(uint8_t channel, std::function fn) 63 | { 64 | for (uint8_t v = 0; v < voices; v++) { 65 | if (this->getChannel(v) == channel) { 66 | fn(v, &this->voiceStatuses[v]); 67 | } 68 | } 69 | } 70 | 71 | private: 72 | uint8_t voicePriorities[voices]; 73 | VoiceStatus voiceStatuses[voices]; 74 | std::vector voiceQueue; 75 | }; 76 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | BasicVoiceAllocator.h 4 | IMIDIDriver.h 5 | INoteManager.h 6 | IToneGenerator.h 7 | IVoiceAllocator.h 8 | LFO.cpp 9 | LFO.h 10 | MIDICommon.h 11 | MIDIDriver.h 12 | MIDIParser.cpp 13 | MIDIParser.h 14 | MonoNoteManager.cpp 15 | MonoNoteManager.h 16 | ParamIDs.h 17 | ParamInfo.g.cpp 18 | ParamInfo.h 19 | Patch.h 20 | PatchManagerBase.h 21 | PatchParams.cpp 22 | PatchParams.h 23 | PatchSerialization.h 24 | PolyNoteManager.h 25 | RAMPatchManager.h 26 | SysEx.cpp 27 | SysEx.h 28 | TimeSource.h 29 | ToneController.h 30 | VoiceStatus.cpp 31 | VoiceStatus.h 32 | ) 33 | 34 | add_subdirectory(OPL) 35 | add_subdirectory(OPM) 36 | add_subdirectory(SD1) 37 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/GlobalParams.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define GLOBAL_PARAM_SD1_ANALOGUE_GAIN 0x100 4 | #define GLOBAL_PARAM_SD1_MASTER_VOL 0x101 5 | #define GLOBAL_PARAM_SD1_MASTER_CH_VOL 0x102 6 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/IMIDIDriver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IMIDIDriver { 7 | public: 8 | std::function onSendMIDI; 9 | 10 | virtual void reset(bool hardReset) = 0; 11 | virtual void noteOn(uint8_t channel, uint8_t note, uint8_t velocity) = 0; 12 | virtual void noteOff(uint8_t channel, uint8_t note, uint8_t velocity) = 0; 13 | virtual void programChange(uint8_t channel, uint8_t program) = 0; 14 | virtual void controlChange(uint8_t channel, uint8_t control, uint8_t value) = 0; 15 | virtual void sysEx(uint8_t* data, unsigned int length) = 0; 16 | virtual void pitchWheel(uint8_t channel, uint16_t value) = 0; 17 | virtual void setChannelDrumMode(uint8_t channel, bool enabled) = 0; 18 | virtual void update() = 0; 19 | }; 20 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/INoteManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class INoteManager { 6 | public: 7 | virtual void resetState() = 0; 8 | virtual void noteOn(uint8_t channel, uint8_t note, uint8_t velocity) = 0; 9 | virtual void noteOff(uint8_t channel, uint8_t note, uint8_t velocity) = 0; 10 | virtual void setSustain(uint8_t channel, bool sustainActive) = 0; 11 | virtual void allNotesOff(uint8_t channel) = 0; 12 | }; 13 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/IToneGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | class IToneGenerator { 7 | public: 8 | virtual void reset(bool hardReset) = 0; 9 | virtual void setTone(uint8_t voice, Tone& tone, uint8_t volume, uint8_t pan) = 0; 10 | virtual void setPitch(uint8_t voice, float note) = 0; 11 | virtual void setVolume(uint8_t voice, uint8_t volume) = 0; 12 | virtual void setPan(uint8_t voice, uint8_t pan) = 0; 13 | virtual void noteOn(uint8_t voice, float note) = 0; 14 | virtual void noteOff(uint8_t voice) = 0; 15 | // Returns true if a note is playing (including during release phase). 16 | // Used to determine whether to apply modulation, for example. 17 | virtual bool isNoteActive(uint8_t voice) 18 | { 19 | return true; 20 | }; 21 | virtual void setGlobalParam(uint16_t paramID, uint16_t value) { } 22 | }; 23 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/IVoiceAllocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "VoiceStatus.h" 7 | 8 | template 9 | class IVoiceAllocator { 10 | public: 11 | virtual void reset() = 0; 12 | virtual bool reserve(uint8_t& outVoice, const VoiceStatus& status, Tone& tone, uint8_t priority) = 0; 13 | virtual void setPriority(uint8_t voice, uint8_t newPriority) = 0; 14 | inline void release(uint8_t voice) 15 | { 16 | this->setPriority(voice, 0); 17 | } 18 | virtual uint8_t getPriority(uint8_t voice) = 0; 19 | virtual const VoiceStatus* getVoiceStatus(uint8_t voice) = 0; 20 | uint8_t getChannel(uint8_t voice) 21 | { 22 | return this->getVoiceStatus(voice)->channel; 23 | } 24 | virtual void applyToChannelVoices(uint8_t channel, std::function fn) = 0; 25 | void applyToChannelVoices(uint8_t channel, std::function fn) 26 | { 27 | this->applyToChannelVoices(channel, [fn](uint8_t v, const VoiceStatus* status) { fn(v); }); 28 | } 29 | virtual bool isVoiceEnabled(uint8_t voice) 30 | { 31 | return true; 32 | } 33 | virtual bool isVoiceCompatible(uint8_t voice, Tone& tone) 34 | { 35 | return true; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/LFO.cpp: -------------------------------------------------------------------------------- 1 | #include "LFO.h" 2 | 3 | #include 4 | #include 5 | 6 | #define LFO_PRIV_FLAG_ONESHOT 1 7 | 8 | LFO::LFO() 9 | : period(0), wave(0), timeOffset(0), rndLastUpdate(0), rndValue(0.0f), flags(0) 10 | { 11 | } 12 | 13 | inline float getTriangleValue(float phase) 14 | { 15 | if (phase > 0.5f) { 16 | return 2.0f - (phase * 2); 17 | } else { 18 | return (phase * 2); 19 | } 20 | } 21 | 22 | float LFO::getValue(Milliseconds time) 23 | { 24 | if (this->period < 2) { 25 | return 0.0f; 26 | } else { 27 | uint16_t phaseMS = this->getPhase(time); 28 | float phase = phaseMS / (float)this->period; 29 | switch (this->wave) { 30 | case LFO_WAVE_TRIANGLE: 31 | return getTriangleValue(phase); 32 | case LFO_WAVE_SAW: 33 | return 1.0f - phase; 34 | case LFO_WAVE_RAMP: 35 | return phase; 36 | case LFO_WAVE_SQUARE: 37 | return (phase >= 0.5f) ? 1.0f : 0.0f; 38 | case LFO_WAVE_RANDOM: 39 | return this->getRandomValue(time); 40 | default: 41 | return 0.0f; 42 | } 43 | } 44 | } 45 | 46 | uint16_t LFO::getPhase(Milliseconds time) 47 | { 48 | if (this->period == 0) { 49 | return 0; 50 | } else if (this->getOneShot() && time >= this->period) { 51 | return this->period; 52 | } else { 53 | Milliseconds offsetTime = time + this->timeOffset; 54 | return offsetTime % this->period; 55 | } 56 | } 57 | 58 | void LFO::setPeriod(Milliseconds time, uint16_t newPeriod) 59 | { 60 | if (newPeriod != this->period) { 61 | if (this->period == 0) { 62 | this->timeOffset = newPeriod - (time % newPeriod); 63 | } else if (newPeriod == 0) { 64 | this->timeOffset = 0; 65 | } else { 66 | uint16_t oldPhase = this->getPhase(time); 67 | Milliseconds newPhase = time % newPeriod; 68 | uint32_t newOffset = newPeriod - newPhase; 69 | newOffset += (oldPhase * newPeriod) / this->period; 70 | newOffset = newOffset % newPeriod; 71 | this->timeOffset = newOffset; 72 | } 73 | this->period = newPeriod; 74 | } 75 | } 76 | 77 | void LFO::setWave(uint8_t newWave) 78 | { 79 | this->wave = newWave; 80 | } 81 | 82 | bool LFO::getOneShot() 83 | { 84 | return (this->flags & LFO_PRIV_FLAG_ONESHOT) != 0; 85 | } 86 | 87 | void LFO::setOneShot(bool oneShot) 88 | { 89 | this->flags = oneShot ? this->flags | LFO_PRIV_FLAG_ONESHOT : this->flags & ~LFO_PRIV_FLAG_ONESHOT; 90 | } 91 | 92 | void LFO::reset() 93 | { 94 | this->rndLastUpdate = 0; 95 | this->updateRandom(); 96 | this->timeOffset = 0; 97 | this->period = 0; 98 | } 99 | 100 | float LFO::getRandomValue(Milliseconds time) 101 | { 102 | if ((time - this->rndLastUpdate) >= this->period) { 103 | this->rndLastUpdate = time; 104 | this->updateRandom(); 105 | } 106 | return this->rndValue; 107 | } 108 | 109 | void LFO::updateRandom() 110 | { 111 | this->rndValue = rand() / (float)RAND_MAX; 112 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/LFO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TimeSource.h" 4 | 5 | #define LFO_WAVE_TRIANGLE 0 6 | #define LFO_WAVE_SAW 1 7 | #define LFO_WAVE_RAMP 2 8 | #define LFO_WAVE_SQUARE 3 9 | #define LFO_WAVE_RANDOM 4 10 | 11 | #define LFO_WAVE_MAX 4 12 | 13 | class LFO { 14 | public: 15 | LFO(); 16 | float getValue(Milliseconds time); 17 | void setPeriod(Milliseconds time, uint16_t newPeriod); 18 | void setWave(uint8_t newWave); 19 | bool getOneShot(); 20 | void setOneShot(bool oneShot); 21 | void reset(); 22 | 23 | private: 24 | uint16_t period; 25 | uint8_t wave; 26 | uint16_t timeOffset; 27 | Milliseconds rndLastUpdate; 28 | float rndValue; 29 | uint8_t flags; 30 | uint16_t getPhase(Milliseconds time); 31 | float getRandomValue(Milliseconds time); 32 | void updateRandom(); 33 | }; 34 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/MIDICommon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DRUM_CHANNEL 9 6 | 7 | #define PRIORITY_ACTIVE 120 8 | #define PRIORITY_SUSTAINED 110 9 | #define PRIORITY_ACTIVE_MONO 130 10 | 11 | typedef enum NoteStatus : uint8_t { 12 | INACTIVE, 13 | ACTIVE, 14 | SUSTAINED 15 | } NoteStatus; 16 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/MIDIParser.cpp: -------------------------------------------------------------------------------- 1 | #include "MIDIParser.h" 2 | 3 | MIDIParser::MIDIParser(IMIDIDriver* driver) : buffer(), bufferPos(0), driver(driver) 4 | { 5 | } 6 | 7 | void MIDIParser::putBuffer(uint8_t* data, uint32_t length) 8 | { 9 | while (length > 0) { 10 | if (data[0] & 0x80) { 11 | if ((this->bufferPos > 0) && (this->buffer[0] == 0xf0)) { 12 | if (data[0] == 0xf7) { 13 | // End of a SysEx message - store the end byte 14 | this->putByte(0xf7); 15 | data++; 16 | length--; 17 | this->parseAndDeletePacket(); 18 | continue; 19 | } 20 | } 21 | this->parseAndDeletePacket(); 22 | this->bufferPos = 0; 23 | this->putByte(data[0]); 24 | data++; 25 | length--; 26 | } else { 27 | if (this->bufferPos > 0) { 28 | this->putByte(data[0]); 29 | } 30 | data++; 31 | length--; 32 | } 33 | } 34 | 35 | this->parseAndDeletePacket(); 36 | } 37 | 38 | void MIDIParser::putByte(uint8_t b) 39 | { 40 | if (this->bufferPos >= MIDI_BUFFER_SIZE) { 41 | this->bufferPos = 0; 42 | } else { 43 | this->buffer[this->bufferPos++] = b; 44 | } 45 | } 46 | 47 | #define CHECK_LENGTH(l) \ 48 | if (length < l) \ 49 | return false 50 | 51 | bool MIDIParser::parsePacket() 52 | { 53 | uint8_t* msgData = &this->buffer[0]; 54 | uint32_t length = this->bufferPos; 55 | 56 | if (length == 0) 57 | return false; 58 | 59 | uint8_t msgType = msgData[0] & 0xf0; 60 | uint8_t channel = msgData[0] & 0x0f; 61 | 62 | if (msgType == 0x80) { 63 | CHECK_LENGTH(3); 64 | this->driver->noteOff(channel, msgData[1] & 0x7f, msgData[2] & 0x7f); 65 | return true; 66 | } else if (msgType == 0x90) { 67 | CHECK_LENGTH(3); 68 | this->driver->noteOn(channel, msgData[1] & 0x7f, msgData[2] & 0x7f); 69 | return true; 70 | } else if (msgType == 0xb0) { 71 | CHECK_LENGTH(3); 72 | this->driver->controlChange(channel, msgData[1] & 0x7f, msgData[2] & 0x7f); 73 | return true; 74 | } else if (msgType == 0xc0) { 75 | CHECK_LENGTH(2); 76 | this->driver->programChange(channel, msgData[1] & 0x7f); 77 | return true; 78 | } else if (msgData[0] == 0xf0) { 79 | if (msgData[length - 1] == 0xf7) { 80 | this->driver->sysEx(msgData, length); 81 | return true; 82 | } 83 | } else if (msgType == 0xe0) { 84 | CHECK_LENGTH(3); 85 | this->driver->pitchWheel(channel, msgData[1] | (msgData[2] << 7)); 86 | return true; 87 | } else { 88 | // Unknown message, skip over it 89 | this->bufferPos = 0; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | void MIDIParser::parseAndDeletePacket() 96 | { 97 | if (this->parsePacket()) { 98 | this->bufferPos = 0; 99 | } 100 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/MIDIParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IMIDIDriver.h" 4 | 5 | #include 6 | 7 | #define MIDI_BUFFER_SIZE 512 8 | 9 | class MIDIParser { 10 | public: 11 | MIDIParser(IMIDIDriver* driver); 12 | void putBuffer(uint8_t* data, uint32_t length); 13 | 14 | private: 15 | uint8_t buffer[MIDI_BUFFER_SIZE]; 16 | uint16_t bufferPos; 17 | IMIDIDriver* driver; 18 | bool parsePacket(); 19 | void parseAndDeletePacket(); 20 | void putByte(uint8_t b); 21 | }; 22 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/MonoNoteManager.cpp: -------------------------------------------------------------------------------- 1 | #include "MonoNoteManager.h" 2 | 3 | bool shouldGlideFromLastNote(uint8_t polyMode) 4 | { 5 | return (polyMode == POLYMODE_MONO_EXT || polyMode == POLYMODE_LEGATO_EXT); 6 | } 7 | 8 | bool isLegato(uint8_t polyMode) 9 | { 10 | return (polyMode == POLYMODE_LEGATO || polyMode == POLYMODE_LEGATO_EXT); 11 | } 12 | 13 | bool MonoChannelStatus::isSustainActive() 14 | { 15 | return this->flags & MONO_FLAG_SUSTAINACTIVE; 16 | } 17 | 18 | void MonoChannelStatus::setSustainActive(bool newSustainActive) 19 | { 20 | if (newSustainActive) 21 | this->flags |= MONO_FLAG_SUSTAINACTIVE; 22 | else 23 | this->flags &= ~MONO_FLAG_SUSTAINACTIVE; 24 | } 25 | 26 | bool MonoChannelStatus::isNotePlaying() 27 | { 28 | return this->flags & MONO_FLAG_NOTE_PLAYING; 29 | } 30 | 31 | void MonoChannelStatus::setNotePlaying(bool newNotePlaying) 32 | { 33 | if (newNotePlaying) 34 | this->flags |= MONO_FLAG_NOTE_PLAYING; 35 | else 36 | this->flags &= ~MONO_FLAG_NOTE_PLAYING; 37 | } 38 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | DefaultOPLPatchManager.cpp 4 | DefaultOPLPatchManager.h 5 | OPL2MIDISystem.cpp 6 | OPL2MIDISystem.h 7 | OPL2VoiceAllocator.cpp 8 | OPL2VoiceAllocator.h 9 | OPL3MIDISystem.cpp 10 | OPL3MIDISystem.h 11 | OPL3VoiceAllocator.cpp 12 | OPL3VoiceAllocator.h 13 | OPLGMPatchData.cpp 14 | OPLGMPatchData.h 15 | OPLParamInfo.g.cpp 16 | OPLParamInfo.h 17 | OPLToneGenerator.cpp 18 | OPLToneGenerator.h 19 | ) 20 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/DefaultOPLPatchManager.cpp: -------------------------------------------------------------------------------- 1 | #include "DefaultOPLPatchManager.h" 2 | #include "OPLGMPatchData.h" 3 | 4 | bool DefaultOPLPatchManager::getPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& outPatch) 5 | { 6 | if (noteNum < 0x80) { 7 | return OPL::getGMDrumPatch(noteNum, outPatch); 8 | } else { 9 | outPatch = OPL::getGMPatch(bank, program); 10 | return true; 11 | } 12 | } 13 | 14 | bool DefaultOPLPatchManager::supportsDrums() 15 | { 16 | return true; 17 | } 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/DefaultOPLPatchManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../PatchManagerBase.h" 5 | 6 | class DefaultOPLPatchManager : public PatchManagerBase { 7 | public: 8 | virtual bool supportsDrums() override; 9 | virtual bool getPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& outPatch) override; 10 | }; 11 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL2MIDISystem.cpp: -------------------------------------------------------------------------------- 1 | #include "OPL2MIDISystem.h" 2 | #include "../SysEx.h" 3 | #include "OPLParamInfo.h" 4 | 5 | OPL2MIDISystem::OPL2MIDISystem(BufferedOPLDevice* device, PatchManagerBase* patchManager) 6 | : timeSource(), voiceAllocator(), toneGenerator(device, &timeSource), 7 | midiDriver(&voiceAllocator, &toneGenerator, patchManager, &timeSource, SYSEX_DEV_ID_OPL2, &OPL_PARAMS_INFO) 8 | { 9 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL2MIDISystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../MIDIDriver.h" 5 | #include "../PatchManagerBase.h" 6 | #include "../TimeSource.h" 7 | #include "OPL2VoiceAllocator.h" 8 | #include "OPLToneGenerator.h" 9 | 10 | class OPL2MIDISystem { 11 | public: 12 | TimeSource timeSource; 13 | OPL2VoiceAllocator voiceAllocator; 14 | OPLToneGenerator toneGenerator; 15 | MIDIDriver midiDriver; 16 | OPL2MIDISystem(BufferedOPLDevice* device, PatchManagerBase* patchManager); 17 | }; 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL2VoiceAllocator.cpp: -------------------------------------------------------------------------------- 1 | #include "OPL2VoiceAllocator.h" 2 | 3 | OPL2VoiceAllocator::OPL2VoiceAllocator() : BasicVoiceAllocator() 4 | { 5 | } 6 | 7 | bool OPL2VoiceAllocator::reserve(uint8_t& outVoice, const VoiceStatus& status, OPLTone& tone, uint8_t priority) 8 | { 9 | if (tone.is4Op()) { 10 | return false; 11 | } else { 12 | return BasicVoiceAllocator::reserve(outVoice, status, tone, priority); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL2VoiceAllocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../BasicVoiceAllocator.h" 5 | 6 | class OPL2VoiceAllocator : public BasicVoiceAllocator { 7 | public: 8 | OPL2VoiceAllocator(); 9 | virtual bool reserve(uint8_t& outVoice, const VoiceStatus& status, OPLTone& tone, uint8_t priority); 10 | }; 11 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL3MIDISystem.cpp: -------------------------------------------------------------------------------- 1 | #include "OPL3MIDISystem.h" 2 | #include "../SysEx.h" 3 | #include "OPLParamInfo.h" 4 | 5 | OPL3MIDISystem::OPL3MIDISystem(OPLReadWriteDeviceBase* device, PatchManagerBase* patchManager) 6 | : timeSource(), voiceAllocator(), toneGenerator(device, &timeSource), 7 | midiDriver(&voiceAllocator, &toneGenerator, patchManager, &timeSource, SYSEX_DEV_ID_OPL3, &OPL_PARAMS_INFO) 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL3MIDISystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../MIDIDriver.h" 5 | #include "../PatchManagerBase.h" 6 | #include "../TimeSource.h" 7 | #include "OPL3VoiceAllocator.h" 8 | #include "OPLToneGenerator.h" 9 | #include "OPLParamInfo.h" 10 | 11 | class OPL3MIDISystem { 12 | public: 13 | TimeSource timeSource; 14 | OPL3VoiceAllocator voiceAllocator; 15 | OPLToneGenerator toneGenerator; 16 | MIDIDriver midiDriver; 17 | OPL3MIDISystem(OPLReadWriteDeviceBase* device, PatchManagerBase* patchManager); 18 | }; 19 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL3VoiceAllocator.cpp: -------------------------------------------------------------------------------- 1 | #include "OPL3VoiceAllocator.h" 2 | #include "../../OPL/OPLUtils.h" 3 | 4 | #include 5 | 6 | void OPL3VoiceAllocator::reset() 7 | { 8 | this->alloc2Op.reset(); 9 | this->alloc4Op.reset(); 10 | this->voice4OpModes.reset(); 11 | } 12 | 13 | bool OPL3VoiceAllocator::reserve(uint8_t& outVoice, const VoiceStatus& status, OPLTone& tone, uint8_t priority) 14 | { 15 | if (tone.toneData.flags & OPL_TONE_FLAG_4OP) { 16 | uint8_t voice4Op; 17 | if (this->alloc4Op.reserve(voice4Op, status, tone, priority)) { 18 | outVoice = voice4Op + 18; 19 | this->setPriority(outVoice, priority, true); 20 | return true; 21 | } else { 22 | return false; 23 | } 24 | } else { 25 | uint8_t voice2Op; 26 | if (this->alloc2Op.reserve(voice2Op, status, tone, priority)) { 27 | outVoice = voice2Op; 28 | this->setPriority(outVoice, priority, true); 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | } 34 | } 35 | 36 | void OPL3VoiceAllocator::setPriority(uint8_t voice, uint8_t newPriority) 37 | { 38 | this->setPriority(voice, newPriority, false); 39 | } 40 | 41 | uint8_t OPL3VoiceAllocator::getPriority(uint8_t voice) 42 | { 43 | if (voice > 17) { 44 | return this->alloc4Op.getPriority(voice - 18); 45 | } else { 46 | return this->alloc2Op.getPriority(voice); 47 | } 48 | } 49 | 50 | void OPL3VoiceAllocator::setPriority(uint8_t voice, uint8_t newPriority, bool alreadyReserved) 51 | { 52 | if (voice > 17) { 53 | uint8_t voice4Op = voice - 18; 54 | uint8_t voices2Op[2]; 55 | OPL::get2OpChannelsFor4OpChannel(voice4Op, &voices2Op[0]); 56 | if (!alreadyReserved) { 57 | this->alloc4Op.setPriority(voice4Op, newPriority); 58 | } 59 | this->alloc2Op.setPriority(voices2Op[0], newPriority); 60 | this->alloc2Op.setPriority(voices2Op[1], newPriority); 61 | this->voice4OpModes[voice4Op] = true; 62 | } else { 63 | if (!alreadyReserved) { 64 | this->alloc2Op.setPriority(voice, newPriority); 65 | } 66 | uint8_t voice4Op = OPL::get4OpChannelFor2OpChannel(voice); 67 | uint8_t voices2Op[2]; 68 | OPL::get2OpChannelsFor4OpChannel(voice4Op, &voices2Op[0]); 69 | if (voice4Op != 255) { 70 | uint8_t maxPriority = std::max( 71 | this->alloc2Op.getPriority(voices2Op[0]), 72 | this->alloc2Op.getPriority(voices2Op[1])); 73 | this->alloc4Op.setPriority(voice4Op, maxPriority); 74 | this->voice4OpModes[voice4Op] = false; 75 | } 76 | } 77 | } 78 | 79 | const VoiceStatus* OPL3VoiceAllocator::getVoiceStatus(uint8_t voice) 80 | { 81 | if (voice > 17) { 82 | return this->alloc4Op.getVoiceStatus(voice - 18); 83 | } else { 84 | return this->alloc2Op.getVoiceStatus(voice); 85 | } 86 | } 87 | 88 | void OPL3VoiceAllocator::applyToChannelVoices(uint8_t channel, std::function fn) 89 | { 90 | for (uint8_t v = 0; v < 24; v++) { 91 | if (this->getChannel(v) == channel) { 92 | auto status = this->getVoiceStatus(v); 93 | fn(v, status); 94 | } 95 | } 96 | } 97 | 98 | bool OPL3VoiceAllocator::isVoiceEnabled(uint8_t voice) 99 | { 100 | if (voice > 17) { 101 | return this->voice4OpModes[voice - 18]; 102 | } else { 103 | uint8_t voice4Op = OPL::get4OpChannelFor2OpChannel(voice); 104 | return !this->voice4OpModes[voice4Op]; 105 | } 106 | } 107 | 108 | bool OPL3VoiceAllocator::isVoiceCompatible(uint8_t voice, OPLTone& tone) 109 | { 110 | if (voice > 17) { 111 | return tone.is4Op(); 112 | } else { 113 | return !tone.is4Op(); 114 | } 115 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPL3VoiceAllocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../BasicVoiceAllocator.h" 5 | #include "../IVoiceAllocator.h" 6 | 7 | #include 8 | 9 | class OPL3VoiceAllocator : public IVoiceAllocator { 10 | public: 11 | virtual void reset(); 12 | virtual bool reserve(uint8_t& outVoice, const VoiceStatus& status, OPLTone& tone, uint8_t priority); 13 | virtual void setPriority(uint8_t voice, uint8_t newPriority); 14 | virtual uint8_t getPriority(uint8_t voice); 15 | virtual const VoiceStatus* getVoiceStatus(uint8_t voice); 16 | virtual void applyToChannelVoices(uint8_t channel, std::function fn); 17 | virtual bool isVoiceEnabled(uint8_t voice); 18 | virtual bool isVoiceCompatible(uint8_t voice, OPLTone& tone); 19 | 20 | private: 21 | BasicVoiceAllocator alloc2Op; 22 | BasicVoiceAllocator alloc4Op; 23 | std::bitset<6> voice4OpModes; 24 | void setPriority(uint8_t voice, uint8_t newPriority, bool alreadyReserved); 25 | }; 26 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPLGMPatchData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../Patch.h" 5 | 6 | #include 7 | 8 | namespace OPL { 9 | Patch getGMPatch(uint8_t bank, uint8_t program); 10 | bool getGMDrumPatch(uint8_t note, Patch& patch); 11 | } 12 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPLParamInfo.g.cpp: -------------------------------------------------------------------------------- 1 | #include "OPLParamInfo.h" 2 | 3 | const ParamInfo OPL_PARAMS_INFO_ITEMS[] = { 4 | { 0x0000, 0 }, // 4-op mode 5 | { 0x0001, 0 }, // Feedback 6 | { 0x0002, 0 }, // Connection 7 | { 0x0010, 0 }, // Op 1 AM 8 | { 0x0011, 0 }, // Op 1 VIB 9 | { 0x0012, 1 }, // Op 1 EGT 10 | { 0x0013, 0 }, // Op 1 KSR 11 | { 0x0014, 1 }, // Op 1 MULT 12 | { 0x0015, 0 }, // Op 1 KSL 13 | { 0x0016, 63 }, // Op 1 level 14 | { 0x0017, 15 }, // Op 1 attack 15 | { 0x0018, 0 }, // Op 1 decay 16 | { 0x0019, 15 }, // Op 1 sustain 17 | { 0x001a, 15 }, // Op 1 release 18 | { 0x001b, 0 }, // Op 1 wave 19 | { 0x0020, 0 }, // Op 2 AM 20 | { 0x0021, 0 }, // Op 2 VIB 21 | { 0x0022, 1 }, // Op 2 EGT 22 | { 0x0023, 0 }, // Op 2 KSR 23 | { 0x0024, 1 }, // Op 2 MULT 24 | { 0x0025, 0 }, // Op 2 KSL 25 | { 0x0026, 0 }, // Op 2 level 26 | { 0x0027, 15 }, // Op 2 attack 27 | { 0x0028, 0 }, // Op 2 decay 28 | { 0x0029, 15 }, // Op 2 sustain 29 | { 0x002a, 15 }, // Op 2 release 30 | { 0x002b, 0 }, // Op 2 wave 31 | { 0x0030, 0 }, // Op 3 AM 32 | { 0x0031, 0 }, // Op 3 VIB 33 | { 0x0032, 1 }, // Op 3 EGT 34 | { 0x0033, 0 }, // Op 3 KSR 35 | { 0x0034, 1 }, // Op 3 MULT 36 | { 0x0035, 0 }, // Op 3 KSL 37 | { 0x0036, 63 }, // Op 3 level 38 | { 0x0037, 15 }, // Op 3 attack 39 | { 0x0038, 0 }, // Op 3 decay 40 | { 0x0039, 15 }, // Op 3 sustain 41 | { 0x003a, 15 }, // Op 3 release 42 | { 0x003b, 0 }, // Op 3 wave 43 | { 0x0040, 0 }, // Op 4 AM 44 | { 0x0041, 0 }, // Op 4 VIB 45 | { 0x0042, 1 }, // Op 4 EGT 46 | { 0x0043, 0 }, // Op 4 KSR 47 | { 0x0044, 1 }, // Op 4 MULT 48 | { 0x0045, 0 }, // Op 4 KSL 49 | { 0x0046, 0 }, // Op 4 level 50 | { 0x0047, 15 }, // Op 4 attack 51 | { 0x0048, 0 }, // Op 4 decay 52 | { 0x0049, 15 }, // Op 4 sustain 53 | { 0x004a, 15 }, // Op 4 release 54 | { 0x004b, 0 }, // Op 4 wave 55 | }; 56 | 57 | const ParamInfoList OPL_PARAMS_INFO = { 51, OPL_PARAMS_INFO_ITEMS }; 58 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPLParamInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ParamInfo.h" 4 | 5 | extern const ParamInfoList OPL_PARAMS_INFO; 6 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPL/OPLToneGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/BufferedOPLDevice.h" 4 | #include "../../OPL/OPLTone.h" 5 | #include "../IToneGenerator.h" 6 | #include "../TimeSource.h" 7 | 8 | class OPLToneGenerator : public IToneGenerator { 9 | public: 10 | OPLToneGenerator(OPLReadWriteDeviceBase* device, TimeSource* timeSource); 11 | virtual void reset(bool hardReset); 12 | virtual void setTone(uint8_t voice, OPLTone& tone, uint8_t volume, uint8_t pan); 13 | virtual void setPitch(uint8_t voice, float note); 14 | virtual void setVolume(uint8_t voice, uint8_t volume); 15 | virtual void setPan(uint8_t voice, uint8_t pan); 16 | virtual void noteOn(uint8_t voice, float note); 17 | virtual void noteOff(uint8_t voice); 18 | virtual bool isNoteActive(uint8_t voice); 19 | 20 | private: 21 | OPLReadWriteDeviceBase* device; 22 | TimeSource* timeSource; 23 | OPLTone voiceTones[24]; 24 | uint8_t voiceVolume[24]; 25 | uint8_t voicePan[24]; 26 | Milliseconds voiceReleaseTime[24]; 27 | uint8_t voiceStates[24]; 28 | void resetState(); 29 | void updateTone(uint8_t voice); 30 | void setPitch(uint8_t voice, float note, bool keyOn); 31 | Milliseconds getReleaseDuration(uint8_t releaseRate); 32 | Milliseconds getReleaseRate(OPLTone& tone); 33 | Milliseconds getReleaseTime(uint8_t releaseRate); 34 | }; 35 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | OPMMIDISystem.cpp 4 | OPMMIDISystem.h 5 | OPMParamInfo.g.cpp 6 | OPMParamInfo.h 7 | OPMToneGenerator.cpp 8 | OPMToneGenerator.h 9 | ) 10 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/OPMMIDISystem.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMMIDISystem.h" 2 | #include "OPMParamInfo.h" 3 | 4 | OPMMIDISystem::OPMMIDISystem(BufferedOPMDevice* device, PatchManagerBase* patchManager) 5 | : voiceAllocator(), toneGenerator(device), timeSource(), 6 | midiDriver(&voiceAllocator, &toneGenerator, patchManager, &timeSource, SYSEX_DEV_ID_OPM, &OPM_PARAMS_INFO) 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/OPMMIDISystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPM/OPMTone.h" 4 | #include "../BasicVoiceAllocator.h" 5 | #include "../MIDIDriver.h" 6 | #include "../PatchManagerBase.h" 7 | #include "../TimeSource.h" 8 | #include "OPMToneGenerator.h" 9 | 10 | class OPMMIDISystem { 11 | public: 12 | BasicVoiceAllocator voiceAllocator; 13 | OPMToneGenerator toneGenerator; 14 | TimeSource timeSource; 15 | MIDIDriver midiDriver; 16 | OPMMIDISystem(BufferedOPMDevice* device, PatchManagerBase* patchManager); 17 | }; 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/OPMParamInfo.g.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMParamInfo.h" 2 | 3 | const ParamInfo OPM_PARAMS_INFO_ITEMS[] = { 4 | { 0x0001, 0 }, // Feedback 5 | { 0x0002, 0 }, // Connection 6 | { 0x0003, 0 }, // PM 7 | { 0x0004, 0 }, // AM 8 | { 0x0005, 15 }, // Operators 9 | { 0x0010, 0 }, // Op 1 detune 1 10 | { 0x0011, 1 }, // Op 1 mult 11 | { 0x0012, 127 }, // Op 1 level 12 | { 0x0013, 0 }, // Op 1 KS 13 | { 0x0014, 31 }, // Op 1 attack 14 | { 0x0015, 0 }, // Op 1 AM 15 | { 0x0016, 0 }, // Op 1 D1R 16 | { 0x0017, 0 }, // Op 1 detune 2 17 | { 0x0018, 0 }, // Op 1 D2R 18 | { 0x0019, 0 }, // Op 1 D1L 19 | { 0x001a, 15 }, // Op 1 RR 20 | { 0x0020, 0 }, // Op 2 detune 1 21 | { 0x0021, 1 }, // Op 2 mult 22 | { 0x0022, 127 }, // Op 2 level 23 | { 0x0023, 0 }, // Op 2 KS 24 | { 0x0024, 31 }, // Op 2 attack 25 | { 0x0025, 0 }, // Op 2 AM 26 | { 0x0026, 0 }, // Op 2 D1R 27 | { 0x0027, 0 }, // Op 2 detune 2 28 | { 0x0028, 0 }, // Op 2 D2R 29 | { 0x0029, 0 }, // Op 2 D1L 30 | { 0x002a, 15 }, // Op 2 RR 31 | { 0x0030, 0 }, // Op 3 detune 1 32 | { 0x0031, 1 }, // Op 3 mult 33 | { 0x0032, 127 }, // Op 3 level 34 | { 0x0033, 0 }, // Op 3 KS 35 | { 0x0034, 31 }, // Op 3 attack 36 | { 0x0035, 0 }, // Op 3 AM 37 | { 0x0036, 0 }, // Op 3 D1R 38 | { 0x0037, 0 }, // Op 3 detune 2 39 | { 0x0038, 0 }, // Op 3 D2R 40 | { 0x0039, 0 }, // Op 3 D1L 41 | { 0x003a, 15 }, // Op 3 RR 42 | { 0x0040, 0 }, // Op 4 detune 1 43 | { 0x0041, 1 }, // Op 4 mult 44 | { 0x0042, 0 }, // Op 4 level 45 | { 0x0043, 0 }, // Op 4 KS 46 | { 0x0044, 31 }, // Op 4 attack 47 | { 0x0045, 0 }, // Op 4 AM 48 | { 0x0046, 0 }, // Op 4 D1R 49 | { 0x0047, 0 }, // Op 4 detune 2 50 | { 0x0048, 0 }, // Op 4 D2R 51 | { 0x0049, 0 }, // Op 4 D1L 52 | { 0x004a, 15 }, // Op 4 RR 53 | }; 54 | 55 | const ParamInfoList OPM_PARAMS_INFO = { 49, OPM_PARAMS_INFO_ITEMS }; 56 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/OPMParamInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ParamInfo.h" 4 | 5 | extern const ParamInfoList OPM_PARAMS_INFO; 6 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/OPMToneGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMToneGenerator.h" 2 | #include "../../OPM/OPMUtils.h" 3 | 4 | #include 5 | 6 | OPMToneGenerator::OPMToneGenerator(BufferedOPMDevice* device) : device(device) 7 | { 8 | this->resetState(); 9 | } 10 | 11 | void OPMToneGenerator::reset(bool hardReset) 12 | { 13 | this->resetState(); 14 | if (hardReset) { 15 | this->device->hardReset(); 16 | } else { 17 | // TODO: Soft reset 18 | this->device->hardReset(); 19 | } 20 | } 21 | 22 | void OPMToneGenerator::resetState() 23 | { 24 | for (uint8_t v = 0; v < 8; v++) { 25 | this->voiceTones[v] = OPMTone(); 26 | this->voiceVolume[v] = 127; 27 | this->voicePan[v] = 63; 28 | } 29 | } 30 | 31 | void OPMToneGenerator::setTone(uint8_t voice, OPMTone& tone, uint8_t volume, uint8_t pan) 32 | { 33 | this->voiceTones[voice] = tone; 34 | this->voiceVolume[voice] = volume; 35 | this->voicePan[voice] = pan; 36 | this->updateTone(voice); 37 | } 38 | 39 | void OPMToneGenerator::setPitch(uint8_t voice, float note) 40 | { 41 | uint8_t intNote = (uint8_t)floor(note); 42 | uint8_t fraction = (uint8_t)floor((note - intNote) * 64); 43 | this->device->write(0x28 + voice, OPM::getKCForMIDINote(intNote)); 44 | this->device->write(0x30 + voice, fraction << 2); 45 | } 46 | 47 | void OPMToneGenerator::setVolume(uint8_t voice, uint8_t volume) 48 | { 49 | this->voiceVolume[voice] = volume; 50 | this->updateTone(voice); 51 | } 52 | 53 | void OPMToneGenerator::setPan(uint8_t voice, uint8_t pan) 54 | { 55 | this->voicePan[voice] = pan; 56 | this->updateTone(voice); 57 | } 58 | 59 | void OPMToneGenerator::noteOn(uint8_t voice, float note) 60 | { 61 | this->setPitch(voice, note); 62 | uint8_t snch = (this->voiceTones[voice].getOpsEnabled() << 3) | (voice & 0x07); 63 | this->device->write(0x08, snch); 64 | } 65 | 66 | void OPMToneGenerator::noteOff(uint8_t voice) 67 | { 68 | this->device->write(0x08, voice & 0x07); 69 | } 70 | 71 | void OPMToneGenerator::writeTone(uint8_t voice, OPMTone& tone) 72 | { 73 | this->device->write(0x20 + voice, tone.toneData.rl_fb_conect); 74 | this->device->write(0x38 + voice, tone.toneData.pms_ams); 75 | for (uint8_t op = 0; op < 4; op++) { 76 | uint8_t opOffset = (op * 8) + voice; 77 | OPMOperatorData& opData = tone.toneData.op[op]; 78 | this->device->write(0x40 + opOffset, opData.dt1_mul); 79 | this->device->write(0x60 + opOffset, opData.tl); 80 | this->device->write(0x80 + opOffset, opData.ks_ar); 81 | this->device->write(0xa0 + opOffset, opData.amsen_d1r); 82 | this->device->write(0xc0 + opOffset, opData.dt2_d2r); 83 | this->device->write(0xe0 + opOffset, opData.d1l_rr); 84 | } 85 | } 86 | 87 | void OPMToneGenerator::updateTone(uint8_t voice) 88 | { 89 | OPMTone tone = this->voiceTones[voice]; 90 | tone.applyVolume(this->voiceVolume[voice]); 91 | tone.applyPan(this->voicePan[voice]); 92 | this->writeTone(voice, tone); 93 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/OPM/OPMToneGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPM/BufferedOPMDevice.h" 4 | #include "../../OPM/OPMTone.h" 5 | #include "../IToneGenerator.h" 6 | 7 | class OPMToneGenerator : public IToneGenerator { 8 | public: 9 | OPMToneGenerator(BufferedOPMDevice* device); 10 | virtual void reset(bool hardReset); 11 | virtual void setTone(uint8_t voice, OPMTone& tone, uint8_t volume, uint8_t pan); 12 | virtual void setPitch(uint8_t voice, float note); 13 | virtual void setVolume(uint8_t voice, uint8_t volume); 14 | virtual void setPan(uint8_t voice, uint8_t pan); 15 | virtual void noteOn(uint8_t voice, float note); 16 | virtual void noteOff(uint8_t voice); 17 | 18 | private: 19 | BufferedOPMDevice* device; 20 | OPMTone voiceTones[8]; 21 | uint8_t voiceVolume[8]; 22 | uint8_t voicePan[8]; 23 | void resetState(); 24 | void writeTone(uint8_t voice, OPMTone& tone); 25 | void updateTone(uint8_t voice); 26 | }; 27 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/ParamIDs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Device-independent parameter IDs 4 | 5 | // General patch parameters 6 | #define PARAM_POLYMODE 0x2000 7 | #define PARAM_FIXEDNOTENUM 0x2001 8 | #define PARAM_PITCHOFFSET 0x2002 9 | #define PARAM_GLIDEDURATION 0x2003 10 | #define PARAM_VELOCITYDEPTH 0x2004 11 | 12 | #define POLYMODE_POLY 0x00 13 | #define POLYMODE_MONO 0x01 14 | #define POLYMODE_MONO_EXT 0x02 15 | #define POLYMODE_LEGATO 0x03 16 | #define POLYMODE_LEGATO_EXT 0x04 17 | 18 | // Parameter map parameters 19 | #define PARAM_PARAMMAP_START 0x3000 20 | #define PARAM_PARAMMAP_END 0x30ff 21 | 22 | #define PARAM_PARAMMAP_SRC 0 23 | #define PARAM_PARAMMAP_DESTPARAM 1 24 | #define PARAM_PARAMMAP_ADJUST_AMOUNT 2 25 | #define PARAM_PARAMMAP_INVERT_SRC 3 26 | 27 | #define PARAMMAP_SRC_NONE 255 28 | #define PARAMMAP_SRC_VELOCITY 192 29 | 30 | // LFO parameters 31 | #define PARAM_LFO_START 0x3100 32 | #define PARAM_LFO_END 0x31ff 33 | 34 | #define PARAM_LFO_WAVE 0 35 | #define PARAM_LFO_PERIOD 1 36 | #define PARAM_LFO_SYNC 2 37 | #define PARAM_LFO_ONESHOT 3 38 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/ParamInfo.g.cpp: -------------------------------------------------------------------------------- 1 | #include "ParamInfo.h" 2 | 3 | const ParamInfo UNIVERSAL_PARAMS_INFO_ITEMS[] = { 4 | { 0x2000, 0 }, // Poly mode 5 | { 0x2001, 0 }, // Note num 6 | { 0x2002, 8192 }, // Pitch offset 7 | { 0x2003, 0 }, // Glide 8 | { 0x2004, 127 }, // Velocity depth 9 | { 0x3000, 255 }, // Map 1 src 10 | { 0x3001, 0 }, // Map 1 dest 11 | { 0x3002, 8192 }, // Map 1 amount 12 | { 0x3003, 0 }, // Map 1 invert 13 | { 0x3010, 255 }, // Map 2 src 14 | { 0x3011, 0 }, // Map 2 dest 15 | { 0x3012, 8192 }, // Map 2 amount 16 | { 0x3013, 0 }, // Map 2 invert 17 | { 0x3020, 255 }, // Map 3 src 18 | { 0x3021, 0 }, // Map 3 dest 19 | { 0x3022, 8192 }, // Map 3 amount 20 | { 0x3023, 0 }, // Map 3 invert 21 | { 0x3030, 255 }, // Map 4 src 22 | { 0x3031, 0 }, // Map 4 dest 23 | { 0x3032, 8192 }, // Map 4 amount 24 | { 0x3033, 0 }, // Map 4 invert 25 | { 0x3100, 0 }, // LFO 1 wave 26 | { 0x3101, 0 }, // LFO 1 period 27 | { 0x3102, 0 }, // LFO 1 sync 28 | { 0x3103, 0 }, // LFO 1 one shot 29 | { 0x3110, 0 }, // LFO 2 wave 30 | { 0x3111, 0 }, // LFO 2 period 31 | { 0x3112, 0 }, // LFO 2 sync 32 | { 0x3113, 0 }, // LFO 2 one shot 33 | }; 34 | 35 | const ParamInfoList UNIVERSAL_PARAMS_INFO = { 29, UNIVERSAL_PARAMS_INFO_ITEMS }; 36 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/ParamInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct ParamInfo { 6 | uint16_t id; 7 | uint16_t defaultValue; 8 | }; 9 | 10 | struct ParamInfoList { 11 | uint16_t numParams; 12 | const ParamInfo* paramInfo; 13 | }; 14 | 15 | extern const ParamInfoList UNIVERSAL_PARAMS_INFO; 16 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/PatchManagerBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Patch.h" 4 | 5 | template 6 | class PatchManagerBase { 7 | public: 8 | virtual bool supportsDrums() 9 | { 10 | return false; 11 | } 12 | 13 | // noteNum should be 255 for melodic patches 14 | virtual bool getPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& outPatch) 15 | { 16 | return false; 17 | } 18 | 19 | virtual bool setPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& patch) 20 | { 21 | return false; 22 | } 23 | 24 | virtual bool deletePatch(uint8_t bank, uint8_t program, uint8_t noteNum) 25 | { 26 | return false; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/PatchParams.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../Utils.h" 4 | #include "PatchParams.h" 5 | 6 | float PatchParamUtils::pitchOffsetFromNRPNValue(uint16_t nrpnValue) 7 | { 8 | uint8_t msb = ((nrpnValue >> 7) & 0x7f); 9 | uint8_t lsb = nrpnValue & 0x7f; 10 | return ((float)msb - 64) + (lsb / 128.0f); 11 | } 12 | 13 | uint16_t PatchParamUtils::nrpnValueFromPitchOffset(float pitch) 14 | { 15 | pitch = clamp(pitch, -64.0f, 63.0f); 16 | uint8_t msb = (uint8_t)floor(pitch); 17 | uint8_t lsb = ((uint8_t)floor((pitch - msb) * 128.0f)) & 0x7f; 18 | return (msb << 7) | lsb; 19 | } 20 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/PatchParams.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ParamIDs.h" 6 | 7 | namespace PatchParamUtils { 8 | float pitchOffsetFromNRPNValue(uint16_t nrpnValue); 9 | uint16_t nrpnValueFromPitchOffset(float pitchOffset); 10 | } 11 | 12 | template 13 | class PatchParams { 14 | public: 15 | Tone tone; 16 | uint8_t polyMode = POLYMODE_POLY; 17 | uint8_t fixedNoteNum = 0; 18 | uint16_t pitchOffset = 0x2000; 19 | uint16_t glideDurationMS = 0; 20 | uint8_t velocityDepth = 0x7f; 21 | 22 | uint16_t getParam(uint16_t paramID) 23 | { 24 | switch (paramID) { 25 | case PARAM_POLYMODE: 26 | return polyMode; 27 | case PARAM_FIXEDNOTENUM: 28 | return fixedNoteNum; 29 | case PARAM_PITCHOFFSET: 30 | return this->pitchOffset; 31 | case PARAM_GLIDEDURATION: 32 | return this->glideDurationMS; 33 | case PARAM_VELOCITYDEPTH: 34 | return this->velocityDepth; 35 | default: 36 | return tone.getParam(paramID); 37 | } 38 | } 39 | void setParam(uint16_t paramID, uint16_t value) 40 | { 41 | switch (paramID) { 42 | case PARAM_POLYMODE: 43 | polyMode = value & 0xff; 44 | break; 45 | case PARAM_FIXEDNOTENUM: 46 | fixedNoteNum = value & 0x7f; 47 | break; 48 | case PARAM_PITCHOFFSET: 49 | pitchOffset = clamp(value, (uint16_t)0, (uint16_t)0x3fff); 50 | break; 51 | case PARAM_GLIDEDURATION: 52 | this->glideDurationMS = value; 53 | break; 54 | case PARAM_VELOCITYDEPTH: 55 | this->velocityDepth = value; 56 | default: 57 | tone.setParam(paramID, value); 58 | } 59 | } 60 | 61 | private: 62 | }; 63 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/PatchSerialization.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../7BitEncoding.h" 4 | #include "../Utils.h" 5 | #include "ParamInfo.h" 6 | #include "Patch.h" 7 | 8 | #include 9 | 10 | namespace PatchSerialization { 11 | 12 | template 13 | bool serialize(const ParamInfoList& chipParamsInfo, Patch& patch, IWriter* dest) 14 | { 15 | const ParamInfoList* paramInfoLists[] = { 16 | &chipParamsInfo, &UNIVERSAL_PARAMS_INFO 17 | }; 18 | 19 | uint16_t lastParamID = 0; 20 | for (uint16_t l = 0; l < 2; l++) { 21 | auto& paramInfoList = *(paramInfoLists[l]); 22 | for (uint16_t i = 0; i < paramInfoList.numParams; i++) { 23 | auto& paramInfo = paramInfoList.paramInfo[i]; 24 | auto paramID = paramInfo.id; 25 | auto paramValue = patch.getParam(paramID); 26 | if (paramValue != paramInfo.defaultValue) { 27 | auto paramIDDiff = paramID - lastParamID; 28 | if (!encodeLEB128(paramIDDiff, dest)) { 29 | return false; 30 | } 31 | if (!encodeLEB128(paramValue, dest)) { 32 | return false; 33 | } 34 | lastParamID = paramID; 35 | } 36 | } 37 | } 38 | 39 | return true; 40 | } 41 | 42 | template 43 | bool deserialize(Patch& outPatch, IReader* src) 44 | { 45 | Patch patch; 46 | 47 | uint16_t lastParamID = 0; 48 | while (!src->eof()) { 49 | uint16_t paramID; 50 | if (!decodeLEB128(paramID, src)) { 51 | return false; 52 | } 53 | paramID += lastParamID; 54 | 55 | uint16_t paramValue; 56 | if (!decodeLEB128(paramValue, src)) { 57 | return false; 58 | } 59 | 60 | patch.setParam(paramID, paramValue); 61 | 62 | lastParamID = paramID; 63 | } 64 | 65 | outPatch = patch; 66 | 67 | return true; 68 | } 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/PolyNoteManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "IToneGenerator.h" 6 | #include "IVoiceAllocator.h" 7 | #include "MIDICommon.h" 8 | #include "ToneController.h" 9 | 10 | typedef struct PolyNoteInfo { 11 | float pitch; // The pitch of the note before pitch wheel is applied 12 | NoteStatus noteStatus; 13 | } PolyNoteInfo; 14 | 15 | typedef struct PolyChannelStatus { 16 | bool sustainActive; 17 | } PolyChannelStatus; 18 | 19 | template 20 | class PolyNoteManager : public INoteManager { 21 | public: 22 | PolyNoteManager(IVoiceAllocator* voiceAllocator, 23 | ToneController* toneController) 24 | : voiceAllocator(voiceAllocator), 25 | toneController(toneController) 26 | { 27 | this->resetState(); 28 | } 29 | virtual void resetState() 30 | { 31 | for (uint8_t c = 0; c < 16; c++) { 32 | auto& status = this->channelStatuses[c]; 33 | status.sustainActive = false; 34 | } 35 | for (uint8_t v = 0; v < voices; v++) { 36 | auto& noteInfo = this->voiceNoteInfo[v]; 37 | noteInfo.pitch = 0.0f; 38 | noteInfo.noteStatus = INACTIVE; 39 | } 40 | } 41 | virtual void noteOn(uint8_t channel, uint8_t note, uint8_t velocity) 42 | { 43 | this->noteOff(channel, note, velocity); 44 | 45 | Patch patch; 46 | if (!this->toneController->getNotePatch(channel, note, patch)) 47 | return; 48 | 49 | uint8_t voice; 50 | if (this->voiceAllocator->reserve(voice, VoiceStatus(channel, note), patch.params.tone, PRIORITY_ACTIVE)) { 51 | uint8_t fixedNoteNum = patch.params.fixedNoteNum; 52 | float pitch = fixedNoteNum == 0 ? note : fixedNoteNum; 53 | PolyNoteInfo newNoteInfo; 54 | newNoteInfo.pitch = pitch; 55 | newNoteInfo.noteStatus = ACTIVE; 56 | this->voiceNoteInfo[voice] = newNoteInfo; 57 | this->toneController->noteOn(channel, voice, note, pitch, velocity); 58 | } 59 | } 60 | virtual void noteOff(uint8_t channel, uint8_t note, uint8_t velocity) 61 | { 62 | this->voiceAllocator->applyToChannelVoices(channel, [this, channel, note](uint8_t v, const VoiceStatus* voiceStatus) { 63 | auto& noteInfo = this->voiceNoteInfo[v]; 64 | if ((voiceStatus->noteNum == note) && (noteInfo.noteStatus != INACTIVE)) { 65 | if (this->channelStatuses[channel].sustainActive) { 66 | if (noteInfo.noteStatus == ACTIVE) { 67 | this->voiceAllocator->setPriority(v, PRIORITY_SUSTAINED); 68 | noteInfo.noteStatus = SUSTAINED; 69 | } 70 | } else { 71 | this->voiceOff(v); 72 | } 73 | } 74 | }); 75 | } 76 | virtual void setSustain(uint8_t channel, bool sustainActive) 77 | { 78 | auto& status = this->channelStatuses[channel]; 79 | status.sustainActive = sustainActive; 80 | if (!sustainActive) { 81 | this->clearSustain(channel); 82 | } 83 | } 84 | virtual void allNotesOff(uint8_t channel) 85 | { 86 | this->voiceAllocator->applyToChannelVoices(channel, [this](uint8_t v, const VoiceStatus* voiceStatus) { 87 | this->voiceOff(v); 88 | }); 89 | } 90 | 91 | private: 92 | IVoiceAllocator* voiceAllocator; 93 | ToneController* toneController; 94 | PolyChannelStatus channelStatuses[16]; 95 | PolyNoteInfo voiceNoteInfo[voices]; 96 | void clearSustain(uint8_t channel) 97 | { 98 | this->voiceAllocator->applyToChannelVoices(channel, [this](uint8_t v) { 99 | auto& noteInfo = this->voiceNoteInfo[v]; 100 | if (noteInfo.noteStatus == SUSTAINED) { 101 | this->voiceOff(v); 102 | } 103 | }); 104 | } 105 | void voiceOff(uint8_t voice) 106 | { 107 | this->voiceAllocator->release(voice); 108 | this->toneController->noteOff(voice); 109 | this->voiceNoteInfo[voice].noteStatus = INACTIVE; 110 | } 111 | }; 112 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/RAMPatchManager.h: -------------------------------------------------------------------------------- 1 | #include "Patch.h" 2 | #include "PatchManagerBase.h" 3 | 4 | template 5 | struct PatchEntry { 6 | PatchEntry() : bank(0xff), program(0xff), noteNum(0xff), patch() { } 7 | PatchEntry(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& patch) 8 | : bank(bank), program(program), noteNum(noteNum), patch(patch) { } 9 | uint8_t bank; 10 | uint8_t program; 11 | uint8_t noteNum; 12 | Patch patch; 13 | }; 14 | 15 | template 16 | class RAMPatchManager : public PatchManagerBase { 17 | public: 18 | RAMPatchManager(PatchManagerBase* fallbackPatchManager) : fallbackPatchManager(fallbackPatchManager), patches() 19 | { 20 | } 21 | 22 | virtual bool supportsDrums() override 23 | { 24 | return true; 25 | } 26 | 27 | virtual bool getPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& outPatch) override 28 | { 29 | auto i = this->findPatchEntry(bank, program, noteNum); 30 | if (i < 0) { 31 | if (this->fallbackPatchManager) { 32 | return this->fallbackPatchManager->getPatch(bank, program, noteNum, outPatch); 33 | } else { 34 | return false; 35 | } 36 | } else { 37 | outPatch = this->patches[i].patch; 38 | return true; 39 | } 40 | } 41 | 42 | virtual bool setPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& patch) override 43 | { 44 | // Look for an existing patch to overwrite 45 | auto i = this->findPatchEntry(bank, program, noteNum); 46 | // Look for an empty space 47 | if (i < 0) { 48 | i = this->findPatchEntry(0xff, 0xff, 0xff); 49 | } 50 | if (i < 0) { 51 | // No space left 52 | return false; 53 | } 54 | 55 | this->patches[i] = PatchEntry(bank, program, noteNum, patch); 56 | 57 | return true; 58 | } 59 | 60 | virtual bool deletePatch(uint8_t bank, uint8_t program, uint8_t noteNum) override 61 | { 62 | auto i = this->findPatchEntry(bank, program, noteNum); 63 | if (i < 0) { 64 | return false; 65 | } else { 66 | this->patches[i] = PatchEntry(); 67 | return true; 68 | } 69 | } 70 | 71 | private: 72 | PatchManagerBase* fallbackPatchManager; 73 | PatchEntry patches[numPatches]; 74 | 75 | int findPatchEntry(uint8_t bank, uint8_t program, uint8_t noteNum) 76 | { 77 | for (int i = 0; i < numPatches; i++) { 78 | auto& aPatch = this->patches[i]; 79 | if (aPatch.bank == bank && aPatch.program == program && aPatch.noteNum == noteNum) { 80 | return i; 81 | } 82 | } 83 | return -1; 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | DefaultSD1PatchManager.cpp 4 | DefaultSD1PatchManager.h 5 | OPLSD1MIDISystem.cpp 6 | OPLSD1MIDISystem.h 7 | OPLSD1ToneGenerator.cpp 8 | OPLSD1ToneGenerator.h 9 | SD1MIDISystem.cpp 10 | SD1MIDISystem.h 11 | SD1ParamInfo.g.cpp 12 | SD1ParamInfo.h 13 | SD1Patches.cpp 14 | SD1Patches.h 15 | SD1ToneGenerator.cpp 16 | SD1ToneGenerator.h 17 | ) -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/DefaultSD1PatchManager.cpp: -------------------------------------------------------------------------------- 1 | #include "DefaultSD1PatchManager.h" 2 | #include "SD1Patches.h" 3 | 4 | bool DefaultSD1PatchManager::supportsDrums() 5 | { 6 | return true; 7 | } 8 | 9 | bool DefaultSD1PatchManager::getPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& outPatch) 10 | { 11 | if (noteNum < 0x80) { 12 | return SD1::getGMDrumPatch(noteNum, outPatch); 13 | } else { 14 | SD1::getGMPatch(bank, program, outPatch); 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/DefaultSD1PatchManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../SD1/SD1Tone.h" 4 | #include "../PatchManagerBase.h" 5 | 6 | class DefaultSD1PatchManager : public PatchManagerBase { 7 | public: 8 | virtual bool supportsDrums() override; 9 | virtual bool getPatch(uint8_t bank, uint8_t program, uint8_t noteNum, Patch& outPatch) override; 10 | }; 11 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/OPLSD1MIDISystem.cpp: -------------------------------------------------------------------------------- 1 | #include "OPLSD1MIDISystem.h" 2 | #include "../OPL/OPLParamInfo.h" 3 | 4 | OPLSD1MIDISystem::OPLSD1MIDISystem(SD1DeviceBase* device, PatchManagerBase* patchManager) 5 | : voiceAllocator(), toneGenerator(device), timeSource(), 6 | midiDriver(&voiceAllocator, &toneGenerator, patchManager, &timeSource, SYSEX_DEV_ID_OPL3, &OPL_PARAMS_INFO) 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/OPLSD1MIDISystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../BasicVoiceAllocator.h" 5 | #include "../MIDIDriver.h" 6 | #include "../PatchManagerBase.h" 7 | #include "../TimeSource.h" 8 | #include "OPLSD1ToneGenerator.h" 9 | 10 | class OPLSD1MIDISystem { 11 | public: 12 | BasicVoiceAllocator voiceAllocator; 13 | OPLSD1ToneGenerator toneGenerator; 14 | TimeSource timeSource; 15 | MIDIDriver midiDriver; 16 | OPLSD1MIDISystem(SD1DeviceBase* device, PatchManagerBase* patchManager); 17 | }; 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/OPLSD1ToneGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "OPLSD1ToneGenerator.h" 2 | #include "../../SD1/SD1Utils.h" 3 | 4 | OPLSD1ToneGenerator::OPLSD1ToneGenerator(SD1DeviceBase* device) : sd1ToneGenerator(device) 5 | { 6 | } 7 | 8 | void OPLSD1ToneGenerator::reset(bool hardReset) 9 | { 10 | this->sd1ToneGenerator.reset(hardReset); 11 | } 12 | 13 | void OPLSD1ToneGenerator::setTone(uint8_t voice, OPLTone& tone, uint8_t volume, uint8_t pan) 14 | { 15 | this->voiceTones[voice] = tone; 16 | this->voiceVolumes[voice] = volume; 17 | this->updateTone(voice); 18 | } 19 | 20 | void OPLSD1ToneGenerator::setPitch(uint8_t voice, float note) 21 | { 22 | this->sd1ToneGenerator.setPitch(voice, note); 23 | } 24 | 25 | void OPLSD1ToneGenerator::setVolume(uint8_t voice, uint8_t volume) 26 | { 27 | this->voiceVolumes[voice] = volume; 28 | this->updateTone(voice); 29 | } 30 | 31 | void OPLSD1ToneGenerator::setPan(uint8_t voice, uint8_t pan) 32 | { 33 | this->sd1ToneGenerator.setPan(voice, pan); 34 | } 35 | 36 | void OPLSD1ToneGenerator::noteOn(uint8_t voice, float note) 37 | { 38 | this->sd1ToneGenerator.noteOn(voice, note); 39 | } 40 | 41 | void OPLSD1ToneGenerator::noteOff(uint8_t voice) 42 | { 43 | this->sd1ToneGenerator.noteOff(voice); 44 | } 45 | 46 | void OPLSD1ToneGenerator::updateTone(uint8_t voice) 47 | { 48 | OPLTone tone = this->voiceTones[voice]; 49 | tone.applyVolume(this->voiceVolumes[voice]); 50 | SD1Tone sd1Tone; 51 | SD1::convertOPLTone(tone, sd1Tone); 52 | this->sd1ToneGenerator.setTone(voice, sd1Tone, 127, 64); 53 | } 54 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/OPLSD1ToneGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../../SD1/SD1DeviceBase.h" 5 | #include "../IToneGenerator.h" 6 | #include "SD1ToneGenerator.h" 7 | 8 | #include 9 | 10 | class OPLSD1ToneGenerator : public IToneGenerator { 11 | public: 12 | OPLSD1ToneGenerator(SD1DeviceBase* device); 13 | virtual void reset(bool hardReset); 14 | virtual void setTone(uint8_t voice, OPLTone& tone, uint8_t volume, uint8_t pan); 15 | virtual void setPitch(uint8_t voice, float note); 16 | virtual void setVolume(uint8_t voice, uint8_t volume); 17 | virtual void setPan(uint8_t voice, uint8_t pan); 18 | virtual void noteOn(uint8_t voice, float note); 19 | virtual void noteOff(uint8_t voice); 20 | 21 | private: 22 | SD1ToneGenerator sd1ToneGenerator; 23 | OPLTone voiceTones[16]; 24 | uint8_t voiceVolumes[16]; 25 | void updateTone(uint8_t voice); 26 | }; 27 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1MIDISystem.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1MIDISystem.h" 2 | #include "../SysEx.h" 3 | #include "SD1ParamInfo.h" 4 | 5 | SD1MIDISystem::SD1MIDISystem(SD1DeviceBase* device, PatchManagerBase* patchManager) 6 | : voiceAllocator(), toneGenerator(device), timeSource(), 7 | midiDriver(&voiceAllocator, &toneGenerator, patchManager, &timeSource, SYSEX_DEV_ID_SD1, &SD1_PARAMS_INFO) 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1MIDISystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../SD1/SD1Tone.h" 4 | #include "../BasicVoiceAllocator.h" 5 | #include "../MIDIDriver.h" 6 | #include "../PatchManagerBase.h" 7 | #include "../TimeSource.h" 8 | #include "SD1ToneGenerator.h" 9 | 10 | class SD1MIDISystem { 11 | public: 12 | BasicVoiceAllocator voiceAllocator; 13 | SD1ToneGenerator toneGenerator; 14 | TimeSource timeSource; 15 | MIDIDriver midiDriver; 16 | SD1MIDISystem(SD1DeviceBase* device, PatchManagerBase* patchManager); 17 | }; 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1ParamInfo.g.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1ParamInfo.h" 2 | 3 | const ParamInfo SD1_PARAMS_INFO_ITEMS[] = { 4 | { 0x0000, 1 }, // Octave 5 | { 0x0001, 0 }, // LFO freq 6 | { 0x0002, 0 }, // Algorithm 7 | { 0x0020, 0 }, // Op 1 sustain 8 | { 0x0021, 0 }, // Op 1 XOF 9 | { 0x0022, 0 }, // Op 1 KSR 10 | { 0x0023, 15 }, // Op 1 release 11 | { 0x0024, 0 }, // Op 1 decay 12 | { 0x0025, 15 }, // Op 1 attack 13 | { 0x0026, 15 }, // Op 1 sus level 14 | { 0x0027, 63 }, // Op 1 level 15 | { 0x0028, 0 }, // Op 1 KSL 16 | { 0x0029, 0 }, // Op 1 AM depth 17 | { 0x002a, 0 }, // Op 1 AM enable 18 | { 0x002b, 0 }, // Op 1 vib depth 19 | { 0x002c, 0 }, // Op 1 vib enable 20 | { 0x002d, 1 }, // Op 1 mult 21 | { 0x002e, 0 }, // Op 1 detune 22 | { 0x002f, 0 }, // Op 1 wave 23 | { 0x0030, 0 }, // Op 1 feedback 24 | { 0x0040, 0 }, // Op 2 sustain 25 | { 0x0041, 0 }, // Op 2 XOF 26 | { 0x0042, 0 }, // Op 2 KSR 27 | { 0x0043, 15 }, // Op 2 release 28 | { 0x0044, 0 }, // Op 2 decay 29 | { 0x0045, 15 }, // Op 2 attack 30 | { 0x0046, 15 }, // Op 2 sus level 31 | { 0x0047, 0 }, // Op 2 level 32 | { 0x0048, 0 }, // Op 2 KSL 33 | { 0x0049, 0 }, // Op 2 AM depth 34 | { 0x004a, 0 }, // Op 2 AM enable 35 | { 0x004b, 0 }, // Op 2 vib depth 36 | { 0x004c, 0 }, // Op 2 vib enable 37 | { 0x004d, 1 }, // Op 2 mult 38 | { 0x004e, 0 }, // Op 2 detune 39 | { 0x004f, 0 }, // Op 2 wave 40 | { 0x0050, 0 }, // Op 2 feedback 41 | { 0x0060, 0 }, // Op 3 sustain 42 | { 0x0061, 0 }, // Op 3 XOF 43 | { 0x0062, 0 }, // Op 3 KSR 44 | { 0x0063, 15 }, // Op 3 release 45 | { 0x0064, 0 }, // Op 3 decay 46 | { 0x0065, 15 }, // Op 3 attack 47 | { 0x0066, 15 }, // Op 3 sus level 48 | { 0x0067, 63 }, // Op 3 level 49 | { 0x0068, 0 }, // Op 3 KSL 50 | { 0x0069, 0 }, // Op 3 AM depth 51 | { 0x006a, 0 }, // Op 3 AM enable 52 | { 0x006b, 0 }, // Op 3 vib depth 53 | { 0x006c, 0 }, // Op 3 vib enable 54 | { 0x006d, 1 }, // Op 3 mult 55 | { 0x006e, 0 }, // Op 3 detune 56 | { 0x006f, 0 }, // Op 3 wave 57 | { 0x0070, 0 }, // Op 3 feedback 58 | { 0x0080, 0 }, // Op 4 sustain 59 | { 0x0081, 0 }, // Op 4 XOF 60 | { 0x0082, 0 }, // Op 4 KSR 61 | { 0x0083, 15 }, // Op 4 release 62 | { 0x0084, 0 }, // Op 4 decay 63 | { 0x0085, 15 }, // Op 4 attack 64 | { 0x0086, 15 }, // Op 4 sus level 65 | { 0x0087, 63 }, // Op 4 level 66 | { 0x0088, 0 }, // Op 4 KSL 67 | { 0x0089, 0 }, // Op 4 AM depth 68 | { 0x008a, 0 }, // Op 4 AM enable 69 | { 0x008b, 0 }, // Op 4 vib depth 70 | { 0x008c, 0 }, // Op 4 vib enable 71 | { 0x008d, 1 }, // Op 4 mult 72 | { 0x008e, 0 }, // Op 4 detune 73 | { 0x008f, 0 }, // Op 4 wave 74 | { 0x0090, 0 }, // Op 4 feedback 75 | }; 76 | 77 | const ParamInfoList SD1_PARAMS_INFO = { 71, SD1_PARAMS_INFO_ITEMS }; 78 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1ParamInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ParamInfo.h" 4 | 5 | extern const ParamInfoList SD1_PARAMS_INFO; 6 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1Patches.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1Patches.h" 2 | #include "../../SD1/SD1Tone.h" 3 | #include "../../SD1/SD1Utils.h" 4 | #include "../OPL/OPLGMPatchData.h" 5 | 6 | #include 7 | #include 8 | 9 | void SD1::getGMPatch(uint8_t bank, uint8_t program, Patch& patch) 10 | { 11 | if (bank == 0 || bank == 1) { 12 | patch = Patch(); 13 | Patch oplPatch = OPL::getGMPatch(bank, program); 14 | SD1::convertOPLTone(oplPatch.params.tone, patch.params.tone); 15 | } else { 16 | patch = Patch(); 17 | } 18 | } 19 | 20 | bool SD1::getGMDrumPatch(uint8_t note, Patch& patch) 21 | { 22 | Patch oplPatch; 23 | if (OPL::getGMDrumPatch(note, oplPatch)) { 24 | patch = Patch(); 25 | SD1::convertOPLTone(oplPatch.params.tone, patch.params.tone); 26 | patch.params.fixedNoteNum = oplPatch.params.fixedNoteNum; 27 | return true; 28 | } else { 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1Patches.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../OPL/OPLTone.h" 4 | #include "../../SD1/SD1Tone.h" 5 | #include "../Patch.h" 6 | 7 | #include 8 | 9 | namespace SD1 { 10 | void getGMPatch(uint8_t bank, uint8_t program, Patch& patch); 11 | bool getGMDrumPatch(uint8_t note, Patch& patch); 12 | } 13 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SD1/SD1ToneGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../SD1/SD1DeviceBase.h" 4 | #include "../../SD1/SD1Tone.h" 5 | #include "../GlobalParams.h" 6 | #include "../IToneGenerator.h" 7 | 8 | class SD1ToneGenerator : public IToneGenerator { 9 | public: 10 | SD1ToneGenerator(SD1DeviceBase* device); 11 | virtual void reset(bool hardReset); 12 | virtual void setTone(uint8_t voice, SD1Tone& tone, uint8_t volume, uint8_t pan); 13 | virtual void setPitch(uint8_t voice, float note); 14 | virtual void setVolume(uint8_t voice, uint8_t volume); 15 | virtual void setPan(uint8_t voice, uint8_t pan); 16 | virtual void noteOn(uint8_t voice, float note); 17 | virtual void noteOff(uint8_t voice); 18 | void setAnalogueGain(uint8_t gain); 19 | void setMasterVolume(uint8_t vol); 20 | void setMasterChannelVolume(uint8_t vol); 21 | void setGlobalParam(uint16_t param, uint16_t value); 22 | 23 | private: 24 | SD1DeviceBase* device; 25 | SD1Tone voiceTones[16]; 26 | uint8_t voiceVolumes[16]; 27 | float voiceNotes[16]; 28 | void writeTones(uint8_t lastVoice); 29 | void writePitch(uint8_t voice, float note); 30 | }; 31 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/SysEx.cpp: -------------------------------------------------------------------------------- 1 | #include "SysEx.h" 2 | 3 | #include 4 | 5 | const uint8_t SysEx::MFR_ID[MFR_ID_LEN] = { 0x00, 0x22, 0x53 }; 6 | 7 | SysEx::Header::Header() 8 | { 9 | // SysEx start 10 | this->sysExMessage = 0xf0; 11 | this->cmd = 0; 12 | // Manufacturer ID 13 | memcpy(&this->manufacturerID[0], &SysEx::MFR_ID[0], MFR_ID_LEN); 14 | } 15 | 16 | bool SysEx::Header::checkManufacturerID() 17 | { 18 | return memcmp(&this->manufacturerID[0], &SysEx::MFR_ID[0], MFR_ID_LEN); 19 | } 20 | 21 | SysEx::IdentifyMsg::IdentifyMsg() : SysExMsg() 22 | { 23 | header.cmd = SYSEX_CMD_IDENTIFY; 24 | } 25 | 26 | SysEx::IdentifyReplyMsg::IdentifyReplyMsg(uint8_t deviceID) : SysExMsg() 27 | { 28 | header.cmd = SYSEX_CMD_IDENTIFY_REPLY; 29 | content.deviceID = deviceID; 30 | } 31 | 32 | SysEx::ReadParamMsg::ReadParamMsg() 33 | { 34 | header.cmd = SYSEX_CMD_READPARAM; 35 | } 36 | 37 | uint16_t SysEx::ReadParamMsg::getParam() 38 | { 39 | return ((content.paramMSB & 0x7f) << 7) | (content.paramLSB & 0x7f); 40 | } 41 | 42 | SysEx::ReadParamReplyMsg::ReadParamReplyMsg(uint8_t channel, uint16_t param, uint16_t value) : SysExMsg() 43 | { 44 | header.cmd = SYSEX_CMD_READPARAM_REPLY; 45 | 46 | content.channel = channel; 47 | content.paramLSB = param & 0x7f; 48 | content.paramMSB = (param >> 7) & 0x7f; 49 | content.valueLSB = value & 0x7f; 50 | content.valueMSB = (value >> 7) & 0x7f; 51 | } 52 | 53 | SysEx::WriteGlobalParamMsg::WriteGlobalParamMsg() : SysExMsg() 54 | { 55 | header.cmd = SYSEX_CMD_WRITEGLOBALPARAM; 56 | } 57 | 58 | uint16_t SysEx::WriteGlobalParamMsg::getParam() 59 | { 60 | return ((content.paramMSB & 0x7f) << 7) | (content.paramLSB & 0x7f); 61 | } 62 | 63 | uint16_t SysEx::WriteGlobalParamMsg::getValue() 64 | { 65 | return ((content.valueMSB & 0x7f) << 7) | (content.valueLSB & 0x7f); 66 | } 67 | 68 | SysEx::StorePatchMsg::StorePatchMsg() : SysExMsg() 69 | { 70 | header.cmd = SYSEX_CMD_STOREPATCH; 71 | } 72 | 73 | SysEx::PatchAddress SysEx::PatchAddress::forMelodicPatch(uint8_t bank, uint8_t program) 74 | { 75 | PatchAddress addr; 76 | addr.type = SYSEX_PATCHADDR_TYPE_MELODICPROG; 77 | addr.melodicPatch.bank = bank; 78 | addr.melodicPatch.program = program; 79 | return addr; 80 | } 81 | 82 | SysEx::PatchAddress SysEx::PatchAddress::forDrumPatch(uint8_t bank, uint8_t program, uint8_t noteNum) 83 | { 84 | PatchAddress addr; 85 | addr.type = SYSEX_PATCHADDR_TYPE_DRUMPROG; 86 | addr.drumPatch.bank = bank; 87 | addr.drumPatch.program = program; 88 | addr.drumPatch.noteNum = noteNum; 89 | return addr; 90 | } 91 | 92 | SysEx::PatchAddress SysEx::PatchAddress::forChannel(uint8_t channel) 93 | { 94 | PatchAddress addr; 95 | addr.type = SYSEX_PATCHADDR_TYPE_CHANNEL; 96 | addr.channel = channel; 97 | return addr; 98 | } 99 | 100 | SysEx::ReadPatchMsg::ReadPatchMsg(PatchAddress srcAddress) 101 | { 102 | header.cmd = SYSEX_CMD_READPATCH; 103 | content.srcAddress = srcAddress; 104 | } 105 | 106 | SysEx::WritePatchReplyMsg::WritePatchReplyMsg(uint8_t resultCode) 107 | { 108 | header.cmd = SYSEX_CMD_WRITEPATCH_REPLY; 109 | content.resultCode = resultCode; 110 | } 111 | 112 | SysEx::SetDrumModeMsg::SetDrumModeMsg(uint8_t channel, bool drumMode) 113 | { 114 | header.cmd = SYSEX_CMD_SETDRUMMODE; 115 | content.drumModeAndChannel = (channel & 0x0f) | (drumMode ? 0x40 : 0); 116 | } 117 | 118 | uint8_t SysEx::SetDrumModeMsg::getChannel() 119 | { 120 | return content.drumModeAndChannel & 0x0f; 121 | } 122 | 123 | bool SysEx::SetDrumModeMsg::getDrumMode() 124 | { 125 | return (content.drumModeAndChannel & 0x40) != 0; 126 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/TimeSource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MILLISECONDS_MAX UINT32_MAX 6 | 7 | typedef uint32_t Milliseconds; 8 | 9 | class TimeSource { 10 | public: 11 | Milliseconds timeMS; 12 | }; 13 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/VoiceStatus.cpp: -------------------------------------------------------------------------------- 1 | #include "VoiceStatus.h" 2 | 3 | VoiceStatus::VoiceStatus() : VoiceStatus(255, 255) 4 | { 5 | } 6 | 7 | VoiceStatus::VoiceStatus(uint8_t channel, uint8_t noteNum) : channel(channel), noteNum(noteNum) 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /libnotesaladcore/src/MIDI/VoiceStatus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class VoiceStatus { 6 | public: 7 | uint8_t channel; 8 | uint8_t noteNum; 9 | VoiceStatus(); 10 | VoiceStatus(uint8_t channel, uint8_t noteNum); 11 | }; 12 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/BufferedOPLDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "BufferedOPLDevice.h" 2 | 3 | BufferedOPLDevice::BufferedOPLDevice(OPLDeviceBase* baseDevice) : baseDevice(baseDevice) 4 | { 5 | } 6 | 7 | void BufferedOPLDevice::hardReset() 8 | { 9 | this->baseDevice->hardReset(); 10 | this->registers.hardReset(); 11 | } 12 | 13 | void BufferedOPLDevice::softReset() 14 | { 15 | this->baseDevice->softReset(); 16 | this->registers.hardReset(); 17 | } 18 | 19 | void BufferedOPLDevice::write(uint16_t addr, uint8_t data) 20 | { 21 | if (this->registers.read(addr) != data) { 22 | this->registers.write(addr, data); 23 | this->baseDevice->write(addr, data); 24 | } 25 | } 26 | 27 | uint8_t BufferedOPLDevice::read(uint16_t addr) 28 | { 29 | return this->registers.read(addr); 30 | } 31 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/BufferedOPLDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OPLDeviceBase.h" 4 | #include "OPLRegisterSet.h" 5 | 6 | class BufferedOPLDevice : public OPLReadWriteDeviceBase { 7 | public: 8 | BufferedOPLDevice(OPLDeviceBase* baseDevice); 9 | virtual void hardReset(); 10 | virtual void softReset(); 11 | virtual void write(uint16_t addr, uint8_t data); 12 | virtual uint8_t read(uint16_t addr); 13 | 14 | private: 15 | OPLDeviceBase* baseDevice; 16 | OPLRegisterSet registers; 17 | }; 18 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | BufferedOPLDevice.cpp 4 | BufferedOPLDevice.h 5 | OPLDeviceBase.cpp 6 | OPLDeviceBase.h 7 | OPLReadWriteDeviceBase.cpp 8 | OPLReadWriteDeviceBase.h 9 | OPLRegisterSet.cpp 10 | OPLRegisterSet.h 11 | OPLTone.cpp 12 | OPLTone.h 13 | OPLUtils.cpp 14 | OPLUtils.h 15 | ) -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/OPLDeviceBase.cpp: -------------------------------------------------------------------------------- 1 | #include "OPLDeviceBase.h" 2 | #include "OPLUtils.h" 3 | 4 | OPLDeviceBase::~OPLDeviceBase() 5 | { 6 | } 7 | 8 | void OPLDeviceBase::softReset() 9 | { 10 | // Set decay rate to maximum to reset envelope generators 11 | for (uint8_t i = 0; i <= 0x15; i++) { 12 | this->write(0x60 + i, 0x0f); 13 | this->write(0x160 + i, 0x0f); 14 | } 15 | 16 | // Disable all channels to immediately stop all sound 17 | for (uint8_t ch = 0; ch <= 0x08; ch++) { 18 | this->write(0xc0 + ch, 0); 19 | this->write(0x1c0 + ch, 0); 20 | } 21 | 22 | // Zero other registers 23 | this->write(0x104, 0); 24 | this->write(0x08, 0); 25 | 26 | for (uint8_t i = 0; i <= 0x15; i++) { 27 | this->write(0x20 + i, 0); 28 | this->write(0x40 + i, 0); 29 | this->write(0x60 + i, 0); 30 | this->write(0x80 + i, 0); 31 | this->write(0xe0 + i, 0); 32 | this->write(0x120 + i, 0); 33 | this->write(0x140 + i, 0); 34 | this->write(0x160 + i, 0); 35 | this->write(0x180 + i, 0); 36 | this->write(0x1e0 + i, 0); 37 | } 38 | 39 | for (uint8_t i = 0; i <= 0x08; i++) { 40 | this->write(0xa0 + i, 0); 41 | this->write(0xb0 + i, 0); 42 | this->write(0xc0 + i, 0); 43 | this->write(0x1a0 + i, 0); 44 | this->write(0x1b0 + i, 0); 45 | this->write(0x1c0 + i, 0); 46 | } 47 | 48 | this->write(0xbd, 0); 49 | } 50 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/OPLDeviceBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class OPLDeviceBase { 6 | public: 7 | virtual ~OPLDeviceBase(); 8 | virtual void hardReset() = 0; 9 | virtual void write(uint16_t addr, uint8_t data) = 0; 10 | virtual void softReset(); 11 | }; 12 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/OPLReadWriteDeviceBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OPLDeviceBase.h" 4 | #include "OPLTone.h" 5 | 6 | class OPLReadWriteDeviceBase : public OPLDeviceBase { 7 | public: 8 | virtual uint8_t read(uint16_t addr) = 0; 9 | 10 | bool is4OpChannelEnabled(uint8_t ch4Op); 11 | void set4OpChannelEnabled(uint8_t ch4Op, bool enabled); 12 | void setChannelKeyOn(uint8_t channel, bool keyOn); 13 | 14 | void allNotesOff(); 15 | void setMinimumReleaseRate(uint8_t rr); 16 | 17 | void pauseEnvelope(uint8_t voice); 18 | void pauseEnvelope(); 19 | 20 | void readTone(uint8_t voice, OPLTone& tone); 21 | void writeTone(uint8_t voice, OPLTone& tone); 22 | bool isVoiceEnabled(uint8_t voice); 23 | void enableVoice(uint8_t voice); 24 | void setVoiceKeyOn(uint8_t voice, bool keyOn); 25 | 26 | void copyTo(OPLDeviceBase& dest); 27 | }; 28 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/OPLRegisterSet.cpp: -------------------------------------------------------------------------------- 1 | #include "OPLRegisterSet.h" 2 | 3 | #include 4 | 5 | static int getArrayIndexForRegister(uint16_t addr) 6 | { 7 | uint8_t reg = addr & 0xff; 8 | uint8_t bank = (addr & 0x100) >> 8; 9 | int index = -1; 10 | 11 | if (reg >= 0x01 && reg <= 0x05) { 12 | index = reg - 1; 13 | } else if (reg == 0x08) { 14 | index = reg - 3; 15 | } else if (reg >= 0x20 && reg <= 0x35) { 16 | index = reg - 26; 17 | } else if (reg >= 0x40 && reg <= 0x55) { 18 | index = reg - 36; 19 | } else if (reg >= 0x60 && reg <= 0x75) { 20 | index = reg - 46; 21 | } else if (reg >= 0x80 && reg <= 0x95) { 22 | index = reg - 56; 23 | } else if (reg >= 0xa0 && reg <= 0xa8) { 24 | index = reg - 66; 25 | } else if (reg >= 0xb0 && reg <= 0xb8) { 26 | index = reg - 73; 27 | } else if (reg == 0xbd) { 28 | index = reg - 77; 29 | } else if (reg >= 0xc0 && reg <= 0xc8) { 30 | index = reg - 79; 31 | } else if (reg >= 0xe0 && reg <= 0xf5) { 32 | index = reg - 102; 33 | } 34 | 35 | if (index >= 0) { 36 | index += bank * 144; 37 | } 38 | 39 | return index; 40 | } 41 | 42 | OPLRegisterSet::OPLRegisterSet() 43 | { 44 | this->hardReset(); 45 | } 46 | 47 | void OPLRegisterSet::hardReset() 48 | { 49 | memset(&this->registers[0], 0, 288); 50 | } 51 | 52 | void OPLRegisterSet::write(uint16_t addr, uint8_t data) 53 | { 54 | int index = getArrayIndexForRegister(addr); 55 | if (index >= 0) { 56 | this->registers[index] = data; 57 | } 58 | } 59 | 60 | uint8_t OPLRegisterSet::read(uint16_t addr) 61 | { 62 | int index = getArrayIndexForRegister(addr); 63 | if (index < 0) { 64 | return 0; 65 | } else { 66 | return this->registers[index]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/OPLRegisterSet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "OPLReadWriteDeviceBase.h" 6 | 7 | class OPLRegisterSet : public OPLReadWriteDeviceBase { 8 | public: 9 | OPLRegisterSet(); 10 | virtual void hardReset(); 11 | virtual void write(uint16_t addr, uint8_t data); 12 | virtual uint8_t read(uint16_t addr); 13 | 14 | private: 15 | uint8_t registers[288]; 16 | }; 17 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPL/OPLUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OPLRegisterSet.h" 4 | #include "OPLTone.h" 5 | 6 | #include 7 | 8 | #define OPL_NUM_VOICES 27 9 | 10 | namespace OPL { 11 | void get2OpChannelsFor4OpChannel(uint8_t ch4op, uint8_t* ch2op); 12 | void get2OpRegOffsets(uint8_t ch2op, uint16_t* regOffsets); 13 | uint16_t get2OpChannelRegOffset(uint8_t ch2op); 14 | uint8_t get4OpChannelFor2OpChannel(uint8_t ch2op); 15 | uint8_t get2OpChannelForOperatorRegOffset(uint16_t offset); 16 | 17 | void getBlockFNum(float note, uint8_t& block, uint16_t& fnum); 18 | uint8_t getTLForMIDIVolume(uint8_t midiVolume); 19 | 20 | void getOperatorRegOffsetsForVoice(uint8_t voice, uint16_t* regOffsets, uint8_t& numOffsets); 21 | void getChannelRegOffsetsForVoice(uint8_t voice, uint16_t* regOffsets, uint8_t& numOffsets); 22 | uint8_t getVoiceForOperatorRegOffset(uint16_t offset, uint8_t connection); 23 | uint8_t getVoiceForChannelRegOffset(uint16_t offset, uint8_t connection); 24 | uint8_t getVoiceForRegister(uint16_t reg, uint8_t connection); 25 | 26 | bool isVoice4Op(uint8_t voice); 27 | void getVoice2OpChannels(uint8_t voice, uint8_t* channels, uint8_t& numChannels); 28 | uint8_t getVoice2OpChannel(uint8_t voice); 29 | uint8_t getVoice4OpChannel(uint8_t voice); 30 | } 31 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/BufferedOPMDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "BufferedOPMDevice.h" 2 | 3 | BufferedOPMDevice::BufferedOPMDevice(OPMDeviceBase* baseDevice) : baseDevice(baseDevice) 4 | { 5 | } 6 | 7 | void BufferedOPMDevice::hardReset() 8 | { 9 | this->baseDevice->hardReset(); 10 | this->registers.reset(); 11 | } 12 | 13 | void BufferedOPMDevice::write(uint8_t addr, uint8_t data) 14 | { 15 | if (this->registers.get(addr) != data) { 16 | this->registers.set(addr, data); 17 | this->baseDevice->write(addr, data); 18 | } 19 | } 20 | 21 | uint8_t BufferedOPMDevice::read(uint8_t addr) 22 | { 23 | return this->registers.get(addr); 24 | } 25 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/BufferedOPMDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OPMDeviceBase.h" 4 | #include "OPMRegisterSet.h" 5 | 6 | class BufferedOPMDevice : public OPMDeviceBase { 7 | public: 8 | BufferedOPMDevice(OPMDeviceBase* baseDevice); 9 | virtual void hardReset(); 10 | virtual void write(uint8_t addr, uint8_t data); 11 | uint8_t read(uint8_t addr); 12 | 13 | private: 14 | OPMDeviceBase* baseDevice; 15 | OPMRegisterSet registers; 16 | }; 17 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | BufferedOPMDevice.cpp 4 | BufferedOPMDevice.h 5 | OPMDeviceBase.cpp 6 | OPMDeviceBase.h 7 | OPMRegisterSet.cpp 8 | OPMRegisterSet.h 9 | OPMTone.cpp 10 | OPMTone.h 11 | OPMUtils.cpp 12 | OPMUtils.h 13 | ) 14 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/OPMDeviceBase.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMDeviceBase.h" 2 | 3 | OPMDeviceBase::~OPMDeviceBase() 4 | { 5 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/OPMDeviceBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class OPMDeviceBase { 6 | public: 7 | virtual ~OPMDeviceBase(); 8 | virtual void hardReset() = 0; 9 | virtual void write(uint8_t addr, uint8_t data) = 0; 10 | }; 11 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/OPMRegisterSet.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMRegisterSet.h" 2 | 3 | #include 4 | 5 | static int getArrayIndexForRegister(uint8_t addr) 6 | { 7 | if (addr >= 0x20) { 8 | return addr - 0x20; 9 | } else { 10 | switch (addr) { 11 | case 0x01: 12 | return 224; 13 | case 0x08: 14 | return 225; 15 | case 0x0f: 16 | return 226; 17 | case 0x10: 18 | return 227; 19 | case 0x11: 20 | return 228; 21 | case 0x12: 22 | return 229; 23 | case 0x14: 24 | return 230; 25 | case 0x18: 26 | return 231; 27 | case 0x19: 28 | return 232; 29 | case 0x1b: 30 | return 233; 31 | } 32 | } 33 | 34 | return -1; 35 | } 36 | 37 | OPMRegisterSet::OPMRegisterSet() 38 | { 39 | this->reset(); 40 | } 41 | 42 | void OPMRegisterSet::reset() 43 | { 44 | memset(&this->registers[0], 0, 234); 45 | } 46 | 47 | void OPMRegisterSet::set(uint8_t addr, uint8_t value) 48 | { 49 | int index = getArrayIndexForRegister(addr); 50 | if (index >= 0) { 51 | this->registers[index] = value; 52 | } 53 | } 54 | 55 | uint8_t OPMRegisterSet::get(uint8_t addr) 56 | { 57 | int index = getArrayIndexForRegister(addr); 58 | if (index < 0) { 59 | return 0; 60 | } else { 61 | return this->registers[index]; 62 | } 63 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/OPMRegisterSet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class OPMRegisterSet { 6 | public: 7 | OPMRegisterSet(); 8 | void set(uint8_t addr, uint8_t value); 9 | uint8_t get(uint8_t addr); 10 | void reset(); 11 | 12 | private: 13 | uint8_t registers[234]; 14 | }; 15 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/OPMUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "OPMUtils.h" 2 | 3 | uint8_t OPM::getKCForMIDINote(uint8_t note) 4 | { 5 | uint8_t oct = ((note - 1) / 12) - 1; 6 | uint8_t kc = (note - 1) % 12; 7 | 8 | switch (kc) { 9 | case 3: 10 | kc = 4; 11 | break; 12 | case 4: 13 | kc = 5; 14 | break; 15 | case 5: 16 | kc = 6; 17 | break; 18 | case 6: 19 | kc = 8; 20 | break; 21 | case 7: 22 | kc = 9; 23 | break; 24 | case 8: 25 | kc = 10; 26 | break; 27 | case 9: 28 | kc = 12; 29 | break; 30 | case 10: 31 | kc = 13; 32 | break; 33 | case 11: 34 | kc = 14; 35 | break; 36 | } 37 | 38 | kc |= (oct << 4); 39 | 40 | return kc; 41 | } 42 | -------------------------------------------------------------------------------- /libnotesaladcore/src/OPM/OPMUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../MIDI/Patch.h" 6 | #include "OPMTone.h" 7 | 8 | namespace OPM { 9 | uint8_t getKCForMIDINote(uint8_t note); 10 | } 11 | -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(notesaladcore 2 | INTERFACE 3 | SD1DeviceBase.cpp 4 | SD1DeviceBase.h 5 | SD1OPLAdaptor.cpp 6 | SD1OPLAdaptor.h 7 | SD1Tone.cpp 8 | SD1Tone.h 9 | SD1Utils.cpp 10 | SD1Utils.h 11 | ) 12 | -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/SD1DeviceBase.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1DeviceBase.h" 2 | 3 | SD1DeviceBase::~SD1DeviceBase() 4 | { 5 | } 6 | 7 | void SD1DeviceBase::writeReg(uint8_t addr, uint8_t data) 8 | { 9 | uint8_t buffer[2] = { addr, data }; 10 | this->write(&buffer[0], 2); 11 | } 12 | -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/SD1DeviceBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class SD1DeviceBase { 6 | public: 7 | virtual ~SD1DeviceBase(); 8 | virtual void delayMicroseconds(uint32_t us) = 0; 9 | virtual void write(const uint8_t* data, const uint16_t len) = 0; 10 | void writeReg(uint8_t addr, uint8_t data); 11 | virtual void hardReset() = 0; 12 | }; 13 | -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/SD1OPLAdaptor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../OPL/OPLDeviceBase.h" 4 | #include "../OPL/OPLRegisterSet.h" 5 | #include "SD1DeviceBase.h" 6 | #include "SD1Tone.h" 7 | 8 | class SD1OPLAdaptor : public OPLDeviceBase { 9 | public: 10 | SD1OPLAdaptor(SD1DeviceBase* device); 11 | virtual void hardReset(); 12 | virtual void write(uint16_t addr, uint8_t data); 13 | void update(); 14 | 15 | private: 16 | SD1DeviceBase* device; 17 | OPLRegisterSet oplReg; 18 | SD1Tone tones[16]; 19 | bool changes[24]; 20 | int8_t sd1Voices[24]; 21 | int8_t oplVoices[16]; 22 | uint8_t nextSD1Voice; 23 | 24 | void initState(); 25 | void sd1WriteTones(int8_t maxTone); 26 | void oplGetBlockFNum(uint8_t oplVoice, uint8_t& block, uint16_t& fnum); 27 | void sd1SelectVoice(uint8_t voice); 28 | void sd1SetPitch(uint8_t block, uint16_t fnum); 29 | void handleOPLFNUMLChange(uint16_t addr, uint8_t oldData); 30 | void handleOPLKONBlockFNUMHChange(uint16_t addr, uint8_t oldData); 31 | void sd1SetKeyOn(bool on, uint8_t tone, bool egRst); 32 | bool allocateSD1Voice(uint8_t oplVoice); 33 | }; 34 | -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/SD1Tone.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1Tone.h" 2 | 3 | #include 4 | 5 | SD1ToneData::SD1ToneData() 6 | { 7 | this->bo = 1; 8 | this->lfo_alg = 0; 9 | for (uint8_t op = 0; op < 4; op++) { 10 | this->op[op] = SD1OperatorData(); 11 | } 12 | this->op[1].tl_ksl = 0; 13 | } 14 | 15 | uint16_t SD1Tone::getParam(uint16_t paramID) 16 | { 17 | uint8_t opID = paramID / 32; 18 | uint8_t param = paramID % 32; 19 | 20 | if (opID == 0) { 21 | switch (param) { 22 | case SD1_TONE_PARAM_BO: 23 | return getBO(); 24 | case SD1_TONE_PARAM_LFO: 25 | return getLFO(); 26 | case SD1_TONE_PARAM_ALG: 27 | return getALG(); 28 | } 29 | } else { 30 | uint8_t op = opID - 1; 31 | 32 | switch (param) { 33 | case SD1_TONE_PARAM_OP_SR: 34 | return getSR(op); 35 | case SD1_TONE_PARAM_OP_XOF: 36 | return getXOF(op); 37 | case SD1_TONE_PARAM_OP_KSR: 38 | return getKSR(op); 39 | case SD1_TONE_PARAM_OP_RR: 40 | return getRR(op); 41 | case SD1_TONE_PARAM_OP_DR: 42 | return getDR(op); 43 | case SD1_TONE_PARAM_OP_AR: 44 | return getAR(op); 45 | case SD1_TONE_PARAM_OP_SL: 46 | return getSL(op); 47 | case SD1_TONE_PARAM_OP_TL: 48 | return getTL(op); 49 | case SD1_TONE_PARAM_OP_KSL: 50 | return getKSL(op); 51 | case SD1_TONE_PARAM_OP_DAM: 52 | return getDAM(op); 53 | case SD1_TONE_PARAM_OP_EAM: 54 | return getEAM(op); 55 | case SD1_TONE_PARAM_OP_DVB: 56 | return getDVB(op); 57 | case SD1_TONE_PARAM_OP_EVB: 58 | return getEVB(op); 59 | case SD1_TONE_PARAM_OP_MULTI: 60 | return getMULTI(op); 61 | case SD1_TONE_PARAM_OP_DT: 62 | return getDT(op); 63 | case SD1_TONE_PARAM_OP_WS: 64 | return getWS(op); 65 | case SD1_TONE_PARAM_OP_FB: 66 | return getFB(op); 67 | } 68 | } 69 | 70 | return 0; 71 | } 72 | 73 | void SD1Tone::setParam(uint16_t paramID, uint16_t value) 74 | { 75 | uint8_t opID = paramID / 32; 76 | uint8_t param = paramID % 32; 77 | 78 | if (opID == 0) { 79 | switch (param) { 80 | case SD1_TONE_PARAM_BO: 81 | return setBO(clamp((uint8_t)value, 0, 3)); 82 | case SD1_TONE_PARAM_LFO: 83 | return setLFO(clamp((uint8_t)value, 0, 3)); 84 | case SD1_TONE_PARAM_ALG: 85 | return setALG(clamp((uint8_t)value, 0, 7)); 86 | } 87 | } else { 88 | uint8_t op = opID - 1; 89 | 90 | switch (param) { 91 | case SD1_TONE_PARAM_OP_SR: 92 | return setSR(op, clamp((uint8_t)value, 0, 15)); 93 | case SD1_TONE_PARAM_OP_XOF: 94 | return setXOF(op, value != 0); 95 | case SD1_TONE_PARAM_OP_KSR: 96 | return setKSR(op, value != 0); 97 | case SD1_TONE_PARAM_OP_RR: 98 | return setRR(op, clamp((uint8_t)value, 0, 15)); 99 | case SD1_TONE_PARAM_OP_DR: 100 | return setDR(op, clamp((uint8_t)value, 0, 15)); 101 | case SD1_TONE_PARAM_OP_AR: 102 | return setAR(op, clamp((uint8_t)value, 0, 15)); 103 | case SD1_TONE_PARAM_OP_SL: 104 | return setSL(op, clamp((uint8_t)value, 0, 15)); 105 | case SD1_TONE_PARAM_OP_TL: 106 | return setTL(op, clamp((uint8_t)value, 0, 63)); 107 | case SD1_TONE_PARAM_OP_KSL: 108 | return setKSL(op, clamp((uint8_t)value, 0, 3)); 109 | case SD1_TONE_PARAM_OP_DAM: 110 | return setDAM(op, clamp((uint8_t)value, 0, 3)); 111 | case SD1_TONE_PARAM_OP_EAM: 112 | return setEAM(op, value != 0); 113 | case SD1_TONE_PARAM_OP_DVB: 114 | return setDVB(op, clamp((uint8_t)value, 0, 3)); 115 | case SD1_TONE_PARAM_OP_EVB: 116 | return setEVB(op, value != 0); 117 | case SD1_TONE_PARAM_OP_MULTI: 118 | return setMULTI(op, clamp((uint8_t)value, 0, 15)); 119 | case SD1_TONE_PARAM_OP_DT: 120 | return setDT(op, clamp((uint8_t)value, 0, 7)); 121 | case SD1_TONE_PARAM_OP_WS: 122 | return setWS(op, clamp((uint8_t)value, 0, 31)); 123 | case SD1_TONE_PARAM_OP_FB: 124 | return setFB(op, clamp((uint8_t)value, 0, 7)); 125 | } 126 | } 127 | } 128 | 129 | bool SD1Tone::operator==(const SD1Tone& b) const 130 | { 131 | return memcmp(&this->toneData, &b.toneData, sizeof(this->toneData)) == 0; 132 | } 133 | 134 | bool SD1Tone::operator!=(const SD1Tone& b) const 135 | { 136 | return memcmp(&this->toneData, &b.toneData, sizeof(this->toneData)) != 0; 137 | } -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/SD1Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "SD1Utils.h" 2 | 3 | void SD1::convertOPLTone(OPLTone& oplTone, SD1Tone& sd1Tone) 4 | { 5 | uint8_t numOps = oplTone.is4Op() ? 4 : 2; 6 | 7 | for (uint8_t op = 0; op < numOps; op++) { 8 | sd1Tone.setBO(1); 9 | sd1Tone.setEAM(op, oplTone.getAM(op)); 10 | sd1Tone.setEVB(op, oplTone.getVIB(op)); 11 | if (oplTone.getEGT(op)) { 12 | sd1Tone.setSR(op, 0); 13 | } else { 14 | sd1Tone.setSR(op, oplTone.getRR(op)); 15 | } 16 | sd1Tone.setKSR(op, oplTone.getKSR(op)); 17 | sd1Tone.setMULTI(op, oplTone.getMULT(op)); 18 | sd1Tone.setKSL(op, oplTone.getKSL(op)); 19 | sd1Tone.setTL(op, oplTone.getTL(op)); 20 | sd1Tone.setAR(op, oplTone.getAR(op)); 21 | sd1Tone.setDR(op, oplTone.getDR(op)); 22 | sd1Tone.setSL(op, oplTone.getSL(op)); 23 | sd1Tone.setRR(op, oplTone.getRR(op)); 24 | sd1Tone.setWS(op, oplTone.getWS(op)); 25 | sd1Tone.setFB(op, op == 0 ? oplTone.getFB() : 0); 26 | } 27 | 28 | if (oplTone.is4Op()) { 29 | sd1Tone.setALG(oplTone.getCNT() + 4); 30 | } else { 31 | sd1Tone.setALG(oplTone.getCNT()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libnotesaladcore/src/SD1/SD1Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../OPL/OPLTone.h" 4 | #include "SD1Tone.h" 5 | 6 | namespace SD1 { 7 | void convertOPLTone(OPLTone& oplTone, SD1Tone& sd1Tone); 8 | } 9 | -------------------------------------------------------------------------------- /libnotesaladcore/src/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | #include "BufferIO.h" 3 | 4 | #include 5 | 6 | const uint8_t midiVolumeCurve[] = { 7 | 0, 11, 16, 19, 22, 25, 27, 29, 32, 33, 35, 37, 39, 40, 42, 43, 8 | 45, 46, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 9 | 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 75, 76, 77, 10 | 78, 79, 80, 80, 81, 82, 83, 83, 84, 85, 86, 86, 87, 88, 89, 89, 11 | 90, 91, 91, 92, 93, 93, 94, 95, 96, 96, 97, 97, 98, 99, 99, 100, 12 | 101, 101, 102, 103, 103, 104, 104, 105, 106, 106, 107, 107, 108, 13 | 109, 109, 110, 110, 111, 112, 112, 113, 113, 114, 114, 115, 115, 14 | 116, 117, 117, 118, 118, 119, 119, 120, 120, 121, 121, 122, 122, 15 | 123, 123, 124, 124, 125, 125, 126, 126, 127 16 | }; 17 | 18 | uint8_t lookupMIDIVolume(uint8_t volume) 19 | { 20 | volume = std::min(volume, (uint8_t)127); 21 | return midiVolumeCurve[volume]; 22 | } 23 | -------------------------------------------------------------------------------- /libnotesaladcore/src/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BufferIO.h" 4 | 5 | #include 6 | #include 7 | 8 | #define PACKED __attribute__((__packed__)) 9 | 10 | template 11 | inline bool getFlag(uint8_t data) 12 | { 13 | return (data & flag) != 0; 14 | } 15 | 16 | template 17 | inline void setFlags(uint8_t& data, bool value) 18 | { 19 | data = (data & ~flags) | (value ? flags : 0); 20 | } 21 | 22 | template 23 | inline bool getBit(uint8_t data) 24 | { 25 | return (data & (1 << bit)) != 0; 26 | } 27 | 28 | template 29 | inline uint8_t getBits(uint8_t data) 30 | { 31 | return (data >> start) & ((1 << count) - 1); 32 | } 33 | 34 | template 35 | inline void setBit(uint8_t& data, bool value) 36 | { 37 | setFlags<1 << bit>(data, value); 38 | } 39 | 40 | template 41 | inline void setBits(uint8_t& data, uint8_t value) 42 | { 43 | uint8_t mask = ((1 << count) - 1) << start; 44 | data &= ~mask; 45 | data |= (value << start) & mask; 46 | } 47 | 48 | template 49 | inline T clamp(T value, T min, T max) 50 | { 51 | if (value < min) 52 | return min; 53 | else if (value > max) 54 | return max; 55 | else 56 | return value; 57 | } 58 | 59 | uint8_t lookupMIDIVolume(uint8_t volume); 60 | -------------------------------------------------------------------------------- /libnotesaladcore/src/notesaladcore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "7BitEncoding.h" 4 | #include "BufferIO.h" 5 | #include "Utils.h" 6 | 7 | #include "OPL/BufferedOPLDevice.h" 8 | #include "OPL/OPLDeviceBase.h" 9 | #include "OPL/OPLReadWriteDeviceBase.h" 10 | #include "OPL/OPLRegisterSet.h" 11 | #include "OPL/OPLTone.h" 12 | #include "OPL/OPLUtils.h" 13 | 14 | #include "OPM/BufferedOPMDevice.h" 15 | #include "OPM/OPMDeviceBase.h" 16 | #include "OPM/OPMRegisterSet.h" 17 | #include "OPM/OPMTone.h" 18 | #include "OPM/OPMUtils.h" 19 | 20 | #include "SD1/SD1DeviceBase.h" 21 | #include "SD1/SD1OPLAdaptor.h" 22 | #include "SD1/SD1Tone.h" 23 | #include "SD1/SD1Utils.h" 24 | 25 | #include "MIDI/BasicVoiceAllocator.h" 26 | #include "MIDI/GlobalParams.h" 27 | #include "MIDI/IMIDIDriver.h" 28 | #include "MIDI/INoteManager.h" 29 | #include "MIDI/IToneGenerator.h" 30 | #include "MIDI/IVoiceAllocator.h" 31 | #include "MIDI/LFO.h" 32 | #include "MIDI/MIDICommon.h" 33 | #include "MIDI/MIDIDriver.h" 34 | #include "MIDI/MIDIParser.h" 35 | #include "MIDI/MonoNoteManager.h" 36 | #include "MIDI/ParamInfo.h" 37 | #include "MIDI/Patch.h" 38 | #include "MIDI/PatchManagerBase.h" 39 | #include "MIDI/PatchParams.h" 40 | #include "MIDI/PatchSerialization.h" 41 | #include "MIDI/PolyNoteManager.h" 42 | #include "MIDI/RAMPatchManager.h" 43 | #include "MIDI/SysEx.h" 44 | #include "MIDI/TimeSource.h" 45 | #include "MIDI/ToneController.h" 46 | #include "MIDI/VoiceStatus.h" 47 | 48 | #include "MIDI/OPL/DefaultOPLPatchManager.h" 49 | #include "MIDI/OPL/OPL2MIDISystem.h" 50 | #include "MIDI/OPL/OPL2VoiceAllocator.h" 51 | #include "MIDI/OPL/OPL3MIDISystem.h" 52 | #include "MIDI/OPL/OPL3VoiceAllocator.h" 53 | #include "MIDI/OPL/OPLGMPatchData.h" 54 | #include "MIDI/OPL/OPLParamInfo.h" 55 | #include "MIDI/OPL/OPLToneGenerator.h" 56 | 57 | #include "MIDI/OPM/OPMMIDISystem.h" 58 | #include "MIDI/OPM/OPMParamInfo.h" 59 | #include "MIDI/OPM/OPMToneGenerator.h" 60 | 61 | #include "MIDI/SD1/DefaultSD1PatchManager.h" 62 | #include "MIDI/SD1/OPLSD1MIDISystem.h" 63 | #include "MIDI/SD1/OPLSD1ToneGenerator.h" 64 | #include "MIDI/SD1/SD1MIDISystem.h" 65 | #include "MIDI/SD1/SD1ParamInfo.h" 66 | #include "MIDI/SD1/SD1Patches.h" 67 | #include "MIDI/SD1/SD1ToneGenerator.h" 68 | -------------------------------------------------------------------------------- /libnotesaladcore/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_policy(SET CMP0135 NEW) 2 | 3 | include(FetchContent) 4 | FetchContent_Declare( 5 | googletest 6 | URL https://github.com/google/googletest/archive/355d57d90d9744c41ac7c99f1e960778f1c63040.zip 7 | ) 8 | # For Windows: Prevent overriding the parent project's compiler/linker settings 9 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 10 | FetchContent_MakeAvailable(googletest) 11 | 12 | add_executable( 13 | libnotesaladcore_test 14 | MIDI/OPL/OPLParamInfo.cpp 15 | MIDI/OPM/OPMParamInfo.cpp 16 | MIDI/SD1/SD1ParamInfo.cpp 17 | MIDI/MIDIDriver.cpp 18 | MIDI/MIDIParser.cpp 19 | MIDI/PatchSerialization.cpp 20 | MIDI/TestTone.cpp 21 | OPL/OPLReadWriteDeviceBase.cpp 22 | SD1/SD1Utils.cpp 23 | 7BitEncoding.cpp 24 | ) 25 | set_target_properties(libnotesaladcore_test PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) 26 | target_link_libraries( 27 | libnotesaladcore_test 28 | PUBLIC 29 | gmock_main 30 | notesaladcore 31 | ) 32 | 33 | include(GoogleTest) 34 | gtest_discover_tests(libnotesaladcore_test) 35 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/IMIDIDriver_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class MockMIDIDriver : public IMIDIDriver { 8 | public: 9 | MOCK_METHOD(void, reset, (bool hardReset), (override)); 10 | MOCK_METHOD(void, noteOn, (uint8_t channel, uint8_t note, uint8_t velocity), (override)); 11 | MOCK_METHOD(void, noteOff, (uint8_t channel, uint8_t note, uint8_t velocity), (override)); 12 | MOCK_METHOD(void, programChange, (uint8_t channel, uint8_t program), (override)); 13 | MOCK_METHOD(void, controlChange, (uint8_t channel, uint8_t control, uint8_t value), (override)); 14 | MOCK_METHOD(void, sysEx, (uint8_t * data, unsigned int length), (override)); 15 | MOCK_METHOD(void, pitchWheel, (uint8_t channel, uint16_t value), (override)); 16 | MOCK_METHOD(void, setChannelDrumMode, (uint8_t channel, bool enabled), (override)); 17 | MOCK_METHOD(void, update, (), (override)); 18 | }; 19 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/IToneGenerator_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "TestTone.h" 8 | 9 | class MockToneGenerator : public IToneGenerator { 10 | public: 11 | MOCK_METHOD(void, reset, (bool hardReset), (override)); 12 | MOCK_METHOD(void, setTone, (uint8_t voice, TestTone& tone, uint8_t volume, uint8_t pan), (override)); 13 | MOCK_METHOD(void, setPitch, (uint8_t voice, float note), (override)); 14 | MOCK_METHOD(void, setVolume, (uint8_t voice, uint8_t volume), (override)); 15 | MOCK_METHOD(void, setPan, (uint8_t voice, uint8_t pan), (override)); 16 | MOCK_METHOD(void, noteOn, (uint8_t voice, float note), (override)); 17 | MOCK_METHOD(void, noteOff, (uint8_t voice), (override)); 18 | MOCK_METHOD(bool, isNoteActive, (uint8_t voice), (override)); 19 | MOCK_METHOD(void, setGlobalParam, (uint16_t paramID, uint16_t value), (override)); 20 | }; 21 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/MIDIParser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "IMIDIDriver_test.h" 4 | #include 5 | #include 6 | 7 | using namespace testing; 8 | 9 | class MIDIParserTest : public Test { 10 | protected: 11 | MockMIDIDriver driver; 12 | MIDIParser parser; 13 | 14 | MIDIParserTest() : driver(), parser(&driver) 15 | { 16 | } 17 | 18 | void SetUp() override 19 | { 20 | } 21 | }; 22 | 23 | TEST_F(MIDIParserTest, ParsesNoteOnMessage) 24 | { 25 | uint8_t buffer[] = { 26 | 0x93, 0x40, 0x7f 27 | }; 28 | EXPECT_CALL(driver, noteOn(3, 0x40, 0x7f)); 29 | parser.putBuffer(&buffer[0], 3); 30 | } 31 | 32 | TEST_F(MIDIParserTest, ParsesNoteOffMessage) 33 | { 34 | uint8_t buffer[] = { 35 | 0x83, 0x40, 0x7f 36 | }; 37 | EXPECT_CALL(driver, noteOff(3, 0x40, 0x7f)); 38 | parser.putBuffer(&buffer[0], 3); 39 | } 40 | 41 | TEST_F(MIDIParserTest, ParsesProgramChangeMessage) 42 | { 43 | uint8_t buffer[] = { 44 | 0xc3, 0x42 45 | }; 46 | EXPECT_CALL(driver, programChange(3, 0x42)); 47 | parser.putBuffer(&buffer[0], 3); 48 | } 49 | 50 | TEST_F(MIDIParserTest, ParsesControlChangeMessage) 51 | { 52 | uint8_t buffer[] = { 53 | 0xb3, 0x42, 0x21 54 | }; 55 | EXPECT_CALL(driver, controlChange(3, 0x42, 0x21)); 56 | parser.putBuffer(&buffer[0], 3); 57 | } 58 | 59 | TEST_F(MIDIParserTest, ParsesSysExMessage) 60 | { 61 | uint8_t buffer[] = { 62 | 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7 63 | }; 64 | EXPECT_CALL(driver, sysEx(_, 7)); 65 | parser.putBuffer(&buffer[0], 7); 66 | } 67 | 68 | TEST_F(MIDIParserTest, ParsesPitchWheelMessage) 69 | { 70 | uint8_t buffer[] = { 71 | 0xe3, 0x34, 0x24 72 | }; 73 | EXPECT_CALL(driver, pitchWheel(3, 0x1234)); 74 | parser.putBuffer(&buffer[0], 3); 75 | } 76 | 77 | TEST_F(MIDIParserTest, IgnoresSpuriousDataBeforeMessages) 78 | { 79 | uint8_t buffer[] = { 80 | 0x42, 0x38, 0x90, 0x42, 0x7f 81 | }; 82 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 83 | parser.putBuffer(&buffer[0], 5); 84 | } 85 | 86 | TEST_F(MIDIParserTest, IgnoresSpuriousDataBetweenMessages) 87 | { 88 | InSequence seq; 89 | 90 | uint8_t buffer[] = { 91 | 0x90, 0x42, 0x7f, 0x38, 0x42, 0x90, 0x43, 0x7f 92 | }; 93 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 94 | EXPECT_CALL(driver, noteOn(0, 0x43, 0x7f)).Times(1); 95 | parser.putBuffer(&buffer[0], 8); 96 | } 97 | 98 | TEST_F(MIDIParserTest, IgnoresSpuriousDataAfterMessages) 99 | { 100 | uint8_t buffer[] = { 101 | 0x90, 0x42, 0x7f, 0x38, 0x21 102 | }; 103 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 104 | parser.putBuffer(&buffer[0], 5); 105 | } 106 | 107 | TEST_F(MIDIParserTest, HandlesMessagesSplitOverMultipleBuffers) 108 | { 109 | uint8_t buffer1[] = { 110 | 0x90, 0x42 111 | }; 112 | uint8_t buffer2[] = { 113 | 0x7f 114 | }; 115 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 116 | parser.putBuffer(&buffer1[0], 2); 117 | parser.putBuffer(&buffer2[0], 1); 118 | } 119 | 120 | TEST_F(MIDIParserTest, HandlesSplitSysExMessages) 121 | { 122 | uint8_t buffer1[] = { 123 | 0xf0, 0x12, 0x34, 0x56 124 | }; 125 | uint8_t buffer2[] = { 126 | 0x78, 0x65, 0x43, 0x21, 0xf7 127 | }; 128 | EXPECT_CALL(driver, sysEx(_, 9)).Times(1); 129 | parser.putBuffer(&buffer1[0], 4); 130 | parser.putBuffer(&buffer2[0], 5); 131 | } 132 | 133 | TEST_F(MIDIParserTest, IgnoresPartialMessages) 134 | { 135 | InSequence seq; 136 | 137 | uint8_t buffer[] = { 138 | 0x93, 139 | 0x42, 140 | 0x7f, 141 | 0x92, 142 | 0x42, 143 | 0x94, 144 | 0x43, 145 | 0x7f, 146 | }; 147 | EXPECT_CALL(driver, noteOn(3, 0x42, 0x7f)).Times(1); 148 | EXPECT_CALL(driver, noteOn(4, 0x43, 0x7f)).Times(1); 149 | parser.putBuffer(&buffer[0], 8); 150 | } 151 | 152 | TEST_F(MIDIParserTest, IgnoresUnrecognizedMessages) 153 | { 154 | InSequence seq; 155 | uint8_t buffer[] = { 156 | 0x90, 0x42, 0x7f, 157 | 0xf4, 0x33, 0x22, 0x11, 158 | 0x90, 0x43, 0x7f 159 | }; 160 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 161 | EXPECT_CALL(driver, noteOn(0, 0x43, 0x7f)).Times(1); 162 | parser.putBuffer(&buffer[0], 10); 163 | } 164 | 165 | TEST_F(MIDIParserTest, IgnoresUnterminatedSysExMessages) 166 | { 167 | InSequence seq; 168 | uint8_t buffer[] = { 169 | 0xf0, 0x12, 0x34, 0x56, 170 | 0x90, 0x42, 0x7f 171 | }; 172 | EXPECT_CALL(driver, sysEx(_, _)).Times(0); 173 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 174 | parser.putBuffer(&buffer[0], 7); 175 | } 176 | 177 | TEST_F(MIDIParserTest, IgnoresMessagesThatOverflowBuffer) 178 | { 179 | uint8_t buffer[MIDI_BUFFER_SIZE + 10]; 180 | memset(&buffer[0], 0x77, MIDI_BUFFER_SIZE + 10); 181 | buffer[0] = 0x90; 182 | buffer[MIDI_BUFFER_SIZE + 7] = 0x90; 183 | buffer[MIDI_BUFFER_SIZE + 8] = 0x42; 184 | buffer[MIDI_BUFFER_SIZE + 9] = 0x7f; 185 | EXPECT_CALL(driver, noteOn(0, 0x42, 0x7f)).Times(1); 186 | parser.putBuffer(&buffer[0], MIDI_BUFFER_SIZE + 10); 187 | } 188 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/OPL/OPLParamInfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../ParamInfo.h" 4 | 5 | template <> 6 | const ParamInfoList* getParamInfoList() 7 | { 8 | return &OPL_PARAMS_INFO; 9 | } 10 | 11 | INSTANTIATE_TYPED_TEST_SUITE_P(OPLParamInfoTest, ParamInfoTest, OPLTone); 12 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/OPM/OPMParamInfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../ParamInfo.h" 4 | 5 | template <> 6 | const ParamInfoList* getParamInfoList() 7 | { 8 | return &OPM_PARAMS_INFO; 9 | } 10 | 11 | INSTANTIATE_TYPED_TEST_SUITE_P(OPMParamInfoTest, ParamInfoTest, OPMTone); 12 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/ParamInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | const ParamInfoList* getParamInfoList(); 9 | 10 | template 11 | class ParamInfoTest : public testing::Test { 12 | }; 13 | 14 | TYPED_TEST_SUITE_P(ParamInfoTest); 15 | 16 | TYPED_TEST_P(ParamInfoTest, MatchesDefaultToneData) 17 | { 18 | TypeParam defaultTone; 19 | const ParamInfoList* paramInfoList = getParamInfoList(); 20 | for (uint16_t i = 0; i < paramInfoList->numParams; i++) { 21 | auto& paramInfo = paramInfoList->paramInfo[i]; 22 | auto paramID = paramInfo.id; 23 | auto expectedValue = paramInfo.defaultValue; 24 | auto actualValue = defaultTone.getParam(paramID); 25 | EXPECT_EQ(expectedValue, actualValue) << "Expected: " << expectedValue << " Actual: " << actualValue << " for parameter: " << std::showbase << std::hex << paramID; 26 | } 27 | } 28 | 29 | TYPED_TEST_P(ParamInfoTest, GetsAndSetsParams) 30 | { 31 | TypeParam tone; 32 | const ParamInfoList* paramInfoList = getParamInfoList(); 33 | for (uint16_t i = 0; i < paramInfoList->numParams; i++) { 34 | auto& paramInfo = paramInfoList->paramInfo[i]; 35 | auto paramID = paramInfo.id; 36 | auto newValue = paramInfo.defaultValue == 0 ? 1 : paramInfo.defaultValue - 1; 37 | tone.setParam(paramID, newValue); 38 | auto readValue = tone.getParam(paramID); 39 | EXPECT_EQ(readValue, newValue) << "Failed to get/set parameter: " << std::showbase << std::hex << paramID; 40 | } 41 | } 42 | 43 | REGISTER_TYPED_TEST_SUITE_P(ParamInfoTest, MatchesDefaultToneData, GetsAndSetsParams); 44 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/SD1/SD1ParamInfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../ParamInfo.h" 4 | 5 | template <> 6 | const ParamInfoList* getParamInfoList() 7 | { 8 | return &SD1_PARAMS_INFO; 9 | } 10 | 11 | INSTANTIATE_TYPED_TEST_SUITE_P(SD1ParamInfoTest, ParamInfoTest, SD1Tone); 12 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/TestTone.cpp: -------------------------------------------------------------------------------- 1 | #include "TestTone.h" 2 | 3 | uint16_t TestTone::getParam(uint16_t paramID) 4 | { 5 | switch (paramID) { 6 | case PARAM_ID_TEST_A: 7 | return paramA; 8 | case PARAM_ID_TEST_B: 9 | return paramB; 10 | case PARAM_ID_TEST_C: 11 | return paramC; 12 | } 13 | return 0; 14 | } 15 | 16 | void TestTone::setParam(uint16_t paramID, uint16_t paramValue) 17 | { 18 | switch (paramID) { 19 | case PARAM_ID_TEST_A: 20 | paramA = paramValue; 21 | break; 22 | case PARAM_ID_TEST_B: 23 | paramB = paramValue; 24 | break; 25 | case PARAM_ID_TEST_C: 26 | paramC = paramValue; 27 | break; 28 | } 29 | } 30 | 31 | const ParamInfo TEST_PARAMS_INFO_ITEMS[] = { 32 | { PARAM_ID_TEST_A, PARAM_DEFAULT_TEST_A }, 33 | { PARAM_ID_TEST_B, PARAM_DEFAULT_TEST_B }, 34 | { PARAM_ID_TEST_C, PARAM_DEFAULT_TEST_C } 35 | }; 36 | 37 | const ParamInfoList TEST_PARAMS_INFO = { 3, TEST_PARAMS_INFO_ITEMS }; 38 | -------------------------------------------------------------------------------- /libnotesaladcore/test/MIDI/TestTone.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define PARAM_ID_TEST_A 0 6 | #define PARAM_ID_TEST_B 27 7 | #define PARAM_ID_TEST_C 488 8 | 9 | #define PARAM_DEFAULT_TEST_A 0 10 | #define PARAM_DEFAULT_TEST_B 37 11 | #define PARAM_DEFAULT_TEST_C 981 12 | 13 | class TestTone { 14 | public: 15 | uint16_t paramA = PARAM_DEFAULT_TEST_A; 16 | uint16_t paramB = PARAM_DEFAULT_TEST_B; 17 | uint16_t paramC = PARAM_DEFAULT_TEST_C; 18 | 19 | uint16_t getParam(uint16_t paramID); 20 | void setParam(uint16_t paramID, uint16_t paramValue); 21 | }; 22 | 23 | extern const ParamInfo TEST_PARAMS_INFO_ITEMS[]; 24 | 25 | extern const ParamInfoList TEST_PARAMS_INFO; 26 | -------------------------------------------------------------------------------- /libnotesaladcore/test/OPL/OPLReadWriteDeviceBase.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace testing; 6 | 7 | class OPLReadWriteDeviceBaseTest : public ::testing::Test { 8 | protected: 9 | OPLRegisterSet regs; 10 | }; 11 | 12 | TEST_F(OPLReadWriteDeviceBaseTest, DeterminesVoiceEnabledStatus) 13 | { 14 | this->regs.write(0x105, 1); 15 | for (uint8_t ch4Op = 0; ch4Op < 6; ch4Op++) { 16 | this->regs.write(0x104, 1 << ch4Op); 17 | 18 | uint8_t v4Op = ch4Op + 18; 19 | uint8_t ch2Op[2]; 20 | OPL::get2OpChannelsFor4OpChannel(ch4Op, &ch2Op[0]); 21 | 22 | for (uint8_t v = 0; v < OPL_NUM_VOICES; v++) { 23 | bool voiceEnabled = this->regs.isVoiceEnabled(v); 24 | if (v == ch2Op[0] || v == ch2Op[1]) { 25 | EXPECT_EQ(voiceEnabled, false) << "2-op voice " << int(v) << " enabled, should be disabled for active 4-op channel " << int(ch4Op); 26 | } else if (v == v4Op) { 27 | EXPECT_EQ(voiceEnabled, true) << "4-op voice " << int(v) << " disabled, should be enabled for active 4-op channel " << int(ch4Op); 28 | } else if (v < 18) { 29 | EXPECT_EQ(voiceEnabled, true) << "2-op voice " << int(v) << " disabled, should be enabled for active 4-op channel " << int(ch4Op); 30 | } else { 31 | EXPECT_EQ(voiceEnabled, false) << "4-op voice " << int(v) << " enabled, should be disabled for active 4-op channel " << int(ch4Op); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /libnotesaladcore/test/SD1/SD1Utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace testing; 6 | 7 | class SD1UtilsTest : public ::testing::Test { 8 | }; 9 | 10 | TEST_F(SD1UtilsTest, ConvertsOPLTone) 11 | { 12 | OPLTone oplTone; 13 | oplTone.set4Op(true); 14 | oplTone.setFB(3); 15 | oplTone.setCNT(1); 16 | 17 | for (uint8_t op = 0; op < 4; op++) { 18 | oplTone.setAM(op, op % 2); 19 | oplTone.setVIB(op, 1 - (op % 2)); 20 | oplTone.setEGT(op, op % 3); 21 | oplTone.setKSR(op, 1 - (op % 3)); 22 | oplTone.setMULT(op, 4 - op); 23 | oplTone.setKSL(op, op); 24 | oplTone.setTL(op, op * 3 + 1); 25 | oplTone.setAR(op, op * 2); 26 | oplTone.setDR(op, (3 - op) * 2); 27 | oplTone.setSL(op, op); 28 | oplTone.setRR(op, 3 - op); 29 | oplTone.setWS(op, op); 30 | } 31 | 32 | SD1Tone sd1Tone; 33 | SD1::convertOPLTone(oplTone, sd1Tone); 34 | 35 | EXPECT_EQ(sd1Tone.getALG(), 5) << "Incorrect algorithm"; 36 | for (uint8_t op = 0; op < 4; op++) { 37 | EXPECT_EQ(sd1Tone.getEAM(op), oplTone.getAM(op)) << "EAM value differs for operator " << (int)op; 38 | EXPECT_EQ(sd1Tone.getEVB(op), oplTone.getVIB(op)) << "EVB value differs for operator " << (int)op; 39 | EXPECT_EQ(sd1Tone.getKSR(op), oplTone.getKSR(op)) << "KSR value differs for operator " << (int)op; 40 | EXPECT_EQ(sd1Tone.getMULTI(op), oplTone.getMULT(op)) << "MULTI value differs for operator " << (int)op; 41 | EXPECT_EQ(sd1Tone.getKSL(op), oplTone.getKSL(op)) << "KSL value differs for operator " << (int)op; 42 | EXPECT_EQ(sd1Tone.getTL(op), oplTone.getTL(op)) << "TL value differs for operator " << (int)op; 43 | EXPECT_EQ(sd1Tone.getAR(op), oplTone.getAR(op)) << "AR value differs for operator " << (int)op; 44 | EXPECT_EQ(sd1Tone.getDR(op), oplTone.getDR(op)) << "DR value differs for operator " << (int)op; 45 | EXPECT_EQ(sd1Tone.getSL(op), oplTone.getSL(op)) << "SL value differs for operator " << (int)op; 46 | EXPECT_EQ(sd1Tone.getRR(op), oplTone.getRR(op)) << "RR value differs for operator " << (int)op; 47 | EXPECT_EQ(sd1Tone.getWS(op), oplTone.getWS(op)) << "WS value differs for operator " << (int)op; 48 | 49 | uint8_t expectedSR = oplTone.getEGT(op) ? 0 : oplTone.getRR(op); 50 | EXPECT_EQ(sd1Tone.getSR(op), expectedSR) << "SR value is incorrect for operator " << (int)op; 51 | } 52 | EXPECT_EQ(sd1Tone.getFB(0), oplTone.getFB()) << "FB value differs"; 53 | } 54 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | *.egg-info 3 | **/__pycache__ -------------------------------------------------------------------------------- /python/notesalad/opl.py: -------------------------------------------------------------------------------- 1 | from . import MIDIBase, OPLResetCallback, OPLWriteCallback, _lib, opl_emu_getsamples 2 | from ctypes import c_void_p 3 | 4 | 5 | DEFAULT_SAMPLE_RATE = 49716 6 | 7 | 8 | class OPLDeviceBase: 9 | def __init__(self, h_device): 10 | self.h_device = h_device 11 | 12 | def __del__(self): 13 | _lib.ntsld_opl_delete(self.h_device) 14 | 15 | def reset(self): 16 | _lib.ntsld_opl_reset(self.h_device) 17 | 18 | def write(self, addr, data): 19 | _lib.ntsld_opl_write(self.h_device, addr, data) 20 | 21 | 22 | class OPLEmulator(OPLDeviceBase): 23 | def __init__(self, sample_rate=DEFAULT_SAMPLE_RATE): 24 | super().__init__(_lib.ntsld_opl_emu_new(sample_rate)) 25 | 26 | def get_samples(self, buffer, offset=0, sample_count=None): 27 | opl_emu_getsamples(self.h_device, buffer, offset, sample_count) 28 | 29 | 30 | class OPLCallbackDevice(OPLDeviceBase): 31 | def __init__(self, write_callback, reset_callback): 32 | def call_write(_, reg, value): 33 | if write_callback is not None: 34 | write_callback(reg, value) 35 | 36 | def call_reset(_): 37 | if reset_callback is not None: 38 | reset_callback() 39 | 40 | self._c_call_write = OPLWriteCallback(call_write) 41 | self._c_call_reset = OPLResetCallback(call_reset) 42 | 43 | super().__init__(_lib.ntsld_opl_cbkdev_new( 44 | c_void_p(0), self._c_call_write, self._c_call_reset)) 45 | 46 | 47 | class OPL2MIDI(MIDIBase): 48 | def __init__(self, device): 49 | self.device = device 50 | super().__init__(_lib.ntsld_opl2midi_new(c_void_p(0), device.h_device)) 51 | 52 | 53 | class OPL3MIDI(MIDIBase): 54 | def __init__(self, device): 55 | self.device = device 56 | super().__init__(_lib.ntsld_opl3midi_new(c_void_p(0), device.h_device)) 57 | -------------------------------------------------------------------------------- /python/notesalad/opm.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_void_p 2 | from . import MIDIBase, OPMResetCallback, OPMWriteCallback, _lib, opm_emu_getsamples 3 | 4 | 5 | DEFAULT_SAMPLE_RATE = 55930 6 | 7 | 8 | class OPMDeviceBase: 9 | def __init__(self, h_device): 10 | self.h_device = h_device 11 | 12 | def __del__(self): 13 | _lib.ntsld_opm_delete(self.h_device) 14 | 15 | def reset(self): 16 | _lib.ntsld_opm_reset(self.h_device) 17 | 18 | def write(self, addr, data): 19 | _lib.ntsld_opm_write(self.h_device, addr, data) 20 | 21 | 22 | class OPMEmulator(OPMDeviceBase): 23 | def __init__(self, sample_rate=DEFAULT_SAMPLE_RATE): 24 | super().__init__(_lib.ntsld_opm_emu_new(sample_rate)) 25 | 26 | def get_samples(self, buffer, offset=0, sample_count=None): 27 | opm_emu_getsamples(self.h_device, buffer, offset, sample_count) 28 | 29 | 30 | class OPMCallbackDevice(OPMDeviceBase): 31 | def __init__(self, write_callback, reset_callback): 32 | def call_write(_, reg, value): 33 | if write_callback is not None: 34 | write_callback(reg, value) 35 | 36 | def call_reset(_): 37 | if reset_callback is not None: 38 | reset_callback() 39 | 40 | self._c_call_write = OPMWriteCallback(call_write) 41 | self._c_call_reset = OPMResetCallback(call_reset) 42 | 43 | super().__init__(_lib.ntsld_opm_cbkdev_new( 44 | c_void_p(0), self._c_call_write, self._c_call_reset)) 45 | 46 | 47 | class OPMMIDI(MIDIBase): 48 | def __init__(self, device): 49 | self.device = device 50 | super().__init__(_lib.ntsld_opmmidi_new(c_void_p(0), device.h_device)) 51 | -------------------------------------------------------------------------------- /python/notesalad/patch.py: -------------------------------------------------------------------------------- 1 | import pydash 2 | from .resources import PARAMS_UNIVERSAL 3 | 4 | 5 | def encode_leb128(value): 6 | result = bytearray() 7 | while True: 8 | byte = value & 0x7f 9 | value >>= 7 10 | if value == 0: 11 | result.append(byte) 12 | break 13 | else: 14 | result.append(byte | 0x80) 15 | return result 16 | 17 | 18 | def serialize(json_patch, json_params): 19 | result = bytearray() 20 | all_params = [*json_params, *PARAMS_UNIVERSAL] 21 | last_param_id = 0 22 | for param_info in all_params: 23 | param_value = pydash.get(json_patch, param_info['path']) 24 | if param_value is not None and param_value != param_info['default']: 25 | param_id = param_info['id'] 26 | result.extend(encode_leb128(param_id - last_param_id)) 27 | result.extend(encode_leb128(param_value)) 28 | last_param_id = param_id 29 | return result 30 | -------------------------------------------------------------------------------- /python/notesalad/resources/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib.resources 2 | import json 3 | 4 | 5 | def load_json_resource(name): 6 | with importlib.resources.files(__package__).joinpath(name).open('rb') as json_file: 7 | return json.load(json_file) 8 | 9 | 10 | PARAMS_OPL = load_json_resource('opl.json') 11 | PARAMS_OPM = load_json_resource('opm.json') 12 | PARAMS_SD1 = load_json_resource('sd1.json') 13 | PARAMS_UNIVERSAL = load_json_resource('universal.json') 14 | -------------------------------------------------------------------------------- /python/notesalad/sd1.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_void_p 2 | 3 | from .opl import OPLDeviceBase 4 | from . import MIDIBase, SD1DelayCallback, SD1WriteCallback, _lib, c_uint8_t 5 | 6 | 7 | class SD1DeviceBase: 8 | def __init__(self, h_device): 9 | self.h_device = h_device 10 | 11 | def __del__(self): 12 | _lib.ntsld_sd1_delete(self.h_device) 13 | 14 | 15 | class SD1CallbackDevice(SD1DeviceBase): 16 | def __init__(self, write_callback, delay_callback): 17 | def call_write(_, data, data_len): 18 | byte_array = c_uint8_t * data_len 19 | write_callback(byte_array.from_address(data)) 20 | 21 | def call_delay(_, ms): 22 | delay_callback(ms) 23 | 24 | self._c_call_write = SD1WriteCallback(call_write) 25 | self._c_call_delay = SD1DelayCallback(call_delay) 26 | 27 | super().__init__(_lib.ntsld_sd1_cbkdev_new( 28 | c_void_p(0), self._c_call_write, self._c_call_delay)) 29 | 30 | 31 | class SD1OPLDevice(OPLDeviceBase): 32 | def __init__(self, sd1_device): 33 | self.sd1_device = sd1_device 34 | super().__init__(_lib.ntsld_oplsd1_new(sd1_device.h_device)) 35 | 36 | 37 | class SD1MIDI(MIDIBase): 38 | def __init__(self, device): 39 | self.device = device 40 | super().__init__(_lib.ntsld_sd1midi_new(c_void_p(0), device.h_device)) 41 | -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "notesalad" 7 | dynamic = ["version", "description"] 8 | dependencies = ["pydash >=6.0.0"] 9 | -------------------------------------------------------------------------------- /scripts/codegen/gen_param_info_cpp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import argparse 4 | import json 5 | 6 | parser = argparse.ArgumentParser( 7 | description='Generate C++ parameter info file') 8 | parser.add_argument('json_file_path', metavar='JSON_FILE', 9 | help='path to the JSON file containing parameter info') 10 | parser.add_argument('cpp_file_path', metavar='CPP_FILE', 11 | help='path to the generated C++ source code file') 12 | parser.add_argument('-i', '--include', metavar='HEADER', nargs=1, 13 | help='headers to include', action='append') 14 | parser.add_argument('-n', '--var-name', metavar='NAME', 15 | nargs=1, help='name of parameter info variable', default=['PARAMS']) 16 | 17 | args = parser.parse_args() 18 | 19 | with open(args.json_file_path, 'rb') as input_file: 20 | params_info = json.load(input_file) 21 | 22 | with open(args.cpp_file_path, 'w', encoding='utf-8') as output: 23 | if args.include is not None: 24 | for include in args.include: 25 | output.write(f'#include "{include[0]}"\n') 26 | output.write("\n") 27 | 28 | output.write(f'const ParamInfo {args.var_name[0]}_ITEMS[] = {{\n') 29 | 30 | for param_info in params_info: 31 | param_id = param_info['id'] 32 | param_default = param_info['default'] 33 | param_desc = param_info['shortDesc'] 34 | output.write( 35 | f' {{ 0x{param_id:04x}, {param_default} }}, // {param_desc}\n') 36 | 37 | output.write('};\n') 38 | output.write('\n') 39 | 40 | output.write( 41 | f'const ParamInfoList {args.var_name[0]} = {{ {len(params_info)}, {args.var_name[0]}_ITEMS }};\n') 42 | -------------------------------------------------------------------------------- /scripts/doc/gen_params_md.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import json 4 | import os.path 5 | 6 | 7 | def format_table_row(row, col_widths): 8 | fmt_row = [] 9 | for col_index in range(0, len(row)): 10 | fmt_row.append(row[col_index] + ' ' * 11 | (col_widths[col_index] - len(row[col_index]))) 12 | return '| ' + ' | '.join(fmt_row) + ' |' 13 | 14 | 15 | def format_table(rows, header=None): 16 | header = [] if header is None else header 17 | table_lines = [] 18 | col_widths = [len(x) for x in header] 19 | for row in rows: 20 | for (col_index, col) in enumerate(row): 21 | if col_index >= len(col_widths): 22 | col_widths.append(0) 23 | col_widths[col_index] = max(len(col), col_widths[col_index]) 24 | if len(header) > 0: 25 | table_lines.append(format_table_row(header, col_widths)) 26 | table_lines.append( 27 | '| ' + ' | '.join(['-' * w for w in col_widths]) + ' |') 28 | for row in rows: 29 | table_lines.append(format_table_row(row, col_widths)) 30 | return table_lines 31 | 32 | 33 | def gen_params_table(params): 34 | rows = [] 35 | for param in params: 36 | param_id = param['id'] 37 | desc = param['description'] 38 | v_min, v_max = param['range'] 39 | row = [f'{param_id:d}', f'0x{param_id:04x}', 40 | desc, f'{v_min:d}-{v_max:d}'] 41 | rows.append(row) 42 | return format_table(rows, ['NRPN', 'Hex', 'Parameter', 'Range']) 43 | 44 | 45 | base_dir = os.path.join(os.path.dirname(__file__), '..', '..') 46 | chips = [ 47 | { 48 | 'name': 'OPL', 49 | 'params_file': 'resources/params/opl.json', 50 | 'md_file': 'doc/opl-params.md' 51 | 52 | }, 53 | { 54 | 'name': 'OPM', 55 | 'params_file': 'resources/params/opm.json', 56 | 'md_file': 'doc/opm-params.md' 57 | }, 58 | { 59 | 'name': 'SD-1', 60 | 'params_file': 'resources/params/sd1.json', 61 | 'md_file': 'doc/sd1-params.md' 62 | } 63 | ] 64 | 65 | json_file_path = os.path.join(base_dir, 'resources/params/universal.json') 66 | with open(json_file_path, 'rb') as json_file: 67 | universal_params_data = json.load(json_file) 68 | 69 | for chip in chips: 70 | json_file_path = os.path.join(base_dir, chip['params_file']) 71 | with open(json_file_path, 'rb') as json_file: 72 | chip_params_data = json.load(json_file) 73 | lines = [] 74 | lines.append(f'# {chip["name"]} Parameters') 75 | lines.append('') 76 | lines.extend(gen_params_table(chip_params_data)) 77 | lines.append('') 78 | lines.append('# Universal Parameters') 79 | lines.append('') 80 | lines.extend(gen_params_table(universal_params_data)) 81 | md_file_path = os.path.join(base_dir, chip['md_file']) 82 | with open(md_file_path, 'w', encoding='utf-8') as md_file: 83 | md_file.writelines([line + '\n' for line in lines]) 84 | -------------------------------------------------------------------------------- /scripts/generate_all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -x 4 | 5 | SCRIPT_DIR="$(dirname $(readlink -f $0))" 6 | cd "$SCRIPT_DIR" 7 | 8 | PARAMINFO_DIR="../resources/params" 9 | PY_RESOURCES_DIR="../python/notesalad/resources" 10 | 11 | ./params/gen_universal.py > "$PARAMINFO_DIR/universal.json" 12 | ./params/gen_opl.py > "$PARAMINFO_DIR/opl.json" 13 | ./params/gen_opm.py > "$PARAMINFO_DIR/opm.json" 14 | ./params/gen_sd1.py > "$PARAMINFO_DIR/sd1.json" 15 | 16 | ./codegen/gen_param_info_cpp.py -i ParamInfo.h -n UNIVERSAL_PARAMS_INFO "$PARAMINFO_DIR/universal.json" ../libnotesaladcore/src/MIDI/ParamInfo.g.cpp 17 | ./codegen/gen_param_info_cpp.py -i OPLParamInfo.h -n OPL_PARAMS_INFO "$PARAMINFO_DIR/opl.json" ../libnotesaladcore/src/MIDI/OPL/OPLParamInfo.g.cpp 18 | ./codegen/gen_param_info_cpp.py -i OPMParamInfo.h -n OPM_PARAMS_INFO "$PARAMINFO_DIR/opm.json" ../libnotesaladcore/src/MIDI/OPM/OPMParamInfo.g.cpp 19 | ./codegen/gen_param_info_cpp.py -i SD1ParamInfo.h -n SD1_PARAMS_INFO "$PARAMINFO_DIR/sd1.json" ../libnotesaladcore/src/MIDI/SD1/SD1ParamInfo.g.cpp 20 | 21 | cp "$PARAMINFO_DIR"/*.json "$PY_RESOURCES_DIR" 22 | 23 | ./doc/gen_params_md.py 24 | -------------------------------------------------------------------------------- /scripts/params/gen_opl.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import json 4 | 5 | params = [ 6 | { 7 | "id": 0, 8 | "path": "is4Op", 9 | "description": "4-op mode", 10 | "shortDesc": "4-op mode", 11 | "range": [0, 1], 12 | "default": 0 13 | }, 14 | { 15 | "id": 1, 16 | "path": "fb", 17 | "description": "Feedback", 18 | "shortDesc": "Feedback", 19 | "range": [0, 7], 20 | "default": 0 21 | }, 22 | { 23 | "id": 2, 24 | "path": "conn", 25 | "description": "Connection", 26 | "shortDesc": "Connection", 27 | "range": [0, 3], 28 | "default": 0 29 | } 30 | ] 31 | 32 | 33 | def op_params(op): 34 | i = ((op + 1) * 16) 35 | p_prefix = f"operators[{op}]" 36 | d_prefix = f"Operator {op+1}" 37 | ds_prefix = f"Op {op+1}" 38 | return [ 39 | { 40 | "id": i + 0, 41 | "path": f"{p_prefix}.am", 42 | "description": f"{d_prefix} enable amplitude modulation", 43 | "shortDesc": f"{ds_prefix} AM", 44 | "range": [0, 1], 45 | "default": 0 46 | }, 47 | { 48 | "id": i + 1, 49 | "path": f"{p_prefix}.vib", 50 | "description": f"{d_prefix} enable vibrato", 51 | "shortDesc": f"{ds_prefix} VIB", 52 | "range": [0, 1], 53 | "default": 0 54 | }, 55 | { 56 | "id": i + 2, 57 | "path": f"{p_prefix}.egt", 58 | "description": f"{d_prefix} envelope type", 59 | "shortDesc": f"{ds_prefix} EGT", 60 | "range": [0, 1], 61 | "default": 1 62 | }, 63 | { 64 | "id": i + 3, 65 | "path": f"{p_prefix}.ksr", 66 | "description": f"{d_prefix} key scale rate", 67 | "shortDesc": f"{ds_prefix} KSR", 68 | "range": [0, 1], 69 | "default": 0 70 | }, 71 | { 72 | "id": i + 4, 73 | "path": f"{p_prefix}.mult", 74 | "description": f"{d_prefix} frequency multiplier", 75 | "shortDesc": f"{ds_prefix} MULT", 76 | "range": [0, 15], 77 | "default": 1 78 | }, 79 | { 80 | "id": i + 5, 81 | "path": f"{p_prefix}.ksl", 82 | "description": f"{d_prefix} key scale level", 83 | "shortDesc": f"{ds_prefix} KSL", 84 | "range": [0, 3], 85 | "default": 0 86 | }, 87 | { 88 | "id": i + 6, 89 | "path": f"{p_prefix}.tl", 90 | "description": f"{d_prefix} total level", 91 | "shortDesc": f"{ds_prefix} level", 92 | "range": [0, 63], 93 | "default": 0 if op in (1, 3) else 63, 94 | "invert": True 95 | }, 96 | { 97 | "id": i + 7, 98 | "path": f"{p_prefix}.ar", 99 | "description": f"{d_prefix} attack rate", 100 | "shortDesc": f"{ds_prefix} attack", 101 | "range": [0, 15], 102 | "default": 15 103 | }, 104 | { 105 | "id": i + 8, 106 | "path": f"{p_prefix}.dr", 107 | "description": f"{d_prefix} decay rate", 108 | "shortDesc": f"{ds_prefix} decay", 109 | "range": [0, 15], 110 | "default": 0 111 | }, 112 | { 113 | "id": i + 9, 114 | "path": f"{p_prefix}.sl", 115 | "description": f"{d_prefix} sustain level", 116 | "shortDesc": f"{ds_prefix} sustain", 117 | "range": [0, 15], 118 | "default": 15 119 | }, 120 | { 121 | "id": i + 10, 122 | "path": f"{p_prefix}.rr", 123 | "description": f"{d_prefix} release rate", 124 | "shortDesc": f"{ds_prefix} release", 125 | "range": [0, 15], 126 | "default": 15 127 | }, 128 | { 129 | "id": i + 11, 130 | "path": f"{p_prefix}.ws", 131 | "description": f"{d_prefix} wave shape", 132 | "shortDesc": f"{ds_prefix} wave", 133 | "range": [0, 7], 134 | "default": 0 135 | } 136 | ] 137 | 138 | 139 | for op in range(0, 4): 140 | params.extend(op_params(op)) 141 | 142 | print(json.dumps(params, indent=2)) 143 | -------------------------------------------------------------------------------- /scripts/params/gen_opm.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import json 4 | 5 | params = [ 6 | { 7 | "id": 1, 8 | "path": "fb", 9 | "description": "Feedback", 10 | "shortDesc": "Feedback", 11 | "range": [0, 7], 12 | "default": 0 13 | }, 14 | { 15 | "id": 2, 16 | "path": "conn", 17 | "description": "Connection", 18 | "shortDesc": "Connection", 19 | "range": [0, 7], 20 | "default": 0 21 | }, 22 | { 23 | "id": 3, 24 | "path": "pms", 25 | "description": "Phase modulation sensitivity", 26 | "shortDesc": "PM", 27 | "range": [0, 7], 28 | "default": 0 29 | }, 30 | { 31 | "id": 4, 32 | "path": "ams", 33 | "description": "Amplitude modulation sensitivity", 34 | "shortDesc": "AM", 35 | "range": [0, 7], 36 | "default": 0 37 | }, 38 | { 39 | "id": 5, 40 | "path": "opsEnabled", 41 | "description": "Enabled operators", 42 | "shortDesc": "Operators", 43 | "range": [0, 0x0f], 44 | "default": 0x0f 45 | } 46 | ] 47 | 48 | 49 | def op_params(op): 50 | i = ((op + 1) * 16) 51 | p_prefix = f"operators[{op}]" 52 | d_prefix = f"Operator {op+1}" 53 | ds_prefix = f"Op {op+1}" 54 | return [ 55 | { 56 | "id": i + 0, 57 | "path": f"{p_prefix}.dt1", 58 | "description": f"{d_prefix} detune 1", 59 | "shortDesc": f"{ds_prefix} detune 1", 60 | "range": [0, 7], 61 | "default": 0 62 | }, 63 | { 64 | "id": i + 1, 65 | "path": f"{p_prefix}.mul", 66 | "description": f"{d_prefix} frequency multiplier", 67 | "shortDesc": f"{ds_prefix} mult", 68 | "range": [0, 15], 69 | "default": 1 70 | }, 71 | { 72 | "id": i + 2, 73 | "path": f"{p_prefix}.tl", 74 | "description": f"{d_prefix} total level", 75 | "shortDesc": f"{ds_prefix} level", 76 | "range": [0, 127], 77 | "default": 0 if op == 3 else 127, 78 | "invert": True 79 | }, 80 | { 81 | "id": i + 3, 82 | "path": f"{p_prefix}.ks", 83 | "description": f"{d_prefix} key scaling", 84 | "shortDesc": f"{ds_prefix} KS", 85 | "range": [0, 3], 86 | "default": 0 87 | }, 88 | { 89 | "id": i + 4, 90 | "path": f"{p_prefix}.ar", 91 | "description": f"{d_prefix} attack rate", 92 | "shortDesc": f"{ds_prefix} attack", 93 | "range": [0, 31], 94 | "default": 31 95 | }, 96 | { 97 | "id": i + 5, 98 | "path": f"{p_prefix}.amsen", 99 | "description": f"{d_prefix} enable amplitude modulation", 100 | "shortDesc": f"{ds_prefix} AM", 101 | "range": [0, 1], 102 | "default": 0 103 | }, 104 | { 105 | "id": i + 6, 106 | "path": f"{p_prefix}.d1r", 107 | "description": f"{d_prefix} decay rate 1", 108 | "shortDesc": f"{ds_prefix} D1R", 109 | "range": [0, 31], 110 | "default": 0 111 | }, 112 | { 113 | "id": i + 7, 114 | "path": f"{p_prefix}.dt2", 115 | "description": f"{d_prefix} detune 2", 116 | "shortDesc": f"{ds_prefix} detune 2", 117 | "range": [0, 3], 118 | "default": 0 119 | }, 120 | { 121 | "id": i + 8, 122 | "path": f"{p_prefix}.d2r", 123 | "description": f"{d_prefix} decay rate 2", 124 | "shortDesc": f"{ds_prefix} D2R", 125 | "range": [0, 31], 126 | "default": 0 127 | }, 128 | { 129 | "id": i + 9, 130 | "path": f"{p_prefix}.d1l", 131 | "description": f"{d_prefix} decay level 1", 132 | "shortDesc": f"{ds_prefix} D1L", 133 | "range": [0, 15], 134 | "default": 0 135 | }, 136 | { 137 | "id": i + 10, 138 | "path": f"{p_prefix}.rr", 139 | "description": f"{d_prefix} release rate", 140 | "shortDesc": f"{ds_prefix} RR", 141 | "range": [0, 15], 142 | "default": 15 143 | } 144 | ] 145 | 146 | 147 | for op in range(0, 4): 148 | params.extend(op_params(op)) 149 | 150 | print(json.dumps(params, indent=2)) 151 | -------------------------------------------------------------------------------- /scripts/params/gen_universal.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import json 4 | 5 | PARAM_MAP_PARAM_STEP = 0x10 6 | LFO_PARAM_STEP = 0x10 7 | 8 | NUM_PARAM_MAPS = 4 9 | NUM_LFOS = 2 10 | 11 | params = [ 12 | { 13 | "id": 0x2000, 14 | "path": "polyMode", 15 | "description": "Polyphony mode", 16 | "shortDesc": "Poly mode", 17 | "range": [0, 4], 18 | "default": 0 19 | }, 20 | { 21 | "id": 0x2001, 22 | "path": "fixedNoteNum", 23 | "description": "Fixed note number", 24 | "shortDesc": "Note num", 25 | "range": [0, 127], 26 | "default": 0 27 | }, 28 | { 29 | "id": 0x2002, 30 | "path": "pitchOffset", 31 | "description": "Pitch offset", 32 | "shortDesc": "Pitch offset", 33 | "range": [0, 16383], 34 | "default": 0x2000, 35 | "zeroOffset": 0x2000 36 | }, 37 | { 38 | "id": 0x2003, 39 | "path": "glideDurationMS", 40 | "description": "Glide duration (ms)", 41 | "shortDesc": "Glide", 42 | "range": [0, 16383], 43 | "default": 0 44 | }, 45 | { 46 | "id": 0x2004, 47 | "path": "velocityDepth", 48 | "description": "Velocity depth", 49 | "shortDesc": "Velocity depth", 50 | "range": [0, 0x7f], 51 | "default": 0x7f 52 | } 53 | ] 54 | 55 | 56 | def map_params(m): 57 | id_base = (m * PARAM_MAP_PARAM_STEP) + 0x3000 58 | p_prefix = f"paramMaps[{m}]" 59 | d_prefix = f"Map {m+1}" 60 | return [ 61 | { 62 | "id": id_base + 0, 63 | "path": f"{p_prefix}.src", 64 | "description": f"{d_prefix} source", 65 | "shortDesc": f"{d_prefix} src", 66 | "range": [0, 0xff], 67 | "default": 0xff 68 | }, 69 | { 70 | "id": id_base + 1, 71 | "path": f"{p_prefix}.dest", 72 | "description": f"{d_prefix} destination", 73 | "shortDesc": f"{d_prefix} dest", 74 | "range": [0, 0x3fff], 75 | "default": 0 76 | }, 77 | { 78 | "id": id_base + 2, 79 | "path": f"{p_prefix}.adjustAmount", 80 | "description": f"{d_prefix} adjustment amount", 81 | "shortDesc": f"{d_prefix} amount", 82 | "range": [0, 0x3fff], 83 | "default": 0x2000, 84 | "zeroOffset": 0x2000 85 | }, 86 | { 87 | "id": id_base + 3, 88 | "path": f"{p_prefix}.invertSrc", 89 | "description": f"{d_prefix} invert source", 90 | "shortDesc": f"{d_prefix} invert", 91 | "range": [0, 1], 92 | "default": 0 93 | } 94 | ] 95 | 96 | 97 | def lfo_params(l): 98 | id_base = (l * LFO_PARAM_STEP) + 0x3100 99 | p_prefix = f"lfoParams[{l}]" 100 | d_prefix = f"LFO {l+1}" 101 | return [ 102 | { 103 | "id": id_base + 0, 104 | "path": f"{p_prefix}.wave", 105 | "description": f"{d_prefix} wave", 106 | "shortDesc": f"{d_prefix} wave", 107 | "range": [0, 4], 108 | "default": 0 109 | }, 110 | { 111 | "id": id_base + 1, 112 | "path": f"{p_prefix}.periodMS", 113 | "description": f"{d_prefix} period (ms)", 114 | "shortDesc": f"{d_prefix} period", 115 | "range": [0, 0x3fff], 116 | "default": 0 117 | }, 118 | { 119 | "id": id_base + 2, 120 | "path": f"{p_prefix}.sync", 121 | "description": f"{d_prefix} sync", 122 | "shortDesc": f"{d_prefix} sync", 123 | "range": [0, 1], 124 | "default": 0 125 | }, 126 | { 127 | "id": id_base + 3, 128 | "path": f"{p_prefix}.oneShot", 129 | "description": f"{d_prefix} one shot", 130 | "shortDesc": f"{d_prefix} one shot", 131 | "range": [0, 1], 132 | "default": 0 133 | } 134 | ] 135 | 136 | 137 | for m in range(0, NUM_PARAM_MAPS): 138 | params.extend(map_params(m)) 139 | 140 | for l in range(0, NUM_LFOS): 141 | params.extend(lfo_params(l)) 142 | 143 | print(json.dumps(params, indent=2)) 144 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | wasm 3 | dist 4 | *.tgz 5 | -------------------------------------------------------------------------------- /web/.npmrc: -------------------------------------------------------------------------------- 1 | @danielrfry:registry=https://npm.pkg.github.com 2 | -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /web/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Dan Fry 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@danielrfry/notesalad", 3 | "version": "0.7.3", 4 | "description": "Device-independent MIDI implementation and software synthesizer", 5 | "repository": "git://github.com/danielrfry/notesalad.git", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "CMAKE_BUILD_TYPE=Release npm run build-common", 10 | "build-debug": "CMAKE_BUILD_TYPE=Debug npm run build-common", 11 | "build-common": "mkdir -p dist && npm run build-wasm && node_modules/.bin/webpack --config worklet.webpack.config.js && cp src/index.js dist/", 12 | "build-wasm": "unset NODE && mkdir -p build/wasm && cd build/wasm && emcmake cmake -DWASM=1 -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE ../../.. && emmake make -j 8" 13 | }, 14 | "author": "Dan Fry", 15 | "license": "BSD-3-Clause", 16 | "devDependencies": { 17 | "prettier": "^1.19.1", 18 | "webpack": "^5.80.0", 19 | "webpack-cli": "^4.9.2" 20 | }, 21 | "files": [ 22 | "src/*.js", 23 | "dist/*.js" 24 | ], 25 | "dependencies": {} 26 | } 27 | -------------------------------------------------------------------------------- /web/src/Worklet/Emulators/EmulatorBase.js: -------------------------------------------------------------------------------- 1 | export default class EmulatorBase { 2 | constructor(lib, hChip) { 3 | this._lib = lib; 4 | this._hChip = hChip; 5 | this._sampleBuffer = null; 6 | this._sampleBufferSize = 0; 7 | this._chipInitialised = true; 8 | } 9 | getSamples(offset, sampleCount, output) { 10 | // Resize buffer if needed 11 | if (sampleCount > this._sampleBufferSize) { 12 | if (this._sampleBuffer) { 13 | this._lib._free(this._sampleBuffer); 14 | } 15 | this._sampleBuffer = this._lib._calloc(sampleCount * 2, 2); 16 | } 17 | 18 | this._chipGetSamples(this._sampleBuffer, sampleCount); 19 | 20 | for (let i = 0; i < sampleCount; i++) { 21 | const sampleL = 22 | this._lib.getValue(this._sampleBuffer + i * 4, 'i16') / 32768; 23 | const sampleR = 24 | this._lib.getValue(this._sampleBuffer + i * 4 + 2, 'i16') / 25 | 32768; 26 | 27 | if (output.length == 1) { 28 | output[0][i + offset] = (sampleL + sampleR) / 2; 29 | } else { 30 | output[0][i + offset] = sampleL; 31 | output[1][i + offset] = sampleR; 32 | } 33 | } 34 | } 35 | _chipGetSamples(buffer, sampleCount) { 36 | // Abstract 37 | } 38 | _chipClose() { 39 | // Abstract 40 | } 41 | close() { 42 | if (this._sampleBuffer) { 43 | this._lib._free(this._sampleBuffer); 44 | this._sampleBuffer = null; 45 | } 46 | if (this._chipInitialised) { 47 | this._chipClose(); 48 | this._chipInitialised = false; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /web/src/Worklet/Emulators/OPLEmulator.js: -------------------------------------------------------------------------------- 1 | import EmulatorBase from './EmulatorBase'; 2 | 3 | export default class OPLEmulator extends EmulatorBase { 4 | constructor(lib) { 5 | const hChip = lib._ntsld_opl_emu_new(sampleRate); 6 | super(lib, hChip); 7 | this.write(0x105, 1); 8 | } 9 | reset() { 10 | this._lib._ntsld_opl_reset(this._hChip, reg, value); 11 | } 12 | write(reg, value) { 13 | this._lib._ntsld_opl_write(this._hChip, reg, value); 14 | } 15 | _chipInit() { 16 | this._hChip = this._lib._ntsld_opl_emu_new(sampleRate); 17 | } 18 | _chipGetSamples(buffer, sampleCount) { 19 | this._lib._ntsld_opl_emu_getsamples(this._hChip, buffer, sampleCount); 20 | } 21 | _chipClose() { 22 | this._lib._ntsld_opl_delete(this._hChip); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /web/src/Worklet/Emulators/OPMEmulator.js: -------------------------------------------------------------------------------- 1 | import EmulatorBase from './EmulatorBase'; 2 | 3 | export default class OPMEmulator extends EmulatorBase { 4 | constructor(lib) { 5 | const hChip = lib._ntsld_opm_emu_new(sampleRate); 6 | super(lib, hChip); 7 | } 8 | reset() { 9 | this._lib._ntsld_opm_reset(this._hChip, reg, value); 10 | } 11 | write(reg, value) { 12 | this._lib._ntsld_opm_write(this._hChip, reg, value); 13 | } 14 | _chipInit() { 15 | this._hChip = this._lib._ntsld_opm_emu_new(sampleRate); 16 | } 17 | _chipGetSamples(buffer, sampleCount) { 18 | this._lib._ntsld_opm_emu_getsamples(this._hChip, buffer, sampleCount); 19 | } 20 | _chipClose() { 21 | this._lib._ntsld_opm_delete(this._hChip); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/src/Worklet/MIDI/MIDIDriverBase.js: -------------------------------------------------------------------------------- 1 | export default class MIDIDriverBase { 2 | constructor(lib, hMIDI) { 3 | this._lib = lib; 4 | this._hMIDI = hMIDI; 5 | this._pReceiveMIDI = lib.addFunction( 6 | (...args) => this._receiveMIDI(...args), 7 | 'viii' 8 | ); 9 | this.onReceiveMIDI = null; 10 | lib._ntsld_midi_set_recv_cbk(this._hMIDI, 0, this._pReceiveMIDI); 11 | } 12 | 13 | reset() { 14 | this._lib._ntsld_midi_reset(this._hMIDI); 15 | } 16 | 17 | send(data) { 18 | const buffer = this._lib._calloc(data.length, 1); 19 | for (let i = 0; i < data.length; i++) { 20 | this._lib.setValue(buffer + i, data[i], 'i8'); 21 | } 22 | this._lib._ntsld_midi_send(this._hMIDI, buffer, data.length); 23 | this._lib._free(buffer); 24 | } 25 | 26 | setTime(timeMS) { 27 | this._lib._ntsld_midi_set_time(this._hMIDI, timeMS); 28 | } 29 | 30 | update() { 31 | this._lib._ntsld_midi_update(this._hMIDI); 32 | } 33 | 34 | close() { 35 | if (this._hMIDI) { 36 | this._lib._ntsld_midi_delete(this._hMIDI); 37 | this._lib.removeFunction(this._pReceiveMIDI); 38 | this._hMIDI = null; 39 | } 40 | } 41 | 42 | _receiveMIDI(_, pMsgData, msgLength) { 43 | if (this.onReceiveMIDI) { 44 | const msgData = new Uint8Array(msgLength); 45 | msgData.set( 46 | this._lib.HEAPU8.subarray(pMsgData, pMsgData + msgLength) 47 | ); 48 | this.onReceiveMIDI(msgData); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /web/src/Worklet/MIDI/OPL3MIDIDriver.js: -------------------------------------------------------------------------------- 1 | import MIDIDriverBase from './MIDIDriverBase'; 2 | 3 | export default class OPL3MIDIDriver extends MIDIDriverBase { 4 | constructor(lib, hChip) { 5 | const hMIDI = lib._ntsld_opl3midi_new(0, hChip); 6 | super(lib, hMIDI); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/src/Worklet/MIDI/OPMMIDIDriver.js: -------------------------------------------------------------------------------- 1 | import MIDIDriverBase from './MIDIDriverBase'; 2 | 3 | export default class OPMMIDIDriver extends MIDIDriverBase { 4 | constructor(lib, hChip) { 5 | const hMIDI = lib._ntsld_opmmidi_new(0, hChip); 6 | super(lib, hMIDI); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/src/Worklet/NoteSaladLibrary.js: -------------------------------------------------------------------------------- 1 | import init_libnotesalad from '../../build/wasm/libnotesalad/libnotesalad.js'; 2 | 3 | const libnotesalad = init_libnotesalad(); 4 | libnotesalad.print = (...args) => console.log(...args); 5 | 6 | export default libnotesalad; 7 | -------------------------------------------------------------------------------- /web/src/Worklet/Processors/OPLProcessor.js: -------------------------------------------------------------------------------- 1 | import libnotesalad from '../NoteSaladLibrary'; 2 | import OPLEmulator from '../Emulators/OPLEmulator'; 3 | import OPL3MIDIDriver from '../MIDI/OPL3MIDIDriver'; 4 | import OPXProcessorBase from './OPXProcessorBase'; 5 | 6 | export default class OPLProcessor extends OPXProcessorBase { 7 | constructor() { 8 | const emulator = new OPLEmulator(libnotesalad); 9 | const midiDriver = new OPL3MIDIDriver(libnotesalad, emulator._hChip); 10 | super(emulator, midiDriver); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /web/src/Worklet/Processors/OPMProcessor.js: -------------------------------------------------------------------------------- 1 | import libnotesalad from '../NoteSaladLibrary'; 2 | import OPMEmulator from '../Emulators/OPMEmulator'; 3 | import OPMMIDIDriver from '../MIDI/OPMMIDIDriver'; 4 | import OPXProcessorBase from './OPXProcessorBase'; 5 | 6 | export default class OPMProcessor extends OPXProcessorBase { 7 | constructor() { 8 | const emulator = new OPMEmulator(libnotesalad); 9 | const midiDriver = new OPMMIDIDriver(libnotesalad, emulator._hChip); 10 | super(emulator, midiDriver); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /web/src/Worklet/Processors/OPXProcessorBase.js: -------------------------------------------------------------------------------- 1 | export default class OPXProcessorBase extends AudioWorkletProcessor { 2 | constructor(emulator, midiDriver) { 3 | super(); 4 | 5 | this._midiDriver = midiDriver; 6 | this._emulator = emulator; 7 | this._sampleCount = 0; 8 | this._writeBuffer = []; 9 | this._writeBufferIndex = 0; 10 | 11 | this.port.onmessage = e => this._handleMessage(e); 12 | this._midiDriver.onReceiveMIDI = (...args) => 13 | this._receiveMIDI(...args); 14 | this._updateMIDITime(); 15 | } 16 | 17 | _handleMessage(e) { 18 | if (e.data.type === 'reset') { 19 | this._handleResetMessage(); 20 | } else if (e.data.type === 'write') { 21 | this._handleWriteMessage(e.data); 22 | } else if (e.data.type === 'midiWrite') { 23 | this._handleMIDIWriteMessage(e.data); 24 | } else if (e.data.type === 'close') { 25 | this._handleCloseMessage(); 26 | } 27 | } 28 | 29 | _handleResetMessage() { 30 | if (this._midiDriver) { 31 | this._midiDriver.reset(); 32 | } else { 33 | this._emulator.reset(); 34 | } 35 | } 36 | 37 | _handleWriteMessage(data) { 38 | this._sampleCount = 0; 39 | this._writeBuffer = data.buffer; 40 | this._writeBufferIndex = 0; 41 | } 42 | 43 | _handleMIDIWriteMessage(data) { 44 | const messages = data.messages; 45 | for (let msg of messages) { 46 | const { data } = msg; 47 | this._midiDriver.send(data); 48 | } 49 | } 50 | 51 | _handleCloseMessage() { 52 | if (this._midiDriver) { 53 | this._midiDriver.close(); 54 | this._midiDriver = null; 55 | } 56 | if (this._emulator) { 57 | this._emulator.close(); 58 | this._emulator = null; 59 | } 60 | } 61 | 62 | _peekEvent() { 63 | const buffer = this._writeBuffer; 64 | const index = this._writeBufferIndex; 65 | 66 | return index < buffer.length ? buffer[index] : null; 67 | } 68 | 69 | _getEvent() { 70 | const event = this._peekEvent(); 71 | if (event) { 72 | this._writeBufferIndex++; 73 | } 74 | return event; 75 | } 76 | 77 | _updateMIDITime() { 78 | const timeMS = Math.floor((this._sampleCount / sampleRate) * 1000); 79 | this._midiDriver.setTime(timeMS); 80 | } 81 | 82 | _receiveMIDI(msgData) { 83 | this.port.postMessage({ type: 'midiReceive', msgData }); 84 | } 85 | 86 | process(inputs, outputs, parameters) { 87 | if (!this._emulator) { 88 | return false; 89 | } 90 | 91 | const bufferSize = outputs[0][0].length; 92 | let offset = 0; 93 | 94 | if (this._midiDriver) { 95 | this._updateMIDITime(); 96 | this._midiDriver.update(); 97 | } 98 | 99 | while (offset < bufferSize) { 100 | let nextChunkSize = 0; 101 | 102 | do { 103 | const event = this._peekEvent(); 104 | if (event) { 105 | const eventSample = Math.round( 106 | (event.time / 1000) * sampleRate 107 | ); 108 | if (eventSample <= this._sampleCount) { 109 | this._getEvent(); 110 | this._emulator.write(event.reg, event.value); 111 | } else { 112 | nextChunkSize = Math.min( 113 | bufferSize - offset, 114 | eventSample - this._sampleCount 115 | ); 116 | break; 117 | } 118 | } else { 119 | nextChunkSize = bufferSize - offset; 120 | break; 121 | } 122 | } while (true); 123 | 124 | this._emulator.getSamples(offset, nextChunkSize, outputs[0]); 125 | offset += nextChunkSize; 126 | this._sampleCount += nextChunkSize; 127 | } 128 | 129 | return true; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /web/src/Worklet/index.js: -------------------------------------------------------------------------------- 1 | import OPLProcessor from './Processors/OPLProcessor'; 2 | import OPMProcessor from './Processors/OPMProcessor'; 3 | 4 | registerProcessor('opl-processor', OPLProcessor); 5 | registerProcessor('opm-processor', OPMProcessor); 6 | -------------------------------------------------------------------------------- /web/src/index.js: -------------------------------------------------------------------------------- 1 | import PARAMS_OPL from '../../resources/params/opl.json'; 2 | import PARAMS_OPM from '../../resources/params/opm.json'; 3 | import PARAMS_SD1 from '../../resources/params/sd1.json'; 4 | import PARAMS_UNIVERSAL from '../../resources/params/universal.json'; 5 | 6 | export const initAudioContext = (context, libPath = 'libnotesalad.js') => 7 | context.audioWorklet.addModule(libPath); 8 | 9 | class NodeWrapperBase { 10 | constructor(node) { 11 | this.node = node; 12 | this.onReceiveMIDI = null; 13 | this.node.port.onmessage = (...args) => this._receiveMessage(...args); 14 | } 15 | 16 | writeEvents(events) { 17 | this.node.port.postMessage({ type: 'write', buffer: events }); 18 | } 19 | 20 | writeMIDI(messages) { 21 | this.node.port.postMessage({ type: 'midiWrite', messages }); 22 | } 23 | 24 | close() { 25 | this.node.port.postMessage({ type: 'close' }); 26 | } 27 | 28 | _receiveMessage(e) { 29 | if (e.data && e.data.type === 'midiReceive') { 30 | if (this.onReceiveMIDI) { 31 | this.onReceiveMIDI(e.data.msgData); 32 | } 33 | } 34 | } 35 | } 36 | 37 | export class OPLNodeWrapper extends NodeWrapperBase { 38 | constructor(audioContext) { 39 | super( 40 | new AudioWorkletNode(audioContext, 'opl-processor', { 41 | numberOfInputs: 0, 42 | numberOfOutputs: 1, 43 | outputChannelCount: [2], 44 | }) 45 | ); 46 | } 47 | } 48 | 49 | export class OPMNodeWrapper extends NodeWrapperBase { 50 | constructor(audioContext) { 51 | super( 52 | new AudioWorkletNode(audioContext, 'opm-processor', { 53 | numberOfInputs: 0, 54 | numberOfOutputs: 1, 55 | outputChannelCount: [2], 56 | }) 57 | ); 58 | } 59 | } 60 | 61 | export { PARAMS_OPL, PARAMS_OPM, PARAMS_SD1, PARAMS_UNIVERSAL }; 62 | -------------------------------------------------------------------------------- /web/worklet.webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/Worklet/index.js', 4 | output: { 5 | filename: 'libnotesalad.js', 6 | }, 7 | performance: { 8 | maxEntrypointSize: 1073741824, 9 | maxAssetSize: 1073741824, 10 | }, 11 | }; 12 | --------------------------------------------------------------------------------