New Teensy 4.1 DIY Synthesizer

Hi Rolf. Think of it this way:-

Code:
// Sysex data lenght [28];                            // Daten lenght max 256
sysexData[sysexCount++] = 0xF0;                        // 0        - Start SysEx
sysexData[sysexCount++] = 0x00;                        // 1        - ID
sysexData[sysexCount++] = 0x00;                        // 2        - ID
sysexData[sysexCount++] = 0x00;                        // 3        - ID
sysexData[sysexCount++] = 0x00;                        // 4        - Device ID 0-64
sysexData[sysexCount++] = patchNo;                     // 5        - Patch No
sysexData[sysexCount++] = currentPatchBank;            // 6        - Folder No

sysexData[sysexCount++] = /* the content of (data[0], 12) goes here */;    // 7  Patchname character 1
sysexData[sysexCount++] = /* the content of (data[1], 12) goes here */;    // 8  Patchname character 2
sysexData[sysexCount++] = /* the content of (data[2], 12) goes here */;    // 9  Patchname character 3
sysexData[sysexCount++] = /* the content of (data[3], 12) goes here */;    // 10 Patchname character 4

// and so on ...

Greetings from Australia.
 
Small update..

Soon there will be SysEx DUMP for Jeannie. Then nothing stands in the way of the patch exchange.

20220501_123341.jpg

The transmission path is selected with DESTINATION, eg Midi or USB
DUMP TYPE selects a patch, bank, or all
With DUMP-Bank the bank
With source the patch
With SYSEX-DUMP transmission to the PC or reception
Transmission or reception is then initialized via the load/save button


Sending and receiving from SysEx already works. But I still have to do some programming :cool:

20220505_084324.jpg

20220505_092444.jpg

20220505_084723.jpg

Never ending programming. Greetings, Rolf
 
Hi Rolf. Think of it this way:-

Code:
// Sysex data lenght [28];                            // Daten lenght max 256
sysexData[sysexCount++] = 0xF0;                        // 0        - Start SysEx
sysexData[sysexCount++] = 0x00;                        // 1        - ID
sysexData[sysexCount++] = 0x00;                        // 2        - ID
sysexData[sysexCount++] = 0x00;                        // 3        - ID
sysexData[sysexCount++] = 0x00;                        // 4        - Device ID 0-64
sysexData[sysexCount++] = patchNo;                     // 5        - Patch No
sysexData[sysexCount++] = currentPatchBank;            // 6        - Folder No

sysexData[sysexCount++] = /* the content of (data[0], 12) goes here */;    // 7  Patchname character 1
sysexData[sysexCount++] = /* the content of (data[1], 12) goes here */;    // 8  Patchname character 2
sysexData[sysexCount++] = /* the content of (data[2], 12) goes here */;    // 9  Patchname character 3
sysexData[sysexCount++] = /* the content of (data[3], 12) goes here */;    // 10 Patchname character 4

// and so on ...

Greetings from Australia.

Hallo,
I optimized my code a bit. I use call-by-referense for string function and variables.
That uses less memory :)

Code:
//*************************************************************************
// usbMidi send SystemExclusive
//*************************************************************************
FLASHMEM void mySendSysEx(void)
{
    screenFlagSysex = true;    // draw ProgressBar
    
    byte sysexData[512];            // SysEx buffer 349 Byte
    sysexChecksum = 0;
    
    uint8_t patchNo = (sysexSource + 1);            // Patch No 1-128
    uint8_t currentPatchBank = sysexBank;            // A-P = 0-15
    String numString = (patchNo);
    String bankString = char(currentPatchBank + 65);
    String fileString = (bankString + "/" + numString);
    uint8_t data_len = NO_OF_PARAMS;
    uint16_t sysexCount = 0;
    
    // get patch parameter from sd card as String
    File patchFile = SD.open(fileString.c_str());
    String data[data_len]; //Array of data read in
    recallPatchData(patchFile, data);
    patchFile.close();
    
    // convert string to sysex                                                           // len / index / value
    sysexData[sysexCount++] = 0xF0;                                            // -    0    - Start SysEx
    sysexData[sysexCount++] = 0x00;                                            // -    1    - ID
    sysexData[sysexCount++] = 0x00;                                            // -    2    - ID
    sysexData[sysexCount++] = 0x00;                                            // -    3    - ID
    sysexData[sysexCount++] = 0x00;                                            // -    4    - Device ID 0-64
    sysexData[sysexCount++] = patchNo;                                        // -    5    - Patch No
    sysexData[sysexCount++] = currentPatchBank;                                // -    6    - Bank No
    float_to_string(data[0], 12, sysexCount, sysexData);                    // (0)    12    7    - Patch Name
    float_to_sysex1Byte(data[1], sysexCount, sysexData);                    // (1)    1    19  - oscALevel (0 - 1.00)
    float_to_sysex1Byte(data[2], sysexCount, sysexData);                    // (2)    1    20  - oscBLevel (0 - 1.00)
    float_to_sysex2Bytes(data[3], sysexCount, sysexData);                    // (3)    2    21  - noiseLevel ( -1.00 - +1.00)
    uint8_to_sysex1Byte(data[4], sysexCount, sysexData);                    // (4)    1    23  - unison (0 - 2)
    uint8_to_sysex1Byte(data[5], sysexCount, sysexData);                    // (5)    1    24  - oscFX    (0 - 6)
    float_to_sysex5Bytes(data[6], sysexCount, sysexData);                    // (6)    5     25  - detune (0 - 1.00000)
    //sysexCount = String_to_bin(data[7], 4, sysexCount);                    // (7)             - lfoSyncFreq (not available)
    float_to_string(data[8], 4, sysexCount, sysexData);                        // (8)    4    30    - midiClkTimeInterval
    //sysexCount = String_to_bin(data[9], 4, sysexCount);                    // (9)            - lfoTempoValue (not available)
    float_to_sysex5Bytes(data[10], sysexCount, sysexData);                    // (10) 5     34  - keytrackingAmount (0 - 1.0000)
    float_to_sysex5Bytes(data[11], sysexCount, sysexData);                    // (11)    5    39  - glideSpeed (0 - 1.00000)
    int8_to_sysex2Bytes(data[12], sysexCount, sysexData);                    // (12)    2    44  - oscPitchA (-24 - +24)
    int8_to_sysex2Bytes(data[13], sysexCount, sysexData);                    // (13) 2     46  - oscPitchB (-24 - +24)
    uint8_to_sysex1Byte(data[14], sysexCount, sysexData);                    // (14) 1     48  - oscWaveformA (0 - 63)
    uint8_to_sysex1Byte(data[15], sysexCount, sysexData);                    // (15)    1    49    - oscWaveformB (0 - 63)
    //sysexCount = String_to_bin(data[16], 1, sysexCount, &sysexData[0]);    // (16)            - pwmSource (1) (not available)
    float_to_sysex1Byte(data[17], sysexCount, sysexData);                    // (17) 1   50  - pwmAmtA (0 - 0.99)
    float_to_sysex1Byte(data[18], sysexCount, sysexData);                    // (18) 1   51  - pwmAmtB (0 - 0.99)
    //sysexCount = String_to_bin(data[19], 6, sysexCount);                    // (19)            - pwmRate (not available)
    float_to_sysex2Bytes(data[20], sysexCount, sysexData);                    // (20) 2     52  - pwA (-1.00 - +1.00)
    float_to_sysex2Bytes(data[21], sysexCount, sysexData);                    // (21) 2     54  - pwB (-1.00 - +1.00)
    float_to_string(data[22], 4, sysexCount, sysexData);                    // (22) 4    56  - filterRes (0 - 15.0)
    uint14_to_sysex2Bytes(data[23], sysexCount, sysexData);                    // (23) 2     60  - filterFreq (18 - 12000)
    float_to_string(data[24], 4, sysexCount, sysexData);                    // (24) 4     62  - filterMix (0 - -99.0)
    float_to_sysex2Bytes(data[25], sysexCount, sysexData);                    // (25) 2    66  - filterEnv (-1.00 - +1.00)
    float_to_sysex5Bytes(data[26], sysexCount, sysexData);                    // (26) 5    67  - oscLfoAmt (0 - 1.00000)
    float_to_sysex5Bytes(data[27], sysexCount, sysexData);                    // (27) 5     73  - oscLfoRate (0 - 40.0000)
    uint8_to_sysex1Byte(data[28], sysexCount, sysexData);                    // (28) 1     78  - oscLFOWaveform (0 - 12)
    uint8_to_sysex1Byte(data[29], sysexCount, sysexData);                    // (29) 1     79  - oscLfoRetrig (0 - 1)
    //sysexCount = String_to_bin(data[30], 1, sysexCount);                    // (30)         - oscLFOMidiClkSync (not available)
    uint8_to_sysex1Byte(data[31], sysexCount, sysexData);                    // (31) 1     80  - myFilterLFORateValue (1 - 127)
    uint8_to_sysex1Byte(data[32], sysexCount, sysexData);                    // (32) 1    81  - filterLfoRetrig (0 - 1)    
    //sysexCount = String_to_bin(data[33], 1, sysexCount);                    // (33)         - oscLFOMidiClkSync (not available)
    float_to_sysex5Bytes(data[34], sysexCount, sysexData);                    // (34) 5     82  - filterLfoAmt (0 - 1.00000)
    uint8_to_sysex1Byte(data[35], sysexCount, sysexData);                    // (35) 1    87  - filterLFOWaveform (0 - 12)
    uint14_to_sysex2Bytes(data[36], sysexCount, sysexData);                    // (36) 2    88  - filterAttack (0 - 11880)
    uint14_to_sysex2Bytes(data[37], sysexCount, sysexData);                    // (37) 2     90  - filterDecay (0 - 11880)
    //float_to_sysex1Byte(data[38], sysexCount, sysexData);                    // (38) 1    92  - filterSustain (0 - 1.00)
    uint14_to_sysex2Bytes(data[39], sysexCount, sysexData);                    // (39) 2    94  - filterRelease (0 - 11880)
    uint14_to_sysex2Bytes(data[40], sysexCount, sysexData);                    // (40) 2    96  - ampAttack (0 - 11880)
    uint14_to_sysex2Bytes(data[41], sysexCount, sysexData);                    // (41) 2     98  - ampDecay (0 - 11880)
    //float_to_sysex1Byte(data[42], sysexCount, sysexData);                    // (42) 1    100  - ampSustain (0 - 1.00)
    uint14_to_sysex2Bytes(data[43], sysexCount, sysexData);                    // (43) 2     101  - ampRelease (0 - 11880)
    //sysexCount = float_to_sysex5Bytes(data[44], sysexCount, &sysexData[0]);// (44)          - fxAmt (not available)
    //sysexCount = float_to_sysex5Bytes(data[45], sysexCount, &sysexData[0]);// (45)            - fxMix (not available)
    //sysexCount = float_to_sysex5Bytes(data[46], sysexCount, &sysexData[0]);// (46)           - pitchEnv (not available)
    uint8_to_sysex1Byte(data[47], sysexCount, sysexData);                    // (47) 1    102  - velocitySens (0-4)
    uint8_to_sysex1Byte(data[48], sysexCount, sysexData);                    // (48) 1    103  - chordDetune (0 - 127)
    uint8_to_sysex1Byte(data[49], sysexCount, sysexData);                    // (49) 1    104  - FxPot1value (0 - 127)
    uint8_to_sysex1Byte(data[50], sysexCount, sysexData);                    // (50) 1    105  - FxPot2value (0 - 127)
    uint8_to_sysex1Byte(data[51], sysexCount, sysexData);                    // (51) 1    106  - FxPot3value (0 - 127)
    uint8_to_sysex1Byte(data[52], sysexCount, sysexData);                    // (52) 1    107  - FxPrgNo (0 - 15)
    uint8_to_sysex1Byte(data[53], sysexCount, sysexData);                    // (53) 1    108  - FxMixValue (0 - 127)
    float_to_sysex5Bytes(data[54], sysexCount, sysexData);                    // (54) 5    109  - FxMixValue (10000 - 60000)
    uint8_to_sysex1Byte(data[55], sysexCount, sysexData);                    // (55) 1    114  - Osc1WaveBank (0 - 15)
    uint8_to_sysex1Byte(data[56], sysexCount, sysexData);                    // (56) 1    115  - Osc1WaveBank (0 - 15)
    uint8_to_sysex1Byte(data[57], sysexCount, sysexData);                    // (57) 1    116  - myBoost (0 - 1)
    float_to_sysex5Bytes(data[58], sysexCount, sysexData);                    // (58) 5    117  - pitchEnvA (-1.00 - +1.00)
    float_to_sysex5Bytes(data[59], sysexCount, sysexData);                    // (59) 5    119  - pitchEnvB (-1.00 - +1.00)
    float_to_sysex2Bytes(data[60], sysexCount, sysexData);                    // (60) 2    121  - driveLevel (Osc level) (0.00 - 1.25)
    float_to_sysex5Bytes(data[61], sysexCount, sysexData);                    // (61) 5    123  - myFilVelocity (0 - 1.00)
    float_to_sysex5Bytes(data[62], sysexCount, sysexData);                    // (62) 2    125  - myAmpVelocity (0 - 1.00)
    uint8_to_sysex1Byte(data[63], sysexCount, sysexData);                    // 1    127 - myUnisono (0-2)
    //sysexCount = uint8_to_sysex1Byte(data[64], sysexCount, &sysexData[0]);//         - dummy
    //sysexCount = uint8_to_sysex1Byte(data[65], sysexCount, &sysexData[0]);//         - dummy
    uint8_to_sysex1Byte(data[66], sysexCount, sysexData);                    // 1    128 - WShaperNo (0-14)
    float_to_sysex5Bytes(data[67], sysexCount, sysexData);                    // 5    129 - WShaperDrive (0.10 - 5.00)
    float_to_sysex5Bytes(data[68], sysexCount, sysexData);                    // 5    134 - LFO1phase (0 - 180.0)
    float_to_sysex5Bytes(data[69], sysexCount, sysexData);                    // 5    139 - LFO2phase (0 - 180.0)
    for (uint8_t i = 0; i < 16; i++) {
        uint8_to_sysex1Byte(data[70+i], sysexCount, sysexData);                // 16    144 - SeqNote1Buf (0 - 127)
    }
    for (uint8_t i = 0; i < 16; i++) {
        uint8_to_sysex1Byte(data[86+i], sysexCount, sysexData);    // 16    160 - SeqNoteBufStatus (0 - 1)
    }
    uint14_to_sysex2Bytes(data[102], sysexCount, sysexData);                // 2     176 - SEQbpmValue (101 - 462)
    float_to_sysex5Bytes(data[103], sysexCount, sysexData);                    // 5    178 - SEQdivValue (float)
    uint8_to_sysex1Byte(data[104], sysexCount, sysexData);                    // 1    183 - SEQstepNumbers (0 - 15)
    float_to_sysex5Bytes(data[105], sysexCount, sysexData);                    // 5    184 - SEQGateTime (float)
    uint8_to_sysex1Byte(data[106], sysexCount, sysexData);                    // 1    189 - SEQdirection (0 - 3)
    uint8_to_sysex1Byte(data[107], sysexCount, sysexData);                    // 1    190 - oscDetuneSync (0 - 1)
    int8_to_sysex2Bytes(data[108], sysexCount, sysexData);                    // 2    191 - oscPitchA (-12 - +12)
    float_to_sysex5Bytes(data[109], sysexCount, sysexData);                    // 5    193 - oscMasterTune (float)
    float_to_sysex5Bytes(data[110], sysexCount, sysexData);                    // 5    198 - OscVCFMOD (float)
    for (uint8_t i = 0; i < 16; i++) {
        uint8_to_sysex1Byte(data[111], sysexCount, sysexData);                // 16    203 - SeqVeloBuf (0 - 127)
    }
    for (uint8_t i = 0; i < 48; i++) {
        uint8_to_sysex1Byte(data[127], sysexCount, sysexData);                // 48    219 - SeqNote1Buf (0 - 127)
    }
    for (uint8_t i = 0; i < 16; i++) {
        uint8_to_sysex1Byte(data[175], sysexCount, sysexData);                // 16    267 - SeqNoteCount (0 - 3)
    }
    uint8_to_sysex1Byte(data[191], sysexCount, sysexData);                    // 1    283 - SEQmode (0 - 2)
    uint8_to_sysex1Byte(data[192], sysexCount, sysexData);                    // 1    284 - SEQMidiClkSwitch (0 - 1)
    uint8_to_sysex1Byte(data[193], sysexCount, sysexData);                    // 1    285 - LadderFilterpassbandgain (0 - 127)
    uint8_to_sysex1Byte(data[194], sysexCount, sysexData);                    // 1    286 - LadderFilterDrive (1 - 127)
    int8_to_sysex2Bytes(data[195], sysexCount, sysexData);                    // 2    287 - envelopeType1 (-8 - +8)
    int8_to_sysex2Bytes(data[196], sysexCount, sysexData);                    // 2    289 - envelopeType2 (-8 - +8)
    float_to_sysex5Bytes(data[197], sysexCount, sysexData);                    // 5    291 - PitchWheelAmt (float)
    float_to_sysex5Bytes(data[198], sysexCount, sysexData);                    // 5    296 - MODWheelAmt (float)
    uint8_to_sysex1Byte(data[199], sysexCount, sysexData);                    // 1    301 - myFilter (1 - 2)
    float_to_sysex5Bytes(data[200], sysexCount, sysexData);                    // 5    302 - pwmRateA (-10.00 - + 10.00)
    float_to_sysex5Bytes(data[201], sysexCount, sysexData);                    // 5    307 - pwmRateB (-10.00 - + 10.00)
    uint14_to_sysex2Bytes(data[202], sysexCount, sysexData);                // 2    312 - LFO1fadeTime (0 - 12000) Fade in
    uint14_to_sysex2Bytes(data[203], sysexCount, sysexData);                // 2    314 - LFO1releaseTime (0 - 12000) Fade out
    float_to_sysex5Bytes(data[204], sysexCount, sysexData);                    // 5    316 - filterFM (0.00000 - 1.00000)  Osc1
    float_to_sysex5Bytes(data[205], sysexCount, sysexData);                    // 5    321 - filterFM2 (0.00000 - 1.00000)    Osc2
    uint14_to_sysex2Bytes(data[206], sysexCount, sysexData);                // 2    326 - LFO2fadeTime (0 - 12000) Fade in
    uint14_to_sysex2Bytes(data[207], sysexCount, sysexData);                // 2    328 - LFO2releaseTime (0 - 12000) Fade out
    float_to_sysex5Bytes(data[208], sysexCount, sysexData);                    // 5    330 - Osc1ModAmt (0.00000 - 1.00000) Fx Mod
    int8_to_sysex2Bytes(data[209], sysexCount, sysexData);                    // 2    335 - LFO1enCurve (-8 - +8)
    int8_to_sysex2Bytes(data[210], sysexCount, sysexData);                    // 2    337 - LFO2enCurve (-8 - +8)
    uint8_to_sysex1Byte(data[211], sysexCount, sysexData);                    // 1    339 - LFO1mode (0 - 1)
    uint8_to_sysex1Byte(data[212], sysexCount, sysexData);                    // 1    340 - LFO2mode (0 - 1)
    float_to_sysex5Bytes(sysexChecksum, sysexCount, sysexData);                // 5    341 - SysEx checksum
    sysexData[sysexCount++] = 0xF7;                                            // 1    346 - End SysEx (0xF7)
    
    usbMIDI.sendSysEx(sysexCount, sysexData, true);        // send SysEx data
    
}




//*************************************************************************
// convert float to SysEx string
//*************************************************************************
FLASHMEM void float_to_string (String value, uint8_t len, uint16_t &sysexCount, byte *sysexData)
{
    for (uint8_t i = 0; i < len; i++) {
        sysexData[sysexCount++] = value[i];
        sysexChecksum += value[i];
    }
}




//*************************************************************************
// convert float into SysEx uint7Bit  (float 0 - 0.127)
//*************************************************************************
FLASHMEM void float_to_sysex1Byte (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t val = (value.toFloat() * 100);
    sysexData[sysexCount++] = val; 
    sysexChecksum += val;
}








//*************************************************************************
// convert float into SysEx uint7Bit  (- 127 - +127)
//*************************************************************************
FLASHMEM void int8_to_sysex2Bytes (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t l_byte = (uint8_t)(value.toInt());
    uint8_t h_byte = 0;
    
    if (l_byte > 127) {
        l_byte = 256 - l_byte;            // make positive value
        h_byte = 1;                        // minus Sign
    }


    sysexData[sysexCount++] = h_byte;    // Sign (- = 1 / + = 0)
    sysexChecksum += h_byte;
    sysexData[sysexCount++] = l_byte;    // data
    sysexChecksum += l_byte;
}


//*************************************************************************
// convert string into SysEx int7 (float -1.27 - +1.27)
//*************************************************************************
FLASHMEM void float_to_sysex2Bytes (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t h_byte = int8_t(value.toFloat() * 100);                            
    int8_t l_byte = h_byte;
    
    // calc h_byte
    h_byte >>= 7;
    
    // l_byte is sign 
    if (l_byte < 0) {    
        l_byte = l_byte * (-1);
    }
    
    sysexData[sysexCount++] = h_byte;    // Sign (- = 1 / + = 0)
    sysexChecksum += h_byte;
    sysexData[sysexCount++] = l_byte;    // data
    sysexChecksum += l_byte;
}


