Tone library and RTTTL (RingTone Text Transfer Language) player

Download Tone and RTTTL player libraries v1.1

Overview

The Tone library is an enhanced version of the arduino library, useful for generating square waves.
It uses a TMR + CCP peripheral, along with an interrupt to generate square waves on any user-defined pin.

Many times the PWM peripheral is used to generate waveforms. While this is convenient, PWM on the PIC18 is tied to the clock frequency and for most devices is limited in its low-frequency range. With a limit of a 1:16 clock prescaler, generating a 30Hz audio tone using PWM would limit you to a max cpu clock of < 1MHz!

Also, unless the device has PPS you are limited by the fixed output pins of the device available for PWM.

This library uses a TMR and CCP peripheral in compare mode to generate the timing, with the output pin controlled by software using the CCP interrupt. This allows for generation of a wide range of tones at almost any clock setting. The code presented here is written for the 18FxxK22 using TMR1 and CCP1 but could easily be adapted for other devices/peripheral combinations.

Enhancements to the original arduino library include both blocking and non-blocking mode operation, and the ability to play an array of notes instead of just a single tone.

V1.1 Update

- adds events that get called when a tone starts and stops playing (see tone_events.bas and RTTTL_events.bas for examples)
- consolidate into single download for both libraries

Basic Operation

Using the module is easy. Simply define your output pin using '#option TONE_OUTPUT_PIN', include tone.bas and call the init() routine to set up the hardware.

// specify output pin
#option TONE_OUTPUT_PIN = PORTC.2
include "tone.bas"

tone.init()

There are several methods available for playing tones, the simplest of which is the 'Play(freq, duration)' routine, where 'freq' is in Hz and 'duration' in msecs. The tones will automatically stop when the duration expires, or you can use Stop() to terminate it. If 'duration' is 0 (or is not specified) then the tone plays continuously until Stop() is called.

tone.play(440, 1000)    // play A (440Hz) for 1000ms

tone.play(440)          // play note until stop
delayms(1000)           // do other stuff
tone.stop()             // stop playing tone

You can also specify a const array of notes (freq and duration word pairs) and play the entire sequence, or just a portion of it. Another method allows you to build an array in ram at runtime and play that. For ease of use, constants are defined for the 88 notes found on standard piano keys (ie NOTE_A4). A special NOTE_PAUSE allows to to insert a delay in the sequence.

const song() as word =       // an array of (freq, duration) note pairs
(
    NOTE_A4, 500,  
    NOTE_PAUSE, 250,  
    NOTE_F4, 350,
    NOTE_CS4, 850
}    
tone.play(song)

Blocking vs Non-blocking Mode

By default, Play() is non-blocking and will start a tone playing and immediately return while the tone plays (this replicates the original arduino library). You must use isPlaying() or Wait() to determine when a tone has finished before calling Play() again or the current tone will just stop.

tone.play(NOTE_A4, 1000)    // play A (440Hz) for 1000ms
while tone.isPlaying()
  // wait for tone to finish...
end while

// or
tone.play(440, 1000)    // play A (440Hz) for 1000ms
tone.wait()             // wait for tone to finish

The module allows you to change the mode to blocking so that Play() will automatically wait for tones to finish before returning. This makes it simpler if you're playing a sequence of tones. Blocking mode is enabled using the SetWait() routine, or can be set by specifying '#option TONE_WAIT = true' before including the module.

tone.setwait()          // enable blocking mode
// no longer have to call wait between notes...
tone.play(NOTE_A4, 500)    // play A for 500ms
tone.play(NOTE_G4, 500)    // play G for 500ms

Start and Stop Events

The Tone module defines two events: a Start event that's called when a tone begins playing, and a Stop event that gets called when a tone stops. You can set these to your user-defined event routines using 'set_events()'. Since the events are implemented as part of the tone library, they can also be defined when using the RTTTL player.

tone.init()
tone.setwait()          // enable blocking mode

// set tone events
tone.set_events(tone_start_event, tone_stop_event)

Tone library example program

// tone library example
program tone_example

device = 18F26K22
clock = 64

// int osc and IO pin libraries
include "intosc.bas"
#option DIGITALIO_INIT = true       // automatically call setalldigital
#option DIGITALIO_SLEWRATE = 1      // 1=slow (we don't need fast edges)
include "setdigitalio.bas"

// tone library options
// specify output pin
#option TONE_OUTPUT_PIN = PORTC.2
// wait flag setting (default = false)
'#option TONE_WAIT = true
include "tone.bas"

// star wars imperial march using individual tone play calls
sub Play_Imperial_March()
    tone.setwait()

    tone.play(NOTE_A4, 500)  
    tone.play(NOTE_A4, 500)  
    tone.play(NOTE_A4, 500)  
    tone.play(NOTE_F4, 350)  
    tone.play(NOTE_C5, 150)  
    tone.play(NOTE_A4, 500)  
    tone.play(NOTE_F4, 350)  
    tone.play(NOTE_C5, 150)  
    tone.play(NOTE_A4, 1000)  
    tone.play(NOTE_PAUSE, 250)  

    tone.play(NOTE_E5, 500)  
    tone.play(NOTE_E5, 500)  
    tone.play(NOTE_E5, 500)  
    tone.play(NOTE_F5, 350)  
    tone.play(NOTE_C5, 150)  
    tone.play(NOTE_GS4, 500)  
    tone.play(NOTE_F4, 350)  
    tone.play(NOTE_C5, 150)  
    tone.play(NOTE_A4, 1000)  
    tone.play(NOTE_PAUSE, 250)  
end sub

// const array of (freq, duration) note pairs
const Imperial_March() as word = 
(
    NOTE_A4, 500,  
    NOTE_A4, 500,  
    NOTE_A4, 500,  
    NOTE_F4, 350,  
    NOTE_C5, 150,  
    NOTE_A4, 500,  
    NOTE_F4, 350,  
    NOTE_C5, 150,  
    NOTE_A4, 1000,  
    NOTE_PAUSE, 250,  

    NOTE_E5, 500,  
    NOTE_E5, 500,  
    NOTE_E5, 500,  
    NOTE_F5, 350,  
    NOTE_C5, 150,  
    NOTE_GS4, 500,  
    NOTE_F4, 350,  
    NOTE_C5, 150,  
    NOTE_A4, 1000,  
    NOTE_PAUSE, 250
)

dim ix as byte
dim note as word
dim num_notes as word

// note list in ram (freq, duration pairs) 
dim note_list(64*2) as word

main:
    // init tone library module
    tone.init()

    // play a note until stop()
    tone.play(NOTE_A4)      // tone A (440) will play forever...
    delayms(1000)           // allow note to play for 1sec
    tone.stop()             // and now stop it

    // tone.play defaults to non-blocking mode...
    // it returns immediately and does not wait
    tone.setwait(false)         // this is the tone library default setting
    tone.play(NOTE_G4, 500)     // play G for 500ms
    tone.wait()                 // wait for note to finish

    // set play to blocking mode...
    // it now returns when done and no need to call wait
    // (you can change the tone library default using '#option TONE_WAIT = true')
    tone.setwait()              // set wait_flag true
    tone.play(NOTE_A4, 100)     // play A for 100ms

    // play all 88 piano notes defined in the tone module for 250ms each
    for ix = 0 to bound(tone.notes)
        note = tone.notes(ix)       // get note from the list
        tone.play(note, 250)        // play note for 250ms
        delayms(10)                 // short delay between notes
    next

    // play a tune using individual calls (long winded approach)
    Play_Imperial_March()

    // array functions
    // play a const array of notes
    tone.play(Imperial_March)

    // play first 9 notes from the const array
    tone.play(Imperial_March, 9)

    // play an array of notes in ram
    clear(note_list)
    note_list = Imperial_March                  // copy the const array to ram
    num_notes = NOTE_COUNT(Imperial_March)      // get number of notes defined in the const array
    tone.play(note_list, num_notes)             // play entire tune

    // play first 9 notes from the array in ram
    tone.play(note_list, 9)

    while (true)
    end while

end program


RingTone Text Transfer Language (RTTTL) Player using Tone library

