Play RTTL ringtone as beeps

Yesterday, while sending remote beep commands to my NSLU2[2 ] to startle my cat, I realised that the beep command accepts -f frequency and -l length parameters. Then I came accross this page[1 ] on the NSLU2 Linux site and saw that someone wrote a simple bash script to play beeps from a file. I thought, hey, what about playing those Nokia ringtones (you know, the simple ASCII based ringtones we used to use when handphones can only do monophonic). So here's a script that plays ringtones in Nokia's RTTL (Ring Tone Transfer Language) format written with the aid of Tcl and a couple of google searches.

BTW, this only works on Linux (unless you have a compatible "beep" executable installed) and you need to run it as a superuser. On Debian based distros you can simply apt-get install beep.

  #! /usr/bin/env tclsh
  # nokia2beeps.tcl - Play an RTTL ringtone file as beeps
  # Usage: ./nokia2beeps.tcl ringtone_file
  set default_octave 4
  set default_duration 4
  set default_beat 100
  set base_time 200000

  array set scale {
    C0   16.35    C#0  17.32    Db0  17.32    D0   18.35    D#0  19.45    Eb0  19.45
    E0   20.60    F0   21.83    F#0  23.12    Gb0  23.12    G0   24.50    G#0  25.96
    Ab0  25.96    A0   27.50    A#0  29.14    Bb0  29.14    B0   30.87    C1   32.70
    C#1  34.65    Db1  34.65    D1   36.71    D#1  38.89    Eb1  38.89    E1   41.20
    F1   43.65    F#1  46.25    Gb1  46.25    G1   49.00    G#1  51.91    Ab1  51.91
    A1   55.00    A#1  58.27    Bb1  58.27    B1   61.74    C2   65.41    C#2  69.30
    Db2  69.30    D2   73.42    D#2  77.78    Eb2  77.78    E2   82.41    F2   87.31
    F#2  92.50    Gb2  92.50    G2   98.00    G#2  103.83   Ab2  103.83   A2   110.00
    A#2  116.54   Bb2  116.54   B2   123.47   C3   130.81   C#3  138.59   Db3  138.59
    D3   146.83   D#3  155.56   Eb3  155.56   E3   164.81   F3   174.61   F#3  185.00
    Gb3  185.00   G3   196.00   G#3  207.65   Ab3  207.65   A3   220.00   A#3  233.08
    Bb3  233.08   B3   246.94   C4   261.63   C#4  277.18   Db4  277.18   D4   293.66
    D#4  311.13   Eb4  311.13   E4   329.63   F4   349.23   F#4  369.99   Gb4  369.99
    G4   392.00   G#4  415.30   Ab4  415.30   A4   440.00   A#4  466.16   Bb4  466.16
    B4   493.88   C5   523.25   C#5  554.37   Db5  554.37   D5   587.33   D#5  622.25
    Eb5  622.25   E5   659.26   F5   698.46   F#5  739.99   Gb5  739.99   G5   783.99
    G#5  830.61   Ab5  830.61   A5   880.00   A#5  932.33   Bb5  932.33   B5   987.77
    C6   1046.50  C#6  1108.73  Db6  1108.73  D6   1174.66  D#6  1244.51  Eb6  1244.51
    E6   1318.51  F6   1396.91  F#6  1479.98  Gb6  1479.98  G6   1567.98  G#6  1661.22
    Ab6  1661.22  A6   1760.00  A#6  1864.66  Bb6  1864.66  B6   1975.53  C7   2093.00
    C#7  2217.46  Db7  2217.46  D7   2349.32  D#7  2489.02  Eb7  2489.02  E7   2637.02
    F7   2793.83  F#7  2959.96  Gb7  2959.96  G7   3135.96  G#7  3322.44  Ab7  3322.44
    A7   3520.00  A#7  3729.31  Bb7  3729.31  B7   3951.07  C8   4186.01  C#8  4434.92
    Db8  4434.92  D8   4698.64  D#8  4978.03  Eb8  4978.03
  }

  proc nokia2scale {note} {
    global default_octave
    set note [string toupper [regexp -inline {[a-gA-GpP].*} $note]]
    if {$note != "P" && ![regexp {\d$} $note]} {
      append note $default_octave
    }
    set note [string map {. ""} $note]
    return $note
  }

  proc nokia2length {note} {
    global default_duration default_beat base_time
    set duration [regexp -inline {^\d+} $note]
    if {$duration == ""} {
      set duration $default_duration
    }
    return [expr {$base_time/$default_beat/$duration}]
  }

  proc playnote {note} {
    global scale

    set note [string trim $note]
    if {$note == ""} return

    puts -nonewline "$note "   
    flush stdout

    set delay [nokia2length $note]
    set note  [nokia2scale  $note]

    if {$note == "P"} {
      after $delay
    } else {
      exec beep -f $scale($note) -l $delay
    }
  }

  proc playRTTL {str} {
    global default_octave default_beat default_duration

    foreach x [regexp -inline -all {[dob]=\d+} $str] {
      regexp {(\w)=(\d+)} $x -> param value
      switch $param {
        d {set default_duration $value}
        o {set default_octave   $value}
        b {set default_beat   $value}
      }
    }

    regexp {\:([^\:]+)$} $str -> str
    puts "beat: $default_beat"

    foreach x [split $str ,] {
      playnote $x
    }
    puts done.
  }

  set f [open [lindex $argv 0]]
  set data [read $f]
  close $f

  playRTTL $data

Sample RTTL file:

Here's Jingle Bells in RTTL format:

  JingleBells:d=4,o=5,b=125:8g,8e6,8d6,8c6,2g,8g,8e6,8d6,8c6,2a,8a,8f6,8e6,
  8d6,8b,8g,8b,8d6,8g.6,16g6,8f6,8d6,2e6,8g,8e6,8d6,8c6,2g,16f#,8g,8e6,
  8d6,8c6,2a,8a,8f6,8e6,8d6,8g6,16g6,16f#6,16g6,16f#6,16g6,16g#6,8a.6,
  16g6,8e6,8d6,c6,g6,8e6,8e6,8e.6,16d#6,8e6,8e6,8e.6,16d#6,8e6,8g6,8c.6,
  16d6,2e6,8f6,8f6,8f.6,16f6,8f6,8e6,8e6,16e6,16e6,8e6,8d6,8d6,8e6,2d6