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

BiquadEQ

5-band parametric EQ using Biquad filters. Demonstrates: lowshelf, 3x bell (peaking EQ), highshelf

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_python package.
# 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_python.MMMAudio import MMMAudio
from mmm_python.GUI import Handle, ControlSpec
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel


def main():
    mmm_audio = MMMAudio(128, graph_name="BiquadEQ", package_name="examples")
    mmm_audio.start_audio()

    app = QApplication([])
    window = QWidget()
    window.setWindowTitle("5-Band Biquad EQ")
    window.resize(400, 500)
    window.closeEvent = lambda event: (mmm_audio.stop_audio(), event.accept())

    layout = QVBoxLayout()

    # Low Shelf
    layout.addWidget(QLabel("<b>Low Shelf</b>"))
    ls_freq = Handle("LS Freq", ControlSpec(50, 200, 0.5), 100,
                     callback=lambda v: mmm_audio.send_float("ls_freq", v), run_callback_on_init=True)
    ls_gain = Handle("LS Gain (dB)", ControlSpec(-12, 12), 0,
                     callback=lambda v: mmm_audio.send_float("ls_gain", v), run_callback_on_init=True)
    layout.addWidget(ls_freq)
    layout.addWidget(ls_gain)

    # Bell 1
    layout.addWidget(QLabel("<b>Bell 1</b>"))
    b1_freq = Handle("B1 Freq", ControlSpec(200, 500, 0.5), 250,
                     callback=lambda v: mmm_audio.send_float("b1_freq", v), run_callback_on_init=True)
    b1_gain = Handle("B1 Gain (dB)", ControlSpec(-12, 12), 0,
                     callback=lambda v: mmm_audio.send_float("b1_gain", v), run_callback_on_init=True)
    b1_q = Handle("B1 Q", ControlSpec(0.5, 5), 1.0,
                  callback=lambda v: mmm_audio.send_float("b1_q", v), run_callback_on_init=True)
    layout.addWidget(b1_freq)
    layout.addWidget(b1_gain)
    layout.addWidget(b1_q)

    # Bell 2
    layout.addWidget(QLabel("<b>Bell 2</b>"))
    b2_freq = Handle("B2 Freq", ControlSpec(500, 2000, 0.5), 1000,
                     callback=lambda v: mmm_audio.send_float("b2_freq", v), run_callback_on_init=True)
    b2_gain = Handle("B2 Gain (dB)", ControlSpec(-12, 12), 0,
                     callback=lambda v: mmm_audio.send_float("b2_gain", v), run_callback_on_init=True)
    b2_q = Handle("B2 Q", ControlSpec(0.5, 5), 1.0,
                  callback=lambda v: mmm_audio.send_float("b2_q", v), run_callback_on_init=True)
    layout.addWidget(b2_freq)
    layout.addWidget(b2_gain)
    layout.addWidget(b2_q)

    # Bell 3
    layout.addWidget(QLabel("<b>Bell 3</b>"))
    b3_freq = Handle("B3 Freq", ControlSpec(2000, 8000, 0.5), 4000,
                     callback=lambda v: mmm_audio.send_float("b3_freq", v), run_callback_on_init=True)
    b3_gain = Handle("B3 Gain (dB)", ControlSpec(-12, 12), 0,
                     callback=lambda v: mmm_audio.send_float("b3_gain", v), run_callback_on_init=True)
    b3_q = Handle("B3 Q", ControlSpec(0.5, 5), 1.0,
                  callback=lambda v: mmm_audio.send_float("b3_q", v), run_callback_on_init=True)
    layout.addWidget(b3_freq)
    layout.addWidget(b3_gain)
    layout.addWidget(b3_q)

    # High Shelf
    layout.addWidget(QLabel("<b>High Shelf</b>"))
    hs_freq = Handle("HS Freq", ControlSpec(5000, 12000, 0.5), 8000,
                     callback=lambda v: mmm_audio.send_float("hs_freq", v), run_callback_on_init=True)
    hs_gain = Handle("HS Gain (dB)", ControlSpec(-12, 12), 0,
                     callback=lambda v: mmm_audio.send_float("hs_gain", v), run_callback_on_init=True)
    layout.addWidget(hs_freq)
    layout.addWidget(hs_gain)

    window.setLayout(layout)
    window.show()
    app.exec()


if __name__ == "__main__":
    main()

Mojo Code

from mmm_audio import *

struct EQSynth(Movable, Copyable):
    """5-band parametric EQ processor using Biquad filters.

    Demonstrates: lowshelf, 3x bell, highshelf
    """
    var world: World
    var buffer: Buffer
    var num_chans: Int64
    var play_buf: Play
    var lowshelf: Biquad[2]
    var bell1: Biquad[2]
    var bell2: Biquad[2]
    var bell3: Biquad[2]
    var highshelf: Biquad[2]
    var messenger: Messenger

    # EQ parameters
    var ls_freq: Float64
    var ls_gain: Float64
    var b1_freq: Float64
    var b1_gain: Float64
    var b1_q: Float64
    var b2_freq: Float64
    var b2_gain: Float64
    var b2_q: Float64
    var b3_freq: Float64
    var b3_gain: Float64
    var b3_q: Float64
    var hs_freq: Float64
    var hs_gain: Float64

    fn __init__(out self, world: World):
        self.world = world

        # Load the audio buffer
        self.buffer = Buffer.load("resources/Shiverer.wav")
        self.num_chans = self.buffer.num_chans

        # without printing this, the compiler wants to free the buffer for some reason
        print("Loaded buffer with", self.buffer.num_chans, "channels and", self.buffer.num_frames, "frames.")

        self.play_buf = Play(self.world)
        self.lowshelf = Biquad[2](self.world)
        self.bell1 = Biquad[2](self.world)
        self.bell2 = Biquad[2](self.world)
        self.bell3 = Biquad[2](self.world)
        self.highshelf = Biquad[2](self.world)
        self.messenger = Messenger(self.world)

        # Default EQ settings (flat response)
        self.ls_freq = 100.0
        self.ls_gain = 0.0
        self.b1_freq = 250.0
        self.b1_gain = 0.0
        self.b1_q = 1.0
        self.b2_freq = 1000.0
        self.b2_gain = 0.0
        self.b2_q = 1.0
        self.b3_freq = 4000.0
        self.b3_gain = 0.0
        self.b3_q = 1.0
        self.hs_freq = 8000.0
        self.hs_gain = 0.0

    fn next(mut self) -> MFloat[2]:
        self.messenger.update(self.ls_freq, "ls_freq")
        self.messenger.update(self.ls_gain, "ls_gain")
        self.messenger.update(self.b1_freq, "b1_freq")
        self.messenger.update(self.b1_gain, "b1_gain")
        self.messenger.update(self.b1_q, "b1_q")
        self.messenger.update(self.b2_freq, "b2_freq")
        self.messenger.update(self.b2_gain, "b2_gain")
        self.messenger.update(self.b2_q, "b2_q")
        self.messenger.update(self.b3_freq, "b3_freq")
        self.messenger.update(self.b3_gain, "b3_gain")
        self.messenger.update(self.b3_q, "b3_q")
        self.messenger.update(self.hs_freq, "hs_freq")
        self.messenger.update(self.hs_gain, "hs_gain")

        var out = self.play_buf.next[num_chans=2](self.buffer, 1.0, True)

        out = self.lowshelf.lowshelf(out, self.ls_freq, 0.707, self.ls_gain)
        out = self.bell1.bell(out, self.b1_freq, self.b1_q, self.b1_gain)
        out = self.bell2.bell(out, self.b2_freq, self.b2_q, self.b2_gain)
        out = self.bell3.bell(out, self.b3_freq, self.b3_q, self.b3_gain)
        out = self.highshelf.highshelf(out, self.hs_freq, 0.707, self.hs_gain)

        return out

struct BiquadEQ(Movable, Copyable):
    var world: World
    var eq_synth: EQSynth

    fn __init__(out self, world: World):
        self.world = world
        self.eq_synth = EQSynth(self.world)

    fn next(mut self) -> MFloat[2]:
        return self.eq_synth.next()