home.social

Search

1000 results for “Matts_Bytes”

  1. Has Cristiano Ronaldo’s 1,000-goal target become an ‘obsession’? Roberto Martinez addresses fears Portuguese GOAT could stop passing to team-mates byteseu.com/1887621/ #AlNassrFC #CRonaldo #Portugal #RMartinez #SaudiProLeague #WorldCup

  2. Working on a maths puzzle game for #TweetTweetJam 10. Catch the prime factors of the target number, avoid being caught by other numbers. Start with a target of 2. Can you reach 100?

    471 bytes of #Pico8 ROM file. But 681 characters of source code. Trimming it down to 500 characters or less will be a challenge.

  3. 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

  4. 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

  5. Wikidata is a good service, Wikibase (on which Wikidata is built) is a better platform.

    I have spoken before about its potential to be added into the file-format registry ecosystem in a federated model.

    If we are to use it as a registry that can perhaps complement the pipelines going into PRONOM, e.g. in vendor’s digital preservation platforms such as the Rosetta Format Library, a Wikidata should be able to output different serializations of signature file for tools such as Siegfried, DROID or FIDO.

    And what about DROID?

    Conversion to DROID

    It’s not straightforward to say to a Wikibase/Wikidata Query Service, “output XML in the shape of a DROID signature file”, but it is straightforward to write a converter script.

    I had this very thought last week while presenting with colleagues at a File Format Workshop at iPRES in Ghent.

    It dawned on me that the conversion script would actually be simple thanks to a change in format to DROID whereby it can process all its own signatures, where previously it required DROID to pre-process them. It’s a long story, a more simple rendition is that DROID no longer requires DROID byte-code to record information about an identification pattern, and can instead store signatures in the attribute of a byte sequence element as-is, i.e. a PRONOM formatted regular expression from PRONOM itself, or Wikidata.

    This realization resulted in my writing a conversion script (it took just over a half-day) during some down-time on the train home this past weekend.

    The script is called wddroidy (after WD-40 🙄🥁) and can be found here.

    Results

    We can see using the skeleton suite from Richard Lehane’s Builder that we can positively identify files using the new signature file.

    Links can also be made to work with Wikidata identifiers by modifying the PUID URL pattern in the DROID configuration, e.g. to:

    http://wikidata.org/entity/%s

    The screenshot below shows where in the dialog that setting is:

    Reference signature file

    A reference signature file can be found in the wddroidy repository here. There are approximately 8119 file formats listed and 8195 file format signatures for those.

    NB. We know there are different issues with Wikidata including how to identify a “format” and the quality of the signatures. We capture some of these in a global repository: https://github.com/ffdev-info/wikidp-issues/issues

    DROID simplified format

    The real headline here might be how easy it was to create the output using the DROID simplified format.

    I have spoken about it briefly before but not in any detail.

    In-short DROID no longer uses its own byte-code encoding that included strange terms such as DefaultShift, Shift Byte, and SubSequence (instructions to DROID about how to perform Boyer Moore Horspool search). See below and note especially how the bytes are split in Shift Byte attributes and elements:

    <?xml version="1.0" encoding="UTF-8"?><FFSignatureFile xmlns="http://www.nationalarchives.gov.uk/pronom/SignatureFile" Version="1" DateCreated="2024-09-23T18:16:09+00:00">  <InternalSignatureCollection>    <InternalSignature ID="1" Specificity="Specific">      <ByteSequence Reference="BOFoffset">        <SubSequence MinFragLength="0" Position="1" SubSeqMaxOffset="0" SubSeqMinOffset="0">          <Sequence>255044462D312E34</Sequence>          <DefaultShift>9</DefaultShift>          <Shift Byte="25">8</Shift>          <Shift Byte="50">7</Shift>          <Shift Byte="44">6</Shift>          <Shift Byte="46">5</Shift>          <Shift Byte="2D">4</Shift>          <Shift Byte="31">3</Shift>          <Shift Byte="2E">2</Shift>          <Shift Byte="34">1</Shift>        </SubSequence>      </ByteSequence>    </InternalSignature>  </InternalSignatureCollection>  <FileFormatCollection>    <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="application/octet-stream">      <InternalSignatureID>1</InternalSignatureID>      <Extension>ext</Extension>    </FileFormat>  </FileFormatCollection></FFSignatureFile>

    The updated format was made possible via Matt Palmer via his ByteSeek work, and can now except a regularly encoded PRONOM formatted regular expression (regex) in an attribute in the ByteSequence element. See here for a signature file equivalent to the above:

    <?xml version="1.0" encoding="UTF-8"?><FFSignatureFile      xmlns="http://www.nationalarchives.gov.uk/pronom/SignatureFile" Version="1" DateCreated="2024-09-23T18:16:09+00:00">  <InternalSignatureCollection>    <InternalSignature ID="1" Specificity="Specific">      <ByteSequence Reference="BOFoffset" Sequence="255044462D312E34" Offset="0" />    </InternalSignature>  </InternalSignatureCollection>  <FileFormatCollection>    <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="application/octet-stream">      <InternalSignatureID>1</InternalSignatureID>      <Extension>ext</Extension>    </FileFormat>  </FileFormatCollection></FFSignatureFile>

    The format is much easier to read, and after a bit of time sitting with the DROID signature file format you realize it is fairly easy to output as well. I use some very rudimentary templates in wddroidy using  Python’s f-strings.

    It means other sources of PRONOM encoded signatures can output much simpler signature files and they can be used by DROID. I myself need to add it to the signature development utility – this would allow the utility to run standalone on anyone’s PC.

    One next step for this approach might be to confirm that it does work entirely as expected by extracting all of PRONOM’s signatures proper and performing a mapping to the simplified format – if we can match against all the skeleton files in the latest Builder release then we should be looking good!

    Priorities

    I am always reminded, but always forget about priorities! This is part of how DROID resolves a file format into a single identifier, e.g. where SVG can match XML, we often want the more specific format returned, and so a priority is used to prioritize that one over the other, resulting in a single unambiguous identification for the DROID user. It manifests in the signature file as:

    <FileFormat ID="634" MIMEType="image/svg+xml" Name="Scalable Vector Graphics"PUID="fmt/91"Version="1.0">   <InternalSignatureID>24</InternalSignatureID>   <Extension>svg</Extension>   <HasPriorityOverFileFormatID>638</HasPriorityOverFileFormatID> </FileFormat> More work needs to be done with Wikidata to understand if priorities can be properly applied to a DROID signature file. They are not written into the reference signature file above.

    Using the results

    Using the results can be done for two things:

    1. (Probably) There are a greater number of patterns in the Wikidata output than in PRONOM. If you have a file that remains unidentified, you can try the reference file for clues as to what it may be. I’d only use caution and investigate the exact byte sequence used for a match and understand its properties. I’d also check that the mapping also looks accurate, I’ve tried one or two runs using the identifier and it looks good, but there may still be mistakes.
    2. For improving the quality of the sources in Wikidata. As you can see from the Skeleton suite there are a lot of gaps. We a) have a rough idea what these are, and b) know the identification doesn’t work via Wikidata. Why is that? Is the signature in Wikidata simply not good enough? Are patterns missing? Is there another error or issue we can help with given our expertise in file format identification?

    Hacking wddroidy

    You can hack wddroidy. Currently it allows you to limit the number of results returned, and also modify the ISO language code used by the tool. You can see this in the command line arguments:

    python wddroidy.py --helpusage: wddroidy [-h] [--definitions DEFINITIONS] [--wdqs] [--lang LANG] [--limit LIMIT] [--output OUTPUT] [--output-date] [--endpoint ENDPOINT]create a DROID compatible signature file from Wikidataoptions: -h, --help show this help message and exit --definitions DEFINITIONS   use a local definitions file, e.g. from Siegfried --wdqs, -w live results from Wikidata --lang LANG, -l LANG change Wikidata language results --limit LIMIT, -n LIMIT   limit the number of resukts --output OUTPUT, -o OUTPUT   filename to output to --output-date, -t output a default file with the current timestamp --endpoint ENDPOINT, -url ENDPOINT   url of the WDQSfor more information visit https://github.com/ross-spencer/wddroidy

    The actual SPARQL query used can be manually edited in the src folder. E.g. you can limit the query by format or family or classification. I provide some more inspiration in the Siegfried Wiki.

    Let me know if it’s useful!

    This is really just a quick hack and it needs a lot more testing to improve the quality of the output. Most can be dealt with on the Wikidata side I am sure, but some might need to be done in the tool. If it’s useful, reach out, and let’s discuss what can be changed or how it can be used in your work.

    Data quality

    It will quickly become apparent the data quality isn’t what it is with PRONOM and that is why a curated and authoritative service such as PRONOM is always going to be needed. As mentioned in previous talks, this can in theory be complemented with downstream data in federated databases. This might mean curating Wikidata better using some of the tools available, or curating data into a Wikibase (the platfom Wikidata is built upon). Both options bring different benefits and advantages such as creating a bigger tent of signature developers on Wikidata, or, another example, more expressive signatures being made available via federated Wikibases.

    And a word on Wikiba.se

    A reminder too, that setting up a Wikibase can take some effort (I was once running three at the same time 😬) but a service called https://wikiba.se/ exists. wikiba.se could form an excellent scratch pad to begin thinking about mapping PRONOM like data to a Wikibase and also begin solving some of the other issues around mapping container signatures and outputting those in a way that is compatible for DROID. Let me know if you give it a whirl, or want to collab on any of that.

    Otherwise, thanks in advance! And enjoy wddroidy!

    https://exponentialdecay.co.uk/blog/making-droid-work-with-wikidata/

    #Code #Coding #digipres #DigitalPreservation #DROID #FileFormat #FileFormats #OpenData #PRONOM #siegfried #SoftwareDevelopment #wikidata

  6. Wikidata is a good service, Wikibase (on which Wikidata is built) is a better platform.

    I have spoken before about its potential to be added into the file-format registry ecosystem in a federated model.

    If we are to use it as a registry that can perhaps complement the pipelines going into PRONOM, e.g. in vendor’s digital preservation platforms such as the Rosetta Format Library, a Wikidata should be able to output different serializations of signature file for tools such as Siegfried, DROID or FIDO.

    And what about DROID?

    Conversion to DROID

    It’s not straightforward to say to a Wikibase/Wikidata Query Service, “output XML in the shape of a DROID signature file”, but it is straightforward to write a converter script.

    I had this very thought last week while presenting with colleagues at a File Format Workshop at iPRES in Ghent.

    It dawned on me that the conversion script would actually be simple thanks to a change in format to DROID whereby it can process all its own signatures, where previously it required DROID to pre-process them. It’s a long story, a more simple rendition is that DROID no longer requires DROID byte-code to record information about an identification pattern, and can instead store signatures in the attribute of a byte sequence element as-is, i.e. a PRONOM formatted regular expression from PRONOM itself, or Wikidata.

    This realization resulted in my writing a conversion script (it took just over a half-day) during some down-time on the train home this past weekend.

    The script is called wddroidy (after WD-40 🙄🥁) and can be found here.

    Results

    We can see using the skeleton suite from Richard Lehane’s Builder that we can positively identify files using the new signature file.

    Links can also be made to work with Wikidata identifiers by modifying the PUID URL pattern in the DROID configuration, e.g. to:

    http://wikidata.org/entity/%s

    The screenshot below shows where in the dialog that setting is:

    Reference signature file

    A reference signature file can be found in the wddroidy repository here. There are approximately 8119 file formats listed and 8195 file format signatures for those.

    NB. We know there are different issues with Wikidata including how to identify a “format” and the quality of the signatures. We capture some of these in a global repository: https://github.com/ffdev-info/wikidp-issues/issues

    DROID simplified format

    The real headline here might be how easy it was to create the output using the DROID simplified format.

    I have spoken about it briefly before but not in any detail.

    In-short DROID no longer uses its own byte-code encoding that included strange terms such as DefaultShift, Shift Byte, and SubSequence (instructions to DROID about how to perform Boyer Moore Horspool search). See below and note especially how the bytes are split in Shift Byte attributes and elements:

    <?xml version="1.0" encoding="UTF-8"?><FFSignatureFile xmlns="http://www.nationalarchives.gov.uk/pronom/SignatureFile" Version="1" DateCreated="2024-09-23T18:16:09+00:00">  <InternalSignatureCollection>    <InternalSignature ID="1" Specificity="Specific">      <ByteSequence Reference="BOFoffset">        <SubSequence MinFragLength="0" Position="1" SubSeqMaxOffset="0" SubSeqMinOffset="0">          <Sequence>255044462D312E34</Sequence>          <DefaultShift>9</DefaultShift>          <Shift Byte="25">8</Shift>          <Shift Byte="50">7</Shift>          <Shift Byte="44">6</Shift>          <Shift Byte="46">5</Shift>          <Shift Byte="2D">4</Shift>          <Shift Byte="31">3</Shift>          <Shift Byte="2E">2</Shift>          <Shift Byte="34">1</Shift>        </SubSequence>      </ByteSequence>    </InternalSignature>  </InternalSignatureCollection>  <FileFormatCollection>    <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="application/octet-stream">      <InternalSignatureID>1</InternalSignatureID>      <Extension>ext</Extension>    </FileFormat>  </FileFormatCollection></FFSignatureFile>

    The updated format was made possible via Matt Palmer via his ByteSeek work, and can now except a regularly encoded PRONOM formatted regular expression (regex) in an attribute in the ByteSequence element. See here for a signature file equivalent to the above:

    <?xml version="1.0" encoding="UTF-8"?><FFSignatureFile      xmlns="http://www.nationalarchives.gov.uk/pronom/SignatureFile" Version="1" DateCreated="2024-09-23T18:16:09+00:00">  <InternalSignatureCollection>    <InternalSignature ID="1" Specificity="Specific">      <ByteSequence Reference="BOFoffset" Sequence="255044462D312E34" Offset="0" />    </InternalSignature>  </InternalSignatureCollection>  <FileFormatCollection>    <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="application/octet-stream">      <InternalSignatureID>1</InternalSignatureID>      <Extension>ext</Extension>    </FileFormat>  </FileFormatCollection></FFSignatureFile>

    The format is much easier to read, and after a bit of time sitting with the DROID signature file format you realize it is fairly easy to output as well. I use some very rudimentary templates in wddroidy using  Python’s f-strings.

    It means other sources of PRONOM encoded signatures can output much simpler signature files and they can be used by DROID. I myself need to add it to the signature development utility – this would allow the utility to run standalone on anyone’s PC.

    One next step for this approach might be to confirm that it does work entirely as expected by extracting all of PRONOM’s signatures proper and performing a mapping to the simplified format – if we can match against all the skeleton files in the latest Builder release then we should be looking good!

    Priorities

    I am always reminded, but always forget about priorities! This is part of how DROID resolves a file format into a single identifier, e.g. where SVG can match XML, we often want the more specific format returned, and so a priority is used to prioritize that one over the other, resulting in a single unambiguous identification for the DROID user. It manifests in the signature file as:

    <FileFormat ID="634" MIMEType="image/svg+xml" Name="Scalable Vector Graphics"PUID="fmt/91"Version="1.0">   <InternalSignatureID>24</InternalSignatureID>   <Extension>svg</Extension>   <HasPriorityOverFileFormatID>638</HasPriorityOverFileFormatID> </FileFormat> More work needs to be done with Wikidata to understand if priorities can be properly applied to a DROID signature file. They are not written into the reference signature file above.

    Using the results

    Using the results can be done for two things:

    1. (Probably) There are a greater number of patterns in the Wikidata output than in PRONOM. If you have a file that remains unidentified, you can try the reference file for clues as to what it may be. I’d only use caution and investigate the exact byte sequence used for a match and understand its properties. I’d also check that the mapping also looks accurate, I’ve tried one or two runs using the identifier and it looks good, but there may still be mistakes.
    2. For improving the quality of the sources in Wikidata. As you can see from the Skeleton suite there are a lot of gaps. We a) have a rough idea what these are, and b) know the identification doesn’t work via Wikidata. Why is that? Is the signature in Wikidata simply not good enough? Are patterns missing? Is there another error or issue we can help with given our expertise in file format identification?

    Hacking wddroidy

    You can hack wddroidy. Currently it allows you to limit the number of results returned, and also modify the ISO language code used by the tool. You can see this in the command line arguments:

    python wddroidy.py --helpusage: wddroidy [-h] [--definitions DEFINITIONS] [--wdqs] [--lang LANG] [--limit LIMIT] [--output OUTPUT] [--output-date] [--endpoint ENDPOINT]create a DROID compatible signature file from Wikidataoptions: -h, --help show this help message and exit --definitions DEFINITIONS   use a local definitions file, e.g. from Siegfried --wdqs, -w live results from Wikidata --lang LANG, -l LANG change Wikidata language results --limit LIMIT, -n LIMIT   limit the number of resukts --output OUTPUT, -o OUTPUT   filename to output to --output-date, -t output a default file with the current timestamp --endpoint ENDPOINT, -url ENDPOINT   url of the WDQSfor more information visit https://github.com/ross-spencer/wddroidy

    The actual SPARQL query used can be manually edited in the src folder. E.g. you can limit the query by format or family or classification. I provide some more inspiration in the Siegfried Wiki.

    Let me know if it’s useful!

    This is really just a quick hack and it needs a lot more testing to improve the quality of the output. Most can be dealt with on the Wikidata side I am sure, but some might need to be done in the tool. If it’s useful, reach out, and let’s discuss what can be changed or how it can be used in your work.

    Data quality

    It will quickly become apparent the data quality isn’t what it is with PRONOM and that is why a curated and authoritative service such as PRONOM is always going to be needed. As mentioned in previous talks, this can in theory be complemented with downstream data in federated databases. This might mean curating Wikidata better using some of the tools available, or curating data into a Wikibase (the platfom Wikidata is built upon). Both options bring different benefits and advantages such as creating a bigger tent of signature developers on Wikidata, or, another example, more expressive signatures being made available via federated Wikibases.

    And a word on Wikiba.se

    A reminder too, that setting up a Wikibase can take some effort (I was once running three at the same time 😬) but a service called https://wikiba.se/ exists. wikiba.se could form an excellent scratch pad to begin thinking about mapping PRONOM like data to a Wikibase and also begin solving some of the other issues around mapping container signatures and outputting those in a way that is compatible for DROID. Let me know if you give it a whirl, or want to collab on any of that.

    Otherwise, thanks in advance! And enjoy wddroidy!

    https://exponentialdecay.co.uk/blog/making-droid-work-with-wikidata/

    #Code #Coding #digipres #DigitalPreservation #DROID #FileFormat #FileFormats #OpenData #PRONOM #siegfried #SoftwareDevelopment #wikidata

  7. Wikidata is a good service, Wikibase (on which Wikidata is built) is a better platform.

    I have spoken before about its potential to be added into the file-format registry ecosystem in a federated model.

    If we are to use it as a registry that can perhaps complement the pipelines going into PRONOM, e.g. in vendor’s digital preservation platforms such as the Rosetta Format Library, a Wikidata should be able to output different serializations of signature file for tools such as Siegfried, DROID or FIDO.

    And what about DROID?

    Conversion to DROID

    It’s not straightforward to say to a Wikibase/Wikidata Query Service, “output XML in the shape of a DROID signature file”, but it is straightforward to write a converter script.

    I had this very thought last week while presenting with colleagues at a File Format Workshop at iPRES in Ghent.

    It dawned on me that the conversion script would actually be simple thanks to a change in format to DROID whereby it can process all its own signatures, where previously it required DROID to pre-process them. It’s a long story, a more simple rendition is that DROID no longer requires DROID byte-code to record information about an identification pattern, and can instead store signatures in the attribute of a byte sequence element as-is, i.e. a PRONOM formatted regular expression from PRONOM itself, or Wikidata.

    This realization resulted in my writing a conversion script (it took just over a half-day) during some down-time on the train home this past weekend.

    The script is called wddroidy (after WD-40 🙄🥁) and can be found here.

    Results

    We can see using the skeleton suite from Richard Lehane’s Builder that we can positively identify files using the new signature file.

    Links can also be made to work with Wikidata identifiers by modifying the PUID URL pattern in the DROID configuration, e.g. to:

    http://wikidata.org/entity/%s

    The screenshot below shows where in the dialog that setting is:

    Reference signature file

    A reference signature file can be found in the wddroidy repository here. There are approximately 8119 file formats listed and 8195 file format signatures for those.

    NB. We know there are different issues with Wikidata including how to identify a “format” and the quality of the signatures. We capture some of these in a global repository: https://github.com/ffdev-info/wikidp-issues/issues

    DROID simplified format

    The real headline here might be how easy it was to create the output using the DROID simplified format.

    I have spoken about it briefly before but not in any detail.

    In-short DROID no longer uses its own byte-code encoding that included strange terms such as DefaultShift, Shift Byte, and SubSequence (instructions to DROID about how to perform Boyer Moore Horspool search). See below and note especially how the bytes are split in Shift Byte attributes and elements:

    <?xml version="1.0" encoding="UTF-8"?><FFSignatureFile xmlns="http://www.nationalarchives.gov.uk/pronom/SignatureFile" Version="1" DateCreated="2024-09-23T18:16:09+00:00">  <InternalSignatureCollection>    <InternalSignature ID="1" Specificity="Specific">      <ByteSequence Reference="BOFoffset">        <SubSequence MinFragLength="0" Position="1" SubSeqMaxOffset="0" SubSeqMinOffset="0">          <Sequence>255044462D312E34</Sequence>          <DefaultShift>9</DefaultShift>          <Shift Byte="25">8</Shift>          <Shift Byte="50">7</Shift>          <Shift Byte="44">6</Shift>          <Shift Byte="46">5</Shift>          <Shift Byte="2D">4</Shift>          <Shift Byte="31">3</Shift>          <Shift Byte="2E">2</Shift>          <Shift Byte="34">1</Shift>        </SubSequence>      </ByteSequence>    </InternalSignature>  </InternalSignatureCollection>  <FileFormatCollection>    <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="application/octet-stream">      <InternalSignatureID>1</InternalSignatureID>      <Extension>ext</Extension>    </FileFormat>  </FileFormatCollection></FFSignatureFile>

    The updated format was made possible via Matt Palmer via his ByteSeek work, and can now except a regularly encoded PRONOM formatted regular expression (regex) in an attribute in the ByteSequence element. See here for a signature file equivalent to the above:

    <?xml version="1.0" encoding="UTF-8"?><FFSignatureFile      xmlns="http://www.nationalarchives.gov.uk/pronom/SignatureFile" Version="1" DateCreated="2024-09-23T18:16:09+00:00">  <InternalSignatureCollection>    <InternalSignature ID="1" Specificity="Specific">      <ByteSequence Reference="BOFoffset" Sequence="255044462D312E34" Offset="0" />    </InternalSignature>  </InternalSignatureCollection>  <FileFormatCollection>    <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="application/octet-stream">      <InternalSignatureID>1</InternalSignatureID>      <Extension>ext</Extension>    </FileFormat>  </FileFormatCollection></FFSignatureFile>

    The format is much easier to read, and after a bit of time sitting with the DROID signature file format you realize it is fairly easy to output as well. I use some very rudimentary templates in wddroidy using  Python’s f-strings.

    It means other sources of PRONOM encoded signatures can output much simpler signature files and they can be used by DROID. I myself need to add it to the signature development utility – this would allow the utility to run standalone on anyone’s PC.

    One next step for this approach might be to confirm that it does work entirely as expected by extracting all of PRONOM’s signatures proper and performing a mapping to the simplified format – if we can match against all the skeleton files in the latest Builder release then we should be looking good!

    Priorities

    I am always reminded, but always forget about priorities! This is part of how DROID resolves a file format into a single identifier, e.g. where SVG can match XML, we often want the more specific format returned, and so a priority is used to prioritize that one over the other, resulting in a single unambiguous identification for the DROID user. It manifests in the signature file as:

    <FileFormat ID="634" MIMEType="image/svg+xml" Name="Scalable Vector Graphics"PUID="fmt/91"Version="1.0">   <InternalSignatureID>24</InternalSignatureID>   <Extension>svg</Extension>   <HasPriorityOverFileFormatID>638</HasPriorityOverFileFormatID> </FileFormat> More work needs to be done with Wikidata to understand if priorities can be properly applied to a DROID signature file. They are not written into the reference signature file above.

    Using the results

    Using the results can be done for two things:

    1. (Probably) There are a greater number of patterns in the Wikidata output than in PRONOM. If you have a file that remains unidentified, you can try the reference file for clues as to what it may be. I’d only use caution and investigate the exact byte sequence used for a match and understand its properties. I’d also check that the mapping also looks accurate, I’ve tried one or two runs using the identifier and it looks good, but there may still be mistakes.
    2. For improving the quality of the sources in Wikidata. As you can see from the Skeleton suite there are a lot of gaps. We a) have a rough idea what these are, and b) know the identification doesn’t work via Wikidata. Why is that? Is the signature in Wikidata simply not good enough? Are patterns missing? Is there another error or issue we can help with given our expertise in file format identification?

    Hacking wddroidy

    You can hack wddroidy. Currently it allows you to limit the number of results returned, and also modify the ISO language code used by the tool. You can see this in the command line arguments:

    python wddroidy.py --helpusage: wddroidy [-h] [--definitions DEFINITIONS] [--wdqs] [--lang LANG] [--limit LIMIT] [--output OUTPUT] [--output-date] [--endpoint ENDPOINT]create a DROID compatible signature file from Wikidataoptions: -h, --help show this help message and exit --definitions DEFINITIONS   use a local definitions file, e.g. from Siegfried --wdqs, -w live results from Wikidata --lang LANG, -l LANG change Wikidata language results --limit LIMIT, -n LIMIT   limit the number of resukts --output OUTPUT, -o OUTPUT   filename to output to --output-date, -t output a default file with the current timestamp --endpoint ENDPOINT, -url ENDPOINT   url of the WDQSfor more information visit https://github.com/ross-spencer/wddroidy

    The actual SPARQL query used can be manually edited in the src folder. E.g. you can limit the query by format or family or classification. I provide some more inspiration in the Siegfried Wiki.

    Let me know if it’s useful!

    This is really just a quick hack and it needs a lot more testing to improve the quality of the output. Most can be dealt with on the Wikidata side I am sure, but some might need to be done in the tool. If it’s useful, reach out, and let’s discuss what can be changed or how it can be used in your work.

    Data quality

    It will quickly become apparent the data quality isn’t what it is with PRONOM and that is why a curated and authoritative service such as PRONOM is always going to be needed. As mentioned in previous talks, this can in theory be complemented with downstream data in federated databases. This might mean curating Wikidata better using some of the tools available, or curating data into a Wikibase (the platfom Wikidata is built upon). Both options bring different benefits and advantages such as creating a bigger tent of signature developers on Wikidata, or, another example, more expressive signatures being made available via federated Wikibases.

    And a word on Wikiba.se

    A reminder too, that setting up a Wikibase can take some effort (I was once running three at the same time 😬) but a service called https://wikiba.se/ exists. wikiba.se could form an excellent scratch pad to begin thinking about mapping PRONOM like data to a Wikibase and also begin solving some of the other issues around mapping container signatures and outputting those in a way that is compatible for DROID. Let me know if you give it a whirl, or want to collab on any of that.

    Otherwise, thanks in advance! And enjoy wddroidy!

    https://exponentialdecay.co.uk/blog/making-droid-work-with-wikidata/

    #Code #Coding #digipres #DigitalPreservation #DROID #FileFormat #FileFormats #OpenData #PRONOM #siegfried #SoftwareDevelopment #wikidata

  8. Reform UK’s Matt Goodwin says he’s waiting for Noel Gallagher to ‘endorse him’. The Gorton and Denton candidate says the singer has ‘on occasions said something approvingly’ of the party. #manchestereveningnews #reform #politics byteseu.com/1821212/ #GreatBritain #UnitedKingdom

  9. 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

  10. 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

  11. 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

  12. 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

    • DOE Awards Six HALEU Fuel Contracts
    • Urenco Boosts US Enrichment Capacity
    • US Navy Releases RFI for Nuclear Power at Seven Sites
    • US Gov’t Wants to Re-start More Nuclear Reactors
    • Blue Energy Secures $45M to Build Underwater SMRs for Data Center Power
    • Zap Energy Lands $130M in New Capital
    • General Atomics Releases FUSE ~ Open Source Fusion Software

    DOE Awards Six HALEU Fuel Contracts for Fuel Advanced Reactors

    • High-assay low-enriched uranium (HALEU – 5-20% U235) is for use by advanced reactors
    • The contracts will allow companies to compete for work to provide deconversion services

    The U.S. Department of Energy (DOE) awarded contracts to six companies to spur the buildout of a U.S. supply chain for fuels for advanced nuclear reactors. Many advanced reactors will require high-assay low-enriched uranium (HALEU) to achieve smaller designs, longer operating cycles, and increased efficiencies over current technologies.

    These contracts will allow selected companies to bid on work for deconversion services, a critical component of the HALEU supply chain. Deconversion transforms the gaseous form of enriched uranium (uranium hexafluoride aka UF6) into either uranium oxide or uranium metal forms for fabrication into solid fuel elements intended for specific reactors.

    The United States currently lacks commercial HALEU enrichment and deconversion services to support the deployment of advanced reactors. DOE also plans to award contracts for enrichment services to support the full breadth of the HALEU supply chain.

    Progress towards commercialization of advanced reactors has been delayed by the lack of access to reliable supplies of HALEU. For instance, TerraPower, which is developing a 345 MW sodium cooled advanced reactor, funded in part with a cost shared contract with DOE, has postponed its startup date by two years to 2030 due to delays in getting HALEU fuel.

    Selected companies awarded HALEU contracts include: BWXT, Centrus, Framatome, GE Vernova, Orano, and Westinghouse. In terms of market shares, the six companies will each receive a minimum contract of $2 million, with up to $800 million available for deconversion services over the 10-year contract period.

    Cumulatively, the firms are expected to deliver 290 tonnes of fuel (638,000 lbs) during the decade long period of performance.  DOE said in 2023 it has immediate needs for 22 metric tonnes of HALEU for first fuel loads of the advanced reactors it has funded including TerraPower and X-Energy.

    The firms bring different capabilities to the program.

    • Centrus through its enrichment operation began producing HALEU fuel last year under a DOE contract in the form of uranium hexafluoride (UF6). To be used in a reactor, the gaseous form of the fuel must be converted to either uranium oxide or uranium metal fuel and fabricated as solid nuclear fuel elements to meet the needs of specific customers.
    • Orano has the capability to carry out the deconversion process from gas to oxide. The firm announced plans in September to build a multi-billion dollar uranium enrichment plant in Oak Ridge, TN. Once built and in production Orano says the plant will produce commercial quantities of low enriched uranium up to 5% U235 and some with higher enrichment levels up to 8% U235. The firm has not indicated, at least for now, that it has any plans to produce enriched uranium for use in HALEU fuel, e.g., 9-20% U235.

    The other four firms – BWX Technologies, Framatome, GE Vernova, and Westinghouse – are positioned as fuel fabrication firms with a wide range of capabilities.

    • BWXT inked a contract in August 2023 with DOE’s NNSA to produce HALEU from “scrap” materials” to produce 2 metric tonnes (4,400 lbs) of HALEU.
    • Framatome last May announced a deal with TerraPower to produce HALEU at its Richland, WA, fuel fabrication plant. It will convert uranium oxide into uranium metal  for eventual use in TerraPower’s 345 MW sodium cooled advanced reactor which the firm is building in Wyoming.
    • In 2022 Global Fuel Americas (GE Verona) announced a $200 million deal with TerraPower to fabricate HALEU uranium metal fuel for the firm’s advanced reactor.  In July 2023
    • Westinghouse announced the UK government’s nuclear fuel fund had awarded the firm $11 million to upgrade its Springfields nuclear fuel production plant in the UK to produce HALEU as well as to produce accident tolerant fuels for light water reactors.

    More information on the HALEU Availability Program can be found at HALEU Availability Program | Department of Energy.

    & & &

    Urenco Boosts US Enrichment Capacity

    Urenco has installed the first new centrifuges of an expansion project in the United States, which is on track to be deliver additional capacity next year. The centrifuges are installed in an existing centrifuge hall at the company’s enrichment site in Eunice, New Mexico.

    The project will provide an approximately 15% increase in enrichment capacity at the site, providing an additional 700,000 SWU per year. Urenco USA is on schedule to begin producing enriched uranium from newly installed centrifuges in 2025.

    The site’s expansion project is the first to be delivered under Urenco’s capacity expansion program, and will strengthen the nuclear fuel supply chain both in the U.S. and globally. In total, under Urenco’s current expansion, an additional 1.8 million SWU will be delivered across three projects, including two others at Urenco’s sites in Germany and the Netherlands.

    Urenco’s Eunice, NM, site is the only commercial enrichment facility in the U.S. In 2023, its annual production was 4.4 million SWU. The site has the physical space and the NRC  license to further expand its annual production up to 10 million SWU.  The firm has not announced plans to produce HALEU fuels at levels of enrichment for between 9-20% U235.

    Urenco CEO, Boris Schucht, said, ““This is only the latest step. We are intending to further expand our capacity in the U.S., subject to market needs, as the strong momentum in the nuclear industry continues.”

    & & &

    US Navy Releases RFI for Nuclear Power at Seven Sites

    The Department of the Navy has released a Request for Information (RFI) to industry, seeking to explore concepts for the development of nuclear power facilities aimed at enhancing energy security at seven Navy and Marine Corps installations in the United States.

    The RFI’s goal is to collect input for privately-funded concepts that would ensure continuous operational capability of Navy installations in the event of grid power outages, adversary attacks, extreme weather events, or other disruptions. At the same time it will improve utility and regional grid capacity, flexibility, affordability and resilience to support continuity of services.

    According to the RFI installation reliance / tactical readiness are a top priorities for the Navy “to achieve full resilience and operational continuity.”

    “We welcome input from developers, utilities, and consortia on innovative approaches that may include, but are not limited to, Shore Based Nuclear Technologies and other advanced technologies.”

    “Contractors who own or operate nuclear power sites on Navy or USMC installations would be responsible for the possession, storage and management of nuclear and spent fuel, as well as complying with the Nuclear Regulatory Commission licensing requirements.”

    Locations Of Interest

    • Virginia

    – Naval Air Station (NAS) Oceana (VA)
    – Naval Support Activity (NSA) South Potomac (includes Naval Support Facility (NSF)
    – Dahlgren, VA and
    – Naval Weapons Station (NWS) Yorktown (VA)
    – Marine Corps Base (MCB) Quantico (VA)

    • Maryland

    – NAS Patuxent River (MD)
    – NSF Indian Head, MD)

    North Carolina

    – Marine Corps Air Station (MCAS) Cherry Point (NC)
    – MCB Camp Lejeune (NC)

    Nuclear reactors, including small modular reactors (SMRs) and microreactors, have the potential to provide defense installations with resilient energy for several years, even in the face of physical or cyberattacks, extreme weather, pandemics, biothreats, and other emerging challenges that could disrupt commercial energy networks.

    This announcement builds on recent Department of Defense initiatives in this area, including an announcement earlier this year by the Assistant Secretary of the Army for Installations, Energy, and Environment regarding the release of an RFI last June to support a deployment program for advanced reactors to power multiple Army sites across the United States.

    In related congressional testimony, the Secretary of the Army, Christine Wormuth, stated: “We are certainly very focused on energy and resilience at our installations and being able to operate in a more resilient manner. We are interested in the potential of micro reactors.”

    This effort is also complemented by other initiatives, such as the Department of the Air Force’s microreactor pathfinder project at Eielson AFB and the Office of the Secretary of Defense Strategic Capabilities Office Project Pele, a transportable microreactor prototype that recently broke ground at the Department of Energy’s Idaho National Laboratory.

    & & &

    US Gov’t Wants to Re-start More Nuclear Reactors

    Reuters reports that White House climate adviser Ali Zaidi said last week the Biden administration is working on plans to bring additional shut down nuclear power reactors back online to help meet soaring demand for emissions-free electricity needed to power AI related data centers.  (Image: Microsoft Bing Image Creator)

    He referenced two projects including the planned recommissioning of Holtec’s Palisades nuclear plant in Michigan and the potential restart of a unit at Constellation Energy’s Three Mile Island plant in Pennsylvania.

    Asked if additional shuttered plants could be restarted, Zaidi said: “We’re working on it in a very concrete way. There are two that I can think of.”

    Zaidi declined to identify the two power plants or provide further details about the effort.

    One of the likely restarts is the Duane Arnold plant in Iowa which faces expensive repairs. On August 10, 2020, the plant cooling towers were damaged during a severe storm and repairs were assessed to be uneconomical, as the plant had was scheduled for decommissioning in October 2020. The operator and majority owner is NextEra Energy Resources (70%). The Central Iowa Power Cooperative owns 20% and the Corn Belt Power Cooperative owns 10%.

    In Iowa NextERA CEO John Ketchum, said that the firm will consider reopening the plant to meet the demand for power from data centers. Ketchum told Bloomberg on June 12th he had inquiries from potential data center customers interested in the 600 MW of power that could be provided by the reactor. Google and other data center users are building computing facilities in Iowa and will need power to run them.

    “I would consider it, if it could be done safely and on budget.”

    Another possible candidate is Indian Point Energy Center – located north of New York city in Buchanan, NY. It is now owned for the purpose of decommissioning by Holtec International. It isn’t clear how much work has been done so far or whether the decommissioning work has gone past the point that would prevent the reactor from being restarted.

    Holtec said on its website that Indian Point’s fuel has been removed from the reactor vessel and placed in the spent fuel pool to cool. An April 2024 technical briefing shows significant work to decommission the twin reactors is underway.

    Indian Point’s reactors, which each generated 1,000 MW of power, were shut down as a result of intense political pressure from New York State’s then governor Andrew Cuomo due to the extraordinary influence of green groups who’s support was essential to his re-election. The reactor’s capacity has since been partially replaced by natural gas plants.

    Three-prong Nuclear Strategy

    Speaking at the Reuters IMPACT conference in New York, Zaidi said repowering existing dormant nuclear plants was part of a three-pronged strategy of President Joe Biden’s administration to bring more nuclear power online to fight climate change and boost production.

    The other two prongs include development of small modular reactors (SMRs) for certain applications, and continuing development of next generation, advanced nuclear reactors.

    Biden has called for a tripling of U.S. nuclear power capacity to respond to energy demand that is accelerating in part due to expansion of power hungry technologies like artificial intelligence and cloud computing.

    Last week, the Biden administration said it closed a $1.52 billion loan to restart  the Palisades nuclear plant in Michigan, which would take at least two years to re-open.

    Constellation and Microsoft signed a power deal last month but the Redmond, WA, computer giant will only pay for power once the reactor is running. Constellation hopes it will receive government support based on its expected request to the DOE Loan Program Office for a similar level of financial support that was given to Holtec at Palisades.

    Zaidi told the conference that the U.S. Navy had requested information to build SMRs on a half dozen bases. “SMR is a technology that is not a decades-away play. It’s one that companies in the United States are looking to deploy in this decade.”

    & & &

    Blue Energy Secures $45M to Build Underwater SMRs for Data Center Power

    Blue Energy, a nuclear power plant company, emerged from stealth mode with a $45 million Series A funding. The investors were co-led by Engine Ventures and At One Ventures, with investment from Angular Ventures, Tamarack Global, Propeller Ventures, Starlight Ventures, and Nucleation Capital.  The firm is a spinoff from MIT.

    Blue Energy  introduced its modular nuclear power plant that can be centrally manufactured in existing shipyards. Shipyard manufacturing reduces the cost and build time of deploying nuclear power safely, making nuclear power economically competitive with fossil fuels and renewables. The funding will be used to advance Blue Energy’s core engineering work and site development, and secure additional partners.

    Blue Energy said it is reactor agnostic, partnering with reactor vendors and designing modular power plants to house them. By partnering with reactor vendors, Blue Energy can leverage existing regulatory progress to further accelerate the time to market for their modular nuclear power plant.

    Blue Energy says it will use mature light water reactor designs and the latest passive safety advancements to create a plant that is 100% walk-away safe. The reactor building is submerged in a pool that provides additional physical protection and backup cooling. The firm says customers can start with one reactor and add additional reactors over time.

    Multi-unit underwater reactor complex – Image: Blue Energy

    The reactor and passive safety functions are fully isolated from the rest of the power plant, allowing the turbine hall and other non-nuclear systems to be manufactured in non-nuclear shipyards.

    Candidate SMR Scale Reactors

    The prospects of sinking an entire nuclear reactor into the ocean or other large body of water immediately brings to mind the question of size. Small modular reactors, with less than 300 MW of generating capability, are plausible candidates for this approach to avoiding the need for a containment structure to protect against airplane crashes or drone attacks.

    In the US only NuScale, as a LWR type SMR, has completed the NRC licensing process for its 50 MW SMR. A 77 MW SMR upgrade is expected to be approved by the NRC.

    Two other LWR type designs are the GE-Hitachi BWRX300 and the Holtect SMR300. Both designs are in pre-licensing status with the NRC and are seen as completing that effort by the end of this decade or early 2030s. Westinghouse recently announced its AP300, a scaled down version of its PWR type AP1000, but work on licensing the design has just gotten underway.

    In the UK the Rolls Royce 470 MW, a PWR,  is making its way through the UK Office of Nuclear Regulation (ONR) generic design assessment process (GDA), which can take up to four years to complete.  The firm is hoping for near term funding for its mid-size PWR from the UK government and has been pushing the for it as being the ‘home town’ team in the UK to gain an edge in the competition for SMR funding sponsored by the government.

    For its part, the UK agency responsible for making the funding award has dithered repeatedly in terms of choosing one or more winners. The ministry named four short list contenders last month eliminating NuScale. In the next stage of the procurement process bidders will be invited to enter negotiations with GBN for funding and contract awards. EDF withdrew its SMR entry from the competition citing the need for further design work.

    China is building its ACP100 small modular reactor demonstration project at the Changjiang site on China’s island province of Hainan. According to World Nuclear News, it has been under development since 2010. The 125 MWe ACP100 integrated PWR’s preliminary design was completed in 2014. In 2016, the design became the first SMR to pass a safety review by the International Atomic Energy Agency. So far China National Nuclear Corporation has not announced plans to export the design.

    In South Korea the SMART100 small modular reactor design has been granted standard design approval by South Korea’s Nuclear Safety and Security Commission.

    The Korea Atomic Energy Research Institute (KAERI), Korea Hydro & Nuclear Power (KHNP) and Saudi Arabia’s King Abdullah City for Atomic and Renewable Energy (KA-CARE) applied for standard design approval of the SMART100 in December 2019. The Nuclear Safety and Security Commission (NSSC) began its review of the application in August 2021. The NSSC announced it has now granted standard design approval for the reactor.

    SMART is a 330 MWt pressurized water reactor with integral steam generators and advanced safety features. The unit is designed for electricity generation (up to 100 MWe) as well as thermal applications, such as seawater desalination, with a 60-year design life and three-year refueling cycle.

    Business Case

    The business case promoted by the firm is that there is no required upfront capital investment from customers. Blue Energy finances, builds, owns, and operates its power plants. Customers buy new nuclear megawatts through risk-managed power purchase agreements.

    The firm posted on its website that it projects it can build its reactors in two years and deliver power from them at $5/kw.

    “Blue Energy is addressing the biggest obstacles to wide adoption of nuclear power: cost and build time. Using the traditional approach, it takes thousands of workers several years to construct nuclear power plants on site. We’ve designed a modular plant that can be fully prefabricated centrally in shipyards and transported to its operating location,” said Jake Jurewicz, CEO of Blue Energy.

    About Blue Energy

    Blue Energy was founded by Jake Jurewicz, based in Edinburgh, and Matt Slotkin, located in the New York city metro area.

    Jurewicz previously co-founded Entropy Power and served on the corporate strategy team at Exelon Corporation. He holds a Masters in Nuclear Science & Engineering, and a dual Bachelors in Physics and Nuclear Science & Engineering from MIT, where he did his thesis on shipyard construction of offshore nuclear power plants.

    Slotkin previously co-founded Vowel, an AI/video company acquired by Zapier, and led engineering in the Systemized Intelligence Lab at Bridgewater Associates. He graduated from Yale University in 2011 with a degree in economics and spent a year studying at Tsinghua University. He is fluent in Chinese.

    Blue Energy’s leadership team also includes Chief Commercial Officer and Corporate Counsel Tom O’Neill, the former General Counsel and Vice President of Licensing at Exelon. CJ Fong, formerly Chief of Staff to NRC Commissioner Wright and NRC staff for 23 years, joins Blue Energy as VP of Regulatory. Charlie Bowser, who engineered, built and commissioned the NETPower pilot plant, joins as SVP of Engineering.

    Blue Energy said in a press statement it has signed a letter of intent with an un-named datacenter and cloud provider to serve as the offtaker for the first plant. The firmis hiring and lists open positions for offices in Edinburgh, Scotland, and Bethesda, MD. The locations indicate a possible interest in both US and European markets.

    & & &

    Zap Energy Lands $130M in New Capital

    • Demo power plant system begins operations and aims for first milestone. Total funding now at $330M
    • Zap Energy is building a test platform for liquid metals and other key fusion energy technologies

    Zap’s $130 million Series D was led by Soros Fund Management LLC, with participation by new investors that include BAM Elevate, Emerson Collective, Leitmotif, Mizuho Financial Group, Plynth Energy and Xplor Ventures.

    Current investors participating in the new round include Addition, Breakthrough Energy Ventures, Chevron Technology Ventures, DCVC, Energy Impact Partners, Lowercarbon Capital and Shell Ventures.

    The new funding will be used to continue parallel development of both plasma R&D and systems-level plant engineering and integration, including the next generation in the company’s FuZE device series and a cutting-edge pulsed power capacitor bank. The firm is located in San Diego, CA.

    The team is now attempting to reach a milestone outlined as part of the U.S. Department of Energy’s (DOE) Milestone-Based Fusion Development Program, and hopes to do so by the end of the year.

    “The race for fusion commercialization has historically been thought of as a triathlon: science, then engineering, then commercialization,” says Zap CEO Benj Conway.

    “But at Zap, we’re attempting to swim, cycle and run at the same time – such a parallel approach is key to delivering commercial fusion on a timescale that matters. Century is a vital part of the engineering leg and we’re quickly coming up to speed.”

    According the company supplied information Zap Energy is building a low-cost, compact and scalable fusion energy platform that confines and compresses plasma without magnetic coils or high-power lasers. Zap’s sheared-flow-stabilized Z-pinch technology provides fusion economics and requires orders of magnitude less capital than conventional approaches.

    About Century

    Century is the world’s first 100-kilowatt-scale repetitive Z-pinch system. Its goal is to integrate and test three major aspects of Zap’s power design: repetitive pulsed power supplies, plasma-facing circulating liquid metal walls, and technology for mitigating electrode damage.

    Century is designed to simulate plant-like operation by firing high-voltage pulses of power every ten seconds in a steady sequence for more than two hours (>1,000 pulses at 0.1 Hz). (Zap Energy image)

    It is circulating 70 kilograms of hot liquid bismuth in its initial configuration and well over a ton in its final configuration. Air-cooled heat exchangers will remove the intense plasma heat absorbed by the liquid metal. The firm is testing critical strategies for mitigating electrode damage due to extreme heat and neutron flux.

    “Zap’s fusion approach is pulsed, so ultimately it will run like an internal combustion engine with cylinders firing all day long to produce steady energy output,” explains Thompson.

    “As you do that you also generate large neutron flux and heat loads in the system over time, which is exactly the energy output that you want, but requires unique engineering solutions. Century will test a lot of our assumptions and define the best path toward our first plant.”

    Next year the platform will gradually ramp to 100 kilowatts of average input power. For comparison, the 100 kilowatts that drives Century is roughly equal to taking the average power draw of 75 U.S. homes and concentrating it into a chamber the size of a hot water heater.

    Century, with a central stack about the size of a double-decker bus, is close to the eventual size of a single Zap Energy module that will produce 50 megawatts of electricity. Future power plants will have multiple modules.

    Founded in 2o17 the firm’s collaborators include the University of Washington, Lawrence Livermore National Laboratory, UC Berkeley, Los Alamos National Laboratory, UC San Diego, University of Nevada, Reno, TransAlta A

    & & &

    General Atomics Releases FUSE ~ Open Source Fusion Software

    • A Powerful Tool to Fast-Track the Development of Fusion Power Plants
    • New Open-Source Software Aims to Drive Fusion Innovation for a Clean Energy Future

    This month San Diego, CA, General Atomics (GA) took a big step towards achieving this goal by releasing the Fusion Synthesis Engine (FUSE)—a state-of-the-art, open-source software designed to help build fusion power plants.

    Created by GA, the software is now accessible to anyone under the Apache 2.0 license, guaranteeing its free usage, modification, and commercialization. Written in “Julia”, a popular programming language, FUSE combines key elements needed to develop fusion power—such as plasma physics, engineering, and cost analysis—into one easy-to-use system.

    GA developers explained that other researchers can easily install and run the program on their own systems, enabling more effective collaboration on fusion energy projects. This approach helps reduce costs and makes it easier to achieve the goal of fusion energy.

    By integrating various complex models, researchers can generate simulations that are both faster and more accurate, including how a fusion plant operates in both steady and dynamic conditions.

    “Releasing FUSE is a bold and exciting step that offers a powerful tool to the entire fusion community,” said Wayne Solomon, vice president of Magnetic Fusion Energy for the General Atomics Energy Group.

    “This platform encourages teamwork and new ideas while fulfilling GA’s commitment to openness and progress. By making FUSE available to everyone, we’re not just advancing our own developments—we’re giving others the ability to build on it, with the goal of accelerating discoveries throughout the entire field.”

    “FUSE could have a big impact on the future of fusion energy,” said Orso Meneghini, a theory and computational science manager for the General Atomics Energy Group.

    “One of its strengths is that it uses machine learning to speed up simulations, making it useful for improving plant designs and reducing uncertainties. Overall, Fuse’s flexibility and generality make it an important tool for advancing research in this critical area of energy research.”

    GA also operates the DIII-D National Fusion Facility, a Department of Energy user facility that houses the only operating fusion reactor (tokamak) in the U.S., where scientists collaborate to find the best solutions for bringing fusion power to market.

    For more information and full documentation about FUSE, visit https://fuse.help

    ###

    https://neutronbytes.com/2024/10/12/doe-awards-six-haleu-fuel-contracts-for-advanced-reactors/

    #deconversion #haleu #nuclearPower #uraniumEnrichment

  13. Day 22 of Advent of Compiler Optimisations!

    Comparing a string_view against "ABCDEFG" should call memcmp, right? Watch what Clang actually generates — no function call at all, just a handful of inline instructions using some rather cunning tricks. How does it compare 7 bytes so efficiently when they don't fit in a single register?

    Read more: xania.org/202512/22-memory-cun
    Watch: youtu.be/kXmqwJoaapg

    #AoCO2025

  14. The Pico bit-banged #Transputer link works! At a staggeringly-fast 96 bytes/second! If I remove the oversampling receiver it could go 16x that, and I could run the timer interrupt 10x faster - but that would max the CPU out. This is good enough for a proof of concept release. PIO later. Still got to do another link implementation over a USB-CDC interface, and route logging over a separate one, then wire all this into the emulator. Looking tight to release by the solstice :(

  15. Finally found an excuse to use an XOF in production code. Importing a trust store of certificates, I need to give each one an alias, but for maximum portability, I need to keep the aliases relatively short. Thus, SHAKE-128 output to 15 bytes and then base64-encoded into a 20 character string. Should be unique enough right? #crypto #keccak #shake #x509

  16. I listened to 1st few episodes of #Hest (ivanish.ca/hest-podcast/), knowing nada about it. Super intrigued. Many early morning dream thoughts, barely grasped. Like: how far down the stack to go? Starting with text and words, as you must to get working prototypes, means they inescapably push deform into the resultant creations. To avoid, to reform, what might visual assembly be like? Visual bytes, bits? All things far beyond my ken, but drawing me on they are.
    @spiralganglion

  17. I listened to 1st few episodes of #Hest (ivanish.ca/hest-podcast/), knowing nada about it. Super intrigued. Many early morning dream thoughts, barely grasped. Like: how far down the stack to go? Starting with text and words, as you must to get working prototypes, means they inescapably push deform into the resultant creations. To avoid, to reform, what might visual assembly be like? Visual bytes, bits? All things far beyond my ken, but drawing me on they are.
    @spiralganglion

  18. I listened to 1st few episodes of #Hest (ivanish.ca/hest-podcast/), knowing nada about it. Super intrigued. Many early morning dream thoughts, barely grasped. Like: how far down the stack to go? Starting with text and words, as you must to get working prototypes, means they inescapably push deform into the resultant creations. To avoid, to reform, what might visual assembly be like? Visual bytes, bits? All things far beyond my ken, but drawing me on they are.
    @spiralganglion

  19. I listened to 1st few episodes of #Hest (ivanish.ca/hest-podcast/), knowing nada about it. Super intrigued. Many early morning dream thoughts, barely grasped. Like: how far down the stack to go? Starting with text and words, as you must to get working prototypes, means they inescapably push deform into the resultant creations. To avoid, to reform, what might visual assembly be like? Visual bytes, bits? All things far beyond my ken, but drawing me on they are.
    @spiralganglion

  20. I listened to 1st few episodes of #Hest (ivanish.ca/hest-podcast/), knowing nada about it. Super intrigued. Many early morning dream thoughts, barely grasped. Like: how far down the stack to go? Starting with text and words, as you must to get working prototypes, means they inescapably push deform into the resultant creations. To avoid, to reform, what might visual assembly be like? Visual bytes, bits? All things far beyond my ken, but drawing me on they are.
    @spiralganglion

  21. Really looking forward to the #mastodon PR being merged that will purge old avatars and headers:

    $ tootctl media usage
    Attachments: 14.9 GB (8.46 MB local)
    Custom emoji: 225 MB (0 Bytes local)
    Preview cards: 585 MB
    Avatars: 6.94 GB (63.6 KB local)
    Headers: 14.5 GB (324 KB local)
    Backups: 0 Bytes
    Imports: 0 Bytes
    Settings: 0 Bytes

    github.com/mastodon/mastodon/i

    This is a ~2 week old almost single-user instance.

    #mastoadmin #tootctl #storage

  22. @byteseu this is literally every #Cryptocurrency, only most don't have the value of the piece of paper behind them. #QuietPartOutLoud

  23. Texas A&M System Goes Nuclear

    • Texas A&M System Goes Nuclear
    • X-Energy Raises $700 Million to Develop its HTGR
    • India Pushes Ahead With Plans To Open Up Nuclear Sector, Deploy More Reactors
    • Vietnam Opens Talks With Potential Partners for Nuclear Plants
    • UK Announces Planning Reforms To Speed Up Nuclear Projects

    Texas A&M System Goes Nuclear

    •  Four Advanced Reactor Companies Could Fastrack Deployment of Latest Nuclear Reactors on System Land

    Texas A&M Chancellor John Sharp announced he has offered land near the Texas A&M University campus (Bryan- College Station, TX) to four nuclear reactor companies so they can build the latest small modular reactors (SMRs).

    Until now, reactor manufacturers – along with the most powerful names in data centers for the major IT platforms – have not been able to find a suitable place to build clusters of nuclear reactors that can supply the power needed for artificial intelligence endeavors, data centers and other projects. (image: Google Gemini>

    The sprawling university campus is located about 100 miles northeast of Austin, TX. Texas is a hotbed of entrepreneurial interest in nuclear energy. The Texas Nuclear Alliance has garnered memberships from a raft of nuclear energy developers, suppliers, and trade groups.

    An inaugural conference of its members last November laid out an agenda to promote the growth of a nuclear technology industry in the the state. A ley objective is building new reactors to stabilize the over-stretched electric grid which during a storm in 2022 revealed Texas’ overreliance on intermittent energy sources.

    Four Nuclear Projects

    CEOs of four nuclear companies – Kairos Power, Natura Resources, Terrestrial Energy and Aalo Atomics have agreed to work with the University to bring reactors to Texas A&M-RELLIS, a 2,400-acre technology and innovation site as part of a project labeled as “The Energy Proving Ground.”

    At the site, the companies will work toward bringing commercial-ready technologies to the university using the project to test the latest nuclear SMR prototypes.

    Construction of the first reactors could start within five years. Once completed, power generated by each reactor at the proving ground could supply power to the Electric Reliability Council of Texas aka ERCOT. Texas is more or less isolated from the big electric transmission grids in both the eastern and western U.S.

    Mike Laufer, co-founder and CEO of Kairos Power, said his company could bring one or more commercial deployments to the site. He added that the surging demand for clean electricity has brought nuclear energy to the forefront of the national discussion as a vital source of reliable, carbon-free energy.

    “We are excited about the momentum for new nuclear deployment at Texas A&M-RELLIS and its potential to support U.S. energy security and continued economic growth. We look forward to collaborating with the Texas A&M System to advance Kairos Power’s clean energy mission and play a new role in developing the nation’s future nuclear workforce.”

    Douglass Robison, the founder and CEO of Natura Resources, said that the Texas A&M System has been an integral partner over the past five years, collaborating with the company to develop the Natura MSR-1 demonstration system.

    “We are thrilled to continue this partnership with the Texas A&M System to deploy our commercial system, the Natura MSR-100, on the Texas A&M-RELLIS campus,” Robison said. “We plan to showcase how our technology can address the energy needs of Texas and the nation.”

    Simon Irish, CEO of Terrestrial Energy, said his hope for the partnership is to develop Integral Molten Salt Reactor, or IMSR, technology at the site in Bryan.

    “Our partnership with Texas A&M at its RELLIS campus is an important strategic relationship, which showcases the commercial potential of our small modular power plant and its advanced nuclear technology,” Irish said.

    Matt Loszak, co-founder and CEO, Aalo Atomics, said his company could build up to six Aalo Pods at the site.

    “This collaboration is a pivotal step for Aalo as it provides us with a platform to demonstrate the potential of our factory mass-manufactured nuclear technology to deliver reliable, clean energy that will ultimately power the next generation of data centers and AI infrastructure.”

    Early Site Permit

    Texas A&M System officials have worked to streamline the regulatory process on the front end to allow the four companies to quickly get their reactors operational. The System has begun the application process with the Nuclear Regulatory Commission for an Early Site Permit (ESP).

    Licensing of each individual reactor will be the the technical and financial responsibility of each vendor. None of the four firms participating in the Texas A&M project have yet reached the stage of being ready to submit a license application for this site. Of the four firms Kairos Power has  the most experience with the NRC licensing process based on its Hermes 2 demonstration project in Tennessee.

    The Texas A&M ESP application will be for the potential development of commercial electrical and thermal power generation facilities. The proposed site at Texas A&M-RELLIS is projected to accommodate multiple SMRs with a combined electrical output of more than one gigawatt.

    It will take multiple units of each of the four participating reactor designs to achieve this level of output assuming all four are licensed by the NRC to build reactors at the Texas site and are able to convince investors to fund their path to full commercialization.

    Texas A&M as a Nuclear R&D Proving Ground

    This project at Texas A&M-RELLIS is part of the Texas A&M System’s broader commitment to advancing nuclear research, education and energy production. System officials believe the Energy Proving Ground will position the Texas A&M System as a leader in sustainable, advanced energy solutions to meet the growing energy demands of the world.

    Joe Elabd, vice chancellor for research at the Texas A&M System, said the Energy Proving Ground will have an extraordinary effect on the future of energy delivery in the U.S.

    “The agreements that the Texas A&M System has with Kairos, Natura, Terrestrial and Aalo are going to change the energy landscape for the whole country,” Elabd said. “The Energy Proving Ground will allow these companies to safely test their SMRs and set the stage for deploying small nuclear reactors across the country.”

    “Plain and simple: the United States needs more power,” Sharp said. “And nowhere in the country, other than Texas, is anyone willing to step up and build the power plants we need. Thanks to the leadership of Gov. Greg Abbott and others in Texas state government, Texas A&M System stands ready to step up and do what is necessary for the country to thrive.”

    About The Texas A&M University System

    The Texas A&M University System is one of the largest systems of higher education in the nation, with a budget of $7.3 billion. Through a statewide network of 11 universities, a comprehensive health science center, eight state agencies, Texas A&M-Fort Worth and Texas A&M-RELLIS, the Texas A&M System educates more than 157,000 students and makes more than 21 million additional educational contacts through service and outreach programs each year. System-wide, research and development expenditures exceed $1.5 billion and help drive the state’s economy.

    & & &

    X-Energy Raises $700 Million to Develop its HTGR

    X-Energy Reactor Company LLC, announced the closing of its Series C-1 financing round of $700 million.  New investors include Segra Capital Management, Jane Street, Ares Management funds, Emerson Collective, and other investors not named in the press statement.

    They join the previously announced round anchored by the Amazon.com, Inc. Climate Pledge Fund, an affiliate of Citadel Founder, and CEO Ken Griffin, affiliates of Ares Management Corporation, NGP, and the University of Michigan.

    Latham & Watkins LLP acted as legal advisor to X-energy, and Moelis & Company acted as exclusive financial advisor and placement agent.

    Proceeds from the funds will support the completion of X-energy’s reactor design and licensing as well as the first phase of its TRISO-X fuel fabrication facility in Oak Ridge, TN. Additionally, the funding will support future, as yet unannounced, projects that will use X- energy’s Xe-100 advanced small modular nuclear reactors (SMRs).

    X-energy is developing its initial Xe-100 plant at Dow’s UCC Seadrift Operations manufacturing site on the Texas Gulf Coast. Supported by the U.S. Department of Energy’s Advanced Reactor Demonstration Program, the project is expected to be the first grid- scale advanced nuclear reactor deployed to serve an industrial site in the U.S.  providing the site with zero-carbon emissions power and high-temperature steam.

    Amazon and X-energy are collaborating to bring more than five gigawatts of new power projects online across the U.S. by 2039. The efforts will help meet growing energy demands in key locations through direct project investments to fund development, licensing, and construction, as well as long-term power purchase agreements to help power Amazon operations. Further, X-energy and Amazon plan to establish and standardize a deployment and financing model to develop projects in partnership with infrastructure and utility partners.

    Under an agreement with Energy Northwest, Amazon will have the right to purchase electricity from the first project (four modules), which is expected to generate 320 MW. Energy Northwest has the option to further build out the site by adding up to eight additional modules (640 MWs) resulting in a total project generating capacity of up to 960 MWs. This additional power will be available to Amazon and northwest utilities to power homes and businesses.

    X-energy’s Xe-100 advanced SMR uses TRISO fuel. Each reactor unit is engineered to provide 80 MW of electricity and is optimized in multi-unit plants ranging from 320 MW to 960 MW. The firm claims the modular design is road-shippable and intended to drive geographic scalability, reduce construction timelines, and create more predictable and manageable construction costs.

    & & &

    India Pushes Ahead With Plans To Open Up Nuclear Sector, Deploy More Reactors

    (NucNet) India wants to amend its liability law to boost foreign and private investments in its civilian nuclear energy sector and also aims to set up a Nuclear Energy Mission with an outlay $2.3 billion to operate at least five indigenously developed 220 MW PHWR small modular reactors (SMRs) by 2033.

    Finance and corporate affairs minister Nirmala Sitharaman said the development of at least 100 GW of nuclear energy by 2047 is essential for the country’s energy transition efforts. India currently has about 6.9 GW of nuclear capacity in operation with about 5.4 GW under construction.

    The announcements were part of Sitharaman’s budget and came ahead of prime minister Narendra Modi’s planned US visit from 12-14 February.

    The proposal to amend the liability laws came ahead of PM Modi’s proposed visit to the US and in the wake of the US action last month removing restrictions on Bhabha Atomic Research Center, Indira Gandhi Atomic Research Centre and Indian Rare Earths.

    Strict liabilities under India’s Civil Liability for Nuclear Damage Act, 2010, have severely hampered implementation of an India-US deal that would allow participation in India new-build projects of US reactor technology companies such as General Electric and Westinghouse.

    US ‘Finalizing Steps’ To Remove Barriers

    The US said last month, before Donald Trump was sworn in as president, that it was finalizing steps to remove barriers to civil nuclear cooperation with India as it seeks to capitalize on the Asian nation’s ambition plans for reactor development and deployment. Washington and New Delhi have been discussing the supply of US nuclear reactors to India since the mid-2000s.

    But a longstanding obstacle has been the need to bring Indian liability rules in line with global norms, which require the costs of any accident to be channeled to the operator rather than the maker of a nuclear power plant.

    The removal of US restrictions on civil nuclear cooperation is expected to bring a number of significant benefits for India’s domestic nuclear energy industry, including access to advanced technology, the transfer of knowledge and skills, greater investment in infrastructure and the strengthening of regulatory frameworks.

    Last month, Nuclear Power Corporation of India Ltd (NPCIL) issued a request for proposals for the deployment of Bharat small modular reactors, opening the doors of the nuclear sector to Indian private companies for the first time. Until now, only state-owned NPCIL has been allowed to build and deploy commercial nuclear power plants in India.

    “For an active partnership with the private sector towards this goal, amendments to the Atomic Energy Act and the Civil Liability for Nuclear Damage Act will be taken up,” Sitharaman said.

    An amendment of the Atomic Energy Act may see NPCIL lose its position as the sole operator of nuclear reactors in the country, making space for private sector operators.

    Plans For 18 More Reactors By 2032

    The proposal would allow private companies to build reactors under NPCIL’s control and supervision. On completion, plants would be operated by NPCIL under a long-term operation and maintenance agreement.

    The deployment of the Bharat SMR – Bharat being the Hindi for “India” – forms part of the country’s plans under ‘Viksit Bharat’, which is Hindi for “Developed India”.

    The details of the Bharat nuclear plant’s development remain unclear, but Sitharaman said in July the initiative would involve a joint venture between state power company National Thermal Power Corporation and state power generation equipment manufacturer Bharat Heavy Electricals Limited.

    NPCIL said recently that India plans to add 18 more nuclear reactors to its national energy mix by 2031-32, bringing the total nuclear power capacity of the country to 22.4 GW.

    & & &

    Vietnam Opens Talks With Potential Partners for Nuclear Plants

    Reuters reports that Vietnam to talk soon with foreign partners on building the first two nuclear power plants

    According to the wire service, State utility EVN and oil and gas firm PetroVietnam have been assigned as the investors for the first two plants, the government said in a statement. It will discuss the projects with partners that include Russia, Japan, South Korea, France and the United States, according to state media.

    Vietnam aims to complete the construction of the two news plants by the end of 2030. More likely the 2030 date will be when the construction of the plants breaks ground.

    Vietnam is a regional manufacturing hub. It is seeking to boost electricity supplies to support its fast-growing economy. It also wants nuclear power to transform its aluminum mining sector from exporting raw ore to having an aluminum finished goods industry to support exports.

    Vietnam shelved its nuclear plans in 2016 in the wake of the Fukushima nuclear disaster in Japan and due to budget constraints. Also, government officials said the country was not ready to manage nuclear energy projects. The proposed plants, with a combined capacity of 4 GW were planned to be built by Russia’s Rosatom and Japan Atomic Power Co in the central province of Ninh Thuan.

    & & &

    UK Announces Planning Reforms To Speed Up Nuclear Projects

    (WNN) The UK government announced plans to reform planning requirements and regulatory rules as part of measures to streamline the process of constructing new nuclear power plants in England and Wales, including small modular reactors.

    The reforms include allowing new plants to be built anywhere across England and Wales, not just in the eight existing nuclear sites specified in current planning rules. However, the government said there will “continue to be robust criteria for nuclear reactor locations, including restrictions near densely-populated areas and military activity, alongside community engagement and high environmental standards.”

    The reforms also include removing the expiration date on nuclear planning rules so that projects do not get “timed out” and industry can plan for the long-term.

    For the first time, in addition to large-scale nuclear power plants, new nuclear technologies such as SMRs and advanced modular reactors will be included, “providing flexibility to co-locate them with energy-intensive industrial sites such as AI data centers.

    In addition, a Nuclear Regulatory Taskforce will be established to ensure nuclear regulation incentivizes investment to deliver new projects more quickly and cost efficiently, while upholding high safety and security standards.

    The taskforce, which will report directly to the prime minister, will speed up the approval of new reactor designs and streamline how developers engage with regulators. This taskforce will better align the UK with international partners so reactor designs approved abroad could be approved more quickly, minimizing expensive changes.

    “This country hasn’t built a nuclear power station in decades – we’ve been let down, and left behind,” said Prime Minister Keir Starmer.

    “Our energy security has been hostage to Putin for too long, with British prices skyrocketing at his whims. I’m putting an end to it – changing the rules to back the builders of this nation, and saying no to the blockers who have strangled our chances of cheaper energy, growth and jobs for far too long.”

    Starmer did not commit any new funds to either of the two massive EDF 1650 MW EPRs now under construction in the UK. Separately, Great British Nuclear (GBN) continues to progress the SMR competition, with contract negotiations currently under way. A final decision on which design or designs will be included in the UK’s official SMR program is expected to be taken in the coming months. The UK nuclear industry, particularly Rolls-Royce, has expressed impatience with GBN’s glacial pace of decision making related to the SMR competition.

    Reforms Welcomed by the UK Nuclear Industry

    Last month, the government announced plans to limit legal challenges to major infrastructure projects – including nuclear power plants – to just one hearing in court instead of the current three hearings. It has also set out reforms to end the block and delay to building homes and infrastructure from current environmental obligations.

    The announced planning reforms were welcomed by industry, with Tom Greatrex, chief executive of the Nuclear Industry Association, saying: “This is the prime minister’s strongest signal yet that new nuclear is critical to the growth and clean power mission. A more streamlined planning system will give certainty to investors, the supply chain and communities, and will enable us to get on with building new nuclear plants on more sites and at pace for a cleaner, more secure power system.

    # # #

    #nuclearEnergy #texasAmUniversity

  24. first update! #YOYOZO on #Playdate

    202311281554
    - add: toast for exceeding previous best
    - add: online scores now also retrieved every 20 games
    - fix: online score numeric id call to action
    - change: explosions now affected by collision speed

    more features; 305 bytes smaller!

  25. “Did you have any orange juice today?”*…

    … if so, it’s less and less likely that it was from Florida.

    The canonical articles on the Florida orange juice industry are John McPhee’s two-parter from The New Yorker from the 1960s. But that was then.

    Alex Sammon has picked up the baton, with an article on the brutal, unrelenting decline of that business…

    Quiet fell over the room, which was neither full nor very loud to begin with, and the 2026 Florida Citrus Show began.

    “It should be a great day,” began the event’s first speaker. “Rain should hold off today, even though we definitely need more rain.” No one laughed.

    There was no need to say that things were bad. Everyone knew it. The mood wasn’t sour—citrus farmers could handle sour. It was something else. Postapocalyptic. Florida is in the midst of its worst drought in 25 years, but the dry spell actually ranked far down on the list of challenges these bedraggled growers were facing.

    In 2003, the mighty Florida orange industry produced 242 million boxes of fruit, with 90 pounds of oranges per box, most of which went on to become orange juice. Now, not even 25 years later, the United States Department of Agriculture was forecasting a pitiful 12 million boxes of oranges, the least in more than 100 years, the worst year since last. A decline of more than 95 percent.

    And everyone knew, more or less, that even that figure was not happening. “Twelve million? I would doubt it,” Matt Joyner, CEO of Florida Citrus Mutual, the state’s largest trade group, told me. There was chatter that even 11 million might be out of reach. Could the total end up being less than that, just seven figures? In Florida, the citrus capital of the world, you are today more likely to see the oranges printed on the state’s 18 million license plates than a box of actual fruit.

    Rick Dantzler, chief operating officer of the Citrus Research and Development Foundation, took the podium. He was blunt. “It’s been a dumpster fire of a year,” he said.

    On the list of immediate problems: the implementation of tariffs and retaliatory tariffs, then the government shutdown, then a stunning, historic freeze, days long, at the end of January and early February, that besieged the fragile orange trees.

    And yet those, too, were just footnotes to the even larger problem. Already, Florida had lost about three-quarters of its citrus growers. The last of them, these spent survivors, these hangers-on, had trudged to the Citrus Show to talk about the real problem, which was the disease.

    In 2005, Florida first got signs of a new affliction in its groves called citrus greening disease. It also has a Chinese name, Huanglongbing, or HLB, because it came from China, where oranges also came from in the first place.

    Citrus greening disease is caused by a bacterial infection that is delivered by the gnawing of the Asian citrus psyllid. (It’s now believed the psyllid first turned up near the Port of Miami in 1998.) The flea-sized psyllid bites the leaves and transmits the disease, which slowly chokes out the tree’s vascular system from the inside, taking years to finally show itself. By the time a tree is displaying symptoms—three to five years, in most cases—it’s too late…

    Read on for an explanation of how this catastrophe has materialized and for a consideration of what it means for Central Florida (and the other major supplier, Brazil, which is also suffering).

    Who Killed the Florida Orange?” from @alexsammon.bsky.social in @slate.com.

    Other comestible news from Florida: “A deadly bacteria is creeping up the Atlantic Coast. How worried should you be?

    * Harold Brodkey, First Love and Other Sorrows: Stories

    ###

    As we contemplate the consequences of climate change and contagion, we might consider an alternative to orange juice on this, National Raisin Day. But while raisins are richly nutricious, they are not so strong on Vitamin C, so we’ll have to keep looking…

    source

    #citrus #CitrusGreeningDisease #climateChange #concentrate #culture #Florida #FloridaOranges #history #NationalRaisinDay #orangeJuice #orangeJuiceConcentrate #oranges #politics #raisins #Science
  26. The Cost of the Clean Exit: When the System Protects the Liar

    3,572 words, 19 minutes read time.

    Grant Miller sat in the clinical, blue-light glow of his home office, the low hum of three synchronized monitors serving as the only soundtrack to the wreckage of a decade. On the center screen, a spreadsheet acted as a cold, digital autopsy of ten years of his life. As a systems architect, Grant didn’t have “hobbies”; he had projects that required infrastructure, precision, and an uncompromising adherence to the truth. When he first walked into the local “Learn to Skate” rink with a camera bag and a laptop, he wasn’t looking for a plaque or a pat on the back. He saw a system that was broken—a chaotic, paper-trail operation where registrations were lost in overstuffed filing cabinets and the club’s “digital presence” was a joke. Over the next ten years, Grant didn’t just volunteer; he engineered. He built a fortress. By the time the dust settled, he had clocked over $65,000 in professional volunteer hours based on federal labor standards, and his private servers groaned under the weight of 100,000 high-resolution images captured on $10,000 of his own professional gear. He was the invisible backbone of the club, the man who turned a disorganized mess into a streamlined, encrypted powerhouse that parents actually trusted with their data and their children’s milestones.

    The sheer volume of the work was staggering when viewed through the lens of objective data. We are talking about ten years of Saturday mornings spent in sub-zero rinks, ten years of weeknights spent editing thousands of RAW files to ensure every kid in the program had a hero shot that made them feel like an Olympian. Grant didn’t just take pictures; he managed the club’s identity. He built the website, secured the databases, and handled the tech support that the board was too technologically illiterate to understand. In the world of non-profits, a man like Grant is a unicorn—a high-level professional providing enterprise-grade solutions for the price of a lukewarm coffee. But the danger of being the man who builds the system is that you eventually become the only person who knows how the gears actually turn, and in a landscape ruled by small-town egos, that technical mastery is often viewed not as an asset, but as a threat to the established order of those who prefer to rule in the dark.

    In the world of small-town sports politics, efficiency is a direct threat to those who thrive on opacity and “good old boy” networks. For years, the club’s board elections had been tainted by what the locals quietly called “funny business.” It was a shadowy, manual practice where Sarah, the Skating Director, and her inner circle would physically call members over the phone, pressuring them to cast votes for her hand-picked candidates in direct violation of the club’s own bylaws. It was a system built on social engineering and intimidation, a way to ensure that the “inner circle” remained unchallenged and that the director’s personal fiefdom remained intact. Sarah wore her high-level credentials with the national Figure Skating Association like a medieval mace, using her title to silence dissent and maintain a status quo that favored her cronies over the actual growth of the program. She didn’t want a fair vote; she wanted a coronation every cycle.

    To kill this corruption and bring the club into the twenty-first century, Grant had implemented a third-party, industry-standard voting system years prior. He didn’t build the software—he was too smart for that—but he selected a platform that offered absolute integrity, two-factor authentication, and a verifiable audit trail. It was a secure tool designed to ensure that every member had a private, un-pressured voice, effectively stripping Sarah of her ability to manipulate the outcomes through late-night phone calls and locker-room arm-twisting. Ironically, that very system is still used by the club today, a testament to its reliability and Grant’s foresight in building something that could withstand the very rot he was trying to excise. But the moment the digital tally finally reflected a result that Sarah couldn’t control, the “funny business” shifted from the voting booth to a direct, surgical strike on Grant Miller’s reputation.

    The transition from “valued volunteer” to “enemy of the state” happened with the flick of a bureaucratic switch. When the election results didn’t go Sarah’s way, she didn’t look in the mirror; she looked for a scapegoat. Using her high-level influence and her direct line to the national Figure Skating Association, she filed an informal grievance that was as calculated as it was malicious. She accused Grant of “digital manipulation,” claiming that he had used his administrative access to rig the election results through the third-party software. It was a character-assassinating smear designed to hit a technical professional where it hurts most: his integrity. She banked on the Association’s fundamental ignorance of technology, knowing that to a group of aging administrators, “software” was a magic black box that could be easily manipulated by a “hacker” in their midst. She didn’t need proof; she only needed to trigger the investigation to isolate Grant and cast a shadow of doubt over the entire digital infrastructure he had built.

    The move was a masterclass in institutional bullying. Suddenly, the man who had donated $65,000 worth of his life to the program was being treated like a criminal in a defensive crouch. The Association, instead of looking at Sarah’s history of “funny business” or the verifiable logs of the third-party system, reflexively protected their director. They launched an inquiry that forced Grant to spend weeks of his own time—time he could have spent with his family or on his actual career—defending his honor against a baseless lie. This is the raw reality of the volunteer grind: the moment you stop being a “useful tool” and start being a “check on power,” the institution will turn on you with a cold, mechanical indifference that would make a corporate HR department blush. Grant found himself in a fight he never asked for, forced to prove a negative against a woman who had spent years treating the club’s bylaws like suggestions.

    Grant didn’t retreat into anger; he retreated into the data. While Sarah was busy playing the victim in rink-side whispers and backroom meetings, Grant was operating with the cold, methodical precision of a man who knew that in a digital world, every lie leaves a footprint. He understood that the burden of proof in an institutional inquisition is rarely on the accuser, so he built a defense that was mathematically irrefutable. He spent dozens of hours—hours on top of the decade he’d already sacrificed—compiling a forensic dossier that documented every interaction with the voting software. He didn’t just tell them he didn’t rig the election; he showed them the server logs, the encrypted handshakes, and the third-party security protocols that made it impossible for an administrator to alter an individual ballot once cast. He presented a timeline of every email sent, every website modification made, and every administrative login, cross-referenced against the club’s own bylaws which Sarah had so casually ignored for years.

    The sheer density of the evidence was a silent middle finger to the incompetence of the board. Grant produced a document that mapped the “funny business” of previous years—the phone call logs and the manual tallies that didn’t add up—and contrasted it with the sterile, unassailable integrity of the digital system he had implemented. He was forcing the Association to look at the mirror, showing them that the only person with a history of manipulation was the woman pointing the finger. For a man who lived by the logic of “if-then” statements, the hearing wasn’t an emotional plea for his reputation; it was a technical demonstration of Sarah’s malice. He sat across from the Association representatives—people who likely struggled to reset their own Wi-Fi routers—and spoke to them in the language of objective truth. He didn’t ask for their trust; he demanded they acknowledge the data.

    The hearing was a collision between professional competence and bureaucratic ego. Grant watched as the Association reps flipped through his forensic audit with the glazed eyes of people who had realized they were in way over their heads. They had walked into the room expecting to slap the wrist of a “rogue volunteer” and instead found themselves staring at a mountain of evidence that implicated their own director in years of procedural misconduct. They saw the locks on the third-party system, they saw the clean logs, and they saw the verified results that matched the will of the members perfectly. There was no “hacker,” no “manipulation,” and no “rigging.” There was only a man who had done his job too well and a woman who had tried to destroy him for it. The truth was sitting on the table, cold and heavy, but the institution wasn’t interested in truth; it was interested in liability.

    The final verdict arrived not with a bang, but with a whimper—a two-paragraph email that was a masterclass in corporate-filtered non-apology. The Association stated they could “find no fault” in Grant’s actions, a clinical way of admitting he was innocent without actually saying he had been wronged. There was a weak, throwaway sentence about the “inconvenience of the investigation,” but no mention of the ten years of service, the $65,000 in labor, or the 100,000 photos that had built their brand. Even more galling was the silence regarding Sarah. There was no reprimand, no suspension, and no acknowledgment of her baseless smear campaign. She was allowed to keep her office and her title, protected by a system that values the survival of the hierarchy over the character of its builders. The Association had looked at a decade of loyalty and a month of character assassination and decided that the status quo was worth more than a man’s honor.

    In the immediate aftermath, Grant felt the weight of the “sunk cost fallacy” pulling at his gut. Ten years. Over a hundred thousand images of kids learning to find their edges, of parents crying in the stands, of a community he thought he was part of. He looked at the hard drives in his office—$10,000 worth of gear and an archive of a decade’s worth of growth—and realized that the club didn’t deserve a single byte of it. The “Actionable Fix” in this scenario wasn’t to stay and fight a guerrilla war against Sarah’s ego; it was to perform a total, scorched-earth decoupling of his identity from the program. He wasn’t just a volunteer leaving a post; he was an architect reclaiming his blueprints. He realized that Sarah had successfully weaponized the institution to run off its most valuable asset, and the board was too weak or too complicit to stop her.

    The raw truth that every high-level volunteer eventually learns is that the institution doesn’t love you back. It is a machine that consumes “useful idiots” until they become “inconvenient truths,” and then it discards them with a form letter. Grant’s exit wasn’t a retreat; it was an evacuation of value. He deleted his administrative access, handed over the keys to the digital fortress he had built, and walked away with the one thing Sarah could never touch: his integrity. He understood that the club would likely devolve back into the “funny business” of phone-call voting and paper-trail chaos within a year, and he finally stopped caring. Forgiveness, for Grant, was the cold realization that he no longer owed his energy to a group of people who would trade his decade of sacrifice for a director’s comfort.

    The first Saturday morning after his resignation was the loudest silence Grant had ever experienced. For ten years, the rhythmic scratch of toe picks, the deep hum of blades carving precise circles, and the echoes of classical scores over the PA system had been the heartbeat of his weekend. Now, sitting in his kitchen with a cup of coffee that didn’t need to be rushed, he felt the phantom weight of the camera bag on his shoulder. He looked at his gear—the Nikon bodies, the 70-200mm f/2.8 lens that had captured a hundred thousand tiny triumphs—and realized they were just tools again, no longer weapons of a community’s legacy. The realization hit him with the cold precision of a data point: he had been a ghostwriter for a story that the lead character was trying to delete. Sarah still held the keys to the rink, but she no longer held the keys to his time, a currency that, once spent, offers no refunds.

    The “funny business” resumed almost immediately. Reports filtered back through the grapevine of the old “phone tree” tactics resurfacing, of board meetings descending back into the opaque, disorganized chaos that had defined the era before Grant’s digital intervention. The club was regressing, shedding its professional skin and returning to its form as a petty fiefdom. It was the natural state of an organization that chooses a comfortable lie over a demanding truth. Grant watched from the sidelines, not with the bitterness of a man who had lost, but with the detached observation of a scientist watching a predictable chemical reaction. When you remove the structural integrity of a building—the architect and the foundation—it doesn’t collapse all at once; it leans until it eventually becomes uninhabitable.

    While the Association’s weak apology sat in his inbox like a digital insult, the real “audit” of Grant’s decade came from the people Sarah couldn’t control: the parents. His private gallery links began to see a spike in traffic. Families were downloading the archives, realizing that the man who had documented their children’s lives from their first wobbles to their high school graduations was gone. Those 100,000 photos weren’t just data; they were the only evidence of a decade of growth that the club had essentially disowned. Grant realized that by attacking his integrity, Sarah had inadvertently highlighted his value. Every high-resolution shot was a reminder of a standard she could never replicate with a smartphone and a grudge.

    The $65,000 in volunteer hours was gone, a sunk cost in the ledger of his life, but the forensic defense he had built remained a masterclass in tactical self-preservation. He had proven that a man with a paper trail is a man who cannot be easily erased. He had shown that even in a rigged game, the player who keeps the best records can walk away with his name intact. This is the raw truth for any man in the trenches of a volunteer organization: build the system, but keep the logs. Serve the community, but never trust the institution. The only thing you truly own at the end of a ten-year grind is your reputation and the data that proves you were the one who held the line when everyone else was busy making phone calls.

    Grant Miller eventually closed the spreadsheet. He archived the folder labeled “Skating Club Litigation” and moved it to a backup drive, a dark corner of his digital life that he intended to visit only if the “funny business” ever crossed the line into legal territory again. He wasn’t waiting for Sarah to be fired, and he wasn’t waiting for the Association to grow a spine and offer a real apology. That would be giving them more of his life, and he had already donated enough. The final transaction was the act of clicking “Logout” for the last time—not just from a server, but from a narrative that no longer served him.

    Author’s Note

    In the world of “sanitized” faith, we’re told forgiveness is a warm, fuzzy reconciliation. We’re fed a version of grace that expects a man to just “shake hands and forget” while his reputation is still bleeding out. But the reality of the grind teaches a harder truth: Sometimes, forgiveness is the tactical decision to stop trying to collect a debt from a bankrupt person. It’s handing the bill to a higher authority and walking off the job site.

    For the men who know me, you’ll recognize the skeleton of this story. It’s loosely based on my own ten-year tour in the trenches—a decade of professional-grade labor met with a calculated strike at my integrity. Note that all specific names and locations have been changed to protect everyone involved. For a man in my field, a formal accusation of “manipulation” or “rigging” is a direct hit on my livelihood. I operate under a strict standard of professional appearance; a smear like this could ha

    Even years later, I still feel the weight. Every year when the house lights dim and the ice shows begin, the struggle resurfaces like a ghost in the rafters. It’s a seasonal reminder of a wound that hasn’t fully closed—not because of a lack of faith, but because I refuse to lie about the truth. I still run the ice show circuit, taking the photos and giving them away for free, promoting the achievements of these young athletes and the sport itself. I do the work because the work has value to those skaters and thier families.

    I’ve had to face the bitter reality that the people who launched this path of destruction were never held accountable—and in all likelihood, they never will be on this side of eternity. Even though her actions and that path of wreckage continue to this day, there was no grand moment of justice, no public clearing of my name, and no professional consequence for the liar. From what I’ve been told, this began long before I arrived and has left a trail of destroyed lives in its wake. This includes one individual handed a lifetime ban from skating—a move reminiscent of the Tonya Harding fallout—simply for trying to protect a skater from abuse. That wake of destruction remains active, and the wreckage continues to pile up. I have to believe that one day, God will say “enough.” This is my way of turning this situation over to God.

    In Enemies of the Heart, Andy Stanley identifies Anger as the result of a “debt” mindset—the conviction that “you owe me.” When a bureaucrat smears your name or devalues a decade of your life, they create a massive debt. We wait for the apology or the admission of guilt to “balance the books,” but a bankrupt person can’t pay you back. Stanley’s solution isn’t “feelings”; it’s a business decision: Cancel the debt. You aren’t saying what they did was right; you’re deciding you will no longer wait for a thief to return what they stole.

    I’ve heard the fake apologies—the corporate-speak non-apologies meant to shift the blame. Specifically: “I’m sorry you got your feelings hurt.” Let’s be blunt: that’s a tactical maneuver, not an apology. It ignores the lie, the rigged system, and the malicious intent. It treats a professional betrayal like an emotional glitch on your part. It’s the cowards’ way out.

    Understand this: there is no commandment that forces you to associate with people like this. In my opinion, based on the Word, there are actually commandments not to associate with them. Scripture doesn’t call us to be door-mats for the deceptive. It tells us to “have nothing to do with them” (2 Timothy 3:5) and to “shun” those who persist in division and deceit. Forgiveness is about your heart’s freedom from their debt; it is not a legal requirement to invite a known liar back to your table.

    “Forgive and forget” is a myth. Even the resurrected Christ carries the record of what was done to Him.

    “Then I saw a Lamb, looking as if it had been slain, standing in the center of the throne…” — Revelation 5:6 (NIV)

    The scars on the resurrected Christ prove that memory and mercy are not mutually exclusive. Those wounds are the eternal record of the price He paid. He hasn’t “forgotten” the cost; He absorbed the debt so the bill never reaches the one who owed it. Forgiveness isn’t forgetting; it’s absorbing the hit.

    I wrote this for the men who still struggle, like I do, with the hard facts. I wrote it for the men who have done the work, kept the logs, and watched the “system” protect the liar. If you’re in those shoes, understand this: Your integrity isn’t defined by their inability to tell the truth. I know that one day God will hold them accountable, even if they never face justice on this earth. Scripture is clear: “It is mine to avenge; I will repay,” says the Lord. Sometimes, the most masculine thing you can do is shake the dust off your boots, cancel the debt, and leave the final audit to the only Judge who actually keeps the books.

    Call to Action

    If this story struck a chord, don’t just scroll on. Join the brotherhood—men learning to build, not borrow, their strength. Subscribe for more stories like this, drop a comment about where you’re growing, or reach out and tell me what you’re working toward. Let’s grow together.

    D. Bryan King

    Sources

    Disclaimer:

    The views and opinions expressed in this post are solely those of the author. The information provided is based on personal research, experience, and understanding of the subject matter at the time of writing. Readers should consult relevant experts or authorities for specific guidance related to their unique situations.

    Related Posts

    Rate this:

    #accountability #AndyStanley #betrayal #biblicalForgiveness #CareerReputation #CharacterAssassination #CorporateGaslighting #dataIntegrity #DebtCancellation #DigitalManipulation #DocumentingTruth #ElectionRigging #enemiesOfTheHeart #FakeApologies #FigureSkatingAssociation #ForensicAudit #ForgivenessVsReconciliation #InstitutionalCorruption #InstitutionalCowardice #IntegrityInTech #LeadershipAccountability #masculineFaith #moralCourage #NonProfitPolitics #PhotographyArchives #ProfessionalIntegrity #recoveringFromBetrayal #ResurrectedScars #Revelation56 #ShakingTheDust #SkatingDirector #SmallTownCorruption #SmearCampaigns #StandardOfAppearance #standingFirm #SystemsArchitect #TheSlainLamb #ThirdPartyVotingSystems #VengeanceIsMine #VolunteerBurnout