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

ChowningFM

These examples are adapted from Chowning's original paper on FM synthesis: https://web.eecs.umich.edu/~fessler/course/100/misc/chowning-73-tso.pdf and can also be found in "Computer Music" by Dodge and Jerse. pg. 123-127.

Python Code

from mmm_src.MMMAudio import MMMAudio
mmm_audio = MMMAudio(128, graph_name="ChowningFM", package_name="examples")
mmm_audio.start_audio()

# bell

mmm_audio.send_float("m_freq", 280.0)
mmm_audio.send_float("c_freq", 200.0)
mmm_audio.send_floats("amp_vals",[0.0,1.0,0.2,0.0])
mmm_audio.send_floats("amp_times",[0.001,1.8,1.7])
mmm_audio.send_floats("amp_curves",[1,1,1])
mmm_audio.send_floats("index_vals",[10.0,2.0,0.0])
mmm_audio.send_floats("index_times",[1.8,1.7])
mmm_audio.send_floats("index_curves",[1,1])
mmm_audio.send_trig("trigger")

# woodblock
mmm_audio.send_float("m_freq", 55.0)
mmm_audio.send_float("c_freq", 80.0)
mmm_audio.send_floats("amp_vals",[0.75, 1.0,  0.6,  0.2, 0.0])
mmm_audio.send_floats("amp_times",[0.02, 0.02, 0.06, 0.1])
mmm_audio.send_floats("amp_curves",[1,    1,    1,    1])
mmm_audio.send_floats("index_vals",[25.0, 0.0])
mmm_audio.send_floats("index_times",[0.012])
mmm_audio.send_floats("index_curves",[1])
mmm_audio.send_trig("trigger")

# brass

mmm_audio.send_float("m_freq", 440.0)
mmm_audio.send_float("c_freq", 440.0)
mmm_audio.send_floats("amp_vals",[0, 1, 0.7, 0.7, 0])
mmm_audio.send_floats("amp_times",[0.075, 0.050, 0.4, 0.06])
mmm_audio.send_floats("amp_curves",[1, 1, 1, 1])
mmm_audio.send_floats("index_vals",[0, 5, 3.5, 3.5, 0])
mmm_audio.send_floats("index_times",[0.075, 0.050, 0.4, 0.06])
mmm_audio.send_floats("index_curves",[1,1,1,1])
mmm_audio.send_trig("trigger")

# brass (less bright)
mmm_audio.send_float("m_freq", 440.0)
mmm_audio.send_float("c_freq", 440.0)
mmm_audio.send_floats("amp_vals",[0, 1, 0.7, 0.7, 0])
mmm_audio.send_floats("amp_times",[0.075, 0.050, 0.4, 0.06])
mmm_audio.send_floats("amp_curves",[1, 1, 1, 1])
mmm_audio.send_floats("index_vals",[0, 3, 2.1, 2.1, 0])
mmm_audio.send_floats("index_times",[0.075, 0.050, 0.4, 0.06])
mmm_audio.send_floats("index_curves",[1,1,1,1])
mmm_audio.send_trig("trigger")

# clarinet
mmm_audio.send_float("m_freq", 600.0)
mmm_audio.send_float("c_freq", 900.0)
mmm_audio.send_floats("amp_vals",[0, 1, 1, 0])
mmm_audio.send_floats("amp_times",[0.087, 0.4, 0.087])
mmm_audio.send_floats("amp_curves",[1, 1, 1])
mmm_audio.send_floats("index_vals",[4, 2])
mmm_audio.send_floats("index_times",[0.073])
mmm_audio.send_floats("index_curves",[1])
mmm_audio.send_trig("trigger")

# stop audio
mmm_audio.stop_audio()

Mojo Code

from mmm_src.MMMWorld import MMMWorld
from mmm_utils.functions import *
from mmm_src.MMMTraits import *
from mmm_dsp.Osc import Osc
from mmm_dsp.Env import *
from collections import Dict
from mmm_utils.Messenger import *

struct ChowningFM(Representable, Movable, Copyable):
    var world: UnsafePointer[MMMWorld] # pointer to the MMMWorld
    var m: Messenger
    var c_osc: Osc[1,0,1]  # Carrier oscillator
    var m_osc: Osc  # Modulator oscillator
    var index_env: Env
    var index_env_params: EnvParams
    var amp_env: Env
    var amp_env_params: EnvParams
    var cfreq: Float64
    var mfreq: Float64
    var vol: Float64

    fn __init__(out self, world: UnsafePointer[MMMWorld]):
        self.world = world
        self.m = Messenger(world)
        self.c_osc = Osc[1,0,1](world)
        self.m_osc = Osc(world)
        self.index_env = Env(world)
        self.index_env_params = EnvParams()
        self.amp_env = Env(world)
        self.amp_env_params = EnvParams()
        self.cfreq = 200.0
        self.mfreq = 100.0
        self.vol = -12.0

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

    @always_inline
    fn update_envs(mut self):

        self.m.update(self.index_env_params.values,"index_vals")
        self.m.update(self.index_env_params.times,"index_times")
        self.m.update(self.index_env_params.curves,"index_curves")
        self.m.update(self.amp_env_params.values,"amp_vals")
        self.m.update(self.amp_env_params.times,"amp_times")
        self.m.update(self.amp_env_params.curves,"amp_curves")

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

        self.m.update(self.cfreq,"c_freq")
        self.m.update(self.mfreq,"m_freq")
        self.m.update(self.vol,"vol")
        trig = self.m.notify_trig("trigger")
        self.update_envs()

        index = self.index_env.next(self.index_env_params, trig)
        msig = self.m_osc.next(self.mfreq) * self.mfreq * index
        csig = self.c_osc.next(self.cfreq + msig)
        csig *= self.amp_env.next(self.amp_env_params, trig)
        csig *= dbamp(self.vol)

        return csig