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

NessStretch

NessStretch is an FFT – based extreme time-stretching algorithm invented by Alex Ness and based on Paul Nasca's PaulStretch algorithm. The algorithm takes the PaulStretch to the extreme, stretching the lowest octave using a 32768 point FFT and the highest octave using a 256 point FFT, resulting in a more detailed sound with better preservation of transients and less smearing.

Python Code

MMMAudio.get_audio_devices()

from mmm_python import *
mmm_audio = MMMAudio(2048, graph_name="NessStretch", package_name="examples")
mmm_audio.start_audio()

# load your own file - the candier pop the better
mmm_audio.send_string("file_name", "tmp/Man_short.wav")

# 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()

import numpy as np

np.arange(1024 // 2 + 1)

Mojo Code

from mmm_audio import *

comptime iterations = 2

struct NessStretchWindow[num_iterations: Int=1](FFTProcessable):
    var world: World
    var window_size: Int
    var hop_size: Int
    var m: Messenger
    var lrbp_window: List[Float64]
    var previous_phases: List[MFloat[2]]
    var previous_mags: List[MFloat[2]]
    var low_cut: Int
    var high_cut: Int
    var m_s: List[Float64]

    fn __init__(out self, world: World, window_size: Int, hop_size: Int, low_cut: Int, high_cut: Int):
        self.world = world
        self.window_size = window_size
        self.hop_size = hop_size
        self.m = Messenger(self.world)
        lrhp_window = create_linkwitz_riley_fft_filter(self.window_size, low_cut, 24, highpass=True)
        lrlp_window = create_linkwitz_riley_fft_filter(self.window_size, high_cut, 24, highpass=False)
        self.lrbp_window = [lrhp_window[i] * lrlp_window[i] for i in range(len(lrhp_window))]
        self.previous_phases = [MFloat[2](0.0, 0.0) for _ in range(self.window_size // 2 + 1)]
        self.previous_mags = [MFloat[2](0.0, 0.0) for _ in range(self.window_size // 2 + 1)]
        self.low_cut = low_cut
        self.high_cut = high_cut
        self.m_s = [0.0 for _ in range(self.window_size // 2 + 1)]

    fn get_messages(mut self) -> None:
        pass

    fn next_stereo_frame(mut self, mut mags: List[MFloat[2]], mut phases: List[MFloat[2]]) -> None:
        mags[0] = 0.0 # zero the bottom bin
        for i in range(len(mags)):
            mags[i] *= self.lrbp_window[i]

        fn call_back(mut phases: List[MFloat[2]]):
            for ref p in phases:
                p = MFloat[2](rrand(0.0, 2.0 * 3.141592653589793), rrand(0.0, 2.0 * 3.141592653589793))
        get_best_coherence[num_iterations=Self.num_iterations](mags, phases, self.previous_mags, self.previous_phases, self.window_size, self.hop_size, call_back)

        self.previous_phases = phases.copy()


struct NessStretch(Movable, Copyable):
    var world: World
    var buffer: SIMDBuffer[2]
    var saw: LFSaw[1]
    var window_sizes: List[Int] 
    var hop_sizes: List[Int]

    var ness_stretches: List[FFTProcess[NessStretchWindow[num_iterations=iterations],ifft=True,input_window_shape=WindowType.sine,output_window_shape=WindowType.sine]]

    var m: Messenger
    var dur_mult: Float64
    var file_name: String

    fn __init__(out self, world: World):
        self.world = world
        self.file_name = "resources/Shiverer.wav"
        self.buffer = SIMDBuffer.load("resources/Shiverer.wav")
        self.saw = LFSaw(self.world)
        self.window_sizes = [65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256]
        self.hop_sizes = [32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128]

        start_cut = [0, 64, 64, 64, 64, 64, 64, 64, 64]

        # the upper register benefit from less coherence, so I am using fewer in the upper register.
        self.ness_stretches = [FFTProcess[
                NessStretchWindow[num_iterations=iterations],
                ifft=True,
                input_window_shape=WindowType.sine,
                output_window_shape=WindowType.sine,

            ](self.world,process=NessStretchWindow[num_iterations=iterations](self.world, self.window_sizes[i], self.hop_sizes[i],start_cut[i], 128),window_size=self.window_sizes[i],hop_size=self.hop_sizes[i]) for i in range(0,9)]

        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")
        new_file = self.m.notify_update(self.file_name, "file_name")
        if new_file:
            self.buffer = SIMDBuffer.load(self.file_name)
        speed = 1.0/self.buffer.duration * (1.0/self.dur_mult)
        phase = self.saw.next(speed, trig = new_file)*0.5 + 0.5 #resets the phase when the file changes
        o = MFloat[2](0.0, 0.0)
        for ref n in self.ness_stretches:
            o += n.buffered_process.next_from_stereo_buffer[Interp.lagrange4](self.buffer, phase)
        # o += self.ness_stretches[1].buffered_process.next_from_stereo_buffer[Interp.lagrange4](self.buffer, phase)
        return o * 0.5