For more information about the examples, such as how the Python and Mojo files interact with each other, see the Examples Overview
PlayExample¶
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.
This example has a corresponding TouchOSC file.
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.MMMAudio import MMMAudio
mmm_audio = MMMAudio(128, graph_name="PlayExample", package_name="examples")
mmm_audio.start_audio() # start the audio thread - or restart it where it left off
# this example uses open sound control to control Play's playback speed and VAMoogFilter's cutoff frequency
# there is a simple touchosc patch provided for control
# it is looking for /fader1 and /fader2 on port 5005; these can be adjusted
# Start the OSC server on its own thread
# this is a bug, but this thread has to start after audio or audio won't start
from mmm_python.OSCServer import OSCServer
from mmm_python.python_utils import *
# Usage:
def osc_msg_handler(key, *args):
print(f"Received OSC message: {key} with arguments: {args}")
if key == "/fader1":
val = lincurve(args[0], 0.0, 1.0, -4.0, 4.0, -1)
print(f"Mapped play_rate value: {val}")
mmm_audio.send_float("play_rate", val)
elif key == "/fader2":
val = linexp(args[0], 0.0, 1.0, 100.0, 20000.0)
mmm_audio.send_float("lpf_freq", val)
# Start server
osc_server = OSCServer("0.0.0.0", 5005, osc_msg_handler)
osc_server.start()
Mojo Code¶
from mmm_audio import *
struct BufSynth(Representable, Movable, Copyable):
var world: UnsafePointer[MMMWorld]
var buffer: Buffer
var num_chans: Int64
var play_buf: Play
var play_rate: Float64
var moog: VAMoogLadder[2, 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: UnsafePointer[MMMWorld]):
self.world = world
# load the audio buffer
self.buffer = Buffer.load("resources/Shiverer.wav")
self.num_chans = self.buffer.num_chans
# 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.play_buf = Play(self.world)
self.moog = VAMoogLadder[2, 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) -> SIMD[DType.float64, 2]:
self.messenger.update(self.lpf_freq, "lpf_freq")
self.messenger.update(self.play_rate, "play_rate")
out = self.play_buf.next[num_chans=2](self.buffer, self.play_rate, True)
freq = self.lpf_freq_lag.next(self.lpf_freq)
out = self.moog.next(out, freq, 1.0)
return out
fn __repr__(self) -> String:
return String("BufSynth")
struct PlayExample(Representable, Movable, Copyable):
var world: UnsafePointer[MMMWorld]
var buf_synth: BufSynth # Instance of the GrainSynth
fn __init__(out self, world: UnsafePointer[MMMWorld]):
self.world = world
self.buf_synth = BufSynth(self.world)
fn __repr__(self) -> String:
return String("PlayExample")
fn next(mut self) -> SIMD[DType.float64, 2]:
return self.buf_synth.next() # Return the combined output sample