Friday, July 12, 2013

Generating music

You may or may not be aware of this, but HTML5 through the new JavaScript APIs enables browsers to act like sand boxed operating systems. Drawing functions and webgl with <canvas>, the joystick api, web sockets(a client-to-host HTTP extension), webRTC(Real-Time Communications), the full screen API, the cursor lock API, ... this list goes on and on. All of these things can be used for gaming, some in more obvious ways than others. This post  is about something I did with the Web Audio API.

It took about a week once I started, but I finally managed to get a primitive synthesizer. Eventually, I hope to make it able to play customized instruments, but for now we are stuck with a sine wave. I wish to thank Rachel Bell, who published the sheet music I used to test the synthesizer to her blog.


Below is the code for my synthesizer:



Here's a sample of what I can do with my synthesizer:

Let's see how it works:
Step 1: declare an audio player variable
var ap;
A load function is not necessary, but can help. I am just used to DOM programming, so I have a load() function.
function load()
{
The load function initializes ap as a new AudioPlayer with an array of objects. In this case, there is only one object in the array.
The array should be filled with songs, which in JSON look something like this:
{
 "notes":[],
 "durations":[],
 "tempo":90,
 "timeSignature":{"top":3,"bottom":4}
}

Notes should be based on the index of a key on the piano. The program uses the function to find the fundamental frequency for a given key given at that wikipedia page.
durations should be the type of note given. For example, if you want a quarter note at a particular index, put 1/4 at the index.
Tempo can be changed to make the song faster or slower.
timeSignature is very important, but defaults to 4/4({top:4, bottom:4}).
The algorithm is the natural mathematics in music. For any time signature with bottom=n, the n-note gets the beat and is played at tempo beats per minute. A quarter notes is 1/4*n beats, A half note is 1/2 * n beats, A dotted half note is 3/4 * n beats, etc. Top doesn't really matter in the time signature right now. I might implement a down beat later.


ap = new AudioPlayer([{"notes":[-1,51,48,46,48,51,48,46,48,51,55,53,51,49,48,44,46,44,43,51,48,46,48,51,48,46,48,51,55,53,56,56,51,56,56,53,58,56,49,54,51,47,49,-1,56,56,53,58,56,61,61,59,58,56,56,58,60,61,63,-1,51,48,46,48,51,48,46,48,51,55,53,51,49,48,44,46,44,43,51,48,46,48,51,48,46,48,51,56,55,58,56,63,56,56,53,58,56,49,54,51,47,49,-1,56,56,53,58,56,61,61,59,58,56,56,58,60,61,63],"durations":[0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,1.25,0.25,0.75,0.5,0.25,0.75,0.75,0.75,0.5,0.25,0.75,0.5,0.25,0.75,0.5,0.25,0.75,0.75,0.25,0.25,0.25,0.25,0.125,0.125,0.125,0.125,1.5,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,0.5,0.25,1.25,0.25,0.75,0.5,0.25,0.75,0.75,0.75,0.5,0.25,0.75,0.5,0.25,0.75,0.5,0.25,0.75,0.75,0.25,0.25,0.25,0.25,0.125,0.125,0.125,0.125,1.5],"tempo":90,"timeSignature":{"top":3,"bottom":4}}]);
}
window.addEventListener('load', load)


When the button is pushed, the play() function is called.
function play()
{
 The return value of AudioPlayer.play() is the duration of the song, in seconds.
 The AudioPlayer.play function takes a few arguments in a specific order:

  1. The index of the song to play
  2. The time to wait before beginning the song
  3. A function that sets up an instrument to play each note.
  4. The node to connect each note to for additional manipulation
 These arguments allow the use of the features of the WebAudioAPI for flexibility. Eventually I will give it room to play to an OfflineAudioContext to record what the programmer wants on initialize, and play the generated music on button press, but now I do not have the time to do such a thing.


 console.log(ap.play(0, window.speakers, AudioPlayer.Instruments.triangleWave ));
}



If you click the button twice and the noise overlaps in an ugly way, refresh the page. I haven't bothered figuring out how to re-enable the button if I were to disable it, so I'll just leave it enabled.

Yes, I am a big Legend of Zelda fan, in case you couldn't tell from my previous post.

No comments:

Post a Comment