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