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

PitchShiftExample

Demonstrates how to use the PitchShift grain-based pitch shifter with microphone input.

This example assumes you have a microphone input device set up and selected as the default input device on your system.

A couple of settings in the .py file are important:

  • num_input_channels: This can be set to any value, but it should be at least as high as the input channel you want to use.
  • in_chan: This should be set to the input channel number of your microphone input source (0-indexed).

Python Code

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from mmm_python import *

def main():

    mmm_audio = MMMAudio(128, num_input_channels = 12, graph_name="PitchShiftExample", package_name="examples")
    mmm_audio.send_int("in_chan", 0) # set input channel to your input source
    mmm_audio.start_audio() # start the audio thread - or restart it where it left off

    app = QApplication([])

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

    # Create layout
    layout = QVBoxLayout()

    pitch_shift_slider = Handle("pitch_shift",ControlSpec(0.25, 4.0, 0.5), 1, callback=lambda v: mmm_audio.send_float("pitch_shift", v), run_callback_on_init=True)
    layout.addWidget(pitch_shift_slider)

    grain_dur_slider = Handle("grain_dur", ControlSpec(0.1, 1.0, 1), 0.4, callback=lambda v: mmm_audio.send_float("grain_dur", v))
    layout.addWidget(grain_dur_slider)

    pitch_dispersion_slider = Handle("pitch_dispersion", ControlSpec(0.0, 1.0, 1), 0, callback=lambda v: mmm_audio.send_float("pitch_dispersion", v), run_callback_on_init=True)
    layout.addWidget(pitch_dispersion_slider)

    time_dispersion_slider = Handle("time_dispersion", ControlSpec(0.0, 1.0, 1), 0, callback=lambda v: mmm_audio.send_float("time_dispersion", v), run_callback_on_init=True)
    layout.addWidget(time_dispersion_slider)

    added_delay_low_slider = Handle("added_delay_low", ControlSpec(0.0, 2.0, 1), 0, callback=lambda v: mmm_audio.send_float("added_delay_low", v), run_callback_on_init=True)
    layout.addWidget(added_delay_low_slider)

    added_delay_high_slider = Handle("added_delay_high", ControlSpec(0.0, 2.0, 1), 0, callback=lambda v: mmm_audio.send_float("added_delay_high", v), run_callback_on_init=True)
    layout.addWidget(added_delay_high_slider)

    overlaps_slider = Handle("overlaps", ControlSpec(1, 16, 1), 4, callback=lambda v: mmm_audio.send_int("overlaps", int(v)), run_callback_on_init=True)
    layout.addWidget(overlaps_slider)

    fb_perc_slider = Handle("feedback", ControlSpec(0.0, 1.0, 1), 0, callback=lambda v: mmm_audio.send_float("fb_perc", v), run_callback_on_init=True)
    layout.addWidget(fb_perc_slider)

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

if __name__ == "__main__":
    main()

Mojo Code

from mmm_audio import *

# THE SYNTH

struct PitchShiftExample(Representable, Movable, Copyable):
    var world: World

    var pitch_shift: PitchShift[num_chans=2]
    var messenger: Messenger
    var shift: Float64
    var grain_dur: Float64
    var pitch_dispersion: Float64
    var time_dispersion: Float64
    var in_chan: Int
    var which_input: Float64
    var noise: WhiteNoise[]
    var overlaps: Int
    var added_delay_low: Float64
    var added_delay_high: Float64
    var fb: MFloat[2]
    var fb_perc: Float64
    var dc_trap: DCTrap[2]

    fn __init__(out self, world: World):
        self.world = world
        self.pitch_shift = PitchShift[num_chans=2](self.world, 2.0) # the duration of the buffer needs to == grain size*(max_pitch_shift-1).
        self.messenger = Messenger(self.world)
        self.shift = 1.0
        self.grain_dur = 0.2
        self.pitch_dispersion = 0.0
        self.time_dispersion = 0.0
        self.in_chan = 0
        self.which_input = 0.0
        self.noise = WhiteNoise()
        self.overlaps = 4
        self.added_delay_low = 0.0
        self.added_delay_high = 0.0
        self.fb = MFloat[2](0.0, 0.0)
        self.fb_perc = 0.0
        self.dc_trap = DCTrap[2](world)

    @always_inline
    fn next(mut self) -> MFloat[2]:
        self.messenger.update(self.in_chan, "in_chan")

        self.messenger.update(self.shift,"pitch_shift")
        self.messenger.update(self.grain_dur,"grain_dur")
        self.messenger.update(self.pitch_dispersion,"pitch_dispersion")
        self.messenger.update(self.time_dispersion,"time_dispersion")
        self.messenger.update(self.overlaps,"overlaps")
        self.messenger.update(self.added_delay_low,"added_delay_low")
        self.messenger.update(self.added_delay_high,"added_delay_high")
        self.messenger.update(self.fb_perc,"fb_perc")

        temp = self.world[].sound_in[self.in_chan]
        input_sig = MFloat[2](temp, temp) + (self.fb * self.fb_perc)

        out = self.pitch_shift.next(input_sig, self.grain_dur, self.overlaps, self.shift, self.pitch_dispersion, self.time_dispersion, self.added_delay_low, self.added_delay_high)
        self.fb = self.dc_trap.next(out)

        return out

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