' ********************************************************************
' *                        PIC firmware for <Gesta>                  *
' *                          beater solenoids                        *
' *                  coded by dr.Godfried-Willem Raes                *
' * http://www.logosfoundation.org/instrum_gwr/gesta/picworks        *
' *                  source code development directory:              *
' *                       Gesta_beat.bas                             *
' ********************************************************************
' 24.09.2019: Starting from the code for the damper board and using the
'             plain vanilla irq coding.
'             This code is for the 18-output PCB, made for tubo. 
' 25.09.2019: Version 1.0 ready.
'             to do: block note on if timer still active.
'             need notes flags to do this fast.
' 26.09.2019: Dur[] lookup changed.
'             note-on blocking implemented. 
'             Firmware loaded in board.  
'             we could also implement a duty cycle protection by
'             implementing wait-timers after time-out of the on-pulses.
' 29.09.2019: first testing with all hardware connected.
'             Velo pulstimes need to be taken a lot shorter! 
'             divided by 8, the range becomes 3.75ms to 18.75ms
'             With this scaling, the anchors start to move at velo=30
'             The traject for striking is sufficient from 64 on
'             The end-positions are reached with value 79
'             127 gives a quite strong hit.
'             It's clear we have to calculate a new lookup table...  
' 01.12.2019: New lookup tables prepared.
'             such that velo = 32 gives p (76dBA) for all notes on tubo
'             and       velo = 64 fives f (90dBA) for all notes on tubo.  
'             We can add more refinement by creating 3 lookups, one for each group of 6 notes/tube diameters.
'             Implemented, but for now, the three lookups are identical.
' 02.12.2019: 3 different lookups implemented and based on dbA measurements.
'             velo 32 = 76dBA
'             velo 64 = 90dBA
'             velo 96 = 102dBA
' ---------------------------------------------------------------------------
' 08.10.2026: starting from <Tubo>, coding for <Gesta>
'             to do: remove note on with velo=0 coding, as we can filter this out on the hub board.
'             we could improve speed, by remapping notes to 0 - 25 such that we can use
'             On noteOn Gosub proc60(), proc61(), proc64()...  or, even more simple:
'             On noteOn Gosub proc0 (), proc1 (), ...
'             On the beat_high processor, we can add an offset of 25 to the notes such that we run notes 0-48 
' 14.10.2026: We better use 2 channels internally: Lowmodulechannel en HighModuleChannel
'             On Gosub's implemented with procs. 
'             Strange compiler bugs...   'ETL' is an Unknown or Unsupported Comparison directive.
'             Please contact storypainter@hotmail.com!            (sic!)
' 15.10.2026: compiler has problems with the names of procedures!!!
' 17.10.2026: beater board, highmodule programmed.
' 19.10.2026: beater board, lowmodule programmed.

'metacompiler innstructions
'we need 4 different compilations here!
$define Beaters = 0
$define LowModule = 1
'$define HighModule = 2

'$define Dampers = 1
'$define LowDamp = 3
'$define HiDamp = 4

'clear
Reminders = On
Warnings = On
Include "18F4620.inc"     '   (40MHz)

' Mapping defines for midi-events on pin outputs and inputs:
' This is very specific for the 26 output board 2017
' mapping:
$ifdef LowModule
    $define Beat0  PORTA.0          ' 60
    $define Beat1  PORTA.1          ' 61
    $define Beat2  PORTA.2          ' 64
    $define Beat3  PORTA.3          ' 65
    $define Beat4  PORTA.4          ' 68
    $define Beat5  PORTA.5          ' 69
    $define Beat6  PORTE.0          ' 72 - pin8
    $define Beat7  PORTE.1          ' 73
    $define Beat8  PORTE.2          ' 76
    $define Beat9  PORTC.0          ' 77
    $define Beat10 PORTC.1          ' 80
    $define Beat11 PORTC.2          ' 81
    $define Beat12 PORTC.3          ' 84
    $define Beat13 PORTD.0          ' 85
    $define Beat14 PORTD.1          ' 88
    $define Beat15 PORTD.7          ' 89
    $define Beat16 PORTD.6          ' 92
    $define Beat17 PORTD.5          ' 93
    $define Beat18 PORTD.4          ' 96
    $define Beat19 PORTC.5          ' 97
    $define Beat20 PORTC.4         ' 100
    $define Beat21 PORTD.3         ' 101
    $define Beat22 PORTD.2         ' 104
    $define Beat23 PORTB.0         ' 105
    $define Beat24 PORTB.1         ' 108
    $define Sol25  PORTB.2         ' nc
$else
    $define Beat0  PORTA.0          ' 62
    $define Beat1  PORTA.1          ' 63
    $define Beat2  PORTA.2          ' 66
    $define Beat3  PORTA.3          ' 67
    $define Beat4  PORTA.4          ' 70
    $define Beat5  PORTA.5          ' 71
    $define Beat6  PORTE.0          ' 74 - pin8
    $define Beat7  PORTE.1          ' 75
    $define Beat8  PORTE.2          ' 78
    $define Beat9  PORTC.0          ' 79
    $define Beat10 PORTC.1          ' 82
    $define Beat11 PORTC.2          ' 83
    $define Beat12 PORTC.3          ' 86
    $define Beat13 PORTD.0          ' 87
    $define Beat14 PORTD.1          ' 90
    $define Beat15 PORTD.7          ' 91
    $define Beat16 PORTD.6          ' 94
    $define Beat17 PORTD.5          ' 95
    $define Beat18 PORTD.4          ' 98
    $define Beat19 PORTC.5          ' 99
    $define Beat20 PORTC.4         ' 102
    $define Beat21 PORTD.3         ' 103
    $define Beat22 PORTD.2         ' 106
    $define Beat23 PORTB.0         ' 107
    $define Sol24  PORTB.1         ' nc
    $define Sol25  PORTB.2         ' nc
$endif

    $define Loopspeed PORTB.4     ' for loopspeed measurement
'red LED for debug:
    $define Debug_Led PORTB.3   ' for testing - red led - watchdog - activated in IRQ code.

Declare All_Digital = True        ' makes all analog pins, digital for I/O
Clear SSPCON1.5                   ' make sure RC3 is free for use.
' configure the input and output pins:
TRISA = %11000000     'bits set to 0 are output, 1 = input  - bits 6 and 7 are the clock!
TRISB = %11100000     'bits 6 and 7 are for the ICP, bit 5 is the red LED
TRISC = %11000000     'RC6 en RC7 zijn USART  I/O and must be set to input
TRISD = %00000000     'all bits can be used
TRISE = %11101000     'low nibble only, bit3 (RE3) is MCLR
                      'bit 4 must be zero to disable PSP mode on port D  - gwr 20.01.2012
                      'this was the bug we had!

'constant definitions:
' midi-mappings:
Symbol Lowtes = 60      ' celesta
Symbol Hightes = 108    
Symbol LowChannel = 0    ' even
Symbol HighChannel = 1   ' odd

'initialisations for the midi input parser:
$ifdef Beaters
    $ifdef LowModule
    Symbol Midichannel = LowChannel      ' = 0 internal channel for this module
    $else
    Symbol Midichannel = HighChannel     ' = 1
    $endif
$endif
$ifdef Dampers
    $ifdef LowDamp
           Midichannel = LowChannel + 2   ' = 2
    $endif
    $ifdef HiDamp
           Midichannel = HighChannel + 2   ' = 3
    $endif
$endif
Symbol NoteOff_Status = 128 + Midichannel      ' 2 bytes follow
Symbol NoteOn_Status  = 144 + Midichannel
Symbol Keypres_Status = 160 + Midichannel
Symbol Control_Status = 176 + Midichannel
Symbol ProgChange_Status = 192 + Midichannel   ' 1 byte message
Symbol Aftertouch_Status = 208 + Midichannel   ' 1 byte follows
Symbol Pitchbend_Status  = 224 + Midichannel   ' lsb msb follow
$ifdef Beaters
    $ifdef LowModule
        Symbol NrTasks = 25
    $else
        Symbol NrTasks = 24
    $endif 
$endif
$ifdef Dampers
        Symbol NrTasks = 18
$endif

Symbol LastTask = NrTasks - 1

' Setup the USART
Declare Hserial_Baud = 31250                   ' Set baud rate for the USART to MIDI specs.
Declare Hserial_TXSTA = 0x24                   ' instead of the normal 0x20 - ?? 0x24
Declare Hserial_Clear = On                   ' should clear on errors. Bytes get lost of course...
                                             ' switched on 22.09.2019 
' Create variables:
    Dim Cnt As Dword System                       '32 bit counter
    Dim CntHw As Cnt.Word1 ' Word System          'used in the timer0 interrupt, where it is incremented
    Dim CntLw As TMR0L.Word                       'this is the trick to read both TMR0L and TMR0H
                                                  'it makes Cntlw the low word of cnt, when we use cnt.word0=CntLw
    Dim time As Cnt                               'make an alias, to avoid having to copy a dword in the main loop.
    Dim Cnt3 As Dword System
    Dim Cnt3Hw As Cnt3.Word1                                                  
    Dim Tim3 As TMR3L.Word                        ' same trick for timer3 
    Dim Bytein As Byte                            ' midi byte read from buffer
    Dim StBit As Bytein.7                         ' highest bit of ByteIn
    Dim i As Byte System                          ' general purpose counter
    Dim Nxt As Dword System
    Dim idx As Byte System
   
    Dim maxtim As time.31                 ' overflow bit, will cause timer reset after 1h45
