For more information about the examples, such as how the Python and Mojo files interact with each other, see the Examples Overview
PaulStretch¶
PaulStretch is an FFT – based extreme time-stretching algorithm invented by Paul Nasca in 2006. The algorithm works similar to a granular time stretch, but each grain is analyzed by an FFT, the phase is randomized, and then transformed back to the time domain using IFFT. This results in a very smooth and ambient sound when stretching audio to extreme lengths.
This example shows how to use the PaulStretch graph in MMM-Audio to stretch audio in real-time. You can change the stretch factor by sending different float values to the "dur_mult" parameter.
Python Code¶
from mmm_python.MMMAudio import MMMAudio
mmm_audio = MMMAudio(2048, graph_name="PaulStretch", package_name="examples")
mmm_audio.start_audio()
# change how slow the audio gets stretched
mmm_audio.send_float("dur_mult", 10.0)
mmm_audio.send_float("dur_mult", 100.0)
mmm_audio.send_float("dur_mult", 40.0)
mmm_audio.send_float("dur_mult", 10000.0)
mmm_audio.stop_audio()
Mojo Code¶
from mmm_audio import *
from random import random_float64
# this really should have a window size of 8192 or more, but the numpy FFT seems to barf on this
alias window_size = 2048
alias hop_size = window_size // 2
struct PaulStretchWindow[window_size: Int](FFTProcessable):
var world: UnsafePointer[MMMWorld]
var m: Messenger
fn __init__(out self, world: UnsafePointer[MMMWorld]):
self.world = world
self.m = Messenger(self.world)
fn get_messages(mut self) -> None:
pass
fn next_stereo_frame(mut self, mut mags: List[SIMD[DType.float64, 2]], mut phases: List[SIMD[DType.float64, 2]]) -> None:
for ref p in phases:
p = SIMD[DType.float64, 2](random_float64(0.0, 2.0 * 3.141592653589793), random_float64(0.0, 2.0 * 3.141592653589793))
# User's Synth
struct PaulStretch(Movable, Copyable):
var world: UnsafePointer[MMMWorld]
var buffer: Buffer
var saw: LFSaw
var paul_stretch: FFTProcess[PaulStretchWindow[window_size],window_size,hop_size,WindowType.sine,WindowType.sine]
var m: Messenger
var dur_mult: Float64
fn __init__(out self, world: UnsafePointer[MMMWorld]):
self.world = world
self.buffer = Buffer.load("resources/Shiverer.wav")
self.saw = LFSaw(self.world)
self.paul_stretch = FFTProcess[
PaulStretchWindow[window_size],
window_size,
hop_size,
WindowType.sine,
WindowType.sine
](self.world,process=PaulStretchWindow[window_size](self.world))
self.m = Messenger(self.world)
self.dur_mult = 40.0
fn next(mut self) -> SIMD[DType.float64,2]:
self.m.update(self.dur_mult,"dur_mult")
speed = 1.0/self.buffer.duration * (1.0/self.dur_mult)
phase = self.saw.next(speed)*0.5 + 0.5
o = self.paul_stretch.next_from_stereo_buffer(self.buffer, phase, 0)
return o