'**************************************************************** '* Name : HarmO_motor.BAS * '* Author : Godfried-Willem RAES * '* Notice : Copyleft (c) 2021 Logosoft Public Domain * '* Date : V1.1 - 25.02.2015 * '* Version : V1.2 - 04.04.2021 * '* Version : V2.0 - 27.11.2022 '* Notes : For Siemens motor controller Sinamics G110 * '**************************************************************** ' special PCB for Reed Organ motor controllers ' we use an intelligent algorithm for wind pressure. ' Started off from the coding for Harma. ' The board is different then the one used for Harma: ' here we use an opamp on the PWM output. ' 25.02.2015: This coding takes registration fully into account ' minspeed implementation removed from coding, as this can better be set on the motorcontroller. ' 28.02.2015: hardware changed. PWM is no longer inverted now. ' This version works quite well, although there might still be to much pumping. ' We also need to reprogram the motor controller now. ' 18.04.2020: This code needs some changes to comply with the latest versions of the Proton Compiler ' 04.04.2021: Rechecked.- IRQ include updated. ' still looks buggy... loopspeed is awfully low at 7kHz... ' This version uploaded on the board. ' 27.11.2022: Code revisited - upgrades after experiences with melauton. ' Loopspeed problem solved with new irq include. However, we do not see a reason in ' the previous include file... Now 119kHz. ' To do: implement 10-bit pwm as done one melauton. Done. ' First 10-bit version up and working. To be tested on the robot... 'TO DO: add timers for entering standby mode en motor off, as done in Melauton ' add scaling controller Include "18F2525.inc" ' (40MHz) 'Include "18F2620.inc" 'also possible 'Include "18F2520.inc" 'also possible. (40MHz) 'Include "18F25K20.inc" 'for test & debug on an Amicus board. (64MHz) ' Mapping defines for midi-events on pin outputs and inputs: $define Motor_On PORTC.5 ' motor on/off switch $define Motor_Dir PORTC.4 ' direction of rotation $define Motor_PWM PORTC.1 ' motor speed control $define Acknowledge PORTC.2 ' error acknowledge $define BlueLED PORTC.3 ' on when intelligent mode is disabled $define YellowLED PORTA.0 $define GreenLED PORTA.2 ' on when CC66 is ON $define loopspeed PORTA.1 ' for measurement 'red LED for debug: $define Debug_Led PORTB.5 ' red led - watchdog $define PWM10 $ifdef PWM10 Include "hpwm10.inc" ' 10-bit pwm extensions $endif ' configure the input and output pins: Clear SSPCON1.5 'RC3 must be available for I/O TRISA = %01000000 'bits set to 0 are output, 1 = input TRISB = %11100000 TRISC = %11000000 'RC1 en RC2 zijn pwm outputs and must be set to output 'RC6 en RC7 zijn USART I/O and must be set to input 'constant definitions: 'initialisations for the midi input parser: Symbol Midichannel = 9 ' HarmO Symbol NoteOff_Status = 128 + Midichannel ' 2 bytes follow Symbol NoteOn_Status = 144 + Midichannel Symbol Keypres_Status = 160 + Midichannel ' 2 bytes follow 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 'application specific constants Symbol NrTasks = 2 Symbol LastTask = NrTasks - 1 $ifdef PWM10 Symbol Maxspeed = 1023 Symbol fPWM = 39000 ' this seems to work for 10 bit mode also... $else Symbol Maxspeed = 255 ' limit as long as we use 8 bit PWM Symbol fPWM = PWMminF * 4 ' in avoidance of audible artifacts $endif ' 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 All_Digital = True ' Declare Hserial_Clear = On ' should clear on errors. Bytes get lost of course... $ifdef PWM10 OpenAnalog2 ' replacing HPWM2 OpenAnalog1 ' replacing HPWM1 $endif ' Create variables Dim Cnt As Dword System 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 'alias Dim MaxTim As time.30 ' overflow flag used to reset timer Dim Tim3 As TMR3L.Word 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 ' channel aftertouch ' Dim pblsb As Byte ' pitch bend lsb ' Dim pbmsb As Byte ' pitch bend msb Dim Timvals[NrTasks] As Dword Dim Resort As Byte Dim Resort_flag As Resort.0 ' flag to signal the requirement to resort timers Dim Off_flag As Resort.1 ' set when motor was switched off Dim Motor_flag As Resort.2 ' set when motor pwm needs update Dim idx As Byte Dim nxt As Dword Dim CC66 As Byte System ' global on/off switch Dim st As Byte System Dim b1 As Byte System Dim b2 As Byte System $ifdef PWM10 Dim Volume As Word ' CC7 value Dim Motorspeed As Word ' calculated continuously Dim pw As Word ' Motor_PWM Dim Minspeed As Word Dim Airflow As Word ' calculated with the note-on/off commands Dim Airflowrange As Word Dim divider As Word ' for scaling in the lookups Dim basfactor As Word Dim treblefactor As Word Dim subbas As Word $else Dim Volume As Byte System ' CC7 value Dim Motorspeed As Byte System ' calculated continuously Dim pw As Byte System ' Motor_PWM Dim Airflow As Word System ' calculated with the note-on/off commands Dim Minspeed As Byte System Dim Airflowrange As Byte System Dim divider As Byte System ' for scaling in the lookups Dim basfactor As Byte System Dim treblefactor As Byte System Dim subbas As Byte System $endif Dim Motor_mode As Byte System ' CC68 value - zero by default ' for analog in: sensor - nc now. Dim Newval As Word Dim Sensorval As Word 'Dim sollspeed As Word Dim ZeroPres As Word ' sensor reading with motor off '----------------------------------------------------------------------------------------- ' Load the USART Interrupt handler And buffer read subroutines into memory 'Include "ADC.inc" ' Load the ADC macros into the program - used in the IRQ include. Dim Ringbuffer[256] As Byte ' Array for holding received characters Include "Harmo_Mot_Irq.inc" ' our own version for UART And Timer0/3 Interrupt ' new version 27.11.2022 Dim Note_Air[61] As Byte ' lookup met de luchtbehoefte in funktie van de toonhoogte 'make sure we initialize the used pins on start up: Set Acknowledge ' so output will be 0 or off Set Motor_On ' 0 or OFF Set Motor_Dir ' 0 or OFF Low Debug_Led Low GreenLED Low BlueLED Low YellowLED $ifdef PWM10 WriteAnalog2 0 ' connected to RC1 - Motor WriteAnalog1 0 ' connected to RC2 - brake - used for debugging only. divider = 16 Note_Air_Lookup () ' this is the default lookup on startup. It can be changed with ctrl#70 Volume = 512 ' default on startup Airflowrange = Maxspeed - Volume $else HPWM 2, 0, fPWM ' motor HPWM 1, 0, fPWM ' connected to RC2 divider = 16 Note_Air_Lookup () ' this is the default lookup on startup. It can be changed with ctrl#70 Volume = 64 ' default on startup Airflowrange = 255 - Volume '(128 - Minspeed) + (127 - Volume) $endif Clear CC66 Clear Airflow Clear Motor_mode ' intelligent mode by default Clear Resort '----------------------------------------------------------------------------------------- ' Main program starts here MAIN: High Debug_Led DelayMS 50 ' wait for stability Low Debug_Led Set Timvals Set idx 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 ' Opentimer0 (Timer_INT_On & T0_16BIT & T0_SOURCE_INT & T0_PS_1_256) in macro file. 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 ' open and start timer3 for sampling: Clear T3CON Clear PIR2BITS_TMR3IF ' clear IRQ flag Set PIE2BITS_TMR3IE ' irq on ' 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 = %10110000 ' 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 set to 0 for Whisper! ' maximum count = 52.42ms, 1 tick =0.8uS, lowest freq.=19Hz HRSOut NoteOff_Status, 68, 111 ' just about anything to make it work... ' start the main program loop: Do Cnt.Word0 = CntLw ' read timer 0 Bytein = GetMidiIn () ' Read data from the serial buffer Clear Motor_flag ' 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 GoTo Check_Timers 'throw away... Else Clear statusbyte 'reset the status byte GoTo Check_Timers 'throw away EndIf 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 Set velo Case Keypres_Status ' used for lights statusbyte = Bytein Set notePres Set pres Case Control_Status ' 7, 66, 67, 68, 123 statusbyte = Bytein Set Ctrl Set value Case ProgChange_Status statusbyte = Bytein Set prog ' Case Aftertouch_Status ' 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 12 To 24 ' wrong, as these notes are mapped on 24-35 ' If subbas = 1 Then ' If Airflow >= 10 Then Airflow = Airflow -10 ' Motorspeed = Volume + (Airflow Min Airflowrange) '+ Minspeed ' EndIf Case 29 To 52 '101 i = Note_Air[noteUit - 29] * basfactor If Airflow >= i Then Airflow = Airflow - i Else Clear Airflow EndIf If subbas = 1 Then If noteUit > 35 And noteUit < 48 Then If Airflow >= 10 Then Airflow = Airflow -10 EndIf EndIf ' following no longer working in Proton compiler: 'Motorspeed = Volume + (Airflow>>2 Min Airflowrange)' + Minspeed ' should we change this for 10-bit mode? If Airflow >> 2 < Airflowrange Then Motorspeed = Volume + (Airflow >>2) Else Motorspeed = Volume + Airflowrange EndIf Set Motor_flag Case 53 To 101 i = Note_Air[noteUit - 29] * treblefactor If Airflow >= i Then Airflow = Airflow - i Else Clear Airflow EndIf 'Motorspeed = Volume + (Airflow>>2 Min Airflowrange) '+ Minspeed If Airflow >> 2 < Airflowrange Then Motorspeed = Volume + (Airflow >>2) Else Motorspeed = Volume + Airflowrange EndIf Set Motor_flag EndSelect Set noteUit EndIf GoTo Check_Timers Case NoteOn_Status If noteAan = 255 Then noteAan = Bytein Else velo = Bytein If velo = 0 Then Select noteAan 'Case 12 To 24 ' If subbas = 1 Then ' If Airflow >= 10 Then Airflow = Airflow - 10 ' Motorspeed = Volume + (Airflow Min Airflowrange)' + Minspeed ' EndIf Case 29 To 52 i = Note_Air[noteAan - 29] * basfactor If Airflow >= i Then Airflow = Airflow - i Else Clear Airflow EndIf If subbas = 1 Then If noteAan > 35 And noteAan < 48 Then If Airflow >= 10 Then Airflow = Airflow -10 End If EndIf 'Motorspeed = Volume + (Airflow>>2 Min Airflowrange)' + Minspeed If Airflow >> 2 < Airflowrange Then Motorspeed = Volume + (Airflow >>2) Else Motorspeed = Volume + Airflowrange EndIf Set Motor_flag Case 53 To 101 i = Note_Air[noteAan - 29] * treblefactor If Airflow >= i Then Airflow = Airflow - i Else Clear Airflow EndIf 'Motorspeed = Volume + (Airflow>>2 Min Airflowrange)' + Minspeed If Airflow >> 2 < Airflowrange Then Motorspeed = Volume + (Airflow >>2) Else Motorspeed = Volume + Airflowrange EndIf Set Motor_flag EndSelect Set noteAan '= 255 'reset !!! GoTo Check_Timers 'jump out EndIf Select noteAan ' Case 12 To 24 ' If subbas = 1 Then ' Airflow = Airflow + 10 ' Motorspeed = Volume + (Airflow Min Airflowrange) '+ Minspeed ' EndIf Case 29 To 52 i = Note_Air[noteAan - 29] * basfactor Airflow = Airflow + i If subbas = 1 Then If noteAan > 35 And noteAan < 48 Then Airflow = Airflow + 10 EndIf EndIf 'Motorspeed = Volume + (Airflow>>2 Min Airflowrange) '+ Minspeed If Airflow >> 2 < Airflowrange Then Motorspeed = Volume + (Airflow >>2) Else Motorspeed = Volume + Airflowrange EndIf Set Motor_flag Case 53 To 101 i = Note_Air[noteAan - 29] * treblefactor Airflow = Airflow + i 'Motorspeed = Volume + (Airflow>>2 Min Airflowrange) '+ Minspeed If Airflow >> 2 < Airflowrange Then Motorspeed = Volume + (Airflow >>2) Else Motorspeed = Volume + Airflowrange EndIf Set Motor_flag EndSelect Set noteAan 'reset EndIf GoTo Check_Timers Case Keypres_Status 'not used here 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 GoTo Check_Timers Case ProgChange_Status 'not relevant for HarmO prog = Bytein Progchange () GoTo Check_Timers End Select EndIf If Resort_flag = 1 Then idx = SortTimers () ' so we resort only if an incoming midi command or a motor process changed something 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 ' this is a time-out timer Set Timvals[0] ' cancel timer ' WriteAnalog2 Minspeed ' sollspeed = Minspeed ' ' start the motor full-off-timer: ' Timvals[1] = time + CC72 ' 208333 ' d.i. = 5 s , in 24 us unit. ' Clear Motor_flag Case 1 ' ' motor-off-timer afgelopen. Set Timvals[1] $ifdef PWM10 WriteAnalog2 0 Clear Motorspeed Set Off_flag Clear Motor_flag $else HPWM 2, 0, fPWM Clear Motorspeed Set Off_flag Clear Motor_flag $endif 'Case Else ' ' in dit geval is idx geset ' GoTo jumpout EndSelect idx = SortTimers () ' find a new nxt and idx EndIf Else ' idx > 1, no timers running, so to avoid overflows, we can reset the timer If MaxTim = 1 Then Clear Cnt Set Timvals EndIf EndIf ' dynamic motor control: If Motor_flag = 1 Then ' adding this condition just speeds up loopspeed from 7kHz to 7.8kHz... ' so the problem is somewhere else... ' now 119kHz, unrelated to this flag. If Motor_mode = 0 Then ' steer motor continuously: (intelligent mode) ' the motorspeed variable is calculated in the main loop If Airflow >> 2 > Airflowrange Then Set YellowLED Else Clear YellowLED EndIf Else Motorspeed = Volume << 1 ' set with CC7 End If $ifdef PWM10 WriteAnalog2 Motorspeed $else HPWM 2, Motorspeed, fPWM $endif Clear Motor_flag EndIf Btg loopspeed ' =PORTA.1 04.04.2021: 7.7kHz - something makes it very slow here... ' 9.71Khz with the ack. timer remmed. ' now 119kHz, with new irq include file... Loop ' end of the main loop Proc SortTimers (), Byte 'look up the next smallest timer value in the Timvals array ' zoek de de volgende kleinste timer waarde: Set Result Set nxt.31 ' nxt is set on entry. - for speed, we just set the highest bit Clear i ' For i = 0 To Lasttask ' refused by compiler... ' If Timvals[i] < Nxt Then ' Nxt = Timvals[i] ' NrTasks dword comparisons ' Result = i ' EndIf ' Next i Do Select Timvals[i] Case < nxt nxt = Timvals[i] Result = i EndSelect Inc i Loop Until i = NrTasks Clear Resort_flag EndProc Sub KeyPres () Set notePres EndSub Sub ProgChange () Set prog 'this is not realy required EndSub 'Sub Pitchbend () ' 'only implemented on dsPIC based robots ' Set pblsb '= 255 'Endsub 'Sub Aftertouch () ' 'this is the channel aftertouch, affecting all notes ' Set aft '= 255 'not mandatory 'Endsub Sub Controller () Select Ctrl Case 7 $ifdef PWM10 Volume = value << 2 ' makes 9 bit, 0-512 Airflowrange = Maxspeed - Volume If Motor_mode = 0 Then If Airflow < Airflowrange Then Motorspeed = Volume + Airflow Else Motorspeed = Volume + Airflowrange EndIf Else Motorspeed = value << 3 ' make 10 bit EndIf $else ' this is the wind controller - 8-bit coding Volume = value Airflowrange = 255 - Volume If Motor_mode = 0 Then ' flag set with ctrl 68 'Motorspeed = Volume + (Airflow Min Airflowrange) '+ Minspeed If Airflow < Airflowrange Then Motorspeed = Volume + Airflow Else Motorspeed = Volume + Airflowrange EndIf Else Motorspeed = value << 1 ' make 8 bit End If $endif Set Motor_flag Case 66 'on/off for the robot 'power down will also reset all controllers to default values If value = 0 Then Clear Airflow Set Motor_On ' inverted! - so this switches the motor OFF Clear CC66 'Clear VelFlags0 Clear Motor_mode ' default to intelligent mode Clear GreenLED Clear BlueLED Clear YellowLED $ifdef PWM10 Volume = 512 Airflowrange = 1023 - Volume $else Volume = 64 ' default volume setting Airflowrange = 255 - Volume $endif Else Clear Motor_On ' inverted - so motor is switched ON Set CC66 ' motor ON Set GreenLED EndIf Set Motor_flag ' Case 67 ' remmed 04.04.2021 ' ' error reset controller , the value is irrelevant here ' ' must set Acknowledge for a certain time (need timertask here) ' Clear Acknowledge ' inverted! ' Cnt.Word0 = CntLw ' Timvals[1] = Cnt + 2000 Case 68 ' same controller as used on If value = 0 Then Clear Motor_mode ' intelligent mode Clear BlueLED Else Set Motor_mode ' user steered mode Set BlueLED Clear YellowLED EndIf Set Motor_flag ' Case 69 ' ' new for testing minspeed. ' Minspeed = value ' Airflowrange = 127 - Minspeed ' If Motor_mode = 0 Then ' flag set with ctrl 68 ' Motorspeed = Volume + (Airflow Min Airflowrange) + Minspeed ' Else ' Motorspeed = Volume << 1 ' make 8 bit ' EndIf Case 70 'bass register 1 notes 29 to 52, 2' register 'basfactor can run from 0, when no bass registers are drawn to 15 when all registers are drawn. If value = 0 Then If basfactor > 0 Then basfactor = basfactor - 1 ' / 2 Else basfactor = basfactor + 1 EndIf Case 71 ' bass register 2, notes 29 to 62, 8' register If value = 0 Then If basfactor > 3 Then basfactor = basfactor - 4 Else basfactor = basfactor + 4 EndIf Case 72 ' bass register 3, notes 29 to 62, 16' register If value = 0 Then If basfactor > 7 Then basfactor = basfactor - 8 Else basfactor = basfactor + 8 EndIf Case 73 ' bass register 4, notes 29 to 62, 4' register If value = 0 Then If basfactor > 0 Then basfactor = basfactor - 2 Else basfactor = basfactor + 2 EndIf Case 74 ' subbas register, notes 12 - 24 - wrong!!!' ' subbas notes are playing on notes 36 to 47 If value = 0 Then subbas = 0 Else subbas = 1 EndIf Case 75 ' treble register 1, notes 63-101, 4' register ' treblefactor can run from 0, when no treble registers are drawn up to 9 with all registers drawn If value = 0 Then If treblefactor > 0 Then treblefactor = treblefactor - 1 Else treblefactor = treblefactor + 1 EndIf Case 76 ' treble register 2, notes 63-101, 8' register If value = 0 Then If treblefactor > 1 Then treblefactor = treblefactor -2 Else treblefactor = treblefactor + 2 EndIf Case 77 ' treble register 3, notes 63-101, 16' register If value = 0 Then If treblefactor > 3 Then treblefactor = treblefactor -4 Else treblefactor = treblefactor + 4 EndIf Case 78 ' treble register 4, notes 63-101, 8' register If value = 0 Then If treblefactor > 1 Then treblefactor = treblefactor -2 Else treblefactor = treblefactor + 2 EndIf Case 100 ' for testing different windflow lookup tables If value > 0 Then ' without this we risk a divide by 0 crash If value <> divider Then divider = value GoSub Note_Air_Lookup EndIf EndIf Case 101 ' for testing motor on/off without affecting the behaviour of other pics in HarmO If value = 0 Then Set Motor_On ' inverted! - so this switches the motor OFF Clear CC66 Clear GreenLED Else Clear Motor_On ' inverted - so motor is switched ON Set CC66 ' motor ON Set GreenLED EndIf Set Motor_flag Case 123 Clear Airflow ' all notes off. Motorspeed = Volume ' half volume this is! Set Motor_flag EndSelect Set Ctrl 'mandatory reset EndSub Sub Note_Air_Lookup () ': ' lookup met het luchtverbruik in funktie van de toonhoogte. ' the divider variable can be set with ctrl #100 For i = 29 To 101 Note_Air[i -29] = ((101 - i) / divider) + 1 ' so these values run between 19 and 1 for divider = 4 ' for divider = 1 the values run between 73 and 1 ' for divider = 2 the values run between 37 and 1 ' for divider = 3 the values run between 25 and 1 ' for divider = 4 the values run between 19 and 1 ' for divider = 5 the values run between 15 and 1 ' for divider = 6 the values run between 13 and 1 ' for divider = 7 the values run between 11 and 1 ' for divider = 8 the values run between 10 and 1 ' for notes 29 to 52: between 10 and 7, for 53 to 101, between 7 and 1 ' with basfactor = 15 we get 150 to 105 ' with treblefactor = 9 we get 63 to 9 ' for divider = 16 the values run between 5 and 1 [ this is now the default] Next i EndSub '[EOF]