Skip to content

For more information about the examples, such as how the Python and Mojo files interact with each other, see the Examples Overview

FeedbackDelaysGui

A GUI for controlling a feedback delay audio graph.

The GUI is made using PySide6 and provides sliders and checkboxes to control various parameters of the feedback delay effect.

Python Code

import sys
from pathlib import Path

# This example is able to run by pressing the "play" button in VSCode
# that executes the whole file.
# In order to do this, it needs to add the parent directory to the path
# (the next line here) so that it can find the mmm_src and mmm_utils packages.
# If you want to run it line by line in a REPL, skip this line!
sys.path.insert(0, str(Path(__file__).parent.parent))

from mmm_python.GUI import Handle, ControlSpec
from mmm_python import *
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QCheckBox


def main():
    mmm_audio = MMMAudio(128, graph_name="FeedbackDelaysGUI", package_name="examples")

    mmm_audio.start_audio() 

    app = QApplication([])

    # Create the main window
    window = QWidget()
    window.setWindowTitle("Feedback Delay Controller")
    window.resize(300, 100)
    # stop audio when window is closed
    window.closeEvent = lambda event: (MMMAudio.exit_all(), event.accept())

    # Create layout
    layout = QVBoxLayout()

    gatebutton = QCheckBox("play")
    gatebutton.setChecked(True)
    gatebutton.stateChanged.connect(lambda state: mmm_audio.send_bool("play", True if state == 2 else False))
    layout.addWidget(gatebutton)

    gatebutton = QCheckBox("delay-input")
    gatebutton.setChecked(True)
    gatebutton.stateChanged.connect(lambda state: mmm_audio.send_bool("delay-input", True if state == 2 else False))
    layout.addWidget(gatebutton)

    # Create a slider
    delaytimeslider = Handle("delay time",ControlSpec(0, 1.0, 0.5), 0.5, callback=lambda v: mmm_audio.send_float("delay_time", v))
    layout.addWidget(delaytimeslider)

    feedbackslider = Handle("feedback",ControlSpec(-130, -0.1, 4), -6, callback=lambda v: mmm_audio.send_float("feedback", v))
    layout.addWidget(feedbackslider)

    freqslider = Handle("ffreq",ControlSpec(20, 20000, 0.5), 8000, callback=lambda v: mmm_audio.send_float("ffreq", v))
    layout.addWidget(freqslider)

    qslider = Handle("q",ControlSpec(0.1, 10, 0.5), 1.0, callback=lambda v: mmm_audio.send_float("q", v))
    layout.addWidget(qslider)

    mixslider = Handle("mix",ControlSpec(0.0, 1.0, 2), 0.2, callback=lambda v: mmm_audio.send_float("mix", v))
    layout.addWidget(mixslider)

    gatebutton = QCheckBox("main")
    gatebutton.setChecked(True)
    gatebutton.stateChanged.connect(lambda state: mmm_audio.send_bool("main", True if state == 2 else False))
    layout.addWidget(gatebutton)

    # Set the layout for the main window
    window.setLayout(layout)

    # Show the window
    window.show()

    # Start the application's event loop
    app.exec()

if __name__ == "__main__":
    main()

Mojo Code

from mmm_audio import *

struct DelaySynth(Movable, Copyable):
    var world: World
    comptime maxdelay = 1.0
    var main_lag: Lag[1]
    var buf: Buffer
    var playBuf: Play
    var delays: FB_Delay[num_chans=2, interp=4]  # FB_Delay with 2 channels and interpolation type 3 ()
    var delay_time_lag: Lag[2]
    var m: Messenger
    var gate_lag: Lag[1]
    var svf: SVF[2]
    var play: Bool
    var delaytime_m: Float64
    var feedback: Float64
    var delay_input: Bool
    var ffreq: Float64
    var q: Float64
    var mix: Float64
    var main: Bool

    def __init__(out self, world: World):
        self.world = world  
        self.main_lag = Lag[1](self.world, 0.03)
        self.buf = Buffer.load("resources/Shiverer.wav")
        self.playBuf = Play(self.world) 
        self.delays = FB_Delay[num_chans=2, interp=4](self.world, self.maxdelay) 
        self.delay_time_lag = Lag[2](self.world, 0.2)  # Initialize Lag with a default time constant
        self.m = Messenger(self.world)
        self.gate_lag = Lag[1](self.world, 0.03)
        self.svf = SVF[2](self.world)
        self.play = True
        self.delaytime_m = 0.5
        self.feedback = -6.0
        self.delay_input = True
        self.ffreq = 8000.0
        self.q = 1
        self.mix = 0.5
        self.main = True


    def next(mut self) -> MFloat[2]:

        self.m.update("play", self.play) 
        self.m.update("feedback", self.feedback) 
        self.m.update("delay-input", self.delay_input)
        self.m.update("ffreq", self.ffreq) 
        self.m.update("delay_time", self.delaytime_m)
        self.m.update("q", self.q) 
        self.m.update("mix", self.mix) 
        self.m.update("main", self.main) 

        var sample = self.playBuf.next[num_chans=2](self.buf, 1.0 if self.play else 0.0)  # Read samples from the buffer
        deltime = self.delay_time_lag.next(MFloat[2](self.delaytime_m, self.delaytime_m * 0.9))


        fb = MFloat[2](dbamp(self.feedback), dbamp(self.feedback) * 0.9)

        delays = self.delays.next(sample * self.gate_lag.next(1.0 if self.delay_input else 0.0), deltime, fb)
        delays = self.svf.lpf(delays, self.ffreq, self.q)
        output = (self.mix * delays) + ((1.0 - self.mix) * sample)
        output *= dbamp(-12.0)
        output *= self.main_lag.next(1.0 if self.main else 0.0)
        return output

struct FeedbackDelaysGUI(Movable, Copyable):
    var world: World
    var delay_synth: DelaySynth  # Instance of the Oscillator

    def __init__(out self, world: World):
        self.world = world
        self.delay_synth = DelaySynth(self.world)  # Initialize the DelaySynth with the world instance

    def next(mut self: FeedbackDelaysGUI) -> MFloat[2]:
        return self.delay_synth.next()  # Return the combined output sample