RTTTL is a text-based method for specifying sequences of notes, first developed by Nokia for cell phone ringtones.

There is a description of the language in the RTTTL.bas module so I won't repeat it here. Since it uses the Tone library to generate the notes, using the player is simple... include RTTTL, define your tune, and play it! You can find many examples of RTTTL and ringtones on the web.

public const HappyBday = "HappyBday:d=4,o=5,b=125:8d.,16d,e,d,g,2f#,8p, 8d.,16d,e,d,a,2g,8p, 8d.,16d,d6,b,g,f#,2e,8p,8c6.,16c6,b,g,a,2g"

rtttl.play(@HappyBday)

RTTTL library example program

// RTTTL example (RingTone Text Transfer Language)
// uses tone module for generating notes
program RTTTL_example

device = 18F26K22
clock = 64

// int osc and IO pin libraries
include "intosc.bas"
#option DIGITALIO_INIT = true       // automatically call setalldigital
#option DIGITALIO_SLEWRATE = 1      // 1=slow (we don't need fast edges)
include "setdigitalio.bas"

// tone library
// specify output pin
#option TONE_OUTPUT_PIN = PORTC.2
include "tone.bas"

// RTTTL library (uses tone library)
include "rtttl.bas"

// some example RTTTL tunes
public const HappyBday = "HappyBday:d=4,o=5,b=125:8d.,16d,e,d,g,2f#,8p, 8d.,16d,e,d,a,2g,8p, 8d.,16d,d6,b,g,f#,2e,8p,8c6.,16c6,b,g,a,2g"
public const Simpsons = "The Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6"
public const IndianaJones = "IndianaJones:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6"
public const TakeOnMe = "TakeOnMe:d=4,o=4,b=160:8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5,8f#5,8e5,8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5"
public const Entertainer = "Entertainer:d=4,o=5,b=140:8d,8d#,8e,c6,8e,c6,8e,2c.6,8c6,8d6,8d#6,8e6,8c6,8d6,e6,8b,d6,2c6,p,8d,8d#,8e,c6,8e,c6,8e,2c.6,8p,8a,8g,8f#,8a,8c6,e6,8d6,8c6,8a,2d6"
public const Muppets = "Muppets:d=4,o=5,b=250:c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,8a,8p,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,8e,8p,8e,g,2p,c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,a,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,d,8d,c"
public const Xfiles = "Xfiles:d=4,o=5,b=125:e,b,a,b,d6,2b.,2p,e,b,a,b,e6,2b.,2p,g6,f#6,e6,d6,e6,2b.,2p,g6,f#6,e6,d6,f#6,2b.,2p,e,b,a,b,d6,2b.,2p,e,b,a,b,e6,2b.,2p,e6,2b."
public const LooneyTunes = "Looney:d=4,o=5,b=140:32p,c6,8f6,8e6,8d6,8c6,a.,8c6,8f6,8e6,8d6,8d#6,e.6,8e6,8e6,8c6,8d6,8c6,8e6,8c6,8d6,8a,8c6,8g,8a#,8a,8f"
public const TwentyCenFox = "20thCenFox:d=16,o=5,b=140:b,8p,b,b,2b,p,c6,32p,b,32p,c6,32p,b,32p,c6,32p,b,8p,b,b,b,32p,b,32p,b,32p,b,32p,b,32p,b,32p,b,32p,g#,32p,a,32p,b,8p,b,b,2b,4p,8e,8g#,8b,1c#6,8f#,8a,8c#6,1e6,8a,8c#6,8e6,1e6,8b,8g#,8a,2b"
public const Bond = "Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6"
public const MASH = "MASH:d=8,o=5,b=140:4a,4g,f#,g,p,f#,p,g,p,f#,p,2e.,p,f#,e,4f#,e,f#,p,e,p,4d.,p,f#,4e,d,e,p,d,p,e,p,d,p,2c#.,p,d,c#,4d,c#,d,p,e,p,4f#,p,a,p,4b,a,b,p,a,p,b,p,2a.,4p,a,b,a,4b,a,b,p,2a.,a,4f#,a,b,p,d6,p,4e.6,d6,b,p,a,p,2b"
public const StarWars = "StarWars:d=4,o=5,b=35:32p,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#.6,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#6"
public const ImperialMarch = "ImperialMarch:d=4,o=5,b=100:e,e,e,8c,16p,16g,e,8c,16p,16g,e,p,b,b,b,8c6,16p,16g,d#,8c,16p,16g,e,8p"
public const GoodBadUgly = "GoodBad:d=4,o=5,b=56:32a#,32d#6,32a#,32d#6,8a#.,16f#.,16g#.,d#,8p,32a#,32d#6,32a#,32d#6,8a#.,16f#.,16g#.,c#6,8p,32a#,32d#6,32a#,32d#6,8a#.,16f#.,32f.,32d#.,c#,8p,32a#,32d#6,32a#,32d#6,8a#.,16g#.,d#"
public const TopGun = "TopGun:d=4,o=4,b=31:32p,16c#,16g#,16g#,32f#,32f,32f#,32f,16d#,16d#,32c#,32d#,16f,32d#,32f,16f#,32f,32c#,16f,d#,16c#,16g#,16g#,32f#,32f,32f#,32f,16d#,16d#,32c#,32d#,16f,32d#,32f,16f#,32f,32c#,g#"
public const ATeam = "A-Team:d=8,o=5,b=125:4d#6,a#,2d#6,16p,g#,4a#,4d#.,p,16g,16a#,d#6,a#,f6,2d#6,16p,c#.6,16c6,16a#,g#.,2a#"
public const Flintstones = "Flinstones:d=4,o=5,b=40:32p,16f6,16a#,16a#6,32g6,16f6,16a#.,16f6,32d#6,32d6,32d6,32d#6,32f6,16a#,16c6,d6,16f6,16a#.,16a#6,32g6,16f6,16a#.,32f6,32f6,32d#6,32d6,32d6,32d#6,32f6,16a#,16c6,a#,16a6,16d.6,16a#6,32a6,32a6,32g6,32f#6,32a6,8g6,16g6,16c.6,32a6,32a6,32g6,32g6,32f6,32e6,32g6,8f6,16f6,16a#.,16a#6,32g6,16f6,16a#.,16f6,32d#6,32d6,32d6,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#6,16c7,8a#.6"
public const Jeopardy = "Jeopardy:d=4,o=6,b=125:c,f,c,f5,c,f,2c,c,f,c,f,a.,8g,8f,8e,8d,8c#,c,f,c,f5,c,f,2c,f.,8d,c,a#5,a5,g5,f5,p,d#,g#,d#,g#5,d#,g#,2d#,d#,g#,d#,g#,c.7,8a#,8g#,8g,8f,8e,d#,g#,d#,g#5,d#,g#,2d#,g#.,8f,d#,c#,c,p,a#5,p,g#.5,d#,g#"
public const Gadget = "Gadget:d=16,o=5,b=50:32d#,32f,32f#,32g#,a#,f#,a,f,g#,f#,32d#,32f,32f#,32g#,a#,d#6,4d6,32d#,32f,32f#,32g#,a#,f#,a,f,g#,f#,8d#"
public const Smurfs = "Smurfs:d=32,o=5,b=200:4c#6,16p,4f#6,p,16c#6,p,8d#6,p,8b,p,4g#,16p,4c#6,p,16a#,p,8f#,p,8a#,p,4g#,4p,g#,p,a#,p,b,p,c6,p,4c#6,16p,4f#6,p,16c#6,p,8d#6,p,8b,p,4g#,16p,4c#6,p,16a#,p,8b,p,8f,p,4f#"
public const MahnaMahna = "MahnaMahna:d=16,o=6,b=125:c#,c.,b5,8a#.5,8f.,4g#,a#,g.,4d#,8p,c#,c.,b5,8a#.5,8f.,g#.,8a#.,4g,8p,c#,c.,b5,8a#.5,8f.,4g#,f,g.,8d#.,f,g.,8d#.,f,8g,8d#.,f,8g,d#,8c,a#5,8d#.,8d#.,4d#,8d#."
public const LeisureSuit = "LeisureSuit:d=16,o=6,b=56:f.5,f#.5,g.5,g#5,32a#5,f5,g#.5,a#.5,32f5,g#5,32a#5,g#5,8c#.,a#5,32c#,a5,a#.5,c#.,32a5,a#5,32c#,d#,8e,c#.,f.,f.,f.,f.,f,32e,d#,8d,a#.5,e,32f,e,32f,c#,d#.,c#"
public const MissionImp = "MissionImp:d=16,o=6,b=95:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,a#,g,2d,32p,a#,g,2c#,32p,a#,g,2c,a#5,8c,2p,32p,a#5,g5,2f#,32p,a#5,g5,2f,32p,a#5,g5,2e,d#,8d"
public const Pacman = "Pacman:d=4,o=5,b=112:32b,32p,32b6,32p,32f#6,32p,32d#6,32p,32b6,32f#6,16p,16d#6,16p,32c6,32p,32c7,32p,32g6,32p,32e6,32p,32c7,32g6,16p,16e6,16p,32b,32p,32b6,32p,32f#6,32p,32d#6,32p,32b6,32f#6,16p,16d#6,16p,32d#6,32e6,32f6,32p,32f6,32f#6,32g6,32p,32g6,32g#6,32a6,32p,32b.6"
public const OffToSeeWiz = "OffToSeeWiz:d=32,o=5,b=45:16g#.,16c#6,f#,16f,f#,g#,8c#6,g#,a#,g#,f#,f,f#,d#,8c#.,c#,16c#,c#,16c#6,c#6,c6,c6,c6,16a#,a#,d#6,d#6,d#6,16d#6,c6,8g#"
public const MontyPython = "MontyPython:d=4,o=5,b=200:32p,f6,8e6,d6,8c#6,c6,8b,a#,8a,8g,8a,8a#,a,8g,2c6,8p,8c6,8a,8p,8a,8a,8g#,8a,8f6,8p,8c6,8c6,8p,8a,8a#,8p,8a#,8a#,8p,8c6,2d6,8p,8a#,8g,8p,8g,8g,8f#,8g,8e6,8p,8d6,8d6,8p,8a#,8a,8p,8a,8a,8p,8a#,2c6,8p,8c6"

const SONG_DELAY = 1000     // 1000ms delay between songs

main:
    // init tone library module
    tone.init()

    // play all the example RTTTL tunes
    rtttl.play(@HappyBday)
    delayms(SONG_DELAY)

    rtttl.play(@Simpsons)
    delayms(SONG_DELAY)

    rtttl.play(@IndianaJones)
    delayms(SONG_DELAY)

    rtttl.play(@TakeOnMe)
    delayms(SONG_DELAY)

    rtttl.play(@Entertainer)
    delayms(SONG_DELAY)

    rtttl.play(@Muppets)
    delayms(SONG_DELAY)

    rtttl.play(@Xfiles)
    delayms(SONG_DELAY)

    rtttl.play(@LooneyTunes)
    delayms(SONG_DELAY)

    rtttl.play(@TwentyCenFox)
    delayms(SONG_DELAY)

    rtttl.play(@Bond)
    delayms(SONG_DELAY)

    rtttl.play(@MASH)
    delayms(SONG_DELAY)

    rtttl.play(@StarWars)
    delayms(SONG_DELAY)

    rtttl.play(@ImperialMarch)
    delayms(SONG_DELAY)

    rtttl.play(@GoodBadUgly)
    delayms(SONG_DELAY)

    rtttl.play(@TopGun)
    delayms(SONG_DELAY)

    rtttl.play(@ATeam)
    delayms(SONG_DELAY)

    rtttl.play(@Flintstones)
    delayms(SONG_DELAY)

    rtttl.play(@Jeopardy)
    delayms(SONG_DELAY)

    rtttl.play(@Gadget)
    delayms(SONG_DELAY)

    rtttl.play(@Smurfs)
    delayms(SONG_DELAY)

    rtttl.play(@MahnaMahna)
    delayms(SONG_DELAY)

    rtttl.play(@LeisureSuit)
    delayms(SONG_DELAY)

    rtttl.play(@MissionImp)
    delayms(SONG_DELAY)

    rtttl.play(@Pacman)
    delayms(SONG_DELAY)

    rtttl.play(@OffToSeeWiz)
    delayms(SONG_DELAY)

    rtttl.play(@MontyPython)
    delayms(SONG_DELAY)

    // we're done
    tone.play(NOTE_A4, 2000)
    tone.wait

    while (true)
    end while

end program

RTTTL example #2 - Using events

// RTTTL example with tone events to control LEDs
program RTTTL_events_example

device = 18F26K22
clock = 16

// int osc and IO pin libraries
include "intosc.bas"
#option DIGITALIO_INIT = true       // automatically call setalldigital
#option DIGITALIO_SLEWRATE = 1      // 1=slow (we don't need fast edges)
include "setdigitalio.bas"

// tone library
// specify output pin
#option TONE_OUTPUT_PIN = PORTA.0
// wait flag setting (default = false)
#option TONE_WAIT = true
include "tone.bas"

// RTTTL library (uses tone library)
include "rtttl.bas"

// there are 8 leds... one for each note (C, D, E, F, G, A, B), and a spare
#option _LED_PORT = PORTC
#option _LED_PORT_TRIS = GetTris(_LED_PORT)

dim LED_PORT as _LED_PORT,
    LED_PORT_TRIS as _LED_PORT_TRIS,
    LED_C as LED_PORT.0,     // led 1
    LED_D as LED_PORT.1,     // led 2
    LED_E as LED_PORT.2,     // led 3
    LED_F as LED_PORT.3,     // led 4
    LED_G as LED_PORT.4,     // led 5
    LED_A as LED_PORT.5,     // led 6
    LED_B as LED_PORT.6,     // led 7
    LED_8 as LED_PORT.7      // led 8 (outside RTTTL range)

//------------
// tone events
//------------
// tone start event - turn on leds
// there are 12 notes in a scale, but there are only 8 leds
// combine the notes and sharps together into a single led (ie, C or C# -> LED_C, G or G# -> LED_G, etc)
// since RTTTL supports 4 scales (C4 to C7) combine all the scales into one
// so that a C played in any scale turns on LED_C
// for any notes outside the 4 scales covered by RTTTL it turns on LED_8
event tone_start_event()
    dim freq as word

    // get the current freq
    freq = tone.freq()

    // put all RTTTL supported notes into the same scale (scale 4)
    if (freq > NOTE_B6) then
        freq = freq >> 3            // divide by 8... scale 7 -> 4 
    elseif (freq > NOTE_B5) then
        freq = freq >> 2            // divide by 4... scale 6 -> 4
    elseif (freq > NOTE_B4) then
        freq = freq >> 1            // divide by 2... scale 5 -> 4
    endif

    // determine which led to turn on
    select (freq)
        case > NOTE_B4      // above B7... lite spare LED
            LED_8 = 1
        case > NOTE_AS4     // B
            LED_B = 1
        case > NOTE_GS4     // A and A#
            LED_A = 1
        case > NOTE_FS4     // G and G#
            LED_G = 1                        
        case > NOTE_E4      // F and F#
            LED_F = 1
        case > NOTE_DS4     // E
            LED_E = 1
        case > NOTE_CS4     // D and D#
            LED_D = 1
        case > NOTE_B3      // C and C#
            LED_C = 1
        else
            LED_8 = 1       // below C4/unknown... lite spare LED
    end select                                                                    
end event

// tone stop event - turn off leds
event tone_stop_event()
    LED_PORT = 0        // turn off all leds
end event


// some example RTTTL tunes
public const IndianaJones = "IndianaJones:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6"

main:
    // set led pins as outputs and turn them off
    LED_PORT = 0
    LED_PORT_TRIS = 0

    // init tone library module
    tone.init()
    tone.setwait()              // set wait_flag true

    // set tone events
    tone.set_events(tone_start_event, tone_stop_event)

    // play tune
    rtttl.play(@IndianaJones)

    while (true)
    end while

end program