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

Chowning FM synthesis example.

These examples are adapted from Chowning's original paper on FM synthesis and can also be found in "Computer Music" by Dodge and Jerse. pg. 123-127.

Python Code

from mmm_python.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_audio import *

struct ChowningFM(Representable, Movable, Copyable):
    var world: UnsafePointer[MMMWorld] # pointer to the MMMWorld
    var m: Messenger
    var c_osc: Osc[1,1,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(self.world)
        self.c_osc = Osc[1,1,1](self.world)
        self.m_osc = Osc(self.world)
        self.index_env = Env(self.world)
        self.index_env_params = EnvParams()
        self.amp_env = Env(self.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