For more information about the examples, such as how the Python and Mojo files interact with each other, see the Examples Overview
FM4¶
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_python import *
from mmm_python.GUI import Handle, ControlSpec
from mmm_python import *
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QCheckBox
app = QApplication([])
def main():
mmm_audio = MMMAudio(128, graph_name="FM4", package_name="examples")
mmm_audio.start_audio()
sliders = []
# Create the main window
window = QWidget()
window.setWindowTitle("FM4")
window.resize(600, 100)
# Create main horizontal layout
main_layout = QHBoxLayout()
layouts = []
# Create left vertical layout
layouts.append(QVBoxLayout())
layouts[0].setSpacing(0)
# Create right vertical layout
layouts.append(QVBoxLayout())
layouts[1].setSpacing(0)
# Add both layouts to the main horizontal layout
main_layout.addLayout(layouts[0])
main_layout.addLayout(layouts[1])
def add_handle(name: str, min: float, max: float, exp: float, default: float, layout_index: int = 0, resolution: int = 1000):
# make the slider
slider = Handle(name, ControlSpec(min, max, exp), default, callback=lambda v: mmm_audio.send_float(name, v))
sliders.append(slider)
# add it to the layout
layouts[layout_index].addWidget(slider)
# send the default value to the graph
mmm_audio.send_float(name, default)
add_handle("osc0_freq", 0.2, 4000.0, 0.125, 100)
add_handle("osc1_freq", 0.2, 4000.0, 0.125, 10)
add_handle("osc2_freq", 0.2, 4000.0, 0.125, 10)
add_handle("osc3_freq", 0.2, 4000.0, 0.125, 10)
add_handle("osc0_mula", 0, 3000.0, 1, 0)
add_handle("osc0_mulb", 0, 3000.0, 1, 0)
add_handle("osc1_mula", 0, 3000.0, 1, 0)
add_handle("osc1_mulb", 0, 3000.0, 1, 0)
add_handle("osc2_mula", 0, 3000.0, 1, 0)
add_handle("osc2_mulb", 0, 3000.0, 1, 0)
add_handle("osc3_mula", 0, 3000.0, 1, 0, 1)
add_handle("osc3_mulb", 0, 3000.0, 1, 0, 1)
add_handle("osc_frac0", 0, 1.0, 1, 0, 1, 4)
add_handle("osc_frac1", 0, 1.0, 1, 0, 1, 4)
add_handle("osc_frac2", 0, 1.0, 1, 0, 1, 4)
add_handle("osc_frac3", 0, 1.0, 1, 0, 1, 4)
button = QPushButton("randomize")
button.clicked.connect(lambda: [s.set_value(s.spec.unnormalize(rrand(0.0001, 1.0))) for s in sliders])
layouts[1].addWidget(button)
# window.closeEvent = lambda event: (app.quit())
# Set the layout for the main window
window.closeEvent = lambda event: (mmm_audio.exit_all(), event.accept())
window.setLayout(main_layout)
# Show the window
window.show()
window.raise_()
# Start the application's event loop
app.exec()
if __name__ == "__main__":
main()
Mojo Code¶
from mmm_audio import *
struct FM4(Movable, Copyable):
var world: World
comptime os_index = 2
comptime times_oversampling = 1 << Self.os_index
var over: Oversampling[2, Self.times_oversampling]
var osc0: Osc[1, Interp.sinc, 0]
var osc1: Osc[1, Interp.sinc, 0]
var osc2: Osc[1, Interp.sinc, 0]
var osc3: Osc[1, Interp.sinc, 0]
var osc0_freq: MFloat[1]
var osc1_freq: MFloat[1]
var osc2_freq: MFloat[1]
var osc3_freq: MFloat[1]
var osc0_mul: List[MFloat[1]]
var osc1_mul: List[MFloat[1]]
var osc2_mul: List[MFloat[1]]
var osc3_mul: List[MFloat[1]]
var m: Messenger
var fb: List[MFloat[1]]
var osc_frac: List[MFloat[1]]
fn __init__(out self, world: World) :
self.world = world
self.over = Oversampling[2, Self.times_oversampling](world)
self.osc0 = Osc[1, Interp.sinc, 0](world)
self.osc1 = Osc[1, Interp.sinc, 0](world)
self.osc2 = Osc[1, Interp.sinc, 0](world)
self.osc3 = Osc[1, Interp.sinc, 0](world)
self.osc0.phasor.freq_mul /= Self.times_oversampling
self.osc1.phasor.freq_mul /= Self.times_oversampling
self.osc2.phasor.freq_mul /= Self.times_oversampling
self.osc3.phasor.freq_mul /= Self.times_oversampling
self.osc0_freq = 220.0
self.osc1_freq = 440.0
self.osc2_freq = 220.0
self.osc3_freq = 220.0
self.osc0_mul = [0.0, 0.0]
self.osc1_mul = [0.0, 0.0]
self.osc2_mul = [0.0, 0.0]
self.osc3_mul = [0.0, 0.0]
self.m = Messenger(world)
self.fb = [0.0, 0.0, 0.0, 0.0]
self.osc_frac = [0.0, 0.0, 0.0, 0.0]
fn next(mut self) -> MFloat[2]:
self.m.update(self.osc0_freq, "osc0_freq")
self.m.update(self.osc1_freq, "osc1_freq")
self.m.update(self.osc2_freq, "osc2_freq")
self.m.update(self.osc3_freq, "osc3_freq")
self.m.update(self.osc0_mul[0], "osc0_mula")
self.m.update(self.osc0_mul[1], "osc0_mulb")
self.m.update(self.osc1_mul[0], "osc1_mula")
self.m.update(self.osc1_mul[1], "osc1_mulb")
self.m.update(self.osc2_mul[0], "osc2_mula")
self.m.update(self.osc2_mul[1], "osc2_mulb")
self.m.update(self.osc3_mul[0], "osc3_mula")
self.m.update(self.osc3_mul[1], "osc3_mulb")
self.m.update(self.osc_frac[0], "osc_frac0")
self.m.update(self.osc_frac[1], "osc_frac1")
self.m.update(self.osc_frac[2], "osc_frac2")
self.m.update(self.osc_frac[3], "osc_frac3")
for i in range(Self.times_oversampling):
fm_0 = self.fb[1] * self.osc0_mul[0] + self.fb[2] * self.osc0_mul[1]
osc0 = self.osc0.next_basic_waveforms(self.osc0_freq + fm_0, osc_frac=self.osc_frac[0])
fm_1 = osc0 * self.osc1_mul[0] + self.fb[3] * self.osc1_mul[1]
osc1 = self.osc1.next_basic_waveforms(self.osc1_freq + fm_1, osc_frac=self.osc_frac[1])
fm_2 = osc1 * self.osc2_mul[0] + self.fb[3] * self.osc2_mul[1]
osc2 = self.osc2.next_basic_waveforms(self.osc2_freq + fm_2, osc_frac=self.osc_frac[2])
fm_3 = osc0 * self.osc3_mul[0] + osc1 * self.osc3_mul[1]
osc3 = self.osc3.next_basic_waveforms(self.osc3_freq + fm_3, osc_frac=self.osc_frac[3])
self.fb = [osc0, osc1, osc2, osc3]
self.over.add_sample(MFloat[2](osc0, osc1))
return self.over.get_sample()