Phillip Trelford's Array

POKE 36879,255

Monokeys

Press a key to hear a note:


GAFBB should sound familiar to fans of 70s sci-fi.

I wanted to programmatically generate sound effects for a retro game I’m working on. Generating wave files using F# turned out to be less than 30 lines of code:

open System.IO

/// Write WAVE PCM soundfile (8KHz Mono 8-bit)
let write stream (data:byte[]) =
    use writer = new BinaryWriter(stream)
    // RIFF
    writer.Write("RIFF"B)
    let size = 36 + data.Length in writer.Write(size)
    writer.Write("WAVE"B)
    // fmt
    writer.Write("fmt "B)
    let headerSize = 16 in writer.Write(headerSize)
    let pcmFormat = 1s in writer.Write(pcmFormat)
    let mono = 1s in writer.Write(mono)
    let sampleRate = 8000 in writer.Write(sampleRate)
    let byteRate = sampleRate in writer.Write(byteRate)
    let blockAlign = 1s in writer.Write(blockAlign)
    let bitsPerSample = 8s in writer.Write(bitsPerSample)
    // data
    writer.Write("data"B)
    writer.Write(data.Length)
    writer.Write(data)

let sample x = (x + 1.)/2. * 255. |> byte 

let data = Array.init 16000 (fun i -> sin (float i/float 8) |> sample)
let stream = File.Create(@"C:\tone.wav")
write stream data

F#’s ASCII string support and light syntax, combined with .Net’s convenient BinaryWriter class made this ridiculously easy.

Then just for fun I created the simple keyboard in 70 lines of code:

namespace MonoKeys

open System
open System.Windows
open System.Windows.Controls
open Microsoft.Xna.Framework.Audio

type App() as this = 
    inherit Application()

    let sampleRate = 8000

    let sample x = x * 32767. |> int16

    let toBytes (xs:int16[]) =
        let bytes = Array.CreateInstance(typeof<byte>, 2 * xs.Length)
        Buffer.BlockCopy(xs, 0, bytes, 0, 2 * xs.Length)
        bytes :?> byte[]

    let create freq =
        let samples = 12000
        let sine i = sin (Math.PI * 2. * float i / float sampleRate * freq)
        let fadeOut i = float (samples-i) / float samples
        Array.init samples (fun i -> sine i * fadeOut i |> sample)
        |> toBytes

    let play freq =        
        let bytes = create freq
        let effect = new SoundEffect(bytes, sampleRate, AudioChannels.Mono)
        effect.Play() |> ignore
    
    let notes = [
        "A", 220.00
        "A#", 233.08
        "B", 246.94
        "C", 261.63
        "C#", 277.18
        "D", 293.66
        "D#", 311.13
        "E", 329.63
        "F", 349.23
        "F#", 369.99
        "G", 392.00
        "G#", 415.30
        "A", 440.00
        "A#", 466.16
        "B", 493.88
        "C", 523.25
        "C#", 554.37
        "D", 587.33]

    let keys =
        notes |> Seq.map (fun (text,freq) ->
            let key = Button(Content=text)
            key.Click.Add(fun _ -> play freq)
            key
        )

    let grid = Grid()
    do  keys |> Seq.iteri (fun i key ->
            grid.ColumnDefinitions.Add(ColumnDefinition())
            Grid.SetColumn(key,i)
            grid.Children.Add key
        )

    do  this.Startup.AddHandler(fun o e -> this.RootVisual <- grid)


This time Silverlight 5’s SoundEffect class makes generating sounds easy accepting an array of bytes. I simply copied the notes from the web, formatted the text as tuples and mapped them to buttons that showed the text and played a note of the specified frequency. The keys were then laid out on a grid. Look no XAML ;)

Next I’m planning to add some more features to generate sounds like a synthesizer maybe one day like my monotribe

If you’re interested in programming and sound I’d recommend taking a look at Sam Aaron’s Overtone for Clojure and Rob Pickering’s Undertone for F#.

Rob will be running a session on Undertone at this year’s Progressive F# Tutorials in London:

progressivefsharplondon2013

Comments (3) -

  • mike

    6/5/2013 6:17:28 AM |

    I think it should be GAFAC Smile

  • Anthony

    7/12/2013 12:44:00 PM |

    Actually, it's GAF4F3C

    "Start with the tone…Up a full tone…down a major third…Now drop an octave…Up a perfect fifth."

Pingbacks and trackbacks (3)+

Comments are closed