B4A Library AudioTrack

This is a wrapper library for the AudioTrack Object, simlar to the AudioRecord object, it's more complex to use than the MediaPlayer but has some functionality that is useful for audio processing.

I haven't had time to test this one thoroughly, I thought you may like to play with it as well. Let me know if you find anything that doesn't work.

The attachment is quite big as it has an example wav file that is loaded via a random access file and played via Audiotrack, just to give an idea.

Edit: V1.01 fixed a typo and Separate file for libs only
17/1/12 Added swt.zip, quick and dirty sine wave generator as an example.
Added swt1-1.zip a few more refinements.
Added swt1-2.zip better tuning
Updated lib files to 1.2 - needed for swt1-2
13/2/12 Added and reformatted Constants - audiotrack1.3
19/3/12 Updated SWT to reach 19khz and added input fields (touch the display labels)

15/1/21
  • Added attest2
  • Updated example with a Stream Static and looping example.
  • Replaced threading with Wait For and Sleep in the streaming example. For heavy usage, it may still be necessary to use threading, so I'll leave the original example here as well.
 

Attachments

  • attest.zip
    305.8 KB · Views: 1,699
  • AudioTrack1.3.zip
    7.8 KB · Views: 1,856
  • swt1-3.zip
    8.5 KB · Views: 1,491
  • attest2.zip
    298.4 KB · Views: 512
Last edited:

swabygw

Active Member
Licensed User
Longtime User
Thanks for the AudioTracks library and the SineWave example. I'm using the SineWave to play a tone at varying frequencies. Do you have an example of how to play two or three tones, of different frequencies, at the same time? (like playing a chord) Btw, I've done this in .NET as such:

SharedSub Beeper(ByVal Amplitude AsInteger, ByVal Duration AsInteger, ByVal Sync AsBoolean, ByVal Frequencies() AsInteger)
Dim A AsDouble = ((Amplitude * 2 ^ 15) / 1000) - 1
Dim Samples AsInteger = (44100 * Duration) / 1000
Dim Bytes AsInteger = Samples * 4
Dim Hdr() AsInteger = {&H46464952, 36 + Bytes, &H45564157,
&H20746D66, 16, &H20001, 44100,
176400, &H100004, &H61746164, Bytes}
Using MS AsNew IO.MemoryStream(36 + Bytes)
Using BW AsNew IO.BinaryWriter(MS)
For I AsInteger = 0 To Hdr.Length - 1
BW.Write(Hdr(I))
Next
For T AsInteger = 0 To Samples - 1
Dim Sample AsShort = 0
For y AsInteger = 0 To Frequencies.Length - 1
Sample += CShort((A / Frequencies.Length) * Math.Sin((2 * Math.PI * Frequencies(y) * T) / 44100))
Next
BW.Write(Sample)
BW.Write(Sample)
Next
BW.Flush()
MS.Seek(0, IO.SeekOrigin.Begin)
Using SP AsNew System.Media.SoundPlayer(MS)
If Sync = TrueThen SP.PlaySync() Else SP.Play()
EndUsing
EndUsing
EndUsing
EndSub
 
Last edited:

stevel05

Expert
Licensed User
Longtime User

swabygw

Active Member
Licensed User
Longtime User
Yes, I went down the path of trying to sum two sine waves together, each with half the amplitude. The problem was that the CycleLength is a different length for each frequency (in the .NET example above, the sample length is always the same length). I think it would work if the CycleLength were a fixed length, such as (SampleRate*Duration)/1000, but I couldn't figure out a way to get it to work that way. Here's what I attempted:

Dim r As Int = (22050 * 100) / 1000
Amplitude = 32767 '((50 * Power(2,15)) / 1000) - 1
Dim Samples(r) As Short
For T = 0 To Samples.Length - 1
Dim Sample As Short = 0
For y = 0 To fr.Length - 1
Dim v1 As Double = Amplitude / fr.Length
Dim v2 As Double = Sin((2 * cPI * 494 * T)/44100)
Sample = Sample + ( (Amplitude/fr.Length) * Sin((2 * cPI * 494 * T)/44100) )
Next
Samples(T) = Sample
Next
audioTrack1.WriteShort(Samples,0,Samples.Length)
 

stevel05

Expert
Licensed User
Longtime User
Yes, all the buffers will need to be the same length, it was done that way for the example because the sound was looped and knowing the exact cycle size meant that there would be no click artefacts that may have been introduced due to zero crossing by looping into the middle of a cycle.

Looping is probably still the way to go, even though the speed of android devices has improved significantly since the example was written, it could still cause issues when calculating multiple waves if the buffers aren't filled quickly enough to stream. You'd just need to continue to fill the buffers of the shorter sample(s) to the length of the longest one.

I'm not going to to have much time to look at it much this weekend, but let me know how you get on and I'll try to help next week if you need it.
 

suha

Member
Licensed User
Longtime User
Thanks for the library. I have downloaded the swt example and got it working (with minor modifications). I am nnable to control the volume (Gain_SB has no effect) in 8 bit encoding.

I'll appreciate your help.
 

Attachments

  • swt_suha.zip
    9.4 KB · Views: 341

stevel05

Expert
Licensed User
Longtime User
Hi Suha,

I haven't looked at this for a long time, but it was a quick and dirty example and you've found an error in it. The 8bit samples should be unsigned. To rectify we need to add the HalfMaxAmp value to yy when assigning to CycleBy(X) so line 188 becomes:

B4X:
CycleBy(X)=yy + HalfMaxAmp

You can hear by the tone that it's a better sin wave.

You will probably need to remove it again to draw the wav correctly.

In audio recording, gain does not equal volume. To get the best quality recording, you want as much gain in the signal as possible. It reduces the overall noise in the recordings and gives a better signal to noise ratio. Volume is on the output side and controlled by the playback routines.

Hope it helps.
 

suha

Member
Licensed User
Longtime User
Yes, it helped. Thank you very much.

Following snippet solves the reverse transformation :

If CycleBy(i)<0 Then
Y=CycleBy(i)*DispFactor8+DispZeroPoint*2
Else
Y=CycleBy(i)*DispFactor8+DispZeroPoint*0
End If

I doubt there's an equivalent linear transformation but it's not

yy = CycleBy(i) - HalfMaxAmp
 

stevel05

Expert
Licensed User
Longtime User
Thanks for posting that.

When dealing with audio volume and gain, to be accurate, we should be dealing with Logarithmic values as a 50% reduction in gain sounds like a 75% reduction. but as I said, it was a quick and dirty example.
 

suha

Member
Licensed User
Longtime User
Linear inversion is the same : (requires type casting)

Dim z As Byte
z = CycleBy(i) + 128
Y = z * DispFactor8 + DispZeroPoint

It is not :

Y=(CycleBy(i)+128) * DispFactor8 + DispZeroPoint

Any plans for implementing floating point encoding ?

Thanks.
 

stevel05

Expert
Licensed User
Longtime User
Floats are only supported from Lollipop and later.

You can access the underlying AudioTrack object via the reflection library, then access any additional methods as defined in the documentation : http://developer.android.com/reference/android/media/AudioTrack.html using JavaObject.

B4X:
Sub getATJO As JavaObject
    Dim R As Reflector
    R.Target = at
    Return R.GetField("at")
End Sub

Will get the object, Assign the result to a Global JavaObject Variable then you can add floats something like this:

B4X:
atJO.RunMethod("write",Array(floatArray,0,floatArray.Length))
 

swabygw

Active Member
Licensed User
Longtime User
I think that I may have stumbled upon the answer to whether or not this can be used to play a chord. The following code, while grossly simplistic and inefficient, does, indeed, play multiple tones at once (A minor, A-C-E)...but it sounds bad....well, on my phone it sounds bad. (sorry, I forgot how to mark code)

Dim at As AudioTrack
If at.IsInitialized Then at.Release
at.Initialize(at.Stream_Music,44100,at.CH_CONF_STEREO,at.AF_PCM_16,at.GetMinBuffersize(44100,at.CH_CONF_STEREO,at.AF_PCM_16),at.Mode_Stream)
at.Play
at.SetStereoVolume(at.GetMaxVolume,at.GetMaxVolume)

Dim count As Int = (44100.0 * 2.0 * (500 / 1000.0))
Dim samples(count) As Short
For b = 0 To count - 1 Step 2
Dim sampleDbl As Double = Sin(2 * cPI * b / (44100.0 / 440.0))
Dim sampleDbl2 As Double = Sin(2 * cPI * b / (44100.0 / 523.3))
Dim sampleDbl3 As Double = Sin(2 * cPI * b / (44100.0 / 659.3))
Dim divisor As Double = 32767/3
Dim sample As Short = (sampleDbl * divisor) + (sampleDbl2 * divisor) + (sampleDbl3 * divisor)
samples(b + 0) = sample
samples(b + 1) = sample​
Next
at.WriteShort(samples,0,samples.Length)​
 

ChrisKrohne

Member
Licensed User
Longtime User
Hi Steve,
Thanks for the library. Did you notice that the function Audiotrack_MarkerReached could lead to different results on different devices? I use the function to trigger a metrenom for knowing tempos on wave files. For Ex. a 120BMP File 16bit Stereo 44.1KHZ takes 44100 Sample per beat (60/120*44100*2) On the tablet works fine, on a Samsung S4 it is half the time, 44100 Samples->2 Beats. Any ideas?
Thanks
 

BarryW

Active Member
Licensed User
Longtime User
This is a wrapper library for the AudioTrack Object, simlar to the AudioRecord object, it's more complex to use than the MediaPlayer but has some functionality that is useful for audio processing.

I haven't had time to test this one thoroughly, I thought you may like to play with it as well. Let me know if you find anything that doesn't work.

The attachment is quite big as it has an example wav file that is loaded via a random access file and played via Audiotrack, just to give an idea.

