mike hodnick

Point your browser to www.hodnick.com for Mike's latest content.

Notice:

You are viewing Mike's old, archived site. For new content, navigate to hodnick.com

Latest From Twitter...

The Blog

July 2009 Entries

Source Code: Sine.zip (12 kb)

I haven’t really found a nuts-and-bolts explanation of how to produce (synthesize) sound with Silverlight 3.  Through trial and error and referencing other more complex examples I was able to finally figure it out.  My hope here is to lay things out so that folks can get their hands on the “boilerplate” Silverlight 3 code required to get an audio stream working and how to also assemble the audio stream bytes into something coherent (such as a sine wave).

First, let’s talk about the definition of the audio stream.  The audio stream has a few important characteristics that will influence how the sine wave tone is generated:

  • Sample – a single point in the audio stream. 
  • Channel Count – this defines whether the sound is in mono or stereo.  One channel = mono, two channels = stereo.
  • Bits Per Sample – this defines the granularity of each sample calculation in your audio.  16 bits is typical.  You could perform calculations of 8 bits or 24 bits per sample if you’d like.  16 bits equals two bytes – so that’d be two bytes per sample. 
  • Sample Rate – CD-quality audio is 44100 samples per second. 
  • Byte Rate – this is the speed at which bytes should be read from the stream.  It is a function of the sample rate, channel count, and bits per sample.

The “boilerplate” audio stream code that sets up these parameters isn’t something I want to spend much time on.  There are a number of examples out there that explain it.  You can reference Pete Brown’s article or Gilles Khouzam’s example for how to get started.  Basically, you need to create a custom class that derives from System.Windows.Media.MediaStreamSource.  In this class you will set up the definition of your audio stream when overriding the OpenMediaAsync() method.  This will include how many channels the audio has, the sample rate, and the bits per sample.

Understand that I’m breezing through the implementation of this code – not the importance of it. 

The meat of your audio-producing code is in your custom MediaStreamSource’s GetSampleAsync() method.  It will look something like this:

protected override void GetSampleAsync(
	MediaStreamType mediaStreamType)
{
	int numSamples = 128; //a number you choose...
	int bufferByteCount = channelCount * 
		BitsPerSample / 
		8 * numSamples;

	//WRITE DATA TO MEMORY STREAM HERE...
	//memoryStream.WriteByte(...);

	//emptySampleDict set up in boilerplate code...
	MediaStreamSample mediaStreamSample =
		new MediaStreamSample(
			mediaStreamDescription, 
			memoryStream, 
			currentPosition,
			bufferByteCount, 
			currentTimeStamp, 
			emptySampleDict);  

	//typical code for stream position/seeking:
	currentTimeStamp += bufferByteCount * 
		10000000L / byteRate;
	currentPosition += bufferByteCount;
	ReportGetSampleCompleted(mediaStreamSample);
}

Again, this is pretty much “boilerplate” code, but let’s stop and take a look at a few things here.  First, the numSamples field.  In my example above, it is set to a value of 128.  In theory, this value can be set to almost any positive integer value.  The purpose of this value though is to control how many samples are written to the memory stream whenever the Silverlight client app requests more samples.  It really comes down to how responsive you want your audio stream to be, or how much of a “buffer” your code needs to catch up.  A larger value will allow your code to load more data into the stream and it won’t have to execute as often.  In my experience, a value between 128 and 384 seems to work well.  You’ll need to find a good value that works for your own code.

Next, there is all the code below the “//WRITE DATA TO MEMORY STREAM HERE” comment.  All that code below is also “boilerplate” code that just sends your memory stream out to the client. 

The real question is: what bytes do you write to the memory stream?

To begin answering this question, we need to refer back to the audio stream definition.  Specifically, the bits per sample and number of channels. 

Let’s say we have a single-channel (a.k.a. mono) audio stream with 16 bits per sample.  That means that each sample you write will contain two bytes.  An Int16 or “short” value type is just this size – so you can actually use an Int16 as the basis for audio calculations.  Once you calculate an Int16 value, you can write it’s two bytes of data out to the stream.  However, you’ll need to reverse the byte order of the Int16 value to make sure Silverlight consumes the stream happily.  The code below shows you how:

