5
\$\begingroup\$

I have put together a basic sound handler class for use in my game. It uses OpenAL.

It's a simple method at the moment to play sounds - here's an extract from a switch block:

case "EATEN_PILL":
    wavPath = "D:\\Programming\\SFX\\EATEN_PILL\\Hit_43.wav";
    ALfilename = Path.Combine(Path.Combine("Data", "Audio"), wavPath);                          
    sound_data = LoadWave(File.Open(ALfilename, FileMode.Open), out channels, out bits_per_sample, out sample_rate);
    buffer_EATEN_PILL = AL.GenBuffer();
    AL.BufferData(buffer_EATEN_PILL, GetSoundFormat(channels, bits_per_sample), sound_data, sound_data.Length, sample_rate);
    AL.Source(source, ALSourcei.Buffer, buffer_EATEN_PILL);
    AL.SourcePlay(source);

    break;

Problems: 1. An annoying click at the end of each sound. 2. Eventually sound stops playing completely. This is after perhaps playing 100 sounds.

Has anyone hit this problem before?

\$\endgroup\$
1
  • \$\begingroup\$ The click at the end could be the result of a DC bias on your clip(s), or it could be some incompatibility/bug between your audio driver and OpenAL. Last time I used OpenAL I had no problems with that. As for stopping after 100ish sounds, make sure you're cleaning up your buffers and sources when you're done using them. I'm not familiar with the C# OpenAL bindings but it doesn't look like you're releasing them anywhere. On a side note, you don't want to load the file every time you play it. Load it once, keep the buffer id around, then give it to a source when you want to play it. \$\endgroup\$
    – bcrist
    Commented Mar 8, 2014 at 21:43

3 Answers 3

14
\$\begingroup\$

This is probably the solution for most of the clicking sounds from OpenAL-Soft library.

Might be too late after more than year for the person who posted the question but might be useful for others who end up here through Google.

I had the same problem of hearing clicks at the end of loops so I tried to figure out where it is coming from. While trying different things I realized that when I create some waves dynamically, there are no clicks being heard but loading the files will result in clicks so I guessed that there are some extra bytes loaded while loading the files that produces the click so I took a look at the wav file spec here:

PCM/WAVE file spec

and found the proper header including the data length and instead of loading the whole data after the header, just loading the amount of bytes mentioned as the data length in the header will fix the problem!

I leave my code snippet here for everyone to take a look and I'm sure it will solve the problem for many developers. ;) :D

    public void BufferData(Stream audioDataStream)
    {
        if (audioDataStream == null)
        {
            throw new ArgumentNullException("audioDataStream");
        }

        using (BinaryReader reader = new BinaryReader(audioDataStream))
        {
            // RIFF File Marker
            string signature = new string(reader.ReadChars(4));

            if (signature != "RIFF")
            {
                throw new NotSupportedException("Specified stream is not a wave file.");
            }

            //Size of the overall file
            reader.ReadInt32();

            // WAVE File Type Header
            string format = new string(reader.ReadChars(4));

            if (format != "WAVE")
            {
                throw new NotSupportedException("Specified stream is not a wave file.");
            }

            // 'fmt ' Format chunk marker (Includes trailing null)
            string formatSignature = new string(reader.ReadChars(4));

            if (formatSignature != "fmt ")
            {
                throw new NotSupportedException("Specified wave file is not supported.");
            }

            //Length of format data as listed above
            reader.ReadInt32();

            //Type of format (1 is PCM)
            reader.ReadInt16();

            //Number of Channels
            int channels = reader.ReadInt16();

            //Sample Rate
            int sampleRate = reader.ReadInt32();

            //(Sample Rate * BitsPerSample * Channels) / 8
            reader.ReadInt32();

            //(BitsPerSample * Channels) / 8
            reader.ReadInt16();

            //Bits per sample
            int bits = reader.ReadInt16();

            //"data" chunk header, Marks the beginning of the data section
            string dataSignature = new string(reader.ReadChars(4));

            if (dataSignature != "data")
            {
                throw new NotSupportedException("Specified wave file is not supported.");
            }

            //Size of the data section
            int dataLength = reader.ReadInt32(); // <========== **The correct data length**

            ALFormat audioFormat = getAudioFormat(channels, bits);

            byte[] audioData = reader.ReadBytes(dataLength);

            AL.BufferData(ID, audioFormat, audioData, audioData.Length, sampleRate);
        }
    }

    private ALFormat getAudioFormat(int channels, int bits)
    {
        switch (channels)
        {
            case 1: return bits == 8 ? ALFormat.Mono8 : ALFormat.Mono16;

            case 2: return bits == 8 ? ALFormat.Stereo8 : ALFormat.Stereo16;

            default: throw new NotSupportedException("The specified sound format is not supported.");
        }
    }
\$\endgroup\$
1
  • 1
    \$\begingroup\$ This fixed two of my issues. Not only were some sounds clicking, but others were refusing to play at all. My fix was to copy your solution where it says byte[] audioData = reader.ReadBytes(dataLength); and also, dataLength has to be the value returned by reader.ReadInt32() as you wrote above. \$\endgroup\$
    – matt_rule
    Commented Jan 11, 2020 at 0:58
1
\$\begingroup\$

You are leaking memory each time a sound is being played.

You might consider:

  • Making a class for a SoundBuffer.
  • Loading all sounds after OpenAL is initialized (after you have created the AudioContext object)
  • Keep in mind that a SoundBuffer is an unmannaged resource.
  • Implement Disposable pattern for SoundBuffer and call Dispose once the resource is no longer needed.
  • You could also simply store an array of buffer IDs and release them afterwards with AL.DeleteBuffer(BufferID) or AL.DeleteBuffers(BufferIDArray)
\$\endgroup\$
0
\$\begingroup\$

If you are doing everything correctly and there's still clicking (a definite possibility if the sound file doesn't end in faded to silence at amplitude 0), the way in general to address the problem is to fade it out to silence over the last 20 milliseconds (exact length of fade can be tuned to your needs). If you have popping in the beginning, you can solve it similarly by fading in from silence over a short period of time. These fades are called envelopes and are a common solution to the problem (:

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .