Android Question Generate a sound (a simple sine wave form) and change its frequency in a continuos manner

giacomo-italy

Member
Licensed User
Longtime User
Hello, if there is an expert in sound generation with Android, can I ask a question?
I need to generate a sound (even a simple sine wave form) and i want to change its frequency and its amplitude in a continuous manner, while the sound is generated.

I tried with AudioStreamer with the code posted by Erel:

B4X:
Sub Process_Globals
Dim streamer As AudioStreamer
Dim timer1 As Timer
Dim freq As Int = 500
End Sub

Sub Globals
End Sub

Sub Activity_Create(FirstTime As Boolean)
If FirstTime Then
streamer.Initialize("streamer", 11025, True, 16, streamer.VOLUME_MUSIC)
streamer.StartPlaying
timer1.Initialize("timer1", 1000)
End If
timer1.Enabled = True
End Sub

Sub Timer1_Tick
Log(freq)
Beep(1000, freq)
freq = freq + 500
End Sub


Sub Beep(Duration As Int, Frequency As Int)
Dim dur As Double = Duration / 10000
Dim sampleRate As Int = 11025
Dim numSamples As Int = sampleRate * dur
Dim snd(2 * numSamples) As Byte
For i = 0To numSamples - 1
Dim d As Double = Sin(2 * cPI * i / (sampleRate / Frequency))Dim val As Short = d * 32767
snd(i * 2) = Bit.AND(0xff, val)
snd(i * 2 + 1) = Bit.ShiftRight(Bit.AND(0xff00, val), 8)Next
streamer.Write(snd)
End Sub

Sub Activity_Pause (UserClosed As Boolean)
timer1.Enabled = False

End Sub

But I can change frequency before each duration.
I tried to reduce the duration of the sound and insert the frequency change in the timer whit the same duration, but the resulting sound is choppy and full of disturbs like 'click' and noise.

Is there an alternative way to generate a sound and change frequency and amplitude, in a continuos way, while the sound is generated (to obtain an effect type like 'theremin')?

Thanks a lot.
 

Troberg

Well-Known Member
Licensed User
Longtime User
Isn't it just a matter of having a flexible function for generating the samples? A sin function is simple enough, so just make sure the changes are smooth. Instead of making a big jump seldom, make it in small steps often. Or, even better, include the change in the function.

If the main wave you play is: Amplitude*Sin(Freq), then make Freq variable, giving something like:

Amplitude*(Sin(Freq*Sin(SomeCounter))

(This assumes that you want the frequency to vary following a sine wave as well. You might want some other function, possibly even sensor data.)

You'll have to add some scale factors to get reasonable values, of course.

Then, just start feeding samples into the AudioStreamer.

Hope this gets you going in the right direction.

(Note: Also, try this code without frequency changing. It might be that the problems are not in the frequency changing at all, it could simply be that you fail to feed it data in a timely manner or that you oversteer the samples.)
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
I thimk there is a small bug in above code. It should be
B4X:
Dim dur As Double = Duration / 1000
instead of
B4X:
Dim dur As Double = Duration / 10000

in order for it to work, appart from timing
 
Upvote 0

giacomo-italy

Member
Licensed User
Longtime User
Isn't it just a matter of having a flexible function for generating the samples? A sin function is simple enough, so just make sure the changes are smooth. Instead of making a big jump seldom, make it in small steps often. Or, even better, include the change in the function.

If the main wave you play is: Amplitude*Sin(Freq), then make Freq variable, giving something like:

Amplitude*(Sin(Freq*Sin(SomeCounter))

(This assumes that you want the frequency to vary following a sine wave as well. You might want some other function, possibly even sensor data.)

You'll have to add some scale factors to get reasonable values, of course.

Then, just start feeding samples into the AudioStreamer.

Hope this gets you going in the right direction.

(Note: Also, try this code without frequency changing. It might be that the problems are not in the frequency changing at all, it could simply be that you fail to feed it data in a timely manner or that you oversteer the samples.)

Thanks for the reply.
This mode of operation allows to change the amplitude and frequency of the sound in a continuous manner.
In this way, before, I can generate samples with the changes I want, and then I can play the sound.
It can be a good start. But it does not solve the problem of changing the characteristics of the sound while the sound is generated.
You must first generate the samples and then listen to them.
I would like a way to listen to samples while I create them: the only way is still choose a time interval, generate the samples for that interval,
and listen to the sound of those samples in that interval, and so on in a loop.

I'll do some tests,
but I think i will always get several noise due to discontinuity with which I use streamer in this way.

This way to operate allows me:
-or I varied the parameters continuously, but listening to the sound offline,
-or listening to the sound in line, but varied parameters discontinuously...
 
Upvote 0

KitCarlson

Active Member
Licensed User
Longtime User
I am not sure if this will help, it is from my experience in embedded systems. The sine function is slow, a sine lookup table is used Instead.
 
Upvote 0

Troberg

Well-Known Member
Licensed User
Longtime User
Another bug which could give you pops:

You have the timer set for a second, and in each event you generate exactly the samples needed for a second.

The problem is that the timer does not work like that. It won't trigger exactly every second. Think of it less like a metronome and more like a "wait one second, then get back to me as soon as there is some CPU time to spare". It will never take less than a second, but it may be slightly more. Also, the timer event itself will consume some time (you are looping 11025 times, doing some trigonometry then feeding a sample into the AudioStreamer each iteration, that is not instantaneous).

So, you'll need to generate a few more samples, so that there is some buffer left the next time the timer fires. I can't access the AudioStreamer methods/properties right now, but I assume there is some event in it to tell you how much data it has buffered. I would probably recommend that, instead of simply clocking out exactly 1 seconds worth of samples each timer event, you should clock out so that the buffer contains maybe 1.2 seconds worth of samples, to ensure you never "run dry".

Also, you don't keep a counter between timer events. This means that the wave might, in the worst case, end at the top value in one timer cycle, then restart next timer cycle at 0. That will give a nasty pop. You need to pick up where you left off.

Another thing: Sin is slow, as is converting floats to integers. If you just do it a little, no problem, but you do it 11025 times a second, and it adds up. Consider pre-generating a lookup table for Sin values at program start. If you are even smarter, you make that lookup table so that the results are already multiplied with 32767, which allows you to both skip the float step and an unnecessary multiplication. You could even take it one step further a pre-calculate the bit operations as well into the table.

Since you need integers in the lookup table, we can multiply the value I've called t in my code below with some integer to get a sufficiently high resolution suitable for our table (I used 1000). As a positive side effect, we don't need t to be a float.

You also recalculate the same things over and over in the loop, things which could be done outside it. I did that and stored it in t in my example. Viola, you've saved another 33075 calculations a second, 2/3 of them divisions.

You would get something like (snipped to the relevant bits):

B4X:
dim t as int= 1000 * 2 * cPI / (sampleRate / Frequency)
For i = 0To numSamples - 1
  dim f as int = t * i
  snd(i * 2) = SinTable(f)
  snd(i * 2 + 1) = SinTable2(f)
Next

I think this code will run at least 10 times faster, possible 100 times or more faster, and also save a lot of battery.

You'll have to figure out how to precalculate the tables yourself, though. I don't have time to do it now.

I would like a way to listen to samples while I create them: the only way is still choose a time interval, generate the samples for that interval, and listen to the sound of those samples in that interval, and so on in a loop.

So, in the timer, don't just slam the frequency to the new frequency. Remember the old frequency and then ease into the new when you generate the samples.

For example, in each timer event, gradually change the frequency from the old to the new freq, over all the samples you generate. So, say the old freq is 5 kHz and the new is 6 kHz. Then you would gradually ramp up the freq, so that, for example, halfway through, you would be at 5.5 kHz.
 
Last edited:
Upvote 0

giacomo-italy

Member
Licensed User
Longtime User
I posted here my work...

Please look it.
I think we are going in the right direction, but the problem is not solved.
Some noise and 'click' are present in the sound generated.

Some instruction to read my code.
With the 4 seek-bar you can variate:

-frequency of the sine wave
-volume of the sine wave
-deltaT : is the time interval or duration of the created samples
-offsetT : is an time interval that i can add to deltaT for duration of time interval of the timer

The samples are created only if i do a variation of parameters: freq, vol, deltaT.
At moment there is not optimization of routine 'create_sample', because i think that the problem is not in the velocity
of creation of the samples.
Probably the problem is in the discontinuity of generated wave form, or other...

If you play in changing the parameters with the seek-bar, you can listen the problem of noise described above,
and you can listen the problem of:
- if you reduce deltaT parameter you can variate more continuosly the freq and vol parameters, but noises ('click') are incremented
- if you increment the deltaT parameter the noises are reduced, but you can not variate in continuosly way the parameters...

We are far far away to a generation of sound like this:
https://play.google.com/store/apps/details?id=com.sokin.android.leon

Every ideas are well accepted....
 

Attachments

  • AudioStreamer2.zip
    365.8 KB · Views: 453
Last edited:
Upvote 0

stari

Active Member
Licensed User
Longtime User
I posted here my work...

Please look it.
I think we are going in the right direction, but the problem is not solved.
Some noise and 'click' are present in the sound generated.

Some instruction to read my code.
With the 4 seek-bar you can variate:

-frequency of the sine wave
-volume of the sine wave
-deltaT : is the time interval or duration of the created samples
-offsetT : is an time interval that i can add to deltaT for duration of time interval of the timer

The samples are created only if i do a variation of parameters: freq, vol, deltaT.
At moment there is not optimization of routine 'create_sample', because i think that the problem is not in the velocity
of creation of the samples.
Probably the problem is in the discontinuity of generated wave form, or other...

If you play in changing the parameters with the seek-bar, you can listen the problem of noise described above,
and you can listen the problem of:
- if you reduce deltaT parameter you can variate more continuosly the freq and vol parameters, but noises ('click') are incremented
- if you increment the deltaT parameter the noises are reduced, but you can not variate in continuosly way the parameters...

We are far far away to a generation of sound like this:
https://play.google.com/store/apps/details?id=com.sokin.android.leon

Every ideas are well accepted....
Hello,
i have trayed your AudioStreamer2. But, the generated frequency is not correct. Do you have better sample, mybee ?
 
Upvote 0

giacomo-italy

Member
Licensed User
Longtime User
Hello,
i have trayed your AudioStreamer2. But, the generated frequency is not correct. Do you have better sample, mybee ?

Hello,
sorry if I answer so late.
I have not worked on this code, I abandoned the project.
I do not have the ability to solve the problem...
 
Last edited:
Upvote 0

giacomo-italy

Member
Licensed User
Longtime User
I thank all those, like you, who have made their contribution to solving the problem.
I just wanted to point out that I am not to have the capacity to solve the problem,
I am not to be able to translate your suggestions in working solutions.
Never claimed that someone do the work for me.
I assure you that I have spent much time and many trials to improve the code, I have not succeeded.

Sorry for the misunderstanding that gave birth to my comment.
The problem is my lack of knowledge of the abilities of the sound generation of Android...
Thanks to everyone anyway.
 
Upvote 0

giacomo-italy

Member
Licensed User
Longtime User
P.S.: the best thing of using B4X is the community in which thousands of people generously help those who have a problem...
I hope in the future I could be able to help someone...
 
Upvote 0

stari

Active Member
Licensed User
Longtime User
I worked on this project and come up with this solution (still in the working stage).
B4X:
#Region  Project Attributes
    #ApplicationLabel: Sound_thr_0307
    #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 kvs As KeyValueStore
    Dim nativeMe As JavaObject
    Dim thread1 As Thread
    Dim Lock1 As Lock
    Dim Thread1_stanje As Boolean         :Thread1_stanje = False            'thread ne deluje še
 
    'Dim MediaPlayer1 As MediaPlayer
    'Private audioStream As AudioStreamer
 
    Dim dummy As Int        :dummy = 1
 
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.

    Private EditText1 As EditText
    Private EditText2 As EditText
    Private Button1 As Button
    Private Button2 As Button
 
    Dim freq As Double       'frequency to play
    Dim dur As Double        'play for 5 seconds
 
    Dim args(0) As Object
 
    'Private astream As AsyncStreams
    'audioStream.Initialize("AudioStream", 22050, True, 16, audioStream.VOLUME_MUSIC)
    '(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    Activity.LoadLayout("main")

    If FirstTime Then
        thread1.Initialise ("Thread1")
        kvs.Initialize(File.DirRootExternal , "datastore")
     
     
        Lock1.Initialize(True)
        thread1.Name = "B4A Thread 1"
     
     
    End If 
 
    nativeMe.InitializeContext
 
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub


Sub Thread1_Ended(fail As Boolean, err As String) 'An error or Exception has occurred in the Thread

    Log("Thread1 ended")
    Thread1_stanje = False
    ustavi                     'log sporočilo_ error .... , vendar deluje zvok
 
End Sub


Sub thread_Sub1

    Log("Thread1 Sub running")
    Thread1_stanje = True
    Dim zvoki() As Byte
    zvoki = nativeMe.RunMethod("playTone",Array(freq,dur))
    Log("Button1_Click")
    'kvs.PutObject("zvoki",zvoki)
    'preberi_zvok
    'For x = 0 To zvoki.Length -  1 Step 100
    '    Log(x & "  " & zvoki(x) & CRLF)
    'Next
 
    'nativeMe.RunMethod("playTone",Array(freq,dur))  'start the java code - it will run in the new thread and not in the main thread
End Sub

Sub Button1_Click
    Log("Button 1 clicked ")
    freq = EditText1.Text
    dur = EditText2.text
 
 
    'preberi_zvok
 
    'For x = 0 To zvoki.Length -  1 Step 100
    '    Log(x & "  " & zvoki(x) & CRLF)
    'Next
 
    thread1.Start(Null, "Thread_Sub1", args)           'start a new thread to run the java code in
     
 
    'If EditText2.Text  > 0 Then
    '    EditText2.Text = 0
    'End If
 
End Sub

Sub Button2_Click
    ustavi
End Sub

Sub ustavi
    Try
        thread1.Initialise ("Thread1")
        nativeMe.RunMethod("ustavi",Array(dummy))
    Catch
        Log("Some error ocured")
    End Try
End Sub



Sub preberi_zvok
    Dim zvoki() As Byte = kvs.GetObject ("zvoki")
 
    For x = 0 To zvoki.Length -  1
        Log(x & "  " & zvoki(x) & CRLF)
    Next
 
    'audioStream.Write(zvoki)

End Sub


#If Java

import android.media.AudioTrack;     //you need to add this as it is not part of the code posted on Stackoverflow - compiler will complain if it can't find a reference to AudioTrack so search for the correct way to import it on the web
import android.media.AudioFormat;    //you need to add this as it is not part of the code posted on Stackoverflow - see comment above
import android.media.AudioManager;   //you need to add this as it is not part of the code posted on Stackoverflow - see comment above


    AudioTrack audioTrack = null;                                   // Get audio track
 
public byte[] playTone(double freqOfTone, double duration) {
     BA.Log("OK, here we are, in Java sub");
//double duration = 1000;                // seconds
//   double freqOfTone = 1000;           // hz
 
 
    int sampleRate = 8000;              // a number
    double dnumSamples = duration * sampleRate;
    dnumSamples = Math.ceil(dnumSamples);
    int numSamples = (int) dnumSamples;
    double sample[] = new double[numSamples];
    byte generatedSnd[] = new byte[2 * numSamples];
 
 
 
    if (freqOfTone == 0) {
        //audioTrack.release(); 
        audioTrack = null;
        return generatedSnd;
    }
 

    //for (int i = 0; i < numSamples; ++i) {      // Fill the sample array                                //sinus
    //sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate));                                    //sinus
 
    for (int i = numSamples; i > 0; --i) {      // Fill the sample array                                //sawtooth
    sample[numSamples - i]= 2*(i%(sampleRate/freqOfTone))/(sampleRate/freqOfTone)-1;                //sawtooth

    }

    // convert to 16 bit pcm sound array
    // assumes the sample buffer is normalized.
    // convert to 16 bit pcm sound array
    // assumes the sample buffer is normalised.
    int idx = 0;
    int i = 0 ;

    int ramp = numSamples / 20 ;                                    // Amplitude ramp as a percent of sample count


    for (i = 0; i< ramp; ++i) {                                     // Ramp amplitude up (to avoid clicks)
        double dVal = sample[i];
                                                                    // Ramp up to maximum
        final short val = (short) ((dVal * 32767 * i/ramp));
                                                                    // in 16 bit wav PCM, first byte is the low order byte
        generatedSnd[idx++] = (byte) (val & 0x00ff);
        generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
    }


    for (i = i; i< numSamples - ramp; ++i) {                        // Max amplitude for most of the samples
        double dVal = sample[i];
                                                                    // scale to maximum amplitude
        final short val = (short) ((dVal * 32767));
                                                                    // in 16 bit wav PCM, first byte is the low order byte
        generatedSnd[idx++] = (byte) (val & 0x00ff);
        generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
    }

    for (i = i; i< numSamples; ++i) {                               // Ramp amplitude down
        double dVal = sample[i];
                                                                    // Ramp down to zero
        final short val = (short) ((dVal * 32767 * (numSamples-i)/ramp ));
                                                                    // in 16 bit wav PCM, first byte is the low order byte
        generatedSnd[idx++] = (byte) (val & 0x00ff);
        generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
     
    }
 
    igraj(sampleRate, generatedSnd);
 
 
    int x = 0;
    do{
        if (audioTrack != null)
                 x = audioTrack.getPlaybackHeadPosition();
             else
                 x = numSamples;         
        }while (x<numSamples);
 
    if (audioTrack != null) audioTrack.release();           // Track play done. Release track.
    return generatedSnd;
}
public void igraj(int sampleRate, byte[] skladba) {
     
         try {
         int bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, bufferSize,
                AudioTrack.MODE_STREAM);
        audioTrack.play();                                          // Play the track
        audioTrack.write(skladba, 0, skladba.length);     // Load the track
    }
    catch (Exception e){
    }
}

public void ustavi(int dummy ) {
    audioTrack.stop();

}


#End If
 
Upvote 0
Top