short myValue = 12345;
memoryStream.WriteByte(
	(byte)(myValue & 0xFF)); //2nd byte
memoryStream.WriteByte(
	(byte)(myValue >> 8)); //1st byte

Now let’s change this up and add a 2nd channel to make it a stereo signal.  For a 2-channel stream, you write one 16-bit sample first for the left channel, then write another 16-bit sample for the right channel.  If you want the same sound to be played in both the left and right channels, you can just write the same data twice:

short myValue = 12345;
memoryStream.WriteByte(
	(byte)(myValue & 0xFF)); //2nd byte left
memoryStream.WriteByte(
	(byte)(myValue >> 8)); //1st byte left
memoryStream.WriteByte(
	(byte)(myValue & 0xFF)); //2nd byte right
memoryStream.WriteByte(
	(byte)(myValue >> 8)); //1st byte right

Referring back to the GetSampleAsync() method, you’ll wrap the code above in your loop that executes for the number of samples you choose:

int numSamples = 128;
for (int i = 0; i < numSamples; i++)
{
	short sample = this.GetNextSampleFromSomewhere();

	stream.WriteByte(
		(byte)(sample & 0xFF));
	stream.WriteByte(
		(byte)(sample >> 8));
}

Now, the next big question: how do you calculate the sample for a sine wave?

I fear I might be going into a little too much detail here, but in order to answer that question I want to cover some basic trigonometry.  You’re still friends with π, right?

A sine wave is a function of a radian value.  If you look at a Unit Circle, it is made up of 2 x pi radians (360 degrees = 2 x pi, 180 degrees = pi).  The unit circle below shows some example points in degrees and the corresponding radian and sin values:

unitcircle_examples

Plotting the Sin values at increments of .25 radians for two passes around the unit circle produces this:

singraph

Now, given how a Sin function works, and given what you now know about producing Int16 value samples in the audio stream, producing sine wave samples is as simple as computing the next angle (a.k.a. “phase”) at which to calculate a Sin value. 

Next you need to understand what a Frequency (in Hertz, or Hz) is.  A musical tone plays at a certain Frequency.  For example, a string orchestra tunes to a Frequency of 440 Hz, or an “A”.  The Frequency is the number of times per second that a full unit circle rotation is made.  In other words, when an oboe plays an “A” for an orchestra to tune to, 440 full cycles of the unit circle are completed per second. 

In an audio stream we know the sample rate (e.g. 44100 samples/second).  Therefore, based on the frequency, we can calculate how many radians we should increment by each time we send out a sample.  In other words, each sample we send out will be a calculation based on the unit circle, and we’ll be moving along the unit circle a little bit each for each sample.

The formula for determining the phase angle increment per sample is this (taken from Charles Petzold's example):

Increments per Sample =
Frequency (cycles/second) *
Increments per Cycle /
Sample Rate

If you write out the calculation on paper and do the cancellations by hand, you’ll see that you will end up with Increments per Sample. 

Increments per Cycle can really be anything you want – but a good choice would be some nice round binary number, like an unsigned Int16.MaxValue.  Since we’re dealing with two-byte Int16 values already, UInt16.MaxValue (65,534) will work perfectly.  That means we’ll have 65,534 points on the unit circle to increment by. 

Example, with 440 Hz Frequency:

Increment Per Sample =
440 * 65,534 / 44100 = 653.85

Thus, for a 400 Hz frequency, we’ll need to increment our phase angle, based on a UInt16 maximum value, by about 654 units.

So how does this work in terms of the Sin calculation?  Each increment of the unit circle is a ratio of the total cycle.  So each 654 increment represents 654/65,534 or about 0.99% around the circle.  If we multiply two-pi by that ratio, that will give us the number of radians at our sample point.  Then we just take the Sin of those radians, then finally multiply the Sin result by Int16.MaxValue (32,767) to get the final result.  We multiply by 32,767 because the Sin result is just a fraction of where we’re at in the wave cycle – with the full possible value being equal to 1.00.  32,767 puts it in terms of our largest possible Int16 value – which is our sample size.

Here is an example C# class that does all of this:

public class SineWave{
	const int SampleRate = 44100;
	ushort phaseAngle;
	double frequency;
	ushort phaseAngleIncrement;

	public SineWave(){
		this.frequency = 440d;
		phaseAngleIncrement = (ushort)(frequency 
			* ushort.MaxValue / SampleRate);
	}

	short GetNextSample(){
		short sample = (short)(short.MaxValue * 
			Math.Sin(
				2 * Math.PI / ushort.MaxValue 
				* this.phaseAngle));
		this.phaseAngle += this.phaseAngleIncrement;
		return sample;
	}

	protected override void WriteSamplesToStream(
		MemoryStream stream, int numSamples){
		for (int i = 0; i < numSamples; i++)
		{
			short sample = this.GetNextSample();

			//single, mono channel
			stream.WriteByte(
				(byte)(sample & 0xFF));
			stream.WriteByte(
				(byte)(sample >> 8));
		}
	}
}

A final implementation of a custom MediaStreamSource class's GetSampleAsync() method may look something like this:

protected override void GetSampleAsync(
	MediaStreamType mediaStreamType)
{
	int numSamples = 128;
	int bufferByteCount = this.channelCount * 
		BaseMediaStreamSource.BitsPerSample / 
		8 * numSamples;

	mySineWave.WriteSamplesToStream(this.memoryStream, numSamples);

	MediaStreamSample mediaStreamSample =
		new MediaStreamSample(mediaStreamDescription, 
			memoryStream, currentPosition,
			bufferByteCount, currentTimeStamp, 
			emptySampleDict);

	currentTimeStamp += bufferByteCount * 
		10000000L / byteRate;
	currentPosition += bufferByteCount;

	ReportGetSampleCompleted(mediaStreamSample);
}

Once you implement a custom MediaStreamSource and some sine wave generating code, you can create an instance of the custom MediaStreamSource in Silverlight and call the SetSource() method of a MediaElement to start playing your sine wave.

Happy noise-making!

Technorati Tags: ,,,

For the past 1-2 weeks I’ve been working on an audio synthesizer in Silverlight 3.  The app is in enough of a stable state that I decided to throw it out into the world.

Try it out:  http://kindohm.com/ksynth

Source code: http://code.google.com/p/kindohm-ksynth/

ksynth

kSynth is a synthesizer. I’ve been caught off guard when people ask me where I got the audio for the app.  I have to explain that the app is generating the audio from scratch (thus, a synthesizer). The app generates raw sound waves from sine, saw, square, and triangle wave calculations.  It then mixes manipulates the generated waves through other controls.

The other half of kSynth is a sequencer.  Using the note grid you can arrange up to eight pitches over a maximum of 32 timed steps.  Each placed note will play a sound from the synthesizer.

The synthesizer itself is made up of three voices.  Each voice has five parameters that you can manipulate:

voice

  • Wave form (sine, saw, square, triangle, or noise)
  • Level – a.k.a. volume
  • Pan – how far to the right or left each voice is played
  • Phase - an offset of how much the wave calculation is delayed
  • Detune - a slight variation of the voice’s pitch

These parameters are fairly common parameters used with most modern synthesizer hardware or software. 

The synthesizer also has a dynamic envelope that is used to control the shape of each note played from the synthesizer.  The dynamic envelope has four parameters:

Envelope

  • Attack – how abruptly or smoothly the note “fades in”
  • Sustain – the length of the note
  • Decay – how abruptly or smoothly the note “fades out”
  • Pitch – bends the pitch of each note up or down

The sequencer also has some additional controls:

SequencerControls

delay 

  • Level – the master volume
  • Tempo – speeds or slows up the playback of notes
  • Steps – the number of slots available in the sequencer grid
  • Delay – adds a delay or “echo” effect to the audio

You can save the voice, envelope, and delay settings by using 16 preset banks:

presets

Click and hold down a preset bank to save the current state of the voices, envelope, and delay to that bank.  Recall a preset by clicking once on a bank.

What is the purpose of this application?  Easy: to have fun.  It’s not a musical plugin that will work with any other audio software.  I suppose you could use it as an instrument – although it doesn’t have a keyboard.  It’s more of a sequencer than a true instrument. 

For me, I had a huge interest in developing an application like this because 1) I love working with synthesizers in electronic music and 2) I knew Silverlight 3 would have audio synthesis capabilities.  From the day Silverlight 3 was released (7/10/2009) I started trying to learn how to build this app. 

