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.filter import firdes
from optparse import OptionParser


class top_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('/')
def index():
    HTML = 'HEAR:'
    for item in (20000, 25000, 32000):
        HTML += ' <a href="/valueofslider?slide_val={}">{}</a>'.format(item, item)
    return HTML

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

def main(slide_val):
    samp_rate = int(slide_val) + 100
    print('[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.filter import firdes
from optparse import OptionParser


class top_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

class MyThread(Thread):

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

    def run(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('/')
def index():
    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')
def off():
    '''use `slide_val=0` to turn it off'''
    main(0)
    return 'off'

@app.route('/valueofslider')
def slide():
    slide_val = request.args.get('slide_val', 32000)
    main(slide_val)
    return slide_val

def main(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 = None

    if 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.filter import firdes
from optparse import OptionParser


class top_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('/')
def index():
    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')
def off():
    '''use `slide_val=0` to turn it off'''
    main(0)
    return 'off'

@app.route('/valueofslider')
def slide():
    slide_val = request.args.get('slide_val', 32000)
    main(slide_val)
    return slide_val

def main(slide_val):
    global tb

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

    # stop old sound
    if 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.filter import firdes
from optparse import OptionParser


class TopBlock22(gr.top_block): # PEP8: CamelCaseName for classes

    def __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('/')
def index():
    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')
def test():
    HTML = 'HEAR:'
    for item in (0, 10000, 20000, 25000, 32000):
        HTML += ' <a href="/set/{}">{}</a>'.format(item, item)
    return HTML

@app.route('/off')
def off():
    """Turn off sound."""
    sound(0)
    #return jsonify({'val': 0})
    return 'off'

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

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

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

def sound(sample_rate):
    global tb

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

    # stop old sound
    if 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.filter import firdes
from optparse import OptionParser


class TopBlock22(gr.top_block): # PEP8: CamelCaseName for classes

    def __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))

    def change(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('/')
def index():
    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')
def test():
    HTML = 'HEAR:'
    for item in (0, 10000, 20000, 25000, 32000):
        HTML += ' <a href="/set/{}">{}</a>'.format(item, item)
    return HTML

@app.route('/off')
def off():
    """Turn off sound."""
    sound(0)
    #return jsonify({'val': 0})
    return 'off'

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

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

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

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

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

    # stop old sound
    if 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()

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

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

    # change or stop old sound
    if tb: # if tb is not None
        if sample_rate > 0:
            tb.change(sample_rate)
        else:        
            tb.stop()
            tb.wait()
            tb = None 

    # create new sound (if not zero)
    if not 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"