Jump to content
  • Advertisement
Sign in to follow this  
Grain

Doesn't sound right.(Still need help...)

This topic is 3493 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This sine wave generator doesn't quite sound correct. But I can't figure it out. 1) If I use any other value besides format.AverageBytesPerSecond I get pops and clicks in the sound. This doesn't make any sense to me. Could some one pleases explain it and maybe tell me how to change it to use a different value. 2) The sound is kind of flat and dirty to my ear. I downloaded a tone generator off the internet and it sounds clear and has a nice "bell ring" to it. Perhapses some one with a discerning ear could tell me if I'm imagining it.
using System.Drawing;
using System.Windows.Forms;
using SlimDX;
using SlimDX.XAudio2;
using SlimDX.Multimedia;

namespace BasicSound
{
    class Program : Form
    {
        public Program()
        {
            ClientSize = new System.Drawing.Size(640, 480);
        }    
        static void Main()
        {
            Program prog = new Program();
            prog.Show();
            prog.Run();
            
        }
        public void Run()
        {
            byte[] data;
            WaveFormat format = new WaveFormat();
            format.BitsPerSample = 16;
            format.Channels = 1;
            format.SamplesPerSecond = 44100;
            format.BlockAlignment = (short)(format.Channels * (format.BitsPerSample / 8));
            format.AverageBytesPerSecond = format.BlockAlignment * format.SamplesPerSecond;
            format.FormatTag = SlimDX.WaveFormatTag.Pcm;

            data = new byte[format.AverageBytesPerSecond]; //any other values here gives pops and clicks every so often.
            double Hz = 221;
            double Amplitude = 0.45;
            double inc = (1.0d / format.SamplesPerSecond) * Hz * 2 * System.Math.PI;
            double theta = 0;
          
            Speaker speaker = new Speaker(format);
            speaker.Play();
            while (this.Created)
            {
                if (speaker.BufferSoundData(data))
                {
                    for (int i = 0; i < data.Length; i += 2) //Where all the action happens. This is where I actually create the sound data.
                    {
                        if (theta > (2 * System.Math.PI)) { theta -= (2 * System.Math.PI); }
                        double dub = System.Math.Sin(theta) * Amplitude;
                        theta += inc;
                        System.Int16 word = (System.Int16)(dub * (System.Int16.MaxValue));
                        data = (byte)(word % 256);
                        data[i + 1] = (byte)(word / 256);
                        //data = (byte)(dub * 128 + 128);
                    }
                }

                Thread.Sleep(1);
                Application.DoEvents();
            }
            speaker.Dispose();
        }
    }

    public class Speaker
    {
        XAudio2 device;
        MasteringVoice masteringVoice;
        SourceVoice sourceVoice;
        WaveFormat waveFormat;
        
        public Speaker(WaveFormat format)
        {
            device = new XAudio2();
            masteringVoice = new MasteringVoice(device);
            sourceVoice = new SourceVoice(device, format);
            waveFormat = format;
        }

        public void Dispose()
        {
            sourceVoice.Dispose(); 
            masteringVoice.Dispose();
            device.Dispose();
        }

        public void Play() { sourceVoice.Start(); }
        public void Stop() { sourceVoice.Stop();  }

        public bool BufferSoundData(byte[] data)
        {
            if (sourceVoice.State.BuffersQueued > 1)
            {
                return false;
            }
            else
            {
                AudioBuffer buffer = new AudioBuffer();
                buffer.AudioData = data;
                buffer.AudioBytes = data.Length;
                buffer.Flags = BufferFlags.None;
                sourceVoice.SubmitSourceBuffer(buffer);
                buffer.Dispose();
                return true;
            }            
        }
    }
}


[Edited by - Grain on January 28, 2009 6:57:36 PM]

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by Grain
This sine wave generator doesn't quite sound correct. But I can't figure it out.

1) If I use any other value besides format.AverageBytesPerSecond I get pops and clicks in the sound. This doesn't make any sense to me. Could some one pleases explain it and maybe tell me how to change it to use a different value.

2) The sound is kind of flat and dirty to my ear. I downloaded a tone generator off the internet and it sounds clear and has a nice "bell ring" to it. Perhapses some one with a discerning ear could tell me if I'm imagining it.

*** Source Snippet Removed ***
If bits per sample is 16 then you should be using an array of shorts, not bytes. You'll also need to be clear on whether the format you're writing to is supposed to be signed or unsigned.

An optimisation I noted would be to scale Amplitude by System.Int16.MaxValue before the loop.

I haven't listened to the sound, but if it sounds like a constant tone whereas you were expecting a bell-like sound then you'll need to use a different algorithm.
E.g. using a low-pass filter with delayed feedback produces a "plucked string" sound (no sine wave required!). For a bell you might need to vary the pitch acording to a seperate lower frequency sine wave, and also taper the sound the longer it plays for.

Note that using Thread.Sleep and Application.DoEvents are usually a bad idea. DoEvents can cause all kinds of re-entrancy problems and should usually be avoided like the plague. If you absolutely need your program to be responsive during this because it takes too long then the way you should do it is write a function that you can call to do a small portion of the work each time and then let it return back to the main message processing loop each time after doing a little work. To many it seems like a magic bullet in terms of making your UI responsive, however if you use it long enough you'll see how disastrous it really is.

Share this post


Link to post
Share on other sites
Quote:
Original post by iMalc
If bits per sample is 16 then you should be using an array of shorts, not bytes. You'll also need to be clear on whether the format you're writing to is supposed to be signed or unsigned.
But the audio API is expecting bytes no matter what. I can't change that. All I can do is convert from 16 bits to 8. Also the 16 bit data IS signed, If I use unsigned it sounds like a circular saw.

Quote:
Original post by iMalcI haven't listened to the sound, but if it sounds like a constant tone whereas you were expecting a bell-like sound then you'll need to use a different algorithm.


Well right now its only supposed to be a flat sine wave, however it just doesn't sound as clear to me as the one I downloaded.

Eventually this is going to be a complex icon based sound generator similar to ReacTable, that has all those filters. But for now it just does sine waves.

Quote:
Original post by iMalcNote that using Thread.Sleep and Application.DoEvents are usually a bad idea. DoEvents can cause all kinds of re-entrancy problems and should usually be avoided like the plague. If you absolutely need your program to be responsive during this because it takes too long then the way you should do it is write a function that you can call to do a small portion of the work each time and then let it return back to the main message processing loop each time after doing a little work. To many it seems like a magic bullet in terms of making your UI responsive, however if you use it long enough you'll see how disastrous it really is.

Well that is essentially what it does, just creates a small portion of the sound and passes it along to audio device. Just in a for loop and not a function. I'm not sure how putting it in a function would change anything. Without Application.DoEvents how else would I be able to close the program besides killing it task manager?

Share this post


Link to post
Share on other sites
Quote:
Original post by Grain
Quote:
Original post by iMalc
If bits per sample is 16 then you should be using an array of shorts, not bytes. You'll also need to be clear on whether the format you're writing to is supposed to be signed or unsigned.
But the audio API is expecting bytes no matter what. I can't change that. All I can do is convert from 16 bits to 8. Also the 16 bit data IS signed, If I use unsigned it sounds like a circular saw.
Well I guess it doesn't matter that much as it should be able to work that way as well, but I would expect most people to just cast it at the point it is passed to the API.
Quote:
Quote:
Original post by iMalcI haven't listened to the sound, but if it sounds like a constant tone whereas you were expecting a bell-like sound then you'll need to use a different algorithm.


Well right now its only supposed to be a flat sine wave, however it just doesn't sound as clear to me as the one I downloaded.

Eventually this is going to be a complex icon based sound generator similar to ReacTable, that has all those filters. But for now it just does sine waves.