'    Dim Sr as TMR0L.7                             '512 S/s   - this works 
                                                  ' As TMR0H.1  would be 128 S/s
                                                  ' As TMR0H.2  would be 64 S/s
                                                  ' As TMR0H.3  would be 32 S/s
                                                  ' As TMR0H.4  would be 16 S/s
    ' midi variables
    Dim statusbyte As Byte System
    Dim noteUit As Byte System                    ' note off + release value
    Dim release As Byte System
    Dim noteAan As Byte System                    ' note on + release value
    Dim velo As Byte System
    Dim notePres As Byte System                   ' note pressure + pressure value
    Dim pres As Byte System
    Dim Ctrl As Byte System                       ' continuous controller + value
    Dim value As Byte System
    Dim prog As Byte System                       ' program change + program-byte
    Dim aft  As Byte System                       ' channel aftertouch
    Dim pblsb As Byte System                      ' pitch bend lsb
    Dim pbmsb As Byte System                      ' pitch bend msb
    Dim CC66 As  Byte System                      ' global on/off switch
    Dim PowerOn As CC66.0
    Dim IndexIn As Byte System                   ' Pointer to the next empty location in the buffer
    Dim IndexOut As Byte System                  ' Pointer to the location of the oldest character in the buffer
    Dim Ringbuffer[256] As Byte                ' Array for holding received midi messages
    Dim TimVals[NrTasks] As Dword
    Dim notes As Dword                         ' flags only: notes.0 to notes.25  
    Dim Dur0[128] As Word                       ' for valve pulsing notes 
'    Dim Dur1[128] As Word                       ' for valve pulsing notes 54- 59
'    Dim Dur2[128] As Word                       ' for valve pulsing notes 60 - 65   
    Dim stap As Float                          ' for lookup calculation
    Dim tmp As Float

'--------------------------------------------------------------------------
' Load the USART Interrupt handler and buffer read subroutines into memory
Include "Gesta_Irq.Inc" 
'--------------------------------------------------------------------------

' Main program starts here
MAIN:
    High Debug_Led
    DelayMS 5                       ' wait for stability
    Low Debug_Led
    Low Beat0
    Low Beat1
    Low Beat2
    Low Beat3
    Low Beat4
    Low Beat5
    Low Beat6
    Low Beat7
    Low Beat8
    Low Beat9
    Low Beat10
    Low Beat11
    Low Beat12
    Low Beat13
    Low Beat14
    Low Beat15
    Low Beat16
    Low Beat17
    Low Beat18
    Low Beat19
    Low Beat20
    Low Beat21
    Low Beat22
    Low Beat23
$ifdef LowModule
    Low Beat24
    Low Sol25
$else
    Low Sol24
    Low Sol25
$endif
    Dur_Lookup ()
    Set idx
    Set TimVals
    Clear CC66
    Init_Usart_Interrupt ()            ' Initiate the USART serial buffer interrupt
                                     ' this procedure is in the include file
    Clear_Serial_Buffer ()             ' Clear the serial buffer and reset its pointers
                                     ' in the include as well
                                     ' Configure Timer0 for:
'                                      Clear TMR0L and TMR0H registers
'                                      Interrupt on Timer0 overflow
'                                      16-bit operation
'                                      Internal clock source  40MHz
'                                      1:256 Prescaler : thus 40MHz / 256 = 156.250kHz
'                                      6.4 us per clock-tick

'        Opentimer0 (Timer_INT_On & T0_16BIT & T0_SOURCE_INT & T0_PS_1_256)
'        replacing above macro with in-line coding:
         Clear T1CON
         Clear IntConBits_T0IF      ' clear interrupt flag
         Set INTCONBITS_T0IE        ' enable interrupt on overflow
         T0CON = %10000111           ' bit 7 = enable/disable
                                     ' bit 6 = 1=8 bot, 0=16 bit
                                     ' bit 5 = 1 pin input, 0= Internal Clk0
                                     ' bit 4 = HL or LH transition when bit5 =1
                                     ' bit 3 = 1= bypass prescaler, 0= input from prescaler
                                     ' bit 2-0 = prescaler select: 111= 1:256
                                     ' Setup the High priorities for the interrupts
' TIMER1: if enabled, all midi-in is blocked, so it must interfere with the UART
             ' Configure Timer1 for:
'                        Clear TMR1L and TMR1H registers
'                        Interrupt on Timer1 overflow
'                        16-bit read/write mode
'                        Internal clock source
'                        1:8 Prescaler
'
'    OpenTimer1(TIMER_INT_ON & T1_16BIT_RW & T1_SOURCE_INT & T1_PS_1_8)    ' dit kompileert o.k.

' TIMER2: if enabled, the UART stops working...
'    Opentimer2 (Timer_Int_On & T2_POST_1_16 & T2_PS_1_16)     ' dit lukt... maar de timer is nodig voor de UART...

' TIMER3:
' Configure Timer3 for:
'                        Interrupt on Timer3 overflow
'                        16-bit read/write mode
'                        Internal clock source
'                        1:8 Prescaler
'                        Don’t sync external clock input
 ' doing it this way seems to work:
    Clear T3CON
    Clear PIR2BITS_TMR3IF ' clear IRQ flag
    Set PIE2BITS_TMR3IE   ' irq on
    'T3_OSC1En_On ()       ' macro  - sets T3CON
    Clear Tim3            ' Clear TMR3L And TMR3H registers
    Set RCONbits_IPEN      ' Enable priority interrupts
    Clear IPR2bits_TMR3IP   ' Set Timer3 as a low priority interrupt source
    ' we can also set T3Con in one instruction as:
    T3CON = %10110001               ' oef, now it works...
                            ' bit 7 = 16 bit mode
                            ' bit 6,3 = 0, 0
                            ' bit 5,4 = 1:8 prescale
                            ' bit 2 = 0
                            ' bit 1 = 0 Internal clock = Fosc/4
                            ' bit 0 : 1= enable timer 3, 0= disable
                            ' maximum count = 52.42ms, 1 tick =0.8uS, freq.=19Hz
    ' Set up priority interrupts.
  '  IPR1bits_TMR1IP = 0    ' Set Timer1 as a low priority interrupt source
  '  INTCONbits_PEIE = 1    ' Enable peripheral interrupts
  '  INTCONbits_GIE = 1     ' Enable global interrupts


   ' Open the ADC:                   ' not used on this board.
'                                     Fosc/32
'                                     Right justified for 10-bit operation
'                                     Tad value of 0
'                                     Vref+ at Vcc : Vref- at Gnd
'                                     Make AN0 an analogue input
'    OpenADC(ADC_FOSC_32 & ADC_RIGHT_JUST & ADC_0_TAD, ADC_REF_VDD_VSS, ADC_1ANA)

'     Set PIE1bits_RCIE           ' disable/enable interrupt for USART receive
     Clear notes                  ' all flags
     HRSOut Ctrl, 66,64           ' crucial for the code to work....
                                     ' start the main program loop:
Hoofdlus:                            ' Create an infinite loop
Do
        Cnt.Word0 = CntLw            ' read timer 0
        Bytein = GetMidiIn()         ' proc. in irq include        
                                     ' Start the midi parser.
Midi_Parse:
            If Bytein > Control_Status Then    ' here higher statusses are not implemented.
                If Bytein > 253 Then       '254 = midiclock, 255= reset
                                           'midiclock can interrupt all other msg's...
                                           '255 had to be intercepted since thats what we
                                           'get when no new byte flows in.
                Else
                    Clear statusbyte       'reset the status byte
                End If
                GoTo Check_Timers          'throw away...
            EndIf
            If StBit =1 Then               'should be faster than If Bytein > 127 Then
                                           'status byte received, bit 7 is set
                Clear statusbyte           'if on another channel, the statusbyte needs a reset
                Select Bytein              
                       Case NoteOff_Status
                            statusbyte = Bytein
                            Set noteUit          'reset value. Cannot be 0 !!!
                            Set release          '0 is a valid midi note!
                       Case NoteOn_Status
                            statusbyte = Bytein
                            Set noteAan 
                            Set velo
                       Case Keypres_Status          'not used here
                            statusbyte = Bytein
                            Set notePres
                            Set pres
                       Case Control_Status          ' controllers and switches
                            statusbyte = Bytein
                            Set Ctrl
                            Set value
                      ' Case ProgChange_Status       ' could be used for different lookup tables
                      '      statusbyte = Bytein
                      '      Set prog
                      ' Case Aftertouch_Status        ' for fingered vibrato, freq.changes during notes
                      '      statusbyte = Bytein
                      '      Set aft
                      ' Case Pitchbend_Status        ' not on this board.
                      '      statusbyte = Bytein
                      '      Set pblsb
                      '      Set pbmsb
                EndSelect
            Else                                        'midi byte is 7 bits
                Select statusbyte
                       Case 0                           'not a message for this channel
                            GoTo Check_Timers           'disregard
                       Case NoteOff_Status
                            If noteUit = 255 Then
                                noteUit = Bytein
                            Else
                                release = Bytein        'message complete, so we can do the action...
                                ' in principle we cannot receive this command at all for the beaters...
                                ' if we get it for the dampers, it should pulse the dampers ON
                                $ifdef Dampers
                                    If release > 0 Then velo = release Else velo = CC19
                                        ' but we might do better by intreoducing another series of procs as we need a
                                        ' different time lookup here.
                                    'On noteAan GoSub Bp0 (), Bp1 (), Bp2 (), Bp3 (), Bp4 (), Bp5 (), Bp6 (), Bp7 (), Bp8 (), Bp9 (), BpA (), BpB (), BpC (), BpD (), BpE () , BpFuck (), BpG (), BpH ()
                                    On noteUit GoSub CB0(), CB1(), CB2(), CB3(), CB4(), CB5(), CB6(), CB7(), CB8(), CB9(), CBA(), CBB(), CBC(), CBD(), CBE(), CBF() , CBG(), CBH()                              
                                $endif
                                Set noteUit                         'reset
                            EndIf
                       Case NoteOn_Status
                            If noteAan = 255 Then
                                noteAan = Bytein
                            Else
                                velo = Bytein           ' we can no longer receive velo=0
                                ' nieuwe konstruktie:
                                $ifdef LowModule
                                 On noteAan GoSub Bp0 (), Bp1 (), Bp2 (), Bp3 (), Bp4 (), Bp5 (), Bp6 (), Bp7 (), Bp8 (), Bp9 (), BpA (), BpB (), BpC (), BpD (), BpE () , BpFuck (), BpG (), BpH (), BpI (), BpJ (), BpK (), BpL (), BpM (), BpN (), BpO ()
                                $else
                                 On noteAan GoSub Bp0(), Bp1(), Bp2(), Bp3(), Bp4(), Bp5(), Bp6(), Bp7(), Bp8(), Bp9(), BpA(), BpB(), BpC(), BpD(), BpE() , BpFuck() , BpG(), BpH(), BpI(), BpJ(), BpK(), BpL(), BpM(), BpN()
                                $endif 
                                Set noteAan                   'reset !!!
                            EndIf
                       Case Keypres_Status           'could be used for lite flashing
                            If notePres = 255 Then
                                notePres = Bytein
                            Else
                                pres = Bytein
                                KeyPres ()
                            EndIf
                            GoTo Check_Timers
                       Case Control_Status    'this is where the action takes place for controllers
                            If Ctrl = 255 Then
                                Ctrl = Bytein
                            Else
                                value = Bytein
                                Controller ()
                            EndIf
'                       Case ProgChange_Status                ' could be used to select alternative fingering lookups
'                            If prog = 255 Then               'single byte message
'                                 prog = Bytein
'                                 ProgChange ()         ' should not happen when a note is playing!
'                            EndIf
'                        Case Aftertouch_Status               ' for changing vibrato frequency during notes
'                             If aft = 255 Then               ' not implemented on this board
'                                  aft = Bytein
'                                  Aftertouch ()
'                             EndIf
                EndSelect
            EndIf

Check_Timers:
If idx < NrTasks Then          ' we moeten alleen checken wanneer er een timer loopt
   If time >= Nxt Then         ' nagaan of de eerstvolgende timer afgelopen is...
        ' in dit geval is de eerste timer afgelopen en moeten we de juiste aktie ondernemen:
        Set Nxt.31             ' timer reset, is immers afgelopen
        ' aan de hand van idx weten we welke timer dit is
        $ifdef LowModule
        On idx GoSub CB0 (), CB1 (), CB2(), CB3(), CB4(), CB5(), CB6(), CB7(), CB8(), CB9(), CBA(), CBB(), CBC(), CBD(), CBE(), CBF() , CBG(), CBH(), CBI(), CBJ (), CBK (), CBL (),CBM (),CBN (),CBO ()
        $else
        On idx GoSub CB0(), CB1(), CB2(), CB3(), CB4(), CB5(), CB6(), CB7(), CB8(), CB9(), CBA(), CBB(), CBC(), CBD(), CBE(), CBF() , CBG(), CBH(), CBI(), CBJ(), CBK(),CBL(),CBM(),CBN()
        $endif
        $ifdef Dampers
        On idx GoSub CB0 (), CB1 (), CB2(), CB3(), CB4(), CB5(), CB6(), CB7(), CB8(), CB9(), CBA(), CBB(), CBC(), CBD(), CBE(), CBF() , CBG(), CBH()
        $endif
  EndIf
Else
      ' idx >= NrTasks, no timers running, so to avoid overflows, we can reset the timer
      If maxtim = 1 Then
            Clear Cnt ' also clears time, as it's an alias. 
            Set TimVals
            Clear notes
      EndIf
EndIf

Jumpout:
   ' here we can insert a bit toggle command to measure the loop speed
   Btg Loopspeed
                                  ' with no midi coming in, the square wave frequency measured here is 111kHz
                                  ' thus the idle loop time is 10 microseconds
                                  ' with fast midi in, the loop time becomes 102kHz
                                  ' Measurements taken with Tektronix TDS2024C oscilloscope. (200MHz, 2GS/s)
   ' end of the main loop
Loop

Proc SortTimers (),Byte
        'look up the next smallest timer value in the Timvals array
        ' zoek de de volgende kleinste timer waarde:
        Set Result           ' was idx, byte
        Set Nxt.31           ' nxt is set on entry. - for speed, we just set the highest bit
        For i = 0 To LastTask           
            If TimVals[i] < Nxt Then
               Nxt = TimVals[i]         
               Result = i
            EndIf
        Next i
EndProc

Proc KeyPres ()
    Set notePres '= 255
EndProc

Proc ProgChange ()
'    Select prog
'        Case 1                    
'
'    EndSelect
    Set prog                      'this is not realy required
EndProc

Proc Pitchbend ()
                 'only implemented on dsPIC based robots, irrelevant here
    Set pblsb 
EndProc

Proc Aftertouch ()
    'this is the channel aftertouch, affecting any playing note
    Set aft                             ' reset
EndProc

Proc Controller()
    Select Ctrl
           Case 19
                ' set the damping time
                release = value
           Case 66
                'on/off for the robot
                If value = 0 Then
                    Clear PowerOn   'CC66.0  =0
                    PowerDown ()
                Else
                    Set PowerOn     'CC66.0  =1
                EndIf
                ' we could also implement a more refined power-on
                ' by using a specific robot ID here.
           Case 123
               ' clear all output ports used:
                Set TimVals
                Low Beat0
                Low Beat1
                Low Beat2
                Low Beat3
                Low Beat4
                Low Beat5
                Low Beat6
                Low Beat7
                Low Beat8
                Low Beat9
                Low Beat10
                Low Beat11
                Low Beat12
                Low Beat13
                Low Beat14
                Low Beat15
                Low Beat16
                Low Beat17
                Low Beat18
                Low Beat19
                Low Beat20
                Low Beat21
                Low Beat22
                Low Beat23
                $ifdef LowModule
                Low Beat24
                $endif
                Clear notes
                idx = SortTimers ()
    EndSelect
    Set Ctrl                                  'mandatory reset
EndProc

' pulse-ON procedures called with ON GOSUB... on reception of a note-on command
Proc Bp0 ()
    ' if a beater is still active, we refuse to strike it again. 
    If notes.0 = 0 Then       
        Set Beat0           
        TimVals[0] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.0
    EndIf
EndProc

Proc Bp1 ()
    If notes.1 = 0 Then       
        Set Beat1
        TimVals[1] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.1
    EndIf    
EndProc

Proc Bp2 ()
    If notes.2 = 0 Then       
        Set Beat2
        TimVals[2] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.2
    EndIf    
EndProc

Proc Bp3 ()
    If notes.3 = 0 Then      
        Set Beat3
        TimVals[3] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.3
    EndIf    
EndProc

Proc Bp4 ()
    If notes.4 = 0 Then      
        Set Beat4
        TimVals[4] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.4
    EndIf    
EndProc

Proc Bp5 ()
    If notes.5 = 0 Then      
        Set Beat5
        TimVals[5] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.5
    EndIf    
EndProc

Proc Bp6 ()
    If notes.6 = 0 Then      
        Set Beat6
        TimVals[6] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.6
    EndIf    
EndProc

Proc Bp7 ()
    If notes.7 = 0 Then      
        Set Beat7
        TimVals[7] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.7
    EndIf    
EndProc

Proc Bp8 ()
    If notes.8 = 0 Then      
        Set Beat8
        TimVals[8] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.8
    EndIf    
EndProc

Proc Bp9 ()
    If notes.9 = 0 Then      
        Set Beat9
        TimVals[9] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.9
    EndIf    
EndProc

Proc BpA ()
    If notes.10 = 0 Then      
        Set Beat10
        TimVals[10] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.10
    EndIf    
EndProc

Proc BpB ()
    If notes.11 = 0 Then      
        Set Beat11
        TimVals[11] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.11
    EndIf    
EndProc

Proc BpC ()
    If notes.12 = 0 Then      
        Set Beat12
        TimVals[12] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.12
    EndIf    
EndProc

Proc BpD ()
    If notes.13 = 0 Then      
        Set Beat13
        TimVals[13] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.13
    EndIf    
EndProc

