' ********************************************************************
' *                    PIC firmware for <Ekla>                       *
' *                           Hub board                              *
' *                  coded by dr.Godfried-Willem Raes                *
' * https://www.logosfoundation.org/instrum_gwr/ekla/picworks        *
' *                  source code development directory:              *
' *                         Ekla_Hub.bas                             *  
' *                         Version 1.4                              *
' *                Proton Compiler version 3.5.8.1                   *
' ********************************************************************
' hardware: single side PCB 08.10.2025 
' 08.10.2025: start coding for <Ubu>
'             6 mosfet outputs
'             2 on board zero cross solid state relays used for power and motor control
'             This board performs midi-filtering and has TTL parsed midi out.
'             This approach will cause a 1 ms latency in the instrument.
' 09.10.2025: Trying to implement motor control under timer3 irq
' 10.10.2025: further work on motor pwm coding. Motorspeed intelligent mode implemented.
'             Scalings to be tested with loads.
' 12.10.2025: Tests with motor passed.
' 18.10.2025: <Ubu> plays its first scales...
' 19.10.2025: for some reason the red light doesn't flash anymore... Solved now.
'             250ms, 500ms, 1000ms periods implemented for motor control
' 20.10.2025: Default velo subsitution value (dVel) could be 6 now.
'             250ms period works fine. Lets try 125ms now...
'             motor range modified as volume values < 30 made the motor stop alltogether...
'             So  now the usable range for the user becomes 27-127.
'             Velo scalings on the PH boards changed: divided by 3. Hence we have to
'             reprogram all four boards now.
' 21.10.2025: dVel now ought to be 45, with the new scaling on the pulse-hold boards.
'             controller 100 implemented as output only to the PH3 board for lightparams.
' 22.10.2025: uploaded and tested o.k. on <Ubu>
' 23.10.2025: Considering to implement a timer to switch the motor off after 60 seconds of inactivity...
'             This feature can be disabled with controller #71.
' 25.10.2025: too many if's for the compiler?
'             CC30 no longer implemented.
'             MotorFlags reorganised and nr. tasks reduced
'             seems to work now. Tested on <Ubu> o.k.
'             In fact we could go with only a single timer.
'             To consider: use timers for gradual speed-up and slowdown for the motor.
'             All light-flashing code eliminated. So we have just 1 timer now.
'             Logos display implemented. Follows motor running now. Tested o.k.
' 26.10.2025: In fact, it would be quite ecological to have our robots switch off
'             automatically when not in use for a given time...
'             setting maxtim to Time.28 , achieves this after 107 minutes (1h47').
'             Tested and found o.k. Frontale LED strip op de voorkant gemonteerd.
' 27.10.2025: Red led lites added under the hub board. Mapping on 122 and 123
' 28.10.2025: code for lights brougth in-line with board 3.
' 29.10.2025: PWM added on the red lights. Upload o.k. Now version 1.4
' -------------------------------------------------------------------
' 05.08.2026: Same board as <Ubu> used for <Ekla>
' 06.08.2026: start coding for <Ekla>
' 18.08.2026: further work on ekla code.
' 21.08.2026: we should copy the coding for the motor from <Teno>!
' 22.08.2026: portamento coding to be copied from <Teno>!
'             testcode in GMT does not look like working... (Xtof fault?) 
' 24.08.2026: registers on Ekla updated. Ctrl.40 reintroduced.
'             Lights added.
' 25.08.2026: Bugs: CC19 had no default. Now set to 64 using declared constant CC19_default
'             modified firmware uploaded. 
' 27.08.2026: checked for CC11, legato controller. Can we make this automatic?

Include "18F2620.inc"      'version for the Ekla board.         (40MHz)
'Include "18F2520.inc"     'also possible.                       (40MHz)
Clear

'$define DebugMode          'conditional compile, using very short sleeptime

