home.social

#picodexed — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #picodexed, aggregated by home.social.

  1. I finally have a good enough Eurorack version of my picoDexed.

    diyelectromusic.com/2025/05/03

    This is using a neat pico-to-zero adaptor from bablokb, released on Github, which allows me to use my existing MiniDexed PCBs and module with no other hardware changes.

    #picoDexed #RaspberryPiPico #EuroRack #SynthDIY

  2. I finally have a good enough Eurorack version of my picoDexed.

    diyelectromusic.com/2025/05/03

    This is using a neat pico-to-zero adaptor from bablokb, released on Github, which allows me to use my existing MiniDexed PCBs and module with no other hardware changes.

    #picoDexed #RaspberryPiPico #EuroRack #SynthDIY

  3. picoDexed + MiniDexed EuroRack

    Since attempting my picoDexed + StackyPi + MiniDexed EuroRack build and failing, I’ve found another Pico-to-Zero board that is provided as open source, so I’ve had some made.

    This post details how to get that running with my MiniDexed Zero Eurorack module.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    As mentioned last time I found a number of options for a RP2040 based dev board in a Raspberry Pi Zero form factor. But this post is about this one:

    It is fairly simple to build. It just requires a Pico and GPIO header pins.

    There is an option for pull-ups on the I2C bus, but I’ve not bothered with them here. There is also a breakout header for a reset switch if required (it will support two sizes of switch by the looks of things).

    Mapping over to MiniDexed/RPi Zero

    The pinout is slightly different to the Stacky-pi, so here is an updated table of the GPIO mappings and which are required to be used with my MiniDexed board.

    RP2040UseRPiRpiUseRP20403V35VGP2LCD SDAGP2 SDA5VGP3LCD SCLGP3 SCLGNDGP4GP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP5GP17GP18I2S BCLKGP18GP6GP27GNDGP7GP22GP23GP83V3GP24GP28GP11RE BGP10 MOSIGNDGP12RE AGP9 MISOGP25GP27GP10RE SWGP11 SCLKGP8GP9GNDGP7GP26ID_SDID_SCGP22SW BACKGP5GNDGP13SW HOMEGP6GP12GP21GP14GP13GNDGP19I2S LCLKGP19GP16GP20GP15GP26GP20GP17GNDGP21I2S DATAGP16

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP11 and 12.

    The I2S interface might be ok – with a BCLK on GP18, it will be expecting LCLK on GP19. Data on GP21 should be ok.

    Unlike the previous attempt, I’m hopeful I can just get this running ok with the correct pin mappings…

    Changing I2C Bus and UARTs

    Unlike the first attempt, I2C is mapped onto GP2 and GP3 which is what I was using in the original picoDexed. So that is all fine, multiplexed onto the I2C bus 1.

    There is an issue with the UART however as picoDexed uses the following by default:

    • UART 0 – GP 0,1 – Serial debug
    • UART 1 – GP 4,5 – MIDI

    I can swap these over so that UART0 (GP0,1) is MIDI, but that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 16
    #define I2S_BCLK_PIN 18
    #define I2S_LRCLK_PIN 19 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 1
    #define DISPLAY_I2C_SDA 2
    #define DISPLAY_I2C_SCL 3
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 11
    #define ENCODER_B_PIN 12 // Not used
    #define ENCODER_SW_PIN 10 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    I’ve added a separate configuration file (config-ER.h) in the repository to allow this version to be built, but the CMakelists.txt change above has not been included.

    I’ve also added a picodexed-v0.03-ER.uf2 file in the build area which can be downloaded and installed directly onto the Pico to provide the above configuration ready to go.

    Bringing it all together…

    The nice thing about this PCB is that I can map everything nicely over to the pinouts used with my MiniDexed EuroRack PCB meaning that once the Pico has the custom firmware installed, it will just plug in and work and no hardware changes or patching is required at all!

    I was slightly concerned that the USB port of the Pico might clash with the two installed electrolytic capacitors on the MiniDexed PCB, but in my case I can just about get away with it!

    Here is the final assembled unit.

    Closing Thoughts

    I finally have my Eurorack picoDexed which is pretty neat. Big thanks to bablokb for putting that PCB up online. That saved me a job. And it was particularly nice that things like assuming consecutive pins for the I2S mapping was included as that made using the PIO I2S code a lot easier.

    I’d like to see what the power usage is like now as I’m really after a lower power Dexed engine compared to the full Zero version.

    Naturally at some point I might still make my own picoDexed Eurorack PCB, but this is a pretty good solution as far as I’m concerned, so that wouldn’t really add much for me now.

    Kevin

    #midi #minidexed #pcb #picodexed #raspberryPiPico

  4. picoDexed + MiniDexed EuroRack

    Since attempting my picoDexed + StackyPi + MiniDexed EuroRack build and failing, I’ve found another Pico-to-Zero board that is provided as open source, so I’ve had some made.

    This post details how to get that running with my MiniDexed Zero Eurorack module.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    As mentioned last time I found a number of options for a RP2040 based dev board in a Raspberry Pi Zero form factor. But this post is about this one:

    It is fairly simple to build. It just requires a Pico and GPIO header pins.

    There is an option for pull-ups on the I2C bus, but I’ve not bothered with them here. There is also a breakout header for a reset switch if required (it will support two sizes of switch by the looks of things).

    Mapping over to MiniDexed/RPi Zero

    The pinout is slightly different to the Stacky-pi, so here is an updated table of the GPIO mappings and which are required to be used with my MiniDexed board.

    RP2040UseRPiRpiUseRP20403V35VGP2LCD SDAGP2 SDA5VGP3LCD SCLGP3 SCLGNDGP4GP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP5GP17GP18I2S BCLKGP18GP6GP27GNDGP7GP22GP23GP83V3GP24GP28GP11RE BGP10 MOSIGNDGP12RE AGP9 MISOGP25GP27GP10RE SWGP11 SCLKGP8GP9GNDGP7GP26ID_SDID_SCGP22SW BACKGP5GNDGP13SW HOMEGP6GP12GP21GP14GP13GNDGP19I2S LCLKGP19GP16GP20GP15GP26GP20GP17GNDGP21I2S DATAGP16

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP11 and 12.

    The I2S interface might be ok – with a BCLK on GP18, it will be expecting LCLK on GP19. Data on GP21 should be ok.

    Unlike the previous attempt, I’m hopeful I can just get this running ok with the correct pin mappings…

    Changing I2C Bus and UARTs

    Unlike the first attempt, I2C is mapped onto GP2 and GP3 which is what I was using in the original picoDexed. So that is all fine, multiplexed onto the I2C bus 1.

    There is an issue with the UART however as picoDexed uses the following by default:

    • UART 0 – GP 0,1 – Serial debug
    • UART 1 – GP 4,5 – MIDI

    I can swap these over so that UART0 (GP0,1) is MIDI, but that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 16
    #define I2S_BCLK_PIN 18
    #define I2S_LRCLK_PIN 19 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 1
    #define DISPLAY_I2C_SDA 2
    #define DISPLAY_I2C_SCL 3
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 11
    #define ENCODER_B_PIN 12 // Not used
    #define ENCODER_SW_PIN 10 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    I’ve added a separate configuration file (config-ER.h) in the repository to allow this version to be built, but the CMakelists.txt change above has not been included.

    I’ve also added a picodexed-v0.03-ER.uf2 file in the build area which can be downloaded and installed directly onto the Pico to provide the above configuration ready to go.

    Bringing it all together…

    The nice thing about this PCB is that I can map everything nicely over to the pinouts used with my MiniDexed EuroRack PCB meaning that once the Pico has the custom firmware installed, it will just plug in and work and no hardware changes or patching is required at all!

    I was slightly concerned that the USB port of the Pico might clash with the two installed electrolytic capacitors on the MiniDexed PCB, but in my case I can just about get away with it!

    Here is the final assembled unit.

    Closing Thoughts

    I finally have my Eurorack picoDexed which is pretty neat. Big thanks to bablokb for putting that PCB up online. That saved me a job. And it was particularly nice that things like assuming consecutive pins for the I2S mapping was included as that made using the PIO I2S code a lot easier.

    I’d like to see what the power usage is like now as I’m really after a lower power Dexed engine compared to the full Zero version.

    Naturally at some point I might still make my own picoDexed Eurorack PCB, but this is a pretty good solution as far as I’m concerned, so that wouldn’t really add much for me now.

    Kevin

    #midi #minidexed #pcb #picodexed #raspberryPiPico

  5. picoDexed + MiniDexed EuroRack

    Since attempting my picoDexed + StackyPi + MiniDexed EuroRack build and failing, I’ve found another Pico-to-Zero board that is provided as open source, so I’ve had some made.

    This post details how to get that running with my MiniDexed Zero Eurorack module.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    As mentioned last time I found a number of options for a RP2040 based dev board in a Raspberry Pi Zero form factor. But this post is about this one:

    It is fairly simple to build. It just requires a Pico and GPIO header pins.

    There is an option for pull-ups on the I2C bus, but I’ve not bothered with them here. There is also a breakout header for a reset switch if required (it will support two sizes of switch by the looks of things).

    Mapping over to MiniDexed/RPi Zero

    The pinout is slightly different to the Stacky-pi, so here is an updated table of the GPIO mappings and which are required to be used with my MiniDexed board.

    RP2040UseRPiRpiUseRP20403V35VGP2LCD SDAGP2 SDA5VGP3LCD SCLGP3 SCLGNDGP4GP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP5GP17GP18I2S BCLKGP18GP6GP27GNDGP7GP22GP23GP83V3GP24GP28GP11RE BGP10 MOSIGNDGP12RE AGP9 MISOGP25GP27GP10RE SWGP11 SCLKGP8GP9GNDGP7GP26ID_SDID_SCGP22SW BACKGP5GNDGP13SW HOMEGP6GP12GP21GP14GP13GNDGP19I2S LCLKGP19GP16GP20GP15GP26GP20GP17GNDGP21I2S DATAGP16

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP11 and 12.

    The I2S interface might be ok – with a BCLK on GP18, it will be expecting LCLK on GP19. Data on GP21 should be ok.

    Unlike the previous attempt, I’m hopeful I can just get this running ok with the correct pin mappings…

    Changing I2C Bus and UARTs

    Unlike the first attempt, I2C is mapped onto GP2 and GP3 which is what I was using in the original picoDexed. So that is all fine, multiplexed onto the I2C bus 1.

    There is an issue with the UART however as picoDexed uses the following by default:

    • UART 0 – GP 0,1 – Serial debug
    • UART 1 – GP 4,5 – MIDI

    I can swap these over so that UART0 (GP0,1) is MIDI, but that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 16
    #define I2S_BCLK_PIN 18
    #define I2S_LRCLK_PIN 19 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 1
    #define DISPLAY_I2C_SDA 2
    #define DISPLAY_I2C_SCL 3
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 11
    #define ENCODER_B_PIN 12 // Not used
    #define ENCODER_SW_PIN 10 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    I’ve added a separate configuration file (config-ER.h) in the repository to allow this version to be built, but the CMakelists.txt change above has not been included.

    I’ve also added a picodexed-v0.03-ER.uf2 file in the build area which can be downloaded and installed directly onto the Pico to provide the above configuration ready to go.

    Bringing it all together…

    The nice thing about this PCB is that I can map everything nicely over to the pinouts used with my MiniDexed EuroRack PCB meaning that once the Pico has the custom firmware installed, it will just plug in and work and no hardware changes or patching is required at all!

    I was slightly concerned that the USB port of the Pico might clash with the two installed electrolytic capacitors on the MiniDexed PCB, but in my case I can just about get away with it!

    Here is the final assembled unit.

    Closing Thoughts

    I finally have my Eurorack picoDexed which is pretty neat. Big thanks to bablokb for putting that PCB up online. That saved me a job. And it was particularly nice that things like assuming consecutive pins for the I2S mapping was included as that made using the PIO I2S code a lot easier.

    I’d like to see what the power usage is like now as I’m really after a lower power Dexed engine compared to the full Zero version.

    Naturally at some point I might still make my own picoDexed Eurorack PCB, but this is a pretty good solution as far as I’m concerned, so that wouldn’t really add much for me now.

    Kevin

    #midi #minidexed #pcb #picodexed #raspberryPiPico

  6. picoDexed + MiniDexed EuroRack

    Since attempting my picoDexed + StackyPi + MiniDexed EuroRack build and failing, I’ve found another Pico-to-Zero board that is provided as open source, so I’ve had some made.

    This post details how to get that running with my MiniDexed Zero Eurorack module.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    As mentioned last time I found a number of options for a RP2040 based dev board in a Raspberry Pi Zero form factor. But this post is about this one:

    It is fairly simple to build. It just requires a Pico and GPIO header pins.

    There is an option for pull-ups on the I2C bus, but I’ve not bothered with them here. There is also a breakout header for a reset switch if required (it will support two sizes of switch by the looks of things).

    Mapping over to MiniDexed/RPi Zero

    The pinout is slightly different to the Stacky-pi, so here is an updated table of the GPIO mappings and which are required to be used with my MiniDexed board.

    RP2040UseRPiRpiUseRP20403V35VGP2LCD SDAGP2 SDA5VGP3LCD SCLGP3 SCLGNDGP4GP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP5GP17GP18I2S BCLKGP18GP6GP27GNDGP7GP22GP23GP83V3GP24GP28GP11RE BGP10 MOSIGNDGP12RE AGP9 MISOGP25GP27GP10RE SWGP11 SCLKGP8GP9GNDGP7GP26ID_SDID_SCGP22SW BACKGP5GNDGP13SW HOMEGP6GP12GP21GP14GP13GNDGP19I2S LCLKGP19GP16GP20GP15GP26GP20GP17GNDGP21I2S DATAGP16

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP11 and 12.

    The I2S interface might be ok – with a BCLK on GP18, it will be expecting LCLK on GP19. Data on GP21 should be ok.

    Unlike the previous attempt, I’m hopeful I can just get this running ok with the correct pin mappings…

    Changing I2C Bus and UARTs

    Unlike the first attempt, I2C is mapped onto GP2 and GP3 which is what I was using in the original picoDexed. So that is all fine, multiplexed onto the I2C bus 1.

    There is an issue with the UART however as picoDexed uses the following by default:

    • UART 0 – GP 0,1 – Serial debug
    • UART 1 – GP 4,5 – MIDI

    I can swap these over so that UART0 (GP0,1) is MIDI, but that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 16
    #define I2S_BCLK_PIN 18
    #define I2S_LRCLK_PIN 19 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 1
    #define DISPLAY_I2C_SDA 2
    #define DISPLAY_I2C_SCL 3
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 11
    #define ENCODER_B_PIN 12 // Not used
    #define ENCODER_SW_PIN 10 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    I’ve added a separate configuration file (config-ER.h) in the repository to allow this version to be built, but the CMakelists.txt change above has not been included.

    I’ve also added a picodexed-v0.03-ER.uf2 file in the build area which can be downloaded and installed directly onto the Pico to provide the above configuration ready to go.

    Bringing it all together…

    The nice thing about this PCB is that I can map everything nicely over to the pinouts used with my MiniDexed EuroRack PCB meaning that once the Pico has the custom firmware installed, it will just plug in and work and no hardware changes or patching is required at all!

    I was slightly concerned that the USB port of the Pico might clash with the two installed electrolytic capacitors on the MiniDexed PCB, but in my case I can just about get away with it!

    Here is the final assembled unit.

    Closing Thoughts

    I finally have my Eurorack picoDexed which is pretty neat. Big thanks to bablokb for putting that PCB up online. That saved me a job. And it was particularly nice that things like assuming consecutive pins for the I2S mapping was included as that made using the PIO I2S code a lot easier.

    I’d like to see what the power usage is like now as I’m really after a lower power Dexed engine compared to the full Zero version.

    Naturally at some point I might still make my own picoDexed Eurorack PCB, but this is a pretty good solution as far as I’m concerned, so that wouldn’t really add much for me now.

    Kevin

    #midi #minidexed #pcb #picodexed #raspberryPiPico

  7. picoDexed + MiniDexed EuroRack

    Since attempting my picoDexed + StackyPi + MiniDexed EuroRack build and failing, I’ve found another Pico-to-Zero board that is provided as open source, so I’ve had some made.

    This post details how to get that running with my MiniDexed Zero Eurorack module.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    As mentioned last time I found a number of options for a RP2040 based dev board in a Raspberry Pi Zero form factor. But this post is about this one:

    It is fairly simple to build. It just requires a Pico and GPIO header pins.

    There is an option for pull-ups on the I2C bus, but I’ve not bothered with them here. There is also a breakout header for a reset switch if required (it will support two sizes of switch by the looks of things).

    Mapping over to MiniDexed/RPi Zero

    The pinout is slightly different to the Stacky-pi, so here is an updated table of the GPIO mappings and which are required to be used with my MiniDexed board.

    RP2040UseRPiRpiUseRP20403V35VGP2LCD SDAGP2 SDA5VGP3LCD SCLGP3 SCLGNDGP4GP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP5GP17GP18I2S BCLKGP18GP6GP27GNDGP7GP22GP23GP83V3GP24GP28GP11RE BGP10 MOSIGNDGP12RE AGP9 MISOGP25GP27GP10RE SWGP11 SCLKGP8GP9GNDGP7GP26ID_SDID_SCGP22SW BACKGP5GNDGP13SW HOMEGP6GP12GP21GP14GP13GNDGP19I2S LCLKGP19GP16GP20GP15GP26GP20GP17GNDGP21I2S DATAGP16

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP11 and 12.

    The I2S interface might be ok – with a BCLK on GP18, it will be expecting LCLK on GP19. Data on GP21 should be ok.

    Unlike the previous attempt, I’m hopeful I can just get this running ok with the correct pin mappings…

    Changing I2C Bus and UARTs

    Unlike the first attempt, I2C is mapped onto GP2 and GP3 which is what I was using in the original picoDexed. So that is all fine, multiplexed onto the I2C bus 1.

    There is an issue with the UART however as picoDexed uses the following by default:

    • UART 0 – GP 0,1 – Serial debug
    • UART 1 – GP 4,5 – MIDI

    I can swap these over so that UART0 (GP0,1) is MIDI, but that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 16
    #define I2S_BCLK_PIN 18
    #define I2S_LRCLK_PIN 19 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 1
    #define DISPLAY_I2C_SDA 2
    #define DISPLAY_I2C_SCL 3
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 11
    #define ENCODER_B_PIN 12 // Not used
    #define ENCODER_SW_PIN 10 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    I’ve added a separate configuration file (config-ER.h) in the repository to allow this version to be built, but the CMakelists.txt change above has not been included.

    I’ve also added a picodexed-v0.03-ER.uf2 file in the build area which can be downloaded and installed directly onto the Pico to provide the above configuration ready to go.

    Bringing it all together…

    The nice thing about this PCB is that I can map everything nicely over to the pinouts used with my MiniDexed EuroRack PCB meaning that once the Pico has the custom firmware installed, it will just plug in and work and no hardware changes or patching is required at all!

    I was slightly concerned that the USB port of the Pico might clash with the two installed electrolytic capacitors on the MiniDexed PCB, but in my case I can just about get away with it!

    Here is the final assembled unit.

    Closing Thoughts

    I finally have my Eurorack picoDexed which is pretty neat. Big thanks to bablokb for putting that PCB up online. That saved me a job. And it was particularly nice that things like assuming consecutive pins for the I2S mapping was included as that made using the PIO I2S code a lot easier.

    I’d like to see what the power usage is like now as I’m really after a lower power Dexed engine compared to the full Zero version.

    Naturally at some point I might still make my own picoDexed Eurorack PCB, but this is a pretty good solution as far as I’m concerned, so that wouldn’t really add much for me now.

    Kevin

    #midi #minidexed #pcb #picodexed #raspberryPiPico

  8. @rpimag I went back to my picoDexed (Synth_Dexed on a Pico) and updated it for the latest Pico SDK and added a simple display and encoder.

    diyelectromusic.com/2025/04/12

    I've not quite got to the stage shown in the photo - the sound isn't working on this one yet - but it's getting close (it worked fine on my breadboard version)!

    And yes, this is one of my MiniDexed front panels...

    #WorkInProgress #MakerMonday #picoDexed

  9. @rpimag I went back to my picoDexed (Synth_Dexed on a Pico) and updated it for the latest Pico SDK and added a simple display and encoder.

    diyelectromusic.com/2025/04/12

    I've not quite got to the stage shown in the photo - the sound isn't working on this one yet - but it's getting close (it worked fine on my breadboard version)!

    And yes, this is one of my MiniDexed front panels...

    #WorkInProgress #MakerMonday #picoDexed

  10. I thought this would be an easy fix - using a Pi Zero-format RP2040 board in my MiniDexed EuroRack module...

    ... but it ended up an afternoon of frustration and unfortunately I still can't get it making sound or booting reliably.

    It does look cool though :)

    Anyone else have any experience of these Stacky-Pi "RP2040 on a Pi Zero shaped PCB" boards?

    diyelectromusic.com/2025/04/12

    #PicoDexed #MIDI #StackyPi #MakerFail

  11. I thought this would be an easy fix - using a Pi Zero-format RP2040 board in my MiniDexed EuroRack module...

    ... but it ended up an afternoon of frustration and unfortunately I still can't get it making sound or booting reliably.

    It does look cool though :)

    Anyone else have any experience of these Stacky-Pi "RP2040 on a Pi Zero shaped PCB" boards?

    diyelectromusic.com/2025/04/12

    #PicoDexed #MIDI #StackyPi #MakerFail

  12. picoDexed + StackyPi + MiniDexed EuroRack

    Now that I have a picoDexed with a display an encoder it is very tempting to create a version of my MiniDexed EuroRack PCB for it.

    But as a short diversion, there is another possibility – can I use a RP2040 that is already in a Pi Zero form factor with my existing MiniDexed EuroRack PCB Design?

    Spoilers: The answer, it turns out, is no. Something is not quite letting this work for me – it’s close, but I’m just not there yet. Here is another attempt where it works: picoDexed + MiniDexed EuroRack.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    I’m aware of a couple of products that were available to put a RP2040 into a Raspberry Pi Zero form factor:

    The first two were available on Tindie and other places, and as far as I can see, both appear to use the same mapping of RP2040 GPIO to RPi 40-pin header. The Red Robot board doesn’t seem to be available anymore, and whilst there are a few Stacky-Pis on Tindie, in most places it seems to be discontinued.

    I don’t know anything about the Waveshare or Pico to Pi, but might grab myself one of each.

    I’ll give the open source design a try (and possibly save me designing my own…).

    As can be seen above, the Red Robots board is designed to take an actual Pico, whereas the Stacky-Pi is its own board with an on-board RP2040.

    Mapping over to MiniDexed/RPi Zero

    The following table lists the RPi GPIO connections I used in my MiniDexed board and how they map onto the RP2040 using the above boards.

    RP2040UseRPiRpiUseRP20403V35VGP20LCD SDAGP2 SDA5VGP21LCD SCLGP3 SCLGNDGP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP17GP18I2S BCLKGP28GP27GNDGP22GP233V3GP24GP3RE BGP10 MOSIGNDGP4RE AGP9 MISOGP25GP2RE SWGP11 SCLKGP8GNDGP7ID_SDID_SCGP10SW BACKGP5GNDGP11SW HOMEGP6GP12GP12GP13GNDGP13I2S LCLKGP19GP16GP26GP20GNDGP21I2S DATAGP15

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP3 and 4.

    The I2S interface is going to be tricky, as with a BCLK on GP28, it will be expecting LCLK on GP29 rather than the GP13 it is currently routed to on the board. Quite apart from the fact that GP29 isn’t even broken out on a Pico.

    The obvious thing to explore is if the BCLK connection can be routed through to GP13 on the RPi header, or GP12 for the RP2040. That would be handy if so.

    Connecting into GP13 should be possible as this pin is currently unconnected on the MiniDexed PCB. It will involve cutting a track however for GP18. The existing track and its new destination is highlighted below in blue.

    Changing I2C Bus and UARTs

    There is one other complication however. The picoDexed configuration has the ssd1306 connected to GP2 and GP3 which are multiplexed onto the I2C bus 1.

    The adaptor configuration maps SDA/SCL onto GP20/21 which are multiplexed onto I2C bus 0. I updated the code to support changing I2C bus in addition to changing IO pins. Arguably, I should probably have supported this in the first place anyway…

    A similar issue exists for the UART, but unfortunately that isn’t quite so easy to change.

    I can take a similar approach to the above for the Serial MIDI link – allowing it to use either UART0 or UART1.

    But that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 15
    #define I2S_BCLK_PIN 12
    #define I2S_LRCLK_PIN 13 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 0
    #define DISPLAY_I2C_SDA 20
    #define DISPLAY_I2C_SCL 21
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 3
    #define ENCODER_B_PIN 4 // Not used
    #define ENCODER_SW_PIN 2 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    Bringing it all together…

    I decided to hack on my (already pretty hacked) prototype MiniDexed EuroRack board and cut and patch the trace to the DAC as described above.

    The complication being the GPIO header pin I need to get to is under the OLED display, but I could just about do it – see below.

    It doesn’t work. Unfortunately.

    First of, I have to say, I’ve not found the Stacky-Pi particularly reliable. It took me ages to get it to successfully boot up into accepting a uf2 file, and once installed, I was struggling to get it to reliably boot and run.

    Weirdly it seems to work best when I have a finger on the flash chip which seems to imply some board layout/grounding/stability issues to me…

    From what I can see, the display, MIDI, serial debug and encoder are working fine though once it does get up and running.

    But I just can’t get any reliable sound out of the thing at all. I managed sound once, but that was it. It is all quiet unreliable for me – far too unreliable to be useful.

    Shame, as it actually looks really cool!

    Closing Thoughts

    It has been a frustrating afternoon. I’m having to leave this one here for now as there are just too many unknowns at the moment to really get to the bottom of what is going on.

    I thought the Stacky-Pi would be a quick and easy fix, but haven’t ever used them before, and the fact that they are discontinued and there is very little information that I can find online about them makes me think perhaps they aren’t worth persevering with.

    So I have a number of options now:

    • Try a Waveshare RP2040 PiZero. As there are a lot more peripherals, I’m not sure how much what I’m doing will translate across, to be honest, it cost wise, it’s essentially the same as a Zero itself, which I know “just works”.
    • Do I design my own Pico to RPI GPIO converter board to let me use that with my existing MiniDexed EuroRack design? Tempting and probably not that hard.
    • Do I attempt to do something with the Pico version of my EuroRack 6HP MCU Experimenter Module? Sounds initially easy but I suspect forcing a MiniDexed into this module eventually will hit other at the moment unforeseen issues.
    • Or do I just go for it and put together a special picoDexed EuroRack module itself.

    I might have one more go with the Stacky-Pi. I haven’t quite given up. I’ll have to do some research – maybe sprinkling a few capacitors around the board or some updated GND connections might help. Answers on a postcard (or in the comments) if you have any ideas.

    I’ll get a Waveshare RP2040 PiZero on order, as I’ve had quite a bit of success with their own “Zero” miniature boards so far, and now I’d like it know if it would work 🙂

    To be continued…

    Kevin

    #define #midi #minidexed #picodexed #raspberryPiPico #StackyPi

  13. picoDexed + StackyPi + MiniDexed EuroRack

    Now that I have a picoDexed with a display an encoder it is very tempting to create a version of my MiniDexed EuroRack PCB for it.

    But as a short diversion, there is another possibility – can I use a RP2040 that is already in a Pi Zero form factor with my existing MiniDexed EuroRack PCB Design?

    Spoilers: The answer, it turns out, is no. Something is not quite letting this work for me – it’s close, but I’m just not there yet. Here is another attempt where it works: picoDexed + MiniDexed EuroRack.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Pi Zero RP2040s

    I’m aware of a couple of products that were available to put a RP2040 into a Raspberry Pi Zero form factor:

    The first two were available on Tindie and other places, and as far as I can see, both appear to use the same mapping of RP2040 GPIO to RPi 40-pin header. The Red Robot board doesn’t seem to be available anymore, and whilst there are a few Stacky-Pis on Tindie, in most places it seems to be discontinued.

    I don’t know anything about the Waveshare or Pico to Pi, but might grab myself one of each.

    I’ll give the open source design a try (and possibly save me designing my own…).

    As can be seen above, the Red Robots board is designed to take an actual Pico, whereas the Stacky-Pi is its own board with an on-board RP2040.

    Mapping over to MiniDexed/RPi Zero

    The following table lists the RPi GPIO connections I used in my MiniDexed board and how they map onto the RP2040 using the above boards.

    RP2040UseRPiRpiUseRP20403V35VGP20LCD SDAGP2 SDA5VGP21LCD SCLGP3 SCLGNDGP4GP14 TXDGP0GNDGP15 RXDMIDI INGP1GP17GP18I2S BCLKGP28GP27GNDGP22GP233V3GP24GP3RE BGP10 MOSIGNDGP4RE AGP9 MISOGP25GP2RE SWGP11 SCLKGP8GNDGP7ID_SDID_SCGP10SW BACKGP5GNDGP11SW HOMEGP6GP12GP12GP13GNDGP13I2S LCLKGP19GP16GP26GP20GNDGP21I2S DATAGP15

    The two key problem areas will be the I2S interface and encoder, which both require consecutive GPIO pins for the PIO code to do its magic.

    The encoder should be fine – pins RE A and RE B map onto the Pico’s GP3 and 4.

    The I2S interface is going to be tricky, as with a BCLK on GP28, it will be expecting LCLK on GP29 rather than the GP13 it is currently routed to on the board. Quite apart from the fact that GP29 isn’t even broken out on a Pico.

    The obvious thing to explore is if the BCLK connection can be routed through to GP13 on the RPi header, or GP12 for the RP2040. That would be handy if so.

    Connecting into GP13 should be possible as this pin is currently unconnected on the MiniDexed PCB. It will involve cutting a track however for GP18. The existing track and its new destination is highlighted below in blue.

    Changing I2C Bus and UARTs

    There is one other complication however. The picoDexed configuration has the ssd1306 connected to GP2 and GP3 which are multiplexed onto the I2C bus 1.

    The adaptor configuration maps SDA/SCL onto GP20/21 which are multiplexed onto I2C bus 0. I updated the code to support changing I2C bus in addition to changing IO pins. Arguably, I should probably have supported this in the first place anyway…

    A similar issue exists for the UART, but unfortunately that isn’t quite so easy to change.

    I can take a similar approach to the above for the Serial MIDI link – allowing it to use either UART0 or UART1.

    But that has to be matched with a change in the debug serial port too. But unfortunately, as far as I can see, that has to be configured in the master CMakeLists.txt file (as I talked about in Part 3).

    New picoDexed GPIO Configuration

    Given the above, the following new GPIO pins should be defined in config.h:

    #define PWM_PIN         10

    #define I2S_DATA_PIN 15
    #define I2S_BCLK_PIN 12
    #define I2S_LRCLK_PIN 13 // Implied by BCLK=12

    #define MIDI_UART 0
    #define MIDI_TX_PIN 0 // Not used
    #define MIDI_RX_PIN 1

    #define DEBUG_UART_TX_PIN 8
    #define DEBUG_UART_RX_PIN 9

    #define DISPLAY_I2C_BUS 0
    #define DISPLAY_I2C_SDA 20
    #define DISPLAY_I2C_SCL 21
    #define DISPLAY_I2C_ADDR 0x3C
    #define DISPLAY_W 128
    #define DISPLAY_H 32

    #define ENCODER_A_PIN 3
    #define ENCODER_B_PIN 4 // Not used
    #define ENCODER_SW_PIN 2 // Not used

    In addition to this, to keep using the debug output requires the following lines adding to ‘target_compile_definitions’ in CMakeLists.txt.

        PICO_DEFAULT_UART=1
    PICO_DEFAULT_UART_TX_PIN=8
    PICO_DEFAULT_UART_RX_PIN=9

    Bringing it all together…

    I decided to hack on my (already pretty hacked) prototype MiniDexed EuroRack board and cut and patch the trace to the DAC as described above.

    The complication being the GPIO header pin I need to get to is under the OLED display, but I could just about do it – see below.

    It doesn’t work. Unfortunately.

    First of, I have to say, I’ve not found the Stacky-Pi particularly reliable. It took me ages to get it to successfully boot up into accepting a uf2 file, and once installed, I was struggling to get it to reliably boot and run.

    Weirdly it seems to work best when I have a finger on the flash chip which seems to imply some board layout/grounding/stability issues to me…

    From what I can see, the display, MIDI, serial debug and encoder are working fine though once it does get up and running.

    But I just can’t get any reliable sound out of the thing at all. I managed sound once, but that was it. It is all quiet unreliable for me – far too unreliable to be useful.

    Shame, as it actually looks really cool!

    Closing Thoughts

    It has been a frustrating afternoon. I’m having to leave this one here for now as there are just too many unknowns at the moment to really get to the bottom of what is going on.

    I thought the Stacky-Pi would be a quick and easy fix, but haven’t ever used them before, and the fact that they are discontinued and there is very little information that I can find online about them makes me think perhaps they aren’t worth persevering with.

    So I have a number of options now:

    • Try a Waveshare RP2040 PiZero. As there are a lot more peripherals, I’m not sure how much what I’m doing will translate across, to be honest, it cost wise, it’s essentially the same as a Zero itself, which I know “just works”.
    • Do I design my own Pico to RPI GPIO converter board to let me use that with my existing MiniDexed EuroRack design? Tempting and probably not that hard.
    • Do I attempt to do something with the Pico version of my EuroRack 6HP MCU Experimenter Module? Sounds initially easy but I suspect forcing a MiniDexed into this module eventually will hit other at the moment unforeseen issues.
    • Or do I just go for it and put together a special picoDexed EuroRack module itself.

    I might have one more go with the Stacky-Pi. I haven’t quite given up. I’ll have to do some research – maybe sprinkling a few capacitors around the board or some updated GND connections might help. Answers on a postcard (or in the comments) if you have any ideas.

    I’ll get a Waveshare RP2040 PiZero on order, as I’ve had quite a bit of success with their own “Zero” miniature boards so far, and now I’d like it know if it would work 🙂

    To be continued…

    Kevin

    #define #midi #minidexed #picodexed #raspberryPiPico #StackyPi

  14. I've updated my #picoDexed to include a basic encoder/display UI and updated to the latest Pico SDK.

    This is a port of Synth_Dexed to the Raspberry Pi PIco.

    I also thought it was probably about time I actually documented the basic architecture :)

    diyelectromusic.com/2025/04/12

    #MIDI #SynthDIY #DX7 #Synth_Dexed

  15. I've updated my #picoDexed to include a basic encoder/display UI and updated to the latest Pico SDK.

    This is a port of Synth_Dexed to the Raspberry Pi PIco.

    I also thought it was probably about time I actually documented the basic architecture :)

    diyelectromusic.com/2025/04/12

    #MIDI #SynthDIY #DX7 #Synth_Dexed

  16. Raspberry Pi Pico Synth_Dexed – Revisited

    I thought it was time I took another look at my Raspberry Pi Pico Synth_Dexed. I’ve a couple of main aims with coming back to this project:

    • Update the build for the latest Pico SDK and Synth_Dexed.
    • See what is involved in getting it running on an RP2350.
    • See if a simple I2C display can be added and possibly an encoder or other controls.
    • Actually document the architecture!

    The current hardware is described in detail in Raspberry Pi Pico Synth_Dexed? – Part 5 and supports serial and USB MIDI, I2S or PWM audio output, voice selection (over MIDI) and up to 16 note polyphony if using an overclocked (to 250MHz) RP2040 based Pico and lower sample rate.

    https://makertube.net/w/tY1u9qFz85NprRYPmtdvEj

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    Core Updates

    Since I first put the PicoDexed together, V2 of the Raspberry Pi Pico SDK has been released. There aren’t many changes, but as noted by okyeron, an additional include file is required in main.c.

    But as I was updating things, I also found a number of changes to Synth_Dexed too that broke the build. The most irritating of which was including a definition of DEXED_SAMPLE_RATE which now clashes with my own definition. I’ve gone through and changed a number of DEXED_ definitions to PICODEXED_ to avoid that in the future too.

    A few other changes have also been introduced, which have necessitated an update to my diff file for Synth_Dexed/src/Dexed.cpp.

    But on the whole, there was nothing major in the end. This has all been uploaded to GitHub as a v0.02.

    PicoDexed Architecture

    I had to go back and attempt to reacquaint myself with what I’d done last time, so as part of that I’ve drawn out a rough architecture diagram as shown below.

    This expands on the design notes I’ve already written up in Part 3.

    The key principles are as follows:

    • Core 0 is used for all IO and is largely driven by MIDI events received over serial or USB.
    • Core 1 is used for all audio processing and is run continually using the Pico’s audio driver (either I2S or PWM) to use a callback function to fill a buffer with samples to be played. The samples come from Synth_Dexed.
    • All synthesis parameters (including keyup/down events for playing notes) is handled within picoDexed in response to the MIDIMessageHandler receiving MIDI messages from the MIDIParser.

    Looking at this, I should be able to include some additional IO handling in the main Process loop of picoDexed that runs on core 0.

    I2C Displays

    Hunting around for libraries to support an SSD1306 display with the Pico SDK, so far I’ve found four options:

    • pico-examples – has an example app that can put simple text or an image on an SSD1306. Note this isn’t created as a library that could be used however – it is all in a single example.
    • sharkis/pico-ssd1306 – what appears to be a couple of files that can be included in your own code. It is pretty low-level with minimal options for any text or graphics and there is no documentation or licensing information as far as I can see.
    • daschr/pico-ssd1306 – a higher level interface that provides basic graphics primitives and simple font support. MIT license. Again a couple of files to include in your project.
    • Harbys/pico-ssd1306 – the most complete support I’ve found so far. What I’d consider to be a “proper” library with what seems to be good documentation and a wide range of usage options.

    Whilst tempted to go with the “proper” library, it might be a little over the top for what I really need right now, and I don’t want to include an additional third-party GitHub link in my code at this time.

    So I’m going to try daschr’s pico-ssd1306 as I can just grab the files, maintain the license information, and include it directly into my code.

    To include this code in the build I’ve done the following:

    • Created a libs area and copied in the files: ssd1306.c, ssd1306.h, font.h
    • Added the c file to the CMakeLists.txt file.
    • Added libs to the include path.
    • Added hardware_i2c as an included library.

    My new CMakeLists.txt file now looks as follows.

    cmake_minimum_required(VERSION 3.13)
    include(pico_sdk_import.cmake)
    include(pico_extras_import.cmake)
    project(picodexed)
    pico_sdk_init()
    add_executable(picodexed)

    target_sources(picodexed PUBLIC
    ${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/pico_perf.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/mididevice.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/picodexed.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/serialmidi.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/sounddevice.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/usbmidi.cpp
    ${CMAKE_CURRENT_LIST_DIR}/src/usbtask.c
    ${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c
    ${CMAKE_CURRENT_LIST_DIR}/libs/ssd1306.c
    )

    add_subdirectory(synth_dexed)

    target_include_directories(picodexed PUBLIC
    ${CMAKE_CURRENT_LIST_DIR}/src
    ${CMAKE_CURRENT_LIST_DIR}/libs
    ${CMAKE_CURRENT_LIST_DIR}/synth_dexed/Synth_Dexed/src
    ${CMAKE_CURRENT_LIST_DIR}/synth_dexed
    )

    target_link_libraries(picodexed PUBLIC synth_dexed pico_stdlib pico_multicore tinyusb_device tinyusb_board pico_audio_i2s pico_audio_pwm hardware_i2c)

    target_compile_definitions(picodexed PRIVATE
    PICO_AUDIO_I2S_MONO_OUTPUT=1
    PICO_AUDIO_I2S_MONO_INPUT=1
    USE_AUDIO_I2S=1
    USE_AUDIO_PWM=1
    )

    pico_add_extra_outputs(picodexed)

    The most basic usage is as follows:

    static ssd1306_t disp;

    i2c_init(i2c1, 400000);
    gpio_set_function(2, GPIO_FUNC_I2C);
    gpio_set_function(3, GPIO_FUNC_I2C);
    gpio_pull_up(2);
    gpio_pull_up(3);

    disp.external_vcc=false;
    ssd1306_init(&disp, 128, 32, 0x3c, i2c1);
    ssd1306_clear(&disp);

    ssd1306_draw_string(&disp, 8, 8, 2, "picoDexed");
    ssd1306_show(&disp);

    This initialises I2C bus 1 on GPIO 2 and 3 for device at address 0x3C.

    Rotary Encoder

    Taking the same approach with a rotary encoder, I’ve so far found the following as candidates for use:

    The first is a more complete library, the second more “bare bones”. The last seems more of an example rather than a reusable object.

    I was initially inclined towards the second which seemed likely to be easier to integrate into my own code, but as it was pretty low level and required a fair bit of glue around it.

    Eventually I actually opted for something based on “GitJer”‘s example. There is a full explanation of how the code works here: https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Rotary_encoder

    I’ve had to change how voices are selected slightly to ensure the display, MIDI BANKSEL/PC and encoder can stay reasonably intuitive.

    I’ve used the following logic:

    • IF BANKSEL/PC both received then treat as Bank=0-7; Voice=0-31.
    • IF PC on its own, then treat as Voice=0-127 across Banks 0 to 3.
    • IF UI changes voices, then treat as Bank=0-7; Voice=0-31.

    In each case the display shows the current bank and voice number in 0-7/0-31 format.

    So just to be clear, MIDI Program Select on its own now (so 0..128) will always only select from the first four banks of 32 voices each. This is a change from the previous version which would allow PC to select based on the currently selected banks and the three following banks.

    The encoder will automatically switch from one bank to the next, and wrap around at either end, so is able to select all voices across all installed banks.

    Raspberry Pi Pico PIO Use

    One issue to keep an eye on is PIO state-machine use.

    I’m still getting my head around PIO (I’ve just not really devoted any significant time to figuring it out yet) but as I understand things, each PIO instance has a 32 instruction memory which is shared across four state machines.

    So if there are several programmes to run and they all combined fit in the 32 instruction memory, then they could all use the same PIO instance whilst being attached to different state machines.

    But if there are two vastly different programs to be running then it may be that they have to be split across the two PIO instances. But there can still be up to four instance of each program, one for each state machine in a PIO instance.

    The I2S audio driver uses PIO to implement I2S. As far as I can see which PIO to use is determined by the following definitions:

    PICO_AUDIO_PIO
    PICO_AUDIO_I2S_PIO
    PICO_AUDIO_PWM_PIO

    If PICO_AUDIO_PIO is not defined then I2S_PIO and PWM_PIO are set to 0, which is then turned into “pio0” via the following in i2s_audio.c:

    #define audio_pio __CONCAT(pio, PICO_AUDIO_I2S_PIO)

    Which later on then uses the following to claim a free statemachine as part of audio_i2s_setup():

    pio_sm_claim(audio_pio, sm);

    I don’t know for certain, but it appears to me that the I2S PIO program is quite complete and so highly likely to fill the 32 word instruction memory.

    On that basis, then the rotary encoder PIO program will have to use PIO instance 1 for its own code.

    Summary of PIO use:

    UsePIO InstanceNumber of State machinesI2SPIO 01EncoderPIO 11

    Update GPIO Usage

    Expanding now on the table from Part 5, updated with the above, now gives the following GPIO usage map.

    GP0Debug UART TXGP1Debug UART RX (unused at present)GP2SDA (I2C bus 1) OLED displayGP3SCL (I2C bus 1) OLED displayGP4MIDI TX (unused at present)GP5MIDI RXGP6Rotary Encoder AGP7Rotary Encoder BGP8Rotary Encoder Switch (not used)GP9I2S Data (DATA, DIN)GP10I2S Bit Clock (BCK)GP11I2S “Left/Right” Clock (LCK, LRCK, LR_CLOCK)GP20Optional: PWM outputGP22Optional: Mute pin for the Pimoroni Audio Pack (not used)VSYS5V power to DAC and OLED display (if required)3V3_OUT3V3 power to MIDI IN and encoder (if required)GND

    PicoDexed on a Breadboard

    Closing Thoughts

    I feel like this is really starting to get interesting now. I have a few choices to make now – do I attempt to go for a more complete menu system similar to MiniDexed, or do I stay with MIDI control and basic voice selection as it is now?

    Future enhancements might include:

    • It will be interesting to see what supporting the RP2350 could bring.
    • It would be useful to be able to set the MIDI channel – either in menu, or perhaps more ideally in hardware somehow (e.g. switches, resistors on an ADC, etc).
    • Act as a USB host for USB MIDI devices.

    In the video I’ve hooked it up to a serial MIDI controller and am just cycling through some of the voices showing how the UI works.

    Kevin

    #dac #dx7 #i2c #midi #picodexed #raspberryPiPico #rotaryEncoder #synthDexed

  17. My picoDexed now has very rudimentary ssd1306 OLED display support.

    All it shows is voice name at the moment.

    #picodexed

  18. My picoDexed now has very rudimentary ssd1306 OLED display support.

    All it shows is voice name at the moment.

    #picodexed

  19. @[email protected] @[email protected] Oh, I forgot it was #MakersHour

    Oh well. I'll perhaps sit and wander through the responses in the morning :)

    (I was buried in the code for my #picoDexed after not really touching it for a year)

  20. @[email protected] @[email protected] Oh, I forgot it was #MakersHour

    Oh well. I'll perhaps sit and wander through the responses in the morning :)

    (I was buried in the code for my #picoDexed after not really touching it for a year)

  21. @todbot With DSP and floating point, my #PicoDexed suddenly feels a lot more plausible!! :)

  22. @todbot With DSP and floating point, my #PicoDexed suddenly feels a lot more plausible!! :)

  23. One last thing I definitely wanted for my PicoDexed was the option for PWM output. This post is by way of a short coda detailing how to do PWM on a Raspberry Pi Pico.

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
    • Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
    • Part 5 details different options for building hardware to run PicoDexed.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    PWM Output Circuit

    The official “Hardware Design with the RP2040” guide includes a PWM circuit as follows (see section 3.4.1).

    Several others have used a slightly simplified version of this circuit, essentially omitting the buffer stage (U3) and perhaps only implementing a single channel. See for example – Raspberry Pi Pico PWM Audio Output Circuit (Jeremy S. Cook) and “pico-pwm-audio” (Robin Grosset).

    An alternative is this circuit used by Tod Kurt for his Circuitpython-tricks“The output circuitry to get line-out levels is a simple 10:1 voltage-divider and a 1uF capacitor to recenter the signal around 0 volts”:

    I’m going with Jeremy S. Cook and Robin Grosset and using the following (diagram from here):

    This is my solderless breadboard version of the above:

    Raspberry Pi Pico Audio PWM

    There appears to be two ways of getting audio style PWM signals out of a Pico:

    • Use the PWM peripherals.
    • Use the PIO.

    It would appear that Raspberry Pi’s pico_audio library in the pico-extras repository uses PIO. I haven’t found a clear explanation as to why the built-in PWM peripherals haven’t been used.

    Here are some other resources that show alternative descriptions of PWM for audion on a Pico:

    As I’m using the pico_audio library for I2S audio, I’m going to use the default PWM pico_audio library too.

    To initialise the pico_audio PWM library requires the following:

    const audio_pwm_channel_config_t config =
    {
    .core = {
    .base_pin = base_pin,
    .dma_channel = dma_ch,
    .pio_sm = pio_sm
    },
    .pattern = 3,
    };

    const struct audio_format *output_format;
    output_format = audio_pwm_setup(pAudioFormat, -1, &config);
    bool status = audio_pwm_default_connect(pBufferPool, false);
    audio_pwm_set_enabled(true);

    Everything else is the same as for I2S.

    At present, overclocking the Pico causes the PWM frequencies to be messed up, so for now the recommended configuration is 8-note polyphony, 24000 sample rate and no overclocking.

    The build uses GPIO 20 for PWM.

    Closing Thoughts

    I’ve uploaded a prototype PWM UF2 file to GitHub too now in case anyone wants to give that a go too: https://github.com/diyelectromusic/picodexed

    Hopefully there is enough information in the above to get something up and running.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/19/raspberry-pi-pico-synth_dexed-part-6/

    #dx7 #midi #picodexed #pwm #raspberryPiPico #usbMidi

  24. One last thing I definitely wanted for my PicoDexed was the option for PWM output. This post is by way of a short coda detailing how to do PWM on a Raspberry Pi Pico.

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
    • Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
    • Part 5 details different options for building hardware to run PicoDexed.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    PWM Output Circuit

    The official “Hardware Design with the RP2040” guide includes a PWM circuit as follows (see section 3.4.1).

    Several others have used a slightly simplified version of this circuit, essentially omitting the buffer stage (U3) and perhaps only implementing a single channel. See for example – Raspberry Pi Pico PWM Audio Output Circuit (Jeremy S. Cook) and “pico-pwm-audio” (Robin Grosset).

    An alternative is this circuit used by Tod Kurt for his Circuitpython-tricks“The output circuitry to get line-out levels is a simple 10:1 voltage-divider and a 1uF capacitor to recenter the signal around 0 volts”:

    I’m going with Jeremy S. Cook and Robin Grosset and using the following (diagram from here):

    This is my solderless breadboard version of the above:

    Raspberry Pi Pico Audio PWM

    There appears to be two ways of getting audio style PWM signals out of a Pico:

    • Use the PWM peripherals.
    • Use the PIO.

    It would appear that Raspberry Pi’s pico_audio library in the pico-extras repository uses PIO. I haven’t found a clear explanation as to why the built-in PWM peripherals haven’t been used.

    Here are some other resources that show alternative descriptions of PWM for audion on a Pico:

    As I’m using the pico_audio library for I2S audio, I’m going to use the default PWM pico_audio library too.

    To initialise the pico_audio PWM library requires the following:

    const audio_pwm_channel_config_t config =
    {
    .core = {
    .base_pin = base_pin,
    .dma_channel = dma_ch,
    .pio_sm = pio_sm
    },
    .pattern = 3,
    };

    const struct audio_format *output_format;
    output_format = audio_pwm_setup(pAudioFormat, -1, &config);
    bool status = audio_pwm_default_connect(pBufferPool, false);
    audio_pwm_set_enabled(true);

    Everything else is the same as for I2S.

    At present, overclocking the Pico causes the PWM frequencies to be messed up, so for now the recommended configuration is 8-note polyphony, 24000 sample rate and no overclocking.

    The build uses GPIO 20 for PWM.

    Closing Thoughts

    I’ve uploaded a prototype PWM UF2 file to GitHub too now in case anyone wants to give that a go too: https://github.com/diyelectromusic/picodexed

    Hopefully there is enough information in the above to get something up and running.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/19/raspberry-pi-pico-synth_dexed-part-6/

    #dx7 #midi #picodexed #pwm #raspberryPiPico #usbMidi

  25. One last thing I definitely wanted for my PicoDexed was the option for PWM output. This post is by way of a short coda detailing how to do PWM on a Raspberry Pi Pico.

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
    • Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
    • Part 5 details different options for building hardware to run PicoDexed.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    PWM Output Circuit

    The official “Hardware Design with the RP2040” guide includes a PWM circuit as follows (see section 3.4.1).

    Several others have used a slightly simplified version of this circuit, essentially omitting the buffer stage (U3) and perhaps only implementing a single channel. See for example – Raspberry Pi Pico PWM Audio Output Circuit (Jeremy S. Cook) and “pico-pwm-audio” (Robin Grosset).

    An alternative is this circuit used by Tod Kurt for his Circuitpython-tricks“The output circuitry to get line-out levels is a simple 10:1 voltage-divider and a 1uF capacitor to recenter the signal around 0 volts”:

    I’m going with Jeremy S. Cook and Robin Grosset and using the following (diagram from here):

    This is my solderless breadboard version of the above:

    Raspberry Pi Pico Audio PWM

    There appears to be two ways of getting audio style PWM signals out of a Pico:

    • Use the PWM peripherals.
    • Use the PIO.

    It would appear that Raspberry Pi’s pico_audio library in the pico-extras repository uses PIO. I haven’t found a clear explanation as to why the built-in PWM peripherals haven’t been used.

    Here are some other resources that show alternative descriptions of PWM for audion on a Pico:

    As I’m using the pico_audio library for I2S audio, I’m going to use the default PWM pico_audio library too.

    To initialise the pico_audio PWM library requires the following:

    const audio_pwm_channel_config_t config =
    {
    .core = {
    .base_pin = base_pin,
    .dma_channel = dma_ch,
    .pio_sm = pio_sm
    },
    .pattern = 3,
    };

    const struct audio_format *output_format;
    output_format = audio_pwm_setup(pAudioFormat, -1, &config);
    bool status = audio_pwm_default_connect(pBufferPool, false);
    audio_pwm_set_enabled(true);

    Everything else is the same as for I2S.

    At present, overclocking the Pico causes the PWM frequencies to be messed up, so for now the recommended configuration is 8-note polyphony, 24000 sample rate and no overclocking.

    The build uses GPIO 20 for PWM.

    Closing Thoughts

    I’ve uploaded a prototype PWM UF2 file to GitHub too now in case anyone wants to give that a go too: https://github.com/diyelectromusic/picodexed

    Hopefully there is enough information in the above to get something up and running.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/19/raspberry-pi-pico-synth_dexed-part-6/

    #dx7 #midi #picodexed #pwm #raspberryPiPico #usbMidi

  26. Ok, ignore my last comment about Part 4 being my last post on this topic. I thought I ought to pull together all the hardware notes on how to build one as I haven’t really written that down anywhere.

    So this shows the connections required between the Pico, I2S DAC and MIDI IN.

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
    • Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
    • Part 6 includes details of how to use PWM output.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    The Circuit

    The easiest way to get a PicoDexed up and running for me, is to use one of my MIDI Proto PCBs and the Pimoroni I2S Audio Pack. This does not allow for any debug output.

    Note the jumpers are set to use UART 1 on GP4 and GP5.

    If only USB-MIDI is required, then the Audio Pack can be plugged directly onto the back of the Pico, in which case simply plugging into the Pico via USB to a computer and connected up the audio out will work.

    It is also possible to use any 3V3 compatible MIDI module. The following shows the use of a Pimoroni “dual expander” with a MIDI module connected to GND, 3V3_OUT and GP5 (RX1).

    This has the advantage that all other GPIO pins are available, so it is relatively straight forward to include a debug link via GP0/GP1 for UART 0.

    A Solderless Breadboard Circuit

    Of course it is also possible to build everything on a solderless breadboard.

    The diagram below shows how a cheap GY-PCM5102 module can be used as the I2S DAC and a serial MIDI interface (optional) can be built using my standard 3V3 MIDI IN circuit.

    This also allows for some debug output using UART 0 (GP0/GP1).

    Things to note:

    • The PCM5102 board will have to have solder jumpers on the rear set as follows: 1=L, 2=L, 3=H, 4=L. Sometimes these come preconfigured with solder bridges, sometimes with zero-ohm SMT resistors, and sometimes with no connection made at all. More details here.
    • I always get pins 4 and 5 mixed up on MIDI DIN sockets. Here is my MIDI Connections Cheat Sheet which may help.

    Pico GPIO Usage

    The following GPIO pins are in use or allocated:

    GP0Debug UART TX (unused at present)GP1Debug UART RXGP4MIDI TX (unused at present)GP5MIDI RXGP9I2S Data (DATA, DIN)GP10I2S Bit Clock (BCK)GP11I2S “Left/Right” Clock (LCK, LRCK, LR_CLOCK)GP20Optional: PWM outputGP22Optional: Mute pin for the Pimoroni Audio Pack (not used)VSYS5V power to DAC (if required)3V3_OUT3V3 power to MIDI IN (if required)GND

    PWM Audio Output

    It is possible to use PWM audio output on the Pico. Full details can be found here: Raspberry Pi Pico Synth_Dexed? – Part 6.

    But this is a lot more limited than I2S and the quality is a lot poorer too. As a DAC can be obtained quite cheaply, the use of an I2S DAC is strongly recommended.

    Closing Thoughts

    I’ve uploaded a prototype UF2 file to GitHub in case anyone wants to give it a go: everything can be found here: https://github.com/diyelectromusic/picodexed

    Hopefully there is enough information in the above to get something up and running.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/18/raspberry-pi-pico-synth_dexed-part-5/

    #dx7 #midi #pcm5102 #picodexed #raspberryPiPico #usbMidi

  27. Ok, ignore my last comment about Part 4 being my last post on this topic. I thought I ought to pull together all the hardware notes on how to build one as I haven’t really written that down anywhere.

    So this shows the connections required between the Pico, I2S DAC and MIDI IN.

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
    • Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
    • Part 6 includes details of how to use PWM output.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    The Circuit

    The easiest way to get a PicoDexed up and running for me, is to use one of my MIDI Proto PCBs and the Pimoroni I2S Audio Pack. This does not allow for any debug output.

    Note the jumpers are set to use UART 1 on GP4 and GP5.

    If only USB-MIDI is required, then the Audio Pack can be plugged directly onto the back of the Pico, in which case simply plugging into the Pico via USB to a computer and connected up the audio out will work.

    It is also possible to use any 3V3 compatible MIDI module. The following shows the use of a Pimoroni “dual expander” with a MIDI module connected to GND, 3V3_OUT and GP5 (RX1).

    This has the advantage that all other GPIO pins are available, so it is relatively straight forward to include a debug link via GP0/GP1 for UART 0.

    A Solderless Breadboard Circuit

    Of course it is also possible to build everything on a solderless breadboard.

    The diagram below shows how a cheap GY-PCM5102 module can be used as the I2S DAC and a serial MIDI interface (optional) can be built using my standard 3V3 MIDI IN circuit.

    This also allows for some debug output using UART 0 (GP0/GP1).

    Things to note:

    • The PCM5102 board will have to have solder jumpers on the rear set as follows: 1=L, 2=L, 3=H, 4=L. Sometimes these come preconfigured with solder bridges, sometimes with zero-ohm SMT resistors, and sometimes with no connection made at all. More details here.
    • I always get pins 4 and 5 mixed up on MIDI DIN sockets. Here is my MIDI Connections Cheat Sheet which may help.

    Pico GPIO Usage

    The following GPIO pins are in use or allocated:

    GP0Debug UART TX (unused at present)GP1Debug UART RXGP4MIDI TX (unused at present)GP5MIDI RXGP9I2S Data (DATA, DIN)GP10I2S Bit Clock (BCK)GP11I2S “Left/Right” Clock (LCK, LRCK, LR_CLOCK)GP20Optional: PWM outputGP22Optional: Mute pin for the Pimoroni Audio Pack (not used)VSYS5V power to DAC (if required)3V3_OUT3V3 power to MIDI IN (if required)GND

    PWM Audio Output

    It is possible to use PWM audio output on the Pico. Full details can be found here: Raspberry Pi Pico Synth_Dexed? – Part 6.

    But this is a lot more limited than I2S and the quality is a lot poorer too. As a DAC can be obtained quite cheaply, the use of an I2S DAC is strongly recommended.

    Closing Thoughts

    I’ve uploaded a prototype UF2 file to GitHub in case anyone wants to give it a go: everything can be found here: https://github.com/diyelectromusic/picodexed

    Hopefully there is enough information in the above to get something up and running.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/18/raspberry-pi-pico-synth_dexed-part-5/

    #dx7 #midi #pcm5102 #picodexed #raspberryPiPico #usbMidi

  28. Ok, ignore my last comment about Part 4 being my last post on this topic. I thought I ought to pull together all the hardware notes on how to build one as I haven’t really written that down anywhere.

    So this shows the connections required between the Pico, I2S DAC and MIDI IN.

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
    • Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
    • Part 6 includes details of how to use PWM output.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    The Circuit

    The easiest way to get a PicoDexed up and running for me, is to use one of my MIDI Proto PCBs and the Pimoroni I2S Audio Pack. This does not allow for any debug output.

    Note the jumpers are set to use UART 1 on GP4 and GP5.

    If only USB-MIDI is required, then the Audio Pack can be plugged directly onto the back of the Pico, in which case simply plugging into the Pico via USB to a computer and connected up the audio out will work.

    It is also possible to use any 3V3 compatible MIDI module. The following shows the use of a Pimoroni “dual expander” with a MIDI module connected to GND, 3V3_OUT and GP5 (RX1).

    This has the advantage that all other GPIO pins are available, so it is relatively straight forward to include a debug link via GP0/GP1 for UART 0.

    A Solderless Breadboard Circuit

    Of course it is also possible to build everything on a solderless breadboard.

    The diagram below shows how a cheap GY-PCM5102 module can be used as the I2S DAC and a serial MIDI interface (optional) can be built using my standard 3V3 MIDI IN circuit.

    This also allows for some debug output using UART 0 (GP0/GP1).

    Things to note:

    • The PCM5102 board will have to have solder jumpers on the rear set as follows: 1=L, 2=L, 3=H, 4=L. Sometimes these come preconfigured with solder bridges, sometimes with zero-ohm SMT resistors, and sometimes with no connection made at all. More details here.
    • I always get pins 4 and 5 mixed up on MIDI DIN sockets. Here is my MIDI Connections Cheat Sheet which may help.

    Pico GPIO Usage

    The following GPIO pins are in use or allocated:

    GP0Debug UART TX (unused at present)GP1Debug UART RXGP4MIDI TX (unused at present)GP5MIDI RXGP9I2S Data (DATA, DIN)GP10I2S Bit Clock (BCK)GP11I2S “Left/Right” Clock (LCK, LRCK, LR_CLOCK)GP20Optional: PWM outputGP22Optional: Mute pin for the Pimoroni Audio Pack (not used)VSYS5V power to DAC (if required)3V3_OUT3V3 power to MIDI IN (if required)GND

    PWM Audio Output

    It is possible to use PWM audio output on the Pico. Full details can be found here: Raspberry Pi Pico Synth_Dexed? – Part 6.

    But this is a lot more limited than I2S and the quality is a lot poorer too. As a DAC can be obtained quite cheaply, the use of an I2S DAC is strongly recommended.

    Closing Thoughts

    I’ve uploaded a prototype UF2 file to GitHub in case anyone wants to give it a go: everything can be found here: https://github.com/diyelectromusic/picodexed

    Hopefully there is enough information in the above to get something up and running.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/18/raspberry-pi-pico-synth_dexed-part-5/

    #dx7 #midi #pcm5102 #picodexed #raspberryPiPico #usbMidi

  29. I was going to leave things at Part 3 blog-wise, and just get on with filling in the gaps in code now, but I’ve come back to add a few more notes. But this is likely to be the final part now.

    Recall so far, I have:

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.

    This is building on the last part and includes notes on how I’ve implemented the following:

    • Fuller MIDI support, including control change, program change and pitch bend messages.
    • Voice and voice banks, selectable over MIDI.
    • MIDI SysEx messages for voice parameters.
    • USB MIDI device support.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    MIDI Support

    I’m not going to walk through all the details of how I’ve added MIDI but suffice to say that once again the implementation owes a lot to MiniDexed and the Arduino MIDI Library.

    At the time of writing the following are all supported as they were already supported in Synth_Dexed, so I just needed to glue the bits together.

    Channel Voice Messages (only channel 1 at present)

    0x80MIDI Note Offnote=0..127, vel=0..1270x90MIDI Note Onnote=0..127, vel=0..1270xA0Channel Aftertouchnote=0..127, val=0..1270xB0Control ChangeSee below0xC0Program Change0..31 (If used with BANKSEL)
    0..127 (if used independently)0xE0Pitch Bend0..16383 (in LSB/MSB 2×7-bit format)

    Channel Control Change Messages

    0Bank Select (MSB)01Modulation0..1272Breath Control0..1274Foot Control0..1277Channel Volume0..12732Bank Select (LSB)0..864Sustain<=63 Off, 64=> On65Portamento<=63 Off, 64=> On95Master Tune0..127 *120All Sound Off0123All Notes Off0126Mono Mode0 **127Poly Mode0

    * There is a bug with the master tuning. It ought to accept -99 to 99 I believe, but only 0..99 will actually register and there is no way to send -99 via MIDI at the moment. I need to read up on what is going on here and what it ought to do!

    ** The Mono Mode parameter has the option for specifying how many of the playable voices can be dedicated to mono mode (at least I think that is what it is saying). I only support a value of 0 which I believe is meant to mean “all available voices”.

    System Messages

    0xF0..0xF7Start/End System ExclusiveSee below0xFEActive SensingFiltered out0xFnOther system messagesIgnored

    System Exclusive Messages

    Any valid Yamaha (DX) system exclusive messages are passed straight into Synth_Dexed. A Yamaha (DX) message has the following format (see the “DX7IIFD/D Supplemental Booklet: Advanced MIDI Data and Charts”):

    F0 - start SysEx message
    43 - Yamaha manufacturer ID
    sd - s=substatus (command class:0,1,2); d=device ID (0..F)
    .. data ..
    F7 - end SysEx message

    The device ID can be set using the UI on a real DX7 to a value between 1 and 16, which becomes a value between 0 and 15 (0..F) as part of the SysEx message (see “DX7IIFD/D Supplemental Booklet: Advanced MIDI Applications, Section 8”). It is a Systems Exclusive value analogous to the MIDI channel for regular channel messages.

    There are a range of Sys Ex parameter settings that have been passed onto Synth_Dexed as follows:

    Mono Mode0..1Pitch Bend Range0..12Pitch Bend Step0..12Portamento Mode0..1Portamento Glissando0..1Portamento Time0..99Mod Wheel Range0..99Mod Wheel Target0..7Foot Control Range0..99Foot Control Target0..7Breath Control Range0..99Breath Control Target0..7Aftertouch Range0..99Aftertouch Target0..7Voice Dump Load<156 bytes of voice data>Voice Parameter SetParameter=0..155; Data=0..99

    At this stage, all of the MIDI support is on a “it’s probably something like this” basis, so it will evolve as I find out what it is meant to be doing!

    Voice and Bank Loading

    Banks of voices are programmed directly into the code. There is a python script from Synth_Dexed that will take a .syx format voice bank and generate a block of C code. I’ve included a script to download the main 8 banks of standard DX voices and run the script:

    #!/bin/sh

    # Get voices from
    # https://yamahablackboxes.com/collection/yamaha-dx7-synthesizer/patches/

    mkdir -p voices

    DIR="https://yamahablackboxes.com/patches/dx7/factory"

    wget -c "${DIR}"/rom1a.syx -O voices/rom1a.syx
    wget -c "${DIR}"/rom1b.syx -O voices/rom1b.syx
    wget -c "${DIR}"/rom2a.syx -O voices/rom2a.syx
    wget -c "${DIR}"/rom2b.syx -O voices/rom2b.syx
    wget -c "${DIR}"/rom3a.syx -O voices/rom3a.syx
    wget -c "${DIR}"/rom3b.syx -O voices/rom3b.syx
    wget -c "${DIR}"/rom4a.syx -O voices/rom4a.syx
    wget -c "${DIR}"/rom4b.syx -O voices/rom4b.syx

    ./synth_dexed/Synth_Dexed/tools/sysex2c.py voices/* > src/voices.h

    This only needs to be run once to create the src/voices.h file which is then included in the build.

    Voices have the following format:

    uint8_t progmem_bank[8][32][128] PROGMEM =
    {
    { // Bank 1
    {<--128 bytes of packed voice data-->} // Voice 1
    ...
    {<--128 bytes of packed voice data-->} // Voice 32
    }
    { // Bank 2
    ...
    }
    ...
    { // Bank 8
    {<--128 bytes of packed voice data-->} // Voice 1
    ...
    {<--128 bytes of packed voice data-->} // Voice 32
    }
    }

    The system assumes 8 banks of 32 voices each, in the “packed” SYX header format, meaning each voice consists of 128 bytes.

    MIDI Bank and Voice Selection

    As there are only 8 banks, only BANKSEL (LSB) values 0..7 are valid. Program Change will work in two ways however:

    • 0..31 will select voices 1 to 32 in the current bank.
    • 31..127 will select voices from the following three adjacent banks.

    To select any voice in all 8 banks thus requires the following sequence:

    BANKSEL MSB = 0
    BANKSEL LSB = 0..7
    PROG CHANGE = 0..31

    But if bank selection is skipped, then Program Change messages can still be used to select one of the first 128 voices across four consecutive banks.

    USB MIDI

    The Raspberry Pi Pico SDK uses the TinyUSB protocol stack to implement USB device or host modes and there is an additional option to implement a second USB host port using the Pico’s PIO.

    However, USB MIDI appears to only be supported for USB devices at the time of writing, so I’m just using the built-in USB port as a USB device, based on the code provided as part of the TinyUSB examples (more details of how to get basic USB MIDI running here).

    TinyUSB MIDI supports two interfaces for reading data, and this wasn’t immediately obvious from the example as that is only sending data and ignores anything coming in.

    • USB MIDI Stream mode: this will fill a provided buffer with MIDI data received over USB.
    • USB MIDI Packet mode: this will return each 4-byte USB packet individually.

    From what I can see of the USB MIDI Spec, all MIDI messages are turned into 4-byte packets for transferring over USB. All normal MIDI messages will consist of 1, 2 or 3 byte messages, and so will fit in a packet each – any unused bytes are padded with 0.

    However SysEx messages are a little more complicated and have to be split across multiple packets.

    This is the format for a USB MIDI Event Packet (see the “Universal Serial Bus Device Class Definition for MIDI Devices”, Release 1.0):

    The code index number is an indication of the contents of each packet. For channel messages, this is basically a repeat of the MIDI command, so a MIDI Note On message might look something like the following:

    09 92 3C 64
    Cable 0
    Code Index Number 9
    MIDI Cmd 0x90 (Note On)
    MIDI Channel 3 (0x0=1; 0x1=2; 0x2=3; ... 0xF=16)
    Note 0x3C (60 = C4)
    Velocity 0x64 (100)

    But things get a little more complex with System Common or System Exclusive messages which have their own set of codes, depending on the chunking of the packets required.

    The critical ones for SysEx are CIN=4,5,6,7 which correspond to SysEx start and then various versions of continuation or end packets. So a larger SysEx message might look something like the following

    04 F0 43 10 -- SysEx Start or Continuation
    04 34 44 4D -- SysEx Start or Continuation
    06 3E F7 00 -- SysEx End after two bytes

    Complete message: F0 43 10 34 44 4D 3E F7

    So, if I opt to use the packet interface to TinyUSB MIDI then all this has to be sorted out in user code myself. However, the streaming interface will take care of all this for me and just return a buffer full of “traditional” MIDI messages.

    Note that there is no concept of Running Status in USB MIDI. Even the oldest USB standard protocol speeds are an order of magnitude, or more, higher than serial MIDI so it isn’t necessary. Every MIDI message will either be a complete 1,2,3 byte message in a single USB packet, or a SysEx multi-packet message as described above.

    The basic structure of the USB MIDI handler is as follows:

    Init:
    Initialise TinyUSB MIDI stack

    Process:
    Run the TinyUSB MIDI task
    IF TinyUSB says MIDI data available:
    Call the stream API to fill our RX buffer
    WHILE data in the RX buffer:
    Call the MIDIParser which reads from the RX buffer
    IF MIDI messages found:
    Call the MIDI Message Handler

    Read:
    Grab the next byte from the RX buffer

    I’ve actually split this over two files: usbmidi.cpp is the companion to serialmidi.cpp and provides the class that inherits from MIDIDevice (which provides the parser and message handler); usbtask.c provides the interface into the TinyUSB C driver code.

    I haven’t done anything special with a USB manufacturer/vendor and device ID yet – so at some point I should see what TinyUSB is using by default and find something unique to PicoDexed (assuming I take it forward in any useful way).

    Closing Thoughts

    I have a fairly complete implementation now, which is quite nice. I do need to find some way to properly exercise the voice loading over SysEx and it would be good to get some idea of the performance when I throw a MIDI file at it over USB!

    I’ve tested some of the parameter changes using the PC version of Dexed. When configured correctly, this can be used to send voice parameter changes to PicoDexed, but I haven’t found a way to download the entire voice as yet.

    It’s a shame I can’t just plug in a USB MIDI controller and play it now, but I’ll work on some kind of interface board that should allow me to do it. It will need to be independently powered to act as a USB host anyway.

    This is probably going to be my last blog post on PicoDexed for now, but I plan to keep tinkering away at the GitHub repository to see how things go. There are still a couple of limitations, the main one being that everything has to be hard-coded in at present. It would be nice to be able to have some kind of system configuration facility for the MIDI channel if nothing else.

    At some point it would also be nice to have a build on the GitHub so others can try it too. And I still need to decide how best to manage the changes I needed to make to Synth_Dexed.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/16/raspberry-pi-pico-synth_dexed-part-4/

    #dx7 #midi #picodexed #raspberryPiPico #usbMidi

  30. I was going to leave things at Part 3 blog-wise, and just get on with filling in the gaps in code now, but I’ve come back to add a few more notes. But this is likely to be the final part now.

    Recall so far, I have:

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.

    This is building on the last part and includes notes on how I’ve implemented the following:

    • Fuller MIDI support, including control change, program change and pitch bend messages.
    • Voice and voice banks, selectable over MIDI.
    • MIDI SysEx messages for voice parameters.
    • USB MIDI device support.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    MIDI Support

    I’m not going to walk through all the details of how I’ve added MIDI but suffice to say that once again the implementation owes a lot to MiniDexed and the Arduino MIDI Library.

    At the time of writing the following are all supported as they were already supported in Synth_Dexed, so I just needed to glue the bits together.

    Channel Voice Messages (only channel 1 at present)

    0x80MIDI Note Offnote=0..127, vel=0..1270x90MIDI Note Onnote=0..127, vel=0..1270xA0Channel Aftertouchnote=0..127, val=0..1270xB0Control ChangeSee below0xC0Program Change0..31 (If used with BANKSEL)
    0..127 (if used independently)0xE0Pitch Bend0..16383 (in LSB/MSB 2×7-bit format)

    Channel Control Change Messages

    0Bank Select (MSB)01Modulation0..1272Breath Control0..1274Foot Control0..1277Channel Volume0..12732Bank Select (LSB)0..864Sustain<=63 Off, 64=> On65Portamento<=63 Off, 64=> On95Master Tune0..127 *120All Sound Off0123All Notes Off0126Mono Mode0 **127Poly Mode0

    * There is a bug with the master tuning. It ought to accept -99 to 99 I believe, but only 0..99 will actually register and there is no way to send -99 via MIDI at the moment. I need to read up on what is going on here and what it ought to do!

    ** The Mono Mode parameter has the option for specifying how many of the playable voices can be dedicated to mono mode (at least I think that is what it is saying). I only support a value of 0 which I believe is meant to mean “all available voices”.

    System Messages

    0xF0..0xF7Start/End System ExclusiveSee below0xFEActive SensingFiltered out0xFnOther system messagesIgnored

    System Exclusive Messages

    Any valid Yamaha (DX) system exclusive messages are passed straight into Synth_Dexed. A Yamaha (DX) message has the following format (see the “DX7IIFD/D Supplemental Booklet: Advanced MIDI Data and Charts”):

    F0 - start SysEx message
    43 - Yamaha manufacturer ID
    sd - s=substatus (command class:0,1,2); d=device ID (0..F)
    .. data ..
    F7 - end SysEx message

    The device ID can be set using the UI on a real DX7 to a value between 1 and 16, which becomes a value between 0 and 15 (0..F) as part of the SysEx message (see “DX7IIFD/D Supplemental Booklet: Advanced MIDI Applications, Section 8”). It is a Systems Exclusive value analogous to the MIDI channel for regular channel messages.

    There are a range of Sys Ex parameter settings that have been passed onto Synth_Dexed as follows:

    Mono Mode0..1Pitch Bend Range0..12Pitch Bend Step0..12Portamento Mode0..1Portamento Glissando0..1Portamento Time0..99Mod Wheel Range0..99Mod Wheel Target0..7Foot Control Range0..99Foot Control Target0..7Breath Control Range0..99Breath Control Target0..7Aftertouch Range0..99Aftertouch Target0..7Voice Dump Load<156 bytes of voice data>Voice Parameter SetParameter=0..155; Data=0..99

    At this stage, all of the MIDI support is on a “it’s probably something like this” basis, so it will evolve as I find out what it is meant to be doing!

    Voice and Bank Loading

    Banks of voices are programmed directly into the code. There is a python script from Synth_Dexed that will take a .syx format voice bank and generate a block of C code. I’ve included a script to download the main 8 banks of standard DX voices and run the script:

    #!/bin/sh

    # Get voices from
    # https://yamahablackboxes.com/collection/yamaha-dx7-synthesizer/patches/

    mkdir -p voices

    DIR="https://yamahablackboxes.com/patches/dx7/factory"

    wget -c "${DIR}"/rom1a.syx -O voices/rom1a.syx
    wget -c "${DIR}"/rom1b.syx -O voices/rom1b.syx
    wget -c "${DIR}"/rom2a.syx -O voices/rom2a.syx
    wget -c "${DIR}"/rom2b.syx -O voices/rom2b.syx
    wget -c "${DIR}"/rom3a.syx -O voices/rom3a.syx
    wget -c "${DIR}"/rom3b.syx -O voices/rom3b.syx
    wget -c "${DIR}"/rom4a.syx -O voices/rom4a.syx
    wget -c "${DIR}"/rom4b.syx -O voices/rom4b.syx

    ./synth_dexed/Synth_Dexed/tools/sysex2c.py voices/* > src/voices.h

    This only needs to be run once to create the src/voices.h file which is then included in the build.

    Voices have the following format:

    uint8_t progmem_bank[8][32][128] PROGMEM =
    {
    { // Bank 1
    {<--128 bytes of packed voice data-->} // Voice 1
    ...
    {<--128 bytes of packed voice data-->} // Voice 32
    }
    { // Bank 2
    ...
    }
    ...
    { // Bank 8
    {<--128 bytes of packed voice data-->} // Voice 1
    ...
    {<--128 bytes of packed voice data-->} // Voice 32
    }
    }

    The system assumes 8 banks of 32 voices each, in the “packed” SYX header format, meaning each voice consists of 128 bytes.

    MIDI Bank and Voice Selection

    As there are only 8 banks, only BANKSEL (LSB) values 0..7 are valid. Program Change will work in two ways however:

    • 0..31 will select voices 1 to 32 in the current bank.
    • 31..127 will select voices from the following three adjacent banks.

    To select any voice in all 8 banks thus requires the following sequence:

    BANKSEL MSB = 0
    BANKSEL LSB = 0..7
    PROG CHANGE = 0..31

    But if bank selection is skipped, then Program Change messages can still be used to select one of the first 128 voices across four consecutive banks.

    USB MIDI

    The Raspberry Pi Pico SDK uses the TinyUSB protocol stack to implement USB device or host modes and there is an additional option to implement a second USB host port using the Pico’s PIO.

    However, USB MIDI appears to only be supported for USB devices at the time of writing, so I’m just using the built-in USB port as a USB device, based on the code provided as part of the TinyUSB examples (more details of how to get basic USB MIDI running here).

    TinyUSB MIDI supports two interfaces for reading data, and this wasn’t immediately obvious from the example as that is only sending data and ignores anything coming in.

    • USB MIDI Stream mode: this will fill a provided buffer with MIDI data received over USB.
    • USB MIDI Packet mode: this will return each 4-byte USB packet individually.

    From what I can see of the USB MIDI Spec, all MIDI messages are turned into 4-byte packets for transferring over USB. All normal MIDI messages will consist of 1, 2 or 3 byte messages, and so will fit in a packet each – any unused bytes are padded with 0.

    However SysEx messages are a little more complicated and have to be split across multiple packets.

    This is the format for a USB MIDI Event Packet (see the “Universal Serial Bus Device Class Definition for MIDI Devices”, Release 1.0):

    The code index number is an indication of the contents of each packet. For channel messages, this is basically a repeat of the MIDI command, so a MIDI Note On message might look something like the following:

    09 92 3C 64
    Cable 0
    Code Index Number 9
    MIDI Cmd 0x90 (Note On)
    MIDI Channel 3 (0x0=1; 0x1=2; 0x2=3; ... 0xF=16)
    Note 0x3C (60 = C4)
    Velocity 0x64 (100)

    But things get a little more complex with System Common or System Exclusive messages which have their own set of codes, depending on the chunking of the packets required.

    The critical ones for SysEx are CIN=4,5,6,7 which correspond to SysEx start and then various versions of continuation or end packets. So a larger SysEx message might look something like the following

    04 F0 43 10 -- SysEx Start or Continuation
    04 34 44 4D -- SysEx Start or Continuation
    06 3E F7 00 -- SysEx End after two bytes

    Complete message: F0 43 10 34 44 4D 3E F7

    So, if I opt to use the packet interface to TinyUSB MIDI then all this has to be sorted out in user code myself. However, the streaming interface will take care of all this for me and just return a buffer full of “traditional” MIDI messages.

    Note that there is no concept of Running Status in USB MIDI. Even the oldest USB standard protocol speeds are an order of magnitude, or more, higher than serial MIDI so it isn’t necessary. Every MIDI message will either be a complete 1,2,3 byte message in a single USB packet, or a SysEx multi-packet message as described above.

    The basic structure of the USB MIDI handler is as follows:

    Init:
    Initialise TinyUSB MIDI stack

    Process:
    Run the TinyUSB MIDI task
    IF TinyUSB says MIDI data available:
    Call the stream API to fill our RX buffer
    WHILE data in the RX buffer:
    Call the MIDIParser which reads from the RX buffer
    IF MIDI messages found:
    Call the MIDI Message Handler

    Read:
    Grab the next byte from the RX buffer

    I’ve actually split this over two files: usbmidi.cpp is the companion to serialmidi.cpp and provides the class that inherits from MIDIDevice (which provides the parser and message handler); usbtask.c provides the interface into the TinyUSB C driver code.

    I haven’t done anything special with a USB manufacturer/vendor and device ID yet – so at some point I should see what TinyUSB is using by default and find something unique to PicoDexed (assuming I take it forward in any useful way).

    Closing Thoughts

    I have a fairly complete implementation now, which is quite nice. I do need to find some way to properly exercise the voice loading over SysEx and it would be good to get some idea of the performance when I throw a MIDI file at it over USB!

    I’ve tested some of the parameter changes using the PC version of Dexed. When configured correctly, this can be used to send voice parameter changes to PicoDexed, but I haven’t found a way to download the entire voice as yet.

    It’s a shame I can’t just plug in a USB MIDI controller and play it now, but I’ll work on some kind of interface board that should allow me to do it. It will need to be independently powered to act as a USB host anyway.

    This is probably going to be my last blog post on PicoDexed for now, but I plan to keep tinkering away at the GitHub repository to see how things go. There are still a couple of limitations, the main one being that everything has to be hard-coded in at present. It would be nice to be able to have some kind of system configuration facility for the MIDI channel if nothing else.

    At some point it would also be nice to have a build on the GitHub so others can try it too. And I still need to decide how best to manage the changes I needed to make to Synth_Dexed.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/16/raspberry-pi-pico-synth_dexed-part-4/

    #dx7 #midi #picodexed #raspberryPiPico #usbMidi

  31. I was going to leave things at Part 3 blog-wise, and just get on with filling in the gaps in code now, but I’ve come back to add a few more notes. But this is likely to be the final part now.

    Recall so far, I have:

    • Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
    • Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.

    This is building on the last part and includes notes on how I’ve implemented the following:

    • Fuller MIDI support, including control change, program change and pitch bend messages.
    • Voice and voice banks, selectable over MIDI.
    • MIDI SysEx messages for voice parameters.
    • USB MIDI device support.

    The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    MIDI Support

    I’m not going to walk through all the details of how I’ve added MIDI but suffice to say that once again the implementation owes a lot to MiniDexed and the Arduino MIDI Library.

    At the time of writing the following are all supported as they were already supported in Synth_Dexed, so I just needed to glue the bits together.

    Channel Voice Messages (only channel 1 at present)

    0x80MIDI Note Offnote=0..127, vel=0..1270x90MIDI Note Onnote=0..127, vel=0..1270xA0Channel Aftertouchnote=0..127, val=0..1270xB0Control ChangeSee below0xC0Program Change0..31 (If used with BANKSEL)
    0..127 (if used independently)0xE0Pitch Bend0..16383 (in LSB/MSB 2×7-bit format)

    Channel Control Change Messages

    0Bank Select (MSB)01Modulation0..1272Breath Control0..1274Foot Control0..1277Channel Volume0..12732Bank Select (LSB)0..864Sustain<=63 Off, 64=> On65Portamento<=63 Off, 64=> On95Master Tune0..127 *120All Sound Off0123All Notes Off0126Mono Mode0 **127Poly Mode0

    * There is a bug with the master tuning. It ought to accept -99 to 99 I believe, but only 0..99 will actually register and there is no way to send -99 via MIDI at the moment. I need to read up on what is going on here and what it ought to do!

    ** The Mono Mode parameter has the option for specifying how many of the playable voices can be dedicated to mono mode (at least I think that is what it is saying). I only support a value of 0 which I believe is meant to mean “all available voices”.

    System Messages

    0xF0..0xF7Start/End System ExclusiveSee below0xFEActive SensingFiltered out0xFnOther system messagesIgnored

    System Exclusive Messages

    Any valid Yamaha (DX) system exclusive messages are passed straight into Synth_Dexed. A Yamaha (DX) message has the following format (see the “DX7IIFD/D Supplemental Booklet: Advanced MIDI Data and Charts”):

    F0 - start SysEx message
    43 - Yamaha manufacturer ID
    sd - s=substatus (command class:0,1,2); d=device ID (0..F)
    .. data ..
    F7 - end SysEx message

    The device ID can be set using the UI on a real DX7 to a value between 1 and 16, which becomes a value between 0 and 15 (0..F) as part of the SysEx message (see “DX7IIFD/D Supplemental Booklet: Advanced MIDI Applications, Section 8”). It is a Systems Exclusive value analogous to the MIDI channel for regular channel messages.

    There are a range of Sys Ex parameter settings that have been passed onto Synth_Dexed as follows:

    Mono Mode0..1Pitch Bend Range0..12Pitch Bend Step0..12Portamento Mode0..1Portamento Glissando0..1Portamento Time0..99Mod Wheel Range0..99Mod Wheel Target0..7Foot Control Range0..99Foot Control Target0..7Breath Control Range0..99Breath Control Target0..7Aftertouch Range0..99Aftertouch Target0..7Voice Dump Load<156 bytes of voice data>Voice Parameter SetParameter=0..155; Data=0..99

    At this stage, all of the MIDI support is on a “it’s probably something like this” basis, so it will evolve as I find out what it is meant to be doing!

    Voice and Bank Loading

    Banks of voices are programmed directly into the code. There is a python script from Synth_Dexed that will take a .syx format voice bank and generate a block of C code. I’ve included a script to download the main 8 banks of standard DX voices and run the script:

    #!/bin/sh

    # Get voices from
    # https://yamahablackboxes.com/collection/yamaha-dx7-synthesizer/patches/

    mkdir -p voices

    DIR="https://yamahablackboxes.com/patches/dx7/factory"

    wget -c "${DIR}"/rom1a.syx -O voices/rom1a.syx
    wget -c "${DIR}"/rom1b.syx -O voices/rom1b.syx
    wget -c "${DIR}"/rom2a.syx -O voices/rom2a.syx
    wget -c "${DIR}"/rom2b.syx -O voices/rom2b.syx
    wget -c "${DIR}"/rom3a.syx -O voices/rom3a.syx
    wget -c "${DIR}"/rom3b.syx -O voices/rom3b.syx
    wget -c "${DIR}"/rom4a.syx -O voices/rom4a.syx
    wget -c "${DIR}"/rom4b.syx -O voices/rom4b.syx

    ./synth_dexed/Synth_Dexed/tools/sysex2c.py voices/* > src/voices.h

    This only needs to be run once to create the src/voices.h file which is then included in the build.

    Voices have the following format:

    uint8_t progmem_bank[8][32][128] PROGMEM =
    {
    { // Bank 1
    {<--128 bytes of packed voice data-->} // Voice 1
    ...
    {<--128 bytes of packed voice data-->} // Voice 32
    }
    { // Bank 2
    ...
    }
    ...
    { // Bank 8
    {<--128 bytes of packed voice data-->} // Voice 1
    ...
    {<--128 bytes of packed voice data-->} // Voice 32
    }
    }

    The system assumes 8 banks of 32 voices each, in the “packed” SYX header format, meaning each voice consists of 128 bytes.

    MIDI Bank and Voice Selection

    As there are only 8 banks, only BANKSEL (LSB) values 0..7 are valid. Program Change will work in two ways however:

    • 0..31 will select voices 1 to 32 in the current bank.
    • 31..127 will select voices from the following three adjacent banks.

    To select any voice in all 8 banks thus requires the following sequence:

    BANKSEL MSB = 0
    BANKSEL LSB = 0..7
    PROG CHANGE = 0..31

    But if bank selection is skipped, then Program Change messages can still be used to select one of the first 128 voices across four consecutive banks.

    USB MIDI

    The Raspberry Pi Pico SDK uses the TinyUSB protocol stack to implement USB device or host modes and there is an additional option to implement a second USB host port using the Pico’s PIO.

    However, USB MIDI appears to only be supported for USB devices at the time of writing, so I’m just using the built-in USB port as a USB device, based on the code provided as part of the TinyUSB examples (more details of how to get basic USB MIDI running here).

    TinyUSB MIDI supports two interfaces for reading data, and this wasn’t immediately obvious from the example as that is only sending data and ignores anything coming in.

    • USB MIDI Stream mode: this will fill a provided buffer with MIDI data received over USB.
    • USB MIDI Packet mode: this will return each 4-byte USB packet individually.

    From what I can see of the USB MIDI Spec, all MIDI messages are turned into 4-byte packets for transferring over USB. All normal MIDI messages will consist of 1, 2 or 3 byte messages, and so will fit in a packet each – any unused bytes are padded with 0.

    However SysEx messages are a little more complicated and have to be split across multiple packets.

    This is the format for a USB MIDI Event Packet (see the “Universal Serial Bus Device Class Definition for MIDI Devices”, Release 1.0):

    The code index number is an indication of the contents of each packet. For channel messages, this is basically a repeat of the MIDI command, so a MIDI Note On message might look something like the following:

    09 92 3C 64
    Cable 0
    Code Index Number 9
    MIDI Cmd 0x90 (Note On)
    MIDI Channel 3 (0x0=1; 0x1=2; 0x2=3; ... 0xF=16)
    Note 0x3C (60 = C4)
    Velocity 0x64 (100)

    But things get a little more complex with System Common or System Exclusive messages which have their own set of codes, depending on the chunking of the packets required.

    The critical ones for SysEx are CIN=4,5,6,7 which correspond to SysEx start and then various versions of continuation or end packets. So a larger SysEx message might look something like the following

    04 F0 43 10 -- SysEx Start or Continuation
    04 34 44 4D -- SysEx Start or Continuation
    06 3E F7 00 -- SysEx End after two bytes

    Complete message: F0 43 10 34 44 4D 3E F7

    So, if I opt to use the packet interface to TinyUSB MIDI then all this has to be sorted out in user code myself. However, the streaming interface will take care of all this for me and just return a buffer full of “traditional” MIDI messages.

    Note that there is no concept of Running Status in USB MIDI. Even the oldest USB standard protocol speeds are an order of magnitude, or more, higher than serial MIDI so it isn’t necessary. Every MIDI message will either be a complete 1,2,3 byte message in a single USB packet, or a SysEx multi-packet message as described above.

    The basic structure of the USB MIDI handler is as follows:

    Init:
    Initialise TinyUSB MIDI stack

    Process:
    Run the TinyUSB MIDI task
    IF TinyUSB says MIDI data available:
    Call the stream API to fill our RX buffer
    WHILE data in the RX buffer:
    Call the MIDIParser which reads from the RX buffer
    IF MIDI messages found:
    Call the MIDI Message Handler

    Read:
    Grab the next byte from the RX buffer

    I’ve actually split this over two files: usbmidi.cpp is the companion to serialmidi.cpp and provides the class that inherits from MIDIDevice (which provides the parser and message handler); usbtask.c provides the interface into the TinyUSB C driver code.

    I haven’t done anything special with a USB manufacturer/vendor and device ID yet – so at some point I should see what TinyUSB is using by default and find something unique to PicoDexed (assuming I take it forward in any useful way).

    Closing Thoughts

    I have a fairly complete implementation now, which is quite nice. I do need to find some way to properly exercise the voice loading over SysEx and it would be good to get some idea of the performance when I throw a MIDI file at it over USB!

    I’ve tested some of the parameter changes using the PC version of Dexed. When configured correctly, this can be used to send voice parameter changes to PicoDexed, but I haven’t found a way to download the entire voice as yet.

    It’s a shame I can’t just plug in a USB MIDI controller and play it now, but I’ll work on some kind of interface board that should allow me to do it. It will need to be independently powered to act as a USB host anyway.

    This is probably going to be my last blog post on PicoDexed for now, but I plan to keep tinkering away at the GitHub repository to see how things go. There are still a couple of limitations, the main one being that everything has to be hard-coded in at present. It would be nice to be able to have some kind of system configuration facility for the MIDI channel if nothing else.

    At some point it would also be nice to have a build on the GitHub so others can try it too. And I still need to decide how best to manage the changes I needed to make to Synth_Dexed.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/16/raspberry-pi-pico-synth_dexed-part-4/

    #dx7 #midi #picodexed #raspberryPiPico #usbMidi

  32. The story so far…

    • In Part 1 I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
    • In Part 2 I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.

    This post describes how I’ve set things up for some further development and the decisions I’ve made to get to the point where it can receive MIDI and actually be somewhat playable within the limitations of 10 note polyphony, a 24000 sample rate, and a single voice only on MIDI channel 1!

    Update: By overclocking the Pico to 250MHz I can do 16 note polyphony at 24000 or 8 note polyphony at 48000!

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    Optimised Dexed->getSamples

    I left things in part 2 noting that Dexed itself is essentially a fully integer-implemented synth engine, so why did I need the floating point calculations. I concluded it is all due to the filter that has been added, which is based on the LP filter code from https://obxd.wordpress.com/ which was added in Dexed, but wasn’t in the original “music synthesizer for Android“.

    So I’ve decided not to bother with it. If I feel like it is really missing out, then I have stumbled across the following which looks promising: https://beammyselfintothefuture.wordpress.com/2015/02/16/simple-c-code-for-resonant-lpf-hpf-filters-and-high-low-shelving-eqs/

    So, here is my integer-only version of Dexed->getSamples.

    void Dexed::getSamples(int16_t* buffer, uint16_t n_samples)
    {
    if (refreshVoice)
    {
    for (uint8_t i = 0; i < max_notes; i++)
    {
    if ( voices[i].live )
    voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity, voices[i].porta, &controllers);
    }
    lfo.reset(data + 137);
    refreshVoice = false;
    }

    for (uint16_t i = 0; i < n_samples; ++i)
    {
    buffer[i] = 0;
    }

    for (uint16_t i = 0; i < n_samples; i += _N_)
    {
    AlignedBuf<int32_t, _N_> audiobuf;

    for (uint8_t j = 0; j < _N_; ++j)
    {
    audiobuf.get()[j] = 0;
    }

    int32_t lfovalue = lfo.getsample();
    int32_t lfodelay = lfo.getdelay();

    for (uint8_t note = 0; note < max_notes; note++)
    {
    if (voices[note].live)
    {
    voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers);

    for (uint8_t j = 0; j < _N_; ++j)
    {
    int16_t tmp = audiobuf.get()[j] >> 16;
    buffer[i + j] += tmp;
    audiobuf.get()[j] = 0;
    }
    }
    }
    }
    }

    With this in place, I appear to be able to comfortably cope with 8-note polyphony. At least with my test chords.

    Debug Output

    Now before I go too far, I want a simple way to get some output out of the device. The Pico Getting Started documentation gives an example of how to get some standard output (stdio) working. There are two options for this output (see chapter 4 “Saying “Hello World” in C”):

    • Using the built-in USB serial port.
    • Outputting to the UART serial port.

    To use USB requires building in TinyUSB, but I’m planning on using that later. It also adds quite a lot of overhead apparently, so the default is to output to the serial port via GP0 (TX) and GP1 (RX). All that is required is to find a way to connect this up to a computer or terminal device.

    There are several options: some kind of 3V3 supporting USB<->Serial converter – there are several, based on the CH240 of FTDI devices for example, although not many of the cheap ones are 3V3 compatible (don’t use a 5V board it could damage the Pico!); or using a native Raspberry Pi development environment, then simply using GPIO directly to connect the Pico to the Pi’s UART.

    It is also possible to use the picoprobe firmware running on another Pico I believe, but I haven’t tried that. It wasn’t totally clear to me if that supports the USB to serial link, although it is strongly implied. The official Raspberry Pi Debug Probe definitely does however, but I haven’t got one of those at the moment.

    I initially opted to use another Pico as a serial to USB gateway by running Circuitpython and the following script on boot by saving it as code.py:

    import board
    import busio
    import digitalio

    uart = busio.UART(tx=board.GP0, rx=board.GP1, baudrate=115200, timeout=0.1)

    while True:
    readbytes = uart.read()
    if readbytes != None:
    print (''.join([chr(b) for b in readbytes]))

    Now this just needs connected to the Pico running PicoDexed as follows:

    PicoDexed        Debug Pico
       GP0    <---->    GP1
       GND    <---->    GND

    As this is running Circuitpython it means I also get the CIRCUITPY virtual drive appear and mounted too which isn’t ideal but not really a big issue.

    Then I had a rummage in my Pico drawer looking for a neater solution and found a Waveshare RP2040-One that I’d forgotten I had! This is perfect as it has a USB plug at one end (via a shaped PCB) and GPIO at the other, including pins connected to UART 0.

    I dropped Micropython onto the board this time, with the following script.

    import time
    from machine import UART, Pin

    # Use one of the GPIO as a GND pin for the serial
    gndpin = Pin(11, Pin.OUT)
    gndpin.value(0)

    print ("Initialising UART 0 on pins gnd=11, tx=12, rx=13...")
    uart = UART(0, baudrate=115200, tx=Pin(12), rx=Pin(13))
    print ("Ready")

    while True:
    # Read raw data version
    rxdata = bytes()
    while uart.any() > 0:
    rxdata += uart.read(1)
    time.sleep_ms(10)

    if rxdata:
    print(rxdata.decode('utf-8'))

    To keep the connections simple, I used GPIO 11 as an additional GND pin as there is only one on the board and it isn’t so convenient.

    PicoDexed        RP2040-One
       GP0    <---->    GP13
       GND    <---->    GP11

    To ensure the code can output text just needs something like the following:

    #include <stdio.h>

    void main () {
      stdio_init_all();
      printf("PicoDexed...");
    }

    Then with both devices connected to my virtual Ubuntu Linux machine, I can run minicom (once installed – it isn’t installed by default):

    $ sudo minicom -b 115200 -D /dev/ttyACM0

    Here is the output.

    Welcome to minicom 2.8

    OPTIONS: I18n
    Port /dev/ttyACM0, 13:27:28
    Press CTRL-A Z for help on special keys

    PicoDexed...

    Connecting PIO I2S audio
    Copying mono to mono at 24000 Hz

    Note, to exit minicom use CTRL-A then X.

    Alternatively I could use PuTTY on Windows on the COM port associated with the “debugging” Pico.

    At some point I’ll probably need to set up proper SWD debugging, but this should do for the time being.

    I might also need to switch UARTs if I want to use UART 0 for MIDI, but apparently there are some defines that can be changed in the CMakeLists.txt file:

    target_compile_definitions(picodexed PRIVATE
    PICO_DEFAULT_UART=0
    PICO_DEFAULT_UART_TX_PIN=0
    PICO_DEFAULT_UART_RX_PIN=1
    )

    PicoDexed design

    It is time to start thinking seriously if I can turn this into something interesting or not, so borrowing from some of the design principles encapsulated in MiniDexed, I’ve now got a project that looks as follows:

    • main.cpp -> Basic IO, initialisation and main update loop.
    • picodexed.cpp -> The core synthesizer wrapper with the following key interface:
      • CPicoDexed::Init -> perform all the required synthesizer initialisation.
      • CPicoDexed::Process -> perform a single “tick” of the synthesizer functions, including updating the sample buffers from Dexed.
    • mididevice.cpp, serialmidi.cpp, usbmidi.cpp -> placeholder classes that will eventually support MIDI message passing and a serial and USB MIDI interface. This borrows heavily from the way it is done in MiniDexed. These classes will support the following interface:
      • CSerialMIDI::Init -> Initialise the hardware (same for USB).
      • CSerialMIDI::Process -> poll the hardware (same for USB).
      • CMIDIDevice::MIDIMessageHandler -> will be called by the lower-level devices when a MIDI message is ready to be processed. Once parsed, it will trigger calls into the PicoDexed main synthesizer to update its state.
    • soundevice.cpp -> Encapsulating the interface to the pico_audio library to use I2S audio, with the following key interface:
      • CSoundDevice::Init -> Set the sample rate and I2S interface pins.
      • CSoundDevice::Update -> Fill the sample buffer using the provided callback, which will be a call to the Dexed->getSamples code above.
    • config.h -> contains some system-wide configuration items, such as sample rate and polyphony.

    PicoDexed will include the functions required to control the synthesizer. Examples include keydown and keyup functions for when MIDI NoteOn and NoteOff messages, and so on. It also includes a means to set the MIDI channel and to load a voice.

    I don’t know yet if the MIDI handling will be interrupt driven or polled. I need to read up on how the Pico SDK handles USB and serial data, but I suspect a polled interface should be fine for my purposes as long as it doesn’t hold up the sample calculations, buffer filling, and sample playing.

    With my first pass of this code, there is no external interface – it is still playing a test chord only. But at least most of the structure is now in place to hook it up to MIDI.

    The “to do” list so far:

    • Ideally find a way to better manage the Synth_Dexed changes. I should submit a PR to Holger, the creator of Synth_Dexed and discuss some conditional compilation steps.
    • Hook up USB MIDI so that the Pico can act as a MIDI device and play the synth that way.
    • Hook up serial MIDI too.
    • Implement volume. Without the filter there is currently no volume changing.
    • Implement some basic voice and bank loading.
    • Connect up some more core MIDI functionality for program change, BANKSEL, channel volume, master volume, and so on.
    • Think about how best to utilise the second core – in theory it should be possible to expand it to 16-note polyphony by using both cores. Or an alternative might be two instances of Synth_Dexed running, so making a second tone generator.

    MIDI/Serial Handling

    Rather than jump into USB, I’ve opted to get serial MIDI working first. The serial port handling I’ve implemented in serialmidi.cpp borrows heavily from the “advanced” UART example: https://github.com/raspberrypi/pico-examples/tree/master/uart/uart_advanced

    It is interrupt driven and shares a simple circular buffer with the main Read function based on the implementation described here: https://embedjournal.com/implementing-circular-buffer-embedded-c/.

    The basic design of the serial MIDI interface is as follows:

    Interrupt Handler:
      Empty the serial hardware of data writing it to the circular buffer

    Init function:
      Initialise the UART as per the uart_advanced example
      Install the interrupt handler and enable interrupts

    Process function:
      Call the MIDI device data parser to piece together any MIDI messages
      Call the MIDI device msg handler to handle any complete MIDI messages

    Read function:
      Read the next byte out of the circular buffer

    There is a common MIDI device that the serial MIDI device inherits from (and that I plan to also use with USB MIDI support when I get that far). This has the following essential functionality:

    MIDIParser:
      Read a byte from the transport interface (e.g. the serial MIDI Read)
      IF starting a new message THEN
        Initialise MIDI msg structures
        IF a single byte message THEN
          Fill in MIDI msg structures for single-byte message
          return TRUE
        IF there is a valid Running Status byte stored THEN
          IF this now completes a valid two-byte msg THEN
            Fill in MIDI msg structures for a two-byte message
            return TRUE
      Otherwise process as a two or three byte message
      IF message now complete THEN
        Fill in MIDI msg structures
        return TRUE
      return FALSE

    MIDI Message Parser:
      IF MIDI msg already processed THEN return
      IF on correct channel or OMNI THEN
        Based on received MIDI command:
          Extract parameters
          Call appropriate picoDexed function
      Mark MIDI msg as processed.

    I had a fun bug where in the serial handling, I was writing to a byte one-out in the circular buffer which meant that the MIDI handling largely worked, but only when using a controller with ActiveSensing – basically the reception of the extra byte “pushed through” the previous message. But it was a bit unresponsive, and occasionally a note of a chord would sound after the others.

    I spent the better part of a day instrumenting the code, attempting to work out where the delays might be coming from. Eventually I got so fed up with the active sensing reception clouding my analysis (and triggering my scope when I didn’t want it to) that I filtered it out in the serial interrupt routine – so as early as I could.

    This the made the delay a whole pile worse! That was the point I realised it was continually essentially one message behind. As a consequence I had another look at the buffer handling and that was when I realised the mistake.

    Multicore support

    My initial thought on the above problem was that it was a performance issue – that the MIDI handling wasn’t responsive enough. So I pushed ahead and moved all the synthesis code over to the second core. This is something I wanted to do anyway as I always had the plan of splitting the functionality across the two cores.

    To enable multicore support requires including pico_multicore in the list of libaries in the CMakeLists.txt file and then it should largely be a case of doing the following:

    #include "pico/multicore.h"

    void core1_entry (void)
    {
      // stuff to do to initialise core 1

      while (1)
      {
        // Stuff to do repeatedly on core 1
      }
    }

    // Rest of "normal" (core 0) initialisation code
    multicore_launch_core1 (core1_entry);

    The question is where to enable this. Eventually I settled on implementing this in picoDexed itself to split out the ProcessSound function over to the second core. This required the following:

    • PicoDexed::Init – initialise multi-core support and start the second core running.
    • PicoDexed::Process – no longer calls ProcessSound.
    • PicoDexed::core1_entry – now calls ProcessSound in an infinite loop.

    In order to ensure that I don’t get Dexed into an inconsistent state, I’ve protected the calls into Dexed from the Dexed_Adaptor with spinlocks (mirroring what was happing in MiniDexed) as shown in the following extract:

    class CDexedAdapter : public Dexed
    {
    public:
    CDexedAdapter (uint8_t maxnotes, int rate)
    : Dexed (maxnotes, rate)
    {
    spinlock_num = spin_lock_claim_unused(true);
    spinlock = spin_lock_init(spinlock_num);
    }

    void getSamples (int16_t* buffer, uint16_t n_samples)
    {
    spin_lock_unsafe_blocking(spinlock);
    Dexed::getSamples (buffer, n_samples);
    spin_unlock_unsafe(spinlock);
    }

    private:
    int spinlock_num;
    spin_lock_t *spinlock;
    }

    Spinlocks are described in chapter 4.1.19 of the RPi C/C++ SDK and are part of the hardware_sync library.

    In order to ensure that the spin_locks are not held too long, and to allow things like keyup/down events to be registered in a timely manner and not hold up core 0 whilst core 1 is calculating samples, I’ve now reduced the sample buffer to 64 bytes.

    As core 1 is essentially free-running calculating samples now, I figured it wouldn’t make much difference how many samples are calculated in each “chunk” but going to 64 from 256 gives four times the number of break points in the cycle where other events can be processed between the spin_locks.

    Once consequence of running multi-core seems to be that I can now push the polyphony up to 10 simultaneous notes without any artefacts.

    If I can find a way to keep some of the sound generation on core 0 too, I might be able to increase that even further, although getting 6 additional sound engines running to get up to the magic 16 note polyphony might be stretching things still. The trick will be finding a way to trigger and mix samples from the sound generators in the two cores, as all of that current happens within Dexed itself.

    Overclocking the Pico

    There have been a number of experiments already in seeing how far a Pico can be pushed. There is a standard API call to set the system clock: set_sys_clock_khz(), although not all values can be accurately configured. But general wisdom seems to be that running the Pico at 250MHz isn’t a big deal…

    Of course, at this point it is running outside of the “normal” spec, so the long term effects may well reduce the life of the Pico…

    But by doing this, the Pico is now running twice as fast and so can now easily cope with 16 note polyphony at a sample rate of 24000, or up the sample rate to 48000 and stick with 8 note polyphony.

    It might even raise the possibility of running two tone generators, one on each core! It really does open up a wide range of possibilities!

    Closing Thoughts

    I’m really pleased with the progress so far. I was starting to think there wouldn’t be a usable combination possible, but 10-note polyphony at a sample rate of 24000 isn’t too bad for a 133MHz CPU with no FPU.

    I think my basic design goal would be for something usable with a MIDI controller. I’m not looking to implement a UI like there is with MiniDexed as part of this build. But I do need a bit more MIDI functionality first and I would like to find a way to squeeze some sample calculations out of core 0 when it isn’t handling MIDI.

    I also want to get USB MIDI up and running too. I’m not sure if I want to push for both device and host USB support though. I’ll see how complicated it all is!

    In the video, I’ve used my Raspberry Pi Pico MIDI Proto Expander. It just needs the addition of the Pimoroni audio board and it is ready to go!

    Of course the key question is: would I recommend this to anyone? Answer: no! No way – get yourself a Raspberry Pi Zero V2 and run a full-blown set of 8 DX7s using MiniDexed 🙂

    Still for me, this is a bit of fun a really good excuse to do something that’s been on my “to do” list for ages – start getting to grips with the Raspberry Pi Pico C/C++ SDK and the RP2040.

    Kevin

    https://diyelectromusic.wordpress.com/2024/02/04/raspberry-pi-pico-synth_dexed-part-3/

    #dx7 #midi #picodexed #raspberryPiPico

  33. Now that the initial elation at getting a reasonable sounding, er, sound from my Raspberry Pi Pico Synth_Dexed has worn off, I’ve been seeing what I can do about the performance.

    TL;DR: this is all analysis and measurements, working out and attempting to understand how it all currently works. I haven’t fixed anything or improved it yet. I’m working on it. Read on if you want all the gory details.

    Recall at the end of part 1 I found I could only really support the following:

    • 2 note polyphony at a sample rate of 44100.
    • 4 note polyphony at a sample rate of 24000.
    • 6 note polyphony with jittering and stutters only…

    My main theory as I start on the next phase of investigation is that this is down to either (or both):

    • The fact that the Raspberry Pi Pico has no hardware floating point accelerator.
    • There is a bottleneck in the Raspberry Pi audio handling somewhere.

    So these are the two things to investigate further at the moment.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers and single board computers, see the Getting Started pages.

    Timing and Existing Performance

    I’ve created some simple “timing by GPIO” routines that allow me to hook up an oscilloscope to get some idea of where the code is spending its time.

    I have the following hooks as a starting point.

    Main.cpp:

    main:
      timingToggle(2) on every scan of the main while(1) loop
      timingOn(3)
      Update audio buffer
      timingOff(3)

    Update buffer callback routine:
      IF new samples from Synth_Dexed are required:
        timingToggle(4)
        Synth_Dexed -> getSamples

    I’ve also updated the code to see how changing the buffer size for both the Pico audio routines and Dexed itself will interact with the polyphony and sample rate settings

    #define DEXED_SAMPLE_RATE 24000
    #define POLYPHONY 4
    #define DEXED_NUM_SAMPLES 256
    #define PICO_NUM_SAMPLES 256

    To show how these are interacting, here are traces for 6-note polyphonic, 24000 sample rate, both buffers 256 bytes in size, showing the two “toggling” timing GPIOs.

    Every transition of the yellow trace (GPIO 2) corresponds to once round the main code loop. Every transition of the blue trace (GPIO 4) corresponds to when Synth_Dexed was called to fill the buffer with samples. The trace on the left is the “silent” trace and the trace on the right is when it is playing the 6-note chord.

    Note that the loop appears to be running at approx 90Hz (there are two transitions for every period measured on the scope). As each period is outputting 256 samples, this gives us our sample rate of approximately 90×256 ~= 24000.

    For reference, we can use the timingOn/Off measurement of GPIO 3 (blue trace below) to see that pretty much all the time spent in the loop is spent in the “update_audio_buffer” code.

    I can’t capture the entire yellow cycle and show the blue trace, but there is just a small “low” block corresponding to the time between calls to the update function. Pretty much all the “high” time is spent in the update function.

    Here are some traces using a 64 byte buffer for Synth_Dexed and a 256 byte buffer for pico_audio (so the same as before). Again, silent on the left, playing a 4-note chord on the right.

    We can clearly see that once the callback is filling the buffer from Dexed (the blue trace) there are four calls in quick succession per each emptying of the 256 Pico buffer. The implication here being that there is some waiting time for the Pico buffer to play before the next call to fill the buffer from Synth_Dexed.

    By way of contrast, here is the same settings playing the 6-note chord. We can clearly see that the time for Synth_Dexed to calculate a return 64 bytes worth of samples is only just about keeping up with the Pico playing 256 samples.

    For completeness, here is a trace of the 256-256 buffer again but this time playing the 8-note chords (on the right). We can clearly see that the time spent playing the notes pushes out the time taken to play the samples, compared to the time when no note is playing (left).

    The Pico has to be stalling whilst Synth_Dexed is calculating the samples when all 8 notes are playing.

    It is interesting to see what happens when the sample rate is increased. These are the same silent (left)/4-chord playing (right) traces for a 44100 sample rate. The frequency of calls to fill the buffers has doubled, as one might expect, but now playing 4 notes pushes Synth_Dexed past the time it takes for the Pico to play its 256 byte buffer.

    What can we take from all this? I draw the following conclusions:

    • Almost all the overhead seems to be in calculating samples, not in playing them.
    • Any playing overhead that exists is (as is to be expected) pretty much constant regardless of the polyphony of Dexed.
    • The current performance of Dexed is pretty much maxed out at 5-note polyphony for a sample rate of 24000. A few optimisations might just about get up to 6-notes – it plays pretty clearly with just the occasional glitches which might be solvable. But something pretty radical is likely to be required to go any higher…

    In short, any improvement is probably going to have to come from optimising the Dexed code and the biggest suspected culprit is at the moment is the floating point maths.

    I’ll come to the floating point subsystem in a moment, but looking at the pico_audio library and how I’m using it, I’ve noticed there seems to be a lot of copying of sample data between buffers going on at present:

    • Within Synth_Dexed, the samples are generated as floating point values and then converted to a signed 16-bit integer using arm_float_to_q15(), one buffer at a time.
    • Within my own code, samples are provided via the callback getNextSample() which returns samples, one at a time, to be placed in the pico_audio “producer” buffer grabbed in update_buffer() using take_audio_buffer().
    • Within take_audio_buffer, eventually the code goes through a sample conversion, but in my case this is a mono, signed 16-bit stream getting converted to a mono, signed 16-bit stream – but a copy from a “producer” buffer to a “consumer” buffer still takes place to achieve it.
    • Finally, DMA is triggered to get the data from the “consumer” buffer out to the I2S PIO driver.

    This really feels like overkill! I should be able to trim down the copying at my end. I don’t know yet if there is a better way to get from the floats used by Synth_Dexed to signed 16-bit values, but it may be that it can be done a bit more “on the fly”. But I would really like to eliminate that producer to consumer copy if I can. Alternatively, maybe I could add a floating point buffer type and leave the conversion to that last minute copy.

    There is a detailed analysis of the layers and buffer handling in the Pico audio library later in this post.

    Pico DEBUG_PINS

    I was interested in finding out how long the Pico takes in the various stages of the buffer transfers in the audio library. It turns out that there is provision for enabling “debug” pins at various points in the Pico’s libraries. This seems to be enabled with the following macros (these ones are from audio_i2s.c):

    CU_REGISTER_DEBUG_PINS(audio_timing)
    //CU_SELECT_DEBUG_PINS(audio_timing)

    DEBUG_PINS_SET(audio_timing, 4);
    DEBUG_PINS_CLR(audio_timing, 4);
    DEBUG_PINS_XOR(audio_timing, 1);

    These are defined in gpio.h in the Pico SDK but there isn’t really any documentation about them. From that I can see if you put a call in your main code to:

    gpio_debug_pins_init();

    And then uncomment one of the CU_SELECT_DEBUG_PINS() macros then the _SET, _CLR and _XOR macros become active and will set, clear or toggle a GPIO pin. By default, in gpio.h, the following defines are set up to start the DEBUG_PINS at GPIO 19:

    #define PICO_DEBUG_PIN_BASE 19u
    #define PICO_DEBUG_PIN_COUNT 3u

    The _SET, _CLR, _XOR macros work on a bit-mask basis, starting at the _PIN_BASE. So if 3 _DEBUG_PINS are defined, the then following will set or enable _DEBUG_PINS:

    DEBUG_PINS_SET(audio_timing, 1) ---> GPIO19
    DEBUG_PINS_SET(audio_timing, 2) ---> GPIO20
    DEBUG_PINS_SET(audio_timing, 4) ---> GPIO21

    If there were 4 DEBUG_PINS enabled then setting (audio_timing, 8) would enable GPIO22.

    Note: other subsystems have their own definitions instead of “audio_timing”.

    Why mention this? Because the DMA IRQ handler uses _SET and _CLR on the third DEBUG_PIN (4, i.e. GPIO21) either side of the audio_start_dma_transfer() function, so this can be used to see how much time is taken up in that “converting” copy.

    In the following trace, we can just about see (the small blue peak) that the time in the DMA handler is pretty insignificant compared to the time processing samples.

    So at this point, I’ve decided I don’t need to worry about the extra copying that appears to be going on in the audio library itself.

    Deep dive into Synth_Dexed getSamples

    In my own code, I’ve switched the audio buffer filling code from the use of a callback function, that will fill a Dexed buffer and then pass it on one sample at a time to the Pico’s audio buffer, to my own custom update routine that just fills an entire buffer directly:

    void fillSampleBuffer(struct audio_buffer_pool *ap) {
    struct audio_buffer *buffer = take_audio_buffer(ap, true);
    int16_t *samples = (int16_t *) buffer->buffer->bytes;
    dexed.getSamples(samples, buffer->max_sample_count);
    buffer->sample_count = buffer->max_sample_count;
    give_audio_buffer(ap, buffer);
    }

    This eliminates the need to copy (one byte at a time, via the callback) from the Dexed buffer to the Pico audio buffer.

    Now it is time to dig into the Dexed getSamples routine and attempt to really see what is going on. This can be found in dexed.cpp.

    First of all, it is interesting to see exactly how much time is taken in the getSamples routine itself, so I’m using timingOn(4) and timingOff(4) at the start and end of the “real” getSamples and timingOn/Off(3) at the start and end of the integer version (that calls the real version and converts the samples).

    This shows how time in getSamples compares (blue) to the default scan time (yellow) for silence (left) vs playing a 5-note chord (right) – i.e. something that plays successfully with no distortion.

    Comparing the time in “real” getSamples (that calculates floats – in blue) with “integer” getSamples (that converts the buffer prior to returning – in yellow), we can see there is only a very marginal increase in overhead (left):

    For comparison, on the right is the trace for playing a 6-note chord, which is where the stuttering starts to appear in the audio output. We can see how the getSamples (blue) is maxed out against the basic Pico Audio buffer filling (yellow).

    Two more traces: on the left we have timing traces for the main “calculate a block of samples” routine. We can see four blocks are required to fill our 256 byte buffer. This comes from a block size definition _N_ = (1<<6) i.e. 64 (from here).

    On the right we have the time taking inside the dx7note->compute function itself. This is called for each possible note, up to the maximum polyphony specified when we initialised Synth_Dexed.

    With the buffer sample size of 256 bytes, we have four times round the “get a block of samples” loop (left) and with 5-note polyphony, we can see 5 calls to dx7note->compute (right) for each call to getSamples – so 5×4 or 20 calls in total.

    Observations so far:

    • getSamples returns a sample buffer of floats, yet dx7note->compute returns 32-bit, signed integers. The other getSamples routine I’m using then converts these converted floats back over to 16-bit signed integers.
    • It would appear that reason for the above is the call to fx.process at the end of getSamples which happens on the entire buffer of (now float) samples. The time taken for this call, after obtaining the filled sample blocks, can be seen as the difference between the yellow and blue traces in the last set of oscilloscope screens.
    • The conversion of each note’s worth of samples from signed 32-bit integers to floats appears to happen due to the following line, which according to the traces, seems to take at least the same amount of time as calculating the samples in the first place on a per-note basis:
    buffer[i + j] += signed_saturate_rshift(audiobuf.get()[j] >> 4, 24, 9) / 32768.0;
    • This line effectively turns the 32-bit signed value (so -2147483648 to 2147483647) into a -1.0 to +1.0 floating point number, using a 32-bit floating point representation (i.e. a “single” float).
    • Then it adds the final result to the value already in the buffer (which starts off at zero).
    • It would appear that it does this as fx.process (from PluginFx.cpp) applies the filters but only works exclusively with floats.

    One thing has been confirmed though. Looking at the assembly listing produced as part of the build process, I can see several calls to the Pico’s “aeabi” wrapper functions, which I believe are the “faster” (compared to the compiler’s own) ROM implementations of (single or double) floating point routines:

    So yes, there is a fair bit of floating point conversion going on, but yes, the code is already using the Pico’s faster library for floating point operations.

    As an experiment I commented out the call to fx.process() and found I was able to squeeze in another note of polyphony, taking me to 6-note polyphony with hardly any artefacts! But I’m still at a sample rate of 24000 and now have no filter!

    Int to Float to Int again

    So, digging deeper into these conversions. Within the float32 version of getSamples, the following is going on:

    • dx7note->compute returns a sample for any “live” note as a signed, 32-bit value.
    • these values are translated into a 32-bit floating point value in the range -1.0 to +1.0 using the above mentioned code.
    • these are processed via fx.process() and returned to the calling function.

    I’m not entirely sure I can untangle the shifting and dividing going on here, but I think the following is happening:

    buffer[i + j] += signed_saturate_rshift(audiobuf.get()[j] >> 4, 24, 9) / 32768.0;
    • The value to be shifted is first normal right-shifted by 4 to yield a 28-bit signed value presumably… it isn’t clear if this will be an “arithmetic bit shift” or a “logical bit shift”. In the former the sign should be “shifted in”. In the latter, it won’t… I’m guessing it has to be arithmetic, otherwise I don’t see how it could ever work…
    • Then it performs a “signed saturated right shift” of 9 places, presumably setting the “saturation” to 24 bits (0x800000 to 0xFFFFFF or +/- approx 8.4 million). I’m not entirely sure why this is required, as wouldn’t shifting by 4 then 9 result in a 19-bit number anyway…?
    • Finally it divides the result by 32768.0 which is essentially another shift right by 15…

    We know this leaves a value in the range -1.0 to +1.0, but it isn’t entirely clear to me how these various combined shifts of what appears to be 28 (4+9+15) places gets us there.

    Interestingly, this does all related to the original MSFA code (from here):

    int32_t val = audiobuf2.get()[j] >> 4;
    int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff :
    val >> 9;

    Continuing on, we can see that in the int16 version of getSamples, the buffer is converted back again from a float to a signed, 16-bit value using the following code:

    arm_float_to_q15(tmp, (q15_t*)buffer, n_samples);

    This is one of the ARM DSP library functions and converts a 32-bit floating point value into a Q15 fixed point value. There seems to be some ambiguity quite what the 15 stands for. Apparently for an ARM system this will include the sign bit, so this would be a number between -1 and 1 with 14 places after the “decimal point” (although in this case we’re talking binary, not decimal of course).

    Reading a Q15 value directly as a signed 16-bit value would thus give you a value between -32768 (0x8000) and 32767 (0x7FFF), so the float to q15 function is effectively equivalent to: q15_value = float_value * 32768, assuming a floating point value between -1.0 and +1.0.

    We can see this is the complete opposite of what the “/ 32768.0” is doing in the conversion code from the “real” version of getSamples. We can therefore trust that the appropriate bit-shifting (by 4, saturated by 9) has the end result of leaving us with a Q15 equivalent value which is then converted again to the -1.0 to +1.0 range via the “/ 32768.0”.

    This presents the possibility that we can leave out the integer to floating point to integer translation completely if we could just convert the filter “fx” code to also work on Q15 fixed point numbers.

    In the meantime, mirroring the “comment out the fx.process” step which gave us 6-note polyphony, this is what happens when the floating point step is removed from getSamples completely. On the left is the floating point version with no fx-process step; on the right is the Q15 version also with no fx.process step (yellow = complete getSamples step; blue = dx7note->compute step):

    We can really see how much time is taken up in the conversions here. It opens up the possibility of more than 8-note polyphony if the filter could be rewritten to use fixed-point maths.

    Interestingly, the original “music synthesizer for Android” (MSFA) says it was optimised for 32-bit fixed point maths. It also includes a fixed point filter calculation, but the comments imply it is a simplification or “initial version”, so it isn’t clear at what point the floating point implementation used in Synth_Dexed came along.

    For what it’s worth, it would appear (assuming I’m reading this right), that the original DX7 had a 14-bit sample format and 12-bit envelope, so I’m wondering if that is how we can bit-shift by 4 then 9 places and end up with a -1.0 to 1.0 range… that would seem to make sense…

    Also, it would appear that a configurable filter stage appeared in Dexed itself, but isn’t part of the original MSFA code, and there doesn’t seem to be any mention of a filter in the original DX7 that I can find. So actually, I could just drop the filter and other effects and then I’d probably end up with a fully integer synth. In fact, the Dexed FAQ does actually say this:

    • “msfa / Dexed is an integer based synth engine, it uses the Q** format.”

    So I’m starting to think just leaving out the filter stage could be a legitimate option. I will have to implement volume somehow though – and then decide if that should be channel volume or “master volume” (in MIDI terms).

    If not, then all this seems to suggest it would be very worthwhile attempting to replace the floating point filter routines with a fixed point equivalent – but that really won’t be a trivial undertaking.

    Another option might be to go for a simpler filter application – the original MSFA includes an integer-base resonant filter implementation which might suffice (resofilter.cc).

    But all that will have to wait for another time.

    Below are two more detailed dives into how the Pico supports floating point and how the Pico Audio library works.

    Kevin

    Floating Point Library

    Synth_Dexed makes use of the ARM CMSIS DSP code for a range of floating point calculations. These had to be pulled in to allow it to build. At the end of part 1 I found out how to replace the CMSIS library en masse with just the few, relatively isolated, functions that Synth_Dexed was using so if it comes down to optimising this code somehow, at least I know the size of the task!

    The Pico Audio Library

    As part of chewing over how everything is working and where the overheads are likely to be, I’ve been trying to understand how the Pico audio library works, just to get my head around where it might be taking time and where there might be alternative ways to use it to improve things.

    It’s a bit complicated!

    The top-level principle is that the “user code” acts as an audio producer and the I2S driver code acts as an audio consumer. I2S is implemented using the PIO subsystem and is fed using the hardware DMA peripheral from a pool of buffers managed by the audio library.

    I’m using the Pimoroni audio.hpp code which essentially as the following structure:

    init_audio:
      define the audio format to use, sample rate, etc
      CALL audio_new_producer_pool() to set up a pool of producer buffers
      CALL audio_i2s_setup() to configure I2S
      CALL audio_i2s_connect() to initialise I2S
      CALL audio_i2s_set_enable() to turn it all on

    update_buffer:
      CALL take_audio_buffer() to get a free producer buffer from the pool
      fill the buffer with samples using a callback mechanism
      CALL give_audio_buffer() to queue the buffer for processing

    So there is no i2s read/write functionality directly visible – that is buried within the PIO I2S layers, so the basic idea is to just keep the buffer filled enough to allow the DMA and PIO to do its thing.

    So digging into these calls a bit more to see exactly what is going on…

    As already mentioned the library works on the idea of producers and consumers and allows you to define the connections between them. The connection is a structure that links the take/give routines for producers and consumers together.

    The default connection is defined in pico_audio.c with the following structure and the following listed four functions:

    ~~ pico_audio.c ~~

    static audio_connection_t connection_default = {
    .producer_pool_take = producer_pool_take_buffer_default,
    .producer_pool_give = producer_pool_give_buffer_default,
    .consumer_pool_take = consumer_pool_take_buffer_default,
    .consumer_pool_give = consumer_pool_give_buffer_default,
    };

    producer_pool_give_buffer_default(connection, buffer) {
    queue_full_audio_buffer(connection->producer_pool, buffer)
    }

    producer_pool_take_buffer_default(connection, block) {
    return get_free_audio_buffer(connection->producer_pool, block)
    }

    consumer_pool_give_buffer_default(connection, buffer) {
    queue_free_audio_buffer(connection->consumer_pool, buffer)
    }

    consumer_pool_take_buffer_default(connection, block) {
    return get_full_audio_buffer(connection->consumer_pool, block)
    }

    The I2S sending (consumer) code uses the default connection for give_audio_buffer() but replaces the take_audio_buffer() connection code with wrap_consumer_take().

    The rest of the audio_i2s code has the following functionality:

    ~~ audio_i2s.c ~~

    audio_i2s_setup:
      Initialises PIO, DMA, DMA data requests (DREQ_PIOx_TX0)
      Set up audio_i2s_dma_irq_handler() as the DMA interrupt handler

    audio_i2s_connect (prodpool):
      CALL audio_i2s_connect_thru(prodpool, no connection):
        CALL audio_i2s_connect_extra(prodpool, no connection):
          CALL audio_new_consumer_pool() for a new consumer buffer pool
          Set up a consumer connection called m2s_audio_i2s_ct_connection
          CALL audio_complete_connection to link the consumer to producer

    audio_i2s_dma_irq_handler:
      IF finished playing the last buffer:
        CALL give_audio_buffer to return the consumer buffer
          CALL consumer_pool_give function
            -> consumer_pool_give_buffer_default() to queue the free buffer
      CALL audio_start_dma_transfer()
        CALL take_audio_buffer() for a new consumer buffer
          CALL consumer_pool_take() function
            -> wrap_consumer_take()
              CALL mono_to_mono_consumer_take() - in my case
                CALL Mono-FmtS16 to Mono-FmtS16 consumer_pool_take()
                  CALL get_free_audio_buffer() from consumer pool
                  CALL get_full_audio_buffer() from producer pool
                  Perform any sample conversions whilst copying from p to c
                  CALL queue_free_audio_buffer() to return to producer pool
                  return filled consumer buffer to call stack
        IF no buffer ready to play, just output silence
        Configure DMA for the new consumer buffer
        CALL dma_channel_transfer_from_buffer_now with the consumer buffer

    The range of C++ templated consumer_pool_take() functions is defined in sample_conversion.h to allow for conversions between stereo or mono, and different formats: unsigned or signed, 8-bit or 16-bit. Each will involve a copy from a producer buffer to a consumer buffer performing any necessary processing on the way.

    So to summarise the buffer actions, it is essentially the sequence of get_free/queue_free routines with get_full/queue_full routines acting on either the producer or consumer pools.

    The specifics for the I2S sending are as follows:

    User calling:
    - take_audio_buffer (producer pool)
    ---> uses get_free_audio_buffer() from producer pool
    - full buffer with data
    - give_audio_buffer (producer pool)
    ---> uses queue_full_audio_buffer() to producer pool

    When DMA data request triggers:
    - give_audio_buffer (consumer pool)
    ---> queue_free_audio_buffer() to consumer pool
    - take_audio_buffer (consumer pool)
    ---> uses get_free_audio_buffer() from consumer pool
    ---> uses get_full_audio_buffer() from producer pool
    ---> transfer data from producer to consumer buffer
    ---> uses queue_free_audio_buffer() to producer pool

    Some of the functions have a parameter that suggests there is an option for a blocking or non-blocking driver. They key issue appears to be waiting for a free or full buffer to be made available via the appropriate queue function.

    To do this, the library is using the __wfe() and __sev() ARM event handling system. If blocking, then the get function will use wfe to wait for an event from the queuing function.

    At the lowest level the pools are managed using spin_lock_blocking() and two spin locks called the free_list_spin_lock and prepared_list_spin_lock, which are both created for each new buffer pool.

    This seems to be working fine as far as I can see, but there does seem to be a lot of processing of various buffers involved! The library seems very flexible supporting different audio output types (PWM, I2S, SPDIF) and a range of audio formats.

    In particular that extra buffer copy as part of the pre-DMA setup seems pretty superfluous in my case as no conversion should be required – the eventual “conversion” routine is for mono, signed 16-bit to mono, signed 16-bit, so that might be an option for some optimisation. It won’t affect the sample rate playback, which is fixed by the DMA/PIO, but it might allow for some additional CPU cycles that could allow Dexed more processing time to calculate new samples.

    It also all happens in an interrupt routine, which is slightly surprising, as typically we’d want these to be as short as possible. There may be other ways of passing these buffers around that doesn’t require a copy prior to DMA. There are also quite a lot of layers involved in each action too. I wonder if a simpler buffer implementation would give more processing time back, but until I have some measure of the actual time taken in any of these calls, it is all speculative.

    There are a few other Pico I2S implementations I’ve found that also use DMA/PIO, so I might have a look at those too to see if it looks like there are any optimisations to be made for my fixed case (fixed format, I2S only, mono, just output):

    But going on the measurements I have, any performance limitations are still in getting the Pico to calculate samples and fill the buffers so it’s probably not worth worrying too much about the audio library at this point.

    Kevin

    https://diyelectromusic.wordpress.com/2024/01/21/raspberry-pi-pico-synth_dexed-part-2/

    #define #dx7 #floatingPoint #picodexed #raspberryPiPico #sampleRate

  34. For a while I’ve wanted to get Synth_Dexed up and running on a Raspberry Pi Pico. This is the library written by Holger Wirtz for use with the Teensy microcontroller, and the core synth engine used for MiniDexed.

    This is how I got everything up and running although it is too early to know if this is a worthwhile activity or not!

    Note: this is just the very first set of tests to see if anything is even feasible, so don’t expect a playable, working synth! It is just making some hardcoded sounds for the time being.

    And the performance isn’t what anyone would call stellar… I can currently manage 4-note polyphony if the sample rate is dropped. That is about it right now!

    • Part 1: Building Synth_Dexed for the Pico.
    • Part 2: Assessing the performance and analysis of the Pico audio library.
    • Part 3: MIDI and some basic usability (finally).
    • Part 4: More MIDI; Bank and Voice loading; SysEx support; and USB-MIDI.
    • Part 5: Details of how to build the hardware.

    This is based on ideas and information found from examining the following:

    Reference material:

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    Intro and Hardware Requirements

    The core aim is to be able to run a instance of Synth_Dexed on one of the cores of a Raspberry Pi Pico and feed it information that controls how it generates sound and then play the samples back out over some kind of audio interface.

    Eventually it should receive this information from MIDI or some kind of built-in interface. Sound output ideally would support an I2S audio DAC – I’ve started with the Pimoroni Pico Audio Pack – but I’d like to add PWM and possibly others.

    It remains to be seen if the performance maps a single instance to a core or allows for several instances to be running concurrently and at what level of polyphony.

    Other requirements that I’m chewing over:

    • Polyphonic to some level.
    • MIDI.
    • Should allow for flexible separation of interface and synth engine.
    • Should allow for several Picos to act together to provide multiple synth engines.
    • Supports voice bank loading from the Pico’s onboard flash memory “disk”.
    • Use the Synth_Dexed library with the minimum necessary changes – ideally none!

    To get started and start experimenting will eventually requires the following hardware:

    • Raspberry Pi Pico.
    • Means of getting audio out – initially its only doing I2S, but PWM could be added.
    • Eventually some means of getting control signals in (USB or serial MIDI or hard-coded).

    The following GPIO pins are being used:

    • I2S: I2S_DATA = GP9; I2S_CLOCK = GP10
    • Debug UART: TX = GP0; RX = GP1
    • MIDI (eventually): TX = GP4; RX = GP5

    Note that the Pimoroni Audio Pack also makes use of GP22 as a mute switch, but I’m not using that.

    The Environment

    I set up the Raspberry Pi C/C++ SDK according to my previous set of instructions from here: Getting Started with the Raspberry Pi Pico C/C++ SDK and TinyUSB MIDI.

    Once again I’m doing all this in the Ubuntu virtual machine.

    The created a picodexed project with the following structure:

    picodexed
        +--- build
        +--- cmsis
        +--- src
        +--- synth_dexed 

    I’m not going through the whole discovery process of how I got to this point, but here are a few notes of things I learned on the way in case I need to refer back to them!

    CMSIS is required to support some of the ARM DSP functions used by Synth_Dexed, but not all of it is necessary. After hunting around for how to build this into the Raspberry Pi Pico (the SDK only has some very basic interface definitions, not the whole proper thing) I eventually stumbled across Chris Hockuba’s Raspberry Pi Pico CMSIS – cmsis-pi-pico – on Gitlab so I cloned that into my cmsis area and used that.

    A really useful clue as to what is required to get Synth_Dexed running on a different architecture to the original Teensy can be found from examining the following files created as part of the MiniDexed discussion:

    In terms of approach, I basically created the most basic main.cpp I could and created CMakeLists.txt files for the main application and the Synth_Dexed library and then just kept trying to build it to see what was still broken.

    I had a frustrating diversion for a while, not noticing that I’d created a main.c and not a main.cpp and that when it included dexed.h from Synth_Dexed it couldn’t find <cstdlib> – the C++ standard library… I spent ages trying to work out why my compiler couldn’t find its own libraries…

    One thing that appeared to be missing totally though, was a definition of boolean. Now I could have gone through Synth_Dexed changing boolean to bool, but in the end implemented a “filler” or “wrapper” header file with everything extra that Synth_Dexed needed but was missing – dexed_if_common.h – and stuck a typedef bool boolean; in there.

    So far, the only change I’ve had to make to the library itself is to add the following line into Synth_Dexed/src/dexed.h and then make sure the path for include files can find it during compilation:

    #include "dexed_if_common.h"

    I still haven’t quite got to grips with the whole cmake infrastructure required for building Raspberry Pi Pico projects, so the CMakeLists.txt files I’ve created are almost certainly sub-optimal at present. I still need to get my head around PRIVATE, PUBLIC, INTERFACE qualifiers for example and probably have more directories defined for INCLUDE paths than is strictly necessary.

    But it builds and everything I have so far can be found on GitHub. This includes a simple script to take the basic repository and add in CMSIS and Synth_Dexed and hack in that single change to dexed.h. This means that to build what I have so far requires the following:

    1. Install the Pico C/C++ SDK, toolchain and libraries (as I described here) including setting PICO_SDK_PATH to the location of the Pico SDK installation.
    2. Clone the picodexed repository.
    3. Run the getsubmod.sh script (only run this once for a fresh repository).
    4. Then build:
    kevin@ubuntu:~/src/picodexed$ cd build
    kevin@ubuntu:~/src/picodexed/build$ cmake ..
    kevin@ubuntu:~/src/picodexed/build$ make
    kevin@ubuntu:~/src/picodexed/build$

    Adding Audio

    The Pico has no in-built audio capability, but there is an audio library that can be found in the “pico_extras” repository that supports I2S audio interfaces or PWM audio output on GPIO pins, both implementing using the Pico’s PIO subsystem.

    There is an example in the “pico_playground” that shows how to output a sine wave and Pimoroni also have a sample mini-synth application for their I2S Pico Audio Pack.

    Links:

    In order to build for use with the pico_audio library, the pico_extras repository needs to be cloned into the source area and the location specified using PICO_EXTRAS_PATH.

    There is another cmake file that can then be copied into your own project area to include the library.

    cp $PICO_EXTRAS_PATH/external/pico_extras_import.cmake .

    And then it can be included in the project’s CMakeLists.txt file, pico_audio_i2s can be added to the list of link libraries, and a compilation definition added to define USE_AUDIO_I2S.

    include(pico_extras_import.cmake)

    target_link_libraries(picodexed PUBLIC synth_dexed pico_stdlib tinyusb_device tinyusb_board pico_audio_i2s)

    target_compile_definitions(picodexed PRIVATE
    USE_AUDIO_I2S=1
    )

    The actual examples for audio output are quite complicated and so far the documentation I’ve found for the pico_audio libraries is pretty minimal.

    The Pimoroni build has abstracted most of the functionality out into a separate audio.hpp file with the following interface:

    // init_audio initialises the audio library and returns a pointer
    // to an object representing the audio buffers.
    struct audio_buffer_pool *init_audio(uint32_t sample_rate, uint8_t pin_data, uint8_t pin_bclk, uint8_t pio_sm=0, uint8_t dma_ch=0);

    // update_buffer will fill the audio buffer with samples returned
    // by the provided callback function.
    void update_buffer(struct audio_buffer_pool *ap, buffer_callback cb);

    The callback function that is meant to provide samples to the audio subsystem has the following definition:

    int16_t getNextSample (void);

    Dexed has a getSamples function that can fill a buffer with the next set of samples in either float or int16_t format, but the callback function only returns a single sample at a time.

    Rather than attempt to make that fit (I did start off that way) I wrote my own “update_buffer” function to fill the Dexed buffer:

    void fillSampleBuffer(struct audio_buffer_pool *ap) {
    struct audio_buffer *buffer = take_audio_buffer(ap, true);
    int16_t *samples = (int16_t *) buffer->buffer->bytes;
    dexed.getSamples(samples, buffer->max_sample_count);
    buffer->sample_count = buffer->max_sample_count;
    give_audio_buffer(ap, buffer);
    }

    Once slight complication is that the Dexed getSamples function is a protected function. To interface into Dexed I’ve thus created a Dexed Adaptor based on the MiniDexed dexedadaptor. I don’t know how many of these functions will really need to be a wrapper around Dexed, but the capability is there if required.

    Typically with embedded audio applications, there is a regular “internal buffer out to audio hardware” layer that has to run at the sample rate and be reliably regular with its timing. This would usually be driven from a timer interrupt or similar. Then there will be a non-timing-critical part of code responsible for filling the buffer itself with samples to be played.

    The Pico’s audio library is quite different. Unfortunately, I can’t quite see with the combination of complex C buffer management code, use of the Pico’s DMA and use of the PIO hardware for the I2S protocol itself, quite what the timing expectations of the pico_audio library are and how they should be split up, but from what I can gather, the basic idea is as follows:

    • PIO is automatically pulling data from a set of buffers to send over I2S.
    • DMA is providing that data somehow at a rate independent of the CPU, but maybe driven by interrupts linked to data requests and the PIO somehow.
    • All I have to do is keep the buffer that can be accessed by the DMA fill of audio samples and magic “just happens”.

    So for the time being, I’m just calling my update audio equivalent function in a relatively fast loop.

    Unfortunately my first attempts all resulting in some kind of horrid growl. But it does turn on and off when the Dexed.keydown and Dexed.keyup calls are made so I was making progress!

    Things that eventually fixed that:

    • I updated the init_audio function to allow me to pass in the sample buffer size and chose a buffer size of 128 and then ensured this was set to the same in Dexed itself.
    • I saw the sine example was using a sample rate of 24000 for I2S so I used that sample rate here too.
    • The thing that eventually fixed everything was realising that Dexed is returning a mono stream of data but the default I2S interface is assuming a stereo set of samples. Setting the following in CMakeLists.txt told the I2S library to use mono:
    target_compile_definitions(picodexed PRIVATE
    PICO_AUDIO_I2S_MONO_OUTPUT=1
    PICO_AUDIO_I2S_MONO_INPUT=1
        USE_AUDIO_I2S=1
    )

    The sine library seems to get away with only defining MONO_OUTPUT, but I was getting compilation errors, so I set MONO_INPUT too, even though I’m not using I2S input.

    At this point I was getting a nice note playing via Dexed, so I grabbed the voice parameters for Brass 1 out of MiniDexed and dropped them into an array here and with a call to Dexed.loadVoiceParameters, I have a Raspberry Pi Pico playing the Dexed Brass 1 sound!

    In terms of sample rates and polyphony, well, things are a little basic! I can currently achieve the following:

    • 2-note polyphony at 44100.
    • 4-note polyphony at 24000.
    • 6-note polyphony, possibly if you squint at it and ask it really nicely, at 12000.

    So it isn’t going to win any prizes for quality or quantity, but this is a point that I feel it’s now worth pushing this post out and sticking the progress up on GitHub.

    Next, I want some simple MIDI control to act as a real synth. Other things on the “todo” list:

    • See what scope there is for optimisation to increase the polyphony ideally with 44.1kHz.
    • Specifically look into how it is currently doing the floating point maths.
    • Investigate the loading of the core to see how the performance is looking more generally.
    • Get some additional voices in there!

    Removing Dependency on CMSIS

    It turns out that only a few functions are required from the CMSIS/DSP area, so in order to examining them more closely I created my own version of arm_math.h with a corresponding arm_math.c containing the following functions:

    void arm_float_to_q15()
    void arm_fill_f32()
    void arm_sub_f32()
    void arm_scale_f32()
    void arm_offset_f32()
    void arm_mult_f32()
    void arm_biquad_cascade_df1_f32()
    void arm_biquad_cascade_df1_init_f32()

    with any additional definitions they require. Turns out it isn’t very many. The only thing left is to mirror how Synth_Dexed builds for the Teensy and ensure that the compressor isn’t included. This requires some conditional compilation in dexed.h, dexed.cpp and compressor.h.

    There doesn’t appear to be a single obvious way to spot a build for a Raspberry Pi Pico though, but there is a definition of RASPBERRYPI_PICO in <board/pico.h> so I used that.

    Find it on GitHub

    The current state of progress can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Closing Thoughts

    I first wanted to do this when I first installed the Pico SDK back in August 2022! Yes it really has taken me that long to even attempt it. Mostly because my C is still quite rusty; my C++ is very much “learn as you go”; I’ve not worked with a CMSIS ARM embedded system in detail before; and the cmake infrastructure for a Raspberry Pi Pico seems so very complicated for what it is.

    But you’ve got to start somewhere as they say, and I learn best from having a reason to do something. So even if this doesn’t amount to anything, it is finally making me learn about all these things in a useful way that will hopefully end up with something I’ll have some fun playing with.

    The big limitation might be floating point maths. But the Pico does have some built-in fast floating point routines. I don’t know if any of these are enabled and running at the moment. I suspect the CMSIS DSP code is probably doing a “soft” floating point, so that is something to look into. But if it is already using the faster routines, then it may well keep the polyphony so low that it isn’t particularly practical to use. To be continued…

    But once again, my renewed respect to all the above-mentioned people who have essentially already done most of the hard work for projects like this that allow someone like me to bumble along and join the various bits together!

    By the way, seeing as MiniDexed exists for the Raspberry Pi and MicroDexed exists for the Teensy, PicoDexed seemed the obvious choice!

    Kevin

    https://diyelectromusic.wordpress.com/2024/01/09/raspberry-pi-pico-synth_dexed/

    #CCSDK #dexed #picodexed #raspberryPiPico

  35. For a while I’ve wanted to get Synth_Dexed up and running on a Raspberry Pi Pico. This is the library written by Holger Wirtz for use with the Teensy microcontroller, and the core synth engine used for MiniDexed.

    This is how I got everything up and running although it is too early to know if this is a worthwhile activity or not!

    Note: this is just the very first set of tests to see if anything is even feasible, so don’t expect a playable, working synth! It is just making some hardcoded sounds for the time being.

    And the performance isn’t what anyone would call stellar… I can currently manage 4-note polyphony if the sample rate is dropped. That is about it right now!

    • Part 1: Building Synth_Dexed for the Pico.
    • Part 2: Assessing the performance and analysis of the Pico audio library.
    • Part 3: MIDI and some basic usability (finally).
    • Part 4: More MIDI; Bank and Voice loading; SysEx support; and USB-MIDI.
    • Part 5: Details of how to build the hardware.

    This is based on ideas and information found from examining the following:

    Reference material:

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    Intro and Hardware Requirements

    The core aim is to be able to run a instance of Synth_Dexed on one of the cores of a Raspberry Pi Pico and feed it information that controls how it generates sound and then play the samples back out over some kind of audio interface.

    Eventually it should receive this information from MIDI or some kind of built-in interface. Sound output ideally would support an I2S audio DAC – I’ve started with the Pimoroni Pico Audio Pack – but I’d like to add PWM and possibly others.

    It remains to be seen if the performance maps a single instance to a core or allows for several instances to be running concurrently and at what level of polyphony.

    Other requirements that I’m chewing over:

    • Polyphonic to some level.
    • MIDI.
    • Should allow for flexible separation of interface and synth engine.
    • Should allow for several Picos to act together to provide multiple synth engines.
    • Supports voice bank loading from the Pico’s onboard flash memory “disk”.
    • Use the Synth_Dexed library with the minimum necessary changes – ideally none!

    To get started and start experimenting will eventually requires the following hardware:

    • Raspberry Pi Pico.
    • Means of getting audio out – initially its only doing I2S, but PWM could be added.
    • Eventually some means of getting control signals in (USB or serial MIDI or hard-coded).

    The following GPIO pins are being used:

    • I2S: I2S_DATA = GP9; I2S_CLOCK = GP10
    • Debug UART: TX = GP0; RX = GP1
    • MIDI (eventually): TX = GP4; RX = GP5

    Note that the Pimoroni Audio Pack also makes use of GP22 as a mute switch, but I’m not using that.

    The Environment

    I set up the Raspberry Pi C/C++ SDK according to my previous set of instructions from here: Getting Started with the Raspberry Pi Pico C/C++ SDK and TinyUSB MIDI.

    Once again I’m doing all this in the Ubuntu virtual machine.

    The created a picodexed project with the following structure:

    picodexed
        +--- build
        +--- cmsis
        +--- src
        +--- synth_dexed 

    I’m not going through the whole discovery process of how I got to this point, but here are a few notes of things I learned on the way in case I need to refer back to them!

    CMSIS is required to support some of the ARM DSP functions used by Synth_Dexed, but not all of it is necessary. After hunting around for how to build this into the Raspberry Pi Pico (the SDK only has some very basic interface definitions, not the whole proper thing) I eventually stumbled across Chris Hockuba’s Raspberry Pi Pico CMSIS – cmsis-pi-pico – on Gitlab so I cloned that into my cmsis area and used that.

    A really useful clue as to what is required to get Synth_Dexed running on a different architecture to the original Teensy can be found from examining the following files created as part of the MiniDexed discussion:

    In terms of approach, I basically created the most basic main.cpp I could and created CMakeLists.txt files for the main application and the Synth_Dexed library and then just kept trying to build it to see what was still broken.

    I had a frustrating diversion for a while, not noticing that I’d created a main.c and not a main.cpp and that when it included dexed.h from Synth_Dexed it couldn’t find <cstdlib> – the C++ standard library… I spent ages trying to work out why my compiler couldn’t find its own libraries…

    One thing that appeared to be missing totally though, was a definition of boolean. Now I could have gone through Synth_Dexed changing boolean to bool, but in the end implemented a “filler” or “wrapper” header file with everything extra that Synth_Dexed needed but was missing – dexed_if_common.h – and stuck a typedef bool boolean; in there.

    So far, the only change I’ve had to make to the library itself is to add the following line into Synth_Dexed/src/dexed.h and then make sure the path for include files can find it during compilation:

    #include "dexed_if_common.h"

    I still haven’t quite got to grips with the whole cmake infrastructure required for building Raspberry Pi Pico projects, so the CMakeLists.txt files I’ve created are almost certainly sub-optimal at present. I still need to get my head around PRIVATE, PUBLIC, INTERFACE qualifiers for example and probably have more directories defined for INCLUDE paths than is strictly necessary.

    But it builds and everything I have so far can be found on GitHub. This includes a simple script to take the basic repository and add in CMSIS and Synth_Dexed and hack in that single change to dexed.h. This means that to build what I have so far requires the following:

    1. Install the Pico C/C++ SDK, toolchain and libraries (as I described here) including setting PICO_SDK_PATH to the location of the Pico SDK installation.
    2. Clone the picodexed repository.
    3. Run the getsubmod.sh script (only run this once for a fresh repository).
    4. Then build:
    kevin@ubuntu:~/src/picodexed$ cd build
    kevin@ubuntu:~/src/picodexed/build$ cmake ..
    kevin@ubuntu:~/src/picodexed/build$ make
    kevin@ubuntu:~/src/picodexed/build$

    Adding Audio

    The Pico has no in-built audio capability, but there is an audio library that can be found in the “pico_extras” repository that supports I2S audio interfaces or PWM audio output on GPIO pins, both implementing using the Pico’s PIO subsystem.

    There is an example in the “pico_playground” that shows how to output a sine wave and Pimoroni also have a sample mini-synth application for their I2S Pico Audio Pack.

    Links:

    In order to build for use with the pico_audio library, the pico_extras repository needs to be cloned into the source area and the location specified using PICO_EXTRAS_PATH.

    There is another cmake file that can then be copied into your own project area to include the library.

    cp $PICO_EXTRAS_PATH/external/pico_extras_import.cmake .

    And then it can be included in the project’s CMakeLists.txt file, pico_audio_i2s can be added to the list of link libraries, and a compilation definition added to define USE_AUDIO_I2S.

    include(pico_extras_import.cmake)

    target_link_libraries(picodexed PUBLIC synth_dexed pico_stdlib tinyusb_device tinyusb_board pico_audio_i2s)

    target_compile_definitions(picodexed PRIVATE
    USE_AUDIO_I2S=1
    )

    The actual examples for audio output are quite complicated and so far the documentation I’ve found for the pico_audio libraries is pretty minimal.

    The Pimoroni build has abstracted most of the functionality out into a separate audio.hpp file with the following interface:

    // init_audio initialises the audio library and returns a pointer
    // to an object representing the audio buffers.
    struct audio_buffer_pool *init_audio(uint32_t sample_rate, uint8_t pin_data, uint8_t pin_bclk, uint8_t pio_sm=0, uint8_t dma_ch=0);

    // update_buffer will fill the audio buffer with samples returned
    // by the provided callback function.
    void update_buffer(struct audio_buffer_pool *ap, buffer_callback cb);

    The callback function that is meant to provide samples to the audio subsystem has the following definition:

    int16_t getNextSample (void);

    Dexed has a getSamples function that can fill a buffer with the next set of samples in either float or int16_t format, but the callback function only returns a single sample at a time.

    Rather than attempt to make that fit (I did start off that way) I wrote my own “update_buffer” function to fill the Dexed buffer:

    void fillSampleBuffer(struct audio_buffer_pool *ap) {
    struct audio_buffer *buffer = take_audio_buffer(ap, true);
    int16_t *samples = (int16_t *) buffer->buffer->bytes;
    dexed.getSamples(samples, buffer->max_sample_count);
    buffer->sample_count = buffer->max_sample_count;
    give_audio_buffer(ap, buffer);
    }

    Once slight complication is that the Dexed getSamples function is a protected function. To interface into Dexed I’ve thus created a Dexed Adaptor based on the MiniDexed dexedadaptor. I don’t know how many of these functions will really need to be a wrapper around Dexed, but the capability is there if required.

    Typically with embedded audio applications, there is a regular “internal buffer out to audio hardware” layer that has to run at the sample rate and be reliably regular with its timing. This would usually be driven from a timer interrupt or similar. Then there will be a non-timing-critical part of code responsible for filling the buffer itself with samples to be played.

    The Pico’s audio library is quite different. Unfortunately, I can’t quite see with the combination of complex C buffer management code, use of the Pico’s DMA and use of the PIO hardware for the I2S protocol itself, quite what the timing expectations of the pico_audio library are and how they should be split up, but from what I can gather, the basic idea is as follows:

    • PIO is automatically pulling data from a set of buffers to send over I2S.
    • DMA is providing that data somehow at a rate independent of the CPU, but maybe driven by interrupts linked to data requests and the PIO somehow.
    • All I have to do is keep the buffer that can be accessed by the DMA fill of audio samples and magic “just happens”.

    So for the time being, I’m just calling my update audio equivalent function in a relatively fast loop.

    Unfortunately my first attempts all resulting in some kind of horrid growl. But it does turn on and off when the Dexed.keydown and Dexed.keyup calls are made so I was making progress!

    Things that eventually fixed that:

    • I updated the init_audio function to allow me to pass in the sample buffer size and chose a buffer size of 128 and then ensured this was set to the same in Dexed itself.
    • I saw the sine example was using a sample rate of 24000 for I2S so I used that sample rate here too.
    • The thing that eventually fixed everything was realising that Dexed is returning a mono stream of data but the default I2S interface is assuming a stereo set of samples. Setting the following in CMakeLists.txt told the I2S library to use mono:
    target_compile_definitions(picodexed PRIVATE
    PICO_AUDIO_I2S_MONO_OUTPUT=1
    PICO_AUDIO_I2S_MONO_INPUT=1
        USE_AUDIO_I2S=1
    )

    The sine library seems to get away with only defining MONO_OUTPUT, but I was getting compilation errors, so I set MONO_INPUT too, even though I’m not using I2S input.

    At this point I was getting a nice note playing via Dexed, so I grabbed the voice parameters for Brass 1 out of MiniDexed and dropped them into an array here and with a call to Dexed.loadVoiceParameters, I have a Raspberry Pi Pico playing the Dexed Brass 1 sound!

    In terms of sample rates and polyphony, well, things are a little basic! I can currently achieve the following:

    • 2-note polyphony at 44100.
    • 4-note polyphony at 24000.
    • 6-note polyphony, possibly if you squint at it and ask it really nicely, at 12000.

    So it isn’t going to win any prizes for quality or quantity, but this is a point that I feel it’s now worth pushing this post out and sticking the progress up on GitHub.

    Next, I want some simple MIDI control to act as a real synth. Other things on the “todo” list:

    • See what scope there is for optimisation to increase the polyphony ideally with 44.1kHz.
    • Specifically look into how it is currently doing the floating point maths.
    • Investigate the loading of the core to see how the performance is looking more generally.
    • Get some additional voices in there!

    Removing Dependency on CMSIS

    It turns out that only a few functions are required from the CMSIS/DSP area, so in order to examining them more closely I created my own version of arm_math.h with a corresponding arm_math.c containing the following functions:

    void arm_float_to_q15()
    void arm_fill_f32()
    void arm_sub_f32()
    void arm_scale_f32()
    void arm_offset_f32()
    void arm_mult_f32()
    void arm_biquad_cascade_df1_f32()
    void arm_biquad_cascade_df1_init_f32()

    with any additional definitions they require. Turns out it isn’t very many. The only thing left is to mirror how Synth_Dexed builds for the Teensy and ensure that the compressor isn’t included. This requires some conditional compilation in dexed.h, dexed.cpp and compressor.h.

    There doesn’t appear to be a single obvious way to spot a build for a Raspberry Pi Pico though, but there is a definition of RASPBERRYPI_PICO in <board/pico.h> so I used that.

    Find it on GitHub

    The current state of progress can be found on GitHub here: https://github.com/diyelectromusic/picodexed

    Closing Thoughts

    I first wanted to do this when I first installed the Pico SDK back in August 2022! Yes it really has taken me that long to even attempt it. Mostly because my C is still quite rusty; my C++ is very much “learn as you go”; I’ve not worked with a CMSIS ARM embedded system in detail before; and the cmake infrastructure for a Raspberry Pi Pico seems so very complicated for what it is.

    But you’ve got to start somewhere as they say, and I learn best from having a reason to do something. So even if this doesn’t amount to anything, it is finally making me learn about all these things in a useful way that will hopefully end up with something I’ll have some fun playing with.

    The big limitation might be floating point maths. But the Pico does have some built-in fast floating point routines. I don’t know if any of these are enabled and running at the moment. I suspect the CMSIS DSP code is probably doing a “soft” floating point, so that is something to look into. But if it is already using the faster routines, then it may well keep the polyphony so low that it isn’t particularly practical to use. To be continued…

    But once again, my renewed respect to all the above-mentioned people who have essentially already done most of the hard work for projects like this that allow someone like me to bumble along and join the various bits together!

    By the way, seeing as MiniDexed exists for the Raspberry Pi and MicroDexed exists for the Teensy, PicoDexed seemed the obvious choice!

    Kevin

    https://diyelectromusic.wordpress.com/2024/01/09/raspberry-pi-pico-synth_dexed/

    #CCSDK #dexed #picodexed #raspberryPiPico