Play programmatically generated audio

Dear community,
I would like to code a page where a sound file is displayed as a spectrogram (a heatmap based on a fast fourier transform - this part I have working) and then a user selects a region in that heatmap (i.e. a time- and frequency range) and audio of only that part is played back.

My question is:
can the html.Audio element play back programmatically generated audio (which can be saved to a file if it makes life easier) triggered by an event such as a selection of a region in a heatmap?

A quick search of this forum suggested to me that nobody has so far succeeded in actually getting html.Audio to do something like this, but I am a noob to Dash, so I thought I’d ask directly.

Thank you for your inputs!

Hey,
you can play sounds from a file like this for example.

from dash import dcc, Dash, html, Input, Output

app = Dash(__name__)

app.layout = html.Div([
    html.Button("Play/Pause Audio", id="pause_audio"),
    html.Div(id="dummy", style={'display': 'none'}),
    html.Audio(id='sample_audio', controls=True, style={'display': 'none'})
])

@app.callback(
        Output('sample_audio', 'src'),
        Input('dummy', 'n_clicks') #gets fired instantly on page start
)
def set_src(n):
    return 'assets/sound/sound.mp3'


app.clientside_callback(
    '''
    function(n){
        const audioElement = document.querySelector('#sample_audio');

        if (n%2 == 1) {
            audioElement.play();
        } else {
            audioElement.pause();
        }

        return 0;
    }
    ''',
    Output('dummy', 'n_clicks'),
    Input('pause_audio', 'n_clicks'),
    prevent_initial_call = True
)

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

The selection in the heatmap could be done using a multislider below.

1 Like

Hi @Louis, thanks for the reply!

I had to make some modifications to your code to make it work, but this helped me a lot.
For anyone else looking for something similar, below is my code (full disclosure: it’s a mash-up of your code above and some ideas I got from the Microsoft Bing Copilot), here are a few things I found out:

  • You need the app.clientside_callback part to start the audio playing. Most code examples I saw just use audioElement.play(); but I found that audioElement.load(); with autoPlay=True set in the html.Audio element works more reliably.

  • I found that the sound file name needs to be different if the sound changes. Just overwriting the same mySound.wav file did not reliably get the new sound played - presumably, this is due to caching at the browser level and can somehow be overcome (if someone knows how, please comment below!).
    So in a scenario where theoretically a very large/infinite number of distinct sounds can be generated, I’ll probably need to think about mechanisms that a) ensure unique names, and b) cleans up older/unused ones.

import dash
import dash_html_components as html
import dash_core_components as dcc
import numpy as np
import soundfile as sf

app = dash.Dash(__name__)


app.layout = html.Div([
    html.H1("Sine Wave Audio Player"),
    html.Label("Frequency (Hz):"),
    html.Div(id="dummy", style={'display': 'none'}),
    dcc.Slider(
        id="frequency-slider",
        min=100,
        max=1000,
        step=10,
        value=500,
        marks={i: str(i) for i in range(100, 1001, 100)}
    ),
    html.Audio(
        id="audio-player",
        autoPlay=True,
        controls=True,
        style={'width': '100%'}
    )
])

@app.callback(
    dash.dependencies.Output("audio-player", "src"),
    dash.dependencies.Input("frequency-slider", "value")
)
def generate_audio(frequency):
    duration = 1.0
    sample_rate = 44100
    t = np.linspace(0, duration, int(duration * sample_rate), False)
    audio_data = np.sin(frequency * 2 * np.pi * t)
    audio_data /= np.max(np.abs(audio_data))
    audio_data = np.asarray(audio_data * 32767, dtype=np.int16)
    filename = f"assets/{frequency}Hz.wav"
    sf.write(filename, audio_data, sample_rate)
    print(filename)
    return filename

app.clientside_callback(
    '''
    function(n){
        const audioElement = document.querySelector('#audio-player');
        audioElement.autoplay=true;
        audioElement.load();
        return 0;
    }
    ''',
    dash.dependencies.Output('dummy', 'n_clicks'),
    dash.dependencies.Input('frequency-slider', 'value'),
    prevent_initial_call = True
)

if __name__ == "__main__":
    app.run_server(debug=True, dev_tools_hot_reload=False)

2 Likes