Android Question AudioTrack - how to loop audio ?

semar

Active Member
Licensed User
Dear all,
I'm using the impressing AdioTrack V. 1.03 by stevel05.

However, by trying to use the LoopStart and LoopEnd properties, it does not loop at all.

May be I didn't set something right. I use the flag MODE_STATIC for the Audiotrack, and set the looping points (start and end) after I call the play method of the audiotrack.
The file (a simple .wav file) is reproduced only once, and seems that the looping points are complete ignored.

Am I doing something wrong here ?

Any help would be greatly appreciated.

The code:
B4X:
#Region  Project Attributes 
	#ApplicationLabel: B4A Example
	#VersionCode: 1
	#VersionName: 
	'SupportedOrientations possible values: unspecified, landscape or portrait.
	#SupportedOrientations: unspecified
	#CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes 
	#FullScreen: False
	#IncludeTitle: True
#End Region

Sub Process_Globals
	'These global variables will be declared once when the application starts.
	'These variables can be accessed from all modules.
	Private xui As XUI
	Dim AT As AudioTrack
	Dim Ra As RandomAccessFile
	'...
	Dim BufferSize As Int
	Dim SampleRate As Int
	Dim ChannelConfig As Int
	Dim AudioFormat As Int
	Dim Buffer() As Byte

	
End Sub

Sub Globals
	'These global variables will be redeclared each time the activity is created.
End Sub

Sub Activity_Create(FirstTime As Boolean)
	Activity.LoadLayout("Layout")
	File.Copy(File.DirAssets,"1.wav",File.DirInternal,"1.wav")
	Ra.Initialize2(File.DirInternal, "1.wav", True, True)
	SampleRate=44100
	ChannelConfig=AT.Ch_Conf_Stereo
	AudioFormat=AT.Af_PCM_16
	BufferSize=Ra.Size - 44              ' RIFF header size is 44 bytes
	Dim Buffer(BufferSize) As Byte
	
	AT.Initialize(AT.Stream_Music,SampleRate,ChannelConfig,AudioFormat,BufferSize,AT.MODE_STATIC)
	Ra.ReadBytes(Buffer, 0, BufferSize, 44)
	AT.WriteByte(Buffer, 0, BufferSize)
	AT.Play
	AT.SetLoopPoints(0, 500, 5)'must be called AFTER calling the .Play Method
	
	
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub Button1_Click
	xui.MsgboxAsync("Hello world!", "B4X")
End Sub
 
Last edited:

stevel05

Expert
Licensed User
Hmm, It's a long time since I looked at this, I just tried it and got the same result. I'm not sure why but it appears that the Loop points should be set BEFORE calling play. The current documentation says " the track must be stopped or paused for the loop points to be changed" Which I am guessing may have changed, although I don't really see how or why, as others have got it to work in the past.

The streaming code in this example uses Wait For and Sleep instead of threading.

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.

    Dim at As AudioTrack
    Dim Ra As RandomAccessFile
  
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim BufferSize As Int
    Dim SampleRate As Int
    Dim ChannelConfig As Int
    Dim AudioFormat As Int
    Dim DataSize As Int
  
    Dim Play As Thread
  
End Sub

Sub Activity_Create(FirstTime As Boolean)

    Play.Initialise("Play")
  
    'Can't load from assets so move it
    If File.Exists(File.DirAssets,"trom.wav") Then
        File.Copy(File.DirAssets,"trom.wav",File.DirDefaultExternal,"trom.wav")
    End If
  
    'Open the file for Random Access
    Ra.Initialize2(File.DirDefaultExternal,"trom.wav",True,True)
  
    'Could get this from wav header, but just enter it
    SampleRate=44100
    AudioFormat=at.Af_PCM_16
    ChannelConfig=at.Ch_Conf_Stereo

    'Play Streaming
'    Wait For(PlayStream) Complete (Resp As Boolean)
'    Log("Playing Finished")
' 
  
'    'Play Static
    PlayStatic
  
'    at.Release
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
    at.Release
End Sub


Sub PlayStream As ResumableSub
  
    'Get the min buffer size
    BufferSize=at.GetMinBuffersize(SampleRate,ChannelConfig,AudioFormat)
  
    'Try to initialize the player
    at.Initialize(at.Stream_Music,SampleRate,ChannelConfig,AudioFormat,BufferSize,at.Mode_Stream)

    Log(at.GetState)
    'Check it initialized
    If at.GetState < 0 Then
        Msgbox("Failed to initialize AudioTrack","Initialize Failed")
        Activity.Finish
    End If
    Log("Playing")
    Log( "File Size " & Ra.Size)
    Dim Pos As Int
    at.SetStereoVolume(at.GetMaxVolume,at.GetMaxVolume)
    'File has a wav header we need to ignore
    Pos=44
    For i = 0 To Ra.Size-BufferSize Step BufferSize
        'Break into buffersize chunks and write to the AudioTrack
        Dim Dat(BufferSize) As Byte
        Ra.ReadBytes(Dat,0,BufferSize,Pos)
        at.WriteByte(Dat,0,BufferSize)
        Sleep(0)
        Pos=Pos+Dat.Length
        If i = 0 Then
            at.Play
            at.SetLoopPoints(0,1024,3)
        End If
    Next
  
    'Write the last bit
    Dim Dat(Ra.Size-Pos) As Byte
    Ra.ReadBytes(Dat,0,Ra.Size-Pos,Pos) 
    at.WriteByte(Dat,0,Ra.Size-Pos)
  
    Return True
End Sub

Sub PlayStatic
  
    'Get the min buffer size
    BufferSize=Ra.Size - 44
  
    'Try to initialize the player
    at.Initialize(at.Stream_Music,SampleRate,ChannelConfig,AudioFormat,BufferSize,at.Mode_Static)

    Log("AT State " & at.GetState)
    'Check it initialized
    If at.GetState < 0 Then
        Msgbox("Failed to initialize AudioTrack","Initialize Failed")
        Activity.Finish
    End If
    Log("Playing")
    Log( "File Size " & Ra.Size)
    at.SetStereoVolume(at.GetMaxVolume,at.GetMaxVolume)
    'File has a wav header we need to ignore
    Dim Pos As Int = 44
  
    Dim Dat(BufferSize) As Byte
    Log("Read " & Ra.ReadBytes(Dat,0,BufferSize,Pos))
    Log("Write " & at.WriteByte(Dat,0,BufferSize))
  
    Dim Frames As Int = (BufferSize / 4) ' Framesize = 4 = Samplesize in bytes * No Channels
    Log("Frames " & Frames)
    at.SetLoopPoints(0,Frames,3)
    at.Play

End Sub
 
Upvote 0

semar

Active Member
Licensed User
Many thanks Steve for your prompt answer, really kind of you !

It works !

And I see, I misunderstand the meaning of the "Frames" values.

In fact, in my example I put small values for the looping points, and doing so it does not work at all. This idea came to me last night just before sleeping :)

That is, we have to choose the right values for the startFrame and endFrame. How to find them, in order to get a smooth "zero-crossing" looping, is the next challenge.
If you could lead me in the right direction, it would be fantastic :)

Anyway, what do you mean with this statement ?
B4X:
Dim Frames As Int = (BufferSize / 4) ' Framesize = 4 = Samplesize in bytes * No Channels
I mean, why Frames are BufferSize/4 ? Which formula do you use for it ? And what does the comment "Framesize = 4 = Samplesize in bytes * No Channels" mean ?
Is there any documentation I can read about ?

Again, many thanks for your great support !

Take care,
Sergio
 
Last edited:
Upvote 0

stevel05

Expert
Licensed User
That will really depend on what the app is doing. Are you drawing the wav file? Will you be auditioning the file? How will you be choosing the approximate position for the loop?

It will probably be trial and error, but you could check nearby frame boundaries to your chosen position to see if it is crossing, or select the one that is nearest crossing and see if that is good enough.
 
Upvote 0

semar

Active Member
Licensed User
What I want to achieve is an App to simulate a Theremin.

To achieve that, I thought I could use single tones for the notes, and loop one of them when the user touches the screen.
Moving the finger on the screen should mute the old note and trigger (loop) the new one, according with the position on the display.

Hence, the loop range is not really critical; however, what is important, is that it loops seamlessy. If I understand right, in order to loop the sound - which is in my case a simple sinus tone of a certain frequency - I have to find out where the wave crosses the "0". Then from there find out the next "zero crossing", so that the range is finally defined.

The loop range for each note could be stored in a list, so that at each position on the display, the correct loop will be performed. Furthermore, the whole wave file is a ramp which should contain all the notes needed, each of them being long enough in order to find out a good loop range, and loop it seamlessy.

This way I don't need a wave file for each note, but only a wave file with all the notes inside. I need however:
- seamless loops
- the possibility to change loop range without "clicks" or stutters during the audition.

Sadly I read that, in order to change the loop start and end positions, the AudioTrack should be stopped first. This kills the whole concept..🤔
 
Last edited:
Upvote 0

stevel05

Expert
Licensed User
Sadly I read that, in order to change the loop start and end positions, the AudioTrack should be stopped first. This kills the whole concept.
Not really, you will have to stop one wav playing before you start another, the time it would take to change the loop point's will be negligible. Or you could use two Audiotracks and queue the second while the first is still playing, then switch. Whichever is the cleanest.

If you are going to store the loop points in a list, you could use an external editor to work out the loop points. Audacity on PC maybe.
 
Upvote 0

semar

Active Member
Licensed User
Not really, you will have to stop one wav playing before you start another, the time it would take to change the loop point's will be negligible. Or you could use two Audiotracks and queue the second while the first is still playing, then switch. Whichever is the cleanest.

If you are going to store the loop points in a list, you could use an external editor to work out the loop points. Audacity on PC maybe.
This is a really nice idea indeed. Can a single AudioTrack be muted/paused ? I guess so. Well.. time to experiment further ;)
 
Upvote 0

stevel05

Expert
Licensed User
Before you get too far into audiotrack. Have you looked as the soundpool library? That may well be a better fit for this situation.
 
Upvote 0

semar

Active Member
Licensed User
Yes I did, but with it there are no possibilities to loop a sound in a specific range. However, a nice library indeed
 
Upvote 0

stevel05

Expert
Licensed User
If you are going to pre-prepare the audio files, then you could trim them so they loop properly. Soundpool will also work with Mp3 files so less size for the APK, although it does have to decompress them, and as it's a pretty generic sound, you could probably get away with changing the playback speed to use one sound file for more than one note.
 
Upvote 0

semar

Active Member
Licensed User
Yes indeed Steve, again a smart concept, to which I came some days ago before to join your AudioTrack wagon, but after lots of try and error sessions I have changed my mind, because I would need to pre-prepare quite a lot of audio files.

In fact, for a Theremin, I would need at least 5 octaves, and all the "nuances" between pure notes, for instance, the transition between a C and a C# should contain quite a few notes.. Ideal would be, each note with "one herz distance", to ensure a Theremin-like transition between notes.
With a "ramp" ( a stair ramp if you get what I mean ) instead, I could save some hassle in that regard.

Furthermore, it is not really clear how to pause a SoundPool channel. I'm aware of the soundID and PoolId, and how to use it, but it simply did not work when I tryed. However is possible that the problem was by my side :rolleyes:

I like when a discussion grows and leads to interesting developments and ideas. Feel free to add your thoughs and insides. For me - and I'm sure, for others too - your comments are rare gems.
 
Upvote 0
Top