Proc BpE ()
    If notes.14 = 0 Then      
        Set Beat14
        TimVals[14] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.14
    EndIf    
EndProc

Proc BpFuck ()                'BpF gaat niet...
    If notes.15 = 0 Then      
        Set Beat15
        TimVals[15] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.15
    EndIf    
EndProc

Proc BpG ()
    If notes.16 = 0 Then      
        Set Beat16
        TimVals[16] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.16
    EndIf    
EndProc

Proc BpH ()
    If notes.17 = 0 Then      
        Set Beat17
        TimVals[17] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.17
    EndIf    
EndProc

Proc BpI ()
    If notes.18 = 0 Then      
        Set Beat18
        TimVals[18] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.18
    EndIf    
EndProc

Proc BpJ ()
    If notes.19 = 0 Then      
        Set Beat19
        TimVals[19] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.19
    EndIf    
EndProc

Proc BpK ()
    If notes.20 = 0 Then      
        Set Beat20
        TimVals[20] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.20
    EndIf    
EndProc

Proc BpL ()
    If notes.21 = 0 Then      
        Set Beat21
        TimVals[21] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.21
    EndIf    
EndProc

Proc BpM ()
    If notes.22 = 0 Then      
        Set Beat22
        TimVals[22] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.22
    EndIf    
EndProc

Proc BpN ()
    If notes.23 = 0 Then      
        Set Beat23
        TimVals[23] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.23
    EndIf    
EndProc

$ifdef LowModule
Proc BpO ()                      
    ' noot 108
    If notes.24 = 0 Then      
        Set Beat24
        TimVals[24] = time + Dur0[velo]    
        idx = SortTimers ()
        Set notes.24
    EndIf    
EndProc
$endif

' procedures cancelling or stopping the timed pulses
Proc CB0 ()
    Clear Beat0
    Set TimVals[0]
    Clear notes.0
EndProc

Proc CB1 ()
    Clear Beat1
    Set TimVals[1]
    Clear notes.1
EndProc

Proc CB2 ()
    Clear Beat2
    Set TimVals[2]
    Clear notes.2
EndProc

Proc CB3 ()
    Clear Beat3
    Set TimVals[3]
    Clear notes.3
EndProc

Proc CB4 ()
    Clear Beat4
    Set TimVals[4]
    Clear notes.4
EndProc

Proc CB5 ()
    Clear Beat5
    Set TimVals[5]
    Clear notes.5
EndProc

Proc CB6 ()
    Clear Beat6
    Set TimVals[6]
    Clear notes.6
EndProc

Proc CB7 ()
    Clear Beat7
    Set TimVals[7]
    Clear notes.7
EndProc
    
Proc CB8 ()
    Clear Beat8
    Set TimVals[8]
    Clear notes.8
EndProc

Proc CB9 ()
    Clear Beat9
    Set TimVals[9]
    Clear notes.9
EndProc

Proc CBA ()
    Clear Beat10
    Set TimVals[10]
    Clear notes.10
EndProc

Proc CBB ()
    Clear Beat11
    Set TimVals[11]
    Clear notes.11
EndProc 

Proc CBC ()
    Clear Beat12
    Set TimVals[12]
    Clear notes.12
EndProc

Proc CBD ()
    Clear Beat13
    Set TimVals[13]
    Clear notes.13
EndProc

Proc CBE ()
    Clear Beat14
    Set TimVals[14]
    Clear notes.14
EndProc 

Proc CBF ()
    Clear Beat15
    Set TimVals[15]
    Clear notes.15
EndProc

Proc CBG ()
    Clear Beat16
    Set TimVals[16]
    Clear notes.16
EndProc

Proc CBH ()
    Clear Beat17
    Set TimVals[17]
    Clear notes.17
EndProc

Proc CBI ()
    Clear Beat18
    Set TimVals[18]
    Clear notes.18
EndProc

Proc CBJ ()
    Clear Beat19
    Set TimVals[19]
    Clear notes.19
EndProc

Proc CBK ()
    Clear Beat20
    Set TimVals[20]
    Clear notes.20
EndProc

Proc CBL ()
    Clear Beat21
    Set TimVals[21]
    Clear notes.21
EndProc

Proc CBM ()
    Clear Beat22
    Set TimVals[22]
    Clear notes.22
EndProc

Proc CBN ()
    Clear Beat23
    Set TimVals[23]
    Clear notes.23
EndProc

$ifdef LowModule
Proc CBO ()
    Clear Beat24
    Set TimVals[24]
    Clear notes.24
EndProc
$endif

Proc PowerDown ()
    'stop  all running timers
    Set TimVals
    Low Beat0
    Low Beat1
    Low Beat2
    Low Beat3
    Low Beat4
    Low Beat5
    Low Beat6
    Low Beat7
    Low Beat8
    Low Beat9
    Low Beat10
    Low Beat11
    Low Beat12
    Low Beat13
    Low Beat14
    Low Beat15
    Low Beat16
    Low Beat17
    Low Beat18
    Low Beat19
    Low Beat20
    Low Beat21
    Low Beat22
    Low Beat23
    $ifdef LowModule
    Low Beat24 
    $endif   
    Clear notes
EndProc

Proc Dur_Lookup ()
' Noten 48 tot 53, buizen 60mm
' 02.12.2019: opnieuw opgemeten
' distributie met drie vaste meetpunten.
Dur0[1] = 280   ' minimum waarde
Dur0[32] = 481  ' nieuwe waarde voor 76dBA   (bereikt met oude velo = 28)
Dur0[64] = 646  ' nieuwe waarde voor 90dBA   (bereikt met oude velo = 51)
Dur0[96] = 976  ' nieuwe waarde voor 102dBA
Dur0[127] = 1057 ' maximum waarde
' en nu lineair interpolleren:
stap = (Dur0[32] - Dur0[1]) / 32.0
For i = 2 To 31                         ' ppp tot p
    tmp = (stap * i) + Dur0[1]
    Dur0[i] = tmp
Next i

stap = (Dur0[64] - Dur0[32]) / 32.0
For i = 33 To 63                        ' p tot f
    tmp = (stap * (i- 32)) + Dur0[32]
    Dur0[i] = tmp
Next i

stap = (Dur0[96] - Dur0[64]) / 32.0
For i = 65 To 95                       ' f tot ff
     tmp = (stap * (i- 64)) + Dur0[64]
     Dur0[i] = tmp
Next i

stap = (Dur0[127] - Dur0[96]) / 32.0
For i = 97 To 126                       ' ff tot fff
     tmp = (stap * (i- 96)) + Dur0[96]
     Dur0[i] = tmp
Next i

$ifdef tubo
' lookup voor 54-59:
' 02.12.2019 : opgemeten
' distributie met drie vaste meetpunten.
Dur1[1] = 280   ' minimum waarde
Dur1[32] = 481  ' nieuwe waarde voor 76dBA   
Dur1[64] = 646  ' nieuwe waarde voor 90dBA   
Dur1[96] = 914  ' voor 102dBA
Dur1[127] = 1040 ' maximum waarde
' en nu lineair interpolleren:
stap = (Dur1[32] - Dur1[1]) / 32.0
For i = 2 To 31                         ' ppp tot p
    tmp = (stap * i) + Dur1[1]
    Dur1[i] = tmp
Next i
stap = (Dur1[64] - Dur1[32]) / 32.0
For i = 33 To 63                        ' p tot f
    tmp = (stap * (i- 32)) + Dur1[32]
    Dur1[i] = tmp
Next i
stap = (Dur1[96] - Dur1[64]) / 32.0
For i = 65 To 95                        ' f tot ff
    tmp = (stap * (i- 64)) + Dur1[64]
    Dur1[i] = tmp
Next i
stap = (Dur1[127] - Dur1[96]) / 32.0
For i = 97 To 126                       ' f tot fff
     tmp = (stap * (i- 96)) + Dur1[96]
     Dur1[i] = tmp
Next i

' lookup voor 60-65: (aluminium buizen 50mm diameter)
' distributie met drie vaste meetpunten. (02.12.2019)
' as we changed the beaters, this has to be measured again now!
Dur2[1] = 280   ' minimum waarde
Dur2[32] = 481  ' nieuwe waarde voor 76dBA   
Dur2[64] = 646  ' nieuwe waarde voor 90dBA   
Dur2[96] = 772  ' nieuwe waarde voor 102dBA 
Dur2[127] = 980 ' maximum waarde
' en nu lineair interpolleren:
stap = (Dur2[32] - Dur2[1]) / 32.0
For i = 2 To 31                         ' ppp tot p
    tmp = (stap * i) + Dur2[1]
    Dur2[i] = tmp
Next i

stap = (Dur2[64] - Dur2[32]) / 32.0
For i = 33 To 63                        ' p tot f
    tmp = (stap * (i- 32)) + Dur2[32]
    Dur2[i] = tmp
Next i

stap = (Dur2[96] - Dur2[64]) / 32.0
For i = 65 To 95                        ' f tot ff
    tmp = (stap * (i- 64)) + Dur2[64]
    Dur2[i] = tmp
Next i

stap = (Dur2[127] - Dur2[96]) / 32.0
For i = 97 To 126                       ' ff tot fff
     tmp = (stap * (i- 96)) + Dur2[96]
     Dur2[i] = tmp
Next i
$endif

EndProc
'[EOF]