//*************************************************************************
// convert float into SysEx uint14 (float 0.00 - 16.383)
//*************************************************************************
FLASHMEM uint16_t float_to_uint14bit (String value, uint16_t sysexCount, byte *sysexData)
{
    uint8_t h_byte = int8_t(value.toFloat() * 100);                            
    int8_t l_byte = h_byte;
    
    // calc h_byte
    h_byte >>= 7;
    
    // l_byte is sign 
    if (l_byte < 0) {    
        l_byte = l_byte * (-1);
    }
    
    sysexData[sysexCount++] = h_byte;    // Sign (- = 1 / + = 0)
    sysexChecksum += h_byte;
    sysexData[sysexCount++] = l_byte;    // data
    sysexChecksum += l_byte;
    
    return sysexCount;
}


//*************************************************************************
// convert 7bit value into SysEx 1Byte ( 0-127)
//*************************************************************************
FLASHMEM void uint8_to_sysex1Byte (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t val =  value.toInt();
    sysexData[sysexCount++] = val;
    sysexChecksum += val;
    
}


//*************************************************************************
// convert 14bit value into SysEx 2Byte (0-16383)
//*************************************************************************
FLASHMEM void uint14_to_sysex2Bytes (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint16_t var16 = (value.toInt());
    uint16_t var16_x = var16;
    
    // calc h_byte & l_byte -----------
    var16 = var16 << 1;
    uint8_t h_byte = var16 >> 8;
    uint8_t l_byte = var16_x & 0x7F;
    
    sysexData[sysexCount++] = h_byte;
    sysexChecksum += h_byte;
    sysexData[sysexCount++] = l_byte;
    sysexChecksum += l_byte;    
}




//*************************************************************************
// convert 32bit float value into SysEx 5Byte
//*************************************************************************
FLASHMEM void float_to_sysex5Bytes(String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t sysexBytes[5];
    uint8_t temp_val = 0;
    uint8_t bit_val = 0;
    
    union {
        float fval;
        byte bval[4];
    } floatAsBytes;
    
    floatAsBytes.fval = (value.toFloat());
    
    //  Bit mask
    for (uint8_t i = 0; i < 4; i++) {
        
        // Bit 0-6 to Byte 1-4
        sysexBytes[i] = floatAsBytes.bval[i] & 0x7F;
        
        // Bit7 to 5.Byte
        temp_val = floatAsBytes.bval[i];
        bit_val = bit_val << 1;
        if (temp_val >= 128) {
            bit_val = (bit_val | 0x01);
        }
    }
    sysexBytes[4] = bit_val;
    
    // write sysex buffer
    for (uint8_t i = 0; i < 5; i++){
        sysexData[sysexCount++] = sysexBytes[i];
        sysexChecksum += sysexBytes[i];
    }
}
 
Hi Rolf. Looks like you're making good progress. Have been quietly following this thread and learned some useful stuff so you are one of my many teachers here.

Must also mention, Paul, Defragster, KurtE, MJS513, FrankB (hope you are not eating too much popcorn), Oddson and many others, Thank you all.

Mmm, there's some good stuff in that last code snippet.

Am planning to use the Audio tool to re-create an analog synth I built in the late '70s but not before I've got a decent set of knobs for it. Had a Sequencer and the Oscilloscope was permanently connected, so was triggered when a note began and the Y axis would be looking at the output. Displayed the effect of envelope, filter cutoff and resonance which was really useful for teaching musicians how to operate it.

Keep up the good work.
 
Hi MatrixRat
The SysEx dump for a patch is very complex. The patch is stored on the SD card as a text string.
This contains values in 32-bit floating point and 16-bit integer values.
You can still save some SysEX data if you convert individual function bits into a 7-bit value.

For example here:

Code:
uint8_to_sysex1Byte(data[29], sysexCount, sysexData);    // (29) 1Byte    oscLfoRetrig (0 - 1)
uint8_to_sysex1Byte(data[32], sysexCount, sysexData);    // (32) 1Byte    filterLfoRetrig (0 - 1)
uint8_to_sysex1Byte(data[57], sysexCount, sysexData);    // (57) 1Byte    myBoost (0 - 1)
uint8_to_sysex1Byte(data[107], sysexCount, sysexData);    // (107) 1Byte   oscDetuneSync (0 - 1)
uint8_to_sysex1Byte(data[86+i], sysexCount, sysexData);   // (86 - 101) 16Bytes   SeqNoteBufStatus (0 - 1)
...
 
Thanks Rolf. Got me thinking, should be soldering.

I understand that the logical choice is to send floats as ascii so we can put the decimal point where needed. But, say if we only have one digit before the decimal point then we don't necessarily need to send the decimal point itself and what's right of point can (i think) be compacted and sent across three sysex "bytes". Thus the payload is reduced and TX and RX handlers will process stuff quicker than midi can send the verbatim ascii float. Of course it's up to the handler to sort out the details and give us back two floats.

A theoretical snip from a Midi Implementation map might look a bit like this:-

Code:
 0x00 //MSD = float1 first digit, LSD = float2 first digit - 0-7 fits both cases
 0x00 //bits 15-21
 0x00 //bits 8-14
 0x00 //bits 0-7 -> packed right of point float2
 0x00 //bits 15-21
 0x00 //bits 8-14
 0x00 //bits 0-7 -> packed right of point float1

If that works then two floats use seven sysex "bytes".

Actually not quite correct, would need to shuffle more bits but you get the idea?
 
Last edited:
I programmed different conversions for different floating point and integer values

Code:
//*************************************************************************
// convert float (0.0 - 0.127) into SysEx 1Byte
//*************************************************************************
FLASHMEM void float_to_sysex1Byte (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t val = (value.toFloat() * 100);
    sysexData[sysexCount++] = val; 
    sysexChecksum += val;
}

//*************************************************************************
// convert float (-1.27 - +1.27) into SysEx 2Byte
//*************************************************************************
FLASHMEM void float_to_sysex2Bytes (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t h_byte = int8_t(value.toFloat() * 100);                            
    int8_t l_byte = h_byte;
    
    // h_byte is Sign
    h_byte >>= 7;
    
    // l_byte is value
    if (l_byte < 0) {    
        l_byte = l_byte * (-1);
    }
    
    sysexData[sysexCount++] = h_byte;    // Sign (- = 1 / + = 0)
    sysexChecksum += h_byte;
    sysexData[sysexCount++] = l_byte;    // value
    sysexChecksum += l_byte;
}

//*************************************************************************
// convert 32bit float into SysEx 5Byte  
//*************************************************************************
FLASHMEM void float_to_sysex5Bytes(String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t sysexBytes[5];
    uint8_t temp_val = 0;
    uint8_t bit_val = 0;
    
    union {
        float fval;
        byte bval[4];
    } floatAsBytes;
    
    floatAsBytes.fval = (value.toFloat());
    
    //  Bit mask
    for (uint8_t i = 0; i < 4; i++) {
        
        // Bit 0-6 to Byte 1-4
        sysexBytes[i] = floatAsBytes.bval[i] & 0x7F;
        
        // Bit7 to 5.Byte
        temp_val = floatAsBytes.bval[i];
        bit_val = bit_val << 1;
        if (temp_val >= 128) {
            bit_val = (bit_val | 0x01);
        }
    }
    sysexBytes[4] = bit_val;
    
    // write sysex buffer
    for (uint8_t i = 0; i < 5; i++){
        sysexData[sysexCount++] = sysexBytes[i];
        sysexChecksum += sysexBytes[i];
    }
}

//*************************************************************************
// convert uint7bit (0-127) into SysEx 1Byte
//*************************************************************************
FLASHMEM void uint7_to_sysex1Byte (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint8_t val =  value.toInt();
    sysexData[sysexCount++] = val;
    sysexChecksum += val;
    
}

//*************************************************************************
// convert uint14bit (0-16383) into SysEx 2Byte
//*************************************************************************
FLASHMEM void uint14_to_sysex2Bytes (String value, uint16_t &sysexCount, byte *sysexData)
{
    uint16_t var16 = (value.toInt());
    uint16_t var16_x = var16;
    
    // calc h_byte & l_byte -----------
    var16 = var16 << 1;
    uint8_t h_byte = var16 >> 8;
    uint8_t l_byte = var16_x & 0x7F;
    
    sysexData[sysexCount++] = h_byte;
    sysexChecksum += h_byte;
    sysexData[sysexCount++] = l_byte;
    sysexChecksum += l_byte;    
}
 
Last edited:
SysEx Dump works now. See short video.

It was a bit more complicated than I thought. First, conversion routines had to be developed for all 212 parameter values in the Jeannie, which convert 8/16-bit integer values and 32-bit floating-point values into a 7-bit format for the SysEx dump. The same had to be converted back again.
This was followed by functions for the transmission and reception of SysEx data. Last but not least, a simple menu for operation had to be developed.

20220523-135020.jpg


The transmission path is selected with Destination (Midi or USB)
DUMP type is either a patch or a bank.
DUMP-Bank sets the bank (A-P) for transmission or reception.
Source is either the patch no. or when receiving the space in the selected patch bank.
SYSEX-DUMP is set to send or receive SysEX data.
The "Load/Save" button starts transmission or reception.
A progress bar shows the progress of the transfer.

Sys-Ex-Dump.png


Video: https://youtu.be/xRoLsR9HgU0

Greetings Rolf
 
Hello..


I'm still in the process of integrating a SuperSaw into the Jeannie. 6 saw teeth per oscillator. This corresponds to 12 per vote.


Unfortunately, the whole thing doesn't sound right yet. Below is the source code and an audio sample with an oscillator.
Maybe someone has an idea about it ?

SuperSAW.png



Supersaw with small spread value (saw_inc32)
Audio Link: https://www.sequencer.de/synthesizer/data/audio/149/149083-8c02e58a5ebdd1b6504b537ab2cc5d59.mp3


My Supersaw waveform code
Code:
// Phase increment for SupperSaw
saw_inc32 += 6378593;


case WAVEFORM_SUPERSAW:
for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
uint32_t ph_1 = phasedata[i];
uint32_t ph_2 = ph_1 + (saw_inc32 * 1);
uint32_t ph_3 = ph_1 + (saw_inc32 * 2);
uint32_t ph_4 = ph_1 + (saw_inc32 * 3);
uint32_t ph_5 = ph_1 + (saw_inc32 * 4);
int16_t val_1 = signed_multiply_32x16t(magnitude * 0.40f, ph_1);
int16_t val_2 = signed_multiply_32x16t(magnitude * 0.15f, ph_2);
int16_t val_3 = signed_multiply_32x16t(magnitude * 0.15f, ph_3);
int16_t val_4 = signed_multiply_32x16t(magnitude * 0.15f, ph_4);
int16_t val_5 = signed_multiply_32x16t(magnitude * 0.15F, ph_5);
*bp++ = val_1 + val_2 + val_3 + val_4 + val_5;
}
break;



Oscillator from Teensy Audio Lib
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 
 ElectroTechnique 2020
 Added WAVEFORM_SILENT, sync()                           
 */


#ifndef synth_waveform_h_
#define synth_waveform_h_


#include <Arduino.h>
#include "AudioStream.h"
#include "arm_math.h"




// waveforms.c
extern "C" {
extern const int16_t AudioWaveformSine[257];
}


extern uint8_t LFO1mode;
extern uint8_t LFO2mode;
extern uint8_t LFO3mode;
extern boolean lfo1oneShoot; 
extern boolean lfo2oneShoot;
extern boolean lfo3oneShoot;  
extern uint8_t LFO1phase;
extern uint8_t LFO2phase;
extern uint8_t LFO3phase;
extern uint8_t lfo1ph;
extern uint8_t lfo2ph;
extern uint8_t lfo3ph;
extern int8_t Lfo3Modoutput;
extern boolean LFO1randomFlag;
extern boolean LFO2randomFlag;
extern boolean LFO3randomFlag;
extern uint32_t LFO1delayTime;
extern uint32_t LFO2delayTime;
extern uint32_t LFO3delayTime;


// Oscillator
#define WAVEFORM_SINE                            0
#define WAVEFORM_TRIANGLE                        1
#define WAVEFORM_SAWTOOTH                        3
#define WAVEFORM_SQUARE                            2
#define WAVEFORM_PULSE                            4
#define WAVEFORM_SAWTOOTH_REVERSE                5
#define WAVEFORM_SAMPLE_HOLD                    6
#define WAVEFORM_TRIANGLE_VARIABLE                7
#define WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE        8
#define WAVEFORM_BANDLIMIT_SAWTOOTH                9
#define WAVEFORM_BANDLIMIT_SQUARE                10
#define WAVEFORM_BANDLIMIT_PULSE                11
#define WAVEFORM_ARBITRARY                        12
#define WAVEFORM_SILENT                            19


// LFO1 (OSC)
#define WAVEFORM_ARBITRARY1                        20
#define WAVEFORM_SAMPLE_HOLD1                    21


// LFO2 (FILTER)
#define WAVEFORM_ARBITRARY2                        22
#define WAVEFORM_SAMPLE_HOLD2                    23


// LFO3 (AMP)
#define WAVEFORM_ARBITRARY3                        24
#define WAVEFORM_SAMPLE_HOLD3                    25


#define PWM_WAVEFORM_SINE                        26
#define PWM_WAVEFORM_TRIANGLE                    27
#define PWM_WAVEFORM_SAWTOOTH                    28
#define PWM_WAVEFORM_SQUARE                        29






typedef struct step_state
{
  int offset ;
  bool positive ;
} step_state ;




