Saturday, March 07, 2009

NAudio Tutorial 3 - Sample Properties

The 3rd installment in this NAudio tutorial series will be focusing on controlling sample properties.

As this 3rd Instalment in the NAudio Tutorial's, is quite large and there is a fair bit of code to review, I have packaged up the entire tutorial, including an RTF and AbiWord version of this tutorial in a convenient zip for you. Available here.

Additionally we will also look at building the starting blocks of an NAudioInterface class to provide us with a generic level of abstraction that can be called through other applications. This component is specifically to start addressing my own personal needs for the use of the NAudio API, as a replacement to the existing audio API's OpenSebJ is currently using - SDL.Net,  OpenAL.Net and previously Direct Sound through Microsoft's Managed DirectX.

The goals for the NAudioInterface we will be building in this tutorial will be to:

  • Provide a single method for all a default configuration and setup of audio output; working on the basis that all audio samples are designated for mixing together

  • Abstract all of the wave file and stream loading operations, such that only a file name needs to be provided

  • Setup a sample concept, such that all audio sample operations can occur through this instance. Such as:

    • Play

    • Pause

    • Pan

    • Volume

    • Looping

    • Sample Playback Position





We will be building upon the first two Tutorials, so if you haven't had a chance to review what's going on, they would be a good place to start.

NAudio & the relationship to NAudioInterface

NAudio does a great job of abstracting the underlying actions required to load audio files and stream them to various output devices and it does present an admirably well thought out mix of complexity vs. the amount of functionality. So why bother abstracting this any further?

Well in the specific scenario that I'll be presenting here, we will want to reduce some of this complexity and tie the common options and functions together to make interfacing and interacting with the samples more to do with the functions that are being performed and requested and less about the implementation of those functions. So think about the NAudioInterface as the level of abstraction removing us from how its done to just getting it done.

Architecture

I'll insert an obligatory diagram, describing the interaction that is going between the various classes and how we are logically grouping these interactions together.



Note: This diagram is not trying to portray an accurate physical implementation of the classes and the internal workings of NAudio; rather it is designed to depict and explain the logical relationships and their basic level of interaction.

Starting at the top, we have

  • Audio Application; This is our Tutorial Application however could be any application which would benefit from utilizing an Interface Structure such as this.

  • NAudioInterface; Groups all of the Audio functionality together, presenting a single unified location for all interaction with the Samples by the Audio Application.

  • WaveMixerStream32; is setup by the NAudioInterface in a Setup method, at this time the WaveMixerStream32 instance is linked to the IWavePlayer. No Changes to this class are made by this Tutorial.

  • IWavePlayer; is our waveOutDevice. In this tutorial we will just initialize an ASIO stream straight to the device. No changes to this class have been made by this Tutorial.

  • AudioSample; Is inherited from the WaveStream class and is the place where the non core NAudio sample related functionality is stored and used to manipulate the data that is read in to the byte array passed through to the WaveMixerStream32. I.e. Setting up a sample to loop.

  • WaveStream; Used in an overrider relationship from the AudioSample instance, to send the byte arrays which are to be mixed by the WaveMixerStream32 and subsequently played y the sound card. No changes to this class have been made in this Tutorial.



As we constrain all of the related NAudio modifications to the AudioSample class, which is inherited from the WaveStream Class and create the new NAudioInterface class, we can isolate any modifications we need to make in relation to the Audio that is playing in specific locations that mirror the functionality they need to invoke with out directly updating the NAudio libraries. So no branch of the underlying NAudio API required.


NAudioInterface

As we will want to access these functions from multiple locations within our program and for a scenario where there are many windows, from many different classes. We also need to keep consistency from what we are requesting. As such the first step is to declare this as a static class, such that it can be accessed without being instantiated, any where within our program.

using NAudio.Wave;

namespace AudioInterface
{
   public static class NAudioInterface
   {

& then declare all of the items we will need to keep state information about globally. I.e. we will only need one waveOutDevice and one mixer, which all audio steams will be mixed to.

       //Declarations required for audio out and mixing
       private static IWavePlayer waveOutDevice;
       private static WaveMixerStream32 mixer;
       
       // The Sample array we will load our Audio Samples in to
       private static AudioSample[] Sample;

The new introduction here, from the previous tutorials, is the AudioSample class. I previously mentioned in Tutorial 2, how there was a really good implementation in one of the NAudio Demo Applications called, MixDiff - specifically in MixDiffStream.cs which provides a great level of abstraction from the heavy lifting and that class has been reused in this tutorial and expanded to add the additional functionality mentioned in the goals.

Conceptually this has been grouped together in to the AudioSample class, such that any interaction with a sample from the calling program should be able to access any sample related functionality through the Sample instance we have created.

Now we have an array of AudioSamples, we need to initiate our waveOutDevice. This is handled in a single method called SetupAudio in the NAudioInterface class and is designed to just be called once when the calling program is setting up all of it's requirements.

       /// <summary>
       /// Setup Audio via NAudio.
       /// Defaults to using Asio for Audio Output.
       /// </summary>
       public static void SetupAudio(int Samples)
       {
           //Setup the Mixer
           mixer = new WaveMixerStream32();
           mixer.AutoStop = false;
           
           if (waveOutDevice == null)
           {
               waveOutDevice = new AsioOut();
               waveOutDevice.Init(mixer);
               waveOutDevice.Play();
           }

          Sample = new AudioSample[Samples];
       }

After the waveOutDevice has been setup and the mixer associated, we will now start loading waves in to the AudioSample instance. As such we have a simplified method to handle loading of the samples, funnily enough named LoadSample.

      public static void LoadSample(string fileName, int sampleNumber)
       {
           Sample[sampleNumber] = new AudioSample(fileName);
           mixer.AddInputStream(Sample[sampleNumber]);
           
           // The stop is required because when an InputStream
           // is added, if it is too long it will start
           // playing because we do not turn off the mixer.
           // This is effectively just a work around by making
           // sure that we move the playback position
           // to the end of the stream to aviod this issue.
           Stop(sampleNumber);
       }

Fairly straight forward but two things to note. First, we are adding the sample which has been loaded, to the mixer input stream. Now it's important to remember that we have already associated the mixer input stream with the waveOutDevice and set the device to be in a status of playing. This can result in the wave file being played mid way through when enough data has been loaded - you wouldn't notice it for a small wave sample being loaded but certainly noticeable in a longer length sample. As such we will call the method Stop, lets have a look at this now.

      public static void Stop(int sampleNumber)
       {
           // Set the position at the end of the sample length
           Sample[sampleNumber].Position = Sample[sampleNumber].Length;
       }

OK, so why have we set the playing position of the Sample to the length of the sample to stop playing the sample? It comes back to our structure of the mixer and the waveOutDevice being a constant state of Play. Therfore we need to make sure that we stop streaming audio from the Sample to the mixer but rather than unloading the sample, moving the current position to the end of the sample ensures that there is no more data is being loaded in to the mixer stream for play back. Similarly or perhaps conversely we have the Play method.

      public static void Play(int sampleNumber)
       {
           Sample[sampleNumber].Position = 0;
       }

It becomes just a simple matter of setting the current position for the sample back to the beginning. So now that we have established that there is a fairly easy way to stop the Sample playback we therefore have everything required to construct a Pause() and a Resume() method.

      public static void Pause(int sampleNumber)
       {
           Sample[sampleNumber].Pause();
       }

       public static void Resume(int sampleNumber)
       {
           Sample[sampleNumber].Resume();
       }

I would agree that these two methods don't have a lot of detail - so let's have a sneak peak in to the AudioSample class to see whats going on:

namespace AudioInterface
{
   public class AudioSample : WaveStream
   {
       // General Sample Settings (Info)
       string _fileName = "";
       bool _loop;
       long _pausePosition = -1;
       bool _pauseLoop;
               
       // Sample WaveStream Settings
       WaveOffsetStream offsetStream;
       WaveChannel32 channelSteam;
       bool muted;
       float volume;

<Snip>

      public void Pause()
       {
           // Store the current stream settings
           _pausePosition = Position;
           _pauseLoop = _loop;

           // Ensure the sample is temporairly
           // not looped and set the position to the
           // end of the stream
           _loop = false;
           Position = Length;

           // Set the loop status back, so that any
           // further modifications of the loop
           // status are observed
           _loop = _pauseLoop;
       }

       public void Resume()
       {
           // Ensure that the sample had actuall been
           // paused and that we are not just jumping
           // to a random position
           if (_pausePosition >= 0)
           {
               // Set the position of the stream
               // back to where it was paused
               Position = _pausePosition;

               // Set the pause position to negative
               // so that we know the sample is not currently paused
               _pausePosition = -1;
           }
       }

If you have been keeping up with me so far, I'll assume this doesn't need more explanation than whats already included in the comments. But for clarity or obsessive compulsive explanations I'll just confirm that we effectively stop the sample the same way we did previously on the Pause and keep a reference to what the position was before stopping. Then on the calling of the resume method we just let it go and start streaming again from the previous position. There are a few other functions from NAudioInterface we will introduce before moving in to the detail of the implementation of these functions in the AudioSample class. These are rather self explanatory and as they are in the NAudioInterface class they contain very little implementation details for whats actually going on under the hood.

The looping will, loop the sample. When it reaches the end of playback, as determined by there being no more audio to add in form the end of the stream, the reading is looped back to the beginning and the process starts over again.

      public static void Loop(int sampleNumber, bool Loop)
       {
           Sample[sampleNumber].SetLoop(Loop);
       }

Set Pan - Left to Right. We are using a float value that goes from -1.0 to 0 being the center and 1.0 being the complete other end of the speaker pan.

       public static void SetPan(int sampleNumber, float pan)
       {
           Sample[sampleNumber].SetPan(pan);
       }

We set the volume here, again using a float. The spectrum goes from 0 to 1.

       public static void SetVolume(int sampleNumber, float volume)
       {
           Sample[sampleNumber].Volume = volume;

       }

Setting the position of the sample playback is certainly a concept previously introduced.

       internal static void SetPostion(int sampleNumber, long position)
       {
           Sample[sampleNumber].Position = position;
       }

& Finally wrapping it all together is the get length method for the audio. Now for clarity this is returning in the same format as the SetPosition uses, which is based on the Length of the Source Stream /  the number of Bytes Per Sample. i.e. the number of individual samples.

       internal static long GetLength(int sampleNumber)
       {
           return Sample[sampleNumber].Length;
       }

Well that wraps up what the NAudioInterface looks like. Time to get on to the AudioSample class and all it's related methods. Harrar.


AudioSample

As mentioned earlier, this class is inherited from the WaveStream class, where most of the underlying functionality will still exist and in this scenario very little has been overridden in the AudioSample class.

Although we have already had a sneak peak at how this class is setup it's worth reviewing the key elements here again:

using NAudio.Wave;

namespace AudioInterface
{
   public class AudioSample : WaveStream
   {
       // General Sample Settings (Info)
       string _fileName = "";
       bool _loop;
       long _pausePosition = -1;
       bool _pauseLoop;
               
       // Sample WaveStream Settings
       WaveOffsetStream offsetStream;
       WaveChannel32 channelSteam;
       bool muted;
       float volume;

The AudioSample object now represents contains all the information that relates to the individual samples, which helps us keep a structured NAudioInterface class, not needing to be aware of the individual sample properties unless it is specifically interested. So internally we store whether or not the sample is set up to loop, the filename of the original sample, if the sample is paused & what the pause position was, what the volume for the sample is etc.

In our constructor for the AudioSample class, we only need take in the filename initially and we continue to use our other defaults for the sample until told otherwise.

       public AudioSample(string fileName)
       {
           _fileName = fileName;
           WaveFileReader reader = new WaveFileReader(fileName);
           offsetStream = new WaveOffsetStream(reader);
           channelSteam = new WaveChannel32(offsetStream);
           muted = false;
           volume = 1.0f;
       }

Key to the constructor is setting up the channelStream, based off the WaveFileReader. The Offset stream is used if we want to change the offset of a sample, based on where it starts etc. I haven't looked in to this much yet but thought it may be useful in the future so I've left it in for the time being. Performance overhead for this? I have no idea.

Rather then pull out every override that is practically the same I'll focus on those that are at least non-obvious or have a slightly less straight forward explanation; if you have managed to read this far in to the tutorial let me know - it would nice to know that some one had bothered reading this chapter from the novel that is my Audio Programming Experience.

Actually doing this only leaves me with one method that is worth talking about, which hasn't already been covered, which is the overridden method Read.

Read is the central arterial vein of the WaveStream in NAudio and as such is an interesting and fun place to override and make constructive changes. In this example we have used the override method to introduce looping capability for samples; the code for this looping was ripped from one of the other NAudio Demo's.

      public override int Read(byte[] buffer, int offset, int count)
       {
           // Check if the stream has been set to loop
           if (_loop)
           {
               // Looping code taken from NAudio Demo
               int read = 0;
               while (read < count)
               {
                   int required = count - read;
                   int readThisTime = channelSteam.Read(buffer, offset + read, required);
                   if (readThisTime < required)
                   {
                       channelSteam.Position = 0;
                   }

                   if (channelSteam.Position >= channelSteam.Length)
                   {
                       channelSteam.Position = 0;
                   }
                   read += readThisTime;
               }
               return read;
           }
           else
           {
               // Normal read code, sample has not been set to loop
               return channelSteam.Read(buffer, offset, count);
           }
       }

What is happening here is that the Read method knows how much data is required to be read into the stream, for sending to the Mixer. As such we use this length to read in all the available data to the end of the stream. Once we reach the end of the stream, identified by readThisTime being less than the required data for the read (The WaveChannel32 instance, channelStream, returns an integer on the read event representing the number of bytes actually read) - I'm not sure in this instance, when we have the number of bytes read less than what was expected to be read actually sounds like. i.e. is there a momentary gap of audio but from my testing so far I personally have not been able to distinguish any auditable issues from this.

int readThisTime = channelSteam.Read(buffer, offset + read, required);

Or the other check being that the current Position of the Channel Stream being greater than the complete length of the channel stream, we move back to the first position in the channel stream and start reading the additional data from the beginning of the stream.

if (channelSteam.Position >= channelSteam.Length)

Audio Application

Time to put a bow on all of this and call it done. I've put together to forms that are going to be used to show of the magic that is NAudio Tutorial 3. The first form is a launcher, that can be used to load different wave samples and allows for the same wave sample to be loaded to multiple positions simultaneously for multi-but-same sample fun.





Brutally boring and beautifully powerful. Check one or a number of boxes and then click Open and find a wave sample to load. Once the sample is loaded a number of windows (based on how many positions you have checked) pop up.



If you have ever seen OpenSebJ and think this window looks strikingly similar to the properties window thats in OpenSebJ - you wouldn't be wrong. For comparison:

 

I've only pasted this here so that you can see how this fits in to the big picture of a Real Time mixing Open Source audio composition tool - OpenSebJ; that and because if I can't bang the drum about OpenSebJ here, then where can I?

Sample Properties

So back on track the sample properties window will allow us to control all of the functionality we have bubbled to the surface through our NAudioInterface and as you can see we have a button, switch or slider for all of them. So lets have a look at whats required to do all this:

using AudioInterface;

namespace NAudioTutorial3
{
   public partial class SampleProperties : Form
   {
       int samplePosition = -1;

       public SampleProperties(int SamplePosition)
       {
           samplePosition = SamplePosition;
           InitializeComponent();
       }

We keep a local variable of the SamplePosition populated so that we know what sample this UI relates to when sending instructions the NAudioInterface.

       private void SampleLauncher_Load(object sender, EventArgs e)
       {
           trkPosition.Maximum = (int)NAudioInterface.GetLength(samplePosition);
       }

On load, we setup the track bar to have a maximum length the same as the sample, so that when we scroll the slider we move the current play position of the sample - think moving a needle along a record but electronically. Also in terms of this analogy of a scratch it is more like if you actually picked up the needle and then moved it to the exact spot and let it play, or when you drag it back it would be like picking it up multiple times putting it back down and letting it play for a moment and then doing the same. Like a leapfrog effect backwards. I will be looking in to how to reverse a sample at a latter point in time to effectively simulate record scratching.

       private void cmbPlay_Click(object sender, EventArgs e)
       {
           NAudioInterface.Play(samplePosition);
       }

       private void chkLoop_CheckedChanged(object sender, EventArgs e)
       {
           NAudioInterface.Loop(samplePosition, chkLoop.Checked);
       }

       private void cmbPause_Click(object sender, EventArgs e)
       {
           if (cmbPause.Text == "Pause")
           {
               NAudioInterface.Pause(samplePosition);
               cmbPause.Text = "Resume";
           }
           else
           {
               NAudioInterface.Resume(samplePosition);
               cmbPause.Text = "Pause";
           }
           
       }

One thing to note with the Pan and Volume change is that the actual class uses a float value of 0 to 1 for the volume and -1 to 1 for the pan. As such I have setup the track values to 0 to 1000 and -1000 to 1000 such that the track bars feel smooth when moving them and thus providing a greater level of precision the the actual adjustments that are occurring.

       private void trkPan_ValueChanged(object sender, EventArgs e)
       {
           NAudioInterface.SetPan(samplePosition, (float)trkPan.Value / (float)1000);
       }

       private void trkVolume_ValueChanged(object sender, EventArgs e)
       {
           NAudioInterface.SetVolume(samplePosition, (float)trkVolume.Value / (float)1000);
       }

Changes the current playing position of the sample in real time. Lots of fun can be had with this - even if I do say so myself.

       private void trkPosition_MouseMove(object sender, MouseEventArgs e)
       {
           if (e.Button == MouseButtons.Left)
           {
               NAudioInterface.SetPostion(samplePosition, (long)trkPosition.Value);
           }
       }

   }
}


Sample Launcher

Most of the code here relates to the UI and ensuring we only load a single sample once but I'll throw it in for completeness. Refer to in-line comments for details.


using AudioInterface;

namespace NAudioTutorial3
{
   public partial class NAudioTutorial3 : Form
   {
       // sampleLoaded used to make sure we don't try and load in to the
       // same sample position twice
       bool[] sampleLoaded = new bool[256];
       
       // Our Sample Properties window.
       SampleProperties[] sampleWindow = new SampleProperties[256];


       public NAudioTutorial3()
       {
           InitializeComponent();
       }

       private void NAudioTutorial3_Load(object sender, EventArgs e)
       {
           // Setup the NAudioInterface first and only required once
           // for the runtime.
           NAudioInterface.SetupAudio(256);
           
           // Set all data to inital values.
           for (int i = 0; i < 256; i++)
           {
               sampleLoaded[i] = false;
               chkList.Items.Add(i.ToString(), false);
           }
       }

       private void cmbOpen_Click(object sender, EventArgs e)
       {
           // prompt for file load
           OpenFileDialog openFileDialog = new OpenFileDialog();
           openFileDialog.Filter = "WAV Files (*.wav)|*.wav";
           if (openFileDialog.ShowDialog() == DialogResult.OK)
           {
               // Want to iterate through every possiable sample
               for (int i = 0; i < 256; i++)
               {
                   // Check if it has previously
                   // been ticked and not previously loaded
                   if (chkList.GetItemChecked(i) == true && sampleLoaded[i] == false)
                   {
                       // One call to the NAudioInterface to
                       // load the sample
                       NAudioInterface.LoadSample(openFileDialog.FileName, i);
                       
                       // Setup the sample window and show it
                       sampleWindow[i] = new SampleProperties(i);
                       sampleWindow[i].Show();

                       // Make sure we don't load the
                       // sample position again
                       chkList.SetItemChecked(i, false);
                       chkList.Items[i] = "Loaded";
                       sampleLoaded[i] = true;
                   }
                   else
                   {
                       // Untick any item which was ticked
                       // to be loaded but has
                       // already been previously loaded
                       chkList.SetItemChecked(i, false);
                   }

               }//end for
           }//end if
           
       }


   }
}

Thats it!

Conclusion

In this tutorial we have run through how to setup an Static Class that will act as a broker for all of the related NAudio functionality, such that the code required in forms and extended classes is minimalist in nature.

Reviewing the tutorial against our original goals we have:

Simple Audio Setup
Provide a single method for all a default configuration and setup of audio output; working on the basis that all audio samples are designated for mixing together

We demonstrated this through a single call:  

AudioInterface.SetupAudio(256);


Simple File Loading

A
bstract all of the wave file and stream loading operations, such that only a file name needs to be provided

Demonstrated through only one method call required to load a sample from our Audio Application:  

NAudioInterface.LoadSample(openFileDialog.FileName, i);

Sample Concept

Setup a sample concept, such that all audio sample operations can occur through this instance. Such as:

  •  Play

  • Pause

  • Pan

  • Volume

  • Looping

  • Sample Playback Position


 
I wont cut and paste the Sample class but you can check the AudioSample class if you don't believe me.

So we have met all our goals and have a Sample Properties interface I can almost drop in as-is to OpenSebJ, brilliant.


Next Time 

There are a few options for the following tutorials and I would be interested in hearing peoples thoughts on what you would like to see covered. Some of the items I am personally interested in are:

  •  How we can introduce a reverse option to a stream, so that we can start supporting a real-time scratch interface.

  • Adding Audio Effects to a Stream

  • Transposing the frequency of the stream being played back

  • Setting up stream to disk recording and (hopefully) getting it to work while we are still using ASIO (or in my case ASIO4ALL)



Final Thoughts

This Tutorial ended up being much longer than I expected and became a way for me to impart my experience with Audio Applications in C#, with generalized concepts and nice framework that I can use in OpenSebJ.

However through this process I have also re-factored the tutorial a few times to make it more universally applicable. Moving the sample properties to a separate class for instance, so that it can be instantiated and used to control many samples and not just one - demonstrating the mixing capability and the number of simultaneous streams that can be handled.

There is still more room for improvement on this,  perhaps some of these actions should be made simpler in the NAudioInterface and re-factored in to the AudioSample class but we can do that latter.


Reminder: You can download entire tutorial, including an RTF and AbiWord version of this tutorial, with all of the associated code, in a convenient zip here.

Saturday, February 28, 2009

NAudio, Tutorial 2, Mixing multiple wave files together in real time.

Introduction:

Assuming you have read my last post about NAudio, if you haven't there is still time - here; we will move on to mixing multiple audio files and outputting to a single audio device, which would be your sound card. For the purposes of simplicity this tutorial is going to only focus on 4 samples, although you could extend this to as many as you like following this structure.

Also please note that this Tutorial is not focusing on the most optimal way to implement the complete set of functions for a wave stream in terms of mixing and as such no additional inheritance or polymorphism has been used - the focus of this tutorial is to demonstrate the capabilities present within the NAudio library and what can be done when using these interfaces directly with out additional abstraction. I say this now because I have actually hacked to bits, what looks like a great level of abstraction that Mark has written in his demo application MixDiff - so after you have finished reading this check out the class MixDiffStream.cs for a really useful set of functions grouping many of these control arrays in to a single object.

Moving on with the Tutorial; I've created a form with 4 buttons on it. We will load a sample against each of these buttons and then use the buttons to trigger the sample for playback, giving us a form to mix the audio samples together in real time - the start of any great Synthesizer or Beat Box. For those of you not using your imagination today we have:


The Code - Declarations:

I'll assume you have read the first tutorial and already know how to add a reference to NAudio and that you remember you need to setup a using statement:

using NAudio.Wave;

We will define the key elements of the API we will be using for this project, within the class. IWavePlayer which you may recall from last time is our waveOutDevice = Sound Card and an instance of WaveMixerStream32, which will handle all of the mixing of the actual wave streams. Like last time we will be using an ASIO device for our waveOutDevice - so make sure you have the ASIO4ALL drivers installed if you don't have a sound card that already supports ASIO (Like me):

namespace NAudioMixTest

{

public partial class frmMixTest : Form

{

//Declarations required for mixing

private IWavePlayer waveOutDevice;

private WaveMixerStream32 mixer;


To reduce explanation latter, I've defined a simple array which will store the file names of the samples we are loading. This will also be used when checking if we have already loaded a sample for that position, by virtue of the absence of a filename.


// File names for the loaded samples

private string[] sampleLoaded = new string[4];


These following settings are the ones which have been included in the abstraction layer I mentioned earlier but for the purpose of demonstration have been striped out for individual specification here. The WaveFileReader is used to load the wave file, resulting in us having access to a stream of data for the wave file.


// Reader instance for the wave file

WaveFileReader[] reader = new WaveFileReader[4];


We don't directly make any modifications to the WaveOffsetStream in this example but it is required to be passed to the WaveChannel32 instance when being setup. The WaveChannel32 instance is then used to control the properties of the stream we are interested in - it's position.


// Other properties of the stream

WaveOffsetStream[] offsetStream = new WaveOffsetStream[4];
WaveChannel32[] channelSteam = new WaveChannel32[4];


The Code - Setup:


Now that we have finished with the formality of the definitions we need, we can start initializing the components. First is to define the mixer and set that it will not AutoStop playback - this allows us to assume that the status of the Mixer is always "Playing" - otherwise it would automatically stop when all inputs have been read, which would be an issue if we want some silence between beats - think boom . tis . boom . tis etc.


The waveOutDevice is then declared on the Form load and set to the status of Play, which means we can start sending audio to our sound card. Again ASIO defaults are being used here, so if it errors for you on this step - get ASIO4ALL.


public frmMixTest()

{

InitializeComponent();


//Setup the Mixer

mixer = new WaveMixerStream32();

mixer.AutoStop = false;

}


private void Form1_Load(object sender, EventArgs e)
{

for (int i = 0; i < 4; i++)

{
sampleLoaded[i] = "";

}

if (waveOutDevice == null)

{

waveOutDevice = new AsioOut();

waveOutDevice.Init(mixer);

waveOutDevice.Play();

}

}


The Code - Load a Sample:


We are almost there, believe it or not. I'll cover the loading of a sample now and then show how it all wraps together, skipping over the boring file loader dialog bit. Skip..


private void loadSample(int position)
{

// prompt for file load

OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "WAV Files (*.wav)|*.wav";

if (openFileDialog.ShowDialog() == DialogResult.OK)

{


Now we pass the FileName from the openFileDialog to the WaveFileReader instance. Which as far as we are concerned has given us a stream for the audio file to be read in to.

reader[position] = new WaveFileReader(openFileDialog.FileName);



Humble reader, I'll be honest with you here, I don't know this inside and out so there is an option to use an WaveOffsetStream but I have also found that it doesn't do anything we need for this actual demo. So if you want to use it you set it up like so and then pass it to WaveChannel32:

offsetStream[position] = new WaveOffsetStream(reader[position]);
channelSteam[position] = new WaveChannel32(offsetStream[position]);

Or to be really simple replace those two lines with this one, which just passes the WaveFileReader stream directly to the WaveChannel32 instance, which lets us control the stream as required for this example.


channelSteam[position] = new WaveChannel32(reader[position]);

After the stream has been defined we need to let the mixer know it exists by using AddInputStream and passing in the WaveChannel32 instance. This only needs to be done once and coincidently because we have set all the statuses to play, this audio file will automatically play when it is added. Then we clean up and store the FileName in the array so we know we have loaded a sample at this position.


// You only need to do this once per stream

mixer.AddInputStream(channelSteam[position]);

sampleLoaded[position] = openFileDialog.FileName;

}

}



The Code - Tying it all together:


You would have noticed that the code in the loadSample method is referring to "position" in the array, that is because we call this from each button click once we know we need to load a sample. I.e.


private void cmbSound1_Click(object sender, EventArgs e)

{

if (sampleLoaded[0] == "")

{

loadSample(0);

}

Else, we have loaded the sample and want to re-trigger the playback, so set the position of the sample to 0 - which is the beginning.


else

{

channelSteam[0].Position = 0;

}

}


& Then effectively the same code for the other 3 buttons, just with the positions set to the incremental position in the array.


Conclusion:


This 2nd Tutorial for NAudio has covered loading and mixing samples in real time in a relatively straight forward solution. This functionality almost represents the feature set of audio API functionality used by OpenSebJ currently. Loading Sample, Positioning Samples and Mixing Samples in real time. For next time we will look at covering the additional set of functionality currently supported by other Audio API's - such as setting the volume, pan and looping of the samples being mixed.



Until next time.




Friday, February 27, 2009

Hip Hop for the Intelectual's

Some call it Nerd Core as if it was some alternate form of hip hop but I still don't know how I feel about being called a Nerd, so let me just introduce it as intellectual Hip Hop - not raping about guns, bling and bitches - makes it a worth while audible experience.

Don't know what I'm talking about still? Check out Optimus Rhyme's new EP - Full for Free Download it's a gloriours selection of beats and rhyme.

Find out more about them here, @ home.

Sunday, February 15, 2009

Introduction to Using NAudio

About NAudio
NAudio, is an Open Source* audio mixing libary written in C#, for the windows platform. It supports PInvoke methods for WaveOut, ASIO, DirectX and some other functions only available on Vista (WASAPI - Windows Vista Core Audio). It doesn't currently include abstraction support for PortAudio, OpenAL or GStreamer but here's hoping for some cross platform compatibility in the future.

The SubVersion repository for NAudio is very clean and easy to distinguish what you may need. My development environment is Windows XP x64 so there was an additional step required for the 64 bit environment, which was to set the platform to build as x86 by default.

You can do this by:

Clicking Build > Configuration Manager and then change all of the projects platform to x86

With the platform set, everything was ready to go. I recompiled and had a working x86 version, that runs on the 64 bit OS.

Introduction: Playing a Sample
This post is going to focus on loading an audio sample, playing the file and being able to reset the position of playback for the sample.

Start a new project, copy the NAudio.dll file over and add it in as a reference. Then set-up the using statements:

using NAudio.Wave;
using NAudio.CoreAudioApi;

In the class, we need to define the items which are going to be visible to all of our methods. We have two declarations we are concerned with in this introduction:
public partial class NAudioTest : Form
{
IWavePlayer waveOutDevice;
WaveStream mainOutputStream;
string fileName = null;

waveOutDevice which is an instance of IWavePlayer is going to be used to define the interface to a device that we will be using to play our audio.

mainOutputStream is used to store the audio sample we load and provides a level of abstraction from the Stream so that we don't need to manually move the stream data around when we want to adjust properties such as position - which allows us to easily seek positions within the sample.

fileName will just store the file name of the wave.

With these class declarations in place, the next logical step for us is to setup the device which is going to be used to output the Audio. At an appropriate location (under a button click event etc.) we can now initialise the instance. In this tutorial we are going to use the default available ASIO interface. This may not be suitable for all sound cards, it doesn't work on mine by default - but thanks to ASIO4ALL it does. Grab a copy of it, it's free and works wonders providing a lowlatency interface for mixing audio on windows.

try
{
waveOutDevice = new AsioOut();
}
catch (Exception driverCreateException)
{
MessageBox.Show(String.Format("{0}", driverCreateException.Message));
return;
}
The default ASIO device has hopefully been created (if it didn't work for you have you downloaded ASIO4ALL yet?). Now we have an audio device declared but nothing too exciting to your sense have started to occur - for that we need to load an audio file and we need to find one first. For the purpose of this tutorial you can either assume a fixed location and assign it to the fileName variable or add something similar to the OpenFileDialog snipet below:
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Wave Files (*.wav)|*.wav|All Files (*.*)|*.*";
openFileDialog.FilterIndex = 1;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
fileName = openFileDialog.FileName;
}
I'll assume that needs no explanation of its own. This sample now needs to be loaded and stored in to a wave stream, which we have called mainOutputStream. We will use the CreateInputStream method from the NAudio Demo application to complete this task.
mainOutputStream = CreateInputStream(fileName);
Passing in the name of the file and having the input stream retuned. This doesn't require a lot of understanding if your just looking for the wave file to be loaded and the stream retuned, lets assume that's all you care about from this tutorial.
private WaveStream CreateInputStream(string fileName)
{
WaveChannel32 inputStream;
if (fileName.EndsWith(".wav"))
{
WaveStream readerStream = new WaveFileReader(fileName);
if (readerStream.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
{
readerStream = WaveFormatConversionStream.CreatePcmStream(readerStream);
readerStream = new BlockAlignReductionStream(readerStream);
}
if (readerStream.WaveFormat.BitsPerSample != 16)
{
var format = new WaveFormat(readerStream.WaveFormat.SampleRate,
16, readerStream.WaveFormat.Channels);
readerStream = new WaveFormatConversionStream(format, readerStream);
}
inputStream = new WaveChannel32(readerStream);
}
else
{
throw new InvalidOperationException("Unsupported extension");
}
return inputStream;
}
Now for assembly of all of the pieces. We have a waveOutDevice defined where we will be sending audio to and a wave file has been loaded in to our mainOutputStream. All that's left is to connect the two and hit play:

try
{
waveOutDevice.Init(mainOutputStream);
}
catch (Exception initException)
{
MessageBox.Show(String.Format("{0}", initException.Message), "Error Initializing Output");
return;
}
waveOutDevice.Play();
Assuming everything was setup right, you should now hear a wave file being played. If you have difficulties check ASIO4ALL is installed. And the finishing touch is to reset the wave file to play back from the beginning. Find a friendly on-click button event and add the following:

mainOutputStream.CurrentTime = TimeSpan.FromSeconds(0);

Ahem. Done.

Thanks to Mark Heath for this great library - check out Mark's Blog Here. All of this code was ripped from the NAudio Demo Application and is available within the source code package available on codeplex.

Next Time

I'll be looking at loading multiple wave files simultaneously and how to use the included Mixing functions. Stay tuned.

* It is licensed under the Microsoft Public License (Ms-PL), which I personally am not to farmiular with. However according to the FAQ there doesn't seem to be any issues distributing it along with another Open Source application. As I use the GPL I need to look a little further in to the licensing compatability but based on what I have read, I am hopeful for the moment that it is fine.

Saturday, January 10, 2009

Ram Disk for Windows XP x86 & 64

Reminds me of the days, when I used to load up a demo of Alien Breed 3d on an A4000 and copy the whole game in to Ram so I could play it.

This Ram Disk works under Windows XP 32 and 64 bit versions. You can use it to mount Disk images or just allocate memory space to copy files to.

Source Code available on the site as well.

Saturday, January 12, 2008

150,000 Downloads

"Oh my Strogg, their going after the data brain" well maybe not but some time over the last few days, OpenSebJ has reached another little milestone in it's history - the 150,000 download mark.

So what's been happening, well away in a lovely tree filled mountain, the development team have been hard at work playing to much ET:QW (Enemy Territory Quake Wars) but on the OpenSebJ development front there are the sounds of movement, sort of rustling, like little twigs breaking underfoot. These twigs are momentous in their meaning though as some of the tough work has been going on in the back ground.

OpenSebJ and previously Beat It have had a long time using interfaces to Microsoft's DirectX, through various interfaces, more recently using an interface called MDX. That added a weighty requirement to the distribution, sometimes in excess of 250 meg and although this was never distributed with OpenSebJ people still needed to download and install it as all good pre-requisites do.

Well those requirements are being shaken off and washed away, don't be mistaken - I haven't forsaken Windows support and I'm not going Mackintosh but I have had a revelation and I am going Open Source. Currently the SVN repository has been split in to 3 branches, a SDL.Net branch, OpenAL.Net and the MDX branch for prosperity (wont have further development). After completing a test migration to SDL.Net, I probably wont be continuing this branch any further. It is functional and does what it does but the lack of direct access to the sound buffers and some of the final control means that a lot of original functionality from OpenSebJ would have to be shelved.

So for the time being (until the foreseeable future that is) welcome OpenAL.Net to the OpenSebJ distribution and expect a release shortly.

~OpenSebJ

Tuesday, December 25, 2007

Not The Best Christmas Present

Like many people around the world, yesterday was spent with family and friends gift giving and much to much eating (the fried rice and turkey was fantastic).

Last night though I received an unexpected gift, from windows - a corrupted hard disk. Oh what a joyous event, a little drive that could was now a little drive that couldn't. Well by little I actually mean 500Gig. So after a few attempts at booting windows, Last Known Good, Safe Mode, Safe Mode Command Prompt etc, I then attempted a restore and low and behold the windows recovery option couldn't recognize the installation. Enough to drive your sanity to the limits.

I attempted to use a Recovery CD - Linux based and while I could use one tool to see the files in the drive still - or at least a listing, that was about it. GParted realised the disk was corrupt and instead recommended chdsk be run before trying to use GParted again. So cutting an unexciting story short, 16hours latter and chkdsk is still running. It's found a whole heap of problems and is still cracking along, slowly.

But here is the real lesson in the story (not the usual back up your stuff one) - avoid hibernate / sleep / standby in windows. Before the crash I had been copying files between two local drives, it had then gone in to standby - when I returned to the PC it was reporting a CRC error in the copy. My first though was a stuffed disk but since watching chkdsk I am thinking again - most of the corruption relates to files which were being copied, the point where windows was crashing relating to the disks or acpi. At the moment it's still a hunch but this MS knowledge base article isn't very reassuring Hard disk may become corrupted when entering standby or hibernation or when writing a memory dump

Any way heres hoping for a happy ending. I think I have at least another 8 hours of disk checking before I'll know the results. After that I'll be avoiding sleep / hibernation and standby, but thats just me.

Hope your technology this Christmas works better for you than it has me ;-)

Saturday, December 22, 2007

Open Source Audio on Windows with OpenAl.Net and Tao.OpenAl

I for some reason have a passion for Open Source Audio on Windows. I've got no idea why I don't just cross over and use Linux and its already fantastic collection of Audio programs (Hydrogen - an advanced drum machine, Rosegarden - Composition tool with MIDI support, Ardour - professional audio editing, multi-track audio mixing etc.) but for some reason I just enjoy C# and the IDE so I'm not moving, at least for the time being ;-)

Any way to get going with Open Source audio libraries in the .Net world there are a few good choices - so far I've used SDL.Net and OpenAl.Net (With Tao.OpenAl - it's a dependency for OpenAl.Net and Tao.SDL - it's a dependency for SDL.Net) and have had really good results.

Currently OpenSebJ - vScaleNotes, the high quality audio, fully sampled, virtual instrument tool, utalises SDL.Net for all of the sample playback and it will soon be using OpenAl.Net for all of the stream to disk recording.

I'm starting down the path of migrating OpenSebJ to Open Source only dependencies, currently OpenSebJ still requires the Managed DirectX libraries to function and as I've spoke of previously, accessibility is a serious issue here - that and the fact that Microsoft aren't further developing the MDX libraries. So a serious port is needed here, hopefully my object encapsulation is going to make this fairly easy ;-) Although I know some functions won't be supported going forward.

The fun part about using these libraries is making sure that you can package all of the dependencies in a single installer and make it really easy for people to use, rather than requiring a separate installation - some times though, the documentation can be a little lacking. So here's a little documentation for those looking for the requirements:

OpenAl.Net
OpenAl.Net requires the Tao.OpenAl framework; the Tao.OpenAl framework does the leg work interop'ing with the OpenAl DLL's.

To use OpenAl.Net in your project:
  1. Add a dependency linking to OpenAl.Net
  2. OpenAl.Net has a dependency to Tao.OpenAL but if you load Tao.OpenAl into your project make sure it is linked properly
  3. Make sure the OpenAl DLL's are accessible, for instance in the output directory. You need alut.dll , OpenAL32.dll & wrap_oal.dll - if you don't have all 3, like if you only have alut.dll you will get an error message from the compiler saying that it can't find alut.dll - you need the other ones even though the compiler error message is wrong - I hate that, any way that's why I'm writing this ;-)
  4. So in your directory with your program you should have alut.dll , OpenAL32.dll , wrap_oal.dll , OpenALDotNet.dll , Tao.OpenAl.dll , AdvanceMath.dll , csogg.dll , csvorbis.dll

SDL.Net

Similar to OpenAL.Net SDL.Net needs Tao.SDL to do the leg work with the underlying SDL DLL's. SDL.Net is fairly similar to setup but doesn't have audio recording support.

To use it in your project:
  1. Add a reference to SDL.Net
  2. SDL.Net has a dependency to Tao.SDL but if you load Tao.SDL into your project make sure it is linked properly
  3. Make sure the SDL DLL's are available - you need a copy of SDL.dll , SDL_gfx.dll , SDL_mixer.dll - just throw them in the output directory if you don't care about locations ;-)
  4. Now in the output directory you should have SDL.dll , SDL_gfx.dll , SDL_mixer.dll, SdlDotNet.dll & Tao.Sdl.dll

The really cool thing is that all of these files can be bundled in the installer. Keep a copy of the original sources, as per the GPL and make sure they are included or accessible. Too easy, well once you know the score any way.

Happy Open Source Audio Programing ;-)

64 Bit Windows with C# Express .Net and 32 Bit DLL's

An architecture change is always welcome and the 64 Bit world presents new and options for memory management and the like but there is certainly a price to pay for this shift. So far my personal experience with the 64 bit world has been one of mixed emotions, many hours of things not working, spending time researching on the net and looking for obtuse answers that sort of solve our problems.

The latest one is trying to develop using C# Express 2008 on Windows XP 64 Bit Edition and then using interoperability with a 32 Bit DLL - OpenAl. For some reason the compiler doesn't realise that the 32 Bit DLL is just that, 32 Bits. By default any .Net application developed will target the platform called "Any CPU" as opposed to a specific architecture. This is cool if your not using native DLL's but is a bit of a pain when you do. You will know that you are getting this problem when you start seeing System.BadImageFormatException popping up. It means that your application is trying to cross the Bit boundary - apparently our gracious OS is a separatist?

Any way there seems to be a saving grace, you can do development on a 64 Bit OS and set C# Express to specifically target your application to x86, it's just not obvious. Targeting x86 will mean that your application will still run on a 64 Bit OS but will use WoW (Windows on Windows, which is a compatibility layer to backwards support 32 Bit applications) so you won't get the native use of those 64 Bit Int's but you will be able to use your 32 Bit DLL's.

To setup your application to do this you need to
  1. G in to the menu option Tools>Options and the in the dialog box tick the box in the bottom right corner to "Show all settings"
  2. Once the settings expand go to "Projects and Settings" expand it and click on "General"
  3. Check the option which is called "Show advanced build configurations", then close the dialog box
  4. Now if you right click on the solution explorer, chose properties
  5. Goto "Configuration Properties", now you should be able to see the platform drop down. This will probably have only "Any CPU" selected; if so click on "Configuration Manager"
  6. Chose the option "New" under the "Active Solution Platform" drop down box
  7. Chose the new platform of x86 and copy your settings from "Any CPU"
  8. Then just make sure that your projects in your solution refer to x86 as the platform rather that "Any CPU"
Done.

Saturday, November 03, 2007

Frameworks & Accessibility

There's something relaxing about a well formed framework, whether it be a clean simple design, a stylish approach to a new problem being solved, the thought process that went in to its production or the functionality it delivers - what ever the case is something attractive and strangely sensual about good design.

Having a strong and simple foundation is important when introducing new people to a team, or a product to an audience and taking this in to account at the start of a project helps present options for future growth.

I've been spending some time building a new framework for OpenSebJ; to be used in both the Mixing and vScaleNotes products. I wanted something that would provide a new forging ground and one that could be easily modified and built upon over time, there were some great lessons learned from the OpenSebJ releases so far and a key one is accessibility.

It relates to User Interface Design, Application Prerequisites and well formed Frameworks. If the UI is to hard to come to grips with then its not an accessible product for people. If the Application Prerequisites are too much then you immediately reduce your potential install base; unless you can bundle it all in and package it up in the installer - this can be a bit of an issue if your trying to mix incompatible licenses. These issues prevent people accessing and using the product, a similar risk is also present for additional developers. It's important to cultivate the development community and as such it's a great idea to have the lowest barrier to entry for future changes and modifications, which can be facilitated through a well formed accessible framework.

Theres also something strangely relaxing about building them..

Saturday, October 13, 2007

Delegates in C#

So you instantiate a person and have a baby class, lets call it aBaby. Now your instance aBaby will occasionally (or regularly) need its diaper changed, so on intervals you check to see if it's dirty but wouldn't it be better for the baby to actually tell you that its nappy needs to be changed?

In an introduction to object oriented programing you would have learned how to create an instance of a class, call its methods and have data returned from the methods. But having the instance send data back to the class that created the instance on intervals or events is a little more tricky. If you see this scenario or design pattern then you need something like Delegates in C#, sure you could probably use a static class and throw data in to a variable that every other class can access - but wheres the class in that, not to mention the disastrous love you experience when you start running multiple threads.

Any way check out the video in the link - brilliant and simple introduction in to the delegates concept and one I'll be putting in place shortly.

Cheers,
Sebastian

Thursday, September 06, 2007

vScaleNotes Finaly Released

Sporting a full complement of Grand Piano Audio Samples, this high fidelity release brings the orchestra sound to a PC near you. Although Alpha and highly unpolished or finished this release lives the Open Source mantra of release early - even if it misses the release often bit - the important thing is that its out there and ready for your composing pleaser and kind words of encouragement (or give me some stick if you so please). Any way it mostly works, Sharp keys haven't been mapped to the keyboard and you can't change the layout yet but the piano is playing.
Feedback please.

-Sebastian

Interview on Laptop Rockers

Laptop Rockers helped celebrate the OpenSebJ download success, breaking 110K downloads, in an interview this week. Covering such topics as my nick names to choices in licensing and sporting a very fashionable photo this is worth a read if your looking in to a little more insight or background in to OpenSebJ - The Open Source, GPL, Audio Music Mixing program and the drivers behind its writing and release.

-Sebastian

Crystal Castles

If you have not had this pleasurable assault reach your ears yet, then I regret to inform you that you may have been missing out on the coolest new 8 bit sound in years. Alice and her practicing may have brought her an eye that she could show in the most depressing circumstances yet these two bring the sound that I have been yearning for all of these years. Crystal Castles, - Ethin and Alice, are an amazing line up and I can only hope that they thrash out in the land of OZ some time soon and release a CD without a limited number available. Perhaps some one could convince them to do a mix with OpenSebJ? Please.

-Sebastian

Wednesday, July 18, 2007

> 100K Downloads

OpenSebJ has now surpassed 100,000 downloads. It's a little moment in the projects history and a momentous occasion for all involved.

Thanks to every one who has contributed to the project, through code, example compositions, feature suggestions, requests, comments, criticisms and moral support. It has been a fantastic journey thus far and we look forward to the continuing experience.

The OpenSebJ team.

Saturday, May 05, 2007

Shout out to other Windows Open Source Audio Projects

OpenSebJ is powering on; with an ever increasing rate of downloads - over 10,000 last month - bringing the total so far to 79,693 (I would have written this latter in the week when it passed 80,000 but I couldn't wait :-)

I have been looking around at the other Open Source audio programs yesterday and then couldn't re-find them all easily this morning; so let me share with you some of the tools I have found and know of so far (All Free Open Source Software (FOSS) with a working Windows Port):

[Obviously my opinions of these programs are just, my opinions]

Audacity: http://audacity.sourceforge.net
The best FOSS wave/sample editor. If you want to change any pre-recorded audio this is the tool to use. It has heaps of downloadable effects, can convert, read and write in almost any format (wav, ogg, snd, mp3, etc) and is easy to use.

VioLet Composer: http://sourceforge.net/projects/buzz-like
It's like Buzz but FOSS. Allowing for programmable plug-ins; with the ability to daisy chain events together, mix channels and all bundled in with a Tracker style sequencer. If your looking for a creative outlet for synthesis

Denemo - (A front end to Lilypond) : http://sourceforge.net/projects/denemo/
Lilypond is a score printing tool but Denemo is much more than just that. It allows for scores to be composed completely from scratch in the traditional sense - these scores are exportable out in the MIDI format; although I wasn't able to get the windows port playing the score I composed directly (but I didn't spend any time trying to get it working either ;-) as the MIDI export worked flawlessly. If you want a musical score editor & composer; start right here.

Canorous: https://canorus.berlios.de/wiki/index.php/Main_Page
Another score editing tool. Its interface is clean and playing the notes entered on the score works as expected. I did have a few issues (a couple of crashes) while trying to get this going but this is defiantly another tool to keep an eye on.

BestPractice (Time stretching tool): http://www.xs4all.nl/~mp2004/bp/
The biggest honor to bestow is that the tool does what it says it will and this one does. To boot, it also includes a Kareoke option, Semitone adjustment, etc. Nice little tool. It doesn't contain a way of capturing the audio output, so use Audactiy and capture it through the sound card instead.

If you know of more that should be here; drop me a line and let me know about them.

Cheers,
Sebastian

Friday, March 23, 2007

OpenSebJ - vScaleNotes - Capability Demonstration

OpenSebJ is continuing on the up & up - with over 65,000 downloads under the belt; advanced audio and video mixing capabilities, a new track editor for even stronger pre-production composition coupled with continued and extending support for real time sample mixing; all touched up and packaged within a new Professional interface. OpenSebJ is positioned as a real contender for all your music mixing, audio composition and real time audio performance needs.

Continuing in this vein OpenSebJ is branching out in to a new, uncharted frontier, fully sampled virtual instruments; this new branch of capability is packaged together and called OpenSebJ - vScaleNotes. It features a range of new capabilities at the center of it's core, one of the most prominent is the Additive Sample Technology.

The new Additive Sample Technology is powered by an advanced SDL.net audio engine; which bridges many gaps, previously only available to highly complicated customised solutions. The Open Source nature of the SDL project allows these features to be capitalized on and brought to the complete populous, rather than the select few - who were prepared to pay the high price tag for the privilege.

Standard and simpler sample based audio engines can only make use of a binary based approach to playing audio; the sample is either playing or it's not. If the sample is to be re-triggered the sound is first stopped and then played again; this can lead to highly audible and distracting clicks and pops in the resulting composition. This removes the flow and reality from the produced sound and is an undesirable situation. See an example of this in the following; Figure 1.


Figure 1; The section within the red boundary highlights the flat point in the audio, as a result of the sample being replayed.


The Additive Sample Technology difference; is that when the same sample is re-triggered, it is done so in a new instance - a new channel is allocated and the sample is played. This removes the possibility of flat lines in the audio as a result of re-triggering a note or a key. See the following example, Figure 2, for a comparative view.


Figure 2; The highlighted section within the black boundary shows that when an audio sample is re-triggered it is done in an additive fashion rather than a strict binary, on or off nature. Due to this OpenSebJ - vScaleNotes, sounds extremely realistic and reminiscently of the original instrument.

vScaleNotes is a collection of Fully Sampled Virtual Instruments based on a full set of real instrument samples to reproduce the harmonics and intricacies of the original instrument; without the need for the original instrument or the understanding of the techniques required to correctly play the associated notes. The technique component may not be as critical to play a single note on a piano but it is certainly a contributing factor for string and wind based instruments.

Sample based virtual instruments also provide possibilities for combinations of notes to be played, which normally aren't capable of being played on a single real instrument (probably possible for multiple instrument harmonics etc.)

vScaleNotes makes extensive use of the previously developed OpenSebJ technology base & draws upon the vast experience of the OpenSebJ team; to deliver a real time, high quality performance grade system based upon solid foundations and a proven technology subsystem.

The samples used for vScaleNotes have been sourced from the University of Iowa's Electronic Music Studios, without their generous contribution of high quality musical instrument samples to the public domain, vScaleNotes would only be a shell of capability. These high quality samples total over 3 Gigabytes in size; the proportion of which will be available in vScaleNotes. To make the most effective use of these samples vScaleNotes will redistribute these in the high quality, audiophile approved, Ogg Vorbis format.

OpenSebJ - vScaleNotes is currently in pre-Alpha testing and as such a downloadable version is not currently available; however alpha & beta testers are being sought. All interested parties should fill out the contact form, stating reasons for interest and any relevant past experience.

Even though it is currently in a pre-Alpha state, OpenSebJ - vScaleNotes, still has full support for a Virtual Piano complement; listen to a sample of a live recording, demonstrating the quality and features available in OpenSebJ - vScaleNotes.

Audio Capability Demonstration:
OpenSebJ - vScaleNotes - Piano.mp3

Notes played in the following order C2 - C6, D6 - D2, E2 - E6, F6 - F2, G2 -G6, A6 - A2, B2 - B6

Regards,
The OpenSebJ Team

Friday, September 01, 2006

> 20,000 Downloads

OpenSebJ downloads are going strong; the 20,000 total download mark have been passed this month. A few bug fixes and some slight feature improvements have been included in the most recent releases.

Tuesday, July 04, 2006

Passed 10,000 Downloads - Still on Beta 0.1 (for the moment)

Passed the 10,000 download mark for OpenSebJ today, even though there hasn't been a subsequent release since the 0.1 Beta version (although it is coming soon ;-) - The next version will have a few bug fixes and a some requested changes will be implemented as well but the fun is still just beginning.

I've been looking in to the 3d side to MDX (Managed DirectX) recently and for the next Beta release I am hoping to introduce a VJ aspect to OpenSebJ. Currently I have limited the scope to being able to play images and videos in the same fashion and/or at the same time samples are playing to add a visual element to the production without a whole separate suite of management tools. I.e. You will be able to assign an image to a key and trigger its display each time the key is pressed (hopefully this will eventually cover video but no promises for the next beta version).

Always open to feedback, so let me know what you think and what you would like to see.

Cheers,
Sebastian

Computer Vision & CG with MDX: Requested: Video texture in MDX

Computer Vision & CG with MDX: Requested: Video texture in MDX

If you want to do anything more with Managed DirectX and Video files than load them and press play - you need to have a look at this post.

Why this process isn't simpler in MDX still amazes me.