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: