##### Driver & Library Initilisation ##### [ING] #####
from machine import Pin, PWM, Timer, I2C
from toneLib import allNotes, mySong
import utime, array, sh1106

##### Configuration Options ##### [ING] #####
songSpeed = 120 #bpm for 4/4
usingWokwi = True  #change to False when using Pi Pico
oledPresent = True

mainVolume = 32000  # max of 32768 (max duty cycle)
pwm1 = machine.PWM(machine.Pin(18))  # buzzer 1 on pin GP18
pwm2 = machine.PWM(machine.Pin(20))  # buzzer 2 on pin GP20
printNotes = 0  #default is 0.  printNotes = 1 for debugging code
decayRate1 = 1.1
decayRate2 = 1.03

##### Blinking LED Initilisation ##### [ING] #####
myLed = Pin("LED",Pin.OUT)  # Raspberry Pi Pico W: built-in LED
LEDTimer = Timer()
def blink(LEDTimer):
    myLed.value(0)

##### Display Initilisation ##### [ING] #####
if oledPresent:
    i2c = machine.I2C(0,sda=machine.Pin(4),scl=machine.Pin(5), freq=2000000) # Around 3MHz is limit.
    oled = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c, rotate=180)
    oled.rotate=180
    oled.sleep(False)
startLine = 0
leftMargin = 0
newLine = False
pageOfText = []
displayTimer = Timer()

##### Routine to feed a line of text (keep to 16 long) ##### [Author: ING] #####
def updateDisplay(displayTimer):
    global newLine, pageOfText
    if oledPresent:
        if newLine:
            linePointer = 0
            oled.fill(0)
            for i in range(startLine,len(pageOfText)):
                oled.text(pageOfText[i], leftMargin, linePointer)
                linePointer += 10
            oled.show()
            newLine = False

##### Routine to process text lists so its ready for updateDisplay() ##### [Author: ING] #####
def oPrint(newText):
    global pageOfText, newLine
    numLines = len(pageOfText)
    i=0
    if numLines < 6:
        pageOfText.append(newText)
    else:
        for i in range(0,numLines-1):
            pageOfText[i] = pageOfText[i+1]
        pageOfText[5]=newText
    newLine = True

def tickNow(noteTimer):
    global noteTicker
    noteTicker = True 
        
def play_poly_note(buzzNum, frequency, volume):
    global pwm1, pwm2
    frequencyHz = int (allNotes[frequency])
    if buzzNum == 1:
        if frequencyHz > 0:
            pwm1.freq(frequencyHz)
            pwm1.duty_u16(volume)
        else:
            pwm1.duty_u16(0) # rest
        
    if buzzNum == 2:
        if frequencyHz > 0:
            pwm2.freq(frequencyHz)
            pwm2.duty_u16(volume)
        else:
            pwm2.duty_u16(0) # rest
         
def stopProg():
    while 1:
        pwm1.deinit()
        pwm2.deinit()

##### Music Initilisation ##### [Author: ING] #####
if usingWokwi:
    songSpeed *= 4    # Wokwi plays a lot slower.
wholeNoteLength = (60/songSpeed)*4*1000 #for 4/4, in msecs.
playLength1 = 0
playLength2 = 0
stopNote1 = False
stopNote2 = False
playCounter1 = 100
playCounter2 = 100
getNextNote = True
noteNumber = 0 # start at the first note in array
note = mySong[noteNumber]
songLength = len (mySong)
noteTimer = Timer()

#################################
#### Main Start ##### [ING] #####
#################################
if printNotes:
    print ('songLength =',songLength)
    print('1 bar (1 whole note) takes', wholeNoteLength/1000,'seconds')
LEDTimer.init(freq=5, mode=Timer.PERIODIC, callback=blink)
if oledPresent:
    displayTimer.init(freq=3, mode=Timer.PERIODIC, callback=updateDisplay)
noteTimer.init(freq=100,mode=Timer.PERIODIC, callback=tickNow) # updateBuzzStates serviced every 10msec (100Hz)
noteTicker = False
barCounter = 0
decayVolume1 = 0
decayVolume2 = 0

#### Main Loop ##### [ING] #####
while 1:
    if getNextNote:
        myLed.value(1)
        getNextNote = False
        if (noteNumber == songLength):
            oPrint('Song finished!')
            stopProg()

        note = mySong[noteNumber]
        if note[0] == 2:  # buzzer 2
            play_poly_note(2, note[1], mainVolume)
            decayVolume2 = mainVolume
            if printNotes:
                print('playing Note:', noteNumber-1, note[1], 'on buzzer 2')
            playLength2 = note[2]
            noteNumber += 1 # advance and also play buzzer 1
            note = mySong[noteNumber]
            
        if note[0] == 1:  # buzzer 1
            play_poly_note(1, note[1], mainVolume)
            decayVolume1 = mainVolume
            if printNotes:
                print('playing Note:', noteNumber-1, note[1], 'on buzzer 1')
            playLength1 = note[2]
       
        if note[0] == 0:  # print the line of text
            if note[1] == 'Bar':
                barCounter += 1
                songString = note[1]+" "+str(barCounter)
            else:
                songString = note[1]
            oPrint(songString)
                      
        noteNumber += 1
####       
    if noteTicker == True:
        noteTicker = False
        if playLength1 >0:
            playCounter1 = wholeNoteLength/(10*playLength1)
            if printNotes:
                print ('for',playCounter1, 'ticks')
            playLength1 = 0
        else:
            if playCounter1 > 0:
                playCounter1 -= 1
                decayVolume1 = pwm1.duty_u16() # get the current duty cycle
                if printNotes:
                    print('Vol 1 is',decayVolume1)
                tempDecay = round(decayVolume1/decayRate1)
                if decayVolume1 < tempDecay:
                    decayVolume1 = 0
                else:
                    decayVolume1 = tempDecay
                pwm1.duty_u16(decayVolume1)
            else:
                stopNote1 = True
        
        if playLength2 >0:
            playCounter2 = wholeNoteLength/(10*playLength2)
            if printNotes:
                print ('for',playCounter2, 'ticks')
            playLength2 = 0
        else:
            if playCounter2 >0:
                playCounter2 -= 1
                decayVolume2 = pwm2.duty_u16() # get the current duty cycle
                if printNotes:
                    print('Vol 2 is',decayVolume2)
                tempDecay = round(decayVolume2/decayRate2)
#                if decayVolume2 < tempDecay:
#                    decayVolume2 = 0
#                else:
                decayVolume2 = tempDecay
                pwm2.duty_u16(decayVolume2)
            else:
                stopNote2 = True 

    if stopNote1 == True:
        stopNote1 = False
        pwm1.duty_u16(0)
        getNextNote = True

    if stopNote2 == True:
        stopNote2 = False
        pwm2.duty_u16(0)

#### END ##### [ING] #####
$abcdeabcde151015202530354045505560fghijfghij
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT