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

PlayRecExample

Shows how to load and audio buffer from a sound file and play it using the Play UGen.

This example uses open sound control to control Play's playback speed and VAMoogFilter's cutoff frequency. These can be sent from a simple touchosc patch or any other OSC controller. A touchosc patch is provided for control.

This example is able to run by pressing the "play" button in VSCode or compiling and running the whole file on the command line.

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 PySide6.QtWidgets import QWidget, QVBoxLayout, QSlider, QPushButton, QLabel, QLineEdit, QFileDialog
from PySide6.QtCore import Qt

def open_save_dialog(parent: QWidget) -> str:
    """Opens a save file dialog and returns the chosen filename."""
    filename, selected_filter = QFileDialog.getSaveFileName(parent, "Save File", "", "WAV Files (*.wav)")
    if filename:
        print(f"File chosen: {filename}")
        return filename
    else:
        print("Dialog was cancelled")
        return None

def open_load_dialog(parent: QWidget) -> str:
    """Opens a load file dialog and returns the chosen filename."""
    filename, _ = QFileDialog.getOpenFileName(parent, "Load File", "", "WAV Files (*.wav)")
    if filename:
        print(f"File chosen: {filename}")
        return filename
    else:
        print("Dialog was cancelled")
        return None


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

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

    app = QApplication(sys.argv)
    # Create the main window
    window = QWidget()
    window.setWindowTitle("Play Example")
    window.resize(300, 100)
    # stop audio when window is closed
    window.closeEvent = lambda event: (mmm_audio.exit_all(), event.accept())

    layout = QVBoxLayout()
    handle = Handle("PlayRate", ControlSpec(0.25,4,0.5), 1, lambda v: mmm_audio.send_float("play_rate", v), run_callback_on_init=True)
    layout.addWidget(handle)

    handle = Handle("LPFFrequency", ControlSpec(100, 20000, 0.5), 20000, 
                    lambda v: mmm_audio.send_float("lpf_freq", v), run_callback_on_init=True)
    layout.addWidget(handle)

    # Load Button
    load_button = QPushButton("Load Audio File")
    load_button.clicked.connect(lambda: mmm_audio.send_string("load_buffer", open_load_dialog(window)))
    layout.addWidget(load_button)

    # Save Button
    rec_button = QPushButton("Start Recording")
    rec_button.clicked.connect(lambda: mmm_audio.send_trig("start_recording"))
    layout.addWidget(rec_button)
    # Save Button
    stop_button = QPushButton("Stop Recording")
    stop_button.clicked.connect(lambda: mmm_audio.send_trig("stop_recording"))
    layout.addWidget(stop_button)

    # Save Button
    save_button = QPushButton("Save Audio File")
    save_button.clicked.connect(lambda: mmm_audio.send_string("save_buffer", open_save_dialog(window)))
    layout.addWidget(save_button)

    window.setLayout(layout)

    window.show()


    sys.exit(app.exec())

if __name__ == "__main__":
    main()

Mojo Code

from mmm_audio import *

comptime num_chans = 2

struct PlayRecExample(Movable, Copyable):
    var world: World
    var buffer: SIMDBuffer[2]
    var num_chans: Int

    var play_buf: Play
    var play_rate: Float64
    var filepath: String
    var recorder: Recorder[2]
    var record_bool: Bool

    var moog: VAMoogLadder[num_chans, 1] # 2 channels, os_index == 1 (2x oversampling)
    var lpf_freq: Float64
    var lpf_freq_lag: Lag[]
    var messenger: Messenger

    fn __init__(out self, world: World):
        self.world = world 
        print("world memory location:", world)

        # load the audio buffer 
        self.filepath = "resources/Shiverer.wav"
        self.buffer = SIMDBuffer[2].load(self.filepath)
        self.num_chans = self.buffer.num_chans  

        self.recorder = Recorder[2](self.world, Int(10*world[].sample_rate), world[].sample_rate)  # Initialize the recorder for 2 channels

        # 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_rate = 1.0
        self.record_bool = False

        self.play_buf = Play(self.world)

        self.moog = VAMoogLadder[num_chans, 1](self.world)
        self.lpf_freq = 20000.0
        self.lpf_freq_lag = Lag(self.world, 0.1)

        self.messenger = Messenger(self.world)

    fn next(mut self) -> MFloat[num_chans]:
        self.messenger.update(self.lpf_freq, "lpf_freq")
        self.messenger.update(self.play_rate, "play_rate")
        load_buffer = self.messenger.notify_update(self.filepath, "load_buffer")
        if load_buffer:
            print("Loading new buffer from:", self.filepath)
            temp_buffer = SIMDBuffer.load(self.filepath)
            if temp_buffer.num_frames > 0 and temp_buffer.num_chans > 0:
                self.buffer = temp_buffer^
                self.play_buf.reset_phase()
        start_rec = self.messenger.notify_trig("start_recording")
        if start_rec:
            print("Starting recording to internal buffer.")
            self.recorder.write_head = 0
            self.record_bool = True
        stop_rec = self.messenger.notify_trig("stop_recording")
        if stop_rec:
            print("Stopping recording.")
            self.record_bool = False


        save_buffer = self.messenger.notify_update(self.filepath, "save_buffer")
        if save_buffer:
            print("Saving current buffer to:", self.filepath)
            self.recorder.buf.write_to_file(self.filepath, self.recorder.write_head)
            print("Saved", self.recorder.write_head, "samples to file.")

        out = self.play_buf.next[num_chans=num_chans](self.buffer, self.play_rate, True)
        if self.record_bool:
            self.recorder.write_next[loop=False](out)

        freq = self.lpf_freq_lag.next(self.lpf_freq)
        out = self.moog.next(out, freq, 1.0)
        return out