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