' Mapping defines for midi-events on pin outputs and inputs:
' zero cross opto-relay's
    $define Power_Relay    PORTA.4     ' o.k. for <Ekla>
    $define Motor_Relay    PORTA.3 
  
' Mosfet outputs:  
    $define Display          PORTA.5   ' Logos-display   noot 125
    $define RoodOnder        PORTC.0   ' red under front     120  - kan knipperen
    $define Tungsten         PORTC.1   ' can pwm HPWM2       121
    $define Lite1            PORTC.2   ' free   can pwm HPWM1  122     
    $define White            PORTC.3   ' free     ? 117      123  - kan knipperen
    $define Lite3            PORTC.4   ' free                124  - kan knipperen      
' inputs:    
'    $define sensor1        PORTA.0     ' not used 
'    $define sensor2        PORTA.1     ' not used  
' LED's:    
     $define GreenLed      PORTB.2       ' on/off   
     $define BlueLed       PORTB.1       ' motor running
     $define Debug_Led     PORTB.5       ' for testing - red led - watchdog
     $define Loopspeed     PORTB.0       ' B.3 and B.4 are NC
    
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 = %11000011     '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     
'Input sensor1        
'Input sensor2     
   
'constant definitions:
Symbol fPwm  = 21000   ' PWMminF
    ' midi note mapping
Symbol RoodOnder_note = 120   ' rood onderaan
Symbol Tungsten_note = 121    ' pwm
Symbol Free1_note = 122     
Symbol White_note = 123  
Symbol Free3_Note = 124 
Symbol Display_note = 125     ' automatic with motor

Symbol lowest_note = 53 ' was 54   ' f#
Symbol highest_note = 106 ' voorlopig 
Symbol NrTasks = 4 '3 '7
Symbol LastTask = NrTasks - 1

' constants:
Symbol True = %1
Symbol False = %0
Symbol Period10 = 53039  ' for 10ms pulses motor control slow pwm , value for TIM3
Symbol Period5 = 59288   ' for 5ms pulses motor control slow pwm
Symbol Period25 = 62412  ' for 2.5ms pulses 4 Hz or period = 250ms.  3124 ticks
Symbol Period125 = 63974 ' for 1.25ms pulses, 8Hz or period = 125ms. 1562 ticks

Symbol Crit = %101        ' = 5
                          ' MotorFlags.1 = MotWait, moet 0 zijn
                          ' MotorFlags.0 = MotorOn, moet 1 zijn
                          ' MotorFlags.2 = Autosleep, moet 1 zijn.
Symbol Period_default = Period5  
Symbol CC19_default = 110      ' new 27.08.2026
Symbol CC10_default = 25  
Symbol CC11_default = 0     