Edit: V1.01 fixed a typo and Separate file for libs only
17/1/12 Added swt.zip, quick and dirty sine wave generator as an example.
Added swt1-1.zip a few more refinements.
Added swt1-2.zip better tuning
Updated lib files to 1.2 - needed for swt1-2
13/2/12 Added and reformatted Constants - audiotrack1.3
19/3/12 Updated SWT to reach 19khz and added input fields (touch the display labels)

I like your library but how to play a true mp3 file then change its pitch on runtime. tnx
 

watesoft

Active Member
Licensed User
Longtime User
This is a wrapper library for the AudioTrack Object, simlar to the AudioRecord object, it's more complex to use than the MediaPlayer but has some functionality that is useful for audio processing.

I haven't had time to test this one thoroughly, I thought you may like to play with it as well. Let me know if you find anything that doesn't work.

The attachment is quite big as it has an example wav file that is loaded via a random access file and played via Audiotrack, just to give an idea.

Edit: V1.01 fixed a typo and Separate file for libs only
17/1/12 Added swt.zip, quick and dirty sine wave generator as an example.
Added swt1-1.zip a few more refinements.
Added swt1-2.zip better tuning
Updated lib files to 1.2 - needed for swt1-2
13/2/12 Added and reformatted Constants - audiotrack1.3
19/3/12 Updated SWT to reach 19khz and added input fields (touch the display labels)

15/1/21
  • Added attest2
  • Updated example with a Stream Static and looping example.
  • Replaced threading with Wait For and Sleep in the streaming example. For heavy usage, it may still be necessary to use threading, so I'll leave the original example her as well.
Hi. stevel, I sent you a private message, hoping for your help,thank you in advance.
 

Roger Taylor

Member
Licensed User
Longtime User
Hello, I'm trying to output single 8-bit samples at my own rate which can change on the fly. To make a long story short, I've written a TRS-80 CoCo emulator in B4A and am now trying to get the 6-bit DAC to output sound on the Android device. It's sort of working except the output is choppy as if the at.Play command is resetting the volume or final output, and I can hear periodic cracking as if the output is not continuous.

All I'm doing for now is CycleBy(0)=Sample, at.WriteByte(CycleBy,0,1), at.Play
That is, I want the one sample to stay on the output channel until I write another.

Here is the APK of my last build of a the emulator running a retro game cartridge. The sound is "working" but is slowing down the emulator. I need to try running at.Play in a thread that only plays when there's a new sample.

Unfortunately, google drive likes to scare the crap out of people when it's an APK.

 
Last edited:

Roger Taylor

Member
Licensed User
Longtime User
at.write is a blocking function, so it should run in another thread.

When I use at.WriteByte within another thread which does a forever loop with a sleep(0) added, I can see my debug messages before and after the command showing that it's returning from the call, but no bytes appear to be written or streamed, resulting in no audio.

I'm learning how the scheme works and what's required, though. I can call at.Play once at the start of my program and then just at.Write samples... however, the writing has to be constant and fast. I just insert my samples into the stream by storing them in buffer(0) as a single byte from my DAC when the CPU writes to it, as long as I have at.WriteByte(buffer, 0, 1) running in a tight loop somewhere. I also had to set the sample rate as such: at.SetPlaybackRate(at.GetNativeOutputSampleRate(at.stream_music)) to prevent the clicking and interruptions on some devices. I can multiply the rate by 3 on my Samsung Galaxy S7 TAB to get a faster write but doing x3 on my phone distorts the audio terribly. Anyway, those are some things I've noticed while experimenting.

Can we do non-blocking? Is the source code for the B4A library available?
 
Last edited:

Roger Taylor

Member
Licensed User
Longtime User
The Android AudioTrack documentation appears to confirm my theory that the last sample isn't held. A popping or interruption won't be heard if the last sample is held, also resulting in silence. If the playback thread is writing a 0x80 or 0x8000 for 8-bit and 16-bit respectively, you'd hear interruptions if your at.Writes are slower than the stream which has me shaking my head if this is the case.

3.3 Playback Thread
A playback thread periodically writes audio data to an output
sink. If any audio data is not available for playback, the playback
thread pads the buffer and writes silent frames. This is done to
keep the output device active and reduce latency for playback when
 

OliverA

Expert
Licensed User
Longtime User

stevel05

Expert
Licensed User
Longtime User
Is the source code for the B4A library available?
This library is 10 years old, I can't immediately find the source code for it (probably have it backed up somewhere). Feel free to inspect it with jd-gui or similar.

I don't think you should be using the sleep command in a thread, it will not do anything useful and If I remember correctly the subsequent restart will be on the main thread.
I don't remember issues with logging from a thread, but you could try the thread RunOnGuiThread method call to delegate to a different method and Log from there if it is an issue for you, although this would probably then be delayed.

If I were to write this library now, I would use JavaObject in a B4xlib, again from memory, there is very little processing in the library and mainly just calls the java AudioTrack methods.
 
Last edited:
Top