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

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.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

mmm_audio.plot(20000)

Mojo Code

from mmm_audio import *

# 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] # set the number of simultaneous grains by setting the max_grains parameter here
    var impulse: Phasor  
    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.load("resources/Shiverer.wav")

        self.tgrains = TGrains[10](self.world)  
        self.impulse = Phasor(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, 0, True)  # Get the next impulse sample

        start_frame = Int64(linlin(self.world[].mouse_x, 0.0, 1.0, 0.0, Float64(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, 1, impulse, start_frame, 0.4, 0, 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:
            # pan each channel separately to num_output_chans speakers
            out0 = self.tgrains.next_pan_az[num_simd_chans=num_simd_chans](self.buffer, 1, impulse, start_frame, 0.4, 0, random_float64(-1.0, 1.0), 1.0, num_output_chans)

            out1 = self.tgrains.next_pan_az[num_simd_chans=num_simd_chans](self.buffer, 1, impulse, start_frame, 0.4, 1, random_float64(-1.0, 1.0), 1.0, num_output_chans)

            return out0 + out1