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

PanAzExample

Shows how to use PanAz to pan audio between multiple speakers arranged in a circle. You can set the number of speakers between 2 and 8 using the "num_speakers" parameter. You can set the width of the panning using the "width" parameter. You can set the frequency of the input tone using the "freq" parameter.

PanAz outputs a SIMD array with one channel per speaker. Since SIMD arrays must be a power of 2 in size, the num_speakers parameter must be set to a value below or equal to the size of the SIMD array (8 in this case). Any unused channels will be silent.

Python Code

from mmm_python.MMMAudio import MMMAudio

# instantiate and load the graph

# PanAz is not quite right as of yet
mmm_audio = MMMAudio(128, graph_name="PanAzExample", package_name="examples", num_output_channels=8)
mmm_audio.start_audio() 

mmm_audio.send_int("num_speakers", 2 ) # set the number of speakers to between 2 and 8

mmm_audio.send_int("num_speakers", 7 ) # set the number of speakers to between 2 and 8
mmm_audio.send_float("width", 1.0 ) # set the width to 1.0 (one speaker at a time)
mmm_audio.send_float("width", 3.0 ) # set the width to 3.0 (extra wide stereo width)

from random import random
mmm_audio.send_float("freq", random() * 500 + 100 ) # set the frequency to a random value

Mojo Code

from mmm_audio import *

struct PanAz_Synth(Representable, Movable, Copyable):
    var world: UnsafePointer[MMMWorld]  
    var osc: Osc
    var freq: Float64

    var pan_osc: Phasor
    var num_speakers: Int64
    var width: Float64
    var messenger: Messenger

    fn __init__(out self, world: UnsafePointer[MMMWorld]):
        self.world = world
        self.osc = Osc(self.world)
        self.freq = 440.0

        self.pan_osc = Phasor(self.world)
        self.num_speakers = 7  # default to 7 speakers
        self.width = 2.0
        self.messenger = Messenger(self.world)

    fn __repr__(self) -> String:
        return String("Default")

    fn next(mut self) -> SIMD[DType.float64, 8]:
        self.messenger.update(self.freq, "freq")
        self.messenger.update(self.num_speakers, "num_speakers")
        self.messenger.update(self.width, "width")

        # PanAz needs to be given a SIMD size that is a power of 2, in this case [8], but the speaker size can be anything smaller than that
        panned = pan_az[8](self.osc.next(self.freq, osc_type=2), self.pan_osc.next(0.1), self.num_speakers, self.width) * 0.1

        if self.num_speakers == 2:
            return SIMD[DType.float64, 8](panned[0], panned[1], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
        else:
            return SIMD[DType.float64, 8](panned[0], panned[2], panned[1], 0.0, panned[6], panned[3], panned[5], panned[4])


# there can only be one graph in an MMMAudio instance
# a graph can have as many synths as you want
struct PanAzExample(Representable, Movable, Copyable):
    var world: UnsafePointer[MMMWorld]
    var synth: PanAz_Synth

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

    fn __repr__(self) -> String:
        return String("PanAzExample")

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

        sample = self.synth.next()  # Get the next sample from the synth

        # the output will pan to the number of channels available 
        # if there are fewer than 5 channels, only those channels will be output
        return sample  # Return the combined output samples