I can’t finish writing this without referencing Charles Petzold’s article on building a simple sequencer in Silverlight 3.  His example helped with the sequencer logic along with some of the base calculations used in the fundamental wave forms and attenuation (amplitude).  I also took some idea’s from Pete Brown’s Silverlight synthesizer.

What doesn’t the app have?  I really wanted to add a custom wave form editor where you could draw your own wave form and use it in the app.  However, this feature got scrapped after successfully proving it out.  It was just much more work from a UI perspective than I wanted to invest.

In addition to custom wave forms, there are a few other possible enhancements to this app that I may implement some day:

  • Save audio output to wave file
  • Save sequencer state
  • Upload custom wave files to sequence with
  • Support for unlimited number of voices
  • EQ and high/low pass filters

I hope to write a few blog posts soon detailing some of the technical aspects of this application.

Enjoy, and happy noisemaking!

Sample audio: ksynth.demo.mp3 [8 MB – 5:27]

These days I’ve been more about producing and futzing with fun stuff than blogging and explaining.  Well, maybe you can see (or hear) what I’ve been up to now…

I’ve been chipping away at a multi-voice audio synthesizer in Silverlight 3 and C# for the past few days.  I’m at a point where I just couldn’t wait to share what the heck this thing sounds like any more.  Here are a few points of interest:

  • There are three separate wave oscillators generating the sound
  • Each oscillator can generate sine, saw, square, triangle, or a “custom” wave form
  • Each oscillator can be panned right or left, and can be phase-shifted from 0 to 360 degrees
  • A simple sequencer is used to control 16 timed steps. Each step has 8 possible notes. 
  • Each possible note can be set up to play any of the 12 half musical steps, with a 6-octave range
  • There is a dynamic envelope that controls each note’s attack, sustain, and decay
  • Tempo can be altered.
  • Added a pitch bender to boot
  • Much help received from the example work of Charles Petzold’s Simple Silverlight 3 Sequencer and Pete Brown’s Silverlight Synthesizer.

The sample mp3 demonstrates most of the synth’s capabilities – starting from a simple single sine voice to full-blown madness with everything turned to 11.  Around 2:35 or so, I add in a custom wave form that I authored using a wave form editing tool I’m working on. 

Here is a screen shot:

ksynth.beta

And here is a glimpse of the waveform editor:

editor

I just saw that Charles Petzold blogged about a multi-voice synthesizer approach.  I’m not showing source code here, but I’m glad to see that he and I agree on a similar approach of using a generic C# List of child sample providers summed up (and divided) by a parent provider. 

Next I’ll be working on voice de-tuning and a delay effect.  I also need to polish up the custom wave editor feature and add the ability to save synth settings for future recall.

Until next time…  keep those earplugs in.

I submitted my topic yesterday for  the next Twin Cities Code Camp, which will be held on 10/24/2009 at the University of Minnesota.  Details and registration info are at www.twincitiescodecamp.com

My topic is Audio Synthesis with Silverlight 3.  Here’s the session description:

Silverlight 3 offers new power to developers in being able to work with raw WAV audio. In this code-heavy session you will see how to produce raw sound wave forms, work with the sound buffer, and send audio to Silverlight 3. We'll cover topics such as sequencing, blending, peaks, panning, and how to generate a considerable amount of annoying noise through your web browser to irritate co-workers.

Final selections for sessions has yet to take place.  I can’t guarantee my topic will be selected.

I have a thing or two to learn about this topic before I present it.  I have a few cool app ideas that would be fun to present at the session, if I can get them ready in time.