Skip to content Skip to sidebar Skip to footer

Wbserver Roundslider Widget Doesn't Start/update Audio Tone Program

I have a 'dial tone' python program that is imported into a Falsk webserver, and I have an html embedded roundSlider widget that I am trying to use to update the sample rate variab

Solution 1:

This code works for me.

Main problem was that function runs so fast that nobody can hear sound - so I added time.sleep(0.5)

I put all code in one file so it is easier to copy and paste to file to run it.

Main page http://localhost:5000/ displays few links with different values to test it.

from gnuradio import analog
from gnuradio import audio

from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filterimport firdes
from optparse import OptionParser


classtop_block_22(gr.top_block):

    def__init__(self, samp_rate=32000): # default value for samp_rate

        gr.top_block.__init__(self, "Top Block 22")        
        ################################################### Variables##################################################
        self.samp_rate = samp_rate   # without "= 32000"print('[DEBUG] top_block_22:', self.samp_rate)

        ################################################### Blocks##################################################
        self.blocks_add_xx = blocks.add_vff(1)
        self.audio_sink = audio.sink(32000, '', True)
        self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.4, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.4, 0)
        self.analog_noise_source_x_0 = analog.noise_source_f(analog.GR_GAUSSIAN, 0.005, -42)

        ################################################### Connections##################################################
        self.connect((self.analog_noise_source_x_0, 0), (self.blocks_add_xx, 2))
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))
        self.connect((self.blocks_add_xx, 0), (self.audio_sink, 0))

# -----------------------------------------------------------------------------from flask import Flask, request
#from top_block_22 import top_block_22 import time


app = Flask(__name__)


@app.route('/')defindex():
    HTML = 'HEAR:'for item in (20000, 25000, 32000):
        HTML += ' <a href="/valueofslider?slide_val={}">{}</a>'.format(item, item)
    return HTML

@app.route('/valueofslider')defslide():
    slide_val = request.args.get('slide_val', 32000) # default value 32000
    main(slide_val)
    return slide_val

defmain(slide_val):
    samp_rate = int(slide_val) + 100print('[DEBUG] main:', samp_rate)

    tb = top_block_22(samp_rate) # run with value from variable
    tb.start()

    time.sleep(0.5)

    tb.stop()
    tb.wait()


if __name__ == '__main__':    
    app.run(debug=True)

EDIT: This version displays slider and it uses Thread to play sound all time (after selecting first value on slider).

When slider sends new value then it stops old thread and creates new one with new samp_rate.

But maybe it can be done without Thread but using only tb.start, tb.stop, etc. It seems top_block_22 already uses thread to work.

It also uses http://localhost:5000/off to send samp_rate=0 which stop sound.

from gnuradio import analog
from gnuradio import audio

from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filterimport firdes
from optparse import OptionParser


classtop_block_22(gr.top_block):

    def__init__(self, samp_rate):

        gr.top_block.__init__(self, "Top Block 22")        
        ################################################### Variables##################################################
        self.samp_rate = samp_rate
        print('[DEBUG] top_block_22:', self.samp_rate)

        ################################################### Blocks##################################################
        self.blocks_add_xx = blocks.add_vff(1)
        self.audio_sink = audio.sink(32000, '', True)
        self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.4, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.4, 0)
        self.analog_noise_source_x_0 = analog.noise_source_f(analog.GR_GAUSSIAN, 0.005, -42)

        ################################################### Connections##################################################
        self.connect((self.analog_noise_source_x_0, 0), (self.blocks_add_xx, 2))
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))
        self.connect((self.blocks_add_xx, 0), (self.audio_sink, 0))

# -----------------------------------------------------------------------------from threading import Thread

classMyThread(Thread):

    def__init__(self, samp_rate):
        Thread.__init__(self)
        self.running = True
        self.samp_rate = samp_rate

    defrun(self):

        tb = top_block_22(self.samp_rate)
        tb.start()

        while self.running:
            time.sleep(0.5)  # need it to head sound

        tb.stop()
        tb.wait()

# -----------------------------------------------------------------------------from flask import Flask, request
#from top_block_22 import top_block_22 import time


app = Flask(__name__)

my_thread = None@app.route('/')defindex():
    return'''<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>jQuery roundSlider - JS Bin</title>
  <link rel="icon" href="data:,">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.css" rel="stylesheet" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.js"></script>
</head>
<body>
  <!-- Only html needed   -->
<form class="form-inline" method="POST" action="{{ url_for('slide') }}">
  <div id="slider"></div>

  <script>
    var val;
    $("#slider").roundSlider({
      radius: 215,
        min: 0,
        max: 100000,
       change: function () {

        var obj1 = $("#slider").data("roundSlider");
        val = obj1.getValue();
        value: 10
        $.getJSON('/valueofslider', {
          slide_val: val
        });
      }
    });
  </script>
</body>
</html>'''@app.route('/off')defoff():
    '''use `slide_val=0` to turn it off'''
    main(0)
    return'off'@app.route('/valueofslider')defslide():
    slide_val = request.args.get('slide_val', 32000)
    main(slide_val)
    return slide_val

defmain(slide_val):
    global my_thread

    print('[DEBUG] main:', slide_val)
    samp_rate = int(slide_val)

    if my_thread: # if my_thread is not None
        my_thread.running = False
        my_thread.join()
        my_thread = Noneif samp_rate > 0:
        my_thread = MyThread(samp_rate)
        my_thread.start()


if __name__ == '__main__':    
    app.run(debug=True)

EDIT: Version without Thread.

As before it display slider and it play sound all time after selecting first value. It use global value tb to keep access to object and stop it when it get new samp_rate, and run new object.

It also uses http://localhost:5000/off to send samp_rate=0 which stop sound.

from gnuradio import analog
from gnuradio import audio

from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filterimport firdes
from optparse import OptionParser


classtop_block_22(gr.top_block):

    def__init__(self, samp_rate):

        gr.top_block.__init__(self, "Top Block 22")        
        ################################################### Variables##################################################
        self.samp_rate = samp_rate
        print('[DEBUG] top_block_22:', self.samp_rate)

        ################################################### Blocks##################################################
        self.blocks_add_xx = blocks.add_vff(1)
        self.audio_sink = audio.sink(32000, '', True)
        self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.4, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.4, 0)
        self.analog_noise_source_x_0 = analog.noise_source_f(analog.GR_GAUSSIAN, 0.005, -42)

        ################################################### Connections##################################################
        self.connect((self.analog_noise_source_x_0, 0), (self.blocks_add_xx, 2))
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))
        self.connect((self.blocks_add_xx, 0), (self.audio_sink, 0))

# -----------------------------------------------------------------------------from flask import Flask, request
#from top_block_22 import top_block_22 


app = Flask(__name__)

tb = None# global variable to keep it between requests@app.route('/')defindex():
    return'''<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>jQuery roundSlider - JS Bin</title>
  <link rel="icon" href="data:,">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.css" rel="stylesheet" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.js"></script>
</head>
<body>
  <!-- Only html needed   -->
<form class="form-inline" method="POST" action="{{ url_for('slide') }}">
  <div id="slider"></div>

  <script>
    var val;
    $("#slider").roundSlider({
      radius: 215,
        min: 0,
        max: 100000,
       change: function () {

        var obj1 = $("#slider").data("roundSlider");
        val = obj1.getValue();
        value: 10
        $.getJSON('/valueofslider', {
          slide_val: val
        });
      }
    });
  </script>
</body>
</html>'''@app.route('/off')defoff():
    '''use `slide_val=0` to turn it off'''
    main(0)
    return'off'@app.route('/valueofslider')defslide():
    slide_val = request.args.get('slide_val', 32000)
    main(slide_val)
    return slide_val

defmain(slide_val):
    global tb

    print('[DEBUG] main:', slide_val)
    samp_rate = int(slide_val)

    # stop old soundif tb: # if tb is not None
        tb.stop()
        tb.wait()
        tb = None# create new sound (if not zero)if samp_rate > 0:
        tb = top_block_22(samp_rate)
        tb.start()


if __name__ == '__main__':    
    app.run(debug=True)

EDIT: last version

Few changes in JavaScript:

  • it sets default value at start - 32000. There was mistake in JS.
  • it sends request at start so it starts sound after loading page. There is no need to select value on slider.

Changes in Python:

  • as suggest PEP 8 -- Style Guide for Python Code I set UpperCaseName for class - TopBlock22
  • renamed main to sound which better describe what function is doing
  • http://localhost/set/<value> sets sample rate so it can be used instead of http://localhost/valueofslider?slider_val=<value>.
  • http://localhost/get gets current sample rate
  • http://localhost/off sets value to 0 so it turns off sound. I used it to fast turn off sound when it was too annoying.
  • print_function from __future__ to use print(text) like in Python 3
  • functions return value as text but there are lines with jsonify() so they can retun it as JSON. Maybe it can be useful later.

In FAQ I found How can I reconfigure a flow graph? How do I use lock(), unlock()? so maybe it can change sample rate without creating new object and maybe it gives better sound. I head click when it change sample rate.

Code:

from __future__ import print_function

from gnuradio import analog
from gnuradio import audio

from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filterimport firdes
from optparse import OptionParser


classTopBlock22(gr.top_block): # PEP8: CamelCaseName for classesdef__init__(self, sample_rate=32000):

        gr.top_block.__init__(self, "Top Block 22")        
        ################################################### Variables##################################################
        self.sample_rate = sample_rate
        print('[TopBlock22] sample_rate:', self.sample_rate)

        ################################################### Blocks##################################################
        self.blocks_add_xx = blocks.add_vff(1)
        self.audio_sink = audio.sink(32000, '', True)
        self.analog_sig_source_x_1 = analog.sig_source_f(sample_rate, analog.GR_COS_WAVE, 440, 0.4, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(sample_rate, analog.GR_COS_WAVE, 350, 0.4, 0)
        self.analog_noise_source_x_0 = analog.noise_source_f(analog.GR_GAUSSIAN, 0.005, -42)

        ################################################### Connections##################################################
        self.connect((self.analog_noise_source_x_0, 0), (self.blocks_add_xx, 2))
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))
        self.connect((self.blocks_add_xx, 0), (self.audio_sink, 0))

# -----------------------------------------------------------------------------from flask import Flask, request, jsonify
#from top_block_22 import TopBlock22 import time


app = Flask(__name__)

tb = None# global variable to keep it between requests@app.route('/')defindex():
    return'''<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>GNURadio Slider Example</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.css"  />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.js"></script>
</head>
<body>

<div id="slider"></div>

<script>

  // keep slider's value
  var val;

  // create slider
  $("#slider").roundSlider({
    radius: 215,
    min: 0,
    max: 100000,
    value: 32000, // default value at start
    change: function () {
      var obj = $("#slider").data("roundSlider");
      val = obj.getValue();
      $.getJSON('/valueofslider', {
        slide_val: val
      });
    }
  });

  // play sound at start
  $.getJSON('/valueofslider', {slide_val: val});

</script>

</body>
</html>'''@app.route('/test')deftest():
    HTML = 'HEAR:'for item in (0, 10000, 20000, 25000, 32000):
        HTML += ' <a href="/set/{}">{}</a>'.format(item, item)
    return HTML

@app.route('/off')defoff():
    """Turn off sound."""
    sound(0)
    #return jsonify({'val': 0})return'off'@app.route('/set/<int:value>')defset_value(value):
    """Set value. Use 0 to turn it off."""
    sound(value)
    #return jsonify({'val': value})returnstr(value)

@app.route('/get')defget_value():
    """Get current value."""if tb:
        value = tb.sample_rate
    else:
        value = 0#return jsonify({'val': value})returnstr(value)

@app.route('/valueofslider')defslide():
    sample_rate = request.args.get('slide_val', '32000')
    sample_rate = int(sample_rate)
    sound(sample_rate)
    #return jsonify({'val': sample_rate})returnstr(sample_rate)

defsound(sample_rate):
    global tb

    print('[sound] sample_rate:', sample_rate)
    sample_rate = int(sample_rate)

    # stop old soundif tb: # if tb is not None
        tb.stop()
        tb.wait()
        tb = None# create new sound (if not zero)if sample_rate > 0:
        tb = TopBlock22(sample_rate)
        tb.start()


if __name__ == '__main__':    
    app.run(debug=True)

EDIT: this time last version.

It uses lock/unlock and disconnect/connect to change sound without creating new object. Based on example from FAQ (How can I reconfigure a flow graph? How do I use lock(), unlock()?) but disconnect needed two endpoints like connect.

from __future__ import print_function

from gnuradio import analog
from gnuradio import audio

from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filterimport firdes
from optparse import OptionParser


classTopBlock22(gr.top_block): # PEP8: CamelCaseName for classesdef__init__(self, sample_rate=32000):

        gr.top_block.__init__(self, "Top Block 22")        
        ################################################### Variables##################################################
        self.sample_rate = sample_rate
        print('[TopBlock22] __init__: sample_rate:', self.sample_rate)

        ################################################### Blocks##################################################
        self.blocks_add_xx = blocks.add_vff(1)
        self.audio_sink = audio.sink(32000, '', True)
        self.analog_sig_source_x_1 = analog.sig_source_f(sample_rate, analog.GR_COS_WAVE, 440, 0.4, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(sample_rate, analog.GR_COS_WAVE, 350, 0.4, 0)
        self.analog_noise_source_x_0 = analog.noise_source_f(analog.GR_GAUSSIAN, 0.005, -42)

        ################################################### Connections##################################################
        self.connect((self.analog_noise_source_x_0, 0), (self.blocks_add_xx, 2))
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))
        self.connect((self.blocks_add_xx, 0), (self.audio_sink, 0))

    defchange(self, sample_rate):
        self.sample_rate = sample_rate
        print('[TopBlock22] change: sample_rate:', self.sample_rate)

        # lock
        self.lock()

        # disconect - needs two endpoints (not like in FAQ)
        self.disconnect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.disconnect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))

        # create new
        self.analog_sig_source_x_1 = analog.sig_source_f(sample_rate, analog.GR_COS_WAVE, 440, 0.4, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(sample_rate, analog.GR_COS_WAVE, 350, 0.4, 0)

        # connect again
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.blocks_add_xx, 1))

        # unlock
        self.unlock()

# -----------------------------------------------------------------------------from flask import Flask, request, jsonify


app = Flask(__name__)

tb = None# global variable to keep it between requests@app.route('/')defindex():
    return'''<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>GNURadio Slider Example</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.css"  />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.js"></script>
</head>
<body>

<div id="slider"></div>

<script>

  // keep slider's value
  var val;

  // create slider
  $("#slider").roundSlider({
    radius: 215,
    min: 0,
    max: 100000,
    value: 32000, // default value at start
    change: function () {
      var obj = $("#slider").data("roundSlider");
      val = obj.getValue();
      $.getJSON('/valueofslider', {
        slide_val: val
      });
    }
  });

  // play sound at start
  $.getJSON('/valueofslider', {slide_val: val});

</script>

</body>
</html>'''@app.route('/test')deftest():
    HTML = 'HEAR:'for item in (0, 10000, 20000, 25000, 32000):
        HTML += ' <a href="/set/{}">{}</a>'.format(item, item)
    return HTML

@app.route('/off')defoff():
    """Turn off sound."""
    sound(0)
    #return jsonify({'val': 0})return'off'@app.route('/set/<int:value>')defset_value(value):
    """Set value. Use 0 to turn it off."""
    sound(value)
    #return jsonify({'val': value})returnstr(value)

@app.route('/get')defget_value():
    """Get value. Returns 0 when turned off."""if tb:
        value = tb.sample_rate
    else:
        value = 0#return jsonify({'val': value})returnstr(value)

@app.route('/valueofslider')defslide():
    sample_rate = request.args.get('slide_val', '32000')
    sample_rate = int(sample_rate)
    sound(sample_rate)
    #return jsonify({'val': sample_rate})returnstr(sample_rate)

defsound_old(sample_rate):
    """version which doesn't use `change()`"""global tb

    print('[sound] sample_rate:', sample_rate)
    sample_rate = int(sample_rate)

    # stop old soundif tb: # if tb is not None
        tb.stop()
        tb.wait()
        tb = None# create new sound (if not zero)if sample_rate > 0:
        tb = TopBlock22(sample_rate)
        tb.start()

defsound(sample_rate):
    """version which uses `change()`"""global tb

    print('[sound] sample_rate:', sample_rate)
    sample_rate = int(sample_rate)

    # change or stop old soundif tb: # if tb is not Noneif sample_rate > 0:
            tb.change(sample_rate)
        else:        
            tb.stop()
            tb.wait()
            tb = None# create new sound (if not zero)ifnot tb:
        if sample_rate > 0:
            tb = TopBlock22(sample_rate)
            tb.start()


if __name__ == '__main__':    
    app.run(debug=True)

EDIT: I found that sample rate can be change with

 self.analog_sig_source_x_1.set_sampling_freq(sample_rate)

so it doesn't have to create new sig_source_f

class TopBlock22(gr.top_block):

    # ... rest ...

    def change(self, sample_rate):
        self.sample_rate = sample_rate
        print('[TopBlock22] change: sample_rate:', self.sample_rate)

        self.analog_sig_source_x_1.set_sampling_freq(sample_rate)
        self.analog_sig_source_x_0.set_sampling_freq(sample_rate)

I think that now I don't hear clicks when it changes sample rate but now I fill it has small delay before it change it after mouse click.

To see other functions in this object you can use dir(self.analog_sig_source_x_1)

print('\n'.join(dir(self.analog_sig_source_x_1)))

    ...        
    set_amplitude
    set_block_alias
    set_frequency
    set_max_noutput_items
    set_max_output_buffer
    set_min_noutput_items
    set_min_output_buffer
    set_offset
    set_processor_affinity
    set_sampling_freq
    set_thread_priority
    set_waveform
    ...

Source: Choosing, defining and configuring blocks, documentation for C/C++ version

Post a Comment for "Wbserver Roundslider Widget Doesn't Start/update Audio Tone Program"