I am writing a simple android compose app in which I want to do some basic synthesized sounds. My starting problem is getting a sine wave to repeat forever. For now, I am trying to get AudioTrack, although I believe a more modern approach would be to use the Oboe library.
Here is what I have:
fun generateSound(sampleRate: Int, timeInHz: Int = 44100, noCycles: Int = 1) : ShortArray {
// Not the most efficient function, but easy to read.
// Note that timeInHz is in Hz => 44100/44100 = 1000 ms
val mSingleCycle = ShortArray(timeInHz)
var mSound = ShortArray(timeInHz*noCycles)
// Single cycle
for (i in 0 until timeInHz) {
mSingleCycle[i] = (sin(2.0*PI * 440.0*i/sampleRate.toDouble())*Short.MAX_VALUE).toInt().toShort()
}
// Multiple cycles
for (i in 0 until timeInHz*noCycles) {
mSound[i] = mSingleCycle[i % timeInHz]
}
return mSound
}
suspend fun playSound(isRunning: Boolean) {
val sampleRate = 44100
val mSound = generateSound(sampleRate, 44100, 4)
// Set up AudioTrack
val mAudioTrack = AudioTrack(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build(),
AudioFormat.Builder()
// We use pcm_16bit => pass Short arrays
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(sampleRate)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(),
// mSoundSize is size of short array. A short is 2 bytes,
mSoundSize*2,
AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE,
)
if (mAudioTrack.playState == AudioTrack.PLAYSTATE_PLAYING) {
Log.d(LOG_TAG,"Was playing. Stopping and reloading.")
mAudioTrack.stop()
mAudioTrack.reloadStaticData()
}
val bytesWritten = mAudioTrack.write(mSound, 0, mSound.size, AudioTrack.WRITE_NON_BLOCKING)
mAudioTrack.setLoopPoints(0,mSound.size-1,-1)
mAudioTrack.play()
// The following is for debugging. If removed, don't need this function to suspend.
for (i in 0..49) {
Log.d(LOG_TAG, "playbackHead = ${mAudioTrack.playbackHeadPosition}")
delay(500)
}
}
And I call playSound
from a LaunchedEffect.
In summary, I am making AudioTrack play a (4 second) sound and using the setLoopPoints
method to make this into an infinite loop.
The problem I have is the sound plays for around 10 or 20 cycles but then cuts out entirely. Can someone explain why? What can I do to make it play ‘forever’?
A curious thing I notice is that if I insert the debugging code just above (with the delay etc) it does not cut out until all the delays have been done. It seems like the AudioTrack has a timeout when it hasn’t been used in a while – is this correct?
As a bonus question, how would accomplish the same idea but using the Oboe library (but maybe that constitutes another question!)?