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

Discussion in 'Android Questions' started by giacomo-italy, Mar 4, 2015.

  1. giacomo-italy

    giacomo-italy Member Licensed 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:

    Code:
    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"11025True16, 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.
     
  2. Troberg

    Troberg Well-Known Member Licensed 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.)
     
    JordiCP likes this.
  3. JordiCP

    JordiCP Well-Known Member Licensed User

    I thimk there is a small bug in above code. It should be
    Code:
    Dim dur As Double = Duration / 1000
    instead of
    Code:
    Dim dur As Double = Duration / 10000
    in order for it to work, appart from timing
     
  4. giacomo-italy

    giacomo-italy Member Licensed User

    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...
     
  5. KitCarlson

    KitCarlson Active Member Licensed 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.
     
  6. Troberg

    Troberg Well-Known Member Licensed 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):

    Code:
    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.

    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: Mar 4, 2015
  7. giacomo-italy

    giacomo-italy Member Licensed 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....
     

    Attached Files:

    Last edited: Mar 7, 2015
  8. stari

    stari Active Member Licensed User

    Hello,
    i have trayed your AudioStreamer2. But, the generated frequency is not correct. Do you have better sample, mybee ?
     
  9. giacomo-italy

    giacomo-italy Member Licensed User

    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: Jul 7, 2015
  10. Troberg

    Troberg Well-Known Member Licensed User

    Several people contributed, they just didn't do all your work for you.
     
  11. giacomo-italy

    giacomo-italy Member Licensed 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.
     
  12. giacomo-italy

    giacomo-italy Member Licensed 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...
     
  13. stari

    stari Active Member Licensed User

    I worked on this project and come up with this solution (still in the working stage).
    Code:
    #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(0As 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
     
    hongbii khaw and Johan Schoeman like this.
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice