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 her as well.
 

Attachments

  • attest.zip
    305.8 KB · Views: 1,301
  • AudioTrack1.3.zip
    7.8 KB · Views: 1,396
  • swt1-3.zip
    8.5 KB · Views: 1,076
  • attest2.zip
    298.4 KB · Views: 119
Last edited:

swabygw

Active Member
Licensed 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

swabygw

Active Member
Licensed 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
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
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: 88

stevel05

Expert
Licensed 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
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
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
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
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
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
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
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
 
Top