Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Mixing multiple static waveforms into a single AudioTrack

I am making a class that takes an array of frequencies values (i.e. 440Hz, 880Hz, 1760Hz) and plays how they would sound combined into a single AudioTrack. I am not a sound programmer, so this is difficult for me to write myself, where I believe that it is a relatively easy problem to an experienced sound programmer. Here is some of the code below in the play method:

public void play() {
    // Get array of frequencies with their relative strengths
    double[][] soundData = getData();

    // TODO
    // Perform a calculation to fill an array with the mixed sound - then play it in an infinite loop
    // Need an AudioTrack that will play calculated loop
    // Track sample info
    int numOfSamples = DURATION * SAMPLE_RATE;
    double sample[] = new double[numOfSamples];
    byte sound[] = new byte[2 * numOfSamples];

    // fill out the array
    for (int i = 0; i < numOfSamples; ++i) {
            sample[i] = Math.sin(2 * Math.PI * i / (SAMPLE_RATE / 440));
    }

    int i = 0;
    for (double dVal : sample) {
        // scale to maximum amplitude
        final short val = (short) ((dVal * 32767));
        // in 16 bit wav PCM, first byte is the low order byte
        sound[i++] = (byte) (val & 0x00ff);
        sound[i++] = (byte) ((val & 0xff00) >>> 8);
    }

    // Obtain a minimum buffer size
    int minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);

    if (minBuffer > 0) {
        // Create an AudioTrack
        AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
                AudioFormat.ENCODING_PCM_16BIT, numOfSamples, AudioTrack.MODE_STATIC);

        // Write audio data to track
        track.write(sound, 0, sound.length);

        // Begin playing track
        track.play();
    }

    // Once everything has successfully begun, indicate such.
    isPlaying = true;
}

Right now, this code simply plays a concert A (440Hz). It was to test whether this code works. Now, I need to take a bunch a frequencies, perform some kind of calculation, and write the sample data.

like image 665
mas Avatar asked Oct 07 '22 00:10

mas


2 Answers

Ok, so the answer did turn out to be a simple summation loop. Here it is, just replace this for loop with the original one:

    // fill out the array
    for (int i = 0; i < numOfSamples; ++i) {
            double valueSum = 0;

            for (int j = 0; j < soundData.length; j++) {
                valueSum += Math.sin(2 * Math.PI * i / (SAMPLE_RATE / soundData[j][0]));
            }

            sample[i] = valueSum / soundData.length;
    }

Now, what this does is simply take all possible frequencies, add them together into the variable, valueSum, and then divide that by the length of the frequency array, soundData, which is a simple average. This produces a nice sine wave mixture of an arbitrarily long array of frequencies.

I haven't tested performance, but I do have this running in a thread, otherwise it could crash the UI. So, hope this helps - I am marking this as the answer.

like image 135
mas Avatar answered Oct 13 '22 02:10

mas


If you intend to mix multiple waveforms into one, you might prevent clipping in several ways.

Assuming sample[i] is a float representing the sum of all sounds.

HARD CLIPPING:

if (sample[i]> 1.0f)
{
    sample[i]= 1.0f;
}
if (sample[i]< -1.0f)
{
    sample[i]= -1.0f;
}

HEADROOM (y= 1.1x - 0.2x^3 for the curve, min and max cap slighty under 1.0f)

if (sample[i] <= -1.25f)
{
    sample[i] = -0.987654f;
}
else if (sample[i] >= 1.25f)
{
    sample[i] = 0.987654f;
}
else
{
    sample[i] = 1.1f * sample[i] - 0.2f * sample[i] * sample[i] * sample[i];
}

For a 3rd polynomial waveshapper (less smooth), replace the last line above with:

sample[i]= 1.1f * sample[i]- 0.2f * sample[i] * sample[i] * sample[i];
like image 32
Aladin Q Avatar answered Oct 13 '22 03:10

Aladin Q