class BandLimitedWaveform
{
public:
  BandLimitedWaveform (void) ;
  int16_t generate_sawtooth (uint32_t new_phase, int i) ;
  int16_t generate_square (uint32_t new_phase, int i) ;
  int16_t generate_pulse (uint32_t new_phase, uint32_t pulse_width, int i) ;
  void init_sawtooth (uint32_t freq_word) ;
  void init_square (uint32_t freq_word) ;
  void init_pulse (uint32_t freq_word, uint32_t pulse_width) ;
  


private:
  int32_t lookup (int offset) ;
  void insert_step (int offset, bool rising, int i) ;
  int32_t process_step (int i) ;
  int32_t process_active_steps (uint32_t new_phase) ;
  int32_t process_active_steps_saw (uint32_t new_phase) ;
  int32_t process_active_steps_pulse (uint32_t new_phase, uint32_t pulse_width) ;
  void new_step_check_square (uint32_t new_phase, int i) ;
  void new_step_check_pulse (uint32_t new_phase, uint32_t pulse_width, int i) ;
  void new_step_check_saw (uint32_t new_phase, int i) ;


  
  uint32_t phase_word ;
  int32_t dc_offset ;
  step_state states [32] ; // circular buffer of active steps
  int newptr ;         // buffer pointers into states, AND'd with PTRMASK to keep in buffer range.
  int delptr ;
  int32_t  cyclic[16] ;    // circular buffer of output samples
  bool pulse_state ;
  uint32_t sampled_width ; // pulse width is sampled once per waveform
};




class AudioSynthWaveformTS : public AudioStream
{
public:
  AudioSynthWaveformTS(void) : AudioStream(0,NULL),
    phase_accumulator(0), phase_increment(0), phase_offset(0),
    magnitude(0), pulse_width(0x40000000),
    arbdata(NULL), sample(0), tone_type(WAVEFORM_SINE),
    tone_offset(0),syncFlag(0)  {
  }


  void frequency(float freq) {
    if (freq < 0.0) {
      freq = 0.0;
    } else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) {
      freq = AUDIO_SAMPLE_RATE_EXACT / 2;
    }
    phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT);
    if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000;
  }
  void phase(float angle) {
    if (angle < 0.0) {
      angle = 0.0;
    } else if (angle > 360.0) {
      angle = angle - 360.0;
      if (angle >= 360.0) return;
    }
    phase_offset = angle * (4294967296.0 / 360.0);
  }
  void sync() {
    syncFlag = 1;
  }       
  void amplitude(float n) { // 0 to 1.0
    if (n < 0) {
      n = 0;
    } else if (n > 1.0) {
      n = 1.0;
    }
    magnitude = n * 65536.0;
  }
  void offset(float n) {
    if (n < -1.0) {
      n = -1.0;
    } else if (n > 1.0) {
      n = 1.0;
    }
    tone_offset = n * 32767.0;
  }
  void pulseWidth(float n) {  // 0.0 to 1.0
    if (n < 0) {
      n = 0;
    } else if (n > 1.0) {
      n = 1.0;
    }
    pulse_width = n * 4294967296.0;
  }
  void begin(short t_type) {
    phase_offset = 0;
    tone_type = t_type;
    if (t_type == WAVEFORM_BANDLIMIT_SQUARE)
      band_limit_waveform.init_square (phase_increment) ;
    else if (t_type == WAVEFORM_BANDLIMIT_PULSE)
      band_limit_waveform.init_pulse (phase_increment, pulse_width) ;
    else if (t_type == WAVEFORM_BANDLIMIT_SAWTOOTH || t_type == WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE)
      band_limit_waveform.init_sawtooth (phase_increment) ;
  }
  void begin(float t_amp, float t_freq, short t_type) {
    amplitude(t_amp);
    frequency(t_freq);
    phase_offset = 0;
    begin (t_type);
  }
  void arbitraryWaveform(const int16_t *data, float maxFreq) {
    arbdata = data;
  }
  virtual void update(void);


private:
  uint32_t phase_accumulator;
  uint32_t phase_increment;
  uint32_t phase_offset;
  int32_t  magnitude;
  uint32_t pulse_width;
  const int16_t *arbdata;
  int16_t  sample; // for WAVEFORM_SAMPLE_HOLD
  int16_t  sample2; // for WAVEFORM_SAMPLE_HOLD and oneShot
  short  tone_type;
  int16_t tone_offset;
  int16_t syncFlag;
  BandLimitedWaveform band_limit_waveform ;
};




class AudioSynthWaveformModulatedTS : public AudioStream
{
public:
  AudioSynthWaveformModulatedTS(void) : AudioStream(2, inputQueueArray),
    phase_accumulator(0), phase_increment(0), modulation_factor(32768),
    magnitude(0), arbdata(NULL), sample(0), tone_offset(0),
    tone_type(WAVEFORM_SINE), modulation_type(0), syncFlag(0) {
  }


  void frequency(float freq) {
    if (freq < 0.0) {
      freq = 0.0;
    } else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) {
      freq = AUDIO_SAMPLE_RATE_EXACT / 2;
    }
    phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT);
    if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000;
  }
  void amplitude(float n) { // 0 to 1.0
    if (n < 0) {
      n = 0;
    } else if (n > 1.0) {
      n = 1.0;
    }
    magnitude = n * 65536.0;
  }
   void sync() {
    syncFlag = 1;
  }              
  void offset(float n) {
    if (n < -1.0) {
      n = -1.0;
    } else if (n > 1.0) {
      n = 1.0;
    }
    tone_offset = n * 32767.0;
  }
  void begin(short t_type) {
    tone_type = t_type;
    if (t_type == WAVEFORM_BANDLIMIT_SQUARE)
      band_limit_waveform.init_square (phase_increment) ;
    else if (t_type == WAVEFORM_BANDLIMIT_PULSE)
      band_limit_waveform.init_pulse (phase_increment, 0x80000000u) ;
    else if (t_type == WAVEFORM_BANDLIMIT_SAWTOOTH || t_type == WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE)
      band_limit_waveform.init_sawtooth (phase_increment) ;
  }
  void begin(float t_amp, float t_freq, short t_type) {
    amplitude(t_amp);
    frequency(t_freq);
    begin (t_type) ;
  }
  void arbitraryWaveform(const int16_t *data, float maxFreq) {
    arbdata = data;
  }
  void frequencyModulation(float octaves) {
    if (octaves > 12.0) {
      octaves = 12.0;
    } else if (octaves < 0.1) {
      octaves = 0.1;
    }
    modulation_factor = octaves * 4096.0;
    modulation_type = 0;
  }
  void phaseModulation(float degrees) {
    if (degrees > 9000.0) {
      degrees = 9000.0;
    } else if (degrees < 30.0) {
      degrees = 30.0;
    }
    modulation_factor = degrees * (65536.0 / 180.0);
    modulation_type = 1;
  }
  virtual void update(void);


private:
  audio_block_t *inputQueueArray[2];
  uint32_t phase_accumulator;
  uint32_t phase_increment;
  uint32_t modulation_factor;
  int32_t  magnitude;
  const int16_t *arbdata;
  uint32_t phasedata[AUDIO_BLOCK_SAMPLES];
  int16_t  sample; // for WAVEFORM_SAMPLE_HOLD
  int16_t  tone_offset;
  uint8_t  tone_type;
  uint8_t  modulation_type;
  int16_t   syncFlag;
  BandLimitedWaveform band_limit_waveform ;
  uint32_t saw_inc32;
};




#endif

Thanks and greetings :)
 
Last edited:
A little sample of the Jeannie with Supersaw Oscillator

MP3 file: https://www.sequencer.de/synthesizer/data/audio/149/149233-11e22716cff1ced3b9499ee8897d1da8.mp3


Code is changed from old SAWTOOTH Oscillator

Code:
case WAVEFORM_SAWTOOTH:

        /*
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
        *bp++ = signed_multiply_32x16t(magnitude, phasedata[i]);
        }
        break;
        */

        // Supersaw
        case WAVEFORM_SAWTOOTH:
        
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
            
            // Phase increment
            saw_inc32 += inc >> 8;
            
            uint32_t spread_2 = saw_inc32;
            uint32_t spread_3 = saw_inc32 << 1;
            uint32_t spread_4 = saw_inc32 << 2;
            uint32_t spread_5 = saw_inc32 << 3;
            uint32_t ph_1 = phasedata[i];
            uint32_t ph_2 = (ph_1 + spread_2) * 3.09f;
            uint32_t ph_3 = (ph_1 + spread_3) * 5.01f;
            uint32_t ph_4 = (ph_1 + spread_4) * 3.15f;
            uint32_t ph_5 = (ph_1 + spread_5) * 4.79f;
            int16_t val_1 = signed_multiply_32x16t(magnitude * 0.300f, ph_1);
            int16_t val_2 = signed_multiply_32x16t(magnitude * 0.175f, ph_2);
            int16_t val_3 = signed_multiply_32x16t(magnitude * 0.175f, ph_3);
            int16_t val_4 = signed_multiply_32x16t(magnitude * 0.175f, ph_4);
            int16_t val_5 = signed_multiply_32x16t(magnitude * 0.175F, ph_5);
            *bp++ = val_1 + val_2 + val_3 + val_4 + val_5;
        }
        break;
 
Last edited:
The Supersaw is generated in the Jeannie with an oscillator. The fundamental wave is a rising sawtooth calculated in real time by a phase register. I use the phase register to calculate additional (four) sawtooths and add them to the fundamental via an addition.

Code:
// Supersaw
        case WAVEFORM_SAWTOOTH:
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
           
            saw_inc32 += 2039 * SupersawSpreadA;
            uint32_t spread_2 = saw_inc32;
            uint32_t spread_3 = saw_inc32 << 1;
            uint32_t spread_4 = saw_inc32 << 2;
            uint32_t spread_5 = saw_inc32 << 3;
            uint32_t ph_1 = phasedata[i];
            uint32_t ph_2 = (ph_1 + spread_2);
            uint32_t ph_3 = (ph_1 + spread_3) >> 1;
            uint32_t ph_4 = (ph_1 + spread_4) >> 2;
            uint32_t ph_5 = (ph_1 + spread_5) >> 3;
            int16_t val_1 = signed_multiply_32x16t(magnitude * Supersaw_gain1A, ph_1);
            int16_t val_2 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_2);
            int16_t val_3 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_3);
            int16_t val_4 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_4);
            int16_t val_5 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_5);
            *bp++ = val_1 + val_2 + val_3 + val_4 + val_5;
        }
        break;

Supersaw.png


 
The Supersaw in the Jeannie now works well. I fixed a few more bugs in the code.

Code:
// Supersaw
        case WAVEFORM_SAWTOOTH:
        phase_spread = (phase_increment >> 14) * SupersawSpreadA;
        ++phase_spread;
        saw_phase_increment = phase_increment & 0x000000FF;
        
        for (uint8_t i = 0; i < 3; ++i) {
            saw_phase_increment += phase_spread;
            increments[i] = saw_phase_increment;
        }
        
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
            
            data_qs_phase[0] += increments[0];
            data_qs_phase[1] += increments[1];
            data_qs_phase[2] += increments[2];
            
            ph_1 = phasedata[i];
            ph_2 = (ph_1 + data_qs_phase[0]);
            ph_3 = (ph_2 + data_qs_phase[1]);
            ph_4 = (ph_3 + data_qs_phase[2]);
            
            int16_t val_1 = signed_multiply_32x16t(magnitude * Supersaw_gain1A, ph_1);
            int16_t val_2 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_2);
            int16_t val_3 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_3);
            int16_t val_4 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_4);
            *bp++ = val_1 + val_2 + val_3 + val_4;
        }
        break;

SSaw-3.png


 
Last edited:
Hallo again and happy new Year ;)
After the many additional features we drew a new block diagram of Jeannie.

Voice-2-12-Frame.png
 
Sounds and looks great as always! Thanks for sharing the architecture, food for thought.

Any thoughts on a hybrid design with analog VCO/VCF/VCF and Teensy control?
 
Back
Top