| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.hyphonate.megaaudio.player.sources; |
| |
| import org.hyphonate.megaaudio.player.AudioSource; |
| |
| /** |
| * An AudioFiller implementation for feeding data from a PCMFLOAT wavetable. |
| * We do simple, linear interpolation for inter-table values. |
| */ |
| public class WaveTableSource extends AudioSource { |
| @SuppressWarnings("unused") private static String TAG = WaveTableSource.class.getSimpleName(); |
| |
| /** The samples defining one cycle of the waveform to play */ |
| protected float[] mWaveTbl; |
| /** The number of samples in the wave table. Note that the wave table is presumed to contain |
| * an "extra" sample (a copy of the 1st sample) in order to simplify the interpolation |
| * calculation. Thus, this value will be 1 less than the length of mWaveTbl. |
| */ |
| protected int mNumWaveTblSamples; |
| /** The phase (offset within the wave table) of the next output sample. |
| * Note that this may (will) be a fractional value. Range 0.0 -> mNumWaveTblSamples. |
| */ |
| protected float mSrcPhase; |
| /** The sample rate at which playback occurs */ |
| protected float mSampleRate = 48000; // This seems likely, but can be changed |
| /** The frequency of the generated audio signal */ |
| protected float mFreq = 1000; // Some reasonable default frequency |
| /** The "Nominal" frequency of the wavetable. i.e., the frequency that would be generated if |
| * each sample in the wave table was sent in turn to the output at the specified sample rate. |
| */ |
| protected float mFN; |
| /** 1 / mFN. Calculated when mFN is set to avoid a division on each call to fill() */ |
| protected float mFNInverse; |
| |
| /** |
| * Constructor. |
| */ |
| public WaveTableSource() { |
| } |
| |
| /** |
| * Calculates the "Nominal" frequency of the wave table. |
| */ |
| private void calcFN() { |
| mFN = mSampleRate / (float)mNumWaveTblSamples; |
| mFNInverse = 1.0f / mFN; |
| } |
| |
| /** |
| * Sets up to play samples from the provided wave table. |
| * @param waveTbl Contains the samples defining a single cycle of the desired waveform. |
| * This wave table contains a redundant sample in the last slot (== first slot) |
| * to make the interpolation calculation simpler, so the logical length of |
| * the wave table is one less than the length of the array. |
| */ |
| public void setWaveTable(float[] waveTbl) { |
| mWaveTbl = waveTbl; |
| mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0; |
| |
| calcFN(); |
| } |
| |
| /** |
| * Sets the playback sample rate for which samples will be generated. |
| * @param sampleRate |
| */ |
| public void setSampleRate(float sampleRate) { |
| mSampleRate = sampleRate; |
| calcFN(); |
| } |
| |
| /** |
| * Set the frequency of the output signal. |
| * @param freq Signal frequency in Hz. |
| */ |
| public void setFreq(float freq) { |
| mFreq = freq; |
| } |
| |
| /** |
| * Resets the playback position to the 1st sample. |
| */ |
| @Override |
| public void reset() { |
| mSrcPhase = 0.0f; |
| } |
| |
| /** |
| * Fills the specified buffer with values generated from the wave table which will playback |
| * at the specified frequency. |
| * |
| * @param buffer The buffer to be filled. |
| * @param numFrames The number of frames of audio to provide. |
| * @param numChans The number of channels (in the buffer) required by the player. |
| * @return The number of samples generated. Since we are generating a continuous periodic |
| * signal, this will always be <code>numFrames</code>. |
| */ |
| @Override |
| public int pull(float[] buffer, int numFrames, int numChans) { |
| final float phaseIncr = mFreq * mFNInverse; |
| int outIndex = 0; |
| for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { |
| // 'mod' back into the waveTable |
| while (mSrcPhase >= (float)mNumWaveTblSamples) { |
| mSrcPhase -= (float)mNumWaveTblSamples; |
| } |
| |
| // linear-interpolate |
| int srcIndex = (int)mSrcPhase; |
| float delta0 = mSrcPhase - (float)srcIndex; |
| float delta1 = 1.0f - delta0; |
| float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1)); |
| |
| // Put the same value in all channels. |
| for (int chanIndex = 0; chanIndex < numChans; chanIndex++) { |
| buffer[outIndex++] = value; |
| } |
| |
| mSrcPhase += phaseIncr; |
| } |
| |
| return numFrames; |
| } |
| |
| /* |
| * Standard wavetable generators |
| */ |
| static public void genSinWave(float[] buffer) { |
| int size = buffer.length; |
| float incr = ((float)Math.PI * 2.0f) / (float)(size - 1); |
| for(int index = 0; index < size; index++) { |
| buffer[index] = (float)Math.sin(index * incr); |
| } |
| } |
| } |