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

BenjolinExample

Benjolin-inspired Synthesizer

Based on the SuperCollider implementation by Hyppasus.

Ported to MMMAudio by Ted Moore, October 2025

Python Code

import sys
from pathlib import Path

# This example is able to run by pressing the "play" button in VSCode
# that executes the whole file.
# In order to do this, it needs to add the parent directory to the path
# (the next line here) so that it can find the mmm_src and mmm_utils packages.
# If you want to run it line by line in a REPL, skip this line!
sys.path.insert(0, str(Path(__file__).parent.parent))

from mmm_utils.GUI import Handle, ControlSpec
from mmm_src.MMMAudio import MMMAudio
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QCheckBox

# instantiate and load the graph
mmm_audio = MMMAudio(128, graph_name="BenjolinExample", package_name="examples")
mmm_audio.start_audio()

app = QApplication([])

# Create the main window
window = QWidget()
window.setWindowTitle("Benjolin")
window.resize(300, 100)
# stop audio when window is closed
window.closeEvent = lambda event: (mmm_audio.stop_audio(), event.accept())

# Create layout
layout = QVBoxLayout()

def add_handle(name: str, min: float, max: float, exp: float, default: float):
    # make the slider
    slider = Handle(name, ControlSpec(min, max, exp), default, callback=lambda v: mmm_audio.send_float(name, v))
    # add it to the layout
    layout.addWidget(slider)
    # send the default value to the graph
    mmm_audio.send_float(name, default)

add_handle("freq1", 20, 14000.0, 0.25, 100)
add_handle("freq2", 0.1, 14000.0, 0.25, 5)
add_handle("scale", 0, 1.0, 1, 0.1)
add_handle("rungler1", 0, 1.0, 1, 0.1)
add_handle("rungler2", 0, 1.0, 1, 0.1)
add_handle("runglerFiltMul", 0, 1.0, 1.0, 0.5)
add_handle("loop", 0, 1.0, 1, 0.1)
add_handle("filterFreq", 20, 20000, 0.25, 3000)
add_handle("q", 0.1, 8.0, 0.5, 1.0)
add_handle("gain", 0.0, 2.0, 1, 1.0)
add_handle("filterType", 0, 8, 1.0, 0.0)
add_handle("outSignalL", 0, 6, 1.0, 1.0)
add_handle("outSignalR", 0, 6, 1.0, 3.0)

# Set the layout for the main window
window.setLayout(layout)

# Show the window
window.show()

# Start the application's event loop
app.exec()

Mojo Code

"""Benjolin-inspired Synthesizer

Based on the SuperCollider implementation by Hyppasus
https://scsynth.org/t/benjolin-inspired-instrument/1074/1

Ported to MMMAudio by Ted Moore, October 2025
"""

from mmm_src.MMMWorld import MMMWorld
from mmm_utils.Messenger import *
from mmm_utils.functions import *
from mmm_src.MMMTraits import *
from mmm_dsp.Delays import Delay
from math import tanh
from mmm_dsp.Distortion import Latch
from mmm_utils.functions import linlin, midicps
from mmm_dsp.Osc import Osc
from mmm_dsp.Filters import *
from mmm_utils.Print import Print

struct Benjolin(Representable, Movable, Copyable):
    var world: UnsafePointer[MMMWorld]  
    var m: Messenger
    var feedback: Float64
    var rungler: Float64
    var tri1: Osc[interp=2,os_index=1]
    var tri2: Osc[interp=2,os_index=1]
    var pulse1: Osc[interp=2,os_index=1]
    var pulse2: Osc[interp=2,os_index=1]
    var delays: List[Delay[1,3]]
    var latches: List[Latch]
    var filters: List[SVF]
    var filter_outputs: List[Float64]
    var sample_dur: Float64
    var sh: List[Float64]
    var dctraps: List[DCTrap]

    var freq1: Float64
    var freq2: Float64
    var scale: Float64
    var rungler1: Float64
    var rungler2: Float64
    var runglerFiltMul: Float64
    var loop: Float64
    var filterFreq: Float64
    var q: Float64
    var gain: Float64
    var filterType: Float64
    var outSignalL: Float64
    var outSignalR: Float64

    fn __init__(out self, world: UnsafePointer[MMMWorld]):
        self.world = world
        self.m = Messenger(self.world)
        self.feedback = 0.0
        self.rungler = 0.0
        self.tri1 = Osc[interp=2,os_index=1](self.world)
        self.tri2 = Osc[interp=2,os_index=1](self.world)
        self.pulse1 = Osc[interp=2,os_index=1](self.world)
        self.pulse2 = Osc[interp=2,os_index=1](self.world)
        self.delays = List[Delay[1,3]](capacity=8)
        self.latches = List[Latch](capacity=8)
        self.filters = List[SVF](capacity=9)
        self.filter_outputs = List[Float64](capacity=9)
        self.sample_dur = 1.0 / self.world[].sample_rate
        self.sh = List[Float64](capacity=9)
        self.dctraps = List[DCTrap](capacity=2)

        self.freq1 = 40
        self.freq2 = 4
        self.scale = 1
        self.rungler1 = 0.16
        self.rungler2 = 0
        self.loop = 0
        self.filterFreq = 40
        self.runglerFiltMul = 1
        self.q = 0.82
        self.gain = 1
        self.filterType = 0
        self.outSignalL = 1
        self.outSignalR = 3

        for _ in range(8):
            self.delays.append(Delay[1,3](self.world, max_delay_time=0.1))
            self.latches.append(Latch(self.world))

        for _ in range(9):
            self.filters.append(SVF(self.world))
            self.filter_outputs.append(0.0)
            self.sh.append(0.0)

        for _ in range(2):
            self.dctraps.append(DCTrap(self.world))

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

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

        self.m.update(self.freq1,"freq1")
        self.m.update(self.freq2,"freq2")
        self.m.update(self.scale,"scale")
        self.m.update(self.rungler1,"rungler1")
        self.m.update(self.rungler2,"rungler2")
        self.m.update(self.runglerFiltMul,"runglerFiltMul")
        self.m.update(self.loop,"loop")
        self.m.update(self.filterFreq,"filterFreq")
        self.m.update(self.q,"q")
        self.m.update(self.gain,"gain")
        self.m.update(self.filterType,"filterType")
        self.m.update(self.outSignalL,"outSignalL")
        self.m.update(self.outSignalR,"outSignalR")

        tri1 = self.tri1.next((self.rungler*self.rungler1)+self.freq1,osc_type=3)
        tri2 = self.tri2.next((self.rungler*self.rungler2)+self.freq2,osc_type=3)
        pulse1 = self.pulse1.next((self.rungler*self.rungler1)+self.freq1,osc_type=2)
        pulse2 = self.pulse2.next((self.rungler*self.rungler2)+self.freq2,osc_type=2)

        pwm = 1.0 if (tri1 + tri2) > 0.0 else 0.0

        pulse1 = (self.feedback*self.loop) + (pulse1 * ((self.loop * -1) + 1))

        self.sh[0] = 1.0 if pulse1 > 0.5 else 0.0
        # pretty sure this makes no sense, but it matches the original code...:
        self.sh[0] = 1.0 if (1.0 > self.sh[0]) == (1.0 < self.sh[0]) else 0.0
        self.sh[0] = (self.sh[0] * -1) + 1

        self.sh[1] = self.delays[0].next(self.latches[0].next(self.sh[0],pulse2 > 0),self.sample_dur)
        self.sh[2] = self.delays[1].next(self.latches[1].next(self.sh[1],pulse2 > 0),self.sample_dur * 2)
        self.sh[3] = self.delays[2].next(self.latches[2].next(self.sh[2],pulse2 > 0),self.sample_dur * 3)
        self.sh[4] = self.delays[3].next(self.latches[3].next(self.sh[3],pulse2 > 0),self.sample_dur * 4)
        self.sh[5] = self.delays[4].next(self.latches[4].next(self.sh[4],pulse2 > 0),self.sample_dur * 5)
        self.sh[6] = self.delays[5].next(self.latches[5].next(self.sh[5],pulse2 > 0),self.sample_dur * 6)
        self.sh[7] = self.delays[6].next(self.latches[6].next(self.sh[6],pulse2 > 0),self.sample_dur * 7)
        self.sh[8] = self.delays[7].next(self.latches[7].next(self.sh[7],pulse2 > 0),self.sample_dur * 8)

        self.rungler = ((self.sh[0]/(2**8)))+(self.sh[1]/(2**7))+(self.sh[2]/(2**6))+(self.sh[3]/(2**5))+(self.sh[4]/(2**4))+(self.sh[5]/(2**3))+(self.sh[6]/(2**2))+(self.sh[7]/(2**1))

        self.feedback = self.rungler
        self.rungler = midicps(self.rungler * linlin(self.scale,0.0,1.0,0.0,127.0))

        self.filter_outputs[0] = self.filters[0].lpf(pwm * self.gain,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q)
        self.filter_outputs[1] = self.filters[1].hpf(pwm * self.gain,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q)
        self.filter_outputs[2] = self.filters[2].bpf(pwm * self.gain,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q)
        self.filter_outputs[3] = self.filters[3].lpf(pwm * self.gain,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q)
        self.filter_outputs[4] = self.filters[4].peak(pwm * self.gain,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q)
        self.filter_outputs[5] = self.filters[5].allpass(pwm * self.gain,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q)
        self.filter_outputs[6] = self.filters[6].bell(pwm,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q,ampdb(self.gain))
        self.filter_outputs[7] = self.filters[7].highshelf(pwm,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q,ampdb(self.gain))
        self.filter_outputs[8] = self.filters[8].lowshelf(pwm,(self.rungler*self.runglerFiltMul)+self.filterFreq,self.q,ampdb(self.gain))

        filter_output = select(self.filterType,self.filter_outputs) * dbamp(-12)
        filter_output = sanitize(filter_output)

        output = SIMD[DType.float64, 2](0.0, 0.0)
        output[0] = select(self.outSignalL,[tri1, pulse1, tri2, pulse2, pwm, self.sh[0], filter_output])
        output[1] = select(self.outSignalR,[tri1, pulse1, tri2, pulse2, pwm, self.sh[0], filter_output])

        for i in range(len(self.dctraps)):
            output[i] = self.dctraps[i].next(output[i])
            output[i] = tanh(output[i])

        return output * 0.4

struct BenjolinExample(Representable, Movable, Copyable):
    var world: UnsafePointer[MMMWorld]
    var benjolin: Benjolin

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

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

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

        return self.benjolin.next()  # Get the next sample from the Benjolin