How to get each sample of a sine wave given the samplerate and frequency, and bit depth?

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

I hope this is the correct forum.

I am learning while doing a little mobile app with C#, in the book I am following there is a part where a triangular wave is generated and I want to change it to a Sine, but I can not figure out how to apply the mathematical sine wave formula to get each sample at (say) 16 bits with a normalized 0 - 1 amplitude and at 44.1Khz.

The audio library expects a buffer with that data, can someone please point me in the right direction?

Thanks!

pd: Just in case, the code used for the triangle wave is:

Code: Select all

class SoundPlayer
    {
        const int sampleRate = 44100;

        // Hard-coded for monaural, 16-bit-per-sample PCM
        public static void PlaySound(double frequency = 440, int duration = 250)
        {
            short[] shortBuffer = new short[sampleRate * duration / 1000];
            double angleIncrement = frequency / sampleRate;
            double angle = 0; // normalized 0 to 1
            for (int i = 0; i < shortBuffer.Length; i++)
            {
                // Define triangle wave
                double sample;
                // 0 to 1
                if (angle < 0.25)
                    sample = 4 * angle;
                // 1 to -1
                else if (angle < 0.75)
                    sample = 4 * (0.5 - angle);
                // -1 to 0
                else
                    sample = 4 * (angle - 1);
                shortBuffer[i] = (short)(32767 * sample);
                angle += angleIncrement;
                while (angle > 1)
                    angle -= 1;
            }
            byte[] byteBuffer = new byte[2 * shortBuffer.Length];
            Buffer.BlockCopy(shortBuffer, 0, byteBuffer, 0, byteBuffer.Length);
            DependencyService.Get<IPlatformSoundPlayer>().PlaySound(sampleRate, byteBuffer);
        }
}
Last edited by distante on Tue Jan 03, 2017 5:19 am, edited 1 time in total.

Post

Code: Select all

class SoundPlayer
{
const int sampleRate = 44100;

// Hard-coded for monaural, 16-bit-per-sample PCM
public static void PlaySound(double frequency = 440, int duration = 250)
{
	int len=sampleRate * duration / 1000;
	short[] shortBuffer = new short[len];
	double period=sampleRate/frequency;
	for (int i = 0; i < len; i++)
		shortBuffer = 32767*Math.sin(2*Math.PI*i/period); // outputs -32767..+32767
	byte[] byteBuffer = new byte[2 * shortBuffer.Length];
	Buffer.BlockCopy(shortBuffer, 0, byteBuffer, 0, len);
	DependencyService.Get<IPlatformSoundPlayer>().PlaySound(sampleRateBuffer);
}
}
P.s. I have not tested or even compiled it.
~stratum~

Post

Thanks a lot for your help@stratum!

There is an error in:

Code: Select all

shortBuffer = 32767*Math.sin(2*Math.PI*i/period); // outputs -32767..+32767
because the formula returns a double and tries to stored in an array without index, I added the index and casted the value to a short.

Code: Select all

shortBuffer[i] = (short)(32767 * Math.Sin(2 * Math.PI * i / period)); // outputs -32767..+32767
So the full code will be:

Code: Select all

    class SoundPlayer
    {
        const int sampleRate = 44100;

        // Hard-coded for monaural, 16-bit-per-sample PCM
        public static void PlaySound(double frequency = 440, int duration = 250)
        {
            int len = sampleRate * duration / 1000;
            short[] shortBuffer = new short[len];
            double period = sampleRate / frequency;
            for (int i = 0; i < len; i++)
                shortBuffer[i] = (short)(32767 * Math.Sin(2 * Math.PI * i / period)); // outputs -32767..+32767
            byte[] byteBuffer = new byte[2 * shortBuffer.Length];
            Buffer.BlockCopy(shortBuffer, 0, byteBuffer, 0, byteBuffer.Length);
            DependencyService.Get<IPlatformSoundPlayer>().PlaySound(sampleRate, byteBuffer);
        }
    }
Can you help me understand the code? I prefer not to just copy and paste but to be able to change it or reproduce it when needed.

What I understand:

1. Declare a integer variable (len) that holds the number total of samples, given by sampleRate*duration in milliseconds (so /1000).
2. Make an Array (shortBuffer ) that will be filled with each sample value. Using the variable in 1.
3. Calculate the Period given by sampleRate/frequency
4. Fill the shortBuffer array with the value of each sample.
Here starts my doubts.

- Can I replace the value of 32767 for something like?:

Code: Select all

( (2^bitDepth)/2 ) - 1 
- Why is it needed to convert shortBuffer to byteBuffer?
- And lastly, how can I control the amplitude?

Thank you, I hope I do not bother you too much with my questions.

Post

32767 is the maximum amplitude for short data type and you can replace it with any value between 0 and 32767. Usually a logarithmic scale is used if you are going to bind that value to a volume knob.
Why is it needed to convert shortBuffer to byteBuffer?
I don't know, I'm not familiar with that API.
~stratum~

Post

A gain control should just be like an analog unity gain, no?

1 = no gain.
0 = full attenuation.

And it should multiply each sample?

I would need to do my self the knob or slider for gain / volume control because the only volume control in those devices is the global one.

Post

It can be any way you like it's just a multiplier and yes for every sample, but human ear has a log-scaled sensitivity and that has nothing to do with programming or math or analog circuits, it's just the way people are.
~stratum~

Post

Yes, thank you. I did one knob where I converted from 0 - 1 to -INF 0dBFS. I should have the code somewhere.

Thank you a lot for your help and patience!!!

:ud:

Post

stratum wrote:32767 is the maximum amplitude for short data type and you can replace it with any value between 0 and 32767. Usually a logarithmic scale is used if you are going to bind that value to a volume knob.
Sorry I realized I did not have it clear about this. Does this have or not a relation with the bit depth? it is just a coincidence that it equals 2^16 ?

What I mean is, if the initial triangle code and last sine code the 16bits are "hard code", so, where? and how can I changed to 8 or 24? :scared:

thanks

Post

distante wrote:
stratum wrote:32767 is the maximum amplitude for short data type and you can replace it with any value between 0 and 32767. Usually a logarithmic scale is used if you are going to bind that value to a volume knob.
Sorry I realized I did not have it clear about this. Does this have or not a relation with the bit depth? it is just a coincidence that it equals 2^16 ?
Well it's not a coincidence and yes this relates to bit depth, but 2^16 = 65536 not 32767. In a c# short (16 bit int) the 65536 possible values are distributed from -32768 to 32767 - the slight imbalance due to the fact that we have to represent 0. (Edit: I see above you have ((2^bitDepth)/2)-1, so you already get this?)
distante wrote:What I mean is, if the initial triangle code and last sine code the 16bits are "hard code", so, where? and how can I changed to 8 or 24? :scared:
For 8 bit switch from short to sbyte. For 32 bit use int. 24 bits is a little tougher as one value has to be represented with 3 bytes. Once you have your triangle wave in the desired bit depth then I dunno, because:

Code: Select all

DependencyService.Get<IPlatformSoundPlayer>().PlaySound(sampleRate, byteBuffer);
There doesn't seem to be a parameter for bit depth here and I'm not a C# guy, but most sound cards will play back at 16, or 24 bits. Probably you just have to rescale back to 16 bits for playback.

From a practical point of view the easiest option is to generate your wave forms and do any processing in floating point. Then convert to 16 bit int before sending the output to the SoundPlayer. You just need to ensure that the floating point values remain withing +-1 before conversion. Depending what you do it could be best to clip to prevent overflows.

Post

Hi matt42, thanks for helping me out!
matt42 wrote:(Edit: I see above you have ((2^bitDepth)/2)-1, so you already get this?)
I deduced because there should be some place where the vertical steps of the sampling points are defined. I was trying to figure out how to put that number in code so I can change the bit depth and the formula still will work.
matt42 wrote:For 8 bit switch from short to sbyte. For 32 bit use int. 24 bits is a little tougher as one value has to be represented with 3 bytes.
But for a one instance only would it be ok if a just use int for 24 bits for it??
matt42 wrote: Once you have your triangle wave in the desired bit depth then I dunno, because:

Code: Select all

DependencyService.Get<IPlatformSoundPlayer>().PlaySound(sampleRate, byteBuffer);
There doesn't seem to be a parameter for bit depth here and I'm not a C# guy, but most sound cards will play back at 16, or 24 bits. Probably you just have to rescale back to 16 bits for playback.[/code]

I totally ignored that line of code! you are right, I took a look into that implementation and I see that internally (in this case for Android devices) it sets the PCM encoding to 16 bits, and it do not have an option for 24, just 32 or float.

Image

The iOS side looks more easy to tweak since it does not have enum for the PCM chunk size, bit or so, just a hardcode 16.

Code: Select all

class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        const int numChannels = 1;
        const int bitsPerSample = 16;

        public void PlaySound(int samplingRate, byte[] pcmData)
        {
            int numSamples = pcmData.Length / (bitsPerSample / 8);

            MemoryStream memoryStream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(memoryStream, Encoding.ASCII);

            // Construct WAVE header.
            writer.Write(new char[] { 'R', 'I', 'F', 'F' });
            writer.Write(36 + sizeof(short) * numSamples);
            writer.Write(new char[] { 'W', 'A', 'V', 'E' });
            writer.Write(new char[] { 'f', 'm', 't', ' ' });                // format chunk
            writer.Write(16);                                               // PCM chunk size
            writer.Write((short)1);                                         // PCM format flag
            writer.Write((short)numChannels);
            writer.Write(samplingRate);
            writer.Write(samplingRate * numChannels * bitsPerSample / 8);   // byte rate
            writer.Write((short)(numChannels * bitsPerSample / 8));         // block align
            writer.Write((short)bitsPerSample);
            writer.Write(new char[] { 'd', 'a', 't', 'a' });                // data chunk
            writer.Write(numSamples * numChannels * bitsPerSample / 8);

            // Write data as well.
            writer.Write(pcmData, 0, pcmData.Length);

            memoryStream.Seek(0, SeekOrigin.Begin);
            NSData data = NSData.FromStream(memoryStream);
            AVAudioPlayer audioPlayer = AVAudioPlayer.FromData(data);
            audioPlayer.Play();
        }

    }
Either way, not all devices generations support 24 bit (or 32), specially in Android so better be safe and stick to 16bits.
matt42 wrote: From a practical point of view the easiest option is to generate your wave forms and do any processing in floating point. Then convert to 16 bit int before sending the output to the SoundPlayer. You just need to ensure that the floating point values remain withing +-1 before conversion. Depending what you do it could be best to clip to prevent overflows.
Eventually I will want to play together two streams of audio, so I will need to check for check for clipping for sure. I will now try to work in the gain control and then learn how to pass from 32 bits for processing to 16 bits for playback :phones:

Thank you again matt42!

Post

distante wrote:But for a one instance only would it be ok if a just use int for 24 bits for it??
Sure, I guess the only reason to handle 24 bit directly is encoding/decoding to/from the soundcard or .wav files. Otherwise the data is best handled in float, or 32 bit int.
distante wrote:Either way, not all devices generations support 24 bit (or 32), specially in Android so better be safe and stick to 16bits
Yes. 16 bit should be more than enough quality for playback, it's CD quality after all and the noise floor is very low. Often processing will benifit from higher precision, but your free to process at whatever bit depth you like and convert to 16 bit later.

Post Reply

Return to “DSP and Plugin Development”