├── .github └── workflows │ ├── build.yml │ └── pr-comment.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── USBID.sh ├── build.sh ├── getsysex.sh ├── hwconfig ├── DT-DX.override ├── chrissy-mt32-pi-midi-hat.override ├── customize.sh ├── diyelectromusic-RPi400MIDIAudio.override ├── diyelectromusic-RpiMiniDexedHD44780.override ├── diyelectromusic-RpiMiniDexedSSD1306.override ├── diyelectromusic-RpiQuadDACMiniDexed.override ├── diyelectromusic-RpiV1MiniDexedIOBoard.override ├── dxeus_machina_eurorack.override ├── genxnoise_desktop_module.override ├── pirate_audio.override └── serdaco_mp32l.override ├── miditest.py ├── src ├── Makefile ├── Rules.mk ├── Synth_Dexed.mk ├── arm_float_to_q23.c ├── arm_float_to_q23.h ├── circle_stdlib_app.h ├── common.h ├── config.cpp ├── config.h ├── config.txt ├── dexedadapter.h ├── effect_compressor.cpp ├── effect_compressor.h ├── effect_mixer.hpp ├── effect_platervbstereo.cpp ├── effect_platervbstereo.h ├── kernel.cpp ├── kernel.h ├── main.cpp ├── midi.h ├── mididevice.cpp ├── mididevice.h ├── midikeyboard.cpp ├── midikeyboard.h ├── midipin.cpp ├── midipin.h ├── minidexed.cpp ├── minidexed.h ├── minidexed.ini ├── net │ ├── applemidi.cpp │ ├── applemidi.h │ ├── byteorder.h │ ├── ftpdaemon.cpp │ ├── ftpdaemon.h │ ├── ftpworker.cpp │ ├── ftpworker.h │ ├── mdnspublisher.cpp │ ├── mdnspublisher.h │ ├── udpmidi.cpp │ ├── udpmidi.h │ └── utility.h ├── patches │ └── WM8960.diff ├── pckeyboard.cpp ├── pckeyboard.h ├── performance.ini ├── performanceconfig.cpp ├── performanceconfig.h ├── perftimer.cpp ├── perftimer.h ├── serialmididevice.cpp ├── serialmididevice.h ├── sysexfileloader.cpp ├── sysexfileloader.h ├── udpmididevice.cpp ├── udpmididevice.h ├── uibuttons.cpp ├── uibuttons.h ├── uimenu.cpp ├── uimenu.h ├── usbminidexedmidigadget.h ├── userinterface.cpp ├── userinterface.h └── voices.c ├── submod.sh ├── syslogserver.py └── updater.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | env: 4 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | build64: 13 | name: Build 64-bit kernels 14 | runs-on: ubuntu-22.04 15 | outputs: 16 | artifact-path: ${{ steps.upload64.outputs.artifact-path }} 17 | git_info: ${{ steps.gitinfo.outputs.git_info }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Compute Git Info for Artifact Name 22 | id: gitinfo 23 | run: echo "::set-output name=git_info::$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" 24 | 25 | - name: Get specific commits of git submodules 26 | run: sh -ex ./submod.sh 27 | 28 | - name: Create sdcard directory 29 | run: mkdir -p ./sdcard/ 30 | 31 | - name: Put git hash in startup message 32 | run: | 33 | sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp 34 | 35 | # Install 64-bit toolchain (aarch64) 36 | - name: Install 64-bit toolchain 37 | run: | 38 | set -ex 39 | wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz 40 | tar xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz 41 | 42 | - name: Build for Raspberry Pi 5 (64-bit) 43 | run: | 44 | set -ex 45 | export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin):$PATH 46 | RPI=5 bash -ex build.sh 47 | cp ./src/kernel*.img ./sdcard/ 48 | 49 | - name: Build for Raspberry Pi 4 (64-bit) 50 | run: | 51 | set -ex 52 | export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin):$PATH 53 | RPI=4 bash -ex build.sh 54 | cp ./src/kernel*.img ./sdcard/ 55 | 56 | - name: Build for Raspberry Pi 3 (64-bit) 57 | run: | 58 | set -ex 59 | export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin):$PATH 60 | RPI=3 bash -ex build.sh 61 | cp ./src/kernel*.img ./sdcard/ 62 | 63 | - name: Prepare SD card content for 64-bit 64 | run: | 65 | set -ex 66 | export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin):$PATH 67 | # cd ./circle-stdlib/libs/circle/boot 68 | # make 69 | # make armstub64 70 | # cd - 71 | # cp -r ./circle-stdlib/libs/circle/boot/* sdcard 72 | cd ./sdcard 73 | wget https://github.com/probonopd/MiniDexed/releases/download/assets/boot.zip 74 | unzip boot.zip && rm boot.zip 75 | cd .. 76 | rm -rf sdcard/config*.txt sdcard/README sdcard/Makefile sdcard/armstub sdcard/COPYING.linux 77 | cp ./src/config.txt ./src/minidexed.ini ./src/performance.ini sdcard/ 78 | cp ./getsysex.sh sdcard/ 79 | echo "usbspeed=full" > sdcard/cmdline.txt 80 | # Performances 81 | git clone https://github.com/Banana71/Soundplantage --depth 1 82 | cp -r ./Soundplantage/performance ./Soundplantage/*.pdf ./sdcard/ 83 | # Hardware configuration 84 | cd hwconfig 85 | sh -ex ./customize.sh 86 | cd - 87 | mkdir -p ./sdcard/hardware/ 88 | cp -r ./hwconfig/minidexed_* ./sdcard/minidexed.ini ./sdcard/hardware/ 89 | # WLAN firmware 90 | # mkdir -p sdcard/firmware 91 | # cp circle-stdlib/libs/circle/addon/wlan/sample/hello_wlan/wpa_supplicant.conf sdcard/ 92 | # cd sdcard/firmware 93 | # make -f ../../circle-stdlib/libs/circle/addon/wlan/firmware/Makefile 94 | cd - 95 | 96 | - name: Upload 64-bit artifacts 97 | id: upload64 98 | uses: actions/upload-artifact@v4 99 | with: 100 | name: MiniDexed_${{ github.run_number }}_${{ steps.gitinfo.outputs.git_info }}_64bit 101 | path: sdcard/* 102 | 103 | build32: 104 | name: Build 32-bit kernels 105 | runs-on: ubuntu-22.04 106 | outputs: 107 | artifact-path: ${{ steps.upload32.outputs.artifact-path }} 108 | steps: 109 | - uses: actions/checkout@v2 110 | 111 | - name: Compute Git Info for Artifact Name 112 | run: echo "GIT_INFO=$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV 113 | 114 | - name: Get specific commits of git submodules 115 | run: sh -ex ./submod.sh 116 | 117 | - name: Create sdcard directory 118 | run: mkdir -p ./sdcard/ 119 | 120 | - name: Put git hash in startup message 121 | run: | 122 | sed -i "s/Loading.../${{ env.GIT_INFO }}/g" src/userinterface.cpp 123 | 124 | # Install 32-bit toolchain (arm-none-eabi) 125 | - name: Install 32-bit toolchain 126 | run: | 127 | set -ex 128 | wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz 129 | tar xf gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz 130 | 131 | - name: Build for Raspberry Pi 2 (32-bit) 132 | run: | 133 | set -ex 134 | export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-arm-none-eabi/bin):$PATH 135 | RPI=2 bash -ex build.sh 136 | cp ./src/kernel*.img ./sdcard/ 137 | 138 | - name: Build for Raspberry Pi 1 (32-bit) 139 | run: | 140 | set -ex 141 | export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-arm-none-eabi/bin):$PATH 142 | RPI=1 bash -ex build.sh 143 | cp ./src/kernel*.img ./sdcard/ 144 | 145 | - name: Upload 32-bit artifacts 146 | id: upload32 147 | uses: actions/upload-artifact@v4 148 | with: 149 | name: MiniDexed_${{ github.run_number }}_${{ env.GIT_INFO }}_32bit 150 | path: sdcard/* 151 | 152 | combine: 153 | name: Combine Artifacts 154 | runs-on: ubuntu-22.04 155 | needs: [ build64, build32 ] 156 | steps: 157 | - name: Download artifacts 158 | uses: actions/download-artifact@v4 159 | with: 160 | pattern: MiniDexed_* 161 | merge-multiple: true 162 | path: combined 163 | 164 | - name: Create combined ZIP file 165 | run: | 166 | cd combined 167 | zip -r ../MiniDexed_${{ github.run_number }}_${{ needs.build64.outputs.git_info }}.zip . 168 | cd .. 169 | 170 | - name: Upload to GitHub Releases (only when building from main branch) 171 | if: ${{ github.ref == 'refs/heads/main' }} 172 | run: | 173 | set -ex 174 | export UPLOADTOOL_ISPRERELEASE=true 175 | export UPLOADTOOL_PR_BODY="This is a continuous build. Feedback is appreciated." 176 | export UPLOADTOOL_BODY="This is a continuous build. Feedback is appreciated." 177 | wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh 178 | bash ./upload.sh ./MiniDexed*.zip 179 | -------------------------------------------------------------------------------- /.github/workflows/pr-comment.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/subsurface/subsurface/blob/master/.github/workflows/artifact-links.yml 2 | 3 | name: Add artifact links to pull request 4 | 5 | on: 6 | workflow_run: 7 | workflows: ["Build"] 8 | types: [completed] 9 | 10 | jobs: 11 | artifacts-url-comments: 12 | name: Add artifact links to PR and issues 13 | runs-on: ubuntu-22.04 14 | 15 | # Restrict permissions for the GITHUB_TOKEN, https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | actions: read 20 | 21 | steps: 22 | - name: Add artifact links to PR and issues 23 | if: github.event.workflow_run.event == 'pull_request' 24 | uses: tonyhallett/artifacts-url-comments@0965ff1a7ae03c5c1644d3c30f956effea4e05ef # v1.1.0 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | with: 28 | prefix: "Build for testing:" 29 | suffix: "Use at your own risk." 30 | format: name 31 | addTo: pull 32 | errorNoArtifacts: false 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | *.O 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | *.smod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | 35 | # Toolchain 36 | gcc-* 37 | 38 | # Build artifacts 39 | kernel* 40 | MiniDexed* 41 | sdcard 42 | *.zip 43 | *.img 44 | 45 | # Editor related files 46 | *.swp 47 | *.swo 48 | 49 | # git submodules 50 | CMSIS_5 51 | circle-stdlib 52 | Synth_Dexed 53 | 54 | # Build directories 55 | minidexed_* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "circle-stdlib"] 2 | path = circle-stdlib 3 | url = https://github.com/smuehlst/circle-stdlib 4 | [submodule "Synth_Dexed"] 5 | path = Synth_Dexed 6 | url = https://codeberg.org/dcoredump/Synth_Dexed.git 7 | [submodule "CMSIS_5"] 8 | path = CMSIS_5 9 | url = https://github.com/ARM-software/CMSIS_5 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniDexed ![Github Build Status](https://github.com/probonopd/MiniDexed/actions/workflows/build.yml/badge.svg) 2 | 3 | ![minidexed](https://user-images.githubusercontent.com/2480569/161813414-bb156a1c-efec-44c0-802a-8926412a08e0.jpg) 4 | 5 | MiniDexed is a FM synthesizer closely modeled on the famous DX7 by a well-known Japanese manufacturer running on a bare metal Raspberry Pi (without a Linux kernel or operating system). On Raspberry Pi 2 and larger, it can run 8 tone generators, not unlike the TX816/TX802 (8 DX7 instances without the keyboard in one box). [Featured by HACKADAY](https://hackaday.com/2022/04/19/bare-metal-gives-this-pi-some-classic-synths/), [Adafruit](https://blog.adafruit.com/2022/04/25/free-yamaha-dx7-synth-emulator-on-a-raspberry-pi/), [The MagPi magazine](https://magpi.raspberrypi.com/articles/mini-dexed) (Issue 142 June 2024, [PDF](https://magpi.raspberrypi.com/issues/142)) and [Synth Geekery](https://www.youtube.com/watch?v=TDSy5nnm0jA). 6 | 7 | ## Demo songs 8 | 9 | Listen to some examples made with MiniDexed by Banana71 [here](https://soundcloud.com/soundplantage/sets/minidexed2). 10 | 11 | ## Features 12 | 13 | - [x] Uses [Synth_Dexed](https://codeberg.org/dcoredump/Synth_Dexed) with [circle-stdlib](https://github.com/smuehlst/circle-stdlib) 14 | - [x] SD card contents can be downloaded from [GitHub Releases](../../releases) 15 | - [x] Runs on all Raspberry Pi models (except Pico); see below for details 16 | - [x] Produces sound on the headphone jack, HDMI display or [audio extractor](https://github.com/probonopd/MiniDexed/wiki/Hardware#hdmi-to-audio) (better), or a [dedicated DAC](https://github.com/probonopd/MiniDexed/wiki/Hardware#i2s-dac) (best) 17 | - [x] Supports multiple voices through Program Change and Bank Change LSB/MSB MIDI messages 18 | - [x] Loads voices from `.syx` files from SD card (e.g., using `getsysex.sh` or from [Dexed_cart_1.0.zip](http://hsjp.eu/downloads/Dexed/Dexed_cart_1.0.zip)) 19 | - [x] Menu structure on optional [HD44780 display](https://www.berrybase.de/sensoren-module/displays/alphanumerische-displays/alphanumerisches-lcd-16x2-gr-252-n/gelb) and rotary encoder 20 | - [x] Runs up to 8 Dexed instances simultaneously (like in a TX816) and mixes their output together 21 | - [x] Allows for each Dexed instance to be detuned and stereo shifted 22 | - [x] Allows to configure multiple Dexed instances through `performance.ini` files (e.g., [converted](https://github.com/BobanSpasic/MDX_Vault) from DX1, DX5, TX816, DX7II, TX802) 23 | - [x] Compressor effect 24 | - [x] Reverb effect 25 | - [x] Voices can be edited over MIDI, e.g., using the [synthmata](https://synthmata.github.io/volca-fm/) online editor (requires [additional hardware](https://github.com/probonopd/MiniDexed/wiki/Hardware#usb-midi-devices)) 26 | 27 | ## Introduction 28 | 29 | Video about this project by [Floyd Steinberg](https://www.youtube.com/watch?v=Z3t94ceMHJo): 30 | 31 | [![YouTube Video about MiniDexed (Floyd Steinberg)](https://i.ytimg.com/vi/Z3t94ceMHJo/sddefault.jpg)](https://www.youtube.com/watch?v=Z3t94ceMHJo) 32 | 33 | ## System Requirements 34 | 35 | - Raspberry Pi 1, 2, 3, 4, or 400. Raspberry Pi Zero and Zero 2 can be used but need HDMI or a supported i2s DAC for audio out. On Raspberry Pi 1 and on Raspberry Pi Zero there will be severely limited functionality (only one tone generator instead of 8) 36 | - Raspberry Pi 5 can be used but currently support is experimental: HDMI sound and USB Gadget mode are not available yet, and it is not clear if there are implications for cooling from running MiniDexed. Also, MiniDexed is currently not taking advantage of the higher processing power of the Raspberry Pi 5 yet. *Hence, you may consider using one of the less expensive, older Raspberry Pi boards for your first build.* 37 | - A [PCM5102A or PCM5122 based DAC](https://github.com/probonopd/MiniDexed/wiki/Hardware#i2s-dac), HDMI display or [audio extractor](https://github.com/probonopd/MiniDexed/wiki/Hardware#hdmi-to-audio) for good sound quality. If you don't have this, you can use the headphone jack on the Raspberry Pi but on anything but the Raspberry 4 the sound quality will be seriously limited 38 | - Optionally (but highly recommended), an [LCDC1602 Display](https://www.berrybase.de/en/sensors-modules/displays/alphanumeric-displays/alphanumerisches-lcd-16x2-gr-252-n/gelb) (with or without i2c "backpack" board) and a [KY-040 rotary encoder](https://www.berrybase.de/en/components/passive-components/potentiometer/rotary-encoder/drehregler/rotary-encoder-mit-breakoutboard-ohne-gewinde-und-mutter) 39 | 40 | ## Usage 41 | 42 | - In the case of Raspberry Pi 4, Update the firmware and bootloader to the latest version (not doing this may cause USB reliability issues) 43 | - Download from [GitHub Releases](../../releases) 44 | - Unzip 45 | - Put the files into the root directory of a FAT32 formatted partition on SD/microSD card (Note for small SD cards which are no longer sold: If less than 65525 clusters, you may need to format as FAT16.) 46 | - Put SD/microSD card into Raspberry Pi 1, 2, 3 or 4, or 400 (Zero and Zero 2 can be used but need HDMI or a supported i2c DAC for audio out) 47 | - Attach headphones to the headphone jack using `SoundDevice=pwm` in `minidexed.ini` (default) (poor audio quality) 48 | - Alternatively, attach a PCM5102A or PCM5122 based DAC and select i2c sound output using `SoundDevice=i2s` in `minidexed.ini` (best audio quality) 49 | - Alternatively, attach a HDMI display with sound and select HDMI sound output using `SoundDevice=hdmi` in `minidexed.ini` (this may introduce slight latency) 50 | - Attach a MIDI keyboard via USB (alternatively you can build a circuit that allows you to attach a "traditional" MIDI keyboard using a DIN connector, or use a DIN-MIDI-to-USB adapter) 51 | - If you are using a LCDC1602 with an i2c "backpack" board, then you need to set `LCDI2CAddress=0x27` (or another address your i2c "backpack" board is set to) in `minidexed.ini` 52 | - Boot 53 | - Start playing 54 | - If the system seems to become unresponsive after a few seconds, remove `usbspeed=full` from `cmdline.txt` and repeat ([details](https://github.com/probonopd/MiniDexed/issues/39)) 55 | - Optionally, put voices in `.syx` files onto the SD card (e.g., using `getsysex.sh`) 56 | - See the Wiki for [Menu](https://github.com/probonopd/MiniDexed/wiki/Menu) operation 57 | - For voice programming, use any DX series editor (using MIDI sysex), including Dexed 58 | - For library management, use the dedicated [MiniDexedLibrarian](https://github.com/BobanSpasic/MiniDexedLibrarian) software 59 | - If something is unclear or does not work, don't hesitate to [ask](https://github.com/probonopd/MiniDexed/discussions/)! 60 | 61 | ## Pinout 62 | 63 | All devices on Raspberry Pi GPIOs are **optional**. 64 | 65 | ![Raspberry Pi Pinout/GPIO Diagram](https://user-images.githubusercontent.com/2480569/166105580-da11481c-8fc7-4375-8ab1-3031ab5c6ad0.png) 66 | 67 | Please see the [wiki](https://github.com/probonopd/MiniDexed/wiki) for more information. 68 | 69 | ## Downloading 70 | 71 | Compiled versions are available on [GitHub Releases](../../releases). Just download and put on a FAT32 formatted SD card. 72 | 73 | ## Building 74 | 75 | Please see the [wiki](https://github.com/probonopd/MiniDexed/wiki/Development#building-locally) on how to compile the code yourself. 76 | 77 | ## Contributing 78 | 79 | This project lives from the contributions of skilled C++ developers, testers, writers, etc. Please see . 80 | 81 | ## Discussions 82 | 83 | We are happy to hear from you. Please join the discussions on . 84 | 85 | ## Documentation 86 | 87 | Project documentation is at . 88 | 89 | ## Acknowledgements 90 | 91 | This project stands on the shoulders of giants. Special thanks to: 92 | 93 | - [raphlinus](https://github.com/raphlinus) for the [MSFA](https://github.com/google/music-synthesizer-for-android) sound engine 94 | - [asb2m10](https://github.com/asb2m10/dexed) for the [Dexed](https://github.com/asb2m10/dexed) software 95 | - [dcoredump](https://github.com/dcoredump) for [Synth Dexed](https://codeberg.org/dcoredump/Synth_Dexed), a port of Dexed for embedded systems 96 | - [rsta2](https://github.com/rsta2) for [Circle](https://github.com/rsta2/circle), the library to run code on bare metal Raspberry Pi (without a Linux kernel or operating system) and for the bulk of the MiniDexed code 97 | - [smuehlst](https://github.com/smuehlst) for [circle-stdlib](https://github.com/smuehlst/circle-stdlib), a version with Standard C and C++ library support 98 | - [Banana71](https://github.com/Banana71) for the sound design of the [Soundplantage](https://github.com/Banana71/Soundplantage) performances shipped with MiniDexed 99 | - [BobanSpasic](https://github.com/BobanSpasic) for the [MiniDexedLibrarian](https://github.com/BobanSpasic/MiniDexedLibrarian) software, [MiniDexed performance converter](https://github.com/BobanSpasic/MDX_PerfConv) and [collection of performances for MiniDexed](https://github.com/BobanSpasic/MDX_Vault) 100 | - [diyelectromusic](https://github.com/diyelectromusic/) for many [contributions](https://github.com/probonopd/MiniDexed/commits?author=diyelectromusic) 101 | - [dwhinham/mt32-pi](https://github.com/dwhinham/mt32-pi) for creating networking support for Circle 102 | - [omersiar](https://github.com/omersiar) for porting networking support to MiniDexed 103 | - [soyersoyer](https://github.com/soyersoyer) for sound and other improvements, and for debugging 104 | 105 | ## Stargazers over time 106 | [![Stargazers over time](https://starchart.cc/probonopd/MiniDexed.svg?variant=adaptive)](https://starchart.cc/probonopd/MiniDexed) 107 | -------------------------------------------------------------------------------- /USBID.sh: -------------------------------------------------------------------------------- 1 | USB_VID=0x1209 2 | USB_DID=0xF043 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | if [ -z "${RPI}" ] ; then 7 | echo "\$RPI missing, exiting" 8 | exit 1 9 | fi 10 | 11 | if [ "${RPI}" -gt "2" ]; then 12 | export TOOLCHAIN_PREFIX="aarch64-none-elf-" 13 | else 14 | export TOOLCHAIN_PREFIX="arm-none-eabi-" 15 | fi 16 | 17 | # Define system options 18 | OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" 19 | if [ "${RPI}" -gt "1" ]; then 20 | OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" 21 | fi 22 | 23 | # For wireless access 24 | if [ "${RPI}" == "3" ]; then 25 | OPTIONS="${OPTIONS} -o USE_SDHOST" 26 | fi 27 | 28 | # USB Vendor and Device ID for use with USB Gadget Mode 29 | source USBID.sh 30 | if [ "${USB_VID}" ] ; then 31 | OPTIONS="${OPTIONS} -o USB_GADGET_VENDOR_ID=${USB_VID}" 32 | fi 33 | if [ "${USB_DID}" ] ; then 34 | OPTIONS="${OPTIONS} -o USB_GADGET_DEVICE_ID_BASE=${USB_DID}" 35 | fi 36 | 37 | # Build circle-stdlib library 38 | cd circle-stdlib/ 39 | make mrproper || true 40 | ./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" ${OPTIONS} -o KERNEL_MAX_SIZE=0x400000 41 | make -j 42 | 43 | # Build additional libraries 44 | cd libs/circle/addon/display/ 45 | make clean || true 46 | make -j 47 | 48 | cd ../wlan/ 49 | make clean || true 50 | make -j 51 | 52 | cd ../sensor/ 53 | make clean || true 54 | make -j 55 | cd ../Properties/ 56 | make clean || true 57 | make -j 58 | cd ../../../.. 59 | 60 | cd .. 61 | 62 | # Build MiniDexed 63 | cd src 64 | make clean 65 | echo "***** DEBUG *****" 66 | env 67 | rm -rf ./gcc-* || true 68 | grep -r 'aarch64-none-elf' . || true 69 | find . -type d -name 'aarch64-none-elf' || true 70 | make -j 71 | ls *.img 72 | cd .. 73 | -------------------------------------------------------------------------------- /getsysex.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Get voices from 4 | # https://yamahablackboxes.com/collection/yamaha-dx7-synthesizer/patches/ 5 | 6 | mkdir -p sysex/voice/ 7 | 8 | DIR="https://yamahablackboxes.com/patches/dx7/factory" 9 | 10 | # wget -c "${DIR}"/rom1a.syx -O sysex/voice/000000_rom1a.syx 11 | # wget -c "${DIR}"/rom1b.syx -O sysex/voice/000001_rom1b.syx 12 | # wget -c "${DIR}"/rom2a.syx -O sysex/voice/000002_rom2a.syx 13 | # wget -c "${DIR}"/rom2b.syx -O sysex/voice/000003_rom2b.syx 14 | wget -c "${DIR}"/rom3a.syx -O sysex/voice/000001_rom3a.syx 15 | wget -c "${DIR}"/rom3b.syx -O sysex/voice/000002_rom3b.syx 16 | wget -c "${DIR}"/rom4a.syx -O sysex/voice/000003_rom4a.syx 17 | wget -c "${DIR}"/rom4b.syx -O sysex/voice/000004_rom4b.syx 18 | 19 | DIR="https://yamahablackboxes.com/patches/dx7/vrc" 20 | 21 | wget -c "${DIR}"/vrc101b.syx -O sysex/voice/000005_vrc101b.syx 22 | wget -c "${DIR}"/vrc102a.syx -O sysex/voice/000006_vrc102a.syx 23 | wget -c "${DIR}"/vrc102b.syx -O sysex/voice/000007_vrc102b.syx 24 | wget -c "${DIR}"/vrc103a.syx -O sysex/voice/000008_vrc103a.syx 25 | wget -c "${DIR}"/vrc103b.syx -O sysex/voice/000009_vrc103b.syx 26 | wget -c "${DIR}"/vrc104a.syx -O sysex/voice/000010_vrc104a.syx 27 | wget -c "${DIR}"/vrc104b.syx -O sysex/voice/000011_vrc104b.syx 28 | wget -c "${DIR}"/vrc105a.syx -O sysex/voice/000012_vrc105a.syx 29 | wget -c "${DIR}"/vrc105b.syx -O sysex/voice/000013_vrc105b.syx 30 | wget -c "${DIR}"/vrc106a.syx -O sysex/voice/000014_vrc106a.syx 31 | wget -c "${DIR}"/vrc106b.syx -O sysex/voice/000015_vrc106b.syx 32 | wget -c "${DIR}"/vrc107a.syx -O sysex/voice/000016_vrc107a.syx 33 | wget -c "${DIR}"/vrc107b.syx -O sysex/voice/000017_vrc107b.syx 34 | wget -c "${DIR}"/vrc108a.syx -O sysex/voice/000018_vrc108a.syx 35 | wget -c "${DIR}"/vrc108b.syx -O sysex/voice/000019_vrc108b.syx 36 | wget -c "${DIR}"/vrc109a.syx -O sysex/voice/000020_vrc109a.syx 37 | wget -c "${DIR}"/vrc109b.syx -O sysex/voice/000021_vrc109b.syx 38 | wget -c "${DIR}"/vrc110a.syx -O sysex/voice/000022_vrc110a.syx 39 | wget -c "${DIR}"/vrc110b.syx -O sysex/voice/000023_vrc110b.syx 40 | wget -c "${DIR}"/vrc111a.syx -O sysex/voice/000024_vrc111a.syx 41 | wget -c "${DIR}"/vrc111b.syx -O sysex/voice/000025_vrc111b.syx 42 | wget -c "${DIR}"/vrc112a.syx -O sysex/voice/000026_vrc112a.syx 43 | wget -c "${DIR}"/vrc112b.syx -O sysex/voice/000027_vrc112b.syx 44 | -------------------------------------------------------------------------------- /hwconfig/DT-DX.override: -------------------------------------------------------------------------------- 1 | # DTronics DT-DX 2 | # https://www.dtronics.nl/dt-dx 3 | 4 | SoundDevice=i2s 5 | SampleRate=48000 6 | ChunkSize=256 7 | DACI2CAddress=0x0 8 | ChannelsSwapped=1 9 | 10 | LCDEnabled=1 11 | LCDPinEnable=17 12 | LCDPinRegisterSelect=27 13 | LCDPinReadWrite=16 14 | LCDPinData4=22 15 | LCDPinData5=23 16 | LCDPinData6=24 17 | LCDPinData7=25 18 | LCDI2CAddress=0x00 19 | 20 | SSD1306LCDI2CAddress=0x00 21 | SSD1306LCDWidth=128 22 | SSD1306LCDHeight=32 23 | SSD1306LCDRotate=0 24 | SSD1306LCDMirror=0 25 | 26 | LCDColumns=16 27 | LCDRows=2 28 | 29 | ButtonPinPrev=0 30 | ButtonActionPrev=0 31 | ButtonPinNext=0 32 | ButtonActionNext=0 33 | ButtonPinBack=26 34 | ButtonActionBack=longpress 35 | ButtonPinSelect=26 36 | ButtonActionSelect=click 37 | ButtonPinHome=26 38 | ButtonActionHome=doubleclick 39 | ButtonPinShortcut=26 40 | 41 | DoubleClickTimeout=400 42 | LongPressTimeout=400 43 | 44 | EncoderEnabled=1 45 | EncoderPinClock=6 46 | EncoderPinData=5 47 | -------------------------------------------------------------------------------- /hwconfig/chrissy-mt32-pi-midi-hat.override: -------------------------------------------------------------------------------- 1 | # mt32-Pi-Midi-Hat by Chrissy version 1.7.2 2 | # https://github.com/chris-jh/mt32-pi-midi-hat 3 | 4 | SoundDevice=i2s 5 | SampleRate=48000 6 | DACI2CAddress=0 7 | ChannelsSwapped=0 8 | 9 | MIDIBaudRate=31250 10 | MIDIThru=umidi1,ttyS1 11 | 12 | SSD1306LCDI2CAddress=0x3c 13 | SSD1306LCDWidth=128 14 | SSD1306LCDHeight=64 15 | SSD1306LCDRotate=0 16 | SSD1306LCDMirror=0 17 | 18 | LCDColumns=20 19 | LCDRows=4 20 | 21 | ButtonPinPrev=22 22 | ButtonActionPrev=click 23 | ButtonPinNext=23 24 | ButtonActionNext=click 25 | ButtonPinBack=27 26 | ButtonActionBack=click 27 | ButtonPinSelect=17 28 | ButtonActionSelect=click 29 | ButtonPinHome=17 30 | ButtonActionHome=longpress 31 | ButtonPinShortcut=0 32 | -------------------------------------------------------------------------------- /hwconfig/customize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script creates a set of ini files from the *.override files 4 | # to provide customized configurations for well-known hardware 5 | 6 | # Find all files named *.override, and run the following on each of them 7 | for file in *.override; do 8 | # Copy the file minidexed.ini to the name of this file but with .ini extension instead 9 | name_of_ini_file=minidexed_$(echo "$file" | sed 's/\.override$/.ini/') 10 | cp ../src/minidexed.ini "$name_of_ini_file" 11 | 12 | # Change the values in the ini file, leaving the rest of the file unchanged 13 | while IFS='=' read -r key value; do 14 | # Skip empty lines and comments 15 | if [ -z "$key" ] || [ "${key#\#}" != "$key" ]; then 16 | continue 17 | fi 18 | value=$(echo "$value" | tr -d '\r') 19 | if [ -n "$value" ]; then 20 | sed -i "s/^$key=.*/$key=$value/" "$name_of_ini_file" 21 | fi 22 | done < "$file" 23 | 24 | # Process the last line of the override file separately, if it doesn't end with a newline 25 | if [ -n "$key" ]; then 26 | value=$(echo "$value" | tr -d '\r') 27 | if [ -n "$value" ]; then 28 | sed -i "s/^$key=.*/$key=$value/" "$name_of_ini_file" 29 | fi 30 | fi 31 | 32 | # Configure genxnoise_desktop_module as USB gadget (as intended by the manufacturer) 33 | case "$file" in 34 | *genxnoise_desktop_module*) 35 | echo "" >> "$name_of_ini_file" 36 | echo "# CAUTION: To prevent hardware damage, DO NOT use the port labeled 'PWR'" >> "$name_of_ini_file" 37 | echo "# (the microUSB port near the edge of the device) when USBGadget is set to 1!" >> "$name_of_ini_file" 38 | echo "# You need to disable USBGadget if you would like to use that port!" >> "$name_of_ini_file" 39 | echo "# See https://github.com/probonopd/MiniDexed/wiki/Hardware#usb-gadget-mode for more information" >> "$name_of_ini_file" 40 | echo "USBGadget=1" >> "$name_of_ini_file" 41 | ;; 42 | esac 43 | 44 | echo "Created $name_of_ini_file" 45 | done 46 | -------------------------------------------------------------------------------- /hwconfig/diyelectromusic-RPi400MIDIAudio.override: -------------------------------------------------------------------------------- 1 | # diyelectromusic Raspberry Pi 400 MIDI and Audio Module (RPi400MIDIAudio) 2 | # https://diyelectromusic.wordpress.com/2023/12/18/rpi-400-midi-and-audio-pcb-design/ 3 | # https://diyelectromusic.wordpress.com/2023/12/18/rpi-400-midi-and-audio-pcb-build-guide/ 4 | 5 | SoundDevice=i2s 6 | 7 | LCDEnabled=1 8 | SSD1306LCDI2CAddress=0x3C 9 | SSD1306LCDWidth=128 10 | SSD1306LCDHeight=32 11 | SSD1306LCDRotate=0 12 | SSD1306LCDMirror=0 13 | 14 | LCDColumns=20 15 | LCDRows=2 16 | 17 | ButtonPinPrev=0 18 | ButtonActionPrev= 19 | ButtonPinNext=0 20 | ButtonActionNext= 21 | ButtonPinBack=5 22 | ButtonActionBack=click 23 | ButtonPinSelect=11 24 | ButtonActionSelect=click 25 | ButtonPinHome=6 26 | ButtonActionHome=click 27 | ButtonPinShortcut=11 28 | 29 | EncoderEnabled=1 30 | EncoderPinClock=10 31 | EncoderPinData=9 32 | -------------------------------------------------------------------------------- /hwconfig/diyelectromusic-RpiMiniDexedHD44780.override: -------------------------------------------------------------------------------- 1 | # diyelectromusic Raspberry Pi MiniDexed IO Module (HD44780 Version) (RpiMiniDexedHD44780) 2 | # https://github.com/diyelectromusic/sdemp_pcbs/tree/main/RpiMiniDexedHD44780 3 | # https://diyelectromusic.wordpress.com/2022/08/16/minidexed-raspberry-pi-io-board-part-3/ 4 | 5 | SoundDevice=i2s 6 | 7 | LCDEnabled=1 8 | LCDPinEnable=10 9 | LCDPinRegisterSelect=9 10 | LCDPinReadWrite=0 11 | LCDPinData4=22 12 | LCDPinData5=27 13 | LCDPinData6=17 14 | LCDPinData7=4 15 | LCDI2CAddress=0x00 16 | SSD1306LCDI2CAddress=0 17 | LCDColumns=16 18 | LCDRows=2 19 | 20 | EncoderEnabled=1 21 | EncoderPinClock=24 22 | EncoderPinData=23 23 | For the two buttons, and the rotary encoder switch itself: 24 | 25 | ButtonPinBack=25 26 | ButtonActionBack=longpress 27 | ButtonPinSelect=25 28 | ButtonActionSelect=click 29 | ButtonPinHome=25 30 | ButtonActionHome=doubleclick 31 | ButtonPinShortcut=25 32 | -------------------------------------------------------------------------------- /hwconfig/diyelectromusic-RpiMiniDexedSSD1306.override: -------------------------------------------------------------------------------- 1 | # diyelectromusic Raspberry Pi MiniDexed IO Module (SSD1306 Version) (RpiMiniDexedSSD1306) 2 | # https://github.com/diyelectromusic/sdemp_pcbs/tree/main/RpiMiniDexedSSD1306 3 | # https://diyelectromusic.com/2022/08/16/minidexed-raspberry-pi-io-board-part-2/ 4 | 5 | SoundDevice=i2s 6 | 7 | LCDEnabled=1 8 | SSD1306LCDI2CAddress=0x3C 9 | SSD1306LCDWidth=128 10 | SSD1306LCDHeight=32 11 | SSD1306LCDRotate=0 12 | SSD1306LCDMirror=0 13 | LCDColumns=20 14 | LCDRows=2 15 | 16 | ButtonPinBack=5 17 | ButtonActionBack=click 18 | ButtonPinSelect=11 19 | ButtonActionSelect=click 20 | ButtonPinHome=6 21 | ButtonActionHome=click 22 | ButtonPinShortcut=11 23 | 24 | EncoderEnabled=1 25 | EncoderPinClock=9 26 | EncoderPinData=10 27 | -------------------------------------------------------------------------------- /hwconfig/diyelectromusic-RpiQuadDACMiniDexed.override: -------------------------------------------------------------------------------- 1 | # diyelectromusic MiniDexed Quad DAC (RpiQuadDACMiniDexed) 2 | # https://github.com/diyelectromusic/sdemp_pcbs/tree/main/RpiQuadDACMiniDexed 3 | # https://diyelectromusic.com/2024/06/09/minidexed-quad-dac-pcb-design/ 4 | # https://diyelectromusic.com/2024/06/09/minidexed-quad-dac-pcb-build-guide/ 5 | 6 | SoundDevice=i2s 7 | QuadDAC8Chan=1 8 | 9 | LCDEnabled=1 10 | SSD1306LCDI2CAddress=0x3C 11 | SSD1306LCDWidth=128 12 | SSD1306LCDHeight=32 13 | SSD1306LCDRotate=1 14 | SSD1306LCDMirror=0 15 | 16 | LCDColumns=20 17 | LCDRows=2 18 | 19 | ButtonPinPrev=0 20 | ButtonActionPrev= 21 | ButtonPinNext=0 22 | ButtonActionNext= 23 | ButtonPinBack=5 24 | ButtonActionBack=click 25 | ButtonPinSelect=11 26 | ButtonActionSelect=click 27 | ButtonPinHome=6 28 | ButtonActionHome=click 29 | 30 | EncoderEnabled=1 31 | EncoderPinClock=10 32 | EncoderPinData=9 33 | -------------------------------------------------------------------------------- /hwconfig/diyelectromusic-RpiV1MiniDexedIOBoard.override: -------------------------------------------------------------------------------- 1 | # diyelectromusic MiniDexed Raspberry Pi V1 IO Board (RpiV1MiniDexedIOBoard) 2 | # https://github.com/diyelectromusic/sdemp_pcbs/tree/main/RpiV1MiniDexedIOBoard 3 | # https://diyelectromusic.com/2023/02/28/minidexed-raspberry-pi-v1-io-board-part-2/ 4 | 5 | SoundDevice=i2s 6 | 7 | LCDEnabled=1 8 | SSD1306LCDI2CAddress=0x3C 9 | SSD1306LCDWidth=128 10 | SSD1306LCDHeight=32 11 | SSD1306LCDRotate=0 12 | SSD1306LCDMirror=0 13 | LCDColumns=20 14 | LCDRows=2 15 | 16 | ButtonPinPrev=0 17 | ButtonActionPrev= 18 | ButtonPinNext=0 19 | ButtonActionNext= 20 | ButtonPinBack=22 21 | ButtonActionBack=click 22 | ButtonPinSelect=11 23 | ButtonActionSelect=click 24 | ButtonPinHome=27 25 | ButtonActionHome=click 26 | ButtonPinShortcut=11 27 | 28 | EncoderEnabled=1 29 | EncoderPinClock=9 30 | EncoderPinData=10 31 | -------------------------------------------------------------------------------- /hwconfig/dxeus_machina_eurorack.override: -------------------------------------------------------------------------------- 1 | # genXnoise dXeus machina 2 | # https://www.genxnoise.com/product-page/dxeus-machina-minidexed-eurorack-format 3 | 4 | SoundDevice=i2s 5 | SampleRate=48000 6 | DACI2CAddress=0 7 | ChannelsSwapped=0 8 | 9 | MIDIThru=ttyS1,ttyS1 10 | 11 | LCDEnabled=1 12 | LCDPinEnable=17 13 | LCDPinRegisterSelect=4 14 | LCDPinReadWrite=0 15 | LCDPinData4=22 16 | LCDPinData5=23 17 | LCDPinData6=24 18 | LCDPinData7=25 19 | LCDI2CAddress=0x00 20 | 21 | SSD1306LCDI2CAddress=0x3c 22 | SSD1306LCDWidth=128 23 | SSD1306LCDHeight=32 24 | SSD1306LCDRotate=0 25 | SSD1306LCDMirror=0 26 | 27 | LCDColumns=20 28 | LCDRows=2 29 | 30 | ButtonPinPrev=0 31 | ButtonActionPrev= 32 | ButtonPinNext=0 33 | ButtonActionNext= 34 | ButtonPinBack=11 35 | ButtonActionBack=longpress 36 | ButtonPinSelect=11 37 | ButtonActionSelect=click 38 | ButtonPinHome=11 39 | ButtonActionHome=doubleclick 40 | ButtonPinShortcut=11 41 | 42 | EncoderEnabled=1 43 | EncoderPinClock=9 44 | EncoderPinData=10 45 | -------------------------------------------------------------------------------- /hwconfig/genxnoise_desktop_module.override: -------------------------------------------------------------------------------- 1 | # genXnoise desktop module 2 | # https://www.genxnoise.com/product-page/minidexed-midi-tone-module 3 | 4 | SoundDevice=i2s 5 | DACI2CAddress=0 6 | ChannelsSwapped=0 7 | 8 | LCDEnabled=1 9 | LCDPinEnable=17 10 | LCDPinRegisterSelect=4 11 | LCDPinReadWrite=0 12 | LCDPinData4=22 13 | LCDPinData5=23 14 | LCDPinData6=24 15 | LCDPinData7=25 16 | LCDI2CAddress=0x00 17 | 18 | SSD1306LCDI2CAddress=0x3c 19 | SSD1306LCDWidth=128 20 | SSD1306LCDHeight=32 21 | SSD1306LCDRotate=0 22 | SSD1306LCDMirror=0 23 | 24 | LCDColumns=20 25 | LCDRows=2 26 | 27 | EncoderEnabled=1 28 | EncoderPinClock=10 29 | EncoderPinData=9 30 | 31 | USBGadget=1 32 | -------------------------------------------------------------------------------- /hwconfig/pirate_audio.override: -------------------------------------------------------------------------------- 1 | # Pimoroni Pirate Audio (screen, buttons and audio output) 2 | # https://shop.pimoroni.com/search?q=pirate%20audio 3 | 4 | SoundDevice=i2s 5 | LCDEnabled=1 6 | 7 | SPIBus=0 8 | ST7789Enabled=1 9 | ST7789Data=9 10 | ST7789Select=1 11 | ST7789Reset= 12 | ST7789Backlight=13 13 | ST7789Width=240 14 | ST7789Height=240 15 | ST7789Rotation=90 16 | 17 | LCDColumns=15 18 | LCDRows=2 19 | 20 | ButtonPinPrev=5 21 | ButtonActionPrev=click 22 | ButtonPinNext=16 23 | ButtonActionNext=click 24 | ButtonPinBack=24 25 | ButtonActionBack=click 26 | ButtonPinSelect=6 27 | ButtonActionSelect=click 28 | ButtonPinHome=24 29 | ButtonActionHome=doubleclick 30 | ButtonPinShortcut=0 31 | 32 | EncoderEnabled=0 33 | -------------------------------------------------------------------------------- /hwconfig/serdaco_mp32l.override: -------------------------------------------------------------------------------- 1 | # Serdaco MP32L 2 | # https://www.serdashop.com/MP32L 3 | 4 | SoundDevice=i2s 5 | SampleRate=48000 6 | DACI2CAddress=0 7 | ChannelsSwapped=0 8 | 9 | MIDIBaudRate=31250 10 | MIDIThru=umidi1,ttyS1 11 | 12 | SSD1306LCDI2CAddress=0x3c 13 | SSD1306LCDWidth=128 14 | SSD1306LCDHeight=32 15 | SSD1306LCDRotate=1 16 | SSD1306LCDMirror=0 17 | 18 | LCDColumns=20 19 | LCDRows=2 20 | 21 | ButtonPinPrev=17 22 | ButtonActionPrev=click 23 | ButtonPinNext=27 24 | ButtonActionNext=click 25 | ButtonPinBack=22 26 | ButtonActionBack=click 27 | ButtonPinSelect=23 28 | ButtonActionSelect=click 29 | ButtonPinHome=23 30 | ButtonActionHome=longpress 31 | ButtonPinShortcut=0 32 | 33 | EncoderEnabled=0 34 | -------------------------------------------------------------------------------- /miditest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Send MIDI data to a MIDI device using the `python-rtmidi` library. 5 | 6 | try: 7 | import rtmidi 8 | except ImportError: 9 | print("Please install the python-rtmidi library: pip install python-rtmidi") 10 | exit(1) 11 | import time 12 | import sys 13 | import argparse 14 | import os 15 | import signal 16 | import atexit 17 | 18 | def signal_handler(sig, frame): 19 | print("\nExiting...") 20 | sys.exit(0) 21 | signal.signal(signal.SIGINT, signal_handler) 22 | 23 | def cleanup_all_notes_off(): 24 | try: 25 | midiout = rtmidi.MidiOut() 26 | ports = midiout.get_ports() 27 | if ports: 28 | midiout.open_port(0) 29 | all_notes_off(midiout, verbose=True) 30 | time.sleep(0.5) 31 | midiout.close_port() 32 | except Exception as e: 33 | print(f"Cleanup error: {e}") 34 | 35 | atexit.register(cleanup_all_notes_off) 36 | 37 | # Ask the user which MIDI port to use 38 | def get_midi_port(midiout): 39 | print("Available MIDI output ports:") 40 | ports = midiout.get_ports() 41 | for i, port in enumerate(ports): 42 | print(f"{i}: {port}") 43 | while True: 44 | try: 45 | choice = int(input("Select a port by number: ")) 46 | if 0 <= choice < len(ports): 47 | return choice 48 | else: 49 | print("Invalid choice. Please select a valid port number.") 50 | except ValueError: 51 | print("Invalid input. Please enter a number.") 52 | 53 | def play_chord(midiout, notes, velocity=100, channel=0, delay=0.25, verbose=True): 54 | print("Playing chord:", notes) 55 | for note in notes: 56 | midiout.send_message([0x90 | channel, note, velocity]) 57 | time.sleep(delay) 58 | time.sleep(0.5) 59 | for note in notes: 60 | midiout.send_message([0x80 | channel, note, 0]) 61 | 62 | def send_sysex(midiout, msg, verbose=True, label=None): 63 | midiout.send_message(msg) 64 | if verbose: 65 | if label: 66 | print(f"Sent: {label} {msg}") 67 | else: 68 | print(f"Sent: {msg}") 69 | 70 | def all_notes_off(midiout, verbose=True): 71 | # Send All Notes Off (CC 123) and individual Note Off for all notes 0-127 on all 16 MIDI channels 72 | for channel in range(16): 73 | midiout.send_message([0xB0 | channel, 120, 0]) 74 | if verbose: 75 | print(f"Sent: All Sound Off (CC120) on channel {channel+1}") 76 | 77 | def send_modwheel(midiout, value, channel=0, verbose=True): 78 | # Modulation wheel is CC 1 79 | midiout.send_message([0xB0 | channel, 1, value]) 80 | if verbose: 81 | print(f"Sent: ModWheel CC1 value {value} on channel {channel+1}") 82 | 83 | if __name__ == "__main__": 84 | parser = argparse.ArgumentParser(description="Send MIDI messages to a device.") 85 | parser.add_argument("-p", "--port", type=str, help="MIDI output port name or number") 86 | # Always verbose, so remove the verbose switch and set verbose to True 87 | args = parser.parse_args() 88 | verbose = True 89 | 90 | midiout = rtmidi.MidiOut() 91 | ports = midiout.get_ports() 92 | 93 | if args.port is not None: 94 | # Allow port to be specified by number or name 95 | try: 96 | port_index = int(args.port) 97 | except ValueError: 98 | # Try to find by name 99 | port_index = next((i for i, p in enumerate(ports) if args.port in p), None) 100 | if port_index is None: 101 | print(f"Port '{args.port}' not found.") 102 | sys.exit(1) 103 | else: 104 | port_index = get_midi_port(midiout) 105 | 106 | if verbose: 107 | print(f"Using MIDI output port: {ports[port_index]}") 108 | 109 | try: 110 | midiout.open_port(port_index) 111 | chord_notes = [60, 64, 67] 112 | # Send all notes off before starting the test program 113 | all_notes_off(midiout, verbose=verbose) 114 | while True: 115 | send_sysex(midiout, [0xF0, 0x43, 0x10, 0x04, 0x05, 0x00, 0xF7], verbose, "Portamento Time 0") 116 | play_chord(midiout, chord_notes, verbose=verbose) 117 | send_sysex(midiout, [0xF0, 0x43, 0x10, 0x04, 0x05, 0x02, 0xF7], verbose, "Portamento Time 2") 118 | time.sleep(1) 119 | 120 | # Test Detune 121 | detune_msgs = [ 122 | ([0xF0, 0x43, 0x10, 0x04, 0x40, 0x2F, 0xF7], "Detune TG1"), 123 | ([0xF0, 0x43, 0x11, 0x04, 0x40, 0x37, 0xF7], "Detune TG2"), 124 | ([0xF0, 0x43, 0x12, 0x04, 0x40, 0x40, 0xF7], "Detune TG3"), 125 | ([0xF0, 0x43, 0x13, 0x04, 0x40, 0x48, 0xF7], "Detune TG4"), 126 | ] 127 | for msg, label in detune_msgs: 128 | send_sysex(midiout, msg, verbose, label) 129 | play_chord(midiout, chord_notes, verbose=verbose) 130 | for ch in range(0x10, 0x14): 131 | send_sysex(midiout, [0xF0, 0x43, ch, 0x04, 0x40, 0x40, 0xF7], verbose, f"Detune TG{ch-0x0F} reset") 132 | time.sleep(1) 133 | 134 | # Test Poly and Mono modes 135 | send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x02, 0x00, 0xF7], verbose, "TG4 Poly") 136 | play_chord(midiout, chord_notes, verbose=verbose) 137 | send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x02, 0x01, 0xF7], verbose, "TG4 Mono") 138 | time.sleep(1) 139 | 140 | # Test ModWheel sensitivity 141 | send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x09, 0x01, 0xF7], verbose, "TG4 ModWheel Sensitivity ON") 142 | send_modwheel(midiout, 127, channel=0, verbose=verbose) 143 | play_chord(midiout, chord_notes, verbose=verbose) 144 | send_modwheel(midiout, 0, channel=0, verbose=verbose) 145 | send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x09, 0x00, 0xF7], verbose, "TG4 ModWheel Sensitivity OFF") 146 | send_modwheel(midiout, 127, channel=0, verbose=verbose) 147 | play_chord(midiout, chord_notes, verbose=verbose) 148 | send_modwheel(midiout, 0, channel=0, verbose=verbose) 149 | time.sleep(1) 150 | 151 | # Disable all Tone Generators except TG1 152 | send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x08, 0x00, 0xF7], verbose, "Disable TG2, TG3, TG4") 153 | play_chord(midiout, chord_notes, verbose=verbose) 154 | # Enable all Tone Generators 155 | send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x08, 0x01, 0xF7], verbose, "Enable TG2, TG3, TG4") 156 | play_chord(midiout, chord_notes, verbose=verbose) 157 | time.sleep(1) 158 | 159 | # Send all notes off after each test program loop 160 | all_notes_off(midiout, verbose=verbose) 161 | except Exception as e: 162 | print(f"Error: {e}") 163 | sys.exit(1) -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # 4 | 5 | CIRCLE_STDLIB_DIR = ../circle-stdlib 6 | SYNTH_DEXED_DIR = ../Synth_Dexed/src 7 | CMSIS_DIR = ../CMSIS_5/CMSIS 8 | 9 | OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ 10 | mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ 11 | sysexfileloader.o performanceconfig.o perftimer.o \ 12 | effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ 13 | arm_float_to_q23.o \ 14 | net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o udpmididevice.o 15 | 16 | OPTIMIZE = -O3 17 | 18 | include ./Synth_Dexed.mk 19 | include ./Rules.mk 20 | 21 | # Clean target 22 | .PHONY: clean 23 | 24 | clean: 25 | @echo "Cleaning up..." 26 | rm -f $(OBJS) *.o *.d *~ core 27 | -------------------------------------------------------------------------------- /src/Rules.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Rules.mk 3 | # 4 | 5 | -include $(CIRCLE_STDLIB_DIR)/Config.mk 6 | 7 | NEWLIBDIR ?= $(CIRCLE_STDLIB_DIR)/install/$(NEWLIB_ARCH) 8 | CIRCLEHOME ?= $(CIRCLE_STDLIB_DIR)/libs/circle 9 | 10 | include $(CIRCLEHOME)/Rules.mk 11 | 12 | INCLUDE += \ 13 | -I $(CIRCLE_STDLIB_DIR)/include \ 14 | -I $(NEWLIBDIR)/include 15 | 16 | LIBS += \ 17 | $(NEWLIBDIR)/lib/libm.a \ 18 | $(NEWLIBDIR)/lib/libc.a \ 19 | $(NEWLIBDIR)/lib/libcirclenewlib.a \ 20 | $(CIRCLEHOME)/addon/display/libdisplay.a \ 21 | $(CIRCLEHOME)/addon/sensor/libsensor.a \ 22 | $(CIRCLEHOME)/addon/Properties/libproperties.a \ 23 | $(CIRCLEHOME)/addon/SDCard/libsdcard.a \ 24 | $(CIRCLEHOME)/lib/usb/libusb.a \ 25 | $(CIRCLEHOME)/lib/usb/gadget/libusbgadget.a \ 26 | $(CIRCLEHOME)/lib/input/libinput.a \ 27 | $(CIRCLEHOME)/lib/sound/libsound.a \ 28 | $(CIRCLEHOME)/addon/fatfs/libfatfs.a \ 29 | $(CIRCLEHOME)/lib/fs/libfs.a \ 30 | $(CIRCLEHOME)/lib/sched/libsched.a \ 31 | $(CIRCLEHOME)/lib/libcircle.a \ 32 | $(CIRCLEHOME)/addon/wlan/hostap/wpa_supplicant/libwpa_supplicant.a \ 33 | $(CIRCLEHOME)/addon/wlan/libwlan.a \ 34 | $(CIRCLEHOME)/lib/net/libnet.a 35 | 36 | -include $(DEPS) 37 | -------------------------------------------------------------------------------- /src/Synth_Dexed.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Synth_Dexed.mk 3 | # 4 | 5 | CMSIS_CORE_INCLUDE_DIR = $(CMSIS_DIR)/Core/Include 6 | CMSIS_DSP_INCLUDE_DIR = $(CMSIS_DIR)/DSP/Include 7 | CMSIS_DSP_PRIVATE_INCLUDE_DIR = $(CMSIS_DIR)/DSP/PrivateInclude 8 | CMSIS_DSP_COMPUTELIB_INCLUDE_DIR = $(CMSIS_DIR)/DSP/ComputeLibrary/Include 9 | CMSIS_DSP_SOURCE_DIR = $(CMSIS_DIR)/DSP/Source 10 | CMSIS_DSP_COMPUTELIB_SRC_DIR = $(CMSIS_DIR)/DSP/ComputeLibrary/Source 11 | 12 | OBJS += \ 13 | $(SYNTH_DEXED_DIR)/PluginFx.o \ 14 | $(SYNTH_DEXED_DIR)/dexed.o \ 15 | $(SYNTH_DEXED_DIR)/dx7note.o \ 16 | $(SYNTH_DEXED_DIR)/env.o \ 17 | $(SYNTH_DEXED_DIR)/exp2.o \ 18 | $(SYNTH_DEXED_DIR)/fm_core.o \ 19 | $(SYNTH_DEXED_DIR)/fm_op_kernel.o \ 20 | $(SYNTH_DEXED_DIR)/freqlut.o \ 21 | $(SYNTH_DEXED_DIR)/lfo.o \ 22 | $(SYNTH_DEXED_DIR)/pitchenv.o \ 23 | $(SYNTH_DEXED_DIR)/porta.o \ 24 | $(SYNTH_DEXED_DIR)/sin.o \ 25 | $(SYNTH_DEXED_DIR)/EngineMkI.o\ 26 | $(SYNTH_DEXED_DIR)/EngineOpl.o\ 27 | $(SYNTH_DEXED_DIR)/EngineMsfa.o\ 28 | $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/SupportFunctions.o \ 29 | $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/BasicMathFunctions.o \ 30 | $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/FastMathFunctions.o \ 31 | $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/FilteringFunctions.o \ 32 | $(CMSIS_DSP_SOURCE_DIR)/CommonTables/CommonTables.o \ 33 | $(CMSIS_DSP_COMPUTELIB_SRC_DIR)/arm_cl_tables.o 34 | 35 | INCLUDE += -I $(SYNTH_DEXED_DIR) 36 | INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR) 37 | INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR) 38 | INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR) 39 | INCLUDE += -I $(CMSIS_DSP_COMPUTELIB_INCLUDE_DIR) 40 | 41 | DEFINE += -DUSE_FX 42 | 43 | ifeq ($(RPI), $(filter $(RPI), 3 4 5)) 44 | DEFINE += -DARM_MATH_NEON 45 | DEFINE += -DARM_MATH_NEON_EXPERIMENTAL 46 | DEFINE += -DHAVE_NEON 47 | endif 48 | 49 | EXTRACLEAN = $(SYNTH_DEXED_DIR)/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/CommonTables/*.[od] $(CMSIS_DSP_COMPUTELIB_SRC_DIR)/*.[od] 50 | -------------------------------------------------------------------------------- /src/arm_float_to_q23.c: -------------------------------------------------------------------------------- 1 | #include "arm_float_to_q23.h" 2 | 3 | #if defined(ARM_MATH_NEON_EXPERIMENTAL) 4 | void arm_float_to_q23(const float32_t * pSrc, q23_t * pDst, uint32_t blockSize) 5 | { 6 | const float32_t *pIn = pSrc; /* Src pointer */ 7 | uint32_t blkCnt; /* loop counter */ 8 | 9 | float32x4_t inV; 10 | 11 | int32x4_t cvt; 12 | 13 | blkCnt = blockSize >> 2U; 14 | 15 | /* Compute 4 outputs at a time. 16 | ** a second loop below computes the remaining 1 to 3 samples. */ 17 | while (blkCnt > 0U) 18 | { 19 | /* C = A * 8388608 */ 20 | /* Convert from float to q23 and then store the results in the destination buffer */ 21 | inV = vld1q_f32(pIn); 22 | 23 | cvt = vcvtq_n_s32_f32(inV, 23); 24 | 25 | /* saturate */ 26 | cvt = vminq_s32(cvt, vdupq_n_s32(0x007fffff)); 27 | cvt = vmaxq_s32(cvt, vdupq_n_s32(0xff800000)); 28 | 29 | vst1q_s32(pDst, cvt); 30 | pDst += 4; 31 | pIn += 4; 32 | 33 | /* Decrement the loop counter */ 34 | blkCnt--; 35 | } 36 | 37 | /* If the blockSize is not a multiple of 4, compute any remaining output samples here. 38 | ** No loop unrolling is used. */ 39 | blkCnt = blockSize & 3; 40 | 41 | while (blkCnt > 0U) 42 | { 43 | /* C = A * 8388608 */ 44 | /* Convert from float to q23 and then store the results in the destination buffer */ 45 | *pDst++ = (q23_t) __SSAT((q31_t) (*pIn++ * 8388608.0f), 24); 46 | 47 | /* Decrement the loop counter */ 48 | blkCnt--; 49 | } 50 | } 51 | #else 52 | void arm_float_to_q23(const float32_t * pSrc, q23_t * pDst, uint32_t blockSize) 53 | { 54 | uint32_t blkCnt; /* Loop counter */ 55 | const float32_t *pIn = pSrc; /* Source pointer */ 56 | 57 | /* Loop unrolling: Compute 4 outputs at a time */ 58 | blkCnt = blockSize >> 2U; 59 | 60 | while (blkCnt > 0U) 61 | { 62 | /* C = A * 8388608 */ 63 | /* convert from float to Q23 and store result in destination buffer */ 64 | 65 | *pDst++ = (q23_t) __SSAT((q31_t) (*pIn++ * 8388608.0f), 24); 66 | *pDst++ = (q23_t) __SSAT((q31_t) (*pIn++ * 8388608.0f), 24); 67 | *pDst++ = (q23_t) __SSAT((q31_t) (*pIn++ * 8388608.0f), 24); 68 | *pDst++ = (q23_t) __SSAT((q31_t) (*pIn++ * 8388608.0f), 24); 69 | 70 | /* Decrement loop counter */ 71 | blkCnt--; 72 | } 73 | 74 | /* Loop unrolling: Compute remaining outputs */ 75 | blkCnt = blockSize % 0x4U; 76 | 77 | while (blkCnt > 0U) 78 | { 79 | /* C = A * 8388608 */ 80 | /* Convert from float to q23 and then store the results in the destination buffer */ 81 | *pDst++ = (q23_t) __SSAT((q31_t) (*pIn++ * 8388608.0f), 24); 82 | 83 | /* Decrement loop counter */ 84 | blkCnt--; 85 | } 86 | 87 | } 88 | #endif /* #if defined(ARM_MATH_NEON_EXPERIMENTAL) */ 89 | -------------------------------------------------------------------------------- /src/arm_float_to_q23.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "arm_math_types.h" 4 | 5 | typedef int32_t q23_t; 6 | 7 | #ifdef __cplusplus 8 | extern "C" 9 | { 10 | #endif 11 | 12 | /** 13 | * @brief Converts the elements of the floating-point vector to Q23 vector. 14 | * @param[in] pSrc points to the floating-point input vector 15 | * @param[out] pDst points to the Q23 output vector 16 | * @param[in] blockSize length of the input vector 17 | */ 18 | void arm_float_to_q23(const float32_t * pSrc, q23_t * pDst, uint32_t blockSize); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /src/circle_stdlib_app.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This file has been taken from the circle-stdlib project: 3 | * https://github.com/smuehlst/circle-stdlib 4 | * 5 | * Convenience classes that package different levels 6 | * of functionality of Circle applications. 7 | * 8 | * Derive the kernel class of the application from one of 9 | * the CStdlibApp* classes and implement at least the 10 | * Run () method. Extend the Initalize () and Cleanup () 11 | * methods if necessary. 12 | */ 13 | #ifndef _circle_stdlib_app_h 14 | #define _circle_stdlib_app_h 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | 38 | /** 39 | * Basic Circle Stdlib application that supports GPIO access. 40 | */ 41 | class CStdlibApp 42 | { 43 | public: 44 | enum TShutdownMode 45 | { 46 | ShutdownNone, 47 | ShutdownHalt, 48 | ShutdownReboot 49 | }; 50 | 51 | CStdlibApp (const char *kernel) : 52 | FromKernel (kernel) 53 | { 54 | } 55 | 56 | virtual ~CStdlibApp (void) 57 | { 58 | } 59 | 60 | virtual bool Initialize (void) 61 | { 62 | return mInterrupt.Initialize (); 63 | } 64 | 65 | virtual void Cleanup (void) 66 | { 67 | } 68 | 69 | virtual TShutdownMode Run (void) = 0; 70 | 71 | const char *GetKernelName(void) const 72 | { 73 | return FromKernel; 74 | } 75 | 76 | protected: 77 | CActLED mActLED; 78 | CKernelOptions mOptions; 79 | CDeviceNameService mDeviceNameService; 80 | CNullDevice mNullDevice; 81 | CExceptionHandler mExceptionHandler; 82 | CInterruptSystem mInterrupt; 83 | 84 | private: 85 | char const *FromKernel; 86 | }; 87 | 88 | /** 89 | * Stdlib application that adds screen support 90 | * to the basic CStdlibApp features. 91 | */ 92 | class CStdlibAppScreen : public CStdlibApp 93 | { 94 | public: 95 | CStdlibAppScreen(const char *kernel) 96 | : CStdlibApp (kernel), 97 | mScreenUnbuffered (mOptions.GetWidth (), mOptions.GetHeight ()), 98 | mScreen (&mScreenUnbuffered), 99 | mbScreenAvailable (false), 100 | mTimer (&mInterrupt), 101 | mLogger (mOptions.GetLogLevel (), &mTimer) 102 | { 103 | } 104 | 105 | virtual bool Initialize (void) 106 | { 107 | if (!CStdlibApp::Initialize ()) 108 | { 109 | return false; 110 | } 111 | 112 | mbScreenAvailable = mScreenUnbuffered.Initialize (); 113 | #if 0 114 | if (!mSerial.Initialize (115200)) 115 | { 116 | return false; 117 | } 118 | #endif 119 | CDevice *pTarget = 120 | mDeviceNameService.GetDevice (mOptions.GetLogDevice (), false); 121 | if (pTarget == 0) 122 | { 123 | pTarget = &mScreen; 124 | } 125 | 126 | if (!mLogger.Initialize (pTarget)) 127 | { 128 | return false; 129 | } 130 | 131 | return mTimer.Initialize (); 132 | } 133 | 134 | protected: 135 | CScreenDevice mScreenUnbuffered; 136 | //CSerialDevice mSerial; 137 | CWriteBufferDevice mScreen; 138 | bool mbScreenAvailable; 139 | CTimer mTimer; 140 | CLogger mLogger; 141 | }; 142 | 143 | /** 144 | * Stdlib application that adds stdio support 145 | * to the CStdlibAppScreen functionality. 146 | */ 147 | class CStdlibAppStdio: public CStdlibAppScreen 148 | { 149 | private: 150 | char const *mpPartitionName; 151 | 152 | public: 153 | // TODO transform to constexpr 154 | // constexpr char static DefaultPartition[] = "emmc1-1"; 155 | #define CSTDLIBAPP_LEGACY_DEFAULT_PARTITION "emmc1-1" 156 | #define CSTDLIBAPP_DEFAULT_PARTITION "SD:" 157 | 158 | CStdlibAppStdio (const char *kernel, 159 | const char *pPartitionName = CSTDLIBAPP_DEFAULT_PARTITION) 160 | : CStdlibAppScreen (kernel), 161 | mpPartitionName (pPartitionName), 162 | mEMMC (&mInterrupt, &mTimer, &mActLED), 163 | #if !defined(__aarch64__) || !defined(LEAVE_QEMU_ON_HALT) 164 | //mConsole (&mScreen, TRUE) 165 | mConsole (&mNullDevice, &mScreen) 166 | #else 167 | mConsole (&mScreen) 168 | #endif 169 | { 170 | } 171 | 172 | virtual bool Initialize (void) 173 | { 174 | if (!CStdlibAppScreen::Initialize ()) 175 | { 176 | return false; 177 | } 178 | 179 | if (!mEMMC.Initialize ()) 180 | { 181 | return false; 182 | } 183 | 184 | char const *partitionName = mpPartitionName; 185 | 186 | // Recognize the old default partion name 187 | if (strcmp(partitionName, CSTDLIBAPP_LEGACY_DEFAULT_PARTITION) == 0) 188 | { 189 | partitionName = CSTDLIBAPP_DEFAULT_PARTITION; 190 | } 191 | 192 | if (f_mount (&mFileSystem, partitionName, 1) != FR_OK) 193 | { 194 | mLogger.Write (GetKernelName (), LogError, 195 | "Cannot mount partition: %s", partitionName); 196 | 197 | return false; 198 | } 199 | 200 | if (!mConsole.Initialize ()) 201 | { 202 | return false; 203 | } 204 | 205 | // Initialize newlib stdio with a reference to Circle's console 206 | // (Remove mFileSystem as a parameter to mirror change in circle-stdlib's 207 | // commit "Remove obsolete FATFS-related code", dated Dec 2022) 208 | CGlueStdioInit (mConsole); 209 | 210 | mLogger.Write (GetKernelName (), LogNotice, "Compile time: " __DATE__ " " __TIME__); 211 | 212 | return true; 213 | } 214 | 215 | virtual void Cleanup (void) 216 | { 217 | f_mount(0, "", 0); 218 | 219 | CStdlibAppScreen::Cleanup (); 220 | } 221 | 222 | protected: 223 | CEMMCDevice mEMMC; 224 | FATFS mFileSystem; 225 | CConsole mConsole; 226 | }; 227 | 228 | /** 229 | * Stdlib application that adds network functionality 230 | * to the CStdlibAppStdio features. 231 | */ 232 | class CStdlibAppNetwork: public CStdlibAppStdio 233 | { 234 | public: 235 | #define CSTDLIBAPP_WLAN_FIRMWARE_PATH CSTDLIBAPP_DEFAULT_PARTITION "/firmware/" 236 | #define CSTDLIBAPP_WLAN_CONFIG_FILE CSTDLIBAPP_DEFAULT_PARTITION "/wpa_supplicant.conf" 237 | 238 | CStdlibAppNetwork (const char *kernel, 239 | const char *pPartitionName = CSTDLIBAPP_DEFAULT_PARTITION, 240 | const u8 *pIPAddress = 0, // use DHCP if pIPAddress == 0 241 | const u8 *pNetMask = 0, 242 | const u8 *pDefaultGateway = 0, 243 | const u8 *pDNSServer = 0, 244 | TNetDeviceType DeviceType = NetDeviceTypeEthernet) 245 | : CStdlibAppStdio(kernel, pPartitionName), 246 | mDeviceType (DeviceType), 247 | mWLAN (CSTDLIBAPP_WLAN_FIRMWARE_PATH), 248 | mNet(pIPAddress, pNetMask, pDefaultGateway, pDNSServer, DEFAULT_HOSTNAME, DeviceType), 249 | mWPASupplicant (CSTDLIBAPP_WLAN_CONFIG_FILE) 250 | { 251 | } 252 | 253 | virtual bool Initialize (bool const bWaitForActivate = true) 254 | { 255 | if (!CStdlibAppStdio::Initialize ()) 256 | { 257 | return false; 258 | } 259 | 260 | if (mDeviceType == NetDeviceTypeWLAN) 261 | { 262 | if (!mWLAN.Initialize ()) 263 | { 264 | return false; 265 | } 266 | } 267 | 268 | if (!mNet.Initialize (false)) 269 | { 270 | return false; 271 | } 272 | 273 | if (mDeviceType == NetDeviceTypeWLAN) 274 | { 275 | if (!mWPASupplicant.Initialize ()) 276 | { 277 | return false; 278 | } 279 | } 280 | 281 | while (bWaitForActivate && !mNet.IsRunning ()) 282 | { 283 | mScheduler.Yield (); 284 | } 285 | 286 | return true; 287 | } 288 | 289 | protected: 290 | CScheduler mScheduler; 291 | TNetDeviceType mDeviceType; 292 | CBcm4343Device mWLAN; 293 | CNetSubSystem mNet; 294 | CWPASupplicant mWPASupplicant; 295 | }; 296 | #endif 297 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _common_h 3 | #define _common_h 4 | 5 | inline long maplong(long x, long in_min, long in_max, long out_min, long out_max) { 6 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 7 | } 8 | 9 | inline float32_t mapfloat(float32_t val, float32_t in_min, float32_t in_max, float32_t out_min, float32_t out_max) 10 | { 11 | return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 12 | } 13 | 14 | inline float32_t mapfloat(int val, int in_min, int in_max, float32_t out_min, float32_t out_max) 15 | { 16 | return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 17 | } 18 | 19 | #define constrain(amt, low, high) ({ \ 20 | __typeof__(amt) _amt = (amt); \ 21 | __typeof__(low) _low = (low); \ 22 | __typeof__(high) _high = (high); \ 23 | (_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \ 24 | }) 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/config.txt: -------------------------------------------------------------------------------- 1 | # 2 | # https://www.raspberrypi.com/documentation/computers/config_txt.html 3 | # 4 | 5 | [all] 6 | boot_delay=0 7 | disable_splash=1 8 | force_eeprom_read=0 9 | gpu_mem=16 10 | disable_overscan=0 11 | 12 | # 13 | # Use 64-bit for RPi 3, 4, 400, 5 and Zero 2, and 32-bit for all other models 14 | # 15 | 16 | [pi3] 17 | arm_64bit=1 18 | 19 | [pi4] 20 | arm_64bit=1 21 | armstub=armstub8-rpi4.bin 22 | kernel=kernel8-rpi4.img 23 | 24 | # Zero 2 W 25 | [pi02] 26 | arm_64bit=1 27 | 28 | [pi5] 29 | arm_64bit=1 30 | kernel=kernel_2712.img 31 | -------------------------------------------------------------------------------- /src/dexedadapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // dexedadapter.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #ifndef _dexedadapter_h 22 | #define _dexedadapter_h 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define DEXED_OP_ENABLE (DEXED_OP_OSC_DETUNE + 1) 29 | 30 | // Some Dexed methods require to be guarded from being interrupted 31 | // by other Dexed calls. This is done herein. 32 | 33 | class CDexedAdapter : public Dexed 34 | { 35 | public: 36 | CDexedAdapter (uint8_t maxnotes, int rate) 37 | : Dexed (maxnotes, rate) 38 | { 39 | } 40 | 41 | void loadVoiceParameters (uint8_t* data) 42 | { 43 | m_SpinLock.Acquire (); 44 | Dexed::loadVoiceParameters (data); 45 | m_SpinLock.Release (); 46 | } 47 | 48 | void keyup (int16_t pitch) 49 | { 50 | m_SpinLock.Acquire (); 51 | Dexed::keyup (pitch); 52 | m_SpinLock.Release (); 53 | } 54 | 55 | void keydown (int16_t pitch, uint8_t velo) 56 | { 57 | m_SpinLock.Acquire (); 58 | Dexed::keydown (pitch, velo); 59 | m_SpinLock.Release (); 60 | } 61 | 62 | void getSamples (float32_t* buffer, uint16_t n_samples) 63 | { 64 | m_SpinLock.Acquire (); 65 | Dexed::getSamples (buffer, n_samples); 66 | m_SpinLock.Release (); 67 | } 68 | 69 | void ControllersRefresh (void) 70 | { 71 | m_SpinLock.Acquire (); 72 | Dexed::ControllersRefresh (); 73 | m_SpinLock.Release (); 74 | } 75 | 76 | void setSustain (bool sustain) 77 | { 78 | m_SpinLock.Acquire (); 79 | Dexed::setSustain (sustain); 80 | m_SpinLock.Release (); 81 | } 82 | 83 | private: 84 | CSpinLock m_SpinLock; 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/effect_compressor.h: -------------------------------------------------------------------------------- 1 | /* From https://github.com/chipaudette/OpenAudio_ArduinoLibrary */ 2 | 3 | /* 4 | AudioEffectCompressor 5 | 6 | Created: Chip Audette, Dec 2016 - Jan 2017 7 | Purpose; Apply dynamic range compression to the audio stream. 8 | Assumes floating-point data. 9 | 10 | This processes a single stream fo audio data (ie, it is mono) 11 | 12 | MIT License. use at your own risk. 13 | */ 14 | 15 | #ifndef _COMPRESSOR_H 16 | #define _COMPRESSOR_H 17 | 18 | #include //ARM DSP extensions. https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html 19 | #include "synth.h" 20 | 21 | class Compressor 22 | { 23 | public: 24 | //constructor 25 | Compressor(const float32_t sample_rate_Hz); 26 | 27 | void doCompression(float32_t *audio_block, uint16_t len); 28 | void setDefaultValues(const float32_t sample_rate_Hz); 29 | void setPreGain(float32_t g); 30 | void setPreGain_dB(float32_t gain_dB); 31 | void setCompressionRatio(float32_t cr); 32 | void setAttack_sec(float32_t a, float32_t fs_Hz); 33 | void setRelease_sec(float32_t r, float32_t fs_Hz); 34 | void setLevelTimeConst_sec(float32_t t_sec, float32_t fs_Hz); 35 | void setThresh_dBFS(float32_t val); 36 | void enableHPFilter(boolean flag); 37 | float32_t getPreGain_dB(void); 38 | float32_t getAttack_sec(void); 39 | float32_t getRelease_sec(void); 40 | float32_t getLevelTimeConst_sec(void); 41 | float32_t getThresh_dBFS(void); 42 | float32_t getCompressionRatio(void); 43 | float32_t getCurrentLevel_dBFS(void); 44 | float32_t getCurrentGain_dB(void); 45 | 46 | protected: 47 | void calcAudioLevel_dB(float32_t *wav_block, float32_t *level_dB_block, uint16_t len); 48 | void calcGain(float32_t *audio_level_dB_block, float32_t *gain_block,uint16_t len); 49 | void calcInstantaneousTargetGain(float32_t *audio_level_dB_block, float32_t *inst_targ_gain_dB_block, uint16_t len); 50 | void calcSmoothedGain_dB(float32_t *inst_targ_gain_dB_block, float32_t *gain_dB_block, uint16_t len); 51 | void resetStates(void); 52 | void setHPFilterCoeff_N2IIR_Matlab(float32_t b[], float32_t a[]); 53 | 54 | //state-related variables 55 | float32_t *inputQueueArray_f32[1]; //memory pointer for the input to this module 56 | float32_t prev_level_lp_pow = 1.0; 57 | float32_t prev_gain_dB = 0.0; //last gain^2 used 58 | 59 | //HP filter state-related variables 60 | arm_biquad_casd_df1_inst_f32 hp_filt_struct; 61 | static const uint8_t hp_nstages = 1; 62 | float32_t hp_coeff[5 * hp_nstages] = {1.0, 0.0, 0.0, 0.0, 0.0}; //no filtering. actual filter coeff set later 63 | float32_t hp_state[4 * hp_nstages]; 64 | void setHPFilterCoeff(void); 65 | //private parameters related to gain calculation 66 | float32_t attack_const, release_const, level_lp_const; //used in calcGain(). set by setAttack_sec() and setRelease_sec(); 67 | float32_t comp_ratio_const, thresh_pow_FS_wCR; //used in calcGain(); set in updateThresholdAndCompRatioConstants() 68 | void updateThresholdAndCompRatioConstants(void); 69 | //settings 70 | float32_t attack_sec, release_sec, level_lp_sec; 71 | float32_t thresh_dBFS = 0.0; //threshold for compression, relative to digital full scale 72 | float32_t thresh_pow_FS = 1.0f; //same as above, but not in dB 73 | void setThreshPow(float32_t t_pow); 74 | float32_t comp_ratio = 1.0; //compression ratio 75 | float32_t pre_gain = -1.0; //gain to apply before the compression. negative value disables 76 | boolean use_HP_prefilter; 77 | }; 78 | 79 | // Accelerate the powf(10.0,x) function 80 | static float32_t pow10f(float32_t x); 81 | // Accelerate the log10f(x) function? 82 | static float32_t log10f_approx(float32_t x); 83 | static float32_t log2f_approx(float32_t X); 84 | #endif 85 | -------------------------------------------------------------------------------- /src/effect_mixer.hpp: -------------------------------------------------------------------------------- 1 | // Taken from https://github.com/manicken/Audio/tree/templateMixer 2 | // Adapted for MiniDexed by Holger Wirtz 3 | 4 | #ifndef effect_mixer_h_ 5 | #define effect_mixer_h_ 6 | 7 | #include 8 | #include 9 | #include "arm_math.h" 10 | 11 | #define UNITY_GAIN 1.0f 12 | #define MAX_GAIN 1.0f 13 | #define MIN_GAIN 0.0f 14 | #define UNITY_PANORAMA 1.0f 15 | #define MAX_PANORAMA 1.0f 16 | #define MIN_PANORAMA 0.0f 17 | 18 | template class AudioMixer 19 | { 20 | public: 21 | AudioMixer(uint16_t len) 22 | { 23 | buffer_length=len; 24 | for (uint8_t i=0; i= NN) return; 50 | 51 | if (gain > MAX_GAIN) 52 | gain = MAX_GAIN; 53 | else if (gain < MIN_GAIN) 54 | gain = MIN_GAIN; 55 | multiplier[channel] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2 56 | } 57 | 58 | void gain(float32_t gain) 59 | { 60 | for (uint8_t i = 0; i < NN; i++) 61 | { 62 | if (gain > MAX_GAIN) 63 | gain = MAX_GAIN; 64 | else if (gain < MIN_GAIN) 65 | gain = MIN_GAIN; 66 | multiplier[i] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2 67 | } 68 | } 69 | 70 | void getMix(float32_t* buffer) 71 | { 72 | assert(buffer); 73 | assert(sumbufL); 74 | arm_copy_f32(sumbufL, buffer, buffer_length); 75 | 76 | if(sumbufL) 77 | arm_fill_f32(0.0f, sumbufL, buffer_length); 78 | } 79 | 80 | protected: 81 | float32_t multiplier[NN]; 82 | float32_t* sumbufL; 83 | uint16_t buffer_length; 84 | }; 85 | 86 | template class AudioStereoMixer : public AudioMixer 87 | { 88 | public: 89 | AudioStereoMixer(uint16_t len) : AudioMixer(len) 90 | { 91 | for (uint8_t i=0; i= NN) return; 109 | 110 | if (pan > MAX_PANORAMA) 111 | pan = MAX_PANORAMA; 112 | else if (pan < MIN_PANORAMA) 113 | pan = MIN_PANORAMA; 114 | 115 | // From: https://stackoverflow.com/questions/67062207/how-to-pan-audio-sample-data-naturally 116 | panorama[channel][0]=arm_sin_f32(mapfloat(pan, MIN_PANORAMA, MAX_PANORAMA, 0.0, M_PI/2.0)); 117 | panorama[channel][1]=arm_cos_f32(mapfloat(pan, MIN_PANORAMA, MAX_PANORAMA, 0.0, M_PI/2.0)); 118 | } 119 | 120 | void doAddMix(uint8_t channel, float32_t* in) 121 | { 122 | float32_t tmp[buffer_length]; 123 | 124 | assert(in); 125 | 126 | // left 127 | arm_scale_f32(in, panorama[channel][0], tmp, buffer_length); 128 | if(multiplier[channel]!=UNITY_GAIN) 129 | arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length); 130 | arm_add_f32(sumbufL, tmp, sumbufL, buffer_length); 131 | // right 132 | arm_scale_f32(in, panorama[channel][1], tmp, buffer_length); 133 | if(multiplier[channel]!=UNITY_GAIN) 134 | arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length); 135 | arm_add_f32(sumbufR, tmp, sumbufR, buffer_length); 136 | } 137 | 138 | void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR) 139 | { 140 | float32_t tmp[buffer_length]; 141 | 142 | assert(inL); 143 | assert(inR); 144 | 145 | // left 146 | if(multiplier[channel]!=UNITY_GAIN) 147 | arm_scale_f32(inL,multiplier[channel],tmp,buffer_length); 148 | arm_add_f32(sumbufL, tmp, sumbufL, buffer_length); 149 | // right 150 | if(multiplier[channel]!=UNITY_GAIN) 151 | arm_scale_f32(inR,multiplier[channel],tmp,buffer_length); 152 | arm_add_f32(sumbufR, tmp, sumbufR, buffer_length); 153 | } 154 | 155 | void getMix(float32_t* bufferL, float32_t* bufferR) 156 | { 157 | assert(bufferR); 158 | assert(bufferL); 159 | assert(sumbufL); 160 | assert(sumbufR); 161 | 162 | arm_copy_f32 (sumbufL, bufferL, buffer_length); 163 | arm_copy_f32 (sumbufR, bufferR, buffer_length); 164 | 165 | if(sumbufL) 166 | arm_fill_f32(0.0f, sumbufL, buffer_length); 167 | if(sumbufR) 168 | arm_fill_f32(0.0f, sumbufR, buffer_length); 169 | } 170 | 171 | protected: 172 | using AudioMixer::sumbufL; 173 | using AudioMixer::multiplier; 174 | using AudioMixer::buffer_length; 175 | float32_t panorama[NN][2]; 176 | float32_t* sumbufR; 177 | }; 178 | 179 | #endif 180 | -------------------------------------------------------------------------------- /src/effect_platervbstereo.h: -------------------------------------------------------------------------------- 1 | /* Stereo plate reverb for Teensy 4 2 | * 3 | * Adapted for use in MiniDexed (Holger Wirtz ) 4 | * 5 | * Author: Piotr Zapart 6 | * www.hexefx.com 7 | * 8 | * Copyright (c) 2020 by Piotr Zapart 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | /*** 30 | * Algorithm based on plate reverbs developed for SpinSemi FV-1 DSP chip 31 | * 32 | * Allpass + modulated delay line based lush plate reverb 33 | * 34 | * Input parameters are float in range 0.0 to 1.0: 35 | * 36 | * size - reverb time 37 | * hidamp - hi frequency loss in the reverb tail 38 | * lodamp - low frequency loss in the reverb tail 39 | * lowpass - output/master lowpass filter, useful for darkening the reverb sound 40 | * diffusion - lower settings will make the reverb tail more "echoey", optimal value 0.65 41 | * 42 | */ 43 | 44 | #ifndef _EFFECT_PLATERVBSTEREO_H 45 | #define _EFFECT_PLATERVBSTEREO_H 46 | 47 | #include 48 | #include 49 | #include "common.h" 50 | 51 | /*** 52 | * Loop delay modulation: comment/uncomment to switch sin/cos 53 | * modulation for the 1st or 2nd tap, 3rd tap is always modulated 54 | * more modulation means more chorus type sounding reverb tail 55 | */ 56 | //#define TAP1_MODULATED 57 | #define TAP2_MODULATED 58 | 59 | class AudioEffectPlateReverb 60 | { 61 | public: 62 | AudioEffectPlateReverb(float32_t samplerate); 63 | void doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR,uint16_t len); 64 | 65 | void size(float n) 66 | { 67 | n = constrain(n, 0.0f, 1.0f); 68 | n = mapfloat(n, 0.0f, 1.0f, 0.2f, rv_time_k_max); 69 | float32_t attn = mapfloat(n, 0.0f, rv_time_k_max, 0.5f, 0.25f); 70 | rv_time_k = n; 71 | input_attn = attn; 72 | } 73 | 74 | void hidamp(float n) 75 | { 76 | n = constrain(n, 0.0f, 1.0f); 77 | lp_hidamp_k = 1.0f - n; 78 | } 79 | 80 | void lodamp(float n) 81 | { 82 | n = constrain(n, 0.0f, 1.0f); 83 | lp_lodamp_k = -n; 84 | rv_time_scaler = 1.0f - n * 0.12f; // limit the max reverb time, otherwise it will clip 85 | } 86 | 87 | void lowpass(float n) 88 | { 89 | n = constrain(n, 0.0f, 1.0f); 90 | n = mapfloat(n*n*n, 0.0f, 1.0f, 0.05f, 1.0f); 91 | master_lowpass_f = n; 92 | } 93 | 94 | void diffusion(float n) 95 | { 96 | n = constrain(n, 0.0f, 1.0f); 97 | n = mapfloat(n, 0.0f, 1.0f, 0.005f, 0.65f); 98 | in_allp_k = n; 99 | loop_allp_k = n; 100 | } 101 | 102 | void level(float n) 103 | { 104 | reverb_level = constrain(n, 0.0f, 1.0f); 105 | } 106 | 107 | float32_t get_size(void) {return rv_time_k;} 108 | bool get_bypass(void) {return bypass;} 109 | void set_bypass(bool state) {bypass = state;}; 110 | void tgl_bypass(void) {bypass ^=1;} 111 | float32_t get_level(void) {return reverb_level;} 112 | private: 113 | bool bypass = false; 114 | float32_t reverb_level; 115 | float32_t input_attn; 116 | 117 | float32_t in_allp_k; // input allpass coeff 118 | float32_t in_allp1_bufL[224]; // input allpass buffers 119 | float32_t in_allp2_bufL[420]; 120 | float32_t in_allp3_bufL[856]; 121 | float32_t in_allp4_bufL[1089]; 122 | uint16_t in_allp1_idxL; 123 | uint16_t in_allp2_idxL; 124 | uint16_t in_allp3_idxL; 125 | uint16_t in_allp4_idxL; 126 | float32_t in_allp_out_L; // L allpass chain output 127 | float32_t in_allp1_bufR[156]; // input allpass buffers 128 | float32_t in_allp2_bufR[520]; 129 | float32_t in_allp3_bufR[956]; 130 | float32_t in_allp4_bufR[1289]; 131 | uint16_t in_allp1_idxR; 132 | uint16_t in_allp2_idxR; 133 | uint16_t in_allp3_idxR; 134 | uint16_t in_allp4_idxR; 135 | float32_t in_allp_out_R; // R allpass chain output 136 | float32_t lp_allp1_buf[2303]; // loop allpass buffers 137 | float32_t lp_allp2_buf[2905]; 138 | float32_t lp_allp3_buf[3175]; 139 | float32_t lp_allp4_buf[2398]; 140 | uint16_t lp_allp1_idx; 141 | uint16_t lp_allp2_idx; 142 | uint16_t lp_allp3_idx; 143 | uint16_t lp_allp4_idx; 144 | float32_t loop_allp_k; // loop allpass coeff 145 | float32_t lp_allp_out; 146 | float32_t lp_dly1_buf[3423]; 147 | float32_t lp_dly2_buf[4589]; 148 | float32_t lp_dly3_buf[4365]; 149 | float32_t lp_dly4_buf[3698]; 150 | uint16_t lp_dly1_idx; 151 | uint16_t lp_dly2_idx; 152 | uint16_t lp_dly3_idx; 153 | uint16_t lp_dly4_idx; 154 | 155 | const uint16_t lp_dly1_offset_L = 201; // delay line tap offets 156 | const uint16_t lp_dly2_offset_L = 145; 157 | const uint16_t lp_dly3_offset_L = 1897; 158 | const uint16_t lp_dly4_offset_L = 280; 159 | 160 | const uint16_t lp_dly1_offset_R = 1897; 161 | const uint16_t lp_dly2_offset_R = 1245; 162 | const uint16_t lp_dly3_offset_R = 487; 163 | const uint16_t lp_dly4_offset_R = 780; 164 | 165 | float32_t lp_hidamp_k; // loop high band damping coeff 166 | float32_t lp_lodamp_k; // loop low baand damping coeff 167 | 168 | float32_t lpf1; // lowpass filters 169 | float32_t lpf2; 170 | float32_t lpf3; 171 | float32_t lpf4; 172 | 173 | float32_t hpf1; // highpass filters 174 | float32_t hpf2; 175 | float32_t hpf3; 176 | float32_t hpf4; 177 | 178 | float32_t lp_lowpass_f; // loop lowpass scaled frequency 179 | float32_t lp_hipass_f; // loop highpass scaled frequency 180 | 181 | float32_t master_lowpass_f; 182 | float32_t master_lowpass_l; 183 | float32_t master_lowpass_r; 184 | 185 | const float32_t rv_time_k_max = 0.95f; 186 | float32_t rv_time_k; // reverb time coeff 187 | float32_t rv_time_scaler; // with high lodamp settings lower the max reverb time to avoid clipping 188 | 189 | uint32_t lfo1_phase_acc; // LFO 1 190 | uint32_t lfo1_adder; 191 | 192 | uint32_t lfo2_phase_acc; // LFO 2 193 | uint32_t lfo2_adder; 194 | }; 195 | 196 | #endif // _EFFECT_PLATEREV_H 197 | -------------------------------------------------------------------------------- /src/kernel.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kernel.cpp 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #include "kernel.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "usbminidexedmidigadget.h" 27 | 28 | #define NET_DEVICE_TYPE NetDeviceTypeWLAN // or: NetDeviceTypeWLAN 29 | 30 | LOGMODULE ("kernel"); 31 | 32 | CKernel *CKernel::s_pThis = 0; 33 | 34 | CKernel::CKernel (void) 35 | : 36 | CStdlibAppStdio ("minidexed"), 37 | m_Config (&mFileSystem), 38 | m_GPIOManager (&mInterrupt), 39 | m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), 40 | m_pSPIMaster (nullptr), 41 | m_pDexed (0) 42 | { 43 | s_pThis = this; 44 | 45 | // mActLED.Blink (5); // show we are alive 46 | } 47 | 48 | CKernel::~CKernel(void) 49 | { 50 | s_pThis = 0; 51 | } 52 | 53 | bool CKernel::Initialize (void) 54 | { 55 | if (!CStdlibAppStdio::Initialize ()) 56 | { 57 | return FALSE; 58 | } 59 | 60 | mLogger.RegisterPanicHandler (PanicHandler); 61 | 62 | if (!m_GPIOManager.Initialize ()) 63 | { 64 | return FALSE; 65 | } 66 | 67 | if (!m_I2CMaster.Initialize ()) 68 | { 69 | return FALSE; 70 | } 71 | 72 | m_Config.Load (); 73 | 74 | unsigned nSPIMaster = m_Config.GetSPIBus(); 75 | unsigned nSPIMode = m_Config.GetSPIMode(); 76 | unsigned long nSPIClock = 1000 * m_Config.GetSPIClockKHz(); 77 | #if RASPPI<4 78 | // By default older RPI versions use SPI 0. 79 | // It is possible to build circle to support SPI 1 for 80 | // devices that use the 40-pin header, but that isn't 81 | // enabled at present... 82 | if (nSPIMaster == 0) 83 | #else 84 | // RPI 4+ has several possible SPI Bus Configurations. 85 | // As mentioned above, SPI 1 is not built by default. 86 | // See circle/include/circle/spimaster.h 87 | if (nSPIMaster == 0 || nSPIMaster == 3 || nSPIMaster == 4 || nSPIMaster == 5 || nSPIMaster == 6) 88 | #endif 89 | { 90 | unsigned nCPHA = (nSPIMode & 1) ? 1 : 0; 91 | unsigned nCPOL = (nSPIMode & 2) ? 1 : 0; 92 | m_pSPIMaster = new CSPIMaster (nSPIClock, nCPOL, nCPHA, nSPIMaster); 93 | if (!m_pSPIMaster->Initialize()) 94 | { 95 | delete (m_pSPIMaster); 96 | m_pSPIMaster = nullptr; 97 | } 98 | } 99 | 100 | bool bUSBGadgetMode = false; 101 | if (m_Config.GetUSBGadget()) 102 | { 103 | unsigned nUSBGadgetPin = m_Config.GetUSBGadgetPin(); 104 | if (nUSBGadgetPin == 0) 105 | { 106 | // No hardware config option 107 | bUSBGadgetMode = true; 108 | } 109 | else 110 | { 111 | // State of USB Gadget Mode determined by state of the pin. 112 | // Pulled down = enable USB Gadget mode 113 | CGPIOPin usbGadgetPin(nUSBGadgetPin, GPIOModeInputPullUp); 114 | 115 | if (usbGadgetPin.Read() == 0) 116 | { 117 | bUSBGadgetMode = true; 118 | } 119 | } 120 | } 121 | 122 | if (bUSBGadgetMode) 123 | { 124 | #if RASPPI==5 125 | #warning No support for USB Gadget Mode on RPI 5 yet 126 | #else 127 | // Run the USB stack in USB Gadget (device) mode 128 | m_pUSB = new CUSBMiniDexedMIDIGadget (&mInterrupt); 129 | #endif 130 | } 131 | else 132 | { 133 | // Run the USB stack in USB Host (default) mode 134 | m_pUSB = new CUSBHCIDevice (&mInterrupt, &mTimer, TRUE); 135 | } 136 | m_Config.SetUSBGadgetMode(bUSBGadgetMode); 137 | 138 | if (!m_pUSB->Initialize ()) 139 | { 140 | return FALSE; 141 | } 142 | 143 | m_pDexed = new CMiniDexed (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster, m_pSPIMaster, 144 | &mFileSystem); 145 | assert (m_pDexed); 146 | 147 | if (!m_pDexed->Initialize ()) 148 | { 149 | return FALSE; 150 | } 151 | 152 | return TRUE; 153 | } 154 | 155 | CStdlibApp::TShutdownMode CKernel::Run (void) 156 | { 157 | assert (m_pDexed); 158 | 159 | while (42 == 42) 160 | { 161 | boolean bUpdated = m_pUSB->UpdatePlugAndPlay (); 162 | 163 | m_pDexed->Process(bUpdated); 164 | 165 | if (mbScreenAvailable) 166 | { 167 | mScreen.Update (); 168 | } 169 | 170 | m_CPUThrottle.Update (); 171 | } 172 | 173 | return ShutdownHalt; 174 | } 175 | 176 | void CKernel::PanicHandler (void) 177 | { 178 | EnableIRQs (); 179 | 180 | if (s_pThis->mbScreenAvailable) 181 | { 182 | s_pThis->mScreen.Update (4096); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/kernel.h: -------------------------------------------------------------------------------- 1 | // 2 | // kernel.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _kernel_h 21 | #define _kernel_h 22 | 23 | #include "circle_stdlib_app.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "config.h" 31 | #include "minidexed.h" 32 | 33 | enum TShutdownMode 34 | { 35 | ShutdownNone, 36 | ShutdownHalt, 37 | ShutdownReboot 38 | }; 39 | 40 | class CKernel : public CStdlibAppStdio 41 | { 42 | public: 43 | CKernel (void); 44 | ~CKernel (void); 45 | 46 | bool Initialize (void); 47 | 48 | TShutdownMode Run (void); 49 | 50 | private: 51 | static void PanicHandler (void); 52 | 53 | private: 54 | // do not change this order 55 | CConfig m_Config; 56 | CCPUThrottle m_CPUThrottle; 57 | CGPIOManager m_GPIOManager; 58 | CI2CMaster m_I2CMaster; 59 | CSPIMaster *m_pSPIMaster; 60 | CMiniDexed *m_pDexed; 61 | CUSBController *m_pUSB; 62 | CScheduler m_Scheduler; 63 | 64 | static CKernel *s_pThis; 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | #include "kernel.h" 18 | #include 19 | 20 | int main (void) 21 | { 22 | // cannot return here because some destructors used in CKernel are not implemented 23 | 24 | CKernel Kernel; 25 | if (!Kernel.Initialize ()) 26 | { 27 | halt (); 28 | return EXIT_HALT; 29 | } 30 | 31 | CStdlibApp::TShutdownMode ShutdownMode = Kernel.Run (); 32 | 33 | Kernel.Cleanup (); 34 | 35 | switch (ShutdownMode) 36 | { 37 | case CStdlibApp::ShutdownReboot: 38 | reboot (); 39 | return EXIT_REBOOT; 40 | 41 | case CStdlibApp::ShutdownHalt: 42 | default: 43 | halt (); 44 | return EXIT_HALT; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/midi.h: -------------------------------------------------------------------------------- 1 | // 2 | // midi.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2025 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _midi_h 21 | #define _midi_h 22 | 23 | #define MIDI_NOTE_OFF 0b1000 24 | #define MIDI_NOTE_ON 0b1001 25 | #define MIDI_AFTERTOUCH 0b1010 // TODO 26 | #define MIDI_CHANNEL_AFTERTOUCH 0b1101 // right now Synth_Dexed just manage Channel Aftertouch not Polyphonic AT -> 0b1010 27 | #define MIDI_CONTROL_CHANGE 0b1011 28 | 29 | #define MIDI_CC_BANK_SELECT_MSB 0 30 | #define MIDI_CC_MODULATION 1 31 | #define MIDI_CC_BREATH_CONTROLLER 2 32 | #define MIDI_CC_FOOT_PEDAL 4 33 | #define MIDI_CC_PORTAMENTO_TIME 5 34 | #define MIDI_CC_VOLUME 7 35 | #define MIDI_CC_PAN_POSITION 10 36 | #define MIDI_CC_EXPRESSION 11 37 | #define MIDI_CC_BANK_SELECT_LSB 32 38 | #define MIDI_CC_BANK_SUSTAIN 64 39 | #define MIDI_CC_PORTAMENTO 65 40 | #define MIDI_CC_SOSTENUTO 66 41 | #define MIDI_CC_HOLD2 69 42 | #define MIDI_CC_RESONANCE 71 43 | #define MIDI_CC_FREQUENCY_CUTOFF 74 44 | #define MIDI_CC_REVERB_LEVEL 91 45 | #define MIDI_CC_DETUNE_LEVEL 94 46 | #define MIDI_CC_ALL_SOUND_OFF 120 47 | #define MIDI_CC_ALL_NOTES_OFF 123 48 | #define MIDI_CC_OMNI_MODE_OFF 124 49 | #define MIDI_CC_OMNI_MODE_ON 125 50 | #define MIDI_CC_MONO_MODE_ON 126 51 | #define MIDI_CC_POLY_MODE_ON 127 52 | 53 | #define MIDI_PROGRAM_CHANGE 0b1100 54 | #define MIDI_PITCH_BEND 0b1110 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/mididevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // mididevice.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | #ifndef _mididevice_h 24 | #define _mididevice_h 25 | 26 | #include "config.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "userinterface.h" 32 | 33 | #define MAX_DX7_SYSEX_LENGTH 4104 34 | #define MAX_MIDI_MESSAGE MAX_DX7_SYSEX_LENGTH 35 | 36 | class CMiniDexed; 37 | 38 | class CMIDIDevice 39 | { 40 | public: 41 | enum TChannel 42 | { 43 | Channels = 16, 44 | OmniMode = Channels, 45 | Disabled, 46 | ChannelUnknown 47 | }; 48 | 49 | public: 50 | CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); 51 | virtual ~CMIDIDevice (void); 52 | 53 | void SetChannel (u8 ucChannel, unsigned nTG); 54 | u8 GetChannel (unsigned nTG) const; 55 | 56 | virtual void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) {} 57 | // Change signature to specify device name 58 | void SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG); 59 | const std::string& GetDeviceName() const { return m_DeviceName; } 60 | 61 | protected: 62 | void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); 63 | void AddDevice (const char *pDeviceName); 64 | void HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG); 65 | 66 | private: 67 | bool HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval); 68 | 69 | private: 70 | CMiniDexed *m_pSynthesizer; 71 | CConfig *m_pConfig; 72 | CUserInterface *m_pUI; 73 | 74 | u8 m_ChannelMap[CConfig::AllToneGenerators]; 75 | u8 m_PreviousChannelMap[CConfig::AllToneGenerators]; // Store previous channels for OMNI OFF restore 76 | 77 | unsigned m_nMIDISystemCCVol; 78 | unsigned m_nMIDISystemCCPan; 79 | unsigned m_nMIDISystemCCDetune; 80 | u32 m_MIDISystemCCBitmap[4]; // to allow for 128 bit entries 81 | unsigned m_nMIDIGlobalExpression; 82 | 83 | std::string m_DeviceName; 84 | 85 | typedef std::unordered_map TDeviceMap; 86 | static TDeviceMap s_DeviceMap; 87 | 88 | CSpinLock m_MIDISpinLock; 89 | }; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/midikeyboard.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // midikeyboard.cpp 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | #include "midikeyboard.h" 24 | #include 25 | #include 26 | #include 27 | 28 | CMIDIKeyboard::CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI, unsigned nInstance) 29 | : CMIDIDevice (pSynthesizer, pConfig, pUI), 30 | m_nSysExIdx (0), 31 | m_nInstance (nInstance), 32 | m_pMIDIDevice (0) 33 | { 34 | m_DeviceName.Format ("umidi%u", nInstance+1); 35 | 36 | AddDevice (m_DeviceName); 37 | } 38 | 39 | CMIDIKeyboard::~CMIDIKeyboard (void) 40 | { 41 | } 42 | 43 | void CMIDIKeyboard::Process (boolean bPlugAndPlayUpdated) 44 | { 45 | while (!m_SendQueue.empty ()) 46 | { 47 | TSendQueueEntry Entry = m_SendQueue.front (); 48 | m_SendQueue.pop (); 49 | 50 | if (m_pMIDIDevice) 51 | { 52 | m_pMIDIDevice->SendPlainMIDI (Entry.nCable, Entry.pMessage, Entry.nLength); 53 | } 54 | 55 | delete [] Entry.pMessage; 56 | } 57 | 58 | if (!bPlugAndPlayUpdated) 59 | { 60 | return; 61 | } 62 | 63 | if (m_pMIDIDevice == 0) 64 | { 65 | m_pMIDIDevice = 66 | (CUSBMIDIDevice *) CDeviceNameService::Get ()->GetDevice (m_DeviceName, FALSE); 67 | if (m_pMIDIDevice != 0) 68 | { 69 | m_pMIDIDevice->RegisterPacketHandler (MIDIPacketHandler, this); 70 | 71 | m_pMIDIDevice->RegisterRemovedHandler (DeviceRemovedHandler, this); 72 | } 73 | } 74 | } 75 | 76 | void CMIDIKeyboard::Send (const u8 *pMessage, size_t nLength, unsigned nCable) 77 | { 78 | TSendQueueEntry Entry; 79 | Entry.pMessage = new u8[nLength]; 80 | Entry.nLength = nLength; 81 | Entry.nCable = nCable; 82 | 83 | memcpy (Entry.pMessage, pMessage, nLength); 84 | 85 | m_SendQueue.push (Entry); 86 | } 87 | 88 | // Most packets will be passed straight onto the main MIDI message handler 89 | // but SysEx messages are multiple USB packets and so will need building up 90 | // before parsing. 91 | void CMIDIKeyboard::USBMIDIMessageHandler (u8 *pPacket, unsigned nLength, unsigned nCable, unsigned nDevice) 92 | { 93 | assert (nDevice == m_nInstance + 1); 94 | 95 | if ((pPacket[0] == 0xF0) && (m_nSysExIdx == 0)) 96 | { 97 | // Start of SysEx message 98 | //printf("SysEx Start Idx=%d, (%d)\n", m_nSysExIdx, nLength); 99 | for (unsigned i=0; i= USB_SYSEX_BUFFER_SIZE) { 116 | // Run out of space, so reset and ignore rest of the message 117 | m_nSysExIdx = 0; 118 | break; 119 | } 120 | else if (pPacket[i] == 0xF7) { 121 | // End of SysEx message 122 | m_SysEx[m_nSysExIdx++] = pPacket[i]; 123 | //printf ("SysEx End Idx=%d\n", m_nSysExIdx); 124 | MIDIMessageHandler (m_SysEx, m_nSysExIdx, nCable); 125 | // Reset ready for next time 126 | m_nSysExIdx = 0; 127 | } 128 | else if ((pPacket[i] & 0x80) != 0) { 129 | // Received another command, so reset processing as something has gone wrong 130 | //printf ("SysEx Reset\n"); 131 | m_nSysExIdx = 0; 132 | break; 133 | } 134 | else 135 | { 136 | // Store the byte 137 | m_SysEx[m_nSysExIdx++] = pPacket[i]; 138 | } 139 | } 140 | } 141 | else 142 | { 143 | // Assume it is a standard message 144 | MIDIMessageHandler (pPacket, nLength, nCable); 145 | } 146 | } 147 | 148 | void CMIDIKeyboard::MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength, unsigned nDevice, void *pParam) 149 | { 150 | CMIDIKeyboard *pThis = static_cast (pParam); 151 | assert (pThis != 0); 152 | 153 | pThis->USBMIDIMessageHandler (pPacket, nLength, nCable, nDevice); 154 | } 155 | 156 | void CMIDIKeyboard::DeviceRemovedHandler (CDevice *pDevice, void *pContext) 157 | { 158 | CMIDIKeyboard *pThis = static_cast (pContext); 159 | assert (pThis != 0); 160 | 161 | pThis->m_pMIDIDevice = 0; 162 | } 163 | -------------------------------------------------------------------------------- /src/midikeyboard.h: -------------------------------------------------------------------------------- 1 | // 2 | // midikeyboard.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | #ifndef _midikeyboard_h 24 | #define _midikeyboard_h 25 | 26 | #include "mididevice.h" 27 | #include "config.h" 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #define USB_SYSEX_BUFFER_SIZE (MAX_DX7_SYSEX_LENGTH+128) // Allow a bit spare to handle unexpected SysEx messages 35 | 36 | class CMiniDexed; 37 | 38 | class CMIDIKeyboard : public CMIDIDevice 39 | { 40 | public: 41 | CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI, unsigned nInstance = 0); 42 | ~CMIDIKeyboard (void); 43 | 44 | void Process (boolean bPlugAndPlayUpdated); 45 | 46 | void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; 47 | 48 | private: 49 | static void MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength, unsigned nDevice, void *pParam); 50 | static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); 51 | 52 | void USBMIDIMessageHandler (u8 *pPacket, unsigned nLength, unsigned nCable, unsigned nDevice); 53 | 54 | private: 55 | struct TSendQueueEntry 56 | { 57 | u8 *pMessage; 58 | size_t nLength; 59 | unsigned nCable; 60 | }; 61 | uint8_t m_SysEx[USB_SYSEX_BUFFER_SIZE]; 62 | unsigned m_nSysExIdx; 63 | 64 | private: 65 | unsigned m_nInstance; 66 | CString m_DeviceName; 67 | 68 | CUSBMIDIDevice * volatile m_pMIDIDevice; 69 | 70 | std::queue m_SendQueue; 71 | }; 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/midipin.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // midipin.cpp 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #include "midipin.h" 21 | #include 22 | #include 23 | 24 | LOGMODULE ("midipin"); 25 | 26 | CMIDIPin::CMIDIPin (unsigned nPinNumber) 27 | : m_nPinNumber (nPinNumber), 28 | m_nValue (HIGH) 29 | { 30 | } 31 | 32 | CMIDIPin::~CMIDIPin (void) 33 | { 34 | } 35 | 36 | unsigned CMIDIPin::Read (void) 37 | { 38 | return m_nValue; 39 | } 40 | 41 | void CMIDIPin::Write (unsigned nValue) 42 | { 43 | // Takes values in the MIDI controller range 0 to 127 44 | // and OFF < 64 < ON. 45 | // Simulates a PULLUP IO pin, so "true" is LOW (0) 46 | if (nValue >= 64) { 47 | // "on" 48 | m_nValue = LOW; 49 | } else { 50 | // "off" 51 | m_nValue = HIGH; 52 | } 53 | return; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/midipin.h: -------------------------------------------------------------------------------- 1 | // 2 | // midipin.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _midipin_h 21 | #define _midipin_h 22 | 23 | #include 24 | #include 25 | 26 | // MIDI CC numbers go 0 to 127. 27 | // NB: 0 is treated as "unused" so CC=0 won't work 28 | // Normal GPIO pins are below 100. 29 | // So use a "pin number" of 128 + MIDI CC message for a "MIDI Pin" 30 | #define MIDI_PINS 128 31 | #define ccToMidiPin(c) (((c)==0)?0:((c)+MIDI_PINS)) 32 | #define MidiPinToCC(p) (((p)>=MIDI_PINS)?((p)-MIDI_PINS):0) 33 | #define isMidiPin(p) (((p)>=MIDI_PINS)?1:0) 34 | 35 | class CMIDIPin 36 | { 37 | public: 38 | CMIDIPin (unsigned nPinNumber); // pinNumber = ccToMidiPin (MIDI CC number) 39 | ~CMIDIPin (void); 40 | 41 | // Will return MP_HIGH or MP_LOW. 42 | // Should be treated as a PULLED UP IO pin 43 | // i.e. treated as "active low" (LOW) when pressed. 44 | unsigned Read (void); 45 | 46 | // MIDI CC values >=64 will set the MIDI pin to LOW ("on") 47 | // MIDI CC values <= 63 will set the MIDI pin to HIGH ("off") 48 | void Write (unsigned nValue); 49 | 50 | private: 51 | unsigned m_nPinNumber; 52 | unsigned m_nValue; 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/minidexed.ini: -------------------------------------------------------------------------------- 1 | # 2 | # minidexed.ini 3 | # 4 | 5 | # Sound device 6 | #SoundDevice=i2s 7 | SoundDevice=pwm 8 | #SoundDevice=hdmi 9 | SampleRate=48000 10 | #ChunkSize=256 11 | DACI2CAddress=0 12 | ChannelsSwapped=0 13 | # Engine Type ( 1=Modern ; 2=Mark I ; 3=OPL ) 14 | EngineType=1 15 | QuadDAC8Chan=0 16 | # Master Volume (0-127) 17 | MasterVolume=64 18 | 19 | # MIDI 20 | MIDIBaudRate=31250 21 | #MIDIThru=umidi1,ttyS1 22 | IgnoreAllNotesOff=0 23 | MIDIAutoVoiceDumpOnPC=0 24 | HeaderlessSysExVoices=0 25 | # Program Change enable 26 | # 0 = Ignore all Program Change messages. 27 | # 1 = Respond to Program Change messages. 28 | MIDIRXProgramChange=1 29 | # Program Change mode 30 | # 0 = Only recognise Program Change 0-31. 31 | # 1 = Support 0-127 across four consecutive banks. 32 | # NB: Only relevant if PerformanceSelectChannel=0 33 | ExpandPCAcrossBanks=1 34 | # Program Change action: 35 | # 0 = Program Change messages select voices on the channel associated with each TG. 36 | # 1-16 = Program Change messages on this channel select performances. 37 | # >16 = Program Change messages on ANY channel select performances. 38 | # NB: In performance mode, all Program Change messages on other channels are ignored. 39 | PerformanceSelectChannel=0 40 | 41 | # HD44780 LCD 42 | LCDEnabled=1 43 | LCDPinEnable=17 44 | LCDPinRegisterSelect=4 45 | LCDPinReadWrite=0 46 | LCDPinData4=22 47 | LCDPinData5=23 48 | LCDPinData6=24 49 | LCDPinData7=25 50 | LCDI2CAddress=0x00 51 | 52 | # SSD1306 LCD 53 | # For a 128x32 display, set LCDColumns=20; LCDRows=2 54 | # For a 128x64 display, set LCDColumns=20; LCDRows=4 55 | SSD1306LCDI2CAddress=0x0 56 | SSD1306LCDWidth=128 57 | SSD1306LCDHeight=32 58 | SSD1306LCDRotate=0 59 | SSD1306LCDMirror=0 60 | 61 | # ST7789 LCD 62 | # SPIBus=0 for any RPi (GPIO 10,11,8,7). 63 | # Note: Leave blank (default) if no SPI device required. 64 | # Select=0|1 for CE0 or CE1 65 | # Data = GPIO pin number 66 | # Optional: Reset, Backlight = GPIO pin numbers 67 | # Rotation=0,90,180,270 68 | # SmallFont=0 (default), 1 69 | # 70 | # For a 240 wide display set LCDColumns=15 with LCDRows=2 71 | SPIBus= 72 | ST7789Enabled=0 73 | ST7789Data= 74 | ST7789Select= 75 | ST7789Reset= 76 | ST7789Backlight= 77 | ST7789Width=240 78 | ST7789Height=240 79 | ST7789Rotation=0 80 | ST7789SmallFont=0 81 | 82 | # Default is 16x2 display (e.g. HD44780) 83 | LCDColumns=16 84 | LCDRows=2 85 | 86 | # GPIO Button Navigation 87 | # Any buttons set to 0 will be ignored 88 | ButtonPinPrev=0 89 | ButtonActionPrev= 90 | ButtonPinNext=0 91 | ButtonActionNext= 92 | ButtonPinBack=11 93 | ButtonActionBack=longpress 94 | ButtonPinSelect=11 95 | ButtonActionSelect=click 96 | ButtonPinHome=11 97 | ButtonActionHome=doubleclick 98 | ButtonPinShortcut=11 99 | # (Shortcut doesn't have an action) 100 | 101 | # GPIO Program/Bank/TG Selection 102 | # Any buttons set to 0 will be ignored 103 | ButtonPinPgmUp=0 104 | ButtonActionPgmUp= 105 | ButtonPinPgmDown=0 106 | ButtonActionPgmDown= 107 | ButtonPinBankUp=0 108 | ButtonActionBankUp= 109 | ButtonPinBankDown=0 110 | ButtonActionBankDown= 111 | ButtonPinTGUp=0 112 | ButtonActionTGUp= 113 | ButtonPinTGDown=0 114 | ButtonActionTGDown= 115 | 116 | # Timeouts in milliseconds for double click and long press 117 | DoubleClickTimeout=400 118 | LongPressTimeout=400 119 | 120 | # MIDI Button Navigation 121 | # Specify MIDI CC to act as a button (0 = ununsed, so don't use CC 0) 122 | # NB: Off < 64 < ON 123 | # CC channel: 0=OFF; 1-16 MIDI Ch; >16 Omni 124 | # If MIDIButtonNotes>0 then treat MIDIButton numbers as MIDI 125 | # Note numbers, triggered with NoteOn/NoteOff, not CC numbers. 126 | MIDIButtonCh=17 127 | MIDIButtonNotes=0 128 | # Arrow left 129 | MIDIButtonPrev=46 130 | # Arrow right 131 | MIDIButtonNext=47 132 | # Arrow up 133 | MIDIButtonBack=48 134 | # Arrow down 135 | MIDIButtonSelect=49 136 | # Home button 137 | MIDIButtonHome=50 138 | MIDIButtonPgmUp=51 139 | MIDIButtonPgmDown=52 140 | MIDIButtonBankUp=53 141 | MIDIButtonBankDown=54 142 | MIDIButtonTGUp=55 143 | MIDIButtonTGDown=56 144 | 145 | # KY-040 Rotary Encoder 146 | EncoderEnabled=1 147 | EncoderPinClock=10 148 | EncoderPinData=9 149 | 150 | # Debug 151 | MIDIDumpEnabled=0 152 | ProfileEnabled=0 153 | 154 | # Network 155 | NetworkEnabled=0 156 | NetworkDHCP=1 157 | # NetworkType ( wlan ; ethernet ) 158 | NetworkType=wlan 159 | NetworkHostname=MiniDexed 160 | NetworkIPAddress=0 161 | NetworkSubnetMask=0 162 | NetworkDefaultGateway=0 163 | NetworkDNSServer=0 164 | NetworkFTPEnabled=0 165 | NetworkSyslogEnabled=0 166 | NetworkSyslogServerIPAddress=0 167 | 168 | # Performance 169 | PerformanceSelectToLoad=0 170 | -------------------------------------------------------------------------------- /src/net/applemidi.h: -------------------------------------------------------------------------------- 1 | // 2 | // applemidi.h 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #ifndef _applemidi_h 24 | #define _applemidi_h 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | class CAppleMIDIHandler 32 | { 33 | public: 34 | virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) = 0; 35 | virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) = 0; 36 | virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) = 0; 37 | }; 38 | 39 | class CAppleMIDIParticipant : protected CTask 40 | { 41 | public: 42 | CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler, const char* pSessionName); 43 | virtual ~CAppleMIDIParticipant() override; 44 | 45 | bool Initialize(); 46 | 47 | virtual void Run() override; 48 | 49 | public: 50 | bool SendMIDIToHost(const u8* pData, size_t nSize); 51 | 52 | private: 53 | void ControlInvitationState(); 54 | void MIDIInvitationState(); 55 | void ConnectedState(); 56 | void Reset(); 57 | 58 | bool SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize); 59 | bool SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort); 60 | bool SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken); 61 | bool SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2); 62 | bool SendFeedbackPacket(); 63 | 64 | CBcmRandomNumberGenerator* m_pRandom; 65 | 66 | // UDP sockets 67 | CSocket* m_pControlSocket; 68 | CSocket* m_pMIDISocket; 69 | 70 | // Foreign peers 71 | CIPAddress m_ForeignControlIPAddress; 72 | CIPAddress m_ForeignMIDIIPAddress; 73 | u16 m_nForeignControlPort; 74 | u16 m_nForeignMIDIPort; 75 | 76 | // Connected peer 77 | CIPAddress m_InitiatorIPAddress; 78 | u16 m_nInitiatorControlPort; 79 | u16 m_nInitiatorMIDIPort; 80 | 81 | // Socket receive buffers 82 | u8 m_ControlBuffer[FRAME_BUFFER_SIZE]; 83 | u8 m_MIDIBuffer[FRAME_BUFFER_SIZE]; 84 | 85 | int m_nControlResult; 86 | int m_nMIDIResult; 87 | 88 | // Callback handler 89 | CAppleMIDIHandler* m_pHandler; 90 | 91 | // Participant state machine 92 | enum class TState 93 | { 94 | ControlInvitation, 95 | MIDIInvitation, 96 | Connected 97 | }; 98 | 99 | TState m_State; 100 | 101 | u32 m_nInitiatorToken = 0; 102 | u32 m_nInitiatorSSRC = 0; 103 | u32 m_nSSRC = 0; 104 | u32 m_nLastMIDISequenceNumber = 0; 105 | 106 | u64 m_nOffsetEstimate = 0; 107 | u64 m_nLastSyncTime = 0; 108 | 109 | u16 m_nSequence = 0; 110 | u16 m_nLastFeedbackSequence = 0; 111 | u64 m_nLastFeedbackTime = 0; 112 | 113 | const char* m_pSessionName; 114 | }; 115 | 116 | #endif -------------------------------------------------------------------------------- /src/net/byteorder.h: -------------------------------------------------------------------------------- 1 | // 2 | // byteorder.h 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #ifndef _byteorder_h 24 | #define _byteorder_h 25 | 26 | #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 27 | #define htons(VALUE) (VALUE) 28 | #define htonl(VALUE) (VALUE) 29 | #define htonll(VALUE) (VALUE) 30 | #define ntohs(VALUE) (VALUE) 31 | #define ntohl(VALUE) (VALUE) 32 | #define ntohll(VALUE) (VALUE) 33 | #else 34 | #define htons(VALUE) __builtin_bswap16(VALUE) 35 | #define htonl(VALUE) __builtin_bswap32(VALUE) 36 | #define htonll(VALUE) __builtin_bswap64(VALUE) 37 | #define ntohs(VALUE) __builtin_bswap16(VALUE) 38 | #define ntohl(VALUE) __builtin_bswap32(VALUE) 39 | #define ntohll(VALUE) __builtin_bswap64(VALUE) 40 | #endif 41 | 42 | #endif -------------------------------------------------------------------------------- /src/net/ftpdaemon.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ftpdaemon.cpp 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "ftpdaemon.h" 30 | #include "ftpworker.h" 31 | 32 | LOGMODULE("ftpd"); 33 | 34 | constexpr u16 ListenPort = 21; 35 | constexpr u8 MaxConnections = 1; 36 | 37 | CFTPDaemon::CFTPDaemon(const char* pUser, const char* pPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig) 38 | : CTask(TASK_STACK_SIZE, true), 39 | m_pListenSocket(nullptr), 40 | m_pUser(pUser), 41 | m_pPassword(pPassword), 42 | m_pmDNSPublisher(pMDNSPublisher), 43 | m_pConfig(pConfig) 44 | { 45 | } 46 | 47 | CFTPDaemon::~CFTPDaemon() 48 | { 49 | if (m_pListenSocket) 50 | delete m_pListenSocket; 51 | } 52 | 53 | bool CFTPDaemon::Initialize() 54 | { 55 | CNetSubSystem* const pNet = CNetSubSystem::Get(); 56 | 57 | if ((m_pListenSocket = new CSocket(pNet, IPPROTO_TCP)) == nullptr) 58 | return false; 59 | 60 | if (m_pListenSocket->Bind(ListenPort) != 0) 61 | { 62 | LOGERR("Couldn't bind to port %d", ListenPort); 63 | return false; 64 | } 65 | 66 | if (m_pListenSocket->Listen() != 0) 67 | { 68 | LOGERR("Failed to listen on control socket"); 69 | return false; 70 | } 71 | 72 | // We started as a suspended task; run now that initialization is successful 73 | Start(); 74 | 75 | return true; 76 | } 77 | 78 | void CFTPDaemon::Run() 79 | { 80 | assert(m_pListenSocket != nullptr); 81 | 82 | LOGNOTE("Listener task spawned"); 83 | 84 | while (true) 85 | { 86 | CIPAddress ClientIPAddress; 87 | u16 nClientPort; 88 | 89 | LOGDBG("Listener: waiting for connection"); 90 | CSocket* pConnection = m_pListenSocket->Accept(&ClientIPAddress, &nClientPort); 91 | 92 | if (pConnection == nullptr) 93 | { 94 | LOGERR("Unable to accept connection"); 95 | continue; 96 | } 97 | 98 | CString IPAddressString; 99 | ClientIPAddress.Format(&IPAddressString); 100 | LOGNOTE("Incoming connection from %s:%d", static_cast(IPAddressString), nClientPort); 101 | 102 | if (CFTPWorker::GetInstanceCount() >= MaxConnections) 103 | { 104 | pConnection->Send("421 Maximum number of connections reached.\r\n", 45, 0); 105 | delete pConnection; 106 | LOGWARN("Maximum number of connections reached"); 107 | continue; 108 | } 109 | 110 | // Spawn new worker 111 | new CFTPWorker(pConnection, m_pUser, m_pPassword, m_pmDNSPublisher, m_pConfig); 112 | } 113 | } -------------------------------------------------------------------------------- /src/net/ftpdaemon.h: -------------------------------------------------------------------------------- 1 | // 2 | // ftpdaemon.h 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #ifndef _ftpdaemon_h 24 | #define _ftpdaemon_h 25 | 26 | #include 27 | #include 28 | #include "mdnspublisher.h" 29 | #include "../config.h" 30 | 31 | class CFTPDaemon : protected CTask 32 | { 33 | public: 34 | CFTPDaemon(const char* pUser, const char* pPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig); 35 | virtual ~CFTPDaemon() override; 36 | 37 | bool Initialize(); 38 | 39 | virtual void Run() override; 40 | 41 | private: 42 | // TCP sockets 43 | CSocket* m_pListenSocket; 44 | 45 | const char* m_pUser; 46 | const char* m_pPassword; 47 | CmDNSPublisher* m_pmDNSPublisher; 48 | CConfig* m_pConfig; 49 | }; 50 | 51 | #endif -------------------------------------------------------------------------------- /src/net/ftpworker.h: -------------------------------------------------------------------------------- 1 | // 2 | // ftpworker.h 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #ifndef _ftpworker_h 24 | #define _ftpworker_h 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "../config.h" 31 | #include "mdnspublisher.h" 32 | 33 | // TODO: These may be incomplete/inaccurate 34 | enum TFTPStatus 35 | { 36 | FileStatusOk = 150, 37 | 38 | Success = 200, 39 | SystemType = 215, 40 | ReadyForNewUser = 220, 41 | ClosingControl = 221, 42 | TransferComplete = 226, 43 | EnteringPassiveMode = 227, 44 | UserLoggedIn = 230, 45 | FileActionOk = 250, 46 | PathCreated = 257, 47 | 48 | PasswordRequired = 331, 49 | AccountRequired = 332, 50 | PendingFurtherInfo = 350, 51 | 52 | ServiceNotAvailable = 421, 53 | DataConnectionFailed = 425, 54 | FileActionNotTaken = 450, 55 | ActionAborted = 451, 56 | 57 | CommandUnrecognized = 500, 58 | SyntaxError = 501, 59 | CommandNotImplemented = 502, 60 | BadCommandSequence = 503, 61 | NotLoggedIn = 530, 62 | FileNotFound = 550, 63 | FileNameNotAllowed = 553, 64 | }; 65 | 66 | enum class TTransferMode 67 | { 68 | Active, 69 | Passive, 70 | }; 71 | 72 | enum class TDataType 73 | { 74 | ASCII, 75 | Binary, 76 | }; 77 | 78 | struct TFTPCommand; 79 | struct TDirectoryListEntry; 80 | 81 | class CFTPWorker : protected CTask 82 | { 83 | public: 84 | CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig); 85 | virtual ~CFTPWorker() override; 86 | 87 | virtual void Run() override; 88 | 89 | static u8 GetInstanceCount() { return s_nInstanceCount; } 90 | 91 | private: 92 | CSocket* OpenDataConnection(); 93 | 94 | bool SendStatus(TFTPStatus StatusCode, const char* pMessage); 95 | 96 | bool CheckLoggedIn(); 97 | 98 | // Directory navigation 99 | CString RealPath(const char* pInBuffer) const; 100 | const TDirectoryListEntry* BuildDirectoryList(size_t& nOutEntries) const; 101 | 102 | // FTP command handlers 103 | bool System(const char* pArgs); 104 | bool Username(const char* pArgs); 105 | bool Port(const char* pArgs); 106 | bool Passive(const char* pArgs); 107 | bool Password(const char* pArgs); 108 | bool Type(const char* pArgs); 109 | bool Retrieve(const char* pArgs); 110 | bool Store(const char* pArgs); 111 | bool Delete(const char* pArgs); 112 | bool MakeDirectory(const char* pArgs); 113 | bool ChangeWorkingDirectory(const char* pArgs); 114 | bool ChangeToParentDirectory(const char* pArgs); 115 | bool PrintWorkingDirectory(const char* pArgs); 116 | bool List(const char* pArgs); 117 | bool ListFileNames(const char* pArgs); 118 | bool RenameFrom(const char* pArgs); 119 | bool RenameTo(const char* pArgs); 120 | bool Bye(const char* pArgs); 121 | bool NoOp(const char* pArgs); 122 | 123 | CString m_LogName; 124 | 125 | // Authentication 126 | const char* m_pExpectedUser; 127 | const char* m_pExpectedPassword; 128 | 129 | // TCP sockets 130 | CSocket* m_pControlSocket; 131 | CSocket* m_pDataSocket; 132 | u16 m_nDataSocketPort; 133 | CIPAddress m_DataSocketIPAddress; 134 | 135 | // Command/data buffers 136 | char m_CommandBuffer[FRAME_BUFFER_SIZE]; 137 | u8 m_DataBuffer[FRAME_BUFFER_SIZE]; 138 | 139 | // Session state 140 | CString m_User; 141 | CString m_Password; 142 | TDataType m_DataType; 143 | TTransferMode m_TransferMode; 144 | CString m_CurrentPath; 145 | CString m_RenameFrom; 146 | 147 | CmDNSPublisher* m_pmDNSPublisher; 148 | CConfig* m_pConfig; 149 | 150 | static void FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); 151 | static void FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); 152 | 153 | static void FatFsParentPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); 154 | 155 | static void FormatLastModifiedDate(u16 nDate, char* pOutBuffer, size_t nSize); 156 | static void FormatLastModifiedTime(u16 nDate, char* pOutBuffer, size_t nSize); 157 | 158 | static const TFTPCommand Commands[]; 159 | static u8 s_nInstanceCount; 160 | }; 161 | 162 | #endif -------------------------------------------------------------------------------- /src/net/mdnspublisher.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // mdnspublisher.cpp 3 | // 4 | // Circle - A C++ bare metal environment for Raspberry Pi 5 | // Copyright (C) 2024 R. Stange 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #include "mdnspublisher.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #define MDNS_HOST_GROUP {224, 0, 0, 251} 27 | #define MDNS_PORT 5353 28 | #define MDNS_DOMAIN "local" 29 | #define RR_TYPE_A 1 30 | #define RR_TYPE_PTR 12 31 | #define RR_TYPE_TXT 16 32 | #define RR_TYPE_SRV 33 33 | #define RR_CLASS_IN 1 34 | #define RR_CACHE_FLUSH 0x8000 35 | LOGMODULE ("mdnspub"); 36 | CmDNSPublisher::CmDNSPublisher (CNetSubSystem *pNet) 37 | : m_pNet (pNet), 38 | m_pSocket (nullptr), 39 | m_bRunning (FALSE), 40 | m_pWritePtr (nullptr), 41 | m_pDataLen (nullptr) 42 | { 43 | SetName ("mdnspub"); 44 | } 45 | CmDNSPublisher::~CmDNSPublisher (void) 46 | { 47 | assert (!m_pSocket); 48 | m_bRunning = FALSE; 49 | } 50 | boolean CmDNSPublisher::PublishService (const char *pServiceName, const char *pServiceType, 51 | u16 usServicePort, const char *ppText[]) 52 | { 53 | if (!m_bRunning) 54 | { 55 | // Let task can run once to initialize 56 | CScheduler::Get ()->Yield (); 57 | if (!m_bRunning) 58 | { 59 | return FALSE; 60 | } 61 | } 62 | assert (pServiceName); 63 | assert (pServiceType); 64 | TService *pService = new TService {pServiceName, pServiceType, usServicePort, 0}; 65 | assert (pService); 66 | if (ppText) 67 | { 68 | for (unsigned i = 0; i < MaxTextRecords && ppText[i]; i++) 69 | { 70 | pService->ppText[i] = new CString (ppText[i]); 71 | assert (pService->ppText[i]); 72 | pService->nTextRecords++; 73 | } 74 | } 75 | m_Mutex.Acquire (); 76 | // Insert as first element into list 77 | TPtrListElement *pElement = m_ServiceList.GetFirst (); 78 | if (pElement) 79 | { 80 | m_ServiceList.InsertBefore (pElement, pService); 81 | } 82 | else 83 | { 84 | m_ServiceList.InsertAfter (nullptr, pService); 85 | } 86 | m_Mutex.Release (); 87 | LOGDBG ("Publish service %s", (const char *) pService->ServiceName); 88 | m_Event.Set (); // Trigger resent for everything 89 | return TRUE; 90 | } 91 | boolean CmDNSPublisher::UnpublishService (const char *pServiceName) 92 | { 93 | if (!m_bRunning) 94 | { 95 | return FALSE; 96 | } 97 | assert (pServiceName); 98 | m_Mutex.Acquire (); 99 | // Find service in the list and remove it 100 | TService *pService = nullptr; 101 | TPtrListElement *pElement = m_ServiceList.GetFirst (); 102 | while (pElement) 103 | { 104 | pService = static_cast (CPtrList::GetPtr (pElement)); 105 | assert (pService); 106 | if (pService->ServiceName.Compare (pServiceName) == 0) 107 | { 108 | m_ServiceList.Remove (pElement); 109 | break; 110 | } 111 | pService = nullptr; 112 | pElement = m_ServiceList.GetNext (pElement); 113 | } 114 | m_Mutex.Release (); 115 | if (!pService) 116 | { 117 | return FALSE; 118 | } 119 | LOGDBG ("Unpublish service %s", (const char *) pService->ServiceName); 120 | SendResponse (pService, FALSE); 121 | /* 122 | if (!SendResponse (pService, TRUE)) 123 | { 124 | LOGWARN ("Send failed"); 125 | } 126 | */ 127 | for (unsigned i = 0; i < pService->nTextRecords; i++) 128 | { 129 | delete pService->ppText[i]; 130 | } 131 | delete pService; 132 | return TRUE; 133 | } 134 | boolean CmDNSPublisher::UnpublishService(const char *pServiceName, const char *pServiceType, u16 usServicePort) 135 | { 136 | if (!m_bRunning) 137 | { 138 | return FALSE; 139 | } 140 | assert(pServiceName); 141 | assert(pServiceType); 142 | m_Mutex.Acquire(); 143 | TService *pService = nullptr; 144 | TPtrListElement *pElement = m_ServiceList.GetFirst(); 145 | while (pElement) 146 | { 147 | pService = static_cast(CPtrList::GetPtr(pElement)); 148 | assert(pService); 149 | if (pService->ServiceName.Compare(pServiceName) == 0 && 150 | pService->ServiceType.Compare(pServiceType) == 0 && 151 | pService->usServicePort == usServicePort) 152 | { 153 | m_ServiceList.Remove(pElement); 154 | break; 155 | } 156 | pService = nullptr; 157 | pElement = m_ServiceList.GetNext(pElement); 158 | } 159 | m_Mutex.Release(); 160 | if (!pService) 161 | { 162 | return FALSE; 163 | } 164 | LOGDBG("Unpublish service %s %s %u", (const char *)pService->ServiceName, (const char *)pService->ServiceType, pService->usServicePort); 165 | SendResponse(pService, FALSE); 166 | for (unsigned i = 0; i < pService->nTextRecords; i++) 167 | { 168 | delete pService->ppText[i]; 169 | } 170 | delete pService; 171 | return TRUE; 172 | } 173 | void CmDNSPublisher::Run (void) 174 | { 175 | assert (m_pNet); 176 | assert (!m_pSocket); 177 | m_pSocket = new CSocket (m_pNet, IPPROTO_UDP); 178 | assert (m_pSocket); 179 | if (m_pSocket->Bind (MDNS_PORT) < 0) 180 | { 181 | LOGERR ("Cannot bind to port %u", MDNS_PORT); 182 | delete m_pSocket; 183 | m_pSocket = nullptr; 184 | while (1) 185 | { 186 | m_Event.Clear (); 187 | m_Event.Wait (); 188 | } 189 | } 190 | static const u8 mDNSIPAddress[] = MDNS_HOST_GROUP; 191 | CIPAddress mDNSIP (mDNSIPAddress); 192 | if (m_pSocket->Connect (mDNSIP, MDNS_PORT) < 0) 193 | { 194 | LOGERR ("Cannot connect to mDNS host group"); 195 | delete m_pSocket; 196 | m_pSocket = nullptr; 197 | while (1) 198 | { 199 | m_Event.Clear (); 200 | m_Event.Wait (); 201 | } 202 | } 203 | m_bRunning = TRUE; 204 | while (1) 205 | { 206 | m_Event.Clear (); 207 | m_Event.WaitWithTimeout ((TTLShort - 10) * 1000000); 208 | for (unsigned i = 1; i <= 3; i++) 209 | { 210 | m_Mutex.Acquire (); 211 | TPtrListElement *pElement = m_ServiceList.GetFirst (); 212 | while (pElement) 213 | { 214 | TService *pService = 215 | static_cast (CPtrList::GetPtr (pElement)); 216 | assert (pService); 217 | SendResponse (pService, FALSE); 218 | /* 219 | if (!SendResponse (pService, FALSE)) 220 | { 221 | LOGWARN ("Send failed"); 222 | } 223 | */ 224 | pElement = m_ServiceList.GetNext (pElement); 225 | } 226 | m_Mutex.Release (); 227 | CScheduler::Get ()->Sleep (1); 228 | } 229 | } 230 | } 231 | boolean CmDNSPublisher::SendResponse (TService *pService, boolean bDelete) 232 | { 233 | assert (pService); 234 | assert (m_pNet); 235 | // Collect data 236 | static const char Domain[] = "." MDNS_DOMAIN; 237 | CString ServiceType (pService->ServiceType); 238 | ServiceType.Append (Domain); 239 | CString ServiceName (pService->ServiceName); 240 | ServiceName.Append ("."); 241 | ServiceName.Append (ServiceType); 242 | CString Hostname (m_pNet->GetHostname ()); 243 | Hostname.Append (Domain); 244 | // Start writing buffer 245 | assert (!m_pWritePtr); 246 | m_pWritePtr = m_Buffer; 247 | // mDNS Header 248 | PutWord (0); // Transaction ID 249 | PutWord (0x8400); // Message is a response, Server is an authority for the domain 250 | PutWord (0); // Questions 251 | PutWord (5); // Answer RRs 252 | PutWord (0); // Authority RRs 253 | PutWord (0); // Additional RRs 254 | // Answer RRs 255 | // PTR 256 | PutDNSName ("_services._dns-sd._udp.local"); 257 | PutWord (RR_TYPE_PTR); 258 | PutWord (RR_CLASS_IN); 259 | PutDWord (bDelete ? TTLDelete : TTLLong); 260 | ReserveDataLength (); 261 | u8 *pServiceTypePtr = m_pWritePtr; 262 | PutDNSName (ServiceType); 263 | SetDataLength (); 264 | // PTR 265 | PutCompressedString (pServiceTypePtr); 266 | PutWord (RR_TYPE_PTR); 267 | PutWord (RR_CLASS_IN); 268 | PutDWord (bDelete ? TTLDelete : TTLLong); 269 | ReserveDataLength (); 270 | u8 *pServiceNamePtr = m_pWritePtr; 271 | PutDNSName (ServiceName); 272 | SetDataLength (); 273 | // SRV 274 | PutCompressedString (pServiceNamePtr); 275 | PutWord (RR_TYPE_SRV); 276 | PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); 277 | PutDWord (bDelete ? TTLDelete : TTLShort); 278 | ReserveDataLength (); 279 | PutWord (0); // Priority 280 | PutWord (0); // Weight 281 | PutWord (pService->usServicePort); 282 | u8 *pHostnamePtr = m_pWritePtr; 283 | PutDNSName (Hostname); 284 | SetDataLength (); 285 | // A 286 | PutCompressedString (pHostnamePtr); 287 | PutWord (RR_TYPE_A); 288 | PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); 289 | PutDWord (TTLShort); 290 | ReserveDataLength (); 291 | PutIPAddress (*m_pNet->GetConfig ()->GetIPAddress ()); 292 | SetDataLength (); 293 | // TXT 294 | PutCompressedString (pServiceNamePtr); 295 | PutWord (RR_TYPE_TXT); 296 | PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); 297 | PutDWord (bDelete ? TTLDelete : TTLLong); 298 | ReserveDataLength (); 299 | for (int i = pService->nTextRecords-1; i >= 0; i--) // In reverse order 300 | { 301 | assert (pService->ppText[i]); 302 | PutString (*pService->ppText[i]); 303 | } 304 | SetDataLength (); 305 | unsigned nMsgSize = m_pWritePtr - m_Buffer; 306 | m_pWritePtr = nullptr; 307 | if (nMsgSize >= MaxMessageSize) 308 | { 309 | return FALSE; 310 | } 311 | assert (m_pSocket); 312 | return m_pSocket->Send (m_Buffer, nMsgSize, MSG_DONTWAIT) == (int) nMsgSize; 313 | } 314 | void CmDNSPublisher::PutByte (u8 uchValue) 315 | { 316 | assert (m_pWritePtr); 317 | if ((unsigned) (m_pWritePtr - m_Buffer) < MaxMessageSize) 318 | { 319 | *m_pWritePtr++ = uchValue; 320 | } 321 | } 322 | void CmDNSPublisher::PutWord (u16 usValue) 323 | { 324 | PutByte (usValue >> 8); 325 | PutByte (usValue & 0xFF); 326 | } 327 | void CmDNSPublisher::PutDWord (u32 nValue) 328 | { 329 | PutWord (nValue >> 16); 330 | PutWord (nValue & 0xFFFF); 331 | } 332 | void CmDNSPublisher::PutString (const char *pValue) 333 | { 334 | assert (pValue); 335 | size_t nLen = strlen (pValue); 336 | assert (nLen <= 255); 337 | PutByte (nLen); 338 | while (*pValue) 339 | { 340 | PutByte (static_cast (*pValue++)); 341 | } 342 | } 343 | void CmDNSPublisher::PutCompressedString (const u8 *pWritePtr) 344 | { 345 | assert (m_pWritePtr); 346 | assert (pWritePtr < m_pWritePtr); 347 | unsigned nOffset = pWritePtr - m_Buffer; 348 | assert (nOffset < MaxMessageSize); 349 | nOffset |= 0xC000; 350 | PutWord (static_cast (nOffset)); 351 | } 352 | void CmDNSPublisher::PutDNSName (const char *pValue) 353 | { 354 | char Buffer[256]; 355 | assert (pValue); 356 | strncpy (Buffer, pValue, sizeof Buffer); 357 | Buffer[sizeof Buffer-1] = '\0'; 358 | char *pSavePtr = nullptr; 359 | char *pToken = strtok_r (Buffer, ".", &pSavePtr); 360 | while (pToken) 361 | { 362 | PutString (pToken); 363 | pToken = strtok_r (nullptr, ".", &pSavePtr); 364 | } 365 | PutByte (0); 366 | } 367 | void CmDNSPublisher::PutIPAddress (const CIPAddress &rValue) 368 | { 369 | u8 Buffer[IP_ADDRESS_SIZE]; 370 | rValue.CopyTo (Buffer); 371 | for (unsigned i = 0; i < IP_ADDRESS_SIZE; i++) 372 | { 373 | PutByte (Buffer[i]); 374 | } 375 | } 376 | void CmDNSPublisher::ReserveDataLength (void) 377 | { 378 | assert (!m_pDataLen); 379 | m_pDataLen = m_pWritePtr; 380 | assert (m_pDataLen); 381 | PutWord (0); 382 | } 383 | void CmDNSPublisher::SetDataLength (void) 384 | { 385 | assert (m_pDataLen); 386 | assert (m_pWritePtr); 387 | assert (m_pWritePtr > m_pDataLen); 388 | *reinterpret_cast (m_pDataLen) = le2be16 (m_pWritePtr - m_pDataLen - sizeof (u16)); 389 | m_pDataLen = nullptr; 390 | } -------------------------------------------------------------------------------- /src/net/mdnspublisher.h: -------------------------------------------------------------------------------- 1 | // 2 | // mdnspublisher.h 3 | // 4 | // Circle - A C++ bare metal environment for Raspberry Pi 5 | // Copyright (C) 2024 R. Stange 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _circle_net_mdnspublisher_h 21 | #define _circle_net_mdnspublisher_h 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | class CmDNSPublisher : public CTask /// mDNS / Bonjour client task 32 | { 33 | public: 34 | static constexpr const char *ServiceTypeAppleMIDI = "_apple-midi._udp"; 35 | static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; 36 | public: 37 | /// \param pNet Pointer to the network subsystem object 38 | CmDNSPublisher (CNetSubSystem *pNet); 39 | ~CmDNSPublisher (void); 40 | /// \brief Start publishing a service 41 | /// \param pServiceName Name of the service to be published 42 | /// \param pServiceType Type of the service to be published (e.g. ServiceTypeAppleMIDI) 43 | /// \param usServicePort Port number of the service to be published (in host byte order) 44 | /// \param ppText Descriptions of the service (terminated with a nullptr, or nullptr itself) 45 | /// \return Operation successful? 46 | boolean PublishService (const char *pServiceName, 47 | const char *pServiceType, 48 | u16 usServicePort, 49 | const char *ppText[] = nullptr); 50 | /// \brief Stop publishing a service 51 | /// \param pServiceName Name of the service to be unpublished (same as when published) 52 | /// \return Operation successful? 53 | boolean UnpublishService (const char *pServiceName); 54 | /// \brief Stop publishing a service 55 | /// \param pServiceName Name of the service to be unpublished 56 | /// \param pServiceType Type of the service to be unpublished 57 | /// \param usServicePort Port number of the service to be unpublished 58 | /// \return Operation successful? 59 | boolean UnpublishService (const char *pServiceName, const char *pServiceType, u16 usServicePort); 60 | void Run (void) override; 61 | private: 62 | static const unsigned MaxTextRecords = 10; 63 | static const unsigned MaxMessageSize = 1400; // safe UDP payload in an Ethernet frame 64 | static const unsigned TTLShort = 15; // seconds 65 | static const unsigned TTLLong = 4500; 66 | static const unsigned TTLDelete = 0; 67 | struct TService 68 | { 69 | CString ServiceName; 70 | CString ServiceType; 71 | u16 usServicePort; 72 | unsigned nTextRecords; 73 | CString *ppText[MaxTextRecords]; 74 | }; 75 | boolean SendResponse (TService *pService, boolean bDelete); 76 | // Helpers for writing to buffer 77 | void PutByte (u8 uchValue); 78 | void PutWord (u16 usValue); 79 | void PutDWord (u32 nValue); 80 | void PutString (const char *pValue); 81 | void PutCompressedString (const u8 *pWritePtr); 82 | void PutDNSName (const char *pValue); 83 | void PutIPAddress (const CIPAddress &rValue); 84 | void ReserveDataLength (void); 85 | void SetDataLength (void); 86 | private: 87 | CNetSubSystem *m_pNet; 88 | CPtrList m_ServiceList; 89 | CMutex m_Mutex; 90 | CSocket *m_pSocket; 91 | boolean m_bRunning; 92 | CSynchronizationEvent m_Event; 93 | u8 m_Buffer[MaxMessageSize]; 94 | u8 *m_pWritePtr; 95 | u8 *m_pDataLen; 96 | }; 97 | #endif -------------------------------------------------------------------------------- /src/net/udpmidi.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // udpmidi.cpp 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "udpmidi.h" 29 | 30 | LOGMODULE("udpmidi"); 31 | 32 | constexpr u16 MIDIPort = 1999; 33 | 34 | CUDPMIDIReceiver::CUDPMIDIReceiver(CUDPMIDIHandler* pHandler) 35 | : CTask(TASK_STACK_SIZE, true), 36 | m_pMIDISocket(nullptr), 37 | m_MIDIBuffer{0}, 38 | m_pHandler(pHandler) 39 | { 40 | } 41 | 42 | CUDPMIDIReceiver::~CUDPMIDIReceiver() 43 | { 44 | if (m_pMIDISocket) 45 | delete m_pMIDISocket; 46 | } 47 | 48 | bool CUDPMIDIReceiver::Initialize() 49 | { 50 | assert(m_pMIDISocket == nullptr); 51 | 52 | CNetSubSystem* const pNet = CNetSubSystem::Get(); 53 | 54 | if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) 55 | return false; 56 | 57 | if (m_pMIDISocket->Bind(MIDIPort) != 0) 58 | { 59 | LOGERR("Couldn't bind to port %d", MIDIPort); 60 | return false; 61 | } 62 | 63 | // We started as a suspended task; run now that initialization is successful 64 | Start(); 65 | 66 | return true; 67 | } 68 | 69 | void CUDPMIDIReceiver::Run() 70 | { 71 | assert(m_pHandler != nullptr); 72 | assert(m_pMIDISocket != nullptr); 73 | 74 | CScheduler* const pScheduler = CScheduler::Get(); 75 | 76 | while (true) 77 | { 78 | // Blocking call 79 | const int nMIDIResult = m_pMIDISocket->Receive(m_MIDIBuffer, sizeof(m_MIDIBuffer), 0); 80 | 81 | if (nMIDIResult < 0) 82 | LOGERR("MIDI socket receive error: %d", nMIDIResult); 83 | else if (nMIDIResult > 0) 84 | m_pHandler->OnUDPMIDIDataReceived(m_MIDIBuffer, nMIDIResult); 85 | 86 | // Allow other tasks to run 87 | pScheduler->Yield(); 88 | } 89 | } -------------------------------------------------------------------------------- /src/net/udpmidi.h: -------------------------------------------------------------------------------- 1 | // 2 | // udpmidi.h 3 | // 4 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 5 | // Copyright (C) 2020-2023 Dale Whinham 6 | // 7 | // This file is part of mt32-pi. 8 | // 9 | // mt32-pi is free software: you can redistribute it and/or modify it under the 10 | // terms of the GNU General Public License as published by the Free Software 11 | // Foundation, either version 3 of the License, or (at your option) any later 12 | // version. 13 | // 14 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 15 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | // details. 18 | // 19 | // You should have received a copy of the GNU General Public License along with 20 | // mt32-pi. If not, see . 21 | // 22 | 23 | #ifndef _udpmidi_h 24 | #define _udpmidi_h 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | class CUDPMIDIHandler 31 | { 32 | public: 33 | virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) = 0; 34 | }; 35 | 36 | class CUDPMIDIReceiver : protected CTask 37 | { 38 | public: 39 | CUDPMIDIReceiver(CUDPMIDIHandler* pHandler); 40 | virtual ~CUDPMIDIReceiver() override; 41 | 42 | bool Initialize(); 43 | 44 | virtual void Run() override; 45 | 46 | private: 47 | // UDP sockets 48 | CSocket* m_pMIDISocket; 49 | 50 | // Socket receive buffer 51 | u8 m_MIDIBuffer[FRAME_BUFFER_SIZE]; 52 | 53 | // Callback handler 54 | CUDPMIDIHandler* m_pHandler; 55 | }; 56 | 57 | #endif -------------------------------------------------------------------------------- /src/net/utility.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // utility.h 4 | // 5 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 6 | // Copyright (C) 2020-2023 Dale Whinham 7 | // 8 | // This file is part of mt32-pi. 9 | // 10 | // mt32-pi is free software: you can redistribute it and/or modify it under the 11 | // terms of the GNU General Public License as published by the Free Software 12 | // Foundation, either version 3 of the License, or (at your option) any later 13 | // version. 14 | // 15 | // mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY 16 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 | // details. 19 | // 20 | // You should have received a copy of the GNU General Public License along with 21 | // mt32-pi. If not, see . 22 | // 23 | 24 | #ifndef _utility_h 25 | #define _utility_h 26 | 27 | #include 28 | #include 29 | 30 | // Macro to extract the string representation of an enum 31 | #define CONFIG_ENUM_VALUE(VALUE, STRING) VALUE, 32 | 33 | // Macro to extract the enum value 34 | #define CONFIG_ENUM_STRING(VALUE, STRING) #STRING, 35 | 36 | // Macro to declare the enum itself 37 | #define CONFIG_ENUM(NAME, VALUES) enum class NAME { VALUES(CONFIG_ENUM_VALUE) } 38 | 39 | // Macro to declare an array of string representations for an enum 40 | #define CONFIG_ENUM_STRINGS(NAME, DATA) static const char* NAME##Strings[] = { DATA(CONFIG_ENUM_STRING) } 41 | 42 | namespace Utility 43 | { 44 | // Templated function for clamping a value between a minimum and a maximum 45 | template 46 | constexpr T Clamp(const T& nValue, const T& nMin, const T& nMax) 47 | { 48 | return (nValue < nMin) ? nMin : (nValue > nMax) ? nMax : nValue; 49 | } 50 | 51 | // Templated function for taking the minimum of two values 52 | template 53 | constexpr T Min(const T& nLHS, const T& nRHS) 54 | { 55 | return nLHS < nRHS ? nLHS : nRHS; 56 | } 57 | 58 | // Templated function for taking the maximum of two values 59 | template 60 | constexpr T Max(const T& nLHS, const T& nRHS) 61 | { 62 | return nLHS > nRHS ? nLHS : nRHS; 63 | } 64 | 65 | // Function for performing a linear interpolation of a value 66 | constexpr float Lerp(float nValue, float nMinA, float nMaxA, float nMinB, float nMaxB) 67 | { 68 | return nMinB + (nValue - nMinA) * ((nMaxB - nMinB) / (nMaxA - nMinA)); 69 | } 70 | 71 | // Return number of elements in an array 72 | template 73 | constexpr size_t ArraySize(const T(&)[N]) { return N; } 74 | 75 | // Returns whether some value is a power of 2 76 | template 77 | constexpr bool IsPowerOfTwo(const T& nValue) 78 | { 79 | return nValue && ((nValue & (nValue - 1)) == 0); 80 | } 81 | 82 | // Rounds a number to a nearest multiple; only works for integer values/multiples 83 | template 84 | constexpr T RoundToNearestMultiple(const T& nValue, const T& nMultiple) 85 | { 86 | return ((nValue + nMultiple / 2) / nMultiple) * nMultiple; 87 | } 88 | 89 | // Convert between milliseconds and ticks of a 1MHz clock 90 | template 91 | constexpr T MillisToTicks(const T& nMillis) 92 | { 93 | return nMillis * 1000; 94 | } 95 | 96 | template 97 | constexpr T TicksToMillis(const T& nTicks) 98 | { 99 | return nTicks / 1000; 100 | } 101 | 102 | // Computes the Roland checksum 103 | constexpr u8 RolandChecksum(const u8* pData, size_t nSize) 104 | { 105 | u8 nSum = 0; 106 | for (size_t i = 0; i < nSize; ++i) 107 | nSum = (nSum + pData[i]) & 0x7F; 108 | 109 | return 128 - nSum; 110 | } 111 | 112 | // Comparators for sorting 113 | namespace Comparator 114 | { 115 | template 116 | using TComparator = bool (*)(const T&, const T&); 117 | 118 | template 119 | inline bool LessThan(const T& ObjectA, const T& ObjectB) 120 | { 121 | return ObjectA < ObjectB; 122 | } 123 | 124 | template 125 | inline bool GreaterThan(const T& ObjectA, const T& ObjectB) 126 | { 127 | return ObjectA > ObjectB; 128 | } 129 | 130 | inline bool CaseInsensitiveAscending(const CString& StringA, const CString& StringB) 131 | { 132 | return strcasecmp(StringA, StringB) < 0; 133 | } 134 | } 135 | 136 | // Swaps two objects in-place 137 | template 138 | inline void Swap(T& ObjectA, T& ObjectB) 139 | { 140 | u8 Buffer[sizeof(T)]; 141 | memcpy(Buffer, &ObjectA, sizeof(T)); 142 | memcpy(&ObjectA, &ObjectB, sizeof(T)); 143 | memcpy(&ObjectB, Buffer, sizeof(T)); 144 | } 145 | 146 | namespace 147 | { 148 | // Quicksort partition function (private) 149 | template 150 | size_t Partition(T* Items, Comparator::TComparator Comparator, size_t nLow, size_t nHigh) 151 | { 152 | const size_t nPivotIndex = (nHigh + nLow) / 2; 153 | T* Pivot = &Items[nPivotIndex]; 154 | 155 | while (true) 156 | { 157 | while (Comparator(Items[nLow], *Pivot)) 158 | ++nLow; 159 | 160 | while (Comparator(*Pivot, Items[nHigh])) 161 | --nHigh; 162 | 163 | if (nLow >= nHigh) 164 | return nHigh; 165 | 166 | Swap(Items[nLow], Items[nHigh]); 167 | 168 | // Update pointer if pivot was swapped 169 | if (nPivotIndex == nLow) 170 | Pivot = &Items[nHigh]; 171 | else if (nPivotIndex == nHigh) 172 | Pivot = &Items[nLow]; 173 | 174 | ++nLow; 175 | --nHigh; 176 | } 177 | } 178 | } 179 | 180 | // Sorts an array in-place using the Tony Hoare Quicksort algorithm 181 | template 182 | void QSort(T* Items, Comparator::TComparator Comparator, size_t nLow, size_t nHigh) 183 | { 184 | if (nLow < nHigh) 185 | { 186 | size_t p = Partition(Items, Comparator, nLow, nHigh); 187 | QSort(Items, Comparator, nLow, p); 188 | QSort(Items, Comparator, p + 1, nHigh); 189 | } 190 | } 191 | } 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /src/patches/WM8960.diff: -------------------------------------------------------------------------------- 1 | diff --git a/include/circle/i2ssoundbasedevice.h b/include/circle/i2ssoundbasedevice.h 2 | index 3cfe21a3..39dea460 100644 3 | --- a/include/circle/i2ssoundbasedevice.h 4 | +++ b/include/circle/i2ssoundbasedevice.h 5 | @@ -26,6 +26,7 @@ 6 | #include 7 | #include 8 | #include 9 | +#include 10 | #include 11 | 12 | class CI2SSoundBaseDevice : public CSoundBaseDevice /// Low level access to the I2S sound device 13 | @@ -98,7 +99,10 @@ class CI2SSoundBaseDevice : public CSoundBaseDevice /// Low level access to the 14 | static unsigned RXCompletedHandler (boolean bStatus, u32 *pBuffer, 15 | unsigned nChunkSize, void *pParam); 16 | 17 | - boolean InitPCM51xx (u8 ucI2CAddress); 18 | + void LogWrite (TLogSeverity Severity, const char *pMessage, ...); 19 | + void DetectDAC (); 20 | + boolean InitDAC (); 21 | + template boolean SendAll (const u8 (&initBytes)[N][2]); 22 | 23 | private: 24 | CInterruptSystem *m_pInterruptSystem; 25 | diff --git a/lib/i2ssoundbasedevice.cpp b/lib/i2ssoundbasedevice.cpp 26 | index c5df60a1..0825031b 100644 27 | --- a/lib/i2ssoundbasedevice.cpp 28 | +++ b/lib/i2ssoundbasedevice.cpp 29 | @@ -164,21 +164,13 @@ boolean CI2SSoundBaseDevice::Start (void) 30 | && m_pI2CMaster != 0 31 | && !m_bI2CInited) 32 | { 33 | - if (m_ucI2CAddress != 0) 34 | - { 35 | - if (!InitPCM51xx (m_ucI2CAddress)) // fixed address, must succeed 36 | - { 37 | - m_bError = TRUE; 38 | + DetectDAC (); 39 | 40 | - return FALSE; 41 | - } 42 | - } 43 | - else 44 | + if (!InitDAC ()) 45 | { 46 | - if (!InitPCM51xx (0x4C)) // auto probing, ignore failure 47 | - { 48 | - InitPCM51xx (0x4D); 49 | - } 50 | + m_bError = TRUE; 51 | + 52 | + return FALSE; 53 | } 54 | 55 | m_bI2CInited = TRUE; 56 | @@ -422,17 +414,51 @@ unsigned CI2SSoundBaseDevice::RXCompletedHandler (boolean bStatus, u32 *pBuffer, 57 | return 0; 58 | } 59 | 60 | +void CI2SSoundBaseDevice::LogWrite (TLogSeverity Severity, const char *pMessage, ...) 61 | +{ 62 | + va_list var; 63 | + va_start (var, pMessage); 64 | + CLogger::Get ()->WriteV ("CI2SSoundBaseDevice", Severity, pMessage, var); 65 | + va_end (var); 66 | +} 67 | + 68 | +void CI2SSoundBaseDevice::DetectDAC () { 69 | + if (m_ucI2CAddress != 0) 70 | + { 71 | + return; // No need to guess if address is provided 72 | + } 73 | + static const u8 knownAddresses[] = {0x1A, 0x4C, 0x4D}; 74 | + for (auto &address : knownAddresses) 75 | + { 76 | + int written = m_pI2CMaster->Write (address, nullptr, 0); 77 | + LogWrite (LogNotice, "Scan result at i2c address %u: %d", address, written); 78 | + if (written == 0) { 79 | + m_ucI2CAddress = address; 80 | + return; 81 | + } 82 | + } 83 | +} 84 | + 85 | +// For WM8960 i2c register is 7 bits and value is 9 bits, 86 | +// so let's have a helper for packing this into two bytes 87 | +#define SHIFT_BIT(r, v) {((v&0x0100)>>8) | (r<<1), (v&0xff)} 88 | + 89 | // 90 | -// Taken from the file mt32pi.cpp from this project: 91 | +// Based on the file mt32pi.cpp from this project: 92 | // 93 | // mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi 94 | // Copyright (C) 2020-2021 Dale Whinham 95 | // 96 | // Licensed under GPLv3 97 | // 98 | -boolean CI2SSoundBaseDevice::InitPCM51xx (u8 ucI2CAddress) 99 | +boolean CI2SSoundBaseDevice::InitDAC () 100 | { 101 | - static const u8 initBytes[][2] = 102 | + if (m_ucI2CAddress == 0) 103 | + { 104 | + return TRUE; // No DAC, no need to init 105 | + } 106 | + 107 | + static const u8 initBytesPCM51xx[][2] = 108 | { 109 | // Set PLL reference clock to BCK (set SREF to 001b) 110 | { 0x0d, 0x10 }, 111 | @@ -444,9 +470,70 @@ boolean CI2SSoundBaseDevice::InitPCM51xx (u8 ucI2CAddress) 112 | { 0x41, 0x04 } 113 | }; 114 | 115 | + // based on https://github.com/RASPIAUDIO/ULTRA/blob/main/ultra.c 116 | + static const u8 initBytesWM8960[][2] = 117 | + { 118 | + // reset 119 | + SHIFT_BIT(15, 0x000), 120 | + // Power 121 | + SHIFT_BIT(25, 0x1FC), 122 | + SHIFT_BIT(26, 0x1F9), 123 | + SHIFT_BIT(47, 0x03C), 124 | + // Clock PLL 125 | + SHIFT_BIT(4, 0x001), 126 | + SHIFT_BIT(52, 0x027), 127 | + SHIFT_BIT(53, 0x086), 128 | + SHIFT_BIT(54, 0x0C2), 129 | + SHIFT_BIT(55, 0x026), 130 | + // ADC/DAC 131 | + SHIFT_BIT(5, 0x000), 132 | + SHIFT_BIT(7, 0x002), 133 | + // ALC and Noise control 134 | + SHIFT_BIT(20, 0x0F9), 135 | + SHIFT_BIT(17, 0x1FB), 136 | + SHIFT_BIT(18, 0x000), 137 | + SHIFT_BIT(19, 0x032), 138 | + // OUT1 volume 139 | + SHIFT_BIT(2, 0x16F), 140 | + SHIFT_BIT(3, 0x16F), 141 | + //SPK volume 142 | + SHIFT_BIT(40, 0x17F), 143 | + SHIFT_BIT(41, 0x178), 144 | + SHIFT_BIT(51, 0x08D), 145 | + // input volume 146 | + SHIFT_BIT(0, 0x13F), 147 | + SHIFT_BIT(1, 0x13F), 148 | + // INPUTS 149 | + SHIFT_BIT(32, 0x138), 150 | + SHIFT_BIT(33, 0x138), 151 | + // OUTPUTS 152 | + SHIFT_BIT(49, 0x0F7), 153 | + SHIFT_BIT(10, 0x1FF), 154 | + SHIFT_BIT(11, 0x1FF), 155 | + SHIFT_BIT(34, 0x100), 156 | + SHIFT_BIT(37, 0x100) 157 | + }; 158 | + 159 | + switch (m_ucI2CAddress) 160 | + { 161 | + case 0x4C: 162 | + case 0x4D: 163 | + return SendAll(initBytesPCM51xx); 164 | + 165 | + case 0x1A: 166 | + return SendAll(initBytesWM8960); 167 | + 168 | + default: 169 | + LogWrite (LogError, "Don't know how to init device at i2c address %u", m_ucI2CAddress); 170 | + return FALSE; 171 | + } 172 | +} 173 | + 174 | +template boolean CI2SSoundBaseDevice::SendAll (const u8 (&initBytes)[N][2]) 175 | +{ 176 | for (auto &command : initBytes) 177 | { 178 | - if ( m_pI2CMaster->Write (ucI2CAddress, &command, sizeof (command)) 179 | + if ( m_pI2CMaster->Write (m_ucI2CAddress, &command, sizeof (command)) 180 | != sizeof (command)) 181 | { 182 | return FALSE; 183 | -------------------------------------------------------------------------------- /src/pckeyboard.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // pckeyboard.cpp 3 | // 4 | // MiniSynth Pi - A virtual analogue synthesizer for Raspberry Pi 5 | // Copyright (C) 2017-2020 R. Stange 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #include "pckeyboard.h" 21 | #include 22 | #include 23 | #include 24 | 25 | struct TKeyInfo 26 | { 27 | char KeyCode; // upper case letter or digit 28 | u8 KeyNumber; // MIDI number 29 | }; 30 | 31 | // KeyCode is valid for standard QWERTY keyboard 32 | static TKeyInfo KeyTable[] = 33 | { 34 | {',', 72}, // C4 35 | {'M', 71}, // B4 36 | {'J', 70}, // A#4 37 | {'N', 69}, // A4 38 | {'H', 68}, // G#3 39 | {'B', 67}, // G3 40 | {'G', 66}, // F#3 41 | {'V', 65}, // F3 42 | {'C', 64}, // E3 43 | {'D', 63}, // D#3 44 | {'X', 62}, // D3 45 | {'S', 61}, // C#3 46 | {'Z', 60}, // C3 47 | {'U', 59}, // B3 48 | {'7', 58}, // A#3 49 | {'Y', 57}, // A3 50 | {'6', 56}, // G#2 51 | {'T', 55}, // G2 52 | {'5', 54}, // F#2 53 | {'R', 53}, // F2 54 | {'E', 52}, // E2 55 | {'3', 51}, // D#2 56 | {'W', 50}, // D2 57 | {'2', 49}, // C#2 58 | {'Q', 48} // C2 59 | }; 60 | 61 | CPCKeyboard *CPCKeyboard::s_pThis = 0; 62 | 63 | CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI) 64 | : CMIDIDevice (pSynthesizer, pConfig, pUI), 65 | m_pKeyboard (0) 66 | { 67 | s_pThis = this; 68 | 69 | memset (m_LastKeys, 0, sizeof m_LastKeys); 70 | 71 | AddDevice ("ukbd1"); 72 | } 73 | 74 | CPCKeyboard::~CPCKeyboard (void) 75 | { 76 | s_pThis = 0; 77 | } 78 | 79 | void CPCKeyboard::Process (boolean bPlugAndPlayUpdated) 80 | { 81 | if (!bPlugAndPlayUpdated) 82 | { 83 | return; 84 | } 85 | 86 | if (m_pKeyboard == 0) 87 | { 88 | m_pKeyboard = 89 | (CUSBKeyboardDevice *) CDeviceNameService::Get ()->GetDevice ("ukbd1", FALSE); 90 | if (m_pKeyboard != 0) 91 | { 92 | m_pKeyboard->RegisterKeyStatusHandlerRaw (KeyStatusHandlerRaw); 93 | 94 | m_pKeyboard->RegisterRemovedHandler (DeviceRemovedHandler); 95 | } 96 | } 97 | } 98 | 99 | void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6]) 100 | { 101 | assert (s_pThis != 0); 102 | 103 | // report released keys 104 | for (unsigned i = 0; i < 6; i++) 105 | { 106 | u8 ucKeyCode = s_pThis->m_LastKeys[i]; 107 | if ( ucKeyCode != 0 108 | && !FindByte (RawKeys, ucKeyCode, 6)) 109 | { 110 | u8 ucKeyNumber = GetKeyNumber (ucKeyCode); 111 | if (ucKeyNumber != 0) 112 | { 113 | u8 NoteOff[] = {0x80, ucKeyNumber, 0}; 114 | s_pThis->MIDIMessageHandler (NoteOff, sizeof NoteOff); 115 | } 116 | } 117 | } 118 | 119 | // report pressed keys 120 | for (unsigned i = 0; i < 6; i++) 121 | { 122 | u8 ucKeyCode = RawKeys[i]; 123 | if ( ucKeyCode != 0 124 | && !FindByte (s_pThis->m_LastKeys, ucKeyCode, 6)) 125 | { 126 | u8 ucKeyNumber = GetKeyNumber (ucKeyCode); 127 | if (ucKeyNumber != 0) 128 | { 129 | u8 NoteOn[] = {0x90, ucKeyNumber, 100}; 130 | s_pThis->MIDIMessageHandler (NoteOn, sizeof NoteOn); 131 | } 132 | } 133 | } 134 | 135 | memcpy (s_pThis->m_LastKeys, RawKeys, sizeof s_pThis->m_LastKeys); 136 | } 137 | 138 | u8 CPCKeyboard::GetKeyNumber (u8 ucKeyCode) 139 | { 140 | char chKey; 141 | if (0x04 <= ucKeyCode && ucKeyCode <= 0x1D) 142 | { 143 | chKey = ucKeyCode-'\x04'+'A'; // key code of 'A' is 0x04 144 | } 145 | else if (0x1E <= ucKeyCode && ucKeyCode <= 0x26) 146 | { 147 | chKey = ucKeyCode-'\x1E'+'1'; // key code of '1' is 0x1E 148 | } 149 | else if (ucKeyCode == 0x36) 150 | { 151 | chKey = ','; // key code of ',' is 0x36 152 | } 153 | else 154 | { 155 | return 0; 156 | } 157 | 158 | for (unsigned i = 0; i < sizeof KeyTable / sizeof KeyTable[0]; i++) 159 | { 160 | if (KeyTable[i].KeyCode == chKey) 161 | { 162 | return KeyTable[i].KeyNumber; 163 | } 164 | } 165 | 166 | return 0; 167 | } 168 | 169 | boolean CPCKeyboard::FindByte (const u8 *pBuffer, u8 ucByte, unsigned nLength) 170 | { 171 | while (nLength-- > 0) 172 | { 173 | if (*pBuffer++ == ucByte) 174 | { 175 | return TRUE; 176 | } 177 | } 178 | 179 | return FALSE; 180 | } 181 | 182 | void CPCKeyboard::DeviceRemovedHandler (CDevice *pDevice, void *pContext) 183 | { 184 | assert (s_pThis != 0); 185 | s_pThis->m_pKeyboard = 0; 186 | } 187 | -------------------------------------------------------------------------------- /src/pckeyboard.h: -------------------------------------------------------------------------------- 1 | // 2 | // pckeyboard.h 3 | // 4 | // MiniSynth Pi - A virtual analogue synthesizer for Raspberry Pi 5 | // Copyright (C) 2017-2020 R. Stange 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _pckeyboard_h 21 | #define _pckeyboard_h 22 | 23 | #include "mididevice.h" 24 | #include "config.h" 25 | #include 26 | #include 27 | #include 28 | 29 | class CMiniDexed; 30 | 31 | class CPCKeyboard : public CMIDIDevice 32 | { 33 | public: 34 | CPCKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); 35 | ~CPCKeyboard (void); 36 | 37 | void Process (boolean bPlugAndPlayUpdated); 38 | 39 | private: 40 | static void KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6]); 41 | 42 | static u8 GetKeyNumber (u8 ucKeyCode); 43 | 44 | static boolean FindByte (const u8 *pBuffer, u8 ucByte, unsigned nLength); 45 | 46 | static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); 47 | 48 | private: 49 | CUSBKeyboardDevice * volatile m_pKeyboard; 50 | 51 | u8 m_LastKeys[6]; 52 | 53 | static CPCKeyboard *s_pThis; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/performance.ini: -------------------------------------------------------------------------------- 1 | # 2 | # performance.ini 3 | # 4 | 5 | # TG# 6 | #BankNumber#=0 # 0 .. 127 7 | #VoiceNumber#=1 # 1 .. 32 8 | #MIDIChannel#=1 # 1 .. 16, 0: off, >16: omni mode 9 | #Volume#=100 # 0 .. 127 10 | #Pan#=64 # 0 .. 127 11 | #Detune#=0 # -99 .. 99 12 | #Cutoff#=99 # 0 .. 99 13 | #Resonance#=0 # 0 .. 99 14 | #NoteLimitLow#=0 # 0 .. 127, C-2 .. G8 15 | #NoteLimitHigh#=127 # 0 .. 127, C-2 .. G8 16 | #NoteShift#=0 # -24 .. 24 17 | #ReverbSend#=0 # 0 .. 99 18 | #PitchBendRange#=2 # 0 .. 12 19 | #PitchBendStep#=0 # 0 .. 12 20 | #PortamentoMode#=0 # 0 .. 1 21 | #PortamentoGlissando#=0 # 0 .. 1 22 | #PortamentoTime#=0 # 0 .. 99 23 | #VoiceData#= # space separated hex numbers of 156 voice parameters. Example: 5F 1D 14 32 63 [....] 20 55 24 | #MonoMode#=0 # 0-off .. 1-On 25 | #ModulationWheelRange#=99 # 0..99 26 | #ModulationWheelTarget#=1 # 0..7 27 | #FootControlRange#=99 # 0..99 28 | #FootControlTarget#=0 # 0..7 29 | #BreathControlRange#=99 # 0..99 30 | #BreathControlTarget#=0 # 0..7 31 | #AftertouchRange#=99 # 0..99 32 | #AftertouchTarget#=0 # 0..7 33 | 34 | # TG1 35 | BankNumber1=0 36 | VoiceNumber1=1 37 | MIDIChannel1=255 38 | Volume1=100 39 | Pan1=0 40 | Detune1=-11 41 | Cutoff1=99 42 | Resonance1=0 43 | NoteLimitLow1=0 44 | NoteLimitHigh1=127 45 | NoteShift1=0 46 | ReverbSend1=99 47 | PitchBendRange1=2 48 | PitchBendStep1=0 49 | PortamentoMode1=0 50 | PortamentoGlissando1=0 51 | PortamentoTime1=0 52 | VoiceData1= 53 | MonoMode1=0 54 | ModulationWheelRange1=99 55 | ModulationWheelTarget1=1 56 | FootControlRange1=99 57 | FootControlTarget1=0 58 | BreathControlRange1=99 59 | BreathControlTarget1=0 60 | AftertouchRange1=99 61 | AftertouchTarget1=0 62 | 63 | # TG2 64 | BankNumber2=0 65 | VoiceNumber2=1 66 | MIDIChannel2=255 67 | Volume2=100 68 | Pan2=127 69 | Detune2=11 70 | Cutoff2=99 71 | Resonance2=0 72 | NoteLimitLow2=0 73 | NoteLimitHigh2=127 74 | NoteShift2=0 75 | ReverbSend2=70 76 | PitchBendRange2=2 77 | PitchBendStep2=0 78 | PortamentoMode2=0 79 | PortamentoGlissando2=0 80 | PortamentoTime2=0 81 | VoiceData2= 82 | MonoMode2=0 83 | ModulationWheelRange2=99 84 | ModulationWheelTarget2=1 85 | FootControlRange2=99 86 | FootControlTarget2=0 87 | BreathControlRange2=99 88 | BreathControlTarget2=0 89 | AftertouchRange2=99 90 | AftertouchTarget2=0 91 | 92 | # TG3 93 | BankNumber3=0 94 | VoiceNumber3=1 95 | MIDIChannel3=255 96 | Volume3=100 97 | Pan3=48 98 | Detune3=-7 99 | Cutoff3=99 100 | Resonance3=0 101 | NoteLimitLow3=0 102 | NoteLimitHigh3=127 103 | NoteShift3=0 104 | ReverbSend3=0 105 | PitchBendRange3=2 106 | PitchBendStep3=0 107 | PortamentoMode3=0 108 | PortamentoGlissando3=0 109 | PortamentoTime3=0 110 | VoiceData3= 111 | MonoMode3=0 112 | ModulationWheelRange3=99 113 | ModulationWheelTarget3=1 114 | FootControlRange3=99 115 | FootControlTarget3=0 116 | BreathControlRange3=99 117 | BreathControlTarget3=0 118 | AftertouchRange3=99 119 | AftertouchTarget3=0 120 | 121 | # TG4 122 | BankNumber4=0 123 | VoiceNumber4=1 124 | MIDIChannel4=255 125 | Volume4=100 126 | Pan4=80 127 | Detune4=7 128 | Cutoff4=99 129 | Resonance4=0 130 | NoteLimitLow4=0 131 | NoteLimitHigh4=127 132 | NoteShift4=0 133 | ReverbSend4=0 134 | PitchBendRange4=2 135 | PitchBendStep4=0 136 | PortamentoMode4=0 137 | PortamentoGlissando4=0 138 | PortamentoTime4=0 139 | VoiceData4= 140 | MonoMode4=0 141 | ModulationWheelRange4=99 142 | ModulationWheelTarget4=1 143 | FootControlRange4=99 144 | FootControlTarget4=0 145 | BreathControlRange4=99 146 | BreathControlTarget4=0 147 | AftertouchRange4=99 148 | AftertouchTarget4=0 149 | 150 | # TG5 151 | BankNumber5=0 152 | VoiceNumber5=1 153 | MIDIChannel5=0 154 | Volume5=100 155 | Pan5=64 156 | Detune5=0 157 | Cutoff5=99 158 | Resonance5=0 159 | NoteLimitLow5=0 160 | NoteLimitHigh5=127 161 | NoteShift5=0 162 | ReverbSend5=0 163 | PitchBendRange5=2 164 | PitchBendStep5=0 165 | PortamentoMode5=0 166 | PortamentoGlissando5=0 167 | PortamentoTime5=0 168 | VoiceData5= 169 | MonoMode5=0 170 | ModulationWheelRange5=99 171 | ModulationWheelTarget5=1 172 | FootControlRange5=99 173 | FootControlTarget5=0 174 | BreathControlRange5=99 175 | BreathControlTarget5=0 176 | AftertouchRange5=99 177 | AftertouchTarget5=0 178 | 179 | # TG6 180 | BankNumber6=0 181 | VoiceNumber6=1 182 | MIDIChannel6=0 183 | Volume6=100 184 | Pan6=64 185 | Detune6=0 186 | Cutoff6=99 187 | Resonance6=0 188 | NoteLimitLow6=0 189 | NoteLimitHigh6=127 190 | NoteShift6=0 191 | ReverbSend6=0 192 | PitchBendRange6=2 193 | PitchBendStep6=0 194 | PortamentoMode6=0 195 | PortamentoGlissando6=0 196 | PortamentoTime6=0 197 | VoiceData6= 198 | MonoMode6=0 199 | ModulationWheelRange6=99 200 | ModulationWheelTarget6=1 201 | FootControlRange6=99 202 | FootControlTarget6=0 203 | BreathControlRange6=99 204 | BreathControlTarget6=0 205 | AftertouchRange6=99 206 | AftertouchTarget6=0 207 | 208 | # TG7 209 | BankNumber7=0 210 | VoiceNumber7=1 211 | MIDIChannel7=0 212 | Volume7=100 213 | Pan7=64 214 | Detune7=0 215 | Cutoff7=99 216 | Resonance7=0 217 | NoteLimitLow7=0 218 | NoteLimitHigh7=127 219 | NoteShift7=0 220 | ReverbSend7=0 221 | PitchBendRange7=2 222 | PitchBendStep7=0 223 | PortamentoMode7=0 224 | PortamentoGlissando7=0 225 | PortamentoTime7=0 226 | VoiceData7= 227 | MonoMode7=0 228 | ModulationWheelRange7=99 229 | ModulationWheelTarget7=1 230 | FootControlRange7=99 231 | FootControlTarget7=0 232 | BreathControlRange7=99 233 | BreathControlTarget7=0 234 | AftertouchRange7=99 235 | AftertouchTarget7=0 236 | 237 | # TG8 238 | BankNumber8=0 239 | VoiceNumber8=1 240 | MIDIChannel8=0 241 | Volume8=100 242 | Pan8=64 243 | Detune8=0 244 | Cutoff8=99 245 | Resonance8=0 246 | NoteLimitLow8=0 247 | NoteLimitHigh8=127 248 | NoteShift8=0 249 | ReverbSend8=0 250 | PitchBendRange8=2 251 | PitchBendStep8=0 252 | PortamentoMode8=0 253 | PortamentoGlissando8=0 254 | PortamentoTime8=0 255 | VoiceData8= 256 | MonoMode8=0 257 | ModulationWheelRange8=99 258 | ModulationWheelTarget8=1 259 | FootControlRange8=99 260 | FootControlTarget8=0 261 | BreathControlRange8=99 262 | BreathControlTarget8=0 263 | AftertouchRange8=99 264 | AftertouchTarget8=0 265 | 266 | # Effects 267 | #CompressorEnable=1 # 0: off, 1: on 268 | #ReverbEnable=1 # 0: off, 1: on 269 | #ReverbSize=70 # 0 .. 99 270 | #ReverbHighDamp=50 # 0 .. 99 271 | #ReverbLowDamp=50 # 0 .. 99 272 | #ReverbLowPass=30 # 0 .. 99 273 | #ReverbDiffusion=65 # 0 .. 99 274 | #ReverbLevel=80 # 0 .. 99 275 | 276 | # Effects 277 | CompressorEnable=1 278 | ReverbEnable=1 279 | ReverbSize=70 280 | ReverbHighDamp=50 281 | ReverbLowDamp=50 282 | ReverbLowPass=30 283 | ReverbDiffusion=65 284 | ReverbLevel=99 285 | -------------------------------------------------------------------------------- /src/performanceconfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // performanceconfig.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | #ifndef _performanceconfig_h 24 | #define _performanceconfig_h 25 | 26 | #include "config.h" 27 | #include 28 | #include 29 | #define NUM_VOICE_PARAM 156 30 | #define NUM_PERFORMANCES 128 31 | #define NUM_PERFORMANCE_BANKS 128 32 | 33 | class CPerformanceConfig // Performance configuration 34 | { 35 | public: 36 | CPerformanceConfig (FATFS *pFileSystem); 37 | ~CPerformanceConfig (void); 38 | 39 | bool Init (unsigned nToneGenerators); 40 | 41 | bool Load (void); 42 | 43 | bool Save (void); 44 | 45 | // TG# 46 | unsigned GetBankNumber (unsigned nTG) const; // 0 .. 127 47 | unsigned GetVoiceNumber (unsigned nTG) const; // 0 .. 31 48 | unsigned GetMIDIChannel (unsigned nTG) const; // 0 .. 15, omni, off 49 | unsigned GetVolume (unsigned nTG) const; // 0 .. 127 50 | unsigned GetPan (unsigned nTG) const; // 0 .. 127 51 | int GetDetune (unsigned nTG) const; // -99 .. 99 52 | unsigned GetCutoff (unsigned nTG) const; // 0 .. 99 53 | unsigned GetResonance (unsigned nTG) const; // 0 .. 99 54 | unsigned GetNoteLimitLow (unsigned nTG) const; // 0 .. 127 55 | unsigned GetNoteLimitHigh (unsigned nTG) const; // 0 .. 127 56 | int GetNoteShift (unsigned nTG) const; // -24 .. 24 57 | unsigned GetReverbSend (unsigned nTG) const; // 0 .. 127 58 | unsigned GetPitchBendRange (unsigned nTG) const; // 0 .. 12 59 | unsigned GetPitchBendStep (unsigned nTG) const; // 0 .. 12 60 | unsigned GetPortamentoMode (unsigned nTG) const; // 0 .. 1 61 | unsigned GetPortamentoGlissando (unsigned nTG) const; // 0 .. 1 62 | unsigned GetPortamentoTime (unsigned nTG) const; // 0 .. 99 63 | bool GetMonoMode (unsigned nTG) const; // 0 .. 1 64 | 65 | unsigned GetModulationWheelRange (unsigned nTG) const; // 0 .. 99 66 | unsigned GetModulationWheelTarget (unsigned nTG) const; // 0 .. 7 67 | unsigned GetFootControlRange (unsigned nTG) const; // 0 .. 99 68 | unsigned GetFootControlTarget (unsigned nTG) const; // 0 .. 7 69 | unsigned GetBreathControlRange (unsigned nTG) const; // 0 .. 99 70 | unsigned GetBreathControlTarget (unsigned nTG) const; // 0 .. 7 71 | unsigned GetAftertouchRange (unsigned nTG) const; // 0 .. 99 72 | unsigned GetAftertouchTarget (unsigned nTG) const; // 0 .. 7 73 | 74 | void SetBankNumber (unsigned nValue, unsigned nTG); 75 | void SetVoiceNumber (unsigned nValue, unsigned nTG); 76 | void SetMIDIChannel (unsigned nValue, unsigned nTG); 77 | void SetVolume (unsigned nValue, unsigned nTG); 78 | void SetPan (unsigned nValue, unsigned nTG); 79 | void SetDetune (int nValue, unsigned nTG); 80 | void SetCutoff (unsigned nValue, unsigned nTG); 81 | void SetResonance (unsigned nValue, unsigned nTG); 82 | void SetNoteLimitLow (unsigned nValue, unsigned nTG); 83 | void SetNoteLimitHigh (unsigned nValue, unsigned nTG); 84 | void SetNoteShift (int nValue, unsigned nTG); 85 | void SetReverbSend (unsigned nValue, unsigned nTG); 86 | void SetPitchBendRange (unsigned nValue, unsigned nTG); 87 | void SetPitchBendStep (unsigned nValue, unsigned nTG); 88 | void SetPortamentoMode (unsigned nValue, unsigned nTG); 89 | void SetPortamentoGlissando (unsigned nValue, unsigned nTG); 90 | void SetPortamentoTime (unsigned nValue, unsigned nTG); 91 | void SetVoiceDataToTxt (const uint8_t *pData, unsigned nTG); 92 | uint8_t *GetVoiceDataFromTxt (unsigned nTG); 93 | void SetMonoMode (bool bOKValue, unsigned nTG); 94 | 95 | void SetModulationWheelRange (unsigned nValue, unsigned nTG); 96 | void SetModulationWheelTarget (unsigned nValue, unsigned nTG); 97 | void SetFootControlRange (unsigned nValue, unsigned nTG); 98 | void SetFootControlTarget (unsigned nValue, unsigned nTG); 99 | void SetBreathControlRange (unsigned nValue, unsigned nTG); 100 | void SetBreathControlTarget (unsigned nValue, unsigned nTG); 101 | void SetAftertouchRange (unsigned nValue, unsigned nTG); 102 | void SetAftertouchTarget (unsigned nValue, unsigned nTG); 103 | 104 | // Effects 105 | bool GetCompressorEnable (void) const; 106 | bool GetReverbEnable (void) const; 107 | unsigned GetReverbSize (void) const; // 0 .. 99 108 | unsigned GetReverbHighDamp (void) const; // 0 .. 99 109 | unsigned GetReverbLowDamp (void) const; // 0 .. 99 110 | unsigned GetReverbLowPass (void) const; // 0 .. 99 111 | unsigned GetReverbDiffusion (void) const; // 0 .. 99 112 | unsigned GetReverbLevel (void) const; // 0 .. 99 113 | 114 | void SetCompressorEnable (bool bValue); 115 | void SetReverbEnable (bool bValue); 116 | void SetReverbSize (unsigned nValue); 117 | void SetReverbHighDamp (unsigned nValue); 118 | void SetReverbLowDamp (unsigned nValue); 119 | void SetReverbLowPass (unsigned nValue); 120 | void SetReverbDiffusion (unsigned nValue); 121 | void SetReverbLevel (unsigned nValue); 122 | 123 | bool VoiceDataFilled(unsigned nTG); 124 | bool ListPerformances(); 125 | //std::string m_DirName; 126 | void SetNewPerformance (unsigned nID); 127 | unsigned FindFirstPerformance (void); 128 | std::string GetPerformanceFileName(unsigned nID); 129 | std::string GetPerformanceFullFilePath(unsigned nID); 130 | std::string GetPerformanceName(unsigned nID); 131 | unsigned GetLastPerformance(); 132 | unsigned GetLastPerformanceBank(); 133 | void SetActualPerformanceID(unsigned nID); 134 | unsigned GetActualPerformanceID(); 135 | void SetActualPerformanceBankID(unsigned nBankID); 136 | unsigned GetActualPerformanceBankID(); 137 | bool CreateNewPerformanceFile(void); 138 | bool GetInternalFolderOk(); 139 | std::string GetNewPerformanceDefaultName(void); 140 | void SetNewPerformanceName(const std::string &Name); 141 | bool DeletePerformance(unsigned nID); 142 | bool CheckFreePerformanceSlot(void); 143 | std::string AddPerformanceBankDirName(unsigned nBankID); 144 | bool IsValidPerformance(unsigned nID); 145 | 146 | bool ListPerformanceBanks(void); 147 | void SetNewPerformanceBank(unsigned nBankID); 148 | unsigned GetPerformanceBank(void); 149 | std::string GetPerformanceBankName(unsigned nBankID); 150 | bool IsValidPerformanceBank(unsigned nBankID); 151 | 152 | private: 153 | CPropertiesFatFsFile m_Properties; 154 | 155 | unsigned m_nToneGenerators; 156 | 157 | unsigned m_nBankNumber[CConfig::AllToneGenerators]; 158 | unsigned m_nVoiceNumber[CConfig::AllToneGenerators]; 159 | unsigned m_nMIDIChannel[CConfig::AllToneGenerators]; 160 | unsigned m_nVolume[CConfig::AllToneGenerators]; 161 | unsigned m_nPan[CConfig::AllToneGenerators]; 162 | int m_nDetune[CConfig::AllToneGenerators]; 163 | unsigned m_nCutoff[CConfig::AllToneGenerators]; 164 | unsigned m_nResonance[CConfig::AllToneGenerators]; 165 | unsigned m_nNoteLimitLow[CConfig::AllToneGenerators]; 166 | unsigned m_nNoteLimitHigh[CConfig::AllToneGenerators]; 167 | int m_nNoteShift[CConfig::AllToneGenerators]; 168 | int m_nReverbSend[CConfig::AllToneGenerators]; 169 | unsigned m_nPitchBendRange[CConfig::AllToneGenerators]; 170 | unsigned m_nPitchBendStep[CConfig::AllToneGenerators]; 171 | unsigned m_nPortamentoMode[CConfig::AllToneGenerators]; 172 | unsigned m_nPortamentoGlissando[CConfig::AllToneGenerators]; 173 | unsigned m_nPortamentoTime[CConfig::AllToneGenerators]; 174 | std::string m_nVoiceDataTxt[CConfig::AllToneGenerators]; 175 | bool m_bMonoMode[CConfig::AllToneGenerators]; 176 | 177 | unsigned m_nModulationWheelRange[CConfig::AllToneGenerators]; 178 | unsigned m_nModulationWheelTarget[CConfig::AllToneGenerators]; 179 | unsigned m_nFootControlRange[CConfig::AllToneGenerators]; 180 | unsigned m_nFootControlTarget[CConfig::AllToneGenerators]; 181 | unsigned m_nBreathControlRange[CConfig::AllToneGenerators]; 182 | unsigned m_nBreathControlTarget[CConfig::AllToneGenerators]; 183 | unsigned m_nAftertouchRange[CConfig::AllToneGenerators]; 184 | unsigned m_nAftertouchTarget[CConfig::AllToneGenerators]; 185 | 186 | unsigned m_nLastPerformance; 187 | unsigned m_nActualPerformance = 0; 188 | unsigned m_nActualPerformanceBank = 0; 189 | unsigned m_nPerformanceBank; 190 | unsigned m_nLastPerformanceBank; 191 | bool m_bPerformanceDirectoryExists; 192 | //unsigned nMenuSelectedPerformance = 0; 193 | std::string m_PerformanceFileName[NUM_PERFORMANCES]; 194 | std::string m_PerformanceBankName[NUM_PERFORMANCE_BANKS]; 195 | FATFS *m_pFileSystem; 196 | 197 | std::string NewPerformanceName=""; 198 | 199 | bool m_bCompressorEnable; 200 | bool m_bReverbEnable; 201 | unsigned m_nReverbSize; 202 | unsigned m_nReverbHighDamp; 203 | unsigned m_nReverbLowDamp; 204 | unsigned m_nReverbLowPass; 205 | unsigned m_nReverbDiffusion; 206 | unsigned m_nReverbLevel; 207 | }; 208 | 209 | #endif 210 | -------------------------------------------------------------------------------- /src/perftimer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // perftimer.cpp 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #include "perftimer.h" 21 | #include 22 | 23 | CPerformanceTimer::CPerformanceTimer (const char *pName, unsigned nDeadlineMicros) 24 | : m_Name (pName), 25 | m_nDeadlineMicros (nDeadlineMicros), 26 | m_nMaximumMicros (0), 27 | m_nLastDumpTicks (0) 28 | { 29 | } 30 | 31 | void CPerformanceTimer::Start (void) 32 | { 33 | m_nStartTicks = CTimer::GetClockTicks (); 34 | } 35 | 36 | void CPerformanceTimer::Stop (void) 37 | { 38 | unsigned nEndTicks = CTimer::GetClockTicks (); 39 | unsigned nMicros = (nEndTicks - m_nStartTicks) / (CLOCKHZ / 1000000); 40 | 41 | if (nMicros > m_nMaximumMicros) 42 | { 43 | m_nMaximumMicros = nMicros; 44 | } 45 | } 46 | 47 | void CPerformanceTimer::Dump (unsigned nIntervalTicks) 48 | { 49 | unsigned nTicks = CTimer::GetClockTicks (); 50 | 51 | if (nTicks - m_nLastDumpTicks >= nIntervalTicks) 52 | { 53 | m_nLastDumpTicks = nTicks; 54 | 55 | unsigned nMaximumMicros = m_nMaximumMicros; // may be overwritten from interrupt 56 | 57 | std::cout << m_Name << ": Maximum duration was " << nMaximumMicros << "us"; 58 | 59 | if (m_nDeadlineMicros != 0) 60 | { 61 | std::cout << " (" << nMaximumMicros*100 / m_nDeadlineMicros << "%)"; 62 | } 63 | 64 | std::cout << std::endl; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/perftimer.h: -------------------------------------------------------------------------------- 1 | // 2 | // perftimer.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _perftimer_h 21 | #define _perftimer_h 22 | 23 | #include 24 | #include 25 | 26 | class CPerformanceTimer 27 | { 28 | public: 29 | CPerformanceTimer (const char *pName, unsigned nDeadlineMicros = 0); 30 | 31 | void Start (void); 32 | void Stop (void); 33 | 34 | void Dump (unsigned nIntervalTicks = CLOCKHZ); 35 | 36 | private: 37 | std::string m_Name; 38 | unsigned m_nDeadlineMicros; 39 | 40 | unsigned m_nStartTicks; 41 | unsigned m_nMaximumMicros; 42 | 43 | unsigned m_nLastDumpTicks; 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/serialmididevice.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // serialmididevice.cpp 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | 24 | #include 25 | #include 26 | #include "serialmididevice.h" 27 | #include 28 | 29 | LOGMODULE("serialmididevice"); 30 | 31 | // There are several UART options - see circle/include/serial.h 32 | // 0 corresponds to GP14/GP15 on all RPi versions. 33 | #define SERIAL_MIDI_DEVICE 0 34 | 35 | CSerialMIDIDevice::CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, 36 | CConfig *pConfig, CUserInterface *pUI) 37 | : CMIDIDevice (pSynthesizer, pConfig, pUI), 38 | m_pConfig (pConfig), 39 | m_Serial (pInterrupt, TRUE, SERIAL_MIDI_DEVICE), 40 | m_nSerialState (0), 41 | m_nSysEx (0), 42 | m_SendBuffer (&m_Serial) 43 | { 44 | AddDevice ("ttyS1"); 45 | } 46 | 47 | CSerialMIDIDevice::~CSerialMIDIDevice (void) 48 | 49 | { 50 | m_nSerialState = 255; 51 | } 52 | 53 | boolean CSerialMIDIDevice::Initialize (void) 54 | { 55 | assert (m_pConfig); 56 | boolean res = m_Serial.Initialize (m_pConfig->GetMIDIBaudRate ()); 57 | unsigned ser_options = m_Serial.GetOptions(); 58 | // Ensure CR->CRLF translation is disabled for MIDI links 59 | ser_options &= ~(SERIAL_OPTION_ONLCR); 60 | m_Serial.SetOptions(ser_options); 61 | return res; 62 | } 63 | 64 | void CSerialMIDIDevice::Process (void) 65 | { 66 | m_SendBuffer.Update (); 67 | 68 | // Read serial MIDI data 69 | u8 Buffer[100]; 70 | int nResult = m_Serial.Read (Buffer, sizeof Buffer); 71 | if (nResult <= 0) 72 | { 73 | if(nResult!=0) 74 | LOGERR("Serial.Read() error: %d\n",nResult); 75 | return; 76 | } 77 | 78 | /* if (m_pConfig->GetMIDIDumpEnabled ()) 79 | { 80 | printf("Incoming MIDI data:"); 81 | for (uint16_t i = 0; i < nResult; i++) 82 | { 83 | if((i % 8) == 0) 84 | printf("\n%04d:",i); 85 | printf(" 0x%02x",Buffer[i]); 86 | } 87 | printf("\n"); 88 | }*/ 89 | 90 | // Process MIDI messages 91 | // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message 92 | // "Running status" see: https://www.lim.di.unimi.it/IEEE/MIDI/SOT5.HTM#Running- 93 | 94 | for (int i = 0; i < nResult; i++) 95 | { 96 | u8 uchData = Buffer[i]; 97 | 98 | if(uchData == 0xF0) 99 | { 100 | // SYSEX found 101 | m_SerialMessage[m_nSysEx++]=uchData; 102 | continue; 103 | } 104 | 105 | // System Real Time messages may appear anywhere in the byte stream, so handle them specially 106 | if(uchData == 0xF8 || uchData == 0xFA || uchData == 0xFB || uchData == 0xFC || uchData == 0xFE || uchData == 0xFF) 107 | { 108 | MIDIMessageHandler (&uchData, 1); 109 | continue; 110 | } 111 | else if(m_nSysEx > 0) 112 | { 113 | m_SerialMessage[m_nSysEx++]=uchData; 114 | if ((uchData & 0x80) == 0x80 || m_nSysEx >= MAX_MIDI_MESSAGE) 115 | { 116 | if(uchData == 0xF7) 117 | MIDIMessageHandler (m_SerialMessage, m_nSysEx); 118 | m_nSysEx = 0; 119 | } 120 | continue; 121 | } 122 | else 123 | { 124 | switch (m_nSerialState) 125 | { 126 | case 0: 127 | MIDIRestart: 128 | if ( (uchData & 0x80) == 0x80 // status byte, all channels 129 | && (uchData & 0xF0) != 0xF0) // ignore system messages 130 | { 131 | m_SerialMessage[m_nSerialState++] = uchData; 132 | } 133 | break; 134 | 135 | case 1: 136 | case 2: 137 | DATABytes: 138 | if (uchData & 0x80) // got status when parameter expected 139 | { 140 | m_nSerialState = 0; 141 | 142 | goto MIDIRestart; 143 | } 144 | 145 | m_SerialMessage[m_nSerialState++] = uchData; 146 | 147 | if ( (m_SerialMessage[0] & 0xE0) == 0xC0 148 | || m_nSerialState == 3 // message is complete 149 | || (m_SerialMessage[0] & 0xF0) == 0xD0) // channel aftertouch 150 | { 151 | MIDIMessageHandler (m_SerialMessage, m_nSerialState); 152 | 153 | m_nSerialState = 4; // State 4 for test if 4th byte is a status byte or a data byte 154 | } 155 | 156 | break; 157 | case 4: 158 | 159 | if ((uchData & 0x80) == 0) // true data byte, false status byte 160 | { 161 | m_nSerialState = 1; 162 | goto DATABytes; 163 | } 164 | else 165 | { 166 | m_nSerialState = 0; 167 | goto MIDIRestart; 168 | } 169 | break; 170 | default: 171 | assert (0); 172 | break; 173 | } 174 | } 175 | } 176 | } 177 | 178 | void CSerialMIDIDevice::Send (const u8 *pMessage, size_t nLength, unsigned nCable) 179 | { 180 | m_SendBuffer.Write (pMessage, nLength); 181 | } 182 | -------------------------------------------------------------------------------- /src/serialmididevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // serialmididevice.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | #ifndef _serialmididevice_h 24 | #define _serialmididevice_h 25 | 26 | #include "mididevice.h" 27 | #include "config.h" 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | class CMiniDexed; 34 | 35 | class CSerialMIDIDevice : public CMIDIDevice 36 | { 37 | public: 38 | CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, CConfig *pConfig, CUserInterface *pUI); 39 | ~CSerialMIDIDevice (void); 40 | 41 | boolean Initialize (void); 42 | 43 | void Process (void); 44 | 45 | void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; 46 | 47 | private: 48 | CConfig *m_pConfig; 49 | 50 | CSerialDevice m_Serial; 51 | unsigned m_nSerialState; 52 | unsigned m_nSysEx; 53 | u8 m_SerialMessage[MAX_MIDI_MESSAGE]; 54 | 55 | CWriteBufferDevice m_SendBuffer; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/sysexfileloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // sysexfileloader.h 3 | // 4 | // See: https://github.com/asb2m10/dexed/blob/master/Documentation/sysex-format.txt 5 | // 6 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 7 | // Copyright (C) 2022 The MiniDexed Team 8 | // 9 | // This program is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU General Public License as published by 11 | // the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU General Public License for more details. 18 | // 19 | // You should have received a copy of the GNU General Public License 20 | // along with this program. If not, see . 21 | // 22 | #ifndef _sysexfileloader_h 23 | #define _sysexfileloader_h 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | class CSysExFileLoader // Loader for DX7 .syx files 30 | { 31 | public: 32 | static const unsigned MaxVoiceBankID = 16383; // i.e. 14-bit MSB/LSB value between 0 and 16383 33 | static const unsigned VoicesPerBank = 32; 34 | static const size_t SizePackedVoice = 128; 35 | static const size_t SizeSingleVoice = 156; 36 | static const unsigned VoiceSysExHdrSize = 8; // Additional (optional) Header/Footer bytes for bank of 32 voices 37 | static const unsigned VoiceSysExSize = 4096; // Bank of 32 voices as per DX7 MIDI Spec 38 | static const unsigned MaxSubDirs = 3; // Number of nested subdirectories supported. 39 | 40 | struct TVoiceBank 41 | { 42 | uint8_t StatusStart; // 0xF0 43 | uint8_t CompanyID; // 0x43 44 | uint8_t SubStatus; // 0x00 45 | uint8_t Format; // 0x09 46 | uint8_t ByteCountMS; // 0x20 47 | uint8_t ByteCountLS; // 0x00 48 | 49 | uint8_t Voice[VoicesPerBank][SizePackedVoice]; 50 | 51 | uint8_t Checksum; 52 | uint8_t StatusEnd; // 0xF7 53 | } 54 | PACKED; 55 | 56 | public: 57 | CSysExFileLoader (const char *pDirName = "/sysex"); 58 | ~CSysExFileLoader (void); 59 | 60 | void Load (bool bHeaderlessSysExVoices = false); 61 | 62 | std::string GetBankName (unsigned nBankID); // 0 .. MaxVoiceBankID 63 | std::string GetVoiceName (unsigned nBankID, unsigned nVoice); // 0 .. MaxVoiceBankID, 0 .. VoicesPerBank-1 64 | unsigned GetNumHighestBank (); // 0 .. MaxVoiceBankID 65 | bool IsValidBank (unsigned nBankID); 66 | unsigned GetNextBankUp (unsigned nBankID); 67 | unsigned GetNextBankDown (unsigned nBankID); 68 | 69 | void GetVoice (unsigned nBankID, // 0 .. MaxVoiceBankID 70 | unsigned nVoiceID, // 0 .. 31 71 | uint8_t *pVoiceData); // returns unpacked format (156 bytes) 72 | 73 | private: 74 | static void DecodePackedVoice (const uint8_t *pPackedData, uint8_t *pDecodedData); 75 | 76 | private: 77 | std::string m_DirName; 78 | 79 | unsigned m_nNumHighestBank; 80 | unsigned m_nBanksLoaded; 81 | 82 | TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1]; 83 | std::string m_BankFileName[MaxVoiceBankID+1]; 84 | 85 | static uint8_t s_DefaultVoice[SizeSingleVoice]; 86 | 87 | void LoadBank (const char * sDirName, const char * sBankName, bool bHeaderlessSysExVoices, unsigned nSubDirCount); 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/udpmididevice.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // udpmididevice.cpp 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | 24 | #include 25 | #include 26 | #include "udpmididevice.h" 27 | #include 28 | #include 29 | #include 30 | 31 | #define VIRTUALCABLE 24 32 | 33 | LOGMODULE("udpmididevice"); 34 | 35 | CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, 36 | CConfig *pConfig, CUserInterface *pUI) 37 | : CMIDIDevice (pSynthesizer, pConfig, pUI), 38 | m_pSynthesizer (pSynthesizer), 39 | m_pConfig (pConfig) 40 | { 41 | AddDevice ("udp"); 42 | } 43 | 44 | CUDPMIDIDevice::~CUDPMIDIDevice (void) 45 | { 46 | //m_pSynthesizer = 0; 47 | } 48 | 49 | boolean CUDPMIDIDevice::Initialize (void) 50 | { 51 | m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this, m_pConfig->GetNetworkHostname()); 52 | if (!m_pAppleMIDIParticipant->Initialize()) 53 | { 54 | LOGERR("Failed to init RTP listener"); 55 | delete m_pAppleMIDIParticipant; 56 | m_pAppleMIDIParticipant = nullptr; 57 | } 58 | else 59 | LOGNOTE("RTP Listener initialized"); 60 | m_pUDPMIDIReceiver = new CUDPMIDIReceiver(this); 61 | if (!m_pUDPMIDIReceiver->Initialize()) 62 | { 63 | LOGERR("Failed to init UDP MIDI receiver"); 64 | delete m_pUDPMIDIReceiver; 65 | m_pUDPMIDIReceiver = nullptr; 66 | } 67 | else 68 | LOGNOTE("UDP MIDI receiver initialized"); 69 | 70 | // UDP MIDI send socket setup (default: broadcast 255.255.255.255:1999) 71 | CNetSubSystem* pNet = CNetSubSystem::Get(); 72 | m_pUDPSendSocket = new CSocket(pNet, IPPROTO_UDP); 73 | m_UDPDestAddress.Set(0xFFFFFFFF); // Broadcast by default 74 | m_UDPDestPort = 1999; 75 | 76 | return true; 77 | } 78 | 79 | // Methods to handle MIDI events 80 | 81 | void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) 82 | { 83 | MIDIMessageHandler(pData, nSize, VIRTUALCABLE); 84 | } 85 | 86 | void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) 87 | { 88 | LOGNOTE("RTP Device connected"); 89 | } 90 | 91 | void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) 92 | { 93 | LOGNOTE("RTP Device disconnected"); 94 | } 95 | 96 | void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) 97 | { 98 | MIDIMessageHandler(pData, nSize, VIRTUALCABLE); 99 | } 100 | 101 | void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable) 102 | { 103 | bool sentRTP = false; 104 | if (m_pAppleMIDIParticipant && m_pAppleMIDIParticipant->SendMIDIToHost(pMessage, nLength)) { 105 | sentRTP = true; 106 | LOGNOTE("Sent %zu bytes to RTP-MIDI host", nLength); 107 | } 108 | if (!sentRTP && m_pUDPSendSocket) { 109 | int res = m_pUDPSendSocket->SendTo(pMessage, nLength, 0, m_UDPDestAddress, m_UDPDestPort); 110 | if (res < 0) { 111 | LOGERR("Failed to send %zu bytes to UDP MIDI host", nLength); 112 | } else { 113 | LOGNOTE("Sent %zu bytes to UDP MIDI host (broadcast)", nLength); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/udpmididevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // udpmididevice.h 3 | // 4 | // Virtual midi device for data recieved on network 5 | // 6 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 7 | // Copyright (C) 2022 The MiniDexed Team 8 | // 9 | // Original author of this class: 10 | // R. Stange 11 | // 12 | // This program is free software: you can redistribute it and/or modify 13 | // it under the terms of the GNU General Public License as published by 14 | // the Free Software Foundation, either version 3 of the License, or 15 | // (at your option) any later version. 16 | // 17 | // This program is distributed in the hope that it will be useful, 18 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | // GNU General Public License for more details. 21 | // 22 | // You should have received a copy of the GNU General Public License 23 | // along with this program. If not, see . 24 | // 25 | #ifndef _udpmididevice_h 26 | #define _udpmididevice_h 27 | 28 | #include "mididevice.h" 29 | #include "config.h" 30 | #include "net/applemidi.h" 31 | #include "net/udpmidi.h" 32 | #include "midi.h" 33 | 34 | class CMiniDexed; 35 | 36 | class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice 37 | { 38 | public: 39 | CUDPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); 40 | ~CUDPMIDIDevice (void); 41 | 42 | boolean Initialize (void); 43 | virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; 44 | virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; 45 | virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; 46 | virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override; 47 | virtual void Send(const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; 48 | 49 | private: 50 | CMiniDexed *m_pSynthesizer; 51 | CConfig *m_pConfig; 52 | CBcmRandomNumberGenerator m_Random; 53 | CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance 54 | CUDPMIDIReceiver* m_pUDPMIDIReceiver; 55 | CSocket* m_pUDPSendSocket = nullptr; 56 | CIPAddress m_UDPDestAddress; 57 | unsigned m_UDPDestPort = 1999; 58 | CIPAddress m_LastUDPSenderAddress; 59 | unsigned m_LastUDPSenderPort = 0; 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/uibuttons.h: -------------------------------------------------------------------------------- 1 | // 2 | // uibuttons.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _uibuttons_h 21 | #define _uibuttons_h 22 | 23 | #include 24 | #include 25 | #include "midipin.h" 26 | #include "config.h" 27 | 28 | #define BUTTONS_UPDATE_NUM_TICKS 100 29 | #define DEBOUNCE_TIME 20 30 | #define MAX_GPIO_BUTTONS 11 // 5 UI buttons, 6 Program/Bank/TG Select buttons 31 | #define MAX_MIDI_BUTTONS 11 32 | #define MAX_BUTTONS (MAX_GPIO_BUTTONS+MAX_MIDI_BUTTONS) 33 | 34 | class CUIButtons; 35 | 36 | class CUIButton 37 | { 38 | public: 39 | enum BtnTrigger 40 | { 41 | BtnTriggerNone = 0, 42 | BtnTriggerClick = 1, 43 | BtnTriggerDoubleClick = 2, 44 | BtnTriggerLongPress = 3 45 | }; 46 | 47 | enum BtnEvent 48 | { 49 | BtnEventNone = 0, 50 | BtnEventPrev = 1, 51 | BtnEventNext = 2, 52 | BtnEventBack = 3, 53 | BtnEventSelect = 4, 54 | BtnEventHome = 5, 55 | BtnEventPgmUp = 6, 56 | BtnEventPgmDown = 7, 57 | BtnEventBankUp = 8, 58 | BtnEventBankDown = 9, 59 | BtnEventTGUp = 10, 60 | BtnEventTGDown = 11, 61 | BtnEventUnknown = 12 62 | }; 63 | 64 | CUIButton (void); 65 | ~CUIButton (void); 66 | 67 | void reset (void); 68 | boolean Initialize (unsigned pinNumber, unsigned doubleClickTimeout, unsigned longPressTimeout); 69 | 70 | void setClickEvent(BtnEvent clickEvent); 71 | void setDoubleClickEvent(BtnEvent doubleClickEvent); 72 | void setLongPressEvent(BtnEvent longPressEvent); 73 | 74 | unsigned getPinNumber(void); 75 | 76 | BtnTrigger ReadTrigger (void); 77 | BtnEvent Read (void); 78 | void Write (unsigned nValue); // MIDI buttons only! 79 | 80 | static BtnTrigger triggerTypeFromString(const char* triggerString); 81 | 82 | private: 83 | // Pin number 84 | unsigned m_pinNumber; 85 | // GPIO pin 86 | CGPIOPin *m_pin; 87 | // MIDI pin 88 | CMIDIPin *m_midipin; 89 | // The value of the pin at the end of the last loop 90 | unsigned m_lastValue; 91 | // Set to 0 on press, increment each read, use to trigger events 92 | uint16_t m_timer; 93 | // Debounce timer 94 | uint16_t m_debounceTimer; 95 | // Number of clicks recorded since last timer reset 96 | uint8_t m_numClicks; 97 | // Event to fire on click 98 | BtnEvent m_clickEvent; 99 | // Event to fire on double click 100 | BtnEvent m_doubleClickEvent; 101 | // Event to fire on long press 102 | BtnEvent m_longPressEvent; 103 | 104 | // Timeout for double click in tenths of a millisecond 105 | unsigned m_doubleClickTimeout; 106 | // Timeout for long press in tenths of a millisecond 107 | unsigned m_longPressTimeout; 108 | }; 109 | 110 | class CUIButtons 111 | { 112 | public: 113 | typedef void BtnEventHandler (CUIButton::BtnEvent Event, void *param); 114 | 115 | public: 116 | CUIButtons (CConfig *pConfig); 117 | ~CUIButtons (void); 118 | 119 | boolean Initialize (void); 120 | 121 | void RegisterEventHandler (BtnEventHandler *handler, void *param = 0); 122 | 123 | void Update (void); 124 | 125 | void ResetButton (unsigned pinNumber); 126 | 127 | void BtnMIDICmdHandler (unsigned nMidiType, unsigned nMidiData1, unsigned nMidiData2); 128 | 129 | private: 130 | CConfig *m_pConfig; 131 | 132 | // Array of normal GPIO buttons and "MIDI buttons" 133 | CUIButton m_buttons[MAX_BUTTONS]; 134 | 135 | // Timeout for double click in tenths of a millisecond 136 | unsigned m_doubleClickTimeout; 137 | // Timeout for long press in tenths of a millisecond 138 | unsigned m_longPressTimeout; 139 | 140 | // Configuration for buttons 141 | unsigned m_prevPin; 142 | CUIButton::BtnTrigger m_prevAction; 143 | unsigned m_nextPin; 144 | CUIButton::BtnTrigger m_nextAction; 145 | unsigned m_backPin; 146 | CUIButton::BtnTrigger m_backAction; 147 | unsigned m_selectPin; 148 | CUIButton::BtnTrigger m_selectAction; 149 | unsigned m_homePin; 150 | CUIButton::BtnTrigger m_homeAction; 151 | 152 | // Program and TG Selection buttons 153 | unsigned m_pgmUpPin; 154 | CUIButton::BtnTrigger m_pgmUpAction; 155 | unsigned m_pgmDownPin; 156 | CUIButton::BtnTrigger m_pgmDownAction; 157 | unsigned m_BankUpPin; 158 | CUIButton::BtnTrigger m_BankUpAction; 159 | unsigned m_BankDownPin; 160 | CUIButton::BtnTrigger m_BankDownAction; 161 | unsigned m_TGUpPin; 162 | CUIButton::BtnTrigger m_TGUpAction; 163 | unsigned m_TGDownPin; 164 | CUIButton::BtnTrigger m_TGDownAction; 165 | 166 | // MIDI button configuration 167 | unsigned m_notesMidi; 168 | unsigned m_prevMidi; 169 | unsigned m_nextMidi; 170 | unsigned m_backMidi; 171 | unsigned m_selectMidi; 172 | unsigned m_homeMidi; 173 | 174 | unsigned m_pgmUpMidi; 175 | unsigned m_pgmDownMidi; 176 | unsigned m_BankUpMidi; 177 | unsigned m_BankDownMidi; 178 | unsigned m_TGUpMidi; 179 | unsigned m_TGDownMidi; 180 | 181 | BtnEventHandler *m_eventHandler; 182 | void *m_eventParam; 183 | 184 | unsigned m_lastTick; 185 | 186 | void bindButton(unsigned pinNumber, CUIButton::BtnTrigger trigger, CUIButton::BtnEvent event); 187 | }; 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /src/uimenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // uimenu.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Original author of this class: 8 | // R. Stange 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // 23 | #ifndef _uimenu_h 24 | #define _uimenu_h 25 | 26 | #include 27 | #include 28 | #include "config.h" 29 | 30 | class CMiniDexed; 31 | class CUserInterface; 32 | 33 | class CUIMenu 34 | { 35 | private: 36 | static const unsigned MaxMenuDepth = 5; 37 | 38 | public: 39 | enum TMenuEvent 40 | { 41 | MenuEventUpdate, 42 | MenuEventUpdateParameter, 43 | MenuEventSelect, 44 | MenuEventBack, 45 | MenuEventHome, 46 | MenuEventStepDown, 47 | MenuEventStepUp, 48 | MenuEventPressAndStepDown, 49 | MenuEventPressAndStepUp, 50 | MenuEventPgmUp, 51 | MenuEventPgmDown, 52 | MenuEventBankUp, 53 | MenuEventBankDown, 54 | MenuEventTGUp, 55 | MenuEventTGDown, 56 | MenuEventUnknown 57 | }; 58 | 59 | public: 60 | CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed, CConfig *pConfig); 61 | 62 | void EventHandler (TMenuEvent Event); 63 | 64 | private: 65 | typedef void TMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event); 66 | 67 | struct TMenuItem 68 | { 69 | const char *Name; 70 | TMenuHandler *Handler; 71 | const TMenuItem *MenuItem; 72 | unsigned Parameter; 73 | }; 74 | 75 | typedef std::string TToString (int nValue); 76 | 77 | struct TParameter 78 | { 79 | int Minimum; 80 | int Maximum; 81 | int Increment; 82 | TToString *ToString; 83 | }; 84 | 85 | private: 86 | static void MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event); 87 | static void EditGlobalParameter (CUIMenu *pUIMenu, TMenuEvent Event); 88 | static void EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); 89 | static void EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event); 90 | static void EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event); 91 | static void EditVoiceParameter (CUIMenu *pUIMenu, TMenuEvent Event); 92 | static void EditOPParameter (CUIMenu *pUIMenu, TMenuEvent Event); 93 | static void SavePerformance (CUIMenu *pUIMenu, TMenuEvent Event); 94 | static void EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event); 95 | static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event); 96 | static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event); 97 | static void SavePerformanceNewFile (CUIMenu *pUIMenu, TMenuEvent Event); 98 | static void EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); 99 | static void EditMasterVolume (CUIMenu *pUIMenu, TMenuEvent Event); 100 | 101 | static std::string GetGlobalValueString (unsigned nParameter, int nValue); 102 | static std::string GetTGValueString (unsigned nTGParameter, int nValue); 103 | static std::string GetVoiceValueString (unsigned nVoiceParameter, int nValue); 104 | static std::string GetOPValueString (unsigned nOPParameter, int nValue); 105 | 106 | static std::string ToVolume (int nValue); 107 | static std::string ToPan (int nValue); 108 | static std::string ToMIDIChannel (int nValue); 109 | 110 | static std::string ToAlgorithm (int nValue); 111 | static std::string ToOnOff (int nValue); 112 | static std::string ToLFOWaveform (int nValue); 113 | static std::string ToTransposeNote (int nValue); 114 | static std::string ToBreakpointNote (int nValue); 115 | static std::string ToKeyboardCurve (int nValue); 116 | static std::string ToOscillatorMode (int nValue); 117 | static std::string ToOscillatorDetune (int nValue); 118 | static std::string ToPortaMode (int nValue); 119 | static std::string ToPortaGlissando (int nValue); 120 | static std::string ToPolyMono (int nValue); 121 | 122 | void TGShortcutHandler (TMenuEvent Event); 123 | void OPShortcutHandler (TMenuEvent Event); 124 | 125 | void PgmUpDownHandler (TMenuEvent Event); 126 | void BankUpDownHandler (TMenuEvent Event); 127 | void TGUpDownHandler (TMenuEvent Event); 128 | 129 | static void TimerHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext); 130 | 131 | static void InputTxt (CUIMenu *pUIMenu, TMenuEvent Event); 132 | static void TimerHandlerNoBack (TKernelTimerHandle hTimer, void *pParam, void *pContext); 133 | 134 | private: 135 | CUserInterface *m_pUI; 136 | CMiniDexed *m_pMiniDexed; 137 | CConfig *m_pConfig; 138 | 139 | unsigned m_nToneGenerators; 140 | 141 | const TMenuItem *m_pParentMenu; 142 | const TMenuItem *m_pCurrentMenu; 143 | unsigned m_nCurrentMenuItem; 144 | unsigned m_nCurrentSelection; 145 | unsigned m_nCurrentParameter; 146 | 147 | const TMenuItem *m_MenuStackParent[MaxMenuDepth]; 148 | const TMenuItem *m_MenuStackMenu[MaxMenuDepth]; 149 | unsigned m_nMenuStackItem[MaxMenuDepth]; 150 | unsigned m_nMenuStackSelection[MaxMenuDepth]; 151 | unsigned m_nMenuStackParameter[MaxMenuDepth]; 152 | unsigned m_nCurrentMenuDepth; 153 | 154 | static const TMenuItem s_MenuRoot[]; 155 | static const TMenuItem s_MainMenu[]; 156 | static const TMenuItem s_TGMenu[]; 157 | static const TMenuItem s_EffectsMenu[]; 158 | static const TMenuItem s_ReverbMenu[]; 159 | static const TMenuItem s_EditVoiceMenu[]; 160 | static const TMenuItem s_OperatorMenu[]; 161 | static const TMenuItem s_SaveMenu[]; 162 | static const TMenuItem s_EditPitchBendMenu[]; 163 | static const TMenuItem s_EditPortamentoMenu[]; 164 | static const TMenuItem s_PerformanceMenu[]; 165 | 166 | static const TMenuItem s_ModulationMenu[]; 167 | static const TMenuItem s_ModulationMenuParameters[]; 168 | 169 | static const TParameter s_GlobalParameter[]; 170 | static const TParameter s_TGParameter[]; 171 | static const TParameter s_VoiceParameter[]; 172 | static const TParameter s_OPParameter[]; 173 | 174 | static const char s_NoteName[100][5]; 175 | 176 | std::string m_InputText="1234567890ABCD"; 177 | unsigned m_InputTextPosition=0; 178 | unsigned m_InputTextChar=32; 179 | bool m_bPerformanceDeleteMode=false; 180 | bool m_bConfirmDeletePerformance=false; 181 | unsigned m_nSelectedPerformanceID =0; 182 | unsigned m_nSelectedPerformanceBankID =0; 183 | bool m_bSplashShow=false; 184 | 185 | }; 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /src/usbminidexedmidigadget.h: -------------------------------------------------------------------------------- 1 | // 2 | // usbminidexedmidigadget.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // Based on circle/usb/gadget/usbmidigadget.h 8 | // 9 | // This program is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU General Public License as published by 11 | // the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU General Public License for more details. 18 | // 19 | // You should have received a copy of the GNU General Public License 20 | // along with this program. If not, see . 21 | // 22 | #ifndef _usbminidexedmidigadget_h 23 | #define _usbminidexedmidigadget_h 24 | 25 | #if RASPPI==5 26 | #include 27 | #include 28 | 29 | #warning No support for USB Gadget Mode on RPI 5 yet 30 | class CUSBMiniDexedMIDIGadget 31 | { 32 | public: 33 | CUSBMiniDexedMIDIGadget (CInterruptSystem *pInterruptSystem) 34 | { 35 | } 36 | 37 | ~CUSBMiniDexedMIDIGadget (void) 38 | { 39 | } 40 | }; 41 | #else 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | class CUSBMiniDexedMIDIGadget : public CUSBMIDIGadget 48 | { 49 | private: 50 | #define MDSTRINGDESCRIPTORS 3 51 | const char *const s_MiniDexedStringDescriptor[MDSTRINGDESCRIPTORS] = 52 | { 53 | "\x04\x03\x09\x04", // Language ID 54 | "probonopd", 55 | "MiniDexed" 56 | }; 57 | 58 | public: 59 | CUSBMiniDexedMIDIGadget (CInterruptSystem *pInterruptSystem) 60 | : CUSBMIDIGadget (pInterruptSystem) 61 | { 62 | } 63 | 64 | ~CUSBMiniDexedMIDIGadget (void) 65 | { 66 | assert(0); 67 | } 68 | 69 | protected: 70 | // Override GetDescriptor frmo CUSBMIDIGadget. 71 | // See CUSBMIDIGadget for details. 72 | // This will only act on the DESCRIPOR_STRING. 73 | // All other descriptors are returned from USBMIDIGadget. 74 | // 75 | const void *GetDescriptor (u16 wValue, u16 wIndex, size_t *pLength) override 76 | { 77 | assert (pLength); 78 | 79 | u8 uchDescIndex = wValue & 0xFF; 80 | 81 | switch (wValue >> 8) 82 | { 83 | case DESCRIPTOR_STRING: 84 | if (!uchDescIndex) 85 | { 86 | *pLength = (u8) s_MiniDexedStringDescriptor[0][0]; 87 | return s_MiniDexedStringDescriptor[0]; 88 | } 89 | else if (uchDescIndex < MDSTRINGDESCRIPTORS) 90 | { 91 | return CUSBMIDIGadget::ToStringDescriptor (s_MiniDexedStringDescriptor[uchDescIndex], pLength); 92 | } 93 | break; 94 | 95 | default: 96 | break; 97 | } 98 | 99 | return CUSBMIDIGadget::GetDescriptor(wValue, wIndex, pLength); 100 | } 101 | }; 102 | #endif 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /src/userinterface.h: -------------------------------------------------------------------------------- 1 | // 2 | // userinterface.h 3 | // 4 | // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi 5 | // Copyright (C) 2022 The MiniDexed Team 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | #ifndef _userinterface_h 21 | #define _userinterface_h 22 | 23 | #include "config.h" 24 | #include "uimenu.h" 25 | #include "uibuttons.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | class CMiniDexed; 36 | 37 | class CUserInterface 38 | { 39 | public: 40 | CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CSPIMaster *pSPIMaster, CConfig *pConfig); 41 | ~CUserInterface (void); 42 | 43 | bool Initialize (void); 44 | 45 | void Process (void); 46 | 47 | void ParameterChanged (void); 48 | void DisplayChanged (void); 49 | 50 | // Write to display in this format: 51 | // +----------------+ 52 | // |PARAM MENU| 53 | // |[<]VALUE [>]| 54 | // +----------------+ 55 | void DisplayWrite (const char *pMenu, const char *pParam, const char *pValue, 56 | bool bArrowDown, bool bArrowUp); 57 | 58 | // To be called from the MIDI device on reception of a MIDI CC message 59 | void UIMIDICmdHandler (unsigned nMidiCh, unsigned nMidiType, unsigned nMidiData1, unsigned nMidiData2); 60 | 61 | private: 62 | void LCDWrite (const char *pString); // Print to optional HD44780 display 63 | 64 | void EncoderEventHandler (CKY040::TEvent Event); 65 | static void EncoderEventStub (CKY040::TEvent Event, void *pParam); 66 | void UIButtonsEventHandler (CUIButton::BtnEvent Event); 67 | static void UIButtonsEventStub (CUIButton::BtnEvent Event, void *pParam); 68 | void UISetMIDIButtonChannel (unsigned uCh); 69 | 70 | private: 71 | CMiniDexed *m_pMiniDexed; 72 | CGPIOManager *m_pGPIOManager; 73 | CI2CMaster *m_pI2CMaster; 74 | CSPIMaster *m_pSPIMaster; 75 | CConfig *m_pConfig; 76 | 77 | CCharDevice *m_pLCD; 78 | CHD44780Device *m_pHD44780; 79 | CSSD1306Device *m_pSSD1306; 80 | CST7789Display *m_pST7789Display; 81 | CST7789Device *m_pST7789; 82 | CWriteBufferDevice *m_pLCDBuffered; 83 | 84 | CUIButtons *m_pUIButtons; 85 | 86 | unsigned m_nMIDIButtonCh; 87 | 88 | CKY040 *m_pRotaryEncoder; 89 | bool m_bSwitchPressed; 90 | 91 | CUIMenu m_Menu; 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /submod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # Update top-level modules as a baseline 5 | git submodule update --init --recursive -f 6 | 7 | # Use fixed master branch of circle-stdlib then re-update 8 | cd circle-stdlib/ 9 | git reset --hard 10 | git checkout 1111eee -f # Matches Circle Step49 11 | git submodule update --init --recursive -f 12 | cd - 13 | 14 | # Optional update submodules explicitly 15 | #cd circle-stdlib/libs/circle 16 | #git reset --hard 17 | #git checkout tags/Step49 18 | #cd - 19 | #cd circle-stdlib/libs/circle-newlib 20 | #git checkout develop 21 | #cd - 22 | 23 | # Use fixed master branch of Synth_Dexed 24 | cd Synth_Dexed/ 25 | git reset --hard 26 | git checkout 2ad9e43095 -f 27 | cd - 28 | -------------------------------------------------------------------------------- /syslogserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Syslog server to receive and display syslog messages from MiniDexed. 6 | """ 7 | 8 | import socket 9 | import time 10 | import threading 11 | 12 | class SyslogServer: 13 | def __init__(self, host='0.0.0.0', port=8514): 14 | self.host = host 15 | self.port = port 16 | self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 17 | self.server.bind((self.host, self.port)) 18 | self.server.settimeout(0.5) # Set timeout to allow checking self.running 19 | self.start_time = None 20 | self.running = True 21 | 22 | def start(self): 23 | ip_address = socket.gethostbyname(socket.gethostname()) 24 | print(f"Syslog server listening on {ip_address}:{self.port}") 25 | input_thread = threading.Thread(target=self.wait_for_input) 26 | input_thread.daemon = True 27 | input_thread.start() 28 | while self.running: 29 | try: 30 | data, address = self.server.recvfrom(1024) 31 | self.handle_message(data) 32 | except socket.timeout: 33 | continue # Check self.running again 34 | except KeyboardInterrupt: 35 | self.running = False 36 | 37 | def handle_message(self, data): 38 | message = data[2:].decode('utf-8').strip() 39 | 40 | if "Time exceeded (0)" in message: 41 | return 42 | 43 | if self.start_time is None: 44 | self.start_time = time.time() 45 | relative_time = "0:00:00.000" 46 | else: 47 | elapsed_time = time.time() - self.start_time 48 | hours = int(elapsed_time // 3600) 49 | minutes = int((elapsed_time % 3600) // 60) 50 | seconds = int(elapsed_time % 60) 51 | milliseconds = int((elapsed_time % 1) * 1000) 52 | relative_time = f"{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}" 53 | 54 | print(f"{relative_time} {message}") 55 | 56 | def wait_for_input(self): 57 | try: 58 | input("Press any key to exit...") 59 | except EOFError: 60 | pass 61 | self.running = False 62 | 63 | if __name__ == "__main__": 64 | server = SyslogServer() 65 | server.start() 66 | print("Syslog server stopped.") 67 | --------------------------------------------------------------------------------