Android Pure Tone Generation Method

Keywords: Android

Because the project requires playback of pure tone in APP and earphone listening, understand that in Android system, there are two ways to play pure tone, one is to simulate a waveform in the program to satisfy the audio data of sinusoidal wave, the other is to prepare several audio files in advance and play directly.If you use pre-prepared audio, the effect is achievable (and possibly better), but you need to prepare audio files of all kinds of decibels and frequencies, which can be counted down to hundreds, so here we discuss the generation of pure tones by program simulation.

All sounds are made up of sinusoidal waves, but pure tones are sinusoidal waves of a fixed frequency.The main idea is that the sine function can be used to achieve the sine wave of the desired frequency, and then the AudioTrack class can be used to play sound.

1. Simple pure tone calculation

First, set the sinusoidal wave height to 127, because using an 8-bit sampling rate here, the eighth power of 2 should be 256, so the sinusoidal peak should be 127.

    /** Height of sinusoidal wave**/
    public static final int HEIGHT = 127;
    /** 2PI **/
    public static final double TWOPI = 2 * 3.1415;

    /** 
     * Generating sinusoidal waves 
     * @param wave 
     * @param waveLen Length of each sine wave segment 
     * @param length Total length
     * @return
     */
    public static byte[] sin(byte[] wave, int waveLen, int length) {
        for (int i = 0; i < length; i++) {
            wave[i] = (byte) (HEIGHT * (1 - Math.sin(TWOPI
                    * ((i % waveLen) * 1.00 / waveLen))));
        }
        return wave;
    }

Initialize AudioTrack first passes in a specified frequency, where the total sound frequency is set to 44100, then the length of a single sine wave (that is, 2PI) is calculated, and finally the length of a single sine wave is multiplied by the frequency to obtain the actual frequency of the sound wave, so as to avoid the final empty sound wave.The parameters are then passed into the sine method described above to obtain the sine wave.
AudioFormat.ENCODING_PCM_8BIT specifies that AudioTrack uses an 8-bit sampling rate.

/**
 * Set Frequency
 * @param rate
 */
public void start(int rate) {
    if (rate > 0) {
        Hz = rate;
        waveLen = 44100 / Hz;
        length = waveLen * Hz;
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                AudioFormat.CHANNEL_CONFIGURATION_STEREO, // CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_8BIT, length, AudioTrack.MODE_STREAM);
        //Generating sinusoidal waves
        wave = SinWave.sin(wave, waveLen, length);
        if (audioTrack != null) {
            audioTrack.play();
        }
    } else {
        return;
    }
}

AudioTrack.play() cannot play sound yet because there is no sound data in AudioTrack at this time.The following code feeds the data to really play the sound:

audioTrack.write(wave, 0, length);

This production method produces errors when the frequency is high. The higher the frequency, the greater the error.

2. Producing pure tones at different decibels and frequencies

Pure tones are produced at different frequencies and in different decibels.

  • Initialize AudioTrack
  • Play audioTrackz.play()
  • Feed Data AudioTrackZThread
  private int sampleRateInHz = 16000; // Sampling rate, MAX 44100
    private int mChannel = AudioFormat.CHANNEL_CONFIGURATION_MONO;// Channel: Mono
    private int mSampBit = AudioFormat.ENCODING_PCM_16BIT;// Sampling accuracy: 16bit
    private AudioTrackZThread audioTrackZThread;
    private boolean isRunning = false;
    private AudioTrack audioTrackz;

    public WaveOutZ() {
        int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, mChannel, mSampBit);
        audioTrackz = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, mChannel, mSampBit, bufferSize * 2, AudioTrack.MODE_STREAM);
        audioTrackz.setStereoVolume(1.0f, 1.0f);
        audioTrackz.play();
    }

    public void palyWaveZ(float rate, int db) {
        if (audioTrackZThread==null) {
            audioTrackZThread = new AudioTrackZThread();
            audioTrackZThread.start();
        }
        audioTrackZThread.setDb(rate,db);
    }

    public void colseWaveZ() {
        if (audioTrackz != null) {
            if (!AudioTrackZThread.interrupted()) {
                isRunning = false;
                audioTrackZThread=null;
            }
        }
    }

    class AudioTrackZThread extends Thread {
        private short m_iAmp = Short.MAX_VALUE;
        private short m_bitDateZ[] = new short[44100];
        private double x = 2.0 * Math.PI * 8250.0 / 44100.0;
        private int mM_bitDateZSize;

        public void setDb(float rate, int db) {
            x = 2.0 * Math.PI * rate / 44100.0;
            m_iAmp = (short) ((Math.pow(10.0, db / 20.0) * 1.414) * Short.MAX_VALUE);
            for (int i = 0; i < 44100; i++) {
                m_bitDateZ[i] = (short) (m_iAmp * Math.sin(x * i));
            }
            mM_bitDateZSize = m_bitDateZ.length;
        }

        @Override
        public void run() {
            isRunning = true;

            do {
                audioTrackz.write(m_bitDateZ, 0, mM_bitDateZSize); 
            } while (isRunning);
            super.run();
        }
    }

Posted by ejwf on Tue, 06 Aug 2019 09:28:11 -0700