blob: 72695db266aa94303ee73cb92dd431d8504acb2b [file] [log] [blame]
/*
* 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);
}
}
}