Click here to Skip to main content
Click here to Skip to main content

Programming Audio Effects in C#

By , 16 Dec 2002
 

Sample Image - cswavplayfx.gif

Introduction

This article is a logical continuation of my article A low-level audio player in C# in which I presented an application that uses the waveout API in C# through Interop to play a WAV file in a continuous loop.

This time I will explain how to create a framework for implementing audio effects and how to extend the basic player to use it.

Audio Effects Framework

The idea of the framework is to provide some helper classes for implementing audio effects. In order to keep things simple, the framework was designed to help you implement effects that don’t modify the length of the sound data, although it can be easily extended to support other kinds of effects.

The basic effect class is called InPlaceEffect. The term in-place means that a single memory buffer is used for input and output. InPlaceEffect is an abstract class; derived classes must implement two methods: Connect and Process.

The Connect method is called when the host application wants to know whether the effect can process samples in a particular format. Sample formats are described by the WaveFormat structure, which holds common properties such as sampling rate, bit depth and number of channels.

Once an effect has been connected, the host application repeatedly calls the Process method passing in buffers with audio data. The audio effect should read data from the buffer and overwrite it with the modified data. In our sample application, the code that applies effects looks like this:

private void Filler(IntPtr data, int size)
{
    byte[] b = new byte[size];

    // ...here goes the code to fill in the data buffer...

    // Apply all selected effects
    foreach(EffectInfo nfo in m_Effects)
        nfo.Effect.Process(b, 0, b.Length);

    // ...here goes the code to send the data to the sound card...
}

The InPlaceEffect class does not help you much when it comes to processing audio samples because you still have to deal with all possible sample formats. The InPlaceEffectFloat class was designed to alleviate this situation. This class implements the Process method to convert each sample to floating point format, call a virtual method to process the sample and then convert the sample back to its original format. This approach is indeed far from being computationally efficient, but it lets you concentrate on your own algorithm by making the code much cleaner. “Real-life” audio effects typically deal with each format in a particular way in order to achieve maximum performance.

Another important aspect of the effect is the user interface that allows you to control the effect’s parameters. The FxForm class works in conjunction with the FxParameter attribute to create on the fly a user interface for your effect.

The FxParameter attribute is defined like this:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class FxParameterAttribute : Attribute
{
    public readonly string Name;
    public readonly float LowerBound;
    public readonly float UpperBound;
    public readonly float Increment;

    public readonly string Unit;
    public FxParameterAttribute(string name, float lowerBound, 
        float upperBound, float increment, string unit)
    {
        Name = name;
        LowerBound = lowerBound;
        UpperBound = upperBound;
        Increment = increment;
        Unit = unit;
    }
    public FxParameterAttribute(string name, float lowerBound, 
        float upperBound, float increment) : 
        this(name, lowerBound, upperBound, increment, string.Empty)
    {
    }
}

This attribute holds all the necessary information to display a NumericUpDown control that allows you to modify the parameter. At runtime, our framework automatically creates a form, based on the information from the attributes defined in the effect’s properties. The FxForm class then uses Reflection to enumerate properties and create the controls accordingly, like this:

private void CreateFxControls()
{
    Type t = Effect.GetType();
    object[] attrs = t.GetCustomAttributes
        (typeof(DescriptionAttribute), true);
    if (attrs.Length > 0)
        Text = ((DescriptionAttribute)attrs[0]).Description;

    // get effect parameters
    int y = 15;
    foreach (PropertyInfo prop in t.GetProperties())
    {
        attrs = prop.GetCustomAttributes
            (typeof(FxParameterAttribute), false);
        if (attrs.Length > 0)
        {
            // This is an effect paramater. Create the necessary controls.
            FxParameterAttribute param = (FxParameterAttribute)attrs[0];

            // ... create the necessary controls to handle the parameter

            y += 25;
        }
    }
    this.ClientSize = new Size(this.ClientSize.Width, y);
}

Other attributes could be defined to display other controls, such as check boxes or sliders. This is left as an exercise to the reader.

A Basic Effect: Volume Control

The code for the volume control is extremely simple since we just have to multiply each sample by a constant value. This approach works well for our purposes, but more sophisticated volume controls usually include some code to prevent zipper noise and to apply dithering at lower volume levels.

[Description("Volume Control")]
public class VolumeEffect : InPlaceEffectFloat
{
    private float m_Volume = 1.0f;

    [FxParameter("Volume", 0, 2.0f, 0.05f)]
    public float Volume
    {
        get { return m_Volume; }
        set
        {
            lock(this)
            m_Volume = value;
        }
    }

    public override bool Connect(WaveFormat format)
    {
        return true;
    }
    protected override float ProcessMono(float x)
    {
        return x * m_Volume;
    }
    protected override void ProcessStereo
            (ref float left, ref float right)
    {
        left *= m_Volume;
        right *= m_Volume;
    }
}

A More Complex Effect: Phase Shifter

The “Phase Shifter”, also known as “Phaser”, is a cool audio effect that can be described as a “breathing effect”. More information about how a Phaser works can be found here. I decided to implement a Phaser because this effect is not included with the standard DirectX effects.

As with the volume control, I derived this effect from the InPlaceEffectFloat class. The source code for the PhaseShiftEffect class can be found in the files that accompany this article.

Conclusion

In this article I explained how to extend a low-level audio player to implement support for audio effects.

A modified version of this sample that implements support for DirectX plug-ins is included with the Adapt-X SDK, which is a commercial product that can be found at www.chronotron.com.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Ianier Munoz
Web Developer
Luxembourg Luxembourg
Member
Ianier Munoz lives in France and works as a senior consultant and analyst for an international consulting firm. His specialty is in multimedia applications, and he has authored some popular software, such as American DJ's Pro-Mix, Chronotron and Adapt-X.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionChoose the sound card used for a specific applicationmemberCHATAIGNON Clément4 Feb '13 - 23:39 
Hi,
 
is it possible to choose de sound card used for the application output?
 
I'am trying to do an application allowing users to change the sound card used for a specific running application (for exemple, having your music on speakers and a game on headphones (speakers are on a first sound card, and headphones on an other)
 
Have i a chance to succeed?
 
Thanks
-----------------
Bonjour,
 
Est-il possible de choisir la carte son utilisée par une application?
 
J'ai pour but de créer une application permettant à un utilisateur de changer la carte son utilisée par une application (par exemple, avoir votre musique sur les hauts parleurs et un jeu sur un casque audio, chacun étant sur une carte son différente).
 
Ai-je une chance ou est-ce impossible?
 
Merci d'avance
 
CHATAIGNON Clément
GeneralMy vote of 5memberronykucse4 Jul '12 - 21:25 
good
SuggestionAn AlternativememberDadajiIn28 Jun '12 - 23:33 
http://dotnetfiles.com/Control/5/sound-library.aspx[^]
GeneralMy vote of 3memberasmitajb14 Feb '11 - 1:36 
ccbc cvc
QuestionHow can I pull out every sample into textBox?memberBernard Opala30 Jan '10 - 12:13 
It could be silly question but I entirely do not know how to do it? I'll appreciate even some suggestions. I can't go any further with c# without it, i'm sure that many similar "stops" will be on my way if won't know it - it's fundamental.
 
Regards!
AnswerRe: How can I pull out every sample into textBox?memberBernard Opala25 May '10 - 10:51 
Huh, I made it!
 
I'm astonished till now how simple it was - who knew it before? Smile | :)
 
Add to MainForm.cs following lines:
public static byte[] sBuffer;
private Thread s_Thread;
 
public void Filler(IntPtr data, int size)
{
    if
    {
        (...)
    }
    else
    {
        (...)
    }
 
    sBuffer = b;
    s_Thread = new Thread(new ThreadStart( DoSth ));
    s_Thread.Start();
 
    System.Runtime.(...)
}
 
public void DoSth()
{
    for (int i = 0; i < sBuffer.Length; i+=128)
    {
        textBox1.Invoke(new UpdateTextCallback(PullOutThisSampleIntoTextBox), sBuffer[i]);
    }
}
 
private void PullOutThisSampleIntoTextBox(byte sample)
{
    textBox1.Text = sample.ToString();
}

Generalband-pass filtermemberondra0019 Jan '10 - 6:01 
Hi,
is there any way how to use your code to make a band-pass filter?
 
thanks Smile | :)
GeneralDirectX Qmembermoosh2 Mar '09 - 10:34 
Hi,
can i play a file in directX and record the same played sound at the same time also in directX?
up to use the same dvice/not?
 
Thanks.
QuestionI include "wave visualicer", Somebody have others examples effects codes?memberYulay29 Dec '08 - 23:40 
Congratulations Ianer, great work. I´m working whith this code for my degree. I have included this code to "wave visulicer" and now i´m trying include more effects to the project. SmBdy could help me, please? Some easy code like echo, rever, delay, compressor,ditorsion... to include in this code?
Sorry for my poor english and Thanks to everyone in this web.
GeneralPlay non-wav audio samplesmemberMember 411849721 Dec '08 - 22:49 
Hi,
 
Great work! Is it possible to play non-wav audio files (e.g. mp3, wma)? Because I can't get it to play mp3 files even after I converted them into wav files.
 
Thanks!
 
Lun
Generalaccessing the data samples...memberjshunnar18 Feb '08 - 4:38 
Hi Ianier,
 
I'm developing a WM5 application that needs to captuer a voice signal and then access the voice samples (data samples) to modify or manipulate it. In fact, My application is about removing the silence in a given voice signal. I read some APIs and I think that the WAVEHDR class would do it. Am I on the right track?
 
THNX,
J
GeneralSample MP3 volume levelmemberbradirby2 Aug '07 - 19:26 
Excellent article on MP3 file manipulation. Thanks for the contribution.
 
I'm looking for some code that will let me detect teh volume level at each sample in an MP3 file. I can find several examples of how to set the volume, but how do I detect the current volume level?
 
thanks in advance for your hlep.
GeneralConvert Wave file to Mp3 filememberLe Quoc Do12 Jul '07 - 17:44 
Please help me! How to convert wave file to Mp3 file?
 
Please help me! How to convert wave file to Mp3 file?

Generalproblems with non 44100Hzmemberwazomba6 Apr '06 - 3:56 
When i change the format to 8000Hz or an other frequenzy, then is the output signal faster (higher frequenzy) than the input signal. what make i false?
 
WaveLib.WaveFormat fmt = new WaveLib.WaveFormat(8000, 16, 2);
GeneralRe: problems with non 44100HzmemberIanier Munoz28 Apr '06 - 20:06 
Hi Wazomba,
 
You should play the WAV file at its original rate, otherwise it will sound distorted. If you want to play at another sample rate, you should first resample the input file.
Regards,

 
Ianier Munoz
www.chronotron.com

GeneralInterrupring the recording while doing other taskssussZarko Petrovski2 May '05 - 15:25 
Thank You for getting me into this recording-paying adventureLaugh | :laugh:
Your projects helped me a lot.
 
But it will be appriciate if You help me arround this problem.
 
Probably You know that while the recorder is running ,and when You are doing some other tasks on the machine like for eg. dragging windows ,minimazing-maxinazing windows ,starting other apps ,the recorder suspends it's work for some short time(as i note the this time and the suspending frequency depend on the size of the buffer).Small buffer size - more often suspendings (pausing) - lowest latency between recording and playing ,and otherwise.So please help me if You have suggestion for avoiding this.
 
I'm stocked on a place where i didn't expect Unsure | :~
 
Thank You in advanceSmile | :)
GeneralRe: Interrupring the recording while doing other tasksmemberIanier Munoz28 Apr '06 - 20:15 
Hi,
 
There are some things you can do to speed up the code. For example, in the Filler method you could promote the temporary buffer variable b to a member variable, in order to reduce the number of garbage collections. Something like this:
 

byte[] m_Buffer;
 
private void Filler(IntPtr data, int size)
{
if (m_Buffer == null || m_Buffer.Length != size)
m_Buffer = new byte[size];
....

 
The general idea is to make the Filler method rus as fast as possible, so any optimization techniques you know are welcome.
 
Regards,
 
Ianier Munoz
www.chronotron.com

GeneralChange Audio samplemembersrenzi4 Apr '05 - 7:28 
Ianer , great job !
Can you tell me where to find some info about chabging the audio samples, from 8 to 16 .
 
Thank you very much
GeneralImplementing IDisposable in WaveStream class...membershit@ok.ru19 Mar '05 - 6:19 
Implementing IDisposable in WaveStream class is really need?
 
public override void Close()
{
if (_stream != null)
_stream.Close();
}
 
..do same work.
GeneralRe: Implementing IDisposable in WaveStream class...memberIanier Munoz20 Mar '05 - 19:05 
It is good practice (and part of the .NET development guidelines) to implement IDisposable on every class that uses unmanaged resources, in order to be consistent with the rest of the framework. This allows you to use the C# using clause (as well as deterministic destruction in C++/CLI, BTW).
That's also the reason why, for example, the System.IO.Stream class implements both IDisposable and a Close method.
Regards,

 
Ianier Munoz
www.chronotron.com

GeneralRe: Implementing IDisposable in WaveStream class... [modified]membergumi_r@msn.com15 Mar '07 - 6:43 
You can actually implement IDisposable without making Dispose() public.
This is achieved easily implementing IDisposable like so:
 

public DisposableClass: IDisposable
{
public void Close()
{
//Dispose stuff here
GC.SupressFinalize(this);
}
 
void IDisposable.Dispose()
{
Close();
}
}

 
This was the way the IDisposable pattern was implemented in NET 1.0 and 1.1. streams. It makes sense as there is no reasonable reason to duplicate the same functionality in two different methods. The proble, IMO is that the .NET team chose to keep the wrong method Big Grin | :-D , it should have been Dispose(), not Close() and thanks to good old friend "backwards compatibilty" we're stuck with keeping both forever Big Grin | :-D . Why Dispose()?
 
1. It can be a little confusing to consumers that are expecting a Dispose method whenever they encounter an IDisposable object. I guess thats why the Dispose() method has been made accesible in .NET 2.0 even though the Close() method offered exactly the same functionality.
 
2. Also the Close() method is inconsistent throughout the .NET framework. Close(), at least to me, implies an object state that seems to be recoverable. For example a Database connection has a Close and Dispose method but this is a valid situation because Close does not dispose the object, it just temporarly closes the connection whereas Dispose will..well...dispose the object forever Big Grin | :-D .
 
On the other hand, Close in streams is as unrecoverable as Dispose, its essentially the same method, and that can be misguiding.
 
The logical step would be to remove the Close() method for consistency's sake if nothing else, but this cant be done obviously because it would be an unacceptable breaking change to the API :p
 
-- modified at 13:15 Thursday 15th March, 2007
GeneralUsing with DirectSoundmemberoversight_failure23 Feb '05 - 17:22 
Hi. This is some really helpful code, but I'm trying to implement it using DirectSound and hitting a roadblock.
 
Basically, I have a SecondaryBuffer with its built in DirectX effects. In addition, I'd like to be able to have my own custom effects thrown into the mix. I had no trouble converting the FX frame stuff and effects code so they compile with DirectSound instead of your WaveLib, but the process function uses a buffer and processes it with the effect. From what I can see, the SecondaryBuffer has no way of giving me the raw buffer it's using. There is a read function that isn't very well documented, but I'm not sure if that's what I want. Essentially I need to process the buffer with my own effects when the buffer's "Play" method is called. It's easy to add built in DirectX effects into the effects array, but I have no clue how to stick my own in.
 
Any pointers on processing a SecondaryBuffer would be great. Actually, any tips on implementing my own custom DirectX effects would be awesome.
 
Thanks!
GeneralRe: Using with DirectSoundmemberIanier Munoz24 Feb '05 - 18:55 
Hi,
The simplest way to go about porting this code to DSound is by replacing just the waveOut part by a DSound streaming buffer (for information on how to create a DSound streaming buffer see my article "Building a Drum Machine with DirectSound").
To stick your own effect in a DSound buffer you have to write an in-place DMO (check the DShow docs for information on DMOs). While using custom DMO effects with secondary buffers is well documented in the unmanaged world, there's no documentation about this topic in the managed SDK. I might write an article on this subject.
Regards,

 
Ianier Munoz
www.chronotron.com

GeneralPlaying Sound Simultaneously and Tempomemberaniljain217 Jan '05 - 23:37 
This is an excellent article to play WAV files. I also need to play audio samples simultaneously and also in Tempo. can this article be modified to achieve both the functionalities or are there any other URL's available which provides samples to play simultaneously and also audio samples in Tempo.
 
Thanks in advance!!
 
Anil
GeneralRe: Playing Sound Simultaneously and TempomemberIanier Munoz21 Jan '05 - 5:56 
Hi,
To play samples simultaneously you can use Managed DirectSound, which you can download from the Microsoft web site.
Regarding tempo synchronization, this is a relatively complex topic, so I would suggest that you use Google to search for libraries or other developer resources on this topic.
Regards,

 
Ianier Munoz
www.chronotron.com

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 17 Dec 2002
Article Copyright 2002 by Ianier Munoz
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid