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

FreeverbExample

A classic reverb effect using the Freeverb UGen.

This example demonstrates how to use the Freeverb graph in MMM-Audio and how to control its parameters via a simple GUI built with PySide6.

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 import *

from mmm_python import *
from PySide6.QtWidgets import *

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

    mmm_audio.start_audio() # start the audio thread - or restart it where it left off

    app = QApplication([])

    # Create the main window
    window = QWidget()
    window.setWindowTitle("Freeverb Controller")
    window.resize(300, 100)
    # stop audio when window is closed
    window.closeEvent = lambda event: (mmm_audio.stop_audio(), event.accept())

    # Create layout
    layout = QVBoxLayout()

    # Create a slider
    roomsize_slider = Handle("room size", ControlSpec(0, 1.0, 4), 0.5, callback=lambda v: mmm_audio.send_float("room_size", v), run_callback_on_init=True)
    layout.addWidget(roomsize_slider)

    lpf = Handle("lpf",ControlSpec(100.0, 20000.0, 0.5), 2000, callback=lambda v: mmm_audio.send_float("lpf_comb", v), run_callback_on_init=True)
    layout.addWidget(lpf)

    added_space = Handle("added space",ControlSpec(0.2, 1.0), 0.0, callback=lambda v: mmm_audio.send_float("added_space", v), run_callback_on_init=True)
    layout.addWidget(added_space)

    mix_slider = Handle("mix",ControlSpec(0.1, 1.0, 0.5), 0.2, callback=lambda v: mmm_audio.send_float("mix", v), run_callback_on_init=True)
    layout.addWidget(mix_slider)

    # 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 *

comptime num_chans = 2

struct FreeverbSynth(Copyable, Movable):
    var world: World 
    var buffer: SIMDBuffer[num_chans]

    var play_buf: Play

    var freeverb: Freeverb[num_chans]
    var m: Messenger

    var room_size: Float64
    var lpf_comb: Float64
    var added_space: Float64
    var mix: Float64

    fn __init__(out self, world: World):
        self.world = world 

        # load the audio buffer 
        self.buffer = SIMDBuffer.load("resources/Shiverer.wav")

        # without printing this, the compiler wants to free the buffer for some reason
        print("Loaded buffer with", self.buffer.num_chans, "channels and", self.buffer.num_frames, "frames.")

        self.play_buf = Play(self.world)
        self.freeverb = Freeverb[num_chans](self.world)

        self.room_size = 0.9
        self.lpf_comb = 1000.0
        self.added_space = 0.5
        self.mix = 0.2

        self.m = Messenger(self.world)

    @always_inline
    fn next(mut self) -> MFloat[2]:

        self.m.update(self.room_size,"room_size")
        self.m.update(self.lpf_comb,"lpf_comb")
        self.m.update(self.added_space,"added_space")
        self.m.update(self.mix,"mix")

        added_space_simd = MFloat[num_chans](self.added_space, self.added_space * 0.99)
        out = self.play_buf.next[num_chans=num_chans](self.buffer, 1.0, True)
        out = self.freeverb.next(out, self.room_size, self.lpf_comb, added_space_simd) * 0.1 * self.mix + out * (1.0 - self.mix)
        return out


struct FreeverbExample(Representable, Movable, Copyable):
    var world: World

    var freeverb_synth: FreeverbSynth  # Instance of the FreeverbSynth

    fn __init__(out self, world: World):
        self.world = world
        self.freeverb_synth = FreeverbSynth(self.world)

    fn __repr__(self) -> String:
        return String("Freeverb_Graph")

    fn next(mut self) -> MFloat[2]:
        #return MFloat[2](0.0)
        return self.freeverb_synth.next()  # Return the combined output sample