'initialisations for the midi input parser:
Symbol Midichannel = 6                         ' Ekla channel - on all boards!
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
' 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...
' Create variables:
    Dim Cnt As Dword System                    ' 1 cnt-unit ought to be 24 us.
    Dim CntHw As Cnt.Word1                     ' used in the timer0 interrupt, to create a 32 bit timer
    Dim CntLw As TMR0L.Word                    ' this is the trick to read both TMR0L and TMR0H
                                               ' it makes Cntlw the low word of cnt
                                               ' We still have to copy the contents of Lw to Cnt
    Dim time As Cnt                            ' was : Dword  'frozen copy of Cnt
    Dim MaxTim As time.28                      ' overflow flag used to reset timer and to power down
                                               ' time.30 is 7 hours.
                                               ' time.29 is 214 minutes or 3h30
                                               ' time.28 would be 107 minutes - checked and found o.k.
 
   ' Dim T3 As Dword                            ' make 32-bit timer
   ' Dim Tim3Hw As T3.Word1 
    Dim MotTim As Byte                         ' for duty cycle control, alternating on and off value for motor 
    Dim MotCnt As Byte                         ' PWM counter increments in 10ms units in the tim3 irq 
    Dim Tim3 As TMR3L.Word                     ' same trick for timer3 , 16-bits
    
    Dim Bytein  As Byte System                    ' midi byte read from buffer
    Dim StBit As Bytein.7                         ' highest bit of ByteIn
    Dim i As Byte System                          ' general purpose counter
    ' 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                        ' handled on the hub board.
        Dim Mtog As CC66.2                           ' used in timer 3 irq

    Dim MotPer As Word System                    ' 1.25ms, 2.5ms, 5 ms or 10 ms value for Tim3

    Dim CC10 As Byte System               ' motor speed  0-100
    Dim oldMotor As Byte
    Dim CC11 As Byte                      ' hub to synth messaging for legato
    Dim CC19 As Byte                      ' release controller - not forwarded!
    Dim Period As Word System             ' controller 67
    Dim CC67 As Byte     
           
    Dim MotorFlags As Byte System
        Dim MotorOn As MotorFlags.0         ' 25.10.2025
        Dim MotWait As MotorFlags.1         ' if 1, motor wait timer is running.
     
    Dim TimVals[NrTasks] As Dword         ' lijst met timer waarden - de kleinste is eerst aan de beurt
    Dim Nxt As Dword System               ' waarde voor de eerstvolgende timer
    Dim idx As Byte System                ' index voor de eerstvolgende timer 
    Dim Playing_note As Byte
    Dim j As Float
    ' for light automation:
    Dim NrNotes As Bit   'Byte System                  ' aantal klinkende noten 0 of 1
    Dim LiTog As Bit
    Dim CC69 As Bit
    Dim Lites As Byte
    Dim Pres1 As Byte
    Dim Pres2 As Byte
    Dim Pres3 As Byte
    
'-----------------------------------------------------------------------------------------
' Load the USART Interrupt handler and buffer read subroutines into memory
Dim Ringbuffer[256] As Byte           ' Array for holding received characters
Include "Ekla_Hub_Irq.inc"                ' for UART,Timer0, Timer3 Interrupt
Dim Dur5[128] As Word                    ' duration lookup for lite flashing repetitions
Dim Toff[128] As Byte 'Dword             ' lookup for motor slow pwm
Dim Ton[128] As Byte  'Dword

Low Power_Relay
Low Motor_Relay
High Debug_Led        ' we must make sure the port is set to output
Clear CC66            ' all flags
Clear MotorFlags      ' 3 flags
Clear NrNotes

' Main program starts here:
MAIN:
    Low GreenLed
    Low BlueLed
    Low RoodOnder
    Low Tungsten
    Low Lite1   
    Low White   
    Low Lite3
    Low Display   
    Clear NrNotes
    Set TimVals                      ' no timers running on startup
    
    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
         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 bit, 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
'                        Dont sync external clock input

Period = Period_default      ' controller 67

 ' doing it this way seems to work:
    Clear T3CON
    Clear PIR2BITS_TMR3IF   ' clear IRQ flag
    Set PIE2BITS_TMR3IE     ' irq on
  ' Clear Tim3              ' Clear TMR3L And TMR3H registers
    Tim3 = Period           ' period5 = 59288            ' 5 ms - measured and found correct.
    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                        
    Clear PIE2Bits_TMR3IE   ' no motor on start-up!  - disable irq
    Clear T3CON.0           ' disable timer 3
Dur_Lookup ()
Motor_PWM_Lookup ()                     

idx = SortTimers () 
Set CC69                     ' by default light automation is ON
CC10 = CC10_default
CC11 = CC11_default

    ' send a power off to the pulse-hold boards and the generator board:
HRSOut Control_Status, 66, 0               

    ' start the main program loop:
