I am looking for a way to generate and play back sound in Kotlin/Java. I have been searching a lot and tried different solutions, but it's not really satisfactory.
I'm not looking for the Java Control class, that lets me add reverb to existing sounds, or the javax.sound.midi package that lets me do MIDI sequencing. Instead, I want to build up sound from scratch as a sound vector/List, through something like this:
fun createSinWaveBuffer(freq: Double, ms: Int, sampleRate: Int = 44100): ByteArray {
val samples = (ms * sampleRate / 1000)
val output = ByteArray(samples)
val period = sampleRate.toDouble() / freq
for (i in output.indices) {
val angle = 2.0 * Math.PI * i.toDouble() / period
output[i] = (Math.sin(angle) * 127f).toByte()
}
//output.forEach { println(it) }
return output
}
I then want to play back the sound, and have the actual output of the speakers match the input parameters sent to the function with regards to frequency, length etc. Of course, creating two sound vectors like this with different frequencies should summing them together or at least taking the average should result in two tones playing simultanously.
This is simple enough in matlab, if you have a vector y, like this
t=0:1/samplerate:duration;
y=sin(2*pi*freq*t);
Just do
sound(y,sampleRate)
Although there might not be as simple or clean solution in Java, I still feel like it should be possible to play custom sound.
After searching around a bit here and on other places, this is one of the cleanest solutions I'm trying now (even though it uses sun.audio, the other suggestions were way messier):
import sun.audio.AudioPlayer
import sun.audio.AudioDataStream
import sun.audio.AudioData
private fun playsound(sound: ByteArray) {
val audiodata = AudioData(sound)
val audioStream = AudioDataStream(audiodata)
AudioPlayer.player.start(audioStream)
}
but playsound(createSinWaveBuffer(440.0, 10000, 44100)) does not sound right in my speakers. It sounds choppy, it is not at 440 hz, it is not a pure sine wave and it is not ten seconds.
What am I missing?
First of all, do not use sun packages. Ever.
For the desktop, the way to go is to generate the data, acquire a SourceDataLine, open and start the line and then write your data to it. It's important that the line is suitable for the AudioFormat you have chosen to generate. In this case, 8 bits/sample and a sample rate of 44,100 Hz.
Here's a working example in Java, which I am sure you can easily translate to Kotlin.
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class ClipDemo {
private static byte[] createSinWaveBuffer(final float freq, final int ms, final float sampleRate) {
final int samples = (int)(ms * sampleRate / 1000);
final byte[] output = new byte[samples];
final float period = sampleRate / freq;
for (int i=0; i<samples; i++) {
final float angle = (float)(2f * Math.PI * i / period);
output[i] = (byte) (Math.sin(angle) * 127f);
}
return output;
}
public static void main(String[] args) throws LineUnavailableException {
final int rate = 44100;
final byte[] sineBuffer = createSinWaveBuffer(440, 5000, rate);
// describe the audio format you're using.
// because its byte-based, it's 8 bit/sample and signed
// if you use 2 bytes for one sample (CD quality), you need to pay more attention
// to endianess and data encoding in your byte buffer
final AudioFormat format = new AudioFormat(rate, 8, 1, true, true);
final SourceDataLine line = AudioSystem.getSourceDataLine(format);
// open the physical line, acquire system resources
line.open(format);
// start the line (... to your speaker)
line.start();
// write to the line (... to your speaker)
// this call blocks.
line.write(sineBuffer, 0, sineBuffer.length);
// cleanup, i.e. close the line again (left out in this example)
}
}
Related
I am managing audio capturing and playing using java sound API (targetDataLine and sourceDataLine). Now suppose in a conference environment, one participant's audio queue size got greater than jitter size (due to processing or network) and I want to fast forward the audio bytes I have of that participant to make it shorter than jitter size.
How can I fast forward the audio byte array of that participant?
I can't do it during playing as normally Player thread just deque 1 frame from every participant's queue and mix it for playing. The only way I can get that is if I deque more than 1 frame of that participant and mix(?) it for fast-forwarding before mixing it with other participants 1 dequeued frame for playing?
Thanks in advance for any kind of help or advice.
There are two ways to speed up the playback that I know of. In one case, the faster pace creates a rise in pitch. The coding for this is relatively easy. In the other case, pitch is kept constant, but it involves a technique of working with sound granules (granular synthesis), and is harder to explain.
For the situation where maintaining the same pitch is not a concern, the basic plan is as follows: instead of advancing by single frames, advance by a frame + a small increment. For example, let's say that advancing 1.1 frames over a course of 44000 frames is sufficient to catch you up. (That would also mean that the pitch increase would be about 1/10 of an octave.)
To advance a "fractional" frame, you first have to convert the bytes of the two bracketing frames to PCM. Then, use linear interpolation to get the intermediate value. Then convert that intermediate value back to bytes for the output line.
For example, if you are advancing from frame[0] to frame["1.1"] you will need to know the PCM for frame[1] and frame[2]. The intermediate value can be calculated using a weighted average:
value = PCM[1] * 9/10 + PCM[2] * 1/10
I think it might be good to make the amount by which you advance change gradually. Take a few dozen frames to ramp up the increment and allow time to ramp down again when returning to normal dequeuing. If you suddenly change the rate at which you are reading the audio data, it is possible to introduce a discontinuity that will be heard as a click.
I have used this basic plan for dynamic control of playback speed, but I haven't had the experience of employing it for the situation that you are describing. Regulating the variable speed could be tricky if you also are trying to enforce keeping the transitions smooth.
The basic idea for using granules involves obtaining contiguous PCM (I'm not clear what the optimum number of frames would be for voice, 1 to 50 millis is cited as commonly being used with this technique in synthesis), and giving it a volume envelope that allows you to mix sequential granules end-to-end (they must overlap).
I think the envelopes for the granules make use of a Hann function or Hamming window--but I'm not clear on the details, such as the overlapping placement of the granules so that they mix/transition smoothly. I've only dabbled, and I'm going to assume folks at Signal Processing will be the best bet for advice on how to code this.
I found a fantastic git repo (sonic library, mainly for audio player) which actually does exactly what I wanted with so much controls. I can input a whole .wav file or even chunks of audio byte arrays and after processing, we can get speed up play experience and so more. For real time processing I actually called this on every chunk of audio byte array.
I found another way/algo to detect whether a audio chunk/byte array is voice or not and after depending on it's result, I can simply ignore playing non voice packets which gives us around 1.5x speedup with less processing.
public class DTHVAD {
public static final int INITIAL_EMIN = 100;
public static final double INITIAL_DELTAJ = 1.0001;
private static boolean isFirstFrame;
private static double Emax;
private static double Emin;
private static int inactiveFrameCounter;
private static double Lamda; //
private static double DeltaJ;
static {
initDTH();
}
private static void initDTH() {
Emax = 0;
Emin = 0;
isFirstFrame = true;
Lamda = 0.950; // range is 0.950---0.999
DeltaJ = 1.0001;
}
public static boolean isAllSilence(short[] samples, int length) {
boolean r = true;
for (int l = 0; l < length; l += 80) {
if (!isSilence(samples, l, l+80)) {
r = false;
break;
}
}
return r;
}
public static boolean isSilence(short[] samples, int offset, int length) {
boolean isSilenceR = false;
long energy = energyRMSE(samples, offset, length);
// printf("en=%ld\n",energy);
if (isFirstFrame) {
Emax = energy;
Emin = INITIAL_EMIN;
isFirstFrame = false;
}
if (energy > Emax) {
Emax = energy;
}
if (energy < Emin) {
if ((int) energy == 0) {
Emin = INITIAL_EMIN;
} else {
Emin = energy;
}
DeltaJ = INITIAL_DELTAJ; // Resetting DeltaJ with initial value
} else {
DeltaJ = DeltaJ * 1.0001;
}
long thresshold = (long) ((1 - Lamda) * Emax + Lamda * Emin);
// printf("e=%ld,Emin=%f, Emax=%f, thres=%ld\n",energy,Emin,Emax,thresshold);
Lamda = (Emax - Emin) / Emax;
if (energy > thresshold) {
isSilenceR = false; // voice marking
} else {
isSilenceR = true; // noise marking
}
Emin = Emin * DeltaJ;
return isSilenceR;
}
private static long energyRMSE(short[] samples, int offset, int length) {
double cEnergy = 0;
float reversOfN = (float) 1 / length;
long step = 0;
for (int i = offset; i < length; i++) {
step = samples[i] * samples[i]; // x*x/N=
// printf("step=%ld cEng=%ld\n",step,cEnergy);
cEnergy += (long) ((float) step * reversOfN);// for length =80
// reverseOfN=0.0125
}
cEnergy = Math.pow(cEnergy, 0.5);
return (long) cEnergy;
}
}
Here I can convert my byte array to short array and detect whether it is voice or non voice by
frame.silence = DTHVAD.isSilence(encodeShortBuffer, 0, shortLen);
thanks for reading this
Well what I'm trying to do is to take a .wav file (only a short audio) and convert it to ints, and every one represent a tone of the audio...
If you're asking why I'm doing this, is because I'm doing an arduino project, and I want to make the arduino to play a song, and for doing that I need an int array where every int is a tone.
So I thought, "well if I program a little application to convert any .wav file to a txt where are stored the ints that represent the melody notes, I just need to copy this values to the arduino project code";
So after all this, maybe you're asking "What is your problem?";
I done the code and is "working", the only problem is that the txt only have "1024" in each line...
So it's obviously that I'm having a problem, no all the tones are 1024 -_-
package WaveToText;
import java.io.*;
/**
*
* #author Luis Miguel Mejía Suárez
* #project This porject is to convert a wav music files to a int array
* Which is going to be printed in a txt file to be used for an arduino
* #serial 1.0.1 (05/11/201)
*/
public final class Converter
{
/**
*
* #Class Here is where is going to be allowed all the code for the application
*
* #Param Text is an .txt file where is going to be stored the ints
* #Param MyFile is the input of the wav file to be converted
*/
PrintStream Text;
InputStream MyFile;
public Converter () throws FileNotFoundException, IOException
{
MyFile = new FileInputStream("C:\\Users\\luismiguel\\Dropbox\\ESTUDIO\\PROGRAMAS\\JAVA\\WavToText\\src\\WaveToText\\prueba.wav");
Text = new PrintStream(new File("Notes.txt"));
}
public void ConvertToTxt() throws IOException
{
BufferedInputStream in = new BufferedInputStream(MyFile);
int read;
byte[] buff = new byte[1024];
while ((read = in.read(buff)) > 0)
{
Text.println(read);
}
Text.close();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException{
// TODO code application logic here
Converter Exc = new Converter();
Exc.ConvertToTxt();
}
}
Wait wait wait..... a lot of things aren't right here....
You can't just read the bytes and send them to Arduino because as you are saying Arduino expects note numbers. The numbers in a Wav file are, first the "header" with audio info, and then the numbers representing discrete points in the signal (Waveform). If you want to get notes you need some algorithms for pitch detection or music transcription.
Pitch detection could work if your music is monophonic or close to monophonic. For full band songs it would be troublesome. So... I guess the "Arduino part" will play monophonic music, and you need to extract the fundamental frequency of the signal in particular time moment (This is called pitch detection and there are different ways to do it (autocorrelation, amdf, spectral analisys)). You must also keep the timing of the notes.
When you extract the frequencies there is a formula to convert frequency into integer number representing a note number on a piano. n=12(log2(f/440)) + 49 where n is the integer note number and f is the fundamental frequency of the note. Before calculating you should also quantize the frequencies you get from the pitch recognition algorithm to the closest (google for the exact note frequencies).
However I really suggest to do some more research. It would be really difficult to detect note in a music where you have few instruments playing, drums, singer, all together....
while ((read = in.read(buff)) > 0)
{
Text.println(read);
}
This bit of code reads 1024 bytes of data from in, then assigns the number of bytes read to read, which is 1024, until the end of file. You then print read to your text file.
You probably wanted to print buff to your text file, but that is going to write 1024 bytes, rather than the 1024 ints you want.
You will need to create a for loop to print the individual bytes as ints.
while ((read = in.read(buff)) > 0)
{
for (int i = 0; i < buff.length; i++)
Text.print((int)buff[i]);
}
I'm working on a voice recording app. In it, I have a Seekbar to change the input voice gain.
I couldn't find any way to adjust the input voice gain.
I am using the AudioRecord class to record voice.
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLERATE, RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING, bufferSize);
recorder.startRecording();
I've seen an app in the Google Play Store using this functionality.
As I understand you don't want any automatic adjustments, only manual from the UI. There is no built-in functionality for this in Android, instead you have to modify your data manually.
Suppose you use read (short[] audioData, int offsetInShorts, int sizeInShorts) for reading the stream. So you should just do something like this:
float gain = getGain(); // taken from the UI control, perhaps in range from 0.0 to 2.0
int numRead = read(audioData, 0, SIZE);
if (numRead > 0) {
for (int i = 0; i < numRead; ++i) {
audioData[i] = (short)Math.min((int)(audioData[i] * gain), (int)Short.MAX_VALUE);
}
}
Math.min is used to prevent overflow if gain is greater than 1.
Dynamic microphone sensitivity is not a thing that the hardware or operating system is capable of as it requires analysis on the recorded sound. You should implement your own algorithm to analyze the recorded sound and adjust (amplify or decrease) the sound level on your own.
You can start by analyzing last few seconds and find a multiplier that is going to "balance" the average amplitude. The multiplier must be inversely proportional to the average amplitude to balance it.
PS: If you still want to do it, the mic levels are accessible when you have a root access, but I am still not sure -and don't think it is possible- if you can change the settings while recording. Hint: "/system/etc/snd_soc_msm" file.
Solution by OP.
I have done it using
final int USHORT_MASK = (1 << 16) - 1;
final ByteBuffer buf = ByteBuffer.wrap(data).order(
ByteOrder.LITTLE_ENDIAN);
final ByteBuffer newBuf = ByteBuffer.allocate(
data.length).order(ByteOrder.LITTLE_ENDIAN);
int sample;
while (buf.hasRemaining()) {
sample = (int) buf.getShort() & USHORT_MASK;
sample *= db_value_global;
newBuf.putShort((short) (sample & USHORT_MASK));
}
data = newBuf.array();
os.write(data);
This is working implementation based on ByteBuffer for 16bit audio. It's important to clamp the increased value from both sides since short is signed. It's also important to set the native byte order to ByteBuffer since audioRecord.read() returns native endian bytes.
You may also want to perform audioRecord.read() and following code in a loop, calling data.clear() after each iteration.
double gain = 2.0;
ByteBuffer data = ByteBuffer.allocateDirect(SAMPLES_PER_FRAME).order(ByteOrder.nativeOrder());
int audioInputLengthBytes = audioRecord.read(data, SAMPLES_PER_FRAME);
ShortBuffer shortBuffer = data.asShortBuffer();
for (int i = 0; i < audioInputLengthBytes / 2; i++) { // /2 because we need the length in shorts
short s = shortBuffer.get(i);
int increased = (int) (s * gain);
s = (short) Math.min(Math.max(increased, Short.MIN_VALUE), Short.MAX_VALUE);
shortBuffer.put(i, s);
}
There have been other questions and answers on this site suggesting that, to create an echo or delay effect, you need only add one audio sample with a stored audio sample from the past. As such, I have the following Java class:
public class DelayAMod extends AudioMod {
private int delay = 500;
private float decay = 0.1f;
private boolean feedback = false;
private int delaySamples;
private short[] samples;
private int rrPointer;
#Override
public void init() {
this.setDelay(this.delay);
this.samples = new short[44100];
this.rrPointer = 0;
}
public void setDecay(final float decay) {
this.decay = Math.max(0.0f, Math.min(decay, 0.99f));
}
public void setDelay(final int msDelay) {
this.delay = msDelay;
this.delaySamples = 44100 / (1000/this.delay);
System.out.println("Delay samples:"+this.delaySamples);
}
#Override
public short process(short sample) {
System.out.println("Got:"+sample);
if (this.feedback) {
//Delay should feed back into the loop:
sample = (this.samples[this.rrPointer] = this.apply(sample));
} else {
//No feedback - store base data, then add echo:
this.samples[this.rrPointer] = sample;
sample = this.apply(sample);
}
++this.rrPointer;
if (this.rrPointer >= this.samples.length) {
this.rrPointer = 0;
}
System.out.println("Returning:"+sample);
return sample;
}
private short apply(short sample) {
int loc = this.rrPointer - this.delaySamples;
if (loc < 0) {
loc += this.samples.length;
}
System.out.println("Found:"+this.samples[loc]+" at "+loc);
System.out.println("Adding:"+(this.samples[loc] * this.decay));
return (short)Math.max(Short.MIN_VALUE, Math.min(sample + (int)(this.samples[loc] * this.decay), (int)Short.MAX_VALUE));
}
}
It accepts one 16-bit sample at a time from an input stream, finds an earlier sample, and adds them together accordingly. However, the output is just horrible noisy static, especially when the decay is raised to a level that would actually cause any appreciable result. Reducing the decay to 0.01 barely allows the original audio to come through, but there's certainly no echo at that point.
Basic troubleshooting facts:
The audio stream sounds fine if this processing is skipped.
The audio stream sounds fine if decay is 0 (nothing to add).
The stored samples are indeed stored and accessed in the proper order and the proper locations.
The stored samples are being decayed and added to the input samples properly.
All numbers from the call of process() to return sample are precisely what I would expect from this algorithm, and remain so even outside this class.
The problem seems to arise from simply adding signed shorts together, and the resulting waveform is an absolute catastrophe. I've seen this specific method implemented in a variety of places - C#, C++, even on microcontrollers - so why is it failing so hard here?
EDIT: It seems I've been going about this entirely wrong. I don't know if it's FFmpeg/avconv, or some other factor, but I am not working with a normal PCM signal here. Through graphing of the waveform, as well as a failed attempt at a tone generator and the resulting analysis, I have determined that this is some version of differential pulse-code modulation; pitch is determined by change from one sample to the next, and halving the intended "volume" multiplier on a pure sine wave actually lowers the pitch and leaves volume the same. (Messing with the volume multiplier on a non-sine sequence creates the same static as this echo algorithm.) As this and other DSP algorithms are intended to work on linear pulse-code modulation, I'm going to need some way to get the proper audio stream first.
It should definitely work unless you have significant clipping.
For example, this is a text file with two columns. The leftmost column is the 16 bit input. The second column is the sum of the first and a version delayed by 4001 samples. The sample rate is 22KHz.
Each sample in the second column is the result of summing x[k] and x[k-4001] (e.g. y[5000] = x[5000] + x[999] = -13840 + 9181 = -4659) You can clearly hear the echo signal when playing the samples in the second column.
Try this signal with your code and see if you get identical results.
I'm playing around with looping a sound snippet at succesively faster speeds, for the fun of it, and have stumbled upon this question, which solves it nicely, I guess. It does get kind of buggy when you go to high speeds, because it drops out anything in between and just takes one byte every so often. So I wanted to change it to take the average of all bytes in a stretch within the array. Problem is, bytes don't lend themselves to be divided by ints, and I'm a bit stupid when it comes to changing from bytes to ints. My solution was to do this (again complementing from the mentioned question.
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import javax.sound.sampled.*;
import java.net.URL;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.Date;
import java.io.File;
class AcceleratePlayback {
public static void main(String[] args) throws Exception {
int playBackSpeed = 3;
File soundFile;
if (args.length>0) {
try {
playBackSpeed = Integer.parseInt(args[0]);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
System.out.println("Playback Rate: " + playBackSpeed);
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
soundFile = chooser.getSelectedFile();
System.out.println("FILE: " + soundFile);
AudioInputStream ais = AudioSystem.getAudioInputStream(soundFile);
AudioFormat af = ais.getFormat();
int frameSize = af.getFrameSize();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[2^16];
int read = 1;
while( read>-1 ) {
read = ais.read(b);
if (read>0) {
baos.write(b, 0, read);
}
}
System.out.println("End entire: \t" + new Date());
//This is the important bit
byte[] b1 = baos.toByteArray();
byte[] b2 = new byte[b1.length/playBackSpeed];
for (int ii=0; ii<b2.length/frameSize; ii++) {
for (int jj=0; jj<frameSize; jj++) {
int b3=0;
for (int kk = 0; kk < playBackSpeed; kk++){
b3 = b3+(int)b1[(ii*frameSize*playBackSpeed)+jj+kk];
}
b3 = b3/playBackSpeed;
b2[(ii*frameSize)+jj] = (byte)b3;
}
}
//ends here
System.out.println("End sub-sample: \t" + new Date());
ByteArrayInputStream bais = new ByteArrayInputStream(b2);
AudioInputStream aisAccelerated = new AudioInputStream(bais, af, b2.length);
Clip clip = AudioSystem.getClip();
clip.open(aisAccelerated);
clip.loop(2*playBackSpeed);
clip.start();
JOptionPane.showMessageDialog(null, "Exit?");
}
}
I do realize this is probably the wrong way to do it, but I'm not sure what else I could do, any thoughts??
Best, Alex.
Since my earlier "solution" was cited, I'll lay out what I use for variable speed playback in a more detail. I confess, I don't totally understand the approach used in this question, and so am not going to make an attempt to improve upon the code. I'm risking "not answering the question" in doing this, but perhaps the increased detail about using linear interpolation will show that this can be a sufficient way to make the higher-speed loops you are going for.
I'm NOT claiming the approach that I came up with is the best. I'm not a sound engineer. But it seems to work. (Am always grateful for any suggested improvements.)
This is for a sound library I've made for my own games. It is based on the idea of a Java Clip, but with some extra capabilities. In my library, there's a place to store data, and another couple structures for playback, one for concurrent single plays, and another for looping. Both allow vari-speed, even to the extent of playing the sound backwards.
To load and hold the "clip" data, I just use a single int[], called 'clipData', but I use it for both L & R, so odd & even ints are for either ear.
Loading 'clipData' initially:
while((bytesRead = ais.read(buffer, 0, 1024)) != -1)
{
bufferIdx = 0;
for (int i = 0, n = bytesRead / 2; i < n; i ++)
{
clipData[(int)clipIdx++] =
( buffer[(int)bufferIdx++] & 0xff )
| ( buffer[(int)bufferIdx++] << 8 ) ;
}
}
For playback, the object that holds this data array has two get() methods. The first is for normal speed. An int is used to index into the clipData array (maybe should be a 'long', for larger audio files!):
public double[] get(int idx) throws ArrayIndexOutOfBoundsException
{
idx *= 2; // assumed: stereo data
double[] audioVals = new double[2];
audioVals[0] = clipData[idx++];
audioVals[1] = clipData[idx];
return audioVals;
}
Maybe returning a float array is acceptable, in place of the double[]?
Here is the enhanced get() method for variable speed. It uses linear interpolation to account for the fractional part of the double used as an index into the clipData:
public double[] get(double idx) throws ArrayIndexOutOfBoundsException
{
int intPart = (int)idx * 2;
double fractionalPart = idx * 2 - intPart;
int valR1 = clipData[intPart++];
int valL1 = clipData[intPart++];
int valR2 = clipData[intPart++];
int valL2 = clipData[intPart];
double[] audioVals = new double[2];
audioVals[0] = (valR1 * (1 - fractionalPart)
+ valR2 * fractionalPart);
audioVals[1] = (valL1 * (1 - fractionalPart)
+ valL2 * fractionalPart);
return audioVals;
}
The while(playing) loop (for loading data into the playback SourceDataLine) has a variable associated with the clipData which I call "cursor" that iterates through the sound data array. For normal playback, 'cursor' is incremented by 1, and tested to make sure it goes back to zero when it reaches the end of the clipData.
You could write something like: audioData = clipData.get(cursor++) to read successive frames of the data.
For varispeed, the above would be more like this:
audioData = clipData.get(cursor += speedIncrement);
'speedIncrement' is a double. If it is set to 2.0, the playback is twice as fast. If it is set to 0.5, it is half as fast. If you put in the right checks you can even make speedIncrement equal a negative value for reverse playback.
This works as long as the speed doesn't go above the Nyquist value (at least theoretically). And again, you have to test to make sure 'cursor' hasn't gone off the edge of the clipData, but restarts at the appropriate spot at the other end of the sound data array.
Hope this helps!
Another note: you may want to rewrite the above get() methods to send a buffer's worth of reads instead of single frames. I'm currently experimenting with doing things on a per-frame basis. I think it makes the code a little easier to understand, and helps with per-frame processing and responsiveness, but it surely slows things down.