The title of this post was a bit obscure as I didn’t really know what I was going to do for this entry on the #FsAdvent Calendar Make sure you check out the previous and future entries in the series.

For today, I thought we would celebrate with sound, in the Key of F# :D

src reddit

To that effect we can use OpenAl. To be honest this was a little more difficult to do that it needed to be, mostly because there was plenty of playing around with values to make sure stuff worked and sounded somewhat ok.

The good thing about this sample is that you can run the sample either via a console app or in F# interactive (make sure you change the path to your location of OpenAl, you should get the NuGet package called OpenTKWithOpenAL) Disclaimer this is the first time I play with vanilla OpenAl (it will probably show).

Bird’s eye

The end goal is to transform something like this "F# F# G# F# F# D# D# D# F# F# G# F# F# D# D# D#" into a collection of numbers that represent a wave, then we can feed that into OpenAl for it to play. The graph below represent one note (generated with F# Charting)

Step by Step

To start off you need a context, a buffer and a source, these are OpenAl requirements.

use audioContext = new AudioContext () let buffer = AL . GenBuffer () let audioSourceIndex = AL . GenSource ()

Then we need to get something to play, if our input is a string with the whole song then we can do something like this to get the frequencies calculated for each note

let toFrequency ( note : string ) = match note . ToUpper () with | "C" -> 261 . 626 f | "C#" -> 277 . 183 f | "D" -> 293 . 665 f | "D#" -> 311 . 127 f | "E" -> 329 . 628 f | "F" -> 349 . 228 f | "F#" -> 369 . 994 f | "G" -> 391 . 995 f | "G#" -> 415 . 305 f | "A" -> 440 . 0 f | "A#" -> 466 . 164 f | "B" -> 493 . 883 f |_ -> 369 . 994 f (*Defaults to F# because xMas ;) *)

I was thinking we could have a discriminated union here, however this seemed to serve the purpose of this example in a simple and concise way.

You might be wondering how I came to those numbers, well turns up the internet is great for this sort of thing, they can be found here. Tried a few octaves and these were the ones I preferred.

Then, with some research into the OpenAl docs and a bit of reading into Duality’s source code this is how we can convert those frequencies into data that can be played.

let samplingFrequency = 44100 . 0 let generateNote ( freq : float32 ) = let noteLength = 0 . 5 let seqLength = int ( samplingFrequency * noteLength ) Seq . init seqLength ( fun i -> ( 2 . 0 * Math . PI * float freq ) / samplingFrequency * float i |> Math . Sin |> ( * ) ( float Int16 . MaxValue ) |> int16 )

As mentioned in the first section, we are using 44100 as the base frequency, to simplify things the note length is set to half a second, however this is something that could be easily changed to support different lengths. Then we are creating a sequence that is half the frequency, and the reason for that is this is the number of samples required. Each item in that sequence is part of a curve that represents the sound for that given note (the freq parameter).

With those two key functions you can create the complete collection. In this case Seq.collect really shines, it is perfectly suited to what we need to do:

Combines the given enumeration-of-enumerations as a single concatenated enumeration.

let freqToWaves = toFrequency >> generateNote let data = notes |> Seq . collect freqToWaves |> Array . ofSeq

Finally we use OpenAl to actually play the wave:

AL . BufferData ( buffer , ALFormat . Mono16 , data , data . Length * 2 , samplingFrequency ) AL . Source ( audioSourceIndex , ALSourcei . Buffer , buffer ) AL . SourcePlay ( audioSourceIndex )

There is a mystery song you can play (in the key of F#)

You can see it all together in this sample here

Summary

Try it, you will probably love it. Another little toy to play with during the jolly season.

Thanks to Sergey and the F# community for this cool FsAdvent.

Also thanks to everyone (yes you, you rock!) for a truly amazing year.

The gif below seems completely unsuitable but you are here already :)