Do                                   
        Cnt.Word0 = CntLw            ' read timer 0
                                     ' now time is an alias for Cnt
        Bytein = GetMidiIn ()        ' Read data from the serial buffer, with no timeout
                                     ' 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              'eqv to Select case 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             '= 255
                            Set velo                '= 255
                       Case Keypres_Status          
                            statusbyte = Bytein
                            Set notePres 
                            Set pres 
                       Case Control_Status          ' controllers and switches
                            statusbyte = Bytein
                            Set Ctrl 
                            Set value 
                       Case ProgChange_Status       ' used for different lookup tables
                            statusbyte = Bytein
                            Set prog
                       Case Aftertouch_Status        ' not used on this board
                            statusbyte = Bytein
                            Set aft 
                       Case Pitchbend_Status        
                            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...
                                Select noteUit
                                       Case Playing_note    'lowest_note To highest_note               ' notes for Ekla
                                             If release = 0 Then release = CC19       ' new for <Ekla>
                                             HRSOut NoteOff_Status, Playing_note, release  '  send to the synth board
                                             Clear NrNotes
                                             Clear Playing_note
                                             Clear CC11        ' clear legato
                                             If MotorOn = 1 Then
                                             '   start timer to switch motor off
                                                TimVals[0] = time + 500
                                                idx = SortTimers ()
                                             EndIf
                                       Case RoodOnder_note       ' 120            
                                             Clear RoodOnder  
                                             Clear Lites.0
                                             Set TimVals[1]
                                             idx = SortTimers ()                                   
                                       Case White_note           ' 123        
                                             Clear White
                                             Clear Lites.1
                                             Set TimVals[2]
                                             idx = SortTimers ()
                                       Case Tungsten_note         ' can pwm  121
                                             HPWM 2, 0, fPwm       
                                             'Clear Tungsten  
                                       Case Free1_note         ' can pwm   122      
                                             'Clear Lite1
                                             HPWM 1, 0, fPwm  
                                       Case Free3_Note            ' 124   
                                             Clear Lite3
                                             Clear Lites.2
                                             Set TimVals[3]
                                             idx = SortTimers ()
                                       Case Display_note
                                             Clear Display                        
                                EndSelect
                                Set noteUit '= 255                         'reset
                            EndIf
                       Case NoteOn_Status
                            If noteAan = 255 Then
                                noteAan = Bytein
                            Else
                                velo = Bytein
                                If velo = 0 Then                      ' note off via velo=0
                                    Select noteAan
                                       Case Playing_note   'lowest_note To highest_note            ' notes for Ekla
                                             HRSOut NoteOff_Status, Playing_note, CC19  ' not a bug!
                                             Clear NrNotes
                                             Clear Playing_note
                                             ' should we clear and forward CC11 here?
                                             Clear CC11
                                             If MotorOn = 1 Then
                                                '  start timer to switch motor off
                                                 TimVals[0] = time + 500
                                                 idx = SortTimers ()
                                             EndIf
                                       Case RoodOnder_note       ' 120            
                                             Clear RoodOnder  
                                             Clear Lites.0
                                             Set TimVals[1]
                                             idx = SortTimers ()                                   
                                       Case White_note           ' 121        
                                             Clear White
                                             Clear Lites.1
                                             Set TimVals[2]
                                             idx = SortTimers ()
                                       Case Tungsten_note         ' can pwm  
                                             HPWM 2, 0, fPwm       
                                             'Clear Tungsten  
                                       Case Free1_note         ' can pwm         
                                             'Clear Lite1
                                             HPWM 1, 0, fPwm  
                                       Case Free3_Note               
                                             Clear Lite3
                                             Clear Lites.2
                                             Set TimVals[3]
                                             idx = SortTimers ()
                                       Case Display_note
                                             Clear Display                                                                                                                                                                   
                                    EndSelect
                                Else                                       ' else for if velo = 0
                                    If PowerOn = 1 Then                    ' dont do lights nor send note-on's if power is off
                                    Select noteAan
                                           Case lowest_note To highest_note          ' notes for Ekla
                                                 HRSOut NoteOn_Status, noteAan, velo
                                                 Set NrNotes 
                                                 If Playing_note = 0 Then
                                                        ' in this case legato should be off and we just start the new note
                                                        If CC11 = 1 Then
                                                            Clear CC11
                                                            HRSOut Control_Status, 11, 0
                                                        EndIf
                                                 Else
                                                        ' in this case, we received a note on whilst a note was still sounding...
                                                        If CC11 = 0 Then
                                                            CC11 = 1
                                                            HRSOut Control_Status, 11, 1
                                                        EndIf
                                                 EndIf
                                                 Playing_note = noteAan  ' so that note-off will only be valid for this one.
                                                 Set TimVals[0]          ' cancel wait timer!
                                                 idx = SortTimers ()
                                                 'Set noteAan.7      ' note-on flag
                                                 If MotorOn = 0 Then MotorCtrl ()
                                           Case RoodOnder_note       ' 120            
                                                Set RoodOnder
                                                Set Lites.0 
                                                Pres1 = velo
                                                If velo < 127 Then
                                                    TimVals[1] = time + Dur5[velo]
                                                    idx = SortTimers
                                                EndIf                                    
                                           Case White_note           ' 121        
                                                Set  White
                                                Set Lites.1
                                                Pres2 = velo
                                                If velo < 127 Then
                                                    TimVals[2] = time + Dur5[velo]
                                                    idx = SortTimers ()
                                                EndIf
                                           Case Tungsten_note         ' can pwm 
                                                velo = velo + velo 
                                                Inc velo              ' so we can reach 255
                                                HPWM 2,velo , fPwm       
                                                'Clear Tungsten  
                                           Case Free1_note         ' can pwm         
                                                'Clear Lite1
                                                velo = velo + velo
                                                Inc velo
                                                HPWM 1, velo, fPwm  
                                            Case Free3_Note               
                                                Set Lite3
                                                Set Lites.2
                                                Pres3 = velo
                                                If velo < 127 Then
                                                    TimVals[3] = time + Dur5[velo]
                                                    idx = SortTimers ()
                                                EndIf
                                            Case Display_note
                                                Set Display                                          
                                    EndSelect     
                                    EndIf                                    
                                    Set noteAan                 
                                EndIf  
                            EndIf
                       Case Keypres_Status           
                            If notePres = 255 Then
                                notePres = Bytein       ' = note
                            Else
                                pres = Bytein
                                KeyPres ()     
                                ' changing brightness on the led's required this!
                            EndIf
                       Case Control_Status            'this is where the action takes place for controllers
                            If Ctrl = 255 Then
                                Ctrl = Bytein
                            Else
                                value = Bytein
                                Select Ctrl                      ' this is filtered though.
                                    Case 1 To 7
                                         HRSOut Control_Status, Ctrl, value     ' for synth board
                                    Case 10
                                         CC10 = value
                                         If NrNotes > 0 Then 
                                            Controller ()
                                         EndIf
                                    Case 11
                                         If value = 0 Then 
                                            Clear CC11 
                                         Else 
                                            CC11 = 1
                                         EndIf
                                         HRSOut Control_Status,11, CC11     ' for synth board
                                    Case 15 To 20
                                         HRSOut Control_Status, Ctrl, value
                                         If Ctrl = 19 Then CC19 = value + 1
                                         If CC19 > 127 Then CC19 = 127
                                    Case 40 To 43              ' wave parameters
                                         HRSOut Control_Status, Ctrl, value
                                    Case 104 To 106            ' 100-103 no longer used
                                         HRSOut Control_Status, Ctrl, value
                                    Case 67
                                         CC67 = value
                                         If NrNotes > 0 Then
                                            Controller ()     ' motor period controller
                                         EndIf
                                    Case 66, 123      
                                         HRSOut Control_Status, Ctrl, value    ' so only relevant controllers are passed to midi out
                                         Controller ()
                                               ' 66 = power switch        
                                               ' 123 = all notes off
                                EndSelect
                                'Controller ()  ' handles all controllers relevant for this board
                            EndIf
                       Case ProgChange_Status                 'not implemented on <Ekla>
                            If prog = 255 Then                'single byte message
                                 prog = Bytein          
                                 Set prog
                            EndIf
                        Case Aftertouch_Status                'not implemented on <Ekla>
                             If aft = 255 Then
                                  aft = Bytein
                                  Aftertouch ()
                                  Set aft
                             EndIf
                       Case Pitchbend_Status                
                            If pblsb = 255 Then
                                pblsb = Bytein
                            Else
                                pbmsb = Bytein
                                'Pitchbend ()
                                HRSOut Pitchbend_Status, pblsb, pbmsb   ' for synth board
                                Set pblsb
                            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
        Select idx
               Case 0                             ' used for automatic motor timeout
                                                  ' when no notes are playing
                     Clear MotorOn  '= False: motor stops
                     Clear MotWait  '= False: timer is afgelopen
                     Set TimVals[0]
                     Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                     Clear T3CON.0
                     Clear PIE2Bits_TMR3IE
                     Clear Motor_Relay
                     Clear BlueLed    
                     idx = SortTimers ()      
               Case 1                             
                     If Lites.0 = 0 Then
                       Clear RoodOnder
                       Set TimVals[1]
                     Else
                       Toggle RoodOnder
                       TimVals[1] = time + Dur5[Pres1] 
                     EndIf
                     idx = SortTimers ()
               Case 2
                     If Lites.1 = 0 Then
                       Clear White
                       Set TimVals[2]
                     Else
                       Toggle White
                       TimVals[2] = time + Dur5[Pres2] 
                     EndIf
                     idx = SortTimers () 
               Case 3
                     If Lites.2 = 0 Then
                       Clear Lite3
                       Set TimVals[3]
                     Else
                       Toggle Lite3
                       TimVals[3] = time + Dur5[Pres3] 
                     EndIf
                     idx = SortTimers ()                        
               'Case Else
               '     ' in dit geval is idx geset
               '     GoTo jumpout               
        EndSelect
   EndIf
      ' beveiliging tegen overflow crashes...
      ' in dit geval loopt er weliswaar een timer!
      If MaxTim = 1 Then
                    Clear time
                  ' Clear notes
                    Set TimVals
      EndIf
Else
      ' idx > LastTask, no timers running, so to avoid overflows, we can reset the loop timer
      ' 26.10.2025: now we switch off power completely (Ubu...):
      ' If MaxTim = 1 Then PowerDown ()   'Clear time 
      If MaxTim = 1 Then Clear time    
EndIf        

'' light automation:
'    ' this slows down the main loop!!!
'If CC69 = 1 Then
'    If time.13 <> LiTog  Then     ' time.12 = 98 ms, time.13 = 0.1966 s
'        Toggle LiTog
'        Select Case NrNotes
'            Case 0
'                Clear White1
'                Clear White2
'            Case Else
'                Set White1
'                Set White2 
'        EndSelect
'    EndIf
'EndIf

              
  Btg PORTB.0     ' average loop-speed: 
                  ' 121kHz with no load. (9.10.2025)
                  ' with autolights in the main loop: 112kHz, 11.10.2025
                  ' with motor control in the main loop: 80kHz
                  ' 22.08.2026, Ekla : 121kHz
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 MotorCtrl ()
    ' cfr.<Ubu> code!!!
    'called whenever a motorcondition or parameter changes...
    'this procedure is only called if nrnotes > 0
    'the nrnotes = 0 case should be handled in  the main loop.
    If NrNotes > 0 Then
        If MotWait = True Then   ' in this case a timer is running to put the motor to sleep
                                 ' and the motor is still running!
            Clear MotWait
            Set TimVals[0]       ' we cancel the wait timer, nothing changes for the motor
            idx = SortTimers ()
        EndIf
    EndIf
        Select CC10                       ' Motspeed set with ctrl.10 ( CC10)
            Case < 1                      ' stop motor
                Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                Clear T3CON.0
                Clear PIE2Bits_TMR3IE
                Clear Motor_Relay
                Clear BlueLed
                Clear MotorOn
                Clear Display
            Case > 99 '126
                If NrNotes > 0 Then
                    Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                    Clear T3CON.0
                    Clear PIE2Bits_TMR3IE
                    Set Motor_Relay
                    Set BlueLed
                    Set MotorOn
                    Set Display
                Else
                    Clear Display
                EndIf
            Case Else
                If NrNotes > 0 Then
                    If T3CON.0 = 0 Then  
                        Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                        Set Mtog
                        Clear MotCnt 
                        MotTim = Ton[CC10]        ' Ton[Motspeed]  
                        Set T3CON.0
                        Set PIE2Bits_TMR3IE    ' enable timer3 interrupt
                        Tim3 = Period          ' 59288 for 5 ms units and period = 500ms.
                    EndIf
                    Set MotorOn
                    Set Display
                Else
                    Clear Display
                EndIf
         EndSelect                                                   
EndProc

Proc KeyPres () 
    ' 06.08.2026: To be implemented for <Ekla> !!!
    ' 25.08.2026: done.
    Select notePres
        Case lowest_note To highest_note       
            HRSOut Keypres_Status, notePres, pres
        Case Tungsten_note
            pres = pres + pres 
            HPWM 2, pres, fPwm
        Case Free1_note
            pres = pres + pres
            HPWM 1, pres, fPwm
        Case RoodOnder_note
            Pres1 = pres
        Case White_note
            Pres2 = pres
        Case Free3_Note
            Pres3 = pres
    EndSelect                
    Set notePres '= 255
EndProc

Proc ProgChange ()
    ' not implemented on this board nor on <Ekla>
    Set prog 
EndProc

Proc Pitchbend ()
    ' forward done above.
    ' used on <Ekla>
    Set pblsb 
EndProc

Proc Aftertouch ()
     'this is the channel aftertouch, affecting any playing note
     'not implemented here.
    Set aft                             
EndProc

Proc Controller ()
    Select Ctrl
           Case 10                                      ' use for motor speed
                 CC10 = value                           ' save setting 
                 If PowerOn = 1 Then     
                     Select CC10                         'Motspeed
                        Case < 1           
                            Clear T3CON.0
                            Clear PIE2Bits_TMR3IE
                            Clear PIR2BITS_TMR3IF   ' clear IRQ flag for timer3
                            Clear Motor_Relay
                            Clear BlueLed
                            If MotWait = 1 Then    ' if timer is running
                                Set TimVals[0]        ' cancel it
                                idx = SortTimers ()
                                Clear MotWait      '  = False
                            EndIf
                            Clear MotorOn
                            Clear Display
                        Case > 99 
                            If NrNotes > 0 Then
                                Clear T3CON.0
                                Clear PIE2Bits_TMR3IE
                                Clear PIR2BITS_TMR3IF   ' clear IRQ flag for timer3
                                Set Motor_Relay
                                Set BlueLed
                                If MotWait = 1 Then
                                    Set TimVals[0]
                                    idx = SortTimers ()
                                    Clear MotWait        ' = False
                                EndIf
                                Set MotorOn
                                Set Display
                            EndIf
                        Case Else  
                            If NrNotes > 0 Then                         
                                If T3CON.0 = 0 Then 
                                    Clear PIR2BITS_TMR3IF   ' clear IRQ flag for timer3
                                    Set Mtog
                                    MotTim = Ton[CC10]
                                    Clear MotCnt
                                    Tim3 = Period          ' 59288 for 5 ms units and period = 500ms.
                                    Set T3CON.0
                                    Set PIE2Bits_TMR3IE    ' enable timer3 interrupt
                                EndIf
                                If MotWait = 1 Then
                                    Set TimVals[0]
                                    idx = SortTimers ()
                                    Clear MotWait          ' = False
                                EndIf
                                Set MotorOn
                                Set Display
                            EndIf
                     EndSelect
                     ' so the motor will allways respond to this controller, even when nrnotes = 0.
                     ' the controller allways clears the sleeptimer!
                 EndIf
                 oldMotor = CC10
           Case 66
                'on/off for the robot
                ' this should also go to the note boards!
                If value = 0 Then
                    PowerDown ()
                Else
                    Set Power_Relay      ' stroom AAN via optorelais
                    Set GreenLed 
                    Set PowerOn      ' CC66.0  =1
                    CC19 = CC19_default
                    CC10 = CC10_default
                    CC11 = CC11_default  ' legato off
                    Set CC69         ' auto-motor on
                EndIf
           Case 67
                ' sets the period for motor control
                CC67 = value
                Select CC67   
                    Case 0
                        Period = Period_default  ' = Period5
                    Case 1
                        Period = Period10   ' 10ms and 1 second
                    Case 2
                        Period = Period5    ' 5 ms and 500 ms
                    Case 3
                        Period = Period25   ' 2.5 ms and 250 ms
                    Case 4
                        Period = Period125   ' 125ms  - tested - dangerous: ubu motor runs very hot.
                    Case Else
                        ' disregard controller
                EndSelect
           Case 69 
                If value = 0 Then
                    Clear CC10             ' disable motor
                Else
                    CC10 = CC10_default
                EndIf 
           Case 123
                ' all notes/lites off - no controller resets
                If MotorOn = 1 Then
                '   start timer to switch motor off
                    TimVals[0] = time + 500
                EndIf
                Clear NrNotes   
                Clear Playing_note
                Clear Display
                Clear Lite3
                Clear RoodOnder
                HPWM 1, 0, fPwm
                HPWM 2, 0, fPwm
                'Clear Lite1
                Clear White  
                Set TimVals[1]
                Set TimVals[2]
                Set TimVals[3]
                idx = SortTimers () 
    EndSelect
    Set Ctrl                                  'mandatory reset
EndProc


Proc PowerDown ()
               Clear Power_Relay      ' power and motor off
               Clear Motor_Relay
               Clear PowerOn   'CC66.0  =0               
               Clear MotorOn          ' MotorFlags.1
               Set TimVals            ' cancel all timers
               Clear time
               Clear NrNotes
               Clear GreenLed
               Clear BlueLed
               Clear RoodOnder            
               'Clear Lite1
               Clear White
               Clear Lite3
               Clear Display
               HPWM 2,0,fPwm          ' tungsten
               HPWM 1,0,fPwm
               CC19 = CC19_default
               Clear T3CON.0
               Clear PIE2Bits_TMR3IE
               Clear MotWait          ' MotorFlags.0
               idx = SortTimers ()    ' idx should be 255 here.
EndProc

Proc Dur_Lookup ()
    'lookup for flashing lights:
    Set Dur5[0]         ' not used 
    Dur5[127] =  17218  
    j = Dur5[127] / 127
    For i = 1 To 127
        Dur5[i] = Dur5[0] - (j * i)
    Next i                                      
EndProc  

Proc Motor_PWM_Lookup ()
    ' lookup for <Ubu>, using timer3
    ' 10.10.2025, counting in 10ms or 5ms units
    ' duty cycle values
    ' 100 x 10ms = period 1s
    ' 100 x 5ms  = period 500ms 
    ' 100 x 2.5ms = period 250ms or 4Hz
    ' 100 x 1.25ms = period 125ms or 8Hz
    Ton[0] = 0
    Toff[0] = 100
    For i  = 1 To 99 Step 1  ' step 2 for 5ms units
        Ton[i] = i
        Toff[i] = 100 - i
    Next i 
    For i = 100 To 127
        Ton[i] = 100
        Toff[i] = 0
    Next i
EndProc
'[EOF]

