Merge branch 'probonopd-network' into network

pull/753/head
Ömer Şiar Baysal 5 months ago
commit 2780513503
  1. 22
      .github/workflows/build.yml
  2. 84
      .github/workflows/pr-comment.yml
  3. 85
      README.md
  4. 2
      Synth_Dexed
  5. 2
      circle-stdlib
  6. 187
      src/config.cpp
  7. 101
      src/config.h
  8. 6
      src/config.txt
  9. 59
      src/kernel.cpp
  10. 2
      src/kernel.h
  11. 140
      src/mididevice.cpp
  12. 14
      src/mididevice.h
  13. 67
      src/midikeyboard.cpp
  14. 7
      src/midikeyboard.h
  15. 737
      src/minidexed.cpp
  16. 98
      src/minidexed.h
  17. 28
      src/minidexed.ini
  18. 657
      src/performanceconfig.cpp
  19. 95
      src/performanceconfig.h
  20. 10
      src/serialmididevice.cpp
  21. 3
      src/serialmididevice.h
  22. 17
      src/sysexfileloader.cpp
  23. 1
      src/sysexfileloader.h
  24. 2
      src/uibuttons.h
  25. 419
      src/uimenu.cpp
  26. 8
      src/uimenu.h
  27. 18
      src/usbminidexedmidigadget.h
  28. 76
      src/userinterface.cpp
  29. 7
      src/userinterface.h
  30. 4
      submod.sh

@ -19,6 +19,12 @@ jobs:
- name: Get specific commits of git submodules
run: |
sh -ex ./submod.sh
- name: Apply patches
run: |
# Put git hash in startup message
sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp
# https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505
sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h
- name: Install toolchains
run: |
set -ex
@ -27,6 +33,12 @@ jobs:
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
tar xf *-arm-none-eabi.tar.xz
mkdir -p kernels
- name: Build for Raspberry Pi 5
run: |
set -ex
export PATH=$(readlink -f ./gcc-*aarch64-none*/bin/):$PATH
RPI=5 bash -ex build.sh
cp ./src/kernel*.img ./kernels/
- name: Build for Raspberry Pi 4
run: |
set -ex
@ -51,7 +63,7 @@ jobs:
export PATH=$(readlink -f ./gcc-*arm-none*/bin/):$PATH
RPI=1 bash -ex build.sh
cp ./src/kernel*.img ./kernels/
- name: Get Raspberry Pi boot files
- name: Get Raspberry Pi boot files and WLAN firmware
run: |
set -ex
export PATH=$(readlink -f ./gcc-*aarch64-none*/bin/):$PATH
@ -68,12 +80,18 @@ jobs:
cd sdcard
cp ../kernels/* . || true
cd -
# WLAN firmware
cp circle-stdlib/libs/circle/addon/wlan/sample/hello_wlan/wpa_supplicant.conf sdcard/
mkdir -p sdcard/firmware
cd sdcard/firmware
make -f ../../circle-stdlib/libs/circle/addon/wlan/firmware/Makefile
cd -
- name: Get performance files
run: |
git clone https://github.com/Banana71/Soundplantage --depth 1 # depth 1 means only the latest commit
cp -r ./Soundplantage/performance ./Soundplantage/*.pdf ./sdcard/
cd sdcard
zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d)-$(git rev-parse --short HEAD).zip *
zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y%m%d)-$(git rev-parse --short HEAD).zip *
echo "artifactName=MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
cd -
- uses: actions/upload-artifact@v3

@ -1,64 +1,32 @@
# https://nightly.link/
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
# https://github.com/subsurface/subsurface/blob/master/.github/workflows/artifact-links.yml
name: Add artifact links to pull request
name: Comment on pull request
on:
workflow_run:
workflows: ['Build']
workflows: ["Build"]
types: [completed]
jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
artifacts-url-comments:
name: Add artifact links to PR and issues
runs-on: ubuntu-22.04
# Restrict permissions for the GITHUB_TOKEN, https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
permissions:
issues: write
pull-requests: write
actions: read
steps:
- uses: actions/github-script@v6
with:
# This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
script: |
async function upsertComment(owner, repo, issue_number, purpose, body) {
const {data: comments} = await github.rest.issues.listComments(
{owner, repo, issue_number});
const marker = `<!-- bot: ${purpose} -->`;
body = marker + "\n" + body;
const existing = comments.filter((c) => c.body.includes(marker));
if (existing.length > 0) {
const last = existing[existing.length - 1];
core.info(`Updating comment ${last.id}`);
await github.rest.issues.updateComment({
owner, repo,
body,
comment_id: last.id,
});
} else {
core.info(`Creating a comment in issue / PR #${issue_number}`);
await github.rest.issues.createComment({issue_number, body, owner, repo});
}
}
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
if (!pull_requests.length) {
return core.error("This workflow doesn't match any pull requests!");
}
const artifacts = await github.paginate(
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
for (const art of artifacts) {
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
core.info("Review thread message body:", body);
for (const pr of pull_requests) {
await upsertComment(owner, repo, pr.number,
"nightly-link", body);
}
- name: Add artifact links to PR and issues
if: github.event.workflow_run.event == 'pull_request'
uses: tonyhallett/artifacts-url-comments@0965ff1a7ae03c5c1644d3c30f956effea4e05ef # v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prefix: "Build for testing:"
suffix: "Use at your own risk."
format: name
addTo: pull
errorNoArtifacts: false

@ -1,15 +1,19 @@
# MiniDexed ![](https://github.com/probonopd/MiniDexed/actions/workflows/build.yml/badge.svg)
# MiniDexed ![Github Build Status](https://github.com/probonopd/MiniDexed/actions/workflows/build.yml/badge.svg)
![minidexed](https://user-images.githubusercontent.com/2480569/161813414-bb156a1c-efec-44c0-802a-8926412a08e0.jpg)
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/), and [Synth Geekery](https://www.youtube.com/watch?v=TDSy5nnm0jA).
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/), and [Synth Geekery](https://www.youtube.com/watch?v=TDSy5nnm0jA).
## Demo songs
Listen to some examples made with MiniDexed by Banana71 [here](https://soundcloud.com/soundplantage/sets/minidexed2).
## Features
- [x] Uses [Synth_Dexed](https://codeberg.org/dcoredump/Synth_Dexed) with [circle-stdlib](https://github.com/smuehlst/circle-stdlib)
- [x] SD card contents can be downloaded from [GitHub Releases](../../releases)
- [x] Runs on all Raspberry Pi models (except Pico); see below for details
- [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#i2c-dac) (best)
- [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)
- [x] Supports multiple voices through Program Change and Bank Change LSB/MSB MIDI messages
- [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))
- [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
@ -18,48 +22,49 @@ MiniDexed is a FM synthesizer closely modeled on the famous DX7 by a well-known
- [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)
- [x] Compressor effect
- [x] Reverb effect
- [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-device))
- [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))
## Introduction
Video about this project by [Floyd Steinberg](https://www.youtube.com/watch?v=Z3t94ceMHJo):
[![](https://i.ytimg.com/vi/Z3t94ceMHJo/sddefault.jpg)](https://www.youtube.com/watch?v=Z3t94ceMHJo)
[![YouTube Video about MiniDexed (Floyd Steinberg)](https://i.ytimg.com/vi/Z3t94ceMHJo/sddefault.jpg)](https://www.youtube.com/watch?v=Z3t94ceMHJo)
## System Requirements
* Raspberry Pi 1, 2, 3, 4, or 400 (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)
* A [PCM5102A or PCM5122 based DAC](https://github.com/probonopd/MiniDexed/wiki/Hardware#i2c-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
* 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)
- 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)
- 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.*
- 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
- 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)
## Usage
* In the case of Raspberry Pi 4, Update the firmware and bootloader to the latest version (not doing this may cause USB reliability issues)
* Download from [GitHub Releases](../../releases)
* Unzip
* 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.)
* 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)
* Attach headphones to the headphone jack using `SoundDevice=pwm` in `minidexed.ini` (default) (poor audio quality)
* Alternatively, attach a PCM5102A or PCM5122 based DAC and select i2c sound output using `SoundDevice=i2s` in `minidexed.ini` (best audio quality)
* Alternatively, attach a HDMI display with sound and select HDMI sound output using `SoundDevice=hdmi` in `minidexed.ini` (this may introduce slight latency)
* 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)
* 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`
* Boot
* Start playing
* 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))
* Optionally, put voices in `.syx` files onto the SD card (e.g., using `getsysex.sh`)
* See the Wiki for [Menu](https://github.com/probonopd/MiniDexed/wiki/Menu) operation
* For voice programming, use any DX series editor (using MIDI sysex), including Dexed
* For library management, use the dedicated [MiniDexedLibrarian](https://github.com/BobanSpasic/MiniDexedLibrarian) software
* If something is unclear or does not work, don't hesitate to [ask](https://github.com/probonopd/MiniDexed/discussions/)!
- In the case of Raspberry Pi 4, Update the firmware and bootloader to the latest version (not doing this may cause USB reliability issues)
- Download from [GitHub Releases](../../releases)
- Unzip
- 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.)
- 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)
- Attach headphones to the headphone jack using `SoundDevice=pwm` in `minidexed.ini` (default) (poor audio quality)
- Alternatively, attach a PCM5102A or PCM5122 based DAC and select i2c sound output using `SoundDevice=i2s` in `minidexed.ini` (best audio quality)
- Alternatively, attach a HDMI display with sound and select HDMI sound output using `SoundDevice=hdmi` in `minidexed.ini` (this may introduce slight latency)
- 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)
- 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`
- Boot
- Start playing
- 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))
- Optionally, put voices in `.syx` files onto the SD card (e.g., using `getsysex.sh`)
- See the Wiki for [Menu](https://github.com/probonopd/MiniDexed/wiki/Menu) operation
- For voice programming, use any DX series editor (using MIDI sysex), including Dexed
- For library management, use the dedicated [MiniDexedLibrarian](https://github.com/BobanSpasic/MiniDexedLibrarian) software
- If something is unclear or does not work, don't hesitate to [ask](https://github.com/probonopd/MiniDexed/discussions/)!
## Pinout
All devices on Raspberry Pi GPIOs are **optional**.
![](https://user-images.githubusercontent.com/2480569/166105580-da11481c-8fc7-4375-8ab1-3031ab5c6ad0.png)
![Raspberry Pi Pinout/GPIO Diagram](https://user-images.githubusercontent.com/2480569/166105580-da11481c-8fc7-4375-8ab1-3031ab5c6ad0.png)
Please the the [wiki](https://github.com/probonopd/MiniDexed/wiki) for more information.
Please see the [wiki](https://github.com/probonopd/MiniDexed/wiki) for more information.
## Downloading
@ -71,24 +76,28 @@ Please see the [wiki](https://github.com/probonopd/MiniDexed/wiki/Development#bu
## Contributing
This project lives from the contributions of skilled C++ developers, testers, writers, etc. Please see https://github.com/probonopd/MiniDexed/issues.
This project lives from the contributions of skilled C++ developers, testers, writers, etc. Please see <https://github.com/probonopd/MiniDexed/issues>.
## Discussions
We are happy to hear from you. Please join the discussions on https://github.com/probonopd/MiniDexed/discussions.
We are happy to hear from you. Please join the discussions on <https://github.com/probonopd/MiniDexed/discussions>.
## Documentation
Project documentation is at https://github.com/probonopd/MiniDexed/wiki.
Project documentation is at <https://github.com/probonopd/MiniDexed/wiki>.
## Acknowledgements
This project stands on the shoulders of giants. Special thanks to:
* [raphlinus](https://github.com/raphlinus) for the [MSFA](https://github.com/google/music-synthesizer-for-android) sound engine
* [asb2m10](https://github.com/asb2m10/dexed) for the [Dexed](https://github.com/asb2m10/dexed) software
* [dcoredump](https://github.com/dcoredump) for https://codeberg.org/dcoredump/Synth_Dexed, a port of Dexed for embedded systems
* [rsta2](https://github.com/rsta2) for 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
* [smuehlst](https://github.com/smuehlst) for https://github.com/smuehlst/circle-stdlib, a version with Standard C and C++ library support
* [Banana71](https://github.com/Banana71) for the sound design of the [Soundplantage](https://github.com/Banana71/Soundplantage) performances shipped with MiniDexed
* [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)
- [raphlinus](https://github.com/raphlinus) for the [MSFA](https://github.com/google/music-synthesizer-for-android) sound engine
- [asb2m10](https://github.com/asb2m10/dexed) for the [Dexed](https://github.com/asb2m10/dexed) software
- [dcoredump](https://github.com/dcoredump) for [Synth Dexed](https://codeberg.org/dcoredump/Synth_Dexed), a port of Dexed for embedded systems
- [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
- [smuehlst](https://github.com/smuehlst) for [circle-stdlib](https://github.com/smuehlst/circle-stdlib), a version with Standard C and C++ library support
- [Banana71](https://github.com/Banana71) for the sound design of the [Soundplantage](https://github.com/Banana71/Soundplantage) performances shipped with MiniDexed
- [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)
- [diyelectromusic](https://github.com/diyelectromusic/) for many [contributions](https://github.com/probonopd/MiniDexed/commits?author=diyelectromusic)
## Stargazers over time
[![Stargazers over time](https://starchart.cc/probonopd/MiniDexed.svg?variant=adaptive)](https://starchart.cc/probonopd/MiniDexed)

@ -1 +1 @@
Subproject commit 8c677ceb4b3fb73f8643e30ff6cf4158dc8b9e53
Subproject commit c9f52741a802ad9bb01263823650f7cc3b0b5108

@ -1 +1 @@
Subproject commit 61cf3a47bf93628039078b7c840e44432e52343e
Subproject commit 3bd135d35ac23c7c726924cb85b5890cbf80bbfe

@ -36,20 +36,43 @@ void CConfig::Load (void)
{
m_Properties.Load ();
m_bUSBGadgetMode = m_Properties.GetNumber ("USBGadget", 0) != 0;
// Number of Tone Generators and Polyphony
m_nToneGenerators = m_Properties.GetNumber ("ToneGenerators", DefToneGenerators);
m_nPolyphony = m_Properties.GetNumber ("Polyphony", DefaultNotes);
// At present there are only two options for tone generators: min or max
// and for the Pi 1,2,3 these are the same anyway.
if ((m_nToneGenerators != MinToneGenerators) && (m_nToneGenerators != AllToneGenerators))
{
m_nToneGenerators = DefToneGenerators;
}
if (m_nPolyphony > MaxNotes)
{
m_nPolyphony = DefaultNotes;
}
m_bUSBGadget = m_Properties.GetNumber ("USBGadget", 0) != 0;
m_nUSBGadgetPin = m_Properties.GetNumber ("USBGadgetPin", 0); // Default OFF
SetUSBGadgetMode(m_bUSBGadget); // Might get overriden later by USBGadgetPin state
m_SoundDevice = m_Properties.GetString ("SoundDevice", "pwm");
m_nSampleRate = m_Properties.GetNumber ("SampleRate", 48000);
m_bQuadDAC8Chan = m_Properties.GetNumber ("QuadDAC8Chan", 0) != 0;
if (m_SoundDevice == "hdmi") {
m_nChunkSize = m_Properties.GetNumber ("ChunkSize", 384*6);
}
else
{
#ifdef ARM_ALLOW_MULTI_CORE
m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 256);
m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_bQuadDAC8Chan ? 1024 : 256); // 128 per channel
#else
m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 1024);
m_nChunkSize = m_Properties.GetNumber ("ChunkSize", 1024);
#endif
}
m_nDACI2CAddress = m_Properties.GetNumber ("DACI2CAddress", 0);
m_bChannelsSwapped = m_Properties.GetNumber ("ChannelsSwapped", 0) != 0;
unsigned newEngineType = m_Properties.GetNumber ("EngineType", 1);
unsigned newEngineType = m_Properties.GetNumber ("EngineType", 1);
if (newEngineType == 2) {
m_EngineType = MKI;
} else if (newEngineType == 3) {
@ -82,9 +105,13 @@ void CConfig::Load (void)
m_bMIDIRXProgramChange = m_Properties.GetNumber ("MIDIRXProgramChange", 1) != 0;
m_bIgnoreAllNotesOff = m_Properties.GetNumber ("IgnoreAllNotesOff", 0) != 0;
m_bMIDIAutoVoiceDumpOnPC = m_Properties.GetNumber ("MIDIAutoVoiceDumpOnPC", 1) != 0;
m_bMIDIAutoVoiceDumpOnPC = m_Properties.GetNumber ("MIDIAutoVoiceDumpOnPC", 0) != 0;
m_bHeaderlessSysExVoices = m_Properties.GetNumber ("HeaderlessSysExVoices", 0) != 0;
m_bExpandPCAcrossBanks = m_Properties.GetNumber ("ExpandPCAcrossBanks", 1) != 0;
m_nMIDISystemCCVol = m_Properties.GetNumber ("MIDISystemCCVol", 0);
m_nMIDISystemCCPan = m_Properties.GetNumber ("MIDISystemCCPan", 0);
m_nMIDISystemCCDetune = m_Properties.GetNumber ("MIDISystemCCDetune", 0);
m_bLCDEnabled = m_Properties.GetNumber ("LCDEnabled", 0) != 0;
m_nLCDPinEnable = m_Properties.GetNumber ("LCDPinEnable", 4);
@ -102,6 +129,20 @@ void CConfig::Load (void)
m_bSSD1306LCDRotate = m_Properties.GetNumber ("SSD1306LCDRotate", 0) != 0;
m_bSSD1306LCDMirror = m_Properties.GetNumber ("SSD1306LCDMirror", 0) != 0;
m_nSPIBus = m_Properties.GetNumber ("SPIBus", SPI_INACTIVE); // Disabled by default
m_nSPIMode = m_Properties.GetNumber ("SPIMode", SPI_DEF_MODE);
m_nSPIClockKHz = m_Properties.GetNumber ("SPIClockKHz", SPI_DEF_CLOCK);
m_bST7789Enabled = m_Properties.GetNumber ("ST7789Enabled", 0) != 0;
m_nST7789Data = m_Properties.GetNumber ("ST7789Data", 0);
m_nST7789Select = m_Properties.GetNumber ("ST7789Select", 0);
m_nST7789Reset = m_Properties.GetNumber ("ST7789Reset", 0); // optional
m_nST7789Backlight = m_Properties.GetNumber ("ST7789Backlight", 0); // optional
m_nST7789Width = m_Properties.GetNumber ("ST7789Width", 240);
m_nST7789Height = m_Properties.GetNumber ("ST7789Height", 240);
m_nST7789Rotation = m_Properties.GetNumber ("ST7789Rotation", 0);
m_bST7789SmallFont = m_Properties.GetNumber ("ST7789SmallFont", 0) != 0;
m_nLCDColumns = m_Properties.GetNumber ("LCDColumns", 16);
m_nLCDRows = m_Properties.GetNumber ("LCDRows", 2);
@ -164,11 +205,68 @@ void CConfig::Load (void)
m_INetworkDNSServer = m_Properties.GetIPAddress("NetworkDNSServer") != 0;
}
unsigned CConfig::GetToneGenerators (void) const
{
return m_nToneGenerators;
}
unsigned CConfig::GetPolyphony (void) const
{
return m_nPolyphony;
}
unsigned CConfig::GetTGsCore1 (void) const
{
#ifndef ARM_ALLOW_MULTI_CORE
return 0;
#else
if (m_nToneGenerators > MinToneGenerators)
{
return TGsCore1 + TGsCore1Opt;
}
else
{
return TGsCore1;
}
#endif
}
unsigned CConfig::GetTGsCore23 (void) const
{
#ifndef ARM_ALLOW_MULTI_CORE
return 0;
#else
if (m_nToneGenerators > MinToneGenerators)
{
return TGsCore23 + TGsCore23Opt;
}
else
{
return TGsCore23;
}
#endif
}
bool CConfig::GetUSBGadget (void) const
{
return m_bUSBGadget;
}
unsigned CConfig::GetUSBGadgetPin (void) const
{
return m_nUSBGadgetPin;
}
bool CConfig::GetUSBGadgetMode (void) const
{
return m_bUSBGadgetMode;
}
void CConfig::SetUSBGadgetMode (bool USBGadgetMode)
{
m_bUSBGadgetMode = USBGadgetMode;
}
const char *CConfig::GetSoundDevice (void) const
{
return m_SoundDevice.c_str ();
@ -199,6 +297,11 @@ unsigned CConfig::GetEngineType (void) const
return m_EngineType;
}
bool CConfig::GetQuadDAC8Chan (void) const
{
return m_bQuadDAC8Chan;
}
unsigned CConfig::GetMIDIBaudRate (void) const
{
return m_nMIDIBaudRate;
@ -239,6 +342,21 @@ bool CConfig::GetExpandPCAcrossBanks (void) const
return m_bExpandPCAcrossBanks;
}
unsigned CConfig::GetMIDISystemCCVol (void) const
{
return m_nMIDISystemCCVol;
}
unsigned CConfig::GetMIDISystemCCPan (void) const
{
return m_nMIDISystemCCPan;
}
unsigned CConfig::GetMIDISystemCCDetune (void) const
{
return m_nMIDISystemCCDetune;
}
bool CConfig::GetLCDEnabled (void) const
{
return m_bLCDEnabled;
@ -309,6 +427,65 @@ bool CConfig::GetSSD1306LCDMirror (void) const
return m_bSSD1306LCDMirror;
}
unsigned CConfig::GetSPIBus (void) const
{
return m_nSPIBus;
}
unsigned CConfig::GetSPIMode (void) const
{
return m_nSPIMode;
}
unsigned CConfig::GetSPIClockKHz (void) const
{
return m_nSPIClockKHz;
}
bool CConfig::GetST7789Enabled (void) const
{
return m_bST7789Enabled;
}
unsigned CConfig::GetST7789Data (void) const
{
return m_nST7789Data;
}
unsigned CConfig::GetST7789Select (void) const
{
return m_nST7789Select;
}
unsigned CConfig::GetST7789Reset (void) const
{
return m_nST7789Reset;
}
unsigned CConfig::GetST7789Backlight (void) const
{
return m_nST7789Backlight;
}
unsigned CConfig::GetST7789Width (void) const
{
return m_nST7789Width;
}
unsigned CConfig::GetST7789Height (void) const
{
return m_nST7789Height;
}
unsigned CConfig::GetST7789Rotation (void) const
{
return m_nST7789Rotation;
}
bool CConfig::GetST7789SmallFont (void) const
{
return m_bST7789SmallFont;
}
unsigned CConfig::GetLCDColumns (void) const
{
return m_nLCDColumns;

@ -29,21 +29,56 @@
#include <circle/sysconfig.h>
#include <string>
#define SPI_INACTIVE 255
#define SPI_DEF_CLOCK 15000 // kHz
#define SPI_DEF_MODE 0 // Default mode (0,1,2,3)
class CConfig // Configuration for MiniDexed
{
public:
// Set maximum, minimum and default numbers of tone generators, depending on Pi version.
// Actual number in can be changed via config settings for some Pis.
#ifndef ARM_ALLOW_MULTI_CORE
static const unsigned ToneGenerators = 1;
// Pi V1 or Zero (single core)
static const unsigned MinToneGenerators = 1;
static const unsigned AllToneGenerators = 1;
static const unsigned DefToneGenerators = AllToneGenerators;
#else
#if (RASPPI==4 || RASPPI==5)
// Pi 4 and 5 quad core
// These are max values, default is to support 8 in total with optional 16 TGs
static const unsigned TGsCore1 = 2; // process 2 TGs on core 1
static const unsigned TGsCore23 = 3; // process 3 TGs on core 2 and 3 each
static const unsigned TGsCore1Opt = 2; // process optional additional 2 TGs on core 1
static const unsigned TGsCore23Opt = 3; // process optional additional 3 TGs on core 2 and 3 each
static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23;
static const unsigned AllToneGenerators = TGsCore1 + TGsCore1Opt + 2*TGsCore23 + 2*TGsCore23Opt;
static const unsigned DefToneGenerators = MinToneGenerators;
#else
// Pi 2 or 3 quad core
static const unsigned TGsCore1 = 2; // process 2 TGs on core 1
static const unsigned TGsCore23 = 3; // process 3 TGs on core 2 and 3 each
static const unsigned ToneGenerators = TGsCore1 + 2*TGsCore23;
static const unsigned TGsCore1Opt = 0;
static const unsigned TGsCore23Opt = 0;
static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23;
static const unsigned AllToneGenerators = MinToneGenerators;
static const unsigned DefToneGenerators = AllToneGenerators;
#endif
#endif
// Set maximum polyphony, depending on PI version. This can be changed via config settings
#if RASPPI == 1
static const unsigned MaxNotes = 8; // polyphony
static const unsigned MaxNotes = 8;
static const unsigned DefaultNotes = 8;
#elif RASPPI == 4
static const unsigned MaxNotes = 32;
static const unsigned DefaultNotes = 24;
#elif RASPPI == 5
static const unsigned MaxNotes = 32;
static const unsigned DefaultNotes = 32;
#else
static const unsigned MaxNotes = 16;
static const unsigned DefaultNotes = 16;
#endif
static const unsigned MaxChunkSize = 4096;
@ -64,8 +99,17 @@ public:
void Load (void);
// TGs and Polyphony
unsigned GetToneGenerators (void) const;
unsigned GetPolyphony (void) const;
unsigned GetTGsCore1 (void) const;
unsigned GetTGsCore23 (void) const;
// USB Mode
bool GetUSBGadgetMode (void) const; // true if in USB gadget mode
bool GetUSBGadget (void) const;
unsigned GetUSBGadgetPin (void) const;
bool GetUSBGadgetMode (void) const; // true if in USB gadget mode depending on USBGadget and USBGadgetPin
void SetUSBGadgetMode (bool USBGadgetMode);
// Sound device
const char *GetSoundDevice (void) const;
@ -74,6 +118,7 @@ public:
unsigned GetDACI2CAddress (void) const; // 0 for auto probing
bool GetChannelsSwapped (void) const;
unsigned GetEngineType (void) const;
bool GetQuadDAC8Chan (void) const; // false if not specified
// MIDI
unsigned GetMIDIBaudRate (void) const;
@ -81,9 +126,12 @@ public:
const char *GetMIDIThruOut (void) const; // "" if not specified
bool GetMIDIRXProgramChange (void) const; // true if not specified
bool GetIgnoreAllNotesOff (void) const;
bool GetMIDIAutoVoiceDumpOnPC (void) const; // true if not specified
bool GetMIDIAutoVoiceDumpOnPC (void) const; // false if not specified
bool GetHeaderlessSysExVoices (void) const; // false if not specified
bool GetExpandPCAcrossBanks (void) const; // true if not specified
unsigned GetMIDISystemCCVol (void) const;
unsigned GetMIDISystemCCPan (void) const;
unsigned GetMIDISystemCCDetune (void) const;
// HD44780 LCD
// GPIO pin numbers are chip numbers, not header positions
@ -104,6 +152,22 @@ public:
bool GetSSD1306LCDRotate (void) const;
bool GetSSD1306LCDMirror (void) const;
// SPI support
unsigned GetSPIBus (void) const;
unsigned GetSPIMode (void) const;
unsigned GetSPIClockKHz (void) const;
// ST7789 LCD
bool GetST7789Enabled (void) const;
unsigned GetST7789Data (void) const;
unsigned GetST7789Select (void) const;
unsigned GetST7789Reset (void) const;
unsigned GetST7789Backlight (void) const;
unsigned GetST7789Width (void) const;
unsigned GetST7789Height (void) const;
unsigned GetST7789Rotation (void) const;
bool GetST7789SmallFont (void) const;
unsigned GetLCDColumns (void) const;
unsigned GetLCDRows (void) const;
@ -182,6 +246,11 @@ public:
private:
CPropertiesFatFsFile m_Properties;
unsigned m_nToneGenerators;
unsigned m_nPolyphony;
bool m_bUSBGadget;
unsigned m_nUSBGadgetPin;
bool m_bUSBGadgetMode;
std::string m_SoundDevice;
@ -190,6 +259,7 @@ private:
unsigned m_nDACI2CAddress;
bool m_bChannelsSwapped;
unsigned m_EngineType;
bool m_bQuadDAC8Chan;
unsigned m_nMIDIBaudRate;
std::string m_MIDIThruIn;
@ -199,6 +269,9 @@ private:
bool m_bMIDIAutoVoiceDumpOnPC;
bool m_bHeaderlessSysExVoices;
bool m_bExpandPCAcrossBanks;
unsigned m_nMIDISystemCCVol;
unsigned m_nMIDISystemCCPan;
unsigned m_nMIDISystemCCDetune;
bool m_bLCDEnabled;
unsigned m_nLCDPinEnable;
@ -215,7 +288,21 @@ private:
unsigned m_nSSD1306LCDHeight;
bool m_bSSD1306LCDRotate;
bool m_bSSD1306LCDMirror;
unsigned m_nSPIBus;
unsigned m_nSPIMode;
unsigned m_nSPIClockKHz;
bool m_bST7789Enabled;
unsigned m_nST7789Data;
unsigned m_nST7789Select;
unsigned m_nST7789Reset;
unsigned m_nST7789Backlight;
unsigned m_nST7789Width;
unsigned m_nST7789Height;
unsigned m_nST7789Rotation;
unsigned m_bST7789SmallFont;
unsigned m_nLCDColumns;
unsigned m_nLCDRows;

@ -10,7 +10,7 @@ gpu_mem=16
disable_overscan=0
#
# Use 64-bit for RPi 3, 4, 400 and Zero 2, and 32-bit for all other models
# Use 64-bit for RPi 3, 4, 400, 5 and Zero 2, and 32-bit for all other models
#
[pi3]
@ -24,3 +24,7 @@ kernel=kernel8-rpi4.img
# Zero 2 W
[pi02]
arm_64bit=1
[pi5]
arm_64bit=1
kernel=kernel_2712.img

@ -20,6 +20,7 @@
#include "kernel.h"
#include <circle/logger.h>
#include <circle/synchronize.h>
#include <circle/gpiopin.h>
#include <assert.h>
#include <circle/usb/usbhcidevice.h>
#include "usbminidexedmidigadget.h"
@ -36,6 +37,7 @@ CKernel::CKernel (void)
m_Config (&mFileSystem),
m_GPIOManager (&mInterrupt),
m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE),
m_pSPIMaster (nullptr),
m_pDexed (0)
{
s_pThis = this;
@ -69,23 +71,76 @@ bool CKernel::Initialize (void)
m_Config.Load ();
if (m_Config.GetUSBGadgetMode())
unsigned nSPIMaster = m_Config.GetSPIBus();
unsigned nSPIMode = m_Config.GetSPIMode();
unsigned long nSPIClock = 1000 * m_Config.GetSPIClockKHz();
#if RASPPI<4
// By default older RPI versions use SPI 0.
// It is possible to build circle to support SPI 1 for
// devices that use the 40-pin header, but that isn't
// enabled at present...
if (nSPIMaster == 0)
#else
// RPI 4+ has several possible SPI Bus Configurations.
// As mentioned above, SPI 1 is not built by default.
// See circle/include/circle/spimaster.h
if (nSPIMaster == 0 || nSPIMaster == 3 || nSPIMaster == 4 || nSPIMaster == 5 || nSPIMaster == 6)
#endif
{
unsigned nCPHA = (nSPIMode & 1) ? 1 : 0;
unsigned nCPOL = (nSPIMode & 2) ? 1 : 0;
m_pSPIMaster = new CSPIMaster (nSPIClock, nCPOL, nCPHA, nSPIMaster);
if (!m_pSPIMaster->Initialize())
{
delete (m_pSPIMaster);
m_pSPIMaster = nullptr;
}
}
bool bUSBGadgetMode = false;
if (m_Config.GetUSBGadget())
{
unsigned nUSBGadgetPin = m_Config.GetUSBGadgetPin();
if (nUSBGadgetPin == 0)
{
// No hardware config option
bUSBGadgetMode = true;
}
else
{
// State of USB Gadget Mode determined by state of the pin.
// Pulled down = enable USB Gadget mode
CGPIOPin usbGadgetPin(nUSBGadgetPin, GPIOModeInputPullUp);
if (usbGadgetPin.Read() == 0)
{
bUSBGadgetMode = true;
}
}
}
if (bUSBGadgetMode)
{
#if RASPPI==5
#warning No support for USB Gadget Mode on RPI 5 yet
#else
// Run the USB stack in USB Gadget (device) mode
m_pUSB = new CUSBMiniDexedMIDIGadget (&mInterrupt);
#endif
}
else
{
// Run the USB stack in USB Host (default) mode
m_pUSB = new CUSBHCIDevice (&mInterrupt, &mTimer, TRUE);
}
m_Config.SetUSBGadgetMode(bUSBGadgetMode);
if (!m_pUSB->Initialize ())
{
return FALSE;
}
m_pDexed = new CMiniDexed (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster,
m_pDexed = new CMiniDexed (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster, m_pSPIMaster,
&mFileSystem);
assert (m_pDexed);

@ -24,6 +24,7 @@
#include <circle/cputhrottle.h>
#include <circle/gpiomanager.h>
#include <circle/i2cmaster.h>
#include <circle/spimaster.h>
#include <circle/usb/usbcontroller.h>
#include <circle/sched/scheduler.h>
#include "config.h"
@ -55,6 +56,7 @@ private:
CCPUThrottle m_CPUThrottle;
CGPIOManager m_GPIOManager;
CI2CMaster m_I2CMaster;
CSPIMaster *m_pSPIMaster;
CMiniDexed *m_pDexed;
CUSBController *m_pUSB;

@ -53,6 +53,21 @@ LOGMODULE ("mididevice");
#define MIDI_PROGRAM_CHANGE 0b1100
#define MIDI_PITCH_BEND 0b1110
// MIDI "System" level (i.e. all TG) custom CC maps
// Note: Even if number of TGs is not 8, there are only 8
// available to be used in the mappings here.
#define NUM_MIDI_CC_MAPS 8
const unsigned MIDISystemCCMap[NUM_MIDI_CC_MAPS][8] = {
{0,0,0,0,0,0,0,0}, // 0 = disabled
{16,17,18,19,80,81,82,83}, // 1 = General Purpose Controllers 1-8
{20,21,22,23,24,25,26,27},
{52,53,54,55,56,57,58,59},
{102,103,104,105,106,107,108,109},
{110,111,112,113,114,115,116,117},
{3,9,14,15,28,29,30,31},
{35,41,46,47,60,61,62,63}
};
#define MIDI_SYSTEM_EXCLUSIVE_BEGIN 0xF0
#define MIDI_SYSTEM_EXCLUSIVE_END 0xF7
#define MIDI_TIMING_CLOCK 0xF8
@ -65,10 +80,38 @@ CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInter
m_pConfig (pConfig),
m_pUI (pUI)
{
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
for (unsigned nTG = 0; nTG < CConfig::AllToneGenerators; nTG++)
{
m_ChannelMap[nTG] = Disabled;
}
m_nMIDISystemCCVol = m_pConfig->GetMIDISystemCCVol();
m_nMIDISystemCCPan = m_pConfig->GetMIDISystemCCPan();
m_nMIDISystemCCDetune = m_pConfig->GetMIDISystemCCDetune();
m_MIDISystemCCBitmap[0] = 0;
m_MIDISystemCCBitmap[1] = 0;
m_MIDISystemCCBitmap[2] = 0;
m_MIDISystemCCBitmap[3] = 0;
for (int tg=0; tg<8; tg++)
{
if (m_nMIDISystemCCVol != 0) {
u8 cc = MIDISystemCCMap[m_nMIDISystemCCVol][tg];
m_MIDISystemCCBitmap[cc>>5] |= (1<<(cc%32));
}
if (m_nMIDISystemCCPan != 0) {
u8 cc = MIDISystemCCMap[m_nMIDISystemCCPan][tg];
m_MIDISystemCCBitmap[cc>>5] |= (1<<(cc%32));
}
if (m_nMIDISystemCCDetune != 0) {
u8 cc = MIDISystemCCMap[m_nMIDISystemCCDetune][tg];
m_MIDISystemCCBitmap[cc>>5] |= (1<<(cc%32));
}
}
if (m_pConfig->GetMIDIDumpEnabled ()) {
LOGNOTE("MIDI System CC Map: %08X %08X %08X %08X", m_MIDISystemCCBitmap[3],m_MIDISystemCCBitmap[2],m_MIDISystemCCBitmap[1],m_MIDISystemCCBitmap[0]);
}
}
CMIDIDevice::~CMIDIDevice (void)
@ -78,13 +121,13 @@ CMIDIDevice::~CMIDIDevice (void)
void CMIDIDevice::SetChannel (u8 ucChannel, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (nTG < CConfig::AllToneGenerators);
m_ChannelMap[nTG] = ucChannel;
}
u8 CMIDIDevice::GetChannel (unsigned nTG) const
{
assert (nTG < CConfig::ToneGenerators);
assert (nTG < CConfig::AllToneGenerators);
return m_ChannelMap[nTG];
}
@ -184,9 +227,35 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
else
{
// Perform any MiniDexed level MIDI handling before specific Tone Generators
unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel();
switch (ucType)
{
case MIDI_CONTROL_CHANGE:
// Check for performance PC messages
if (nPerfCh != Disabled)
{
if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode))
{
if (pMessage[1] == MIDI_CC_BANK_SELECT_MSB)
{
m_pSynthesizer->BankSelectMSBPerformance (pMessage[2]);
}
else if (pMessage[1] == MIDI_CC_BANK_SELECT_LSB)
{
m_pSynthesizer->BankSelectLSBPerformance (pMessage[2]);
}
else
{
// Ignore any other CC messages at this time
}
}
}
if (nLength == 3)
{
m_pUI->UIMIDICmdHandler (ucChannel, ucStatus & 0xF0, pMessage[1], pMessage[2]);
}
break;
case MIDI_NOTE_OFF:
case MIDI_NOTE_ON:
if (nLength < 3)
@ -195,11 +264,11 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
}
m_pUI->UIMIDICmdHandler (ucChannel, ucStatus & 0xF0, pMessage[1], pMessage[2]);
break;
case MIDI_PROGRAM_CHANGE:
// Check for performance PC messages
if( m_pConfig->GetMIDIRXProgramChange() )
{
unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel();
if( nPerfCh != Disabled)
{
if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode))
@ -211,8 +280,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
break;
}
// Process MIDI for each Tone Generator
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
// Process MIDI for each active Tone Generator
bool bSystemCCHandled = false;
bool bSystemCCChecked = false;
for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++)
{
if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN)
{
@ -345,6 +416,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
m_pSynthesizer->notesOff (pMessage[2], nTG);
}
break;
default:
// Check for system-level, cross-TG MIDI Controls, but only do it once.
// Also, if successfully handled, then no need to process other TGs,
// so it is possible to break out of the main TG loop too.
// Note: We handle this here so we get the TG MIDI channel checking.
if (!bSystemCCChecked) {
bSystemCCHandled = HandleMIDISystemCC(pMessage[1], pMessage[2]);
bSystemCCChecked = true;
}
break;
}
break;
@ -390,6 +472,52 @@ void CMIDIDevice::AddDevice (const char *pDeviceName)
s_DeviceMap.insert (std::pair<std::string, CMIDIDevice *> (pDeviceName, this));
}
bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval)
{
// This only makes sense when there are at least 8 TGs.
// Note: If more than 8 TGs then only 8 TGs are controllable this way.
if (m_pConfig->GetToneGenerators() < 8) {
return false;
}
// Quickly reject any CCs not in the configured maps
if ((m_MIDISystemCCBitmap[ucCC>>5] & (1<<(ucCC%32))) == 0) {
// Not in the map
return false;
}
// Not looking for duplicate CCs so return once handled
for (unsigned tg=0; tg<8; tg++) {
if (m_nMIDISystemCCVol != 0) {
if (ucCC == MIDISystemCCMap[m_nMIDISystemCCVol][tg]) {
m_pSynthesizer->SetVolume (ucCCval, tg);
return true;
}
}
if (m_nMIDISystemCCPan != 0) {
if (ucCC == MIDISystemCCMap[m_nMIDISystemCCPan][tg]) {
m_pSynthesizer->SetPan (ucCCval, tg);
return true;
}
}
if (m_nMIDISystemCCDetune != 0) {
if (ucCC == MIDISystemCCMap[m_nMIDISystemCCDetune][tg]) {
if (ucCCval == 0)
{
m_pSynthesizer->SetMasterTune (0, tg);
}
else
{
m_pSynthesizer->SetMasterTune (maplong (ucCCval, 1, 127, -99, 99), tg);
}
return true;
}
}
}
return false;
}
void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG)
{
int16_t sysex_return;

@ -30,6 +30,9 @@
#include <circle/spinlock.h>
#include "userinterface.h"
#define MAX_DX7_SYSEX_LENGTH 4104
#define MAX_MIDI_MESSAGE MAX_DX7_SYSEX_LENGTH
class CMiniDexed;
class CMIDIDevice
@ -57,12 +60,21 @@ protected:
void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0);
void AddDevice (const char *pDeviceName);
void HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG);
private:
bool HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval);
private:
CMiniDexed *m_pSynthesizer;
CConfig *m_pConfig;
CUserInterface *m_pUI;
u8 m_ChannelMap[CConfig::ToneGenerators];
u8 m_ChannelMap[CConfig::AllToneGenerators];
unsigned m_nMIDISystemCCVol;
unsigned m_nMIDISystemCCPan;
unsigned m_nMIDISystemCCDetune;
u32 m_MIDISystemCCBitmap[4]; // to allow for 128 bit entries
std::string m_DeviceName;

@ -37,6 +37,7 @@ TMIDIPacketHandler * const CMIDIKeyboard::s_pMIDIPacketHandler[MaxInstances] =
CMIDIKeyboard::CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI, unsigned nInstance)
: CMIDIDevice (pSynthesizer, pConfig, pUI),
m_nSysExIdx (0),
m_nInstance (nInstance),
m_pMIDIDevice (0)
{
@ -100,28 +101,86 @@ void CMIDIKeyboard::Send (const u8 *pMessage, size_t nLength, unsigned nCable)
m_SendQueue.push (Entry);
}
// Most packets will be passed straight onto the main MIDI message handler
// but SysEx messages are multiple USB packets and so will need building up
// before parsing.
void CMIDIKeyboard::USBMIDIMessageHandler (u8 *pPacket, unsigned nLength, unsigned nCable)
{
if ((pPacket[0] == 0xF0) && (m_nSysExIdx == 0))
{
// Start of SysEx message
//printf("SysEx Start Idx=%d, (%d)\n", m_nSysExIdx, nLength);
for (unsigned i=0; i<USB_SYSEX_BUFFER_SIZE; i++) {
m_SysEx[i] = 0;
}
for (unsigned i=0; i<nLength; i++) {
m_SysEx[m_nSysExIdx++] = pPacket[i];
}
}
else if (m_nSysExIdx != 0)
{
// Continue processing SysEx message
//printf("SysEx Packet Idx=%d, (%d)\n", m_nSysExIdx, nLength);
for (unsigned i=0; i<nLength; i++) {
if (pPacket[i] == 0xF8 || pPacket[i] == 0xFA || pPacket[i] == 0xFB || pPacket[i] == 0xFC || pPacket[i] == 0xFE || pPacket[i] == 0xFF) {
// Singe-byte System Realtime Messages can happen at any time!
MIDIMessageHandler (&pPacket[i], 1, nCable);
}
else if (m_nSysExIdx >= USB_SYSEX_BUFFER_SIZE) {
// Run out of space, so reset and ignore rest of the message
m_nSysExIdx = 0;
break;
}
else if (pPacket[i] == 0xF7) {
// End of SysEx message
m_SysEx[m_nSysExIdx++] = pPacket[i];
//printf ("SysEx End Idx=%d\n", m_nSysExIdx);
MIDIMessageHandler (m_SysEx, m_nSysExIdx, nCable);
// Reset ready for next time
m_nSysExIdx = 0;
}
else if ((pPacket[i] & 0x80) != 0) {
// Received another command, so reset processing as something has gone wrong
//printf ("SysEx Reset\n");
m_nSysExIdx = 0;
break;
}
else
{
// Store the byte
m_SysEx[m_nSysExIdx++] = pPacket[i];
}
}
}
else
{
// Assume it is a standard message
MIDIMessageHandler (pPacket, nLength, nCable);
}
}
void CMIDIKeyboard::MIDIPacketHandler0 (unsigned nCable, u8 *pPacket, unsigned nLength)
{
assert (s_pThis[0] != 0);
s_pThis[0]->MIDIMessageHandler (pPacket, nLength, nCable);
s_pThis[0]->USBMIDIMessageHandler (pPacket, nLength, nCable);
}
void CMIDIKeyboard::MIDIPacketHandler1 (unsigned nCable, u8 *pPacket, unsigned nLength)
{
assert (s_pThis[1] != 0);
s_pThis[1]->MIDIMessageHandler (pPacket, nLength, nCable);
s_pThis[1]->USBMIDIMessageHandler (pPacket, nLength, nCable);
}
void CMIDIKeyboard::MIDIPacketHandler2 (unsigned nCable, u8 *pPacket, unsigned nLength)
{
assert (s_pThis[2] != 0);
s_pThis[2]->MIDIMessageHandler (pPacket, nLength, nCable);
s_pThis[2]->USBMIDIMessageHandler (pPacket, nLength, nCable);
}
void CMIDIKeyboard::MIDIPacketHandler3 (unsigned nCable, u8 *pPacket, unsigned nLength)
{
assert (s_pThis[3] != 0);
s_pThis[3]->MIDIMessageHandler (pPacket, nLength, nCable);
s_pThis[3]->USBMIDIMessageHandler (pPacket, nLength, nCable);
}
void CMIDIKeyboard::DeviceRemovedHandler (CDevice *pDevice, void *pContext)

@ -31,6 +31,8 @@
#include <circle/types.h>
#include <queue>
#define USB_SYSEX_BUFFER_SIZE (MAX_DX7_SYSEX_LENGTH+128) // Allow a bit spare to handle unexpected SysEx messages
class CMiniDexed;
class CMIDIKeyboard : public CMIDIDevice
@ -53,6 +55,8 @@ private:
static void MIDIPacketHandler3 (unsigned nCable, u8 *pPacket, unsigned nLength);
static void DeviceRemovedHandler (CDevice *pDevice, void *pContext);
void USBMIDIMessageHandler (u8 *pPacket, unsigned nLength, unsigned nCable);
private:
struct TSendQueueEntry
@ -61,6 +65,8 @@ private:
size_t nLength;
unsigned nCable;
};
uint8_t m_SysEx[USB_SYSEX_BUFFER_SIZE];
unsigned m_nSysExIdx;
private:
unsigned m_nInstance;
@ -73,6 +79,7 @@ private:
static CMIDIKeyboard *s_pThis[MaxInstances];
static TMIDIPacketHandler * const s_pMIDIPacketHandler[MaxInstances];
};
#endif

File diff suppressed because it is too large Load Diff

@ -36,6 +36,7 @@
#include <circle/interrupt.h>
#include <circle/gpiomanager.h>
#include <circle/i2cmaster.h>
#include <circle/spimaster.h>
#include <circle/multicore.h>
#include <circle/sound/soundbasedevice.h>
#include <circle/sched/scheduler.h>
@ -58,7 +59,7 @@ class CMiniDexed
{
public:
CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, FATFS *pFileSystem);
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CSPIMaster *pSPIMaster, FATFS *pFileSystem);
bool Initialize (void);
@ -68,10 +69,14 @@ public:
void Run (unsigned nCore);
#endif
CSysExFileLoader *GetSysExFileLoader (void);
CPerformanceConfig *GetPerformanceConfig (void);
void BankSelect (unsigned nBank, unsigned nTG);
void BankSelectPerformance (unsigned nBank);
void BankSelectMSB (unsigned nBankMSB, unsigned nTG);
void BankSelectMSBPerformance (unsigned nBankMSB);
void BankSelectLSB (unsigned nBankLSB, unsigned nTG);
void BankSelectLSBPerformance (unsigned nBankLSB);
void ProgramChange (unsigned nProgram, unsigned nTG);
void ProgramChangePerformance (unsigned nProgram);
void SetVolume (unsigned nVolume, unsigned nTG);
@ -123,17 +128,27 @@ public:
std::string GetPerformanceFileName(unsigned nID);
std::string GetPerformanceName(unsigned nID);
unsigned GetLastPerformance();
unsigned GetPerformanceBank();
unsigned GetLastPerformanceBank();
unsigned GetActualPerformanceID();
void SetActualPerformanceID(unsigned nID);
unsigned GetActualPerformanceBankID();
void SetActualPerformanceBankID(unsigned nBankID);
bool SetNewPerformance(unsigned nID);
bool SetNewPerformanceBank(unsigned nBankID);
void SetFirstPerformance(void);
void DoSetFirstPerformance(void);
bool SavePerformanceNewFile ();
bool DoSavePerformanceNewFile (void);
bool DoSetNewPerformance (void);
bool DoSetNewPerformanceBank (void);
bool GetPerformanceSelectToLoad(void);
bool SavePerformance (bool bSaveAsDeault);
unsigned GetPerformanceSelectChannel (void);
void SetPerformanceSelectChannel (unsigned uCh);
bool IsValidPerformance(unsigned nID);
bool IsValidPerformanceBank(unsigned nBankID);
// Must match the order in CUIMenu::TParameter
enum TParameter
@ -147,6 +162,7 @@ public:
ParameterReverbDiffusion,
ParameterReverbLevel,
ParameterPerformanceSelectChannel,
ParameterPerformanceBank,
ParameterUnknown
};
@ -222,7 +238,7 @@ public:
private:
int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note
uint8_t m_uchOPMask[CConfig::ToneGenerators];
uint8_t m_uchOPMask[CConfig::AllToneGenerators];
void LoadPerformanceParameters(void);
void ProcessSound (void);
const char* GetNetworkDeviceShortName() const;
@ -242,39 +258,44 @@ private:
CConfig *m_pConfig;
int m_nParameter[ParameterUnknown]; // global (non-TG) parameters
CDexedAdapter *m_pTG[CConfig::ToneGenerators];
unsigned m_nVoiceBankID[CConfig::ToneGenerators];
unsigned m_nVoiceBankIDMSB[CConfig::ToneGenerators];
unsigned m_nProgram[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators];
unsigned m_nPan[CConfig::ToneGenerators];
int m_nMasterTune[CConfig::ToneGenerators];
int m_nCutoff[CConfig::ToneGenerators];
int m_nResonance[CConfig::ToneGenerators];
unsigned m_nMIDIChannel[CConfig::ToneGenerators];
unsigned m_nPitchBendRange[CConfig::ToneGenerators];
unsigned m_nPitchBendStep[CConfig::ToneGenerators];
unsigned m_nPortamentoMode[CConfig::ToneGenerators];
unsigned m_nPortamentoGlissando[CConfig::ToneGenerators];
unsigned m_nPortamentoTime[CConfig::ToneGenerators];
bool m_bMonoMode[CConfig::ToneGenerators];
unsigned m_nToneGenerators;
unsigned m_nPolyphony;
CDexedAdapter *m_pTG[CConfig::AllToneGenerators];
unsigned m_nVoiceBankID[CConfig::AllToneGenerators];
unsigned m_nVoiceBankIDMSB[CConfig::AllToneGenerators];
unsigned m_nVoiceBankIDPerformance;
unsigned m_nVoiceBankIDMSBPerformance;
unsigned m_nProgram[CConfig::AllToneGenerators];
unsigned m_nVolume[CConfig::AllToneGenerators];
unsigned m_nPan[CConfig::AllToneGenerators];
int m_nMasterTune[CConfig::AllToneGenerators];
int m_nCutoff[CConfig::AllToneGenerators];
int m_nResonance[CConfig::AllToneGenerators];
unsigned m_nMIDIChannel[CConfig::AllToneGenerators];
unsigned m_nPitchBendRange[CConfig::AllToneGenerators];
unsigned m_nPitchBendStep[CConfig::AllToneGenerators];
unsigned m_nPortamentoMode[CConfig::AllToneGenerators];
unsigned m_nPortamentoGlissando[CConfig::AllToneGenerators];
unsigned m_nPortamentoTime[CConfig::AllToneGenerators];
bool m_bMonoMode[CConfig::AllToneGenerators];
unsigned m_nModulationWheelRange[CConfig::ToneGenerators];
unsigned m_nModulationWheelTarget[CConfig::ToneGenerators];
unsigned m_nFootControlRange[CConfig::ToneGenerators];
unsigned m_nFootControlTarget[CConfig::ToneGenerators];
unsigned m_nBreathControlRange[CConfig::ToneGenerators];
unsigned m_nBreathControlTarget[CConfig::ToneGenerators];
unsigned m_nAftertouchRange[CConfig::ToneGenerators];
unsigned m_nAftertouchTarget[CConfig::ToneGenerators];
unsigned m_nModulationWheelRange[CConfig::AllToneGenerators];
unsigned m_nModulationWheelTarget[CConfig::AllToneGenerators];
unsigned m_nFootControlRange[CConfig::AllToneGenerators];
unsigned m_nFootControlTarget[CConfig::AllToneGenerators];
unsigned m_nBreathControlRange[CConfig::AllToneGenerators];
unsigned m_nBreathControlTarget[CConfig::AllToneGenerators];
unsigned m_nAftertouchRange[CConfig::AllToneGenerators];
unsigned m_nAftertouchTarget[CConfig::AllToneGenerators];
unsigned m_nNoteLimitLow[CConfig::ToneGenerators];
unsigned m_nNoteLimitHigh[CConfig::ToneGenerators];
int m_nNoteShift[CConfig::ToneGenerators];
unsigned m_nNoteLimitLow[CConfig::AllToneGenerators];
unsigned m_nNoteLimitHigh[CConfig::AllToneGenerators];
int m_nNoteShift[CConfig::AllToneGenerators];
unsigned m_nReverbSend[CConfig::ToneGenerators];
unsigned m_nReverbSend[CConfig::AllToneGenerators];
uint8_t m_nRawVoiceData[156];
@ -289,24 +310,25 @@ private:
CPCKeyboard m_PCKeyboard;
CSerialMIDIDevice m_SerialMIDI;
bool m_bUseSerial;
bool m_bQuadDAC8Chan;
CSoundBaseDevice *m_pSoundDevice;
bool m_bChannelsSwapped;
unsigned m_nQueueSizeFrames;
#ifdef ARM_ALLOW_MULTI_CORE
unsigned m_nActiveTGsLog2;
// unsigned m_nActiveTGsLog2;
volatile TCoreStatus m_CoreStatus[CORES];
volatile unsigned m_nFramesToProcess;
float32_t m_OutputLevel[CConfig::ToneGenerators][CConfig::MaxChunkSize];
float32_t m_OutputLevel[CConfig::AllToneGenerators][CConfig::MaxChunkSize];
#endif
CPerformanceTimer m_GetChunkTimer;
bool m_bProfileEnabled;
AudioEffectPlateReverb* reverb;
AudioStereoMixer<CConfig::ToneGenerators>* tg_mixer;
AudioStereoMixer<CConfig::ToneGenerators>* reverb_send_mixer;
AudioStereoMixer<CConfig::AllToneGenerators>* tg_mixer;
AudioStereoMixer<CConfig::AllToneGenerators>* reverb_send_mixer;
CSpinLock m_ReverbSpinLock;
@ -324,9 +346,13 @@ private:
bool m_bSavePerformanceNewFile;
bool m_bSetNewPerformance;
unsigned m_nSetNewPerformanceID;
bool m_bSetNewPerformanceBank;
unsigned m_nSetNewPerformanceBankID;
bool m_bSetFirstPerformance;
bool m_bDeletePerformance;
unsigned m_nDeletePerformanceID;
bool m_bLoadPerformanceBusy;
bool m_bLoadPerformanceBankBusy;
bool m_bSaveAsDeault;

@ -17,7 +17,7 @@ EngineType=1
MIDIBaudRate=31250
#MIDIThru=umidi1,ttyS1
IgnoreAllNotesOff=0
MIDIAutoVoiceDumpOnPC=1
MIDIAutoVoiceDumpOnPC=0
HeaderlessSysExVoices=0
# Program Change enable
# 0 = Ignore all Program Change messages.
@ -55,6 +55,27 @@ SSD1306LCDHeight=32
SSD1306LCDRotate=0
SSD1306LCDMirror=0
# ST7789 LCD
# SPIBus=0 for any RPi (GPIO 10,11,8,7).
# Note: Leave blank (default) if no SPI device required.
# Select=0|1 for CE0 or CE1
# Data = GPIO pin number
# Optional: Reset, Backlight = GPIO pin numbers
# Rotation=0,90,180,270
# SmallFont=0 (default), 1
#
# For a 240 wide display set LCDColumns=15 with LCDRows=2
SPIBus=
ST7789Enabled=0
ST7789Data=
ST7789Select=
ST7789Reset=
ST7789Backlight=
ST7789Width=240
ST7789Height=240
ST7789Rotation=0
ST7789SmallFont=0
# Default is 16x2 display (e.g. HD44780)
LCDColumns=16
LCDRows=2
@ -116,5 +137,10 @@ EncoderPinData=9
MIDIDumpEnabled=0
ProfileEnabled=0
# Network
NetworkEnabled=0
NetworkDHCP=1
NetworkType=wifi
# Performance
PerformanceSelectToLoad=1

File diff suppressed because it is too large Load Diff

@ -27,14 +27,16 @@
#include <fatfs/ff.h>
#include <Properties/propertiesfatfsfile.h>
#define NUM_VOICE_PARAM 156
#define PERFORMANCE_DIR "performance"
#define NUM_PERFORMANCES 256
#define NUM_PERFORMANCES 128
#define NUM_PERFORMANCE_BANKS 128
class CPerformanceConfig // Performance configuration
{
public:
CPerformanceConfig (FATFS *pFileSystem);
~CPerformanceConfig (void);
bool Init (unsigned nToneGenerators);
bool Load (void);
@ -122,59 +124,76 @@ public:
bool ListPerformances();
//std::string m_DirName;
void SetNewPerformance (unsigned nID);
unsigned FindFirstPerformance (void);
std::string GetPerformanceFileName(unsigned nID);
std::string GetPerformanceFullFilePath(unsigned nID);
std::string GetPerformanceName(unsigned nID);
unsigned GetLastPerformance();
unsigned GetLastPerformanceBank();
void SetActualPerformanceID(unsigned nID);
unsigned GetActualPerformanceID();
void SetActualPerformanceBankID(unsigned nBankID);
unsigned GetActualPerformanceBankID();
bool CreateNewPerformanceFile(void);
bool GetInternalFolderOk();
std::string GetNewPerformanceDefaultName(void);
void SetNewPerformanceName(std::string nName);
bool DeletePerformance(unsigned nID);
bool CheckFreePerformanceSlot(void);
std::string AddPerformanceBankDirName(unsigned nBankID);
bool IsValidPerformance(unsigned nID);
bool ListPerformanceBanks(void);
void SetNewPerformanceBank(unsigned nBankID);
unsigned GetPerformanceBank(void);
std::string GetPerformanceBankName(unsigned nBankID);
bool IsValidPerformanceBank(unsigned nBankID);
private:
CPropertiesFatFsFile m_Properties;
unsigned m_nBankNumber[CConfig::ToneGenerators];
unsigned m_nVoiceNumber[CConfig::ToneGenerators];
unsigned m_nMIDIChannel[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators];
unsigned m_nPan[CConfig::ToneGenerators];
int m_nDetune[CConfig::ToneGenerators];
unsigned m_nCutoff[CConfig::ToneGenerators];
unsigned m_nResonance[CConfig::ToneGenerators];
unsigned m_nNoteLimitLow[CConfig::ToneGenerators];
unsigned m_nNoteLimitHigh[CConfig::ToneGenerators];
int m_nNoteShift[CConfig::ToneGenerators];
int m_nReverbSend[CConfig::ToneGenerators];
unsigned m_nPitchBendRange[CConfig::ToneGenerators];
unsigned m_nPitchBendStep[CConfig::ToneGenerators];
unsigned m_nPortamentoMode[CConfig::ToneGenerators];
unsigned m_nPortamentoGlissando[CConfig::ToneGenerators];
unsigned m_nPortamentoTime[CConfig::ToneGenerators];
std::string m_nVoiceDataTxt[CConfig::ToneGenerators];
bool m_bMonoMode[CConfig::ToneGenerators];
unsigned m_nModulationWheelRange[CConfig::ToneGenerators];
unsigned m_nModulationWheelTarget[CConfig::ToneGenerators];
unsigned m_nFootControlRange[CConfig::ToneGenerators];
unsigned m_nFootControlTarget[CConfig::ToneGenerators];
unsigned m_nBreathControlRange[CConfig::ToneGenerators];
unsigned m_nBreathControlTarget[CConfig::ToneGenerators];
unsigned m_nAftertouchRange[CConfig::ToneGenerators];
unsigned m_nAftertouchTarget[CConfig::ToneGenerators];
unsigned nLastPerformance;
unsigned nLastFileIndex;
unsigned nActualPerformance = 0;
unsigned m_nToneGenerators;
unsigned m_nBankNumber[CConfig::AllToneGenerators];
unsigned m_nVoiceNumber[CConfig::AllToneGenerators];
unsigned m_nMIDIChannel[CConfig::AllToneGenerators];
unsigned m_nVolume[CConfig::AllToneGenerators];
unsigned m_nPan[CConfig::AllToneGenerators];
int m_nDetune[CConfig::AllToneGenerators];
unsigned m_nCutoff[CConfig::AllToneGenerators];
unsigned m_nResonance[CConfig::AllToneGenerators];
unsigned m_nNoteLimitLow[CConfig::AllToneGenerators];
unsigned m_nNoteLimitHigh[CConfig::AllToneGenerators];
int m_nNoteShift[CConfig::AllToneGenerators];
int m_nReverbSend[CConfig::AllToneGenerators];
unsigned m_nPitchBendRange[CConfig::AllToneGenerators];
unsigned m_nPitchBendStep[CConfig::AllToneGenerators];
unsigned m_nPortamentoMode[CConfig::AllToneGenerators];
unsigned m_nPortamentoGlissando[CConfig::AllToneGenerators];
unsigned m_nPortamentoTime[CConfig::AllToneGenerators];
std::string m_nVoiceDataTxt[CConfig::AllToneGenerators];
bool m_bMonoMode[CConfig::AllToneGenerators];
unsigned m_nModulationWheelRange[CConfig::AllToneGenerators];
unsigned m_nModulationWheelTarget[CConfig::AllToneGenerators];
unsigned m_nFootControlRange[CConfig::AllToneGenerators];
unsigned m_nFootControlTarget[CConfig::AllToneGenerators];
unsigned m_nBreathControlRange[CConfig::AllToneGenerators];
unsigned m_nBreathControlTarget[CConfig::AllToneGenerators];
unsigned m_nAftertouchRange[CConfig::AllToneGenerators];
unsigned m_nAftertouchTarget[CConfig::AllToneGenerators];
unsigned m_nLastPerformance;
unsigned m_nActualPerformance = 0;
unsigned m_nActualPerformanceBank = 0;
unsigned m_nPerformanceBank;
unsigned m_nLastPerformanceBank;
bool m_bPerformanceDirectoryExists;
//unsigned nMenuSelectedPerformance = 0;
std::string m_nPerformanceFileName[NUM_PERFORMANCES];
std::string m_PerformanceFileName[NUM_PERFORMANCES];
std::string m_PerformanceBankName[NUM_PERFORMANCE_BANKS];
FATFS *m_pFileSystem;
bool nInternalFolderOk=false;
bool nExternalFolderOk=false; // for future USB implementation
std::string NewPerformanceName="";
bool m_bCompressorEnable;

@ -28,11 +28,15 @@
LOGMODULE("serialmididevice");
// There are several UART options - see circle/include/serial.h
// 0 corresponds to GP14/GP15 on all RPi versions.
#define SERIAL_MIDI_DEVICE 0
CSerialMIDIDevice::CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt,
CConfig *pConfig, CUserInterface *pUI)
: CMIDIDevice (pSynthesizer, pConfig, pUI),
m_pConfig (pConfig),
m_Serial (pInterrupt, TRUE),
m_Serial (pInterrupt, TRUE, SERIAL_MIDI_DEVICE),
m_nSerialState (0),
m_nSysEx (0),
m_SendBuffer (&m_Serial)
@ -71,7 +75,7 @@ void CSerialMIDIDevice::Process (void)
return;
}
if (m_pConfig->GetMIDIDumpEnabled ())
/* if (m_pConfig->GetMIDIDumpEnabled ())
{
printf("Incoming MIDI data:");
for (uint16_t i = 0; i < nResult; i++)
@ -81,7 +85,7 @@ void CSerialMIDIDevice::Process (void)
printf(" 0x%02x",Buffer[i]);
}
printf("\n");
}
}*/
// Process MIDI messages
// See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message

@ -30,9 +30,6 @@
#include <circle/writebuffer.h>
#include <circle/types.h>
#define MAX_DX7_SYSEX_LENGTH 4104
#define MAX_MIDI_MESSAGE MAX_DX7_SYSEX_LENGTH
class CMiniDexed;
class CSerialMIDIDevice : public CMIDIDevice

@ -273,6 +273,23 @@ std::string CSysExFileLoader::GetBankName (unsigned nBankID)
return "NO NAME";
}
std::string CSysExFileLoader::GetVoiceName (unsigned nBankID, unsigned nVoiceID)
{
if ((nBankID <= MaxVoiceBankID) && (nVoiceID < VoicesPerBank))
{
if (IsValidBank(nBankID))
{
// The name is the last 10 characters of the voice data
char sVoiceName[11];
strncpy (sVoiceName, (char *)((char *)&(m_pVoiceBank[nBankID]->Voice[nVoiceID]) + SizePackedVoice - 10), 10);
sVoiceName[10] = 0;
std::string result(sVoiceName);
return result;
}
}
return "INIT VOICE";
}
unsigned CSysExFileLoader::GetNextBankUp (unsigned nBankID)
{
// Find the next loaded bank "up" from the provided bank ID

@ -60,6 +60,7 @@ public:
void Load (bool bHeaderlessSysExVoices = false);
std::string GetBankName (unsigned nBankID); // 0 .. MaxVoiceBankID
std::string GetVoiceName (unsigned nBankID, unsigned nVoice); // 0 .. MaxVoiceBankID, 0 .. VoicesPerBank-1
unsigned GetNumHighestBank (); // 0 .. MaxVoiceBankID
bool IsValidBank (unsigned nBankID);
unsigned GetNextBankUp (unsigned nBankID);

@ -26,7 +26,7 @@
#include "config.h"
#define BUTTONS_UPDATE_NUM_TICKS 100
#define DEBOUNCE_TIME 100
#define DEBOUNCE_TIME 20
#define MAX_GPIO_BUTTONS 9 // 5 UI buttons, 4 Program/TG Select buttons
#define MAX_MIDI_BUTTONS 9
#define MAX_BUTTONS (MAX_GPIO_BUTTONS+MAX_MIDI_BUTTONS)

@ -51,6 +51,16 @@ const CUIMenu::TMenuItem CUIMenu::s_MainMenu[] =
{"TG6", MenuHandler, s_TGMenu, 5},
{"TG7", MenuHandler, s_TGMenu, 6},
{"TG8", MenuHandler, s_TGMenu, 7},
#if (RASPPI==4 || RASPPI==5)
{"TG9", MenuHandler, s_TGMenu, 8},
{"TG10", MenuHandler, s_TGMenu, 9},
{"TG11", MenuHandler, s_TGMenu, 10},
{"TG12", MenuHandler, s_TGMenu, 11},
{"TG13", MenuHandler, s_TGMenu, 12},
{"TG14", MenuHandler, s_TGMenu, 13},
{"TG15", MenuHandler, s_TGMenu, 14},
{"TG16", MenuHandler, s_TGMenu, 15},
#endif
#endif
{"Effects", MenuHandler, s_EffectsMenu},
{"Performance", MenuHandler, s_PerformanceMenu},
@ -214,7 +224,8 @@ const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknow
{0, 99, 1}, // ParameterReverbLowPass
{0, 99, 1}, // ParameterReverbDiffusion
{0, 99, 1}, // ParameterReverbLevel
{0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel} // ParameterPerformanceSelectChannel
{0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // ParameterPerformanceSelectChannel
{0, NUM_PERFORMANCE_BANKS, 1} // ParameterPerformanceBank
};
// must match CMiniDexed::TTGParameter
@ -327,14 +338,16 @@ const CUIMenu::TMenuItem CUIMenu::s_PerformanceMenu[] =
{"Load", PerformanceMenu, 0, 0},
{"Save", MenuHandler, s_SaveMenu},
{"Delete", PerformanceMenu, 0, 1},
{"Bank", EditPerformanceBankNumber, 0, 0},
{"PCCH", EditGlobalParameter, 0, CMiniDexed::ParameterPerformanceSelectChannel},
{0}
};
CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed)
CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed, CConfig *pConfig)
: m_pUI (pUI),
m_pMiniDexed (pMiniDexed),
m_pConfig (pConfig),
m_pParentMenu (s_MenuRoot),
m_pCurrentMenu (s_MainMenu),
m_nCurrentMenuItem (0),
@ -342,23 +355,27 @@ CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed)
m_nCurrentParameter (0),
m_nCurrentMenuDepth (0)
{
#ifndef ARM_ALLOW_MULTI_CORE
// If there is just one core, then there is only a single
// tone generator so start on the TG1 menu...
m_pParentMenu = s_MainMenu;
m_pCurrentMenu = s_TGMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 1;
assert (m_pConfig);
m_nToneGenerators = m_pConfig->GetToneGenerators();
// Place the "root" menu at the top of the stack
m_MenuStackParent[0] = s_MenuRoot;
m_MenuStackMenu[0] = s_MainMenu;
m_nMenuStackItem[0] = 0;
m_nMenuStackSelection[0] = 0;
m_nMenuStackParameter[0] = 0;
#endif
if (m_nToneGenerators == 1)
{
// If there is just one core, then there is only a single
// tone generator so start on the TG1 menu...
m_pParentMenu = s_MainMenu;
m_pCurrentMenu = s_TGMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 1;
// Place the "root" menu at the top of the stack
m_MenuStackParent[0] = s_MenuRoot;
m_MenuStackMenu[0] = s_MainMenu;
m_nMenuStackItem[0] = 0;
m_nMenuStackSelection[0] = 0;
m_nMenuStackParameter[0] = 0;
}
}
void CUIMenu::EventHandler (TMenuEvent Event)
@ -381,28 +398,31 @@ void CUIMenu::EventHandler (TMenuEvent Event)
break;
case MenuEventHome:
#ifdef ARM_ALLOW_MULTI_CORE
m_pParentMenu = s_MenuRoot;
m_pCurrentMenu = s_MainMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 0;
#else
// "Home" is the TG0 menu if only one TG active
m_pParentMenu = s_MainMenu;
m_pCurrentMenu = s_TGMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 1;
// Place the "root" menu at the top of the stack
m_MenuStackParent[0] = s_MenuRoot;
m_MenuStackMenu[0] = s_MainMenu;
m_nMenuStackItem[0] = 0;
m_nMenuStackSelection[0] = 0;
m_nMenuStackParameter[0] = 0;
#endif
if (m_nToneGenerators == 1)
{
// "Home" is the TG0 menu if only one TG active
m_pParentMenu = s_MainMenu;
m_pCurrentMenu = s_TGMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 1;
// Place the "root" menu at the top of the stack
m_MenuStackParent[0] = s_MenuRoot;
m_MenuStackMenu[0] = s_MainMenu;
m_nMenuStackItem[0] = 0;
m_nMenuStackSelection[0] = 0;
m_nMenuStackParameter[0] = 0;
}
else
{
m_pParentMenu = s_MenuRoot;
m_pCurrentMenu = s_MainMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 0;
}
EventHandler (MenuEventUpdate);
break;
@ -451,7 +471,30 @@ void CUIMenu::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event)
break;
case MenuEventStepDown:
if (pUIMenu->m_nCurrentSelection > 0)
if (pUIMenu->m_nCurrentSelection == 0)
{
// If in main mennu, wrap around
if (pUIMenu->m_pCurrentMenu == s_MainMenu)
{
// Find last entry with a name
while (pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name)
{
pUIMenu->m_nCurrentSelection++;
}
}
}
else if (pUIMenu->m_nCurrentSelection > 0)
{
pUIMenu->m_nCurrentSelection--;
}
// Might need to trim menu if number of TGs is configured to be less than the maximum supported
while ((pUIMenu->m_pCurrentMenu == s_MainMenu) && (pUIMenu->m_nCurrentSelection > 0) &&
( // Skip any unused menus
(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].MenuItem == s_TGMenu) &&
(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter >= pUIMenu->m_nToneGenerators) &&
(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter < CConfig::AllToneGenerators)
)
)
{
pUIMenu->m_nCurrentSelection--;
}
@ -461,7 +504,27 @@ void CUIMenu::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event)
++pUIMenu->m_nCurrentSelection;
if (!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name) // more entries?
{
pUIMenu->m_nCurrentSelection--;
if (pUIMenu->m_pCurrentMenu == s_MainMenu)
{
// If in main mennu, wrap around
pUIMenu->m_nCurrentSelection = 0;
}
else
{
// Return to last known good item
pUIMenu->m_nCurrentSelection--;
}
}
// Might need to trim menu if number of TGs is configured to be less than the maximum supported
while ((pUIMenu->m_pCurrentMenu == s_MainMenu) && (pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name) &&
( // Skip any unused TG menus
(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].MenuItem == s_TGMenu) &&
(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter >= pUIMenu->m_nToneGenerators) &&
(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter < CConfig::AllToneGenerators)
)
)
{
pUIMenu->m_nCurrentSelection++;
}
break;
@ -1149,7 +1212,7 @@ void CUIMenu::TGShortcutHandler (TMenuEvent Event)
assert (m_nCurrentMenuDepth >= 2);
assert (m_MenuStackMenu[0] = s_MainMenu);
unsigned nTG = m_nMenuStackSelection[0];
assert (nTG < CConfig::ToneGenerators);
assert (nTG < CConfig::AllToneGenerators);
assert (m_nMenuStackItem[1] == nTG);
assert (m_nMenuStackParameter[1] == nTG);
@ -1164,7 +1227,7 @@ void CUIMenu::TGShortcutHandler (TMenuEvent Event)
nTG++;
}
if (nTG < CConfig::ToneGenerators)
if (nTG < m_nToneGenerators)
{
m_nMenuStackSelection[0] = nTG;
m_nMenuStackItem[1] = nTG;
@ -1211,24 +1274,43 @@ void CUIMenu::PgmUpDownHandler (TMenuEvent Event)
// Program Up/Down acts on performances
unsigned nLastPerformance = m_pMiniDexed->GetLastPerformance();
unsigned nPerformance = m_pMiniDexed->GetActualPerformanceID();
unsigned nStart = nPerformance;
//LOGNOTE("Performance actual=%d, last=%d", nPerformance, nLastPerformance);
if (Event == MenuEventPgmDown)
{
if (nPerformance > 0)
do
{
m_nSelectedPerformanceID = nPerformance-1;
m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID);
//LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance);
}
if (nPerformance == 0)
{
// Wrap around
nPerformance = nLastPerformance;
}
else if (nPerformance > 0)
{
--nPerformance;
}
} while ((m_pMiniDexed->IsValidPerformance(nPerformance) != true) && (nPerformance != nStart));
m_nSelectedPerformanceID = nPerformance;
m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID);
//LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance);
}
else
else // MenuEventPgmUp
{
if (nPerformance < nLastPerformance-1)
do
{
m_nSelectedPerformanceID = nPerformance+1;
m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID);
//LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance);
}
if (nPerformance == nLastPerformance)
{
// Wrap around
nPerformance = 0;
}
else if (nPerformance < nLastPerformance)
{
++nPerformance;
}
} while ((m_pMiniDexed->IsValidPerformance(nPerformance) != true) && (nPerformance != nStart));
m_nSelectedPerformanceID = nPerformance;
m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID);
//LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance);
}
}
else
@ -1241,51 +1323,53 @@ void CUIMenu::PgmUpDownHandler (TMenuEvent Event)
if (m_MenuStackMenu[0] == s_MainMenu && (m_pCurrentMenu == s_TGMenu) || (m_MenuStackMenu[1] == s_TGMenu)) {
nTG = m_nMenuStackSelection[0];
}
assert (nTG < CConfig::ToneGenerators);
int nPgm = m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterProgram, nTG);
assert (Event == MenuEventPgmDown || Event == MenuEventPgmUp);
if (Event == MenuEventPgmDown)
assert (nTG < CConfig::AllToneGenerators);
if (nTG < m_nToneGenerators)
{
//LOGNOTE("PgmDown");
if (--nPgm < 0)
int nPgm = m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterProgram, nTG);
assert (Event == MenuEventPgmDown || Event == MenuEventPgmUp);
if (Event == MenuEventPgmDown)
{
// Switch down a voice bank and set to the last voice
nPgm = CSysExFileLoader::VoicesPerBank-1;
int nVB = m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG);
nVB = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankDown(nVB);
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG);
//LOGNOTE("PgmDown");
if (--nPgm < 0)
{
// Switch down a voice bank and set to the last voice
nPgm = CSysExFileLoader::VoicesPerBank-1;
int nVB = m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG);
nVB = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankDown(nVB);
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG);
}
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG);
}
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG);
}
else
{
//LOGNOTE("PgmUp");
if (++nPgm > (int) CSysExFileLoader::VoicesPerBank-1)
else
{
// Switch up a voice bank and reset to voice 0
nPgm = 0;
int nVB = m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG);
nVB = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankUp(nVB);
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG);
//LOGNOTE("PgmUp");
if (++nPgm > (int) CSysExFileLoader::VoicesPerBank-1)
{
// Switch up a voice bank and reset to voice 0
nPgm = 0;
int nVB = m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG);
nVB = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankUp(nVB);
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG);
}
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG);
}
m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG);
}
// Skip empty voices.
// Use same criteria in EditProgramNumber () too.
string voiceName = m_pMiniDexed->GetVoiceName (nTG);
if (voiceName == "EMPTY "
|| voiceName == " "
|| voiceName == "----------"
|| voiceName == "~~~~~~~~~~" )
{
if (Event == MenuEventPgmUp) {
PgmUpDownHandler (MenuEventPgmUp);
}
if (Event == MenuEventPgmDown) {
PgmUpDownHandler (MenuEventPgmDown);
// Skip empty voices.
// Use same criteria in EditProgramNumber () too.
string voiceName = m_pMiniDexed->GetVoiceName (nTG);
if (voiceName == "EMPTY "
|| voiceName == " "
|| voiceName == "----------"
|| voiceName == "~~~~~~~~~~" )
{
if (Event == MenuEventPgmUp) {
PgmUpDownHandler (MenuEventPgmUp);
}
if (Event == MenuEventPgmDown) {
PgmUpDownHandler (MenuEventPgmDown);
}
}
}
}
@ -1296,7 +1380,7 @@ void CUIMenu::TGUpDownHandler (TMenuEvent Event)
// This will update the menus to position it for the next TG up or down
unsigned nTG = 0;
if (CConfig::ToneGenerators <= 1) {
if (m_nToneGenerators <= 1) {
// Nothing to do if only a single TG
return;
}
@ -1307,7 +1391,7 @@ void CUIMenu::TGUpDownHandler (TMenuEvent Event)
nTG = m_nMenuStackSelection[0];
}
assert (nTG < CConfig::ToneGenerators);
assert (nTG < CConfig::AllToneGenerators);
assert (Event == MenuEventTGDown || Event == MenuEventTGUp);
if (Event == MenuEventTGDown)
{
@ -1319,7 +1403,7 @@ void CUIMenu::TGUpDownHandler (TMenuEvent Event)
else
{
//LOGNOTE("TGUp");
if (nTG < CConfig::ToneGenerators - 1) {
if (nTG < m_nToneGenerators - 1) {
nTG++;
}
}
@ -1366,7 +1450,15 @@ void CUIMenu::TimerHandlerNoBack (TKernelTimerHandle hTimer, void *pParam, void
void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event)
{
bool bPerformanceSelectToLoad = pUIMenu->m_pMiniDexed->GetPerformanceSelectToLoad();
unsigned nLastPerformance = pUIMenu->m_pMiniDexed->GetLastPerformance();
unsigned nValue = pUIMenu->m_nSelectedPerformanceID;
unsigned nStart = nValue;
if (pUIMenu->m_pMiniDexed->IsValidPerformance(nValue) != true)
{
// A bank change has left the selected performance out of sync
nValue = pUIMenu->m_pMiniDexed->GetActualPerformanceID();
pUIMenu->m_nSelectedPerformanceID = nValue;
}
std::string Value;
if (Event == MenuEventUpdate)
@ -1380,17 +1472,25 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event)
}
if(!pUIMenu->m_bPerformanceDeleteMode)
{
{
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
if (nValue > 0)
do
{
--nValue;
}
if (nValue == 0)
{
// Wrap around
nValue = nLastPerformance;
}
else if (nValue > 0)
{
--nValue;
}
} while ((pUIMenu->m_pMiniDexed->IsValidPerformance(nValue) != true) && (nValue != nStart));
pUIMenu->m_nSelectedPerformanceID = nValue;
if (!bPerformanceSelectToLoad && pUIMenu->m_nCurrentParameter==0)
{
@ -1399,10 +1499,18 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event)
break;
case MenuEventStepUp:
if (++nValue > (unsigned) pUIMenu->m_pMiniDexed->GetLastPerformance()-1)
do
{
nValue = pUIMenu->m_pMiniDexed->GetLastPerformance()-1;
}
if (nValue == nLastPerformance)
{
// Wrap around
nValue = 0;
}
else if (nValue < nLastPerformance)
{
++nValue;
}
} while ((pUIMenu->m_pMiniDexed->IsValidPerformance(nValue) != true) && (nValue != nStart));
pUIMenu->m_nSelectedPerformanceID = nValue;
if (!bPerformanceSelectToLoad && pUIMenu->m_nCurrentParameter==0)
{
@ -1421,7 +1529,7 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event)
break;
case 1:
if (pUIMenu->m_nSelectedPerformanceID != 0)
if (pUIMenu->m_pMiniDexed->IsValidPerformance(pUIMenu->m_nSelectedPerformanceID))
{
pUIMenu->m_bPerformanceDeleteMode=true;
pUIMenu->m_bConfirmDeletePerformance=false;
@ -1474,17 +1582,24 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event)
if(!pUIMenu->m_bPerformanceDeleteMode)
{
Value = pUIMenu->m_pMiniDexed->GetPerformanceName(nValue);
unsigned nBankNum = pUIMenu->m_pMiniDexed->GetPerformanceBank();
std::string nPSelected = "";
std::string nPSelected = "000";
nPSelected += std::to_string(nBankNum+1); // Convert to user-facing bank number rather than index
nPSelected = nPSelected.substr(nPSelected.length()-3,3);
std::string nPPerf = "000";
nPPerf += std::to_string(nValue+1); // Convert to user-facing performance number rather than index
nPPerf = nPPerf.substr(nPPerf.length()-3,3);
nPSelected += ":"+nPPerf;
if(nValue == pUIMenu->m_pMiniDexed->GetActualPerformanceID())
{
nPSelected= "[L]";
nPSelected += " [L]";
}
pUIMenu->m_pUI->DisplayWrite (pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, nPSelected.c_str(),
Value.c_str (),
(int) nValue > 0, (int) nValue < (int) pUIMenu->m_pMiniDexed->GetLastPerformance()-1);
Value.c_str (), true, true);
// (int) nValue > 0, (int) nValue < (int) pUIMenu->m_pMiniDexed->GetLastPerformance());
}
else
{
@ -1492,6 +1607,90 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event)
}
}
void CUIMenu::EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event)
{
bool bPerformanceSelectToLoad = pUIMenu->m_pMiniDexed->GetPerformanceSelectToLoad();
unsigned nLastPerformanceBank = pUIMenu->m_pMiniDexed->GetLastPerformanceBank();
unsigned nValue = pUIMenu->m_nSelectedPerformanceBankID;
unsigned nStart = nValue;
std::string Value;
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
do
{
if (nValue == 0)
{
// Wrap around
nValue = nLastPerformanceBank;
}
else if (nValue > 0)
{
--nValue;
}
} while ((pUIMenu->m_pMiniDexed->IsValidPerformanceBank(nValue) != true) && (nValue != nStart));
pUIMenu->m_nSelectedPerformanceBankID = nValue;
if (!bPerformanceSelectToLoad)
{
// Switch to the new bank and select the first performance voice
pUIMenu->m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nValue);
pUIMenu->m_pMiniDexed->SetFirstPerformance();
}
break;
case MenuEventStepUp:
do
{
if (nValue == nLastPerformanceBank)
{
// Wrap around
nValue = 0;
}
else if (nValue < nLastPerformanceBank)
{
++nValue;
}
} while ((pUIMenu->m_pMiniDexed->IsValidPerformanceBank(nValue) != true) && (nValue != nStart));
pUIMenu->m_nSelectedPerformanceBankID = nValue;
if (!bPerformanceSelectToLoad)
{
pUIMenu->m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nValue);
pUIMenu->m_pMiniDexed->SetFirstPerformance();
}
break;
case MenuEventSelect:
if (bPerformanceSelectToLoad)
{
pUIMenu->m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nValue);
pUIMenu->m_pMiniDexed->SetFirstPerformance();
}
break;
default:
return;
}
Value = pUIMenu->m_pMiniDexed->GetPerformanceConfig ()->GetPerformanceBankName(nValue);
std::string nPSelected = "000";
nPSelected += std::to_string(nValue+1); // Convert to user-facing number rather than index
nPSelected = nPSelected.substr(nPSelected.length()-3,3);
if(nValue == (unsigned)pUIMenu->m_pMiniDexed->GetParameter (CMiniDexed::ParameterPerformanceBank))
{
nPSelected += " [L]";
}
pUIMenu->m_pUI->DisplayWrite (pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, nPSelected.c_str(),
Value.c_str (),
nValue > 0,
nValue < pUIMenu->m_pMiniDexed->GetLastPerformanceBank()-1);
}
void CUIMenu::InputTxt (CUIMenu *pUIMenu, TMenuEvent Event)
{
unsigned nTG=0;

@ -25,6 +25,7 @@
#include <string>
#include <circle/timer.h>
#include "config.h"
class CMiniDexed;
class CUserInterface;
@ -53,7 +54,7 @@ public:
};
public:
CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed);
CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed, CConfig *pConfig);
void EventHandler (TMenuEvent Event);
@ -91,6 +92,7 @@ private:
static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event);
static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event);
static void SavePerformanceNewFile (CUIMenu *pUIMenu, TMenuEvent Event);
static void EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event);
static std::string GetGlobalValueString (unsigned nParameter, int nValue);
static std::string GetTGValueString (unsigned nTGParameter, int nValue);
@ -127,6 +129,9 @@ private:
private:
CUserInterface *m_pUI;
CMiniDexed *m_pMiniDexed;
CConfig *m_pConfig;
unsigned m_nToneGenerators;
const TMenuItem *m_pParentMenu;
const TMenuItem *m_pCurrentMenu;
@ -169,6 +174,7 @@ private:
bool m_bPerformanceDeleteMode=false;
bool m_bConfirmDeletePerformance=false;
unsigned m_nSelectedPerformanceID =0;
unsigned m_nSelectedPerformanceBankID =0;
bool m_bSplashShow=false;
};

@ -22,6 +22,23 @@
#ifndef _usbminidexedmidigadget_h
#define _usbminidexedmidigadget_h
#if RASPPI==5
#include <circle/sysconfig.h>
#include <assert.h>
#warning No support for USB Gadget Mode on RPI 5 yet
class CUSBMiniDexedMIDIGadget
{
public:
CUSBMiniDexedMIDIGadget (CInterruptSystem *pInterruptSystem)
{
}
~CUSBMiniDexedMIDIGadget (void)
{
}
};
#else
#include <circle/usb/gadget/usbmidigadget.h>
#include <circle/usb/gadget/usbmidigadgetendpoint.h>
#include <circle/sysconfig.h>
@ -82,5 +99,6 @@ protected:
return CUSBMIDIGadget::GetDescriptor(wValue, wIndex, pLength);
}
};
#endif
#endif

@ -27,17 +27,18 @@
LOGMODULE ("ui");
CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CConfig *pConfig)
CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CSPIMaster *pSPIMaster, CConfig *pConfig)
: m_pMiniDexed (pMiniDexed),
m_pGPIOManager (pGPIOManager),
m_pI2CMaster (pI2CMaster),
m_pSPIMaster (pSPIMaster),
m_pConfig (pConfig),
m_pLCD (0),
m_pLCDBuffered (0),
m_pUIButtons (0),
m_pRotaryEncoder (0),
m_bSwitchPressed (false),
m_Menu (this, pMiniDexed)
m_Menu (this, pMiniDexed, pConfig)
{
}
@ -57,17 +58,69 @@ bool CUserInterface::Initialize (void)
{
unsigned i2caddr = m_pConfig->GetLCDI2CAddress ();
unsigned ssd1306addr = m_pConfig->GetSSD1306LCDI2CAddress ();
bool st7789 = m_pConfig->GetST7789Enabled ();
if (ssd1306addr != 0) {
m_pSSD1306 = new CSSD1306Device (m_pConfig->GetSSD1306LCDWidth (), m_pConfig->GetSSD1306LCDHeight (),
m_pI2CMaster, ssd1306addr,
m_pConfig->GetSSD1306LCDRotate (), m_pConfig->GetSSD1306LCDMirror ());
LOGDBG ("LCD: SSD1306");
if (!m_pSSD1306->Initialize ())
{
LOGDBG("LCD: SSD1306 initialization failed");
return false;
}
LOGDBG ("LCD: SSD1306");
m_pLCD = m_pSSD1306;
} else if (i2caddr == 0)
}
else if (st7789)
{
if (m_pSPIMaster == nullptr)
{
LOGDBG("LCD: ST7789 Enabled but SPI Initialisation Failed");
return false;
}
unsigned long nSPIClock = 1000 * m_pConfig->GetSPIClockKHz();
unsigned nSPIMode = m_pConfig->GetSPIMode();
unsigned nCPHA = (nSPIMode & 1) ? 1 : 0;
unsigned nCPOL = (nSPIMode & 2) ? 1 : 0;
LOGDBG("SPI: CPOL=%u; CPHA=%u; CLK=%u",nCPOL,nCPHA,nSPIClock);
m_pST7789Display = new CST7789Display (m_pSPIMaster,
m_pConfig->GetST7789Data(),
m_pConfig->GetST7789Reset(),
m_pConfig->GetST7789Backlight(),
m_pConfig->GetST7789Width(),
m_pConfig->GetST7789Height(),
nCPOL, nCPHA, nSPIClock,
m_pConfig->GetST7789Select());
if (m_pST7789Display->Initialize())
{
m_pST7789Display->SetRotation (m_pConfig->GetST7789Rotation());
bool bLargeFont = !(m_pConfig->GetST7789SmallFont());
m_pST7789 = new CST7789Device (m_pSPIMaster, m_pST7789Display, m_pConfig->GetLCDColumns (), m_pConfig->GetLCDRows (), bLargeFont, bLargeFont);
if (m_pST7789->Initialize())
{
LOGDBG ("LCD: ST7789");
m_pLCD = m_pST7789;
}
else
{
LOGDBG ("LCD: Failed to initalize ST7789 character device");
delete (m_pST7789);
delete (m_pST7789Display);
m_pST7789 = nullptr;
m_pST7789Display = nullptr;
return false;
}
}
else
{
LOGDBG ("LCD: Failed to initialize ST7789 display");
delete (m_pST7789Display);
m_pST7789Display = nullptr;
return false;
}
}
else if (i2caddr == 0)
{
m_pHD44780 = new CHD44780Device (m_pConfig->GetLCDColumns (), m_pConfig->GetLCDRows (),
m_pConfig->GetLCDPinData4 (),
@ -77,22 +130,24 @@ bool CUserInterface::Initialize (void)
m_pConfig->GetLCDPinEnable (),
m_pConfig->GetLCDPinRegisterSelect (),
m_pConfig->GetLCDPinReadWrite ());
LOGDBG ("LCD: HD44780");
if (!m_pHD44780->Initialize ())
{
LOGDBG("LCD: HD44780 initialization failed");
return false;
}
LOGDBG ("LCD: HD44780");
m_pLCD = m_pHD44780;
}
else
{
m_pHD44780 = new CHD44780Device (m_pI2CMaster, i2caddr,
m_pConfig->GetLCDColumns (), m_pConfig->GetLCDRows ());
LOGDBG ("LCD: HD44780 I2C");
if (!m_pHD44780->Initialize ())
{
LOGDBG("LCD: HD44780 (I2C) initialization failed");
return false;
}
LOGDBG ("LCD: HD44780 I2C");
m_pLCD = m_pHD44780;
}
assert (m_pLCD);
@ -217,7 +272,7 @@ void CUserInterface::DisplayWrite (const char *pMenu, const char *pParam, const
CString Value (" ");
if (bArrowDown)
{
Value = "\x7F"; // arrow left character
Value = "<"; // arrow left character
}
Value.Append (pValue);
@ -232,7 +287,7 @@ void CUserInterface::DisplayWrite (const char *pMenu, const char *pParam, const
}
}
Value.Append ("\x7E"); // arrow right character
Value.Append (">"); // arrow right character
}
Msg.Append (Value);
@ -388,13 +443,16 @@ void CUserInterface::UISetMIDIButtonChannel (unsigned uCh)
if (uCh == 0)
{
m_nMIDIButtonCh = CMIDIDevice::Disabled;
LOGNOTE("MIDI Button channel not set");
}
else if (uCh < CMIDIDevice::Channels)
else if (uCh <= CMIDIDevice::Channels)
{
m_nMIDIButtonCh = uCh - 1;
LOGNOTE("MIDI Button channel set to: %d", m_nMIDIButtonCh+1);
}
else
{
m_nMIDIButtonCh = CMIDIDevice::OmniMode;
LOGNOTE("MIDI Button channel set to: OMNI");
}
}