Quote:
Original post by iMalcNote that using Thread.Sleep and Application.DoEvents are usually a bad idea. DoEvents can cause all kinds of re-entrancy problems and should usually be avoided like the plague. If you absolutely need your program to be responsive during this because it takes too long then the way you should do it is write a function that you can call to do a small portion of the work each time and then let it return back to the main message processing loop each time after doing a little work. To many it seems like a magic bullet in terms of making your UI responsive, however if you use it long enough you'll see how disastrous it really is.

Well that is essentially what it does, just creates a small portion of the sound and passes it along to audio device. Just in a for loop and not a function. I'm not sure how putting it in a function would change anything. Without Application.DoEvents how else would I be able to close the program besides killing it task manager?
Another way, which might be best in this case would be to run the audio in a different thread from the UI. However if this is just some kind of test app, then don't worry about it for now.

Here's some more ideas:

Have you tried graphing the values you're generating? That would be the easiest way to see if there is anything wrong, besides listening to it.

Does it sound any better with an amplitude of 1.0?

One possibility is that the tone generator you've listened to might be performing anti-aliasing on the wave. Same principle as for images, but applied to one dimension instead of two. You generate twice as many samples and average two consecutive samples to produce a final sample. You'd hope that the sin function would be pretty good to begin with, but if it were me I'd try it.

One last thought: Should the call to BufferSoundData go after the generation of the samples? Wont it start reading them whilst you're still writing them the way you have it? Have you tried putting it after the for loop?

Good luck! I hope someone else comes along to try giving you a hand.

Share this post


Link to post
Share on other sites
Very strange. For some reason this worked. I changed the API warper to do the conversion from doubles to bytes internally and now I can give it any buffer length I want. (provided it not insanely short. 300 samples seems to be the lower limit. At least on my hardware.) I don't understand it though, functionally it's doing the exact same thing, just in a different place.

Though I still have the "dirty sound" to it. I'm going to try creating two samples and averaging them next.

Anyway, moving the place where I call BufferSoundData didn't seem to have any effect.

If I get rid of Thread.Sleep the sound becomes choppy again, and if I get rid of Application.DoEvents I cant X out my app and need to use task manager to kill it. Why is it bad to use them together?

How would I go about running the Audio portion in a different thread? I'm not too familiar with multi threaded programing in general and not at all in C#.



using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.Runtime.InteropServices;
using System.Threading;
using SlimDX;
using SlimDX.XAudio2;
using SlimDX.Multimedia;

namespace BasicSound
{
class Program : Form
{
public Program()
{
ClientSize = new System.Drawing.Size(640, 480);
}
static void Main()
{
Program prog = new Program();
prog.Show();
prog.Run();

}
public void Run()
{
double[] data;
WaveFormat format = new WaveFormat();
format.BitsPerSample = 16;
format.Channels = 1;
format.SamplesPerSecond = 44100;
format.BlockAlignment = (short)(format.Channels * (format.BitsPerSample / 8));
format.AverageBytesPerSecond = format.BlockAlignment * format.SamplesPerSecond;
format.FormatTag = SlimDX.WaveFormatTag.Pcm;

data = new double[format.SamplesPerSecond / 15];
double Hz = 220;
double Amplitude = 0.45;
double inc = (1.0d / format.SamplesPerSecond) * Hz * 2 * System.Math.PI;
double theta = 0;

Speaker speaker = new Speaker(format);
speaker.Play();
bool BufferOpen = true;
while (this.Created)
{
if(BufferOpen)
{
for (int i = 0; i < data.Length; i++)
{
if (theta > (2 * System.Math.PI)) { theta -= (2 * System.Math.PI); }
data = System.Math.Sin(theta) * Amplitude;
theta += inc;
}

}
BufferOpen = speaker.BufferSoundData(data);
Thread.Sleep(1);
Application.DoEvents();
}
speaker.Dispose();
}
}

public class Speaker
{
XAudio2 device;
MasteringVoice masteringVoice;
SourceVoice sourceVoice;
WaveFormat waveFormat;
int bytesPerSample;

public Speaker(WaveFormat format)
{
device = new XAudio2();
masteringVoice = new MasteringVoice(device);
sourceVoice = new SourceVoice(device, format);
waveFormat = format;
bytesPerSample = waveFormat.BitsPerSample / 8;
}

public void Dispose()
{
sourceVoice.Dispose();
masteringVoice.Dispose();
device.Dispose();
}

public void Play() { sourceVoice.Start(); }
public void Stop() { sourceVoice.Stop(); }

public bool BufferSoundData(double[] data)
{
if (sourceVoice.State.BuffersQueued > 1)
{
return false;
}
else
{
byte[] bData = new byte[data.Length * bytesPerSample];
if (waveFormat.BitsPerSample == 8)
for (int i = 0; i < bData.Length; i++ )
{
bData = (byte)(data * 128 + 128);
}
if (waveFormat.BitsPerSample == 16) // I'd like to add 24-bit as well.
for (int i = 0, j = 0; i < bData.Length; i += 2, j++)
{
System.Int16 word = (System.Int16)(data[j] * (System.Int16.MaxValue));
bData = (byte)(word % 256);
bData[i + 1] = (byte)(word / 256);
}
AudioBuffer buffer = new AudioBuffer();
buffer.AudioData = bData;
buffer.AudioBytes = bData.Length;
buffer.Flags = BufferFlags.None;
sourceVoice.SubmitSourceBuffer(buffer);
buffer.Dispose();
return true;
}
}
}
}



BTW in Speaker.BufferSoundData Id like to to add a branch to support 24-bit sound as well. But I have no idea how to do he conversion. How should the bytes be aligned?

Share this post


Link to post
Share on other sites
Quote:
Original post by Grain
Very strange. For some reason this worked. I changed the API warper to do the conversion from doubles to bytes internally and now I can give it any buffer length I want. (provided it not insanely short. 300 samples seems to be the lower limit. At least on my hardware.) I don't understand it though, functionally it's doing the exact same thing, just in a different place.

Must be not quite the exact same thing. No way to know what the difference is though I guess.
Quote:
Though I still have the "dirty sound" to it. I'm going to try creating two samples and averaging them next.

Anyway, moving the place where I call BufferSoundData didn't seem to have any effect.
Okay, just be sure to check the documentation about it. If you're supposed to pass it a buffer of the data to play, then you really shouldn't be writing to it whilst it's supposed to be playing it.
Quote:


If I get rid of Thread.Sleep the sound becomes choppy again, and if I get rid of Application.DoEvents I cant X out my app and need to use task manager to kill it. Why is it bad to use them together?
It's not about them being used together. DoEvents is the problem. It means that any button click or keypress or menu item selection or whatever can happen right there and then. This is bad because whatever buton you clicked to even enter this function might get clicked again etc causing chaos. You seldom want to allow your program to start doing multiple things that each interrupt and then block one another in the same thread. If you app is simple and you're very careful it's possible to use it without problems.
The Sleep is just hopefully unnecessary, and a potential cause of any popping sounds.
Quote:
How would I go about running the Audio portion in a different thread? I'm not too familiar with multi threaded programing in general and not at all in C#.
Threading in C# isn't something I've had to do much with yet. A little yes, but not much, so I'd rather someone someone else provide help there. If it were C or C++ though I'd be fine explaining how to do it.
Quote:
BTW in Speaker.BufferSoundData Id like to to add a branch to support 24-bit sound as well. But I have no idea how to do he conversion. How should the bytes be aligned?
I've only dealt with 16-bit sound generation myself, but the bytes would surely be packed with no padding bytes just like 24-bit images.

Share this post


Link to post
Share on other sites
So I still kind of need help with this.

I used some high quality headphones and the difference is clear. My sound has a definite electronic sounding static in the background thats not present in the tone generator I downloaded.

I tried generating 4 times as many samples and averaged the extra ones, but it didn't change how it sounded.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!