Jump to content
Sign in to follow this  
oPossum

Fraunchpad poly synth

Recommended Posts

16 voices of 16 bit x 257 interpolated wavetable - derived from this code for the Launchpad.

PWM output, but could easily be adapted to use IIC DAC.

 

This is preliminary and eXperimental. The CPU core seems to be running slower than it should and I don't yet know why. Right now it is limited to 9 voices. The 9600 bps UART on the dev board was used for this demo, but standard MIDI can also be used - just change the bit rate divisor and wire up the usual opto circuit for MIDI.

 

This is part of a larger project I am working on.

 

 

//main.c
#include "msp430fr5739.h"

void synth_init(void);
void set_note(int, int, int);

static int assign[16];

void note_on(int n, int v)
{
   int i;
   for(i = 0; i < 16; ++i) {
       if(assign[i] == -1) {
           assign[i] = n;
           set_note(i, n, v);
           break;
       }
   }
}

void note_off(int n)
{
   int i;
   for(i = 0; i < 16; ++i) {
       if(assign[i] == n) {
           assign[i] = -1;
           set_note(i, -1, 0);
           break;
       }
   }
}

void midi_ex(unsigned a, unsigned b, unsigned c)
{
   ++PJOUT;
   switch(a & 0xF0) {
       case 0x90:  // Note on
           if(c) {
               note_on(b, c);
               break;
           }
           // fall thru
       case 0x80:  // Note off
           note_off(;
           break;
/*          
       case 0xA0:  // Note aftertouch
           break;
       case 0xB0:  // Controller
           break;
       case 0xC0:  // Program change
           break;
       case 0xD0:  // Channel aftertouch
           break;
       case 0xE0:  // Pitch bend
           break;
       case 0xF0:  // Sysex / Meta
           break;
*/
   }
}

void midi(unsigned c)
{
   static const unsigned ps[8] = { 2, 2, 2, 2, 1, 1, 2, 4 };
   static unsigned state = 0;
   static unsigned rs = 0;
   static unsigned d1 = 0;

   if(c & 0x80) {                  // First octect of sequence
       rs = c;
       state =  ps[(rs >> 4) & 7];
   } else {
       switch(state) {
           case 0:                 // Discard          
               break;
           case 1:                 // Second and final octet
               midi_ex(rs, c, 0);
               break;
           case 2:                 // Second octet of three
               d1 = c;
               ++state;
               break;
           case 3:                 // Third and final octet
               midi_ex(rs, d1, c);
               state = 2;
               break;
           //case 4:               // Sysex data
           //  break;
           }
   }
}

void main(void)
{
   unsigned n;

   WDTCTL = WDTPW + WDTHOLD;

   CSCTL0 = 0xA500;    // Unlock clock registers
   CSCTL1 = DCORSEL | DCOFSEL1 | DCOFSEL0; // 24 MHz
   CSCTL2 = 0x0333;    // Use DCO clock for ACLK, SMCLK and MCLK
   CSCTL3 = 0x0000;    // Set all clock dividers to 1
   //CSCTL4 =
   //CSCTL5 = 

   P1DIR  = 0x01;      // PWM audio out
   P1REN  = 0x00;      //
   P1OUT  = 0x00;      //
   P1SEL0 = 0x01;      // Enable Timer A output
   P1SEL1 = 0x00;      //

   P2DIR  = 0x00;      //
   P2REN  = 0x00;      //
   P2OUT  = 0x00;      //
   P2SEL0 = 0x00;      //
   P2SEL1 = 0x03;      // Enable UART UCA0

   P3DIR  = 0x10;      //
   P3REN  = 0x00;      //
   P3OUT  = 0x00;      //
   P3SEL0 = 0x10;      // SMCLK output
   P3SEL1 = 0x10;      // SMCLK output

   P4DIR  = 0x00;      //
   P4REN  = 0x00;      //
   P4OUT  = 0x00;      //
   P4SEL0 = 0x00;      //
   P4SEL1 = 0x00;      //

   PJDIR  = 0x0F;      // 4 LEDs
   PJREN  = 0x00;      //
   PJOUT  = 0x0F;      //
   PJSEL0 = 0x00;      //
   PJSEL1 = 0x00;      //

   UCA0CTLW0 = 0x0080;             // 
   UCA0BRW = 24000000 / 9600;      // Fraunchpad UART
   //UCA0BRW = 24000000 / 31250;   // Standard MIDI bit rate

   synth_init();

   //set_note(0, 69, 700);  // 440 Hz test

   for(n = 0; n < 16; ++n) assign[n] = -1;

   for(; {
       if(UCA0IFG & 1) {
           UCA0IFG &= ~1;
           n = UCA0RXBUF;
           midi(n);
       }
   }
}

 

;synth.asm

       .cdecls C, LIST, "msp430fr5739.h"

smplrt  .equ    32000                   ; Sample rate

       .text
       .global set_tick
       .global get_tick
       .global synth_init
       .global set_note

       .bss    tick, 2
       .bss    pwm_out, 2
       .bss    phase_inc, 6 * 16       ; Phase increment LSW/MSW (from note table)
                                       ; Level
       .bss    phase_acc, 4 * 16       ; Phase accumulator LSW/MSW

set_tick
       mov     R12, &tick
       reta

get_tick
       mov     &tick, R12
       reta

synth_init
       mov     #0x0210, &TA0CTL        ; Timer A config: SMCLK, count up
       mov     #750, &TA0CCR0          ; Setup Timer A period for 32000 sps
       mov     #375, &TA0CCR1          ; Setup Timer A compare
       mov     #0x00E0, &TA0CCTL1      ; Setup Timer A reset/set output mode
                                       ;
       mov     #phase_inc, R12         ; Clear all phase inc and accum
       mov     #5 * 16, R14            ; Word count
       clr     0(R12)                  ; Clear word
       incd    R12                     ; Next word
       dec     R14                     ; Dec word count
       jne     $ - 8                   ; Loop until all words done...
                                       ;
       eint                            ; Enable interupts
       bis     #0x0010, &TA0CCTL0      ; Enable PWM interupt
                                       ;
       reta                            ;
                                       ;
synth_isr                               ;
       mov     &pwm_out, &TA0CCR1      ; Output sample
                                       ;
       push    R4                      ; Wavetable pointer
       push    R5                      ; Phase increment / level pointer
       push    R6                      ; Phase accumulator pointer
       push    R7                      ; Voice count
       push    R8                      ; Wave sample pointer / next sample
       push    R9                      ; Wave sample
       push    R10                     ; Voice mix accumulator MSW
       push    R11                     ; Voice mix accumulator LSW
                                       ;
       mov     #sine, R4               ; Get wavetable pointer
       mov     #phase_inc, R5          ; Setup phase increment pointer
       mov     #phase_acc, R6          ; Setup phase accumulator pointer
       mov     #16, R7                 ; Setup voice count
       mov     #9, R7                  ;                                       
       clr     R10                     ; Clear voice mix
       clr     R11                     ;
voice_loop                              ;
       mov     @R6+, &MPYS32L          ; Get phase acc LSW (fraction) to multiplier
       clr     &MPYS32H                ;
       mov     @R6+, R8                ; Get phase acc MSW (wave table index)
       mov.b   R8, R8                  ; Clear MSB (use mask for smaller / larger tables)
       add     R4, R8                  ; Add wavetable pointer
       mov     @R8+, R9                ; Get wave sample
       mov     @R8, R8                 ; Get next wave sample
       sub     R9, R8                  ; Calc delta
       mov     R8, &OP2L               ; Multiply by delta
       subc    R8, R8                  ; Sign extend delta
       mov     R8, &OP2H               ;
       add     @R5+, -4(R6)            ; Update phase acc
       addc    @R5+, -2(R6)            ;
       add     &RES1, R9               ; Add interpolation to sample
       mov     R9, &MPYS               ; Multiply by voice level
       mov     @R5+, &OP2L             ;
       add     &RES0, R11              ; Update mix
       addc    &RES1, R10              ;
       dec     R7                      ; Dec voice count
       jne     voice_loop              ; Next voice...
                                       ;
       add     #375, R10               ; Bias to center of PWM range
       mov     R10, &pwm_out           ;
                                       ;
       dec     &tick                   ;
       jc      $ + 6                   ;
       clr     &tick                   ;
                                       ;                                       
       pop     R11                     ;
       pop     R10                     ;
       pop     R9                      ;
       pop     R8                      ;
       pop     R7                      ;
       pop     R6                      ;
       pop     R5                      ;
       pop     R4                      ;
       reti                            ;
                                       ;
set_note                                ;
       push    R14                     ; Save level
       mov     R12, R14                ; Voice * 6
       add     R14, R12                ; (+1 = *2)
       add     R14, R12                ; (+1 = *3)
       rla     R12                     ; (*2 = *6)
       add     #phase_inc, R12         ; Add phase inc pointer
       cmp     #128, R13               ; Out of range note values are note off
       jhs     note_off                ;
       clr     R14                     ; Clear octave count
tst_note                                ;
       cmp     #116, R13               ; Within note table?
       jge     get_pi                  ; Yes...
       inc     R14                     ; Inc octave count
       add     #12, R13                ; Add octave to note
       jmp     tst_note                ; Check again...            
get_pi                                  ; Get phase increment
       sub     #116, R13               ; Adjust for first note in table
       rla     R13                     ; MIDI note * 4
       rla     R13                     ;
       add     #notes, R13             ; Add note table pointer
       mov     @R13+, R15              ; Get LSW
       mov     @R13, R13               ; Get MSW
       tst     R14                     ; Shifting required?
       jeq     set_phase               ; No...
shift_phase                             ;
       rra     R13                     ; Shift phase inc
       rrc     R15                     ;
       dec     R14                     ; Dec octave count
       jne     shift_phase             ; Repeat until zero...
set_phase                               ;
       mov     R15, 0(R12)             ; Set phase inc
       mov     R13, 2(R12)             ;
       pop     4(R12)                  ; Set voice level
       reta                            ; Return
                                       ;
note_off                                ;
       incd    SP                      ; Discard level                                     
       clr     0(R12)                  ; Clear phase inc
       clr     2(R12)                  ;
       .if 0                           ; Note: Abrupt return to zero causes poping
       clr     4(R12)                  ; Clear level
       add     #phase_acc - phase_inc, R12 ; Phase accum pointer
       clr     0(R12)                  ; Clear phase accum
       clr     2(R12)                  ;
       .endif                          ;
       reta                            ; Return
                                       ;
                                       ;
notes                                   ; MIDI Note  Frequency
       .if smplrt == 32000             ; 32000 sps
       .long    3483828                ; 116  G#8  6644.87457275391
       .long    3690988                ; 117   A8  7040.00091552734
       .long    3910465                ; 118  A#8  7458.62007141113
       .long    4142993                ; 119   B8  7902.13203430176
       .long    4389349                ; 120   C9  8372.01881408691
       .long    4650353                ; 121  C#9  8869.84443664551
       .long    4926877                ; 122   D9  9397.27210998535
       .long    5219845                ; 123  D#9  9956.06422424316
       .long    5530233                ; 124   E9  10548.0823516846
       .long    5859077                ; 125   F9  11175.3025054932
       .long    6207476                ; 126  F#9  11839.8208618164
       .long    6576592                ; 127   G9  12543.8537597656
       .endif                          ;
                                       ;        
       .if smplrt == 48000             ; 48000 sps
       .long    2322552                ; 116  G#8  6644.87457275391
       .long    2460658                ; 117   A8  7039.99900817871
       .long    2606977                ; 118  A#8  7458.62102508545
       .long    2761996                ; 119   B8  7902.13394165039
       .long    2926232                ; 120   C9  8372.01690673828
       .long    3100235                ; 121  C#9  8869.84348297119
       .long    3284585                ; 122   D9  9397.27306365967
       .long    3479896                ; 123  D#9  9956.06231689453
       .long    3686822                ; 124   E9  10548.0823516846
       .long    3906052                ; 125   F9  11175.3044128418
       .long    4138318                ; 126  F#9  11839.8227691650
       .long    4384395                ; 127   G9  12543.8547134399
       .endif                          ;                   

sine
       .int    0
       .int    804
       .int    1608
       .int    2410
       .int    3212
       .int    4011
       .int    4808
       .int    5602
       .int    6393
       .int    7179
       .int    7962
       .int    8739
       .int    9512
       .int    10278
       .int    11039
       .int    11793
       .int    12539
       .int    13279
       .int    14010
       .int    14732
       .int    15446
       .int    16151
       .int    16846
       .int    17530
       .int    18204
       .int    18868
       .int    19519
       .int    20159
       .int    20787
       .int    21403
       .int    22005
       .int    22594
       .int    23170
       .int    23731
       .int    24279
       .int    24811
       .int    25329
       .int    25832
       .int    26319
       .int    26790
       .int    27245
       .int    27683
       .int    28105
       .int    28510
       .int    28898
       .int    29268
       .int    29621
       .int    29956
       .int    30273
       .int    30571
       .int    30852
       .int    31113
       .int    31356
       .int    31580
       .int    31785
       .int    31971
       .int    32137
       .int    32285
       .int    32412
       .int    32521
       .int    32609
       .int    32678
       .int    32728
       .int    32757
       .int    32767
       .int    32757
       .int    32728
       .int    32678
       .int    32609
       .int    32521
       .int    32412
       .int    32285
       .int    32137
       .int    31971
       .int    31785
       .int    31580
       .int    31356
       .int    31113
       .int    30852
       .int    30571
       .int    30273
       .int    29956
       .int    29621
       .int    29268
       .int    28898
       .int    28510
       .int    28105
       .int    27683
       .int    27245
       .int    26790
       .int    26319
       .int    25832
       .int    25329
       .int    24811
       .int    24279
       .int    23731
       .int    23170
       .int    22594
       .int    22005
       .int    21403
       .int    20787
       .int    20159
       .int    19519
       .int    18868
       .int    18204
       .int    17530
       .int    16846
       .int    16151
       .int    15446
       .int    14732
       .int    14010
       .int    13279
       .int    12539
       .int    11793
       .int    11039
       .int    10278
       .int    9512
       .int    8739
       .int    7962
       .int    7179
       .int    6393
       .int    5602
       .int    4808
       .int    4011
       .int    3212
       .int    2410
       .int    1608
       .int    804
       .int    0
       .int    -804
       .int    -1608
       .int    -2410
       .int    -3212
       .int    -4011
       .int    -4808
       .int    -5602
       .int    -6393
       .int    -7179
       .int    -7962
       .int    -8739
       .int    -9512
       .int    -10278
       .int    -11039
       .int    -11793
       .int    -12539
       .int    -13279
       .int    -14010
       .int    -14732
       .int    -15446
       .int    -16151
       .int    -16846
       .int    -17530
       .int    -18204
       .int    -18868
       .int    -19519
       .int    -20159
       .int    -20787
       .int    -21403
       .int    -22005
       .int    -22594
       .int    -23170
       .int    -23731
       .int    -24279
       .int    -24811
       .int    -25329
       .int    -25832
       .int    -26319
       .int    -26790
       .int    -27245
       .int    -27683
       .int    -28105
       .int    -28510
       .int    -28898
       .int    -29268
       .int    -29621
       .int    -29956
       .int    -30273
       .int    -30571
       .int    -30852
       .int    -31113
       .int    -31356
       .int    -31580
       .int    -31785
       .int    -31971
       .int    -32137
       .int    -32285
       .int    -32412
       .int    -32521
       .int    -32609
       .int    -32678
       .int    -32728
       .int    -32757
       .int    -32767
       .int    -32757
       .int    -32728
       .int    -32678
       .int    -32609
       .int    -32521
       .int    -32412
       .int    -32285
       .int    -32137
       .int    -31971
       .int    -31785
       .int    -31580
       .int    -31356
       .int    -31113
       .int    -30852
       .int    -30571
       .int    -30273
       .int    -29956
       .int    -29621
       .int    -29268
       .int    -28898
       .int    -28510
       .int    -28105
       .int    -27683
       .int    -27245
       .int    -26790
       .int    -26319
       .int    -25832
       .int    -25329
       .int    -24811
       .int    -24279
       .int    -23731
       .int    -23170
       .int    -22594
       .int    -22005
       .int    -21403
       .int    -20787
       .int    -20159
       .int    -19519
       .int    -18868
       .int    -18204
       .int    -17530
       .int    -16846
       .int    -16151
       .int    -15446
       .int    -14732
       .int    -14010
       .int    -13279
       .int    -12539
       .int    -11793
       .int    -11039
       .int    -10278
       .int    -9512
       .int    -8739
       .int    -7962
       .int    -7179
       .int    -6393
       .int    -5602
       .int    -4808
       .int    -4011
       .int    -3212
       .int    -2410
       .int    -1608
       .int    -804
       .int    0


                                   ; Interrupt Vectors
       .sect   ".int53"            ; TA0CCR0 CCIFG0
       .short  synth_isr           ;
                                   ;
       .end                        ;

Share this post


Link to post
Share on other sites
Looking good and wow blue LEDs?

Unfortunately the 8 blue LED are split across two ports. I use the four on port J to show MIDI message activity. Each time a message is parsed the port is incremented, so the LED are a binary count of MIDI messages.

 

What's the assembly code for?

 

C code:

 

main() - Setup clocks and ports. Check for UART rx and send to MIDI parser.

 

midi() - Parse serial data into MIDI messages.

 

midi_ex() - Dispatch MIDI message

 

note_on() - Find a free voice and assign the note to it

 

note_off() - Find the note assignment and turn the voice off

 

 

Asm code:

 

synth_init() - Initialize the synthesis hardware (Timer A0) and setup initial values in RAM.

 

synth_isr() - Synthesis Interrupt Service Routine. Do NCO for each voice and sum the NCO outputs. Send audio sample to Timer A0 for PWM. This is the most important code. Study this if you want to understand simple wavetable synthesis.

 

set_note() - Convert a MIDI note value to a phase increment value and setup a voice. A lookup table is used for the highest octave, lower octaves are divided by 2 as needed. An out of range note value will turn off the voice (phase increment set to 0).

 

set_tick() - Set a value that will decrement down to zero at the synth sample rate. Can be used for timing in the mainline code.

 

get_tick() - Get the tick count

Share this post


Link to post
Share on other sites

Here is synth.asm rewritten in C.

I briefly tested it and is seems to work properly. It can only do 3 voices - the asm code can do 9 (should do 16 - not sure why it is running slow).

 

// synth.c
#include "msp430fr5739.h"
#include "string.h"

static unsigned tick = 0;
static unsigned sample = 0;
static struct TVOICE {
   unsigned long pi;
   unsigned vl;
   union {
       unsigned long l;
       struct {
           unsigned l;
           unsigned h;
       } w;
   } pa;
} voices[16];

void set_tick(unsigned n)
{
   tick = n;
}

unsigned get_tick(void)
{
   return tick;
}

void synth_init(void)
{
   TA0CTL = 0x0210;        // Timer A config: SMCLK, count up
   TA0CCR0 = 750;          // Setup Timer A period for 32000 sps
   TA0CCR1 = 375;          // Setup Timer A compare
   TA0CCTL1 = 0x00E0;      // Setup Timer A reset/set output mode
                           //
   memset(voices, 0, sizeof(voices)); // Clear voice structure
                           //
   _EINT();                // Enable interupts
   TA0CCTL0 |= 0x0010;     // Enable PWM interupt
}

static int wave[257] = {
   0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, 7179, 7962, 8739, 9512, 10278, 11039, 11793,
   12539, 13279, 14010, 14732, 15446, 16151, 16846, 17530, 18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594,
   23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790, 27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956,
   30273, 30571, 30852, 31113, 31356, 31580, 31785, 31971, 32137, 32285, 32412, 32521, 32609, 32678, 32728, 32757,
   32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285, 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571,
   30273, 29956, 29621, 29268, 28898, 28510, 28105, 27683, 27245, 26790, 26319, 25832, 25329, 24811, 24279, 23731,
   23170, 22594, 22005, 21403, 20787, 20159, 19519, 18868, 18204, 17530, 16846, 16151, 15446, 14732, 14010, 13279,
   12539, 11793, 11039, 10278, 9512, 8739, 7962, 7179, 6393, 5602, 4808, 4011, 3212, 2410, 1608, 804,
   0, -804, -1608, -2410, -3212, -4011, -4808, -5602, -6393, -7179, -7962, -8739, -9512, -10278, -11039,
   -11793, -12539, -13279, -14010, -14732, -15446, -16151, -16846, -17530, -18204, -18868, -19519, -20159, -20787, -21403, -22005, -22594,
   -23170, -23731, -24279, -24811, -25329, -25832, -26319, -26790, -27245, -27683, -28105, -28510, -28898, -29268, -29621, -29956, -30273,
   -30571, -30852, -31113, -31356, -31580, -31785, -31971, -32137, -32285, -32412, -32521, -32609, -32678, -32728, -32757, -32767, -32757,
   -32728, -32678, -32609, -32521, -32412, -32285, -32137, -31971, -31785, -31580, -31356, -31113, -30852, -30571, -30273, -29956, -29621,
   -29268, -28898, -28510, -28105, -27683, -27245, -26790, -26319, -25832, -25329, -24811, -24279, -23731, -23170, -22594, -22005, -21403,
   -20787, -20159, -19519, -18868, -18204, -17530, -16846, -16151, -15446, -14732, -14010, -13279, -12539, -11793, -11039, -10278, -9512,
   -8739, -7962, -7179, -6393, -5602, -4808, -4011, -3212, -2410, -1608, -804,
   0
};

#pragma vector = TIMER0_A0_VECTOR
__interrupt void synth_isr(void)
{
   register int *wp, s, sn;                                // Wavetable pointer and samples
   register long w = 0;                                    // Clear sum of voices
   register unsigned n = 3;                                // 16 voices to do
   register struct TVOICE *v = voices;                     // Make pointer to voice
   TA0CCR1 = sample;                                       // Output sample
   do {                                                    //
       wp = wave + (v->pa.w.h & 0x00FF);                   // Make pointer to wavetable sample
       s  = *wp++;                                         // Get sample from wavetable
       sn = *wp;                                           // Get next sample from wavetable
       s += (((long int)(sn - s) * v->pa.w.l) >> 16);      // Interpolate
       w += ((long int)s * v->vl);                         // Level and sum
       v->pa.l += v->pi;                                   // Update phase accumulator
       ++v;                                                // Next voice
   } while(--n);                                           //
   sample = (w >> 16) + 375;                               // Scale and offset sample, save
   if(tick) --tick;                                        // Decrement tick if not zero
}

static unsigned long notes[12] = {
   3483828,                // 116  G#8  6644.87457275391
   3690988,                // 117   A8  7040.00091552734
   3910465,                // 118  A#8  7458.62007141113
   4142993,                // 119   B8  7902.13203430176
   4389349,                // 120   C9  8372.01881408691
   4650353,                // 121  C#9  8869.84443664551
   4926877,                // 122   D9  9397.27210998535
   5219845,                // 123  D#9  9956.06422424316
   5530233,                // 124   E9  10548.0823516846
   5859077,                // 125   F9  11175.3025054932
   6207476,                // 126  F#9  11839.8208618164
   6576592                 // 127   G9  12543.8537597656
};

void set_note(unsigned vi, unsigned n, unsigned l)
{
   struct TVOICE *v = &voices[vi];
   unsigned o = 0;
   if(n > 127) {                   // Out of range notes are note off
       v->pi = 0;                  // Clear phase increment
       //v->vl = 0;                // Clear level
       //v->PA.pa = 0;             // Clear phase accumulator
       return;                     // Return
   }                               //
   while(n < 116) {                // Note must be in table
       n += 12;                    // Octave higher
       ++o;                        // Inc octave count
   }                               //
   v->pi = notes[n - 116] >> o;    // Set phase increment
   v->vl = l;                      // Set level
}

Share this post


Link to post
Share on other sites

Please forgive me but where can I find out about the -> construct in your example, please?

 

v->pa.l += v->pi;

Share this post


Link to post
Share on other sites
Any C material. it's the structure member dereference operator.

 

Ok. Found it in section 6.2 of K&R. A bit incomprehensible or cryptic at this stage for me. On the other hand, learning about the 'register' keyword was fantastic. Thanks...

 

Cheerful regards, Mike

Share this post


Link to post
Share on other sites

The way the parser handles running status and note on messages is not clear, so here is an explanation...

 

---

 

Running status allows the first octet of a MIDI message to be ommited if it is the same as the last message.

For example, this sequence of three messages: 0x80 0x11 0x22 / 0x80 0x33 0x44 / 0x80 0x55 0x66

Can be sent as: 0x80 0x11 0x22 0x33 0x44 0x55 0x66

 

---

 

MIDI messages begin with an octet that has the msb set (0x80 to 0xFF). All folowing octets will not have the msb set (0x00 to 0x7F). Most messages are two or three octets long. The exception are system messages than can be 1 to many octets. The MIDI parser checks if the msb of an octet is set and will initialize a state machine that will then handle the following octets. If the message is two octets long the state machine is set to state 1. If the message is three octets long the state machine is set to state 2. If the message is System Common the state machine is set to state 4.

 

void midi(unsigned c)
{
   static const unsigned ps[8] = { 2, 2, 2, 2, 1, 1, 2, 4 };  // Initial state for message
   static unsigned state = 0;
   static unsigned rs = 0;
   static unsigned d1 = 0;

   if(c & 0x80) {                  // First octet of sequence
       if(c > 0xF7) return;      // Ignore Real Time Category messages (one octet)
       rs = c;                     // Save first octet (running status)
       state =  ps[(rs >> 4) & 7]; // Set inital state of state machine
   } ...

 

State 1 handles messages that are 2 octets long. The first octet was saved in the variable rs, so when the next octet is received the message is complete and can be processed. The state machine remains in state 1 to handle running status.

 

   ... else {
       switch(state) {
           case 0:                 // Discard          
               break;
           case 1:                 // Second and final octet
               midi_ex(rs, c, 0);
               break;

 

States 2 and 3 handle messges that are 3 octets long. When the second octet is received it is stored in the varialble d1 and the state is incremented to 3. When the third octet is received it is handled by state 3. The message is now complete and can be processed. The state is set to 2 to handle running status.

 

           case 2:                 // Second octet of three
               d1 = c;
               ++state;
               break;
           case 3:                 // Third and final octet
               midi_ex(rs, d1, c);
               state = 2;
               break;

 

State 4 would handle System Common messages. This is currently not implemented.

 

           //case 4:               // System Common
           //  break;
           }

 

---

 

A note on message with a veloctiy of 0 is handled as a note off message (with velocity of 0). This allows running status to be used with a mix of note on and note off messages.

For example, these messages: 0x90 0x12 0x34 / 0x80 0x12 0x00 / 0x90 0x56 0x78 / 0x80 0x56 0x00

Can be converted to this: 0x90 0x12 0x34 / 0x90 0x12 0x00 / 0x90 0x56 0x78 / 0x90 0x56 0x00

And then running status used: 0x90 0x12 0x34 0x12 0x00 0x56 0x78 0x56 0x00

 

The switch/case statement that handles message dispatch checks if the velocity of a note on message is zero and will fall thru to the note off handler. This will result in a call to note_off() when a note on message has a velocity of zero.

 

void midi_ex(unsigned a, unsigned b, unsigned c)
{
   ++PJOUT;
   switch(a & 0xF0) {
       case 0x90:  // Note on
           if(c) {  // Check for non-zero velocity
               note_on(b, c);
               break;
           }
           // fall thru
       case 0x80:  // Note off
           note_off(;
           break;

Share this post


Link to post
Share on other sites

There are couple of things I would like to comment on.

 

Looks like your parser is channel unaware. This may be a problem when there are more than one used:

0x90 0x40 0x7F ch1 note on

0x81 0x40 0x7F ch2 note off, wrong note is turned off

I guess this isn't big deal because you could filter MIDI channels on your port.

 

RealTime Category messages (status 0xF8 to 0xFF,) will cancel running status in your parser, all running status data after RT message will be lost.

RT messages are one byte and can be inserted anywhere, even between 2 data bytes. They should not cancel running status.

Most of keyboards with sequencers will be sending those out.

Share this post


Link to post
Share on other sites

Responding to all channels is intentional - I don't want to have to edit MIDI files to get all notes to play. I just turn off channel 10 (percussion) and let the synth play everything else.

 

A future version may be multitimbral and respond to program change messages on a per channel basis.

 

Thanks for the reminder about RTC. Easy fix to handle them...

 

void midi(unsigned c)
{
   static const unsigned ps[8] = { 2, 2, 2, 2, 1, 1, 2, 4 };  // Initial state for message
   static unsigned state = 0;
   static unsigned rs = 0;
   static unsigned d1 = 0;

   if(c & 0x80) {                  // First octet of sequence
       if(c > 0xF7) return;        // Ignore Real Time Category messages (single octet)
       rs = c;                     // Save first octet (running status)
       state =  ps[(rs >> 4) & 7]; // Set inital state of state machine
   } ...

 

The parser wan't intended to be comprehensive - just enough to make some noise. Proper handling of all the 0xFx messages will require more code.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...