@ -26,16 +26,18 @@
#include <sensor/ky040.h>
#include <display/hd44780device.h>
#include <display/ssd1306device.h>
#include <display/st7789device.h>
#include <circle/gpiomanager.h>
#include <circle/writebuffer.h>
#include <circle/i2cmaster.h>
#include <circle/spimaster.h>
class CMiniDexed;
class CUserInterface
{
public:
CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CConfig *pConfig);
CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CSPIMaster *pSPIMaster, CConfig *pConfig);
~CUserInterface (void);
bool Initialize (void);
@ -68,11 +70,14 @@ private:
CMiniDexed *m_pMiniDexed;
CGPIOManager *m_pGPIOManager;
CI2CMaster *m_pI2CMaster;
CSPIMaster *m_pSPIMaster;
CConfig *m_pConfig;
CCharDevice *m_pLCD;
CHD44780Device *m_pHD44780;
CSSD1306Device *m_pSSD1306;
CST7789Display *m_pST7789Display;
CST7789Device *m_pST7789;
CWriteBufferDevice *m_pLCDBuffered;
CUIButtons *m_pUIButtons;

@ -6,13 +6,13 @@ git submodule update --init --recursive
#
# Use fixed master branch of circle-stdlib then re-update
cd circle-stdlib/
git checkout 695ab4a
git checkout 3bd135d
git submodule update --init --recursive
cd -
#
# Optional update submodules explicitly
cd circle-stdlib/libs/circle
git checkout tags/Step48
git checkout c243194
cd -
cd circle-stdlib/libs/circle-newlib
#git checkout develop

Loading…
Cancel
Save