For more information about the examples, such as how the Python and Mojo files interact with each other, see the Examples Overview
Grains¶
Demonstrates granular synthesis using TGrains, using a mouse to control granular playback.
Left and right moves around in the buffer. Up and down controls rate of triggers.
Python Code¶
from mmm_python import *
mmm_audio = MMMAudio(128, num_output_channels = 8, graph_name="Grains", package_name="examples")
mmm_audio.start_audio()
# for Wayland use the fake mouse
MMMAudio.fake_mouse()
# when using a user defined env, setting the grain envelope should change the sound dramatically
mmm_audio.send_floats("env_points", [0.0, 0.0, 0.01, 1.0, 1.0, 0.0])
mmm_audio.send_floats("env_points", [0.0, 0.0, 0.5, 1.0, 1.0, 0.0])
mmm_audio.send_floats("env_points", [0.0, 0.0, 0.1, 1.0, 0.2, 0.75, 0.8, 0.75, 1.0,0.0])
# this will increase the trig rate, but there won't be enough grains
# so increase the number of grains
mmm_audio.send_float("max_trig_rate", 80.0)
mmm_audio.send_int("set_num_grains", 40)
mmm_audio.stop_audio()
# the below version is the same except it uses a custom grain with a BandPass filter embedded directly in the grain
from mmm_python import *
mmm_audio = MMMAudio(128, num_output_channels = 2, graph_name="Grains_Custom", package_name="examples")
mmm_audio.start_audio()
# setting the grain envelope should change the sound dramatically
mmm_audio.send_floats("env_points", [0.0, 0.0, 0.01, 1.0, 1.0, 0.0])
mmm_audio.send_floats("env_points", [0.0, 0.0, 0.5, 1.0, 1.0, 0.0])
mmm_audio.send_floats("env_points", [0.0, 0.0, 0.1, 1.0, 0.2, 0.75, 0.8, 0.75, 1.0,0.0])
MMMAudio.get_audio_devices()
dbamp(-120)
Mojo Code¶
from mmm_audio import *
# THE SYNTH
comptime num_speakers = 2
comptime num_simd_chans = 2
struct Grains(Movable, Copyable):
var world: World
var buffer: SIMDBuffer[2]
var tgrains: TGrains[win_type = WindowType.hann] # try changing to WindowType.user_defined
var impulse: Phasor[1]
var start_frame: Float64
var m: Messenger
var max_trig_rate: Float64
var points_temp: List[Float64]
def __init__(out self, world: World):
self.world = world
# buffer uses numpy to load a buffer into an N channel array
self.buffer = SIMDBuffer[2].load("resources/Shiverer.wav")
self.tgrains = TGrains[win_type = WindowType.hann](self.world, 10) # Set the number of simultaneous grains
self.impulse = Phasor[1](self.world)
self.m = Messenger(world)
self.max_trig_rate = 20.0
self.points_temp = List[Float64]()
self.start_frame = 0.0
@always_inline
def next(mut self) -> MFloat[num_simd_chans]:
self.m.update("max_trig_rate", self.max_trig_rate)
new_points = self.m.notify_update("env_points", self.points_temp)
if new_points:
self.tgrains.set_env_points(self.points_temp)
num_grains = 0
if self.m.notify_update("set_num_grains", num_grains):
self.tgrains.set_num_grains(num_grains)
imp_freq = linlin(self.world[].mouse_y(), 0.0, 1.0, 1.0, self.max_trig_rate)
var impulse = self.impulse.next_bool(imp_freq, 0, True)
start_frame = Int(linlin(self.world[].mouse_x(), 0.0, 1.0, 0.0, Float64(self.buffer.num_frames) - 1.0))
comptime if num_speakers == 2:
grain_num = self.tgrains.trig(impulse)
if grain_num >= 0:
self.tgrains.grains[grain_num].set_vals(1, start_frame, 0.4, random_float64(-1.0, 1.0), 1.0, 0)
out = self.tgrains.next_2[2](self.buffer)
return MFloat[num_simd_chans](out[0], out[1])
else:
grain_num = self.tgrains.trig(impulse)
if grain_num >= 0:
self.tgrains.grains[grain_num].set_vals(1, start_frame, 0.4, random_float64(-1.0, 1.0), 1.0, 0)
out2 = self.tgrains.next_multi_channel[num_speakers=num_speakers, num_simd_chans=num_simd_chans](self.buffer, 0, 1.0)
return out2