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

Grains

this uses the mouse to control granular playback of the buffer left and right moves around in the buffer. up and down controls rate of triggers.

Python Code

from mmm_src.MMMAudio import MMMAudio
mmm_audio = MMMAudio(128, num_output_channels = 8, graph_name="Grains", package_name="examples")
mmm_audio.start_audio() # start the audio thread - or restart it where it left off


mmm_audio.stop_audio() # stop/pause the audio thread

Mojo Code

from mmm_src.MMMWorld import MMMWorld
from mmm_utils.functions import *
from mmm_src.MMMTraits import *

from mmm_dsp.Buffer import *
from mmm_dsp.PlayBuf import *
from mmm_dsp.Osc import *
from mmm_dsp.Filters import VAMoogLadder
from mmm_utils.functions import linexp
from random import random_float64

# THE SYNTH


alias num_output_chans = 2
alias num_simd_chans = next_power_of_two(num_output_chans)

struct Grains(Movable, Copyable):
    var world: UnsafePointer[MMMWorld]
    var buffer: Buffer

    var tgrains: TGrains[10]
    var impulse: Impulse  
    var start_frame: Float64

    fn __init__(out self, world: UnsafePointer[MMMWorld]):
        self.world = world  

        # buffer uses numpy to load a buffer into an N channel array
        self.buffer = Buffer("resources/Shiverer.wav")

        self.tgrains = TGrains[10](self.world)  
        self.impulse = Impulse(self.world)


        self.start_frame = 0.0 

    @always_inline
    fn next(mut self) -> SIMD[DType.float64, num_simd_chans]:

        imp_freq = linlin(self.world[].mouse_y, 0.0, 1.0, 1.0, 20.0)
        var impulse = self.impulse.next_bool(imp_freq, True)  # Get the next impulse sample

        start_frame = linlin(self.world[].mouse_x, 0.0, 1.0, 0.0, self.buffer.num_frames - 1.0)


        # if there are 2 (or fewer) output channels, pan the stereo buffer out to 2 channels by panning the stereo playback with pan2
        # if there are more than 2 output channels, pan each of the 2 channels separately and randomly pan each grain channel to a different speaker
        @parameter
        if num_output_chans == 2:
            out = self.tgrains.next[2](self.buffer, 0, impulse, 1, start_frame, 0.4, random_float64(-1.0, 1.0), 1.0)
            return SIMD[DType.float64, num_simd_chans](out[0], out[1]) # because pan2 outputs a SIMD vector size 2, and we require a SIMD vector of size num_simd_chans, you have to manually make the SIMD vector in this case (the compiler does not agree that num_simd_chans == 2, even though it does)
        else:
            out0 = self.tgrains.next_pan_az[num_simd_chans=num_simd_chans](self.buffer, 0, impulse, 1, start_frame, 0.4, num_output_chans, random_float64(0.0, 1.0), 1.0)
            out1 = self.tgrains.next_pan_az[num_simd_chans=num_simd_chans](self.buffer, 1, impulse, 1, start_frame, 0.4, num_output_chans, random_float64(0.0, 1.0), 1.0)
            return out0 + out1