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

AnalysisExample

This example demonstrates how to use YIN pitch detection, RMS amplitude analysis, and spectral centroid analysis in MMM-Audio.

Python Code

from mmm_python.MMMAudio import MMMAudio
ma = MMMAudio(128, graph_name="AnalysisExample", package_name="examples")
ma.start_audio()

ma.send_float("freq", 290.5)
ma.send_float("which", 2.0)
ma.stop_audio()

Mojo Code

from mmm_audio import *

struct CustomAnalysis[window_size: Int = 1024](BufferedProcessable):
    var world: UnsafePointer[MMMWorld]
    var centroid: Float64
    var rms: Float64
    var pitch: Float64
    var pitch_conf: Float64
    var sr: Float64
    var yin: YIN[window_size, 50, 5000]

    fn __init__(out self, world: UnsafePointer[MMMWorld]):
        self.world = world
        self.sr = self.world[].sample_rate
        self.centroid = 0.0
        self.pitch = 0.0
        self.pitch_conf = 0.0
        self.rms = 0.0
        self.yin = YIN[window_size, 50, 5000](world)

    fn next_window(mut self, mut frame: List[Float64]):
        self.yin.next_window(frame)
        self.pitch = self.yin.pitch
        self.pitch_conf = self.yin.confidence
        self.rms = RMS.from_window(frame)
        # YIN has to do a special FFT internally no matter what, 
        # so we'll just use the "raw" mags it computes
        # for spectral centroid. It is an FFT with double the frequency resolution
        # (i.e., it's higher resolution, just "interpolated" FFT mags). But it will work just fine here.
        self.centroid = SpectralCentroid.from_mags(self.yin.fft.mags, self.world[].sample_rate)

struct AnalysisExample(Movable, Copyable):
    var world: UnsafePointer[MMMWorld]
    var osc: Osc[2]
    var buffer: Buffer
    var playBuf: Play
    var freq: Float64
    var analyzer: BufferedInput[CustomAnalysis[1024],1024,512]
    var m: Messenger
    var which: Float64

    fn __init__(out self, world: UnsafePointer[MMMWorld]):
        self.world = world
        self.osc = Osc[2](self.world)
        self.buffer = Buffer.load("resources/Shiverer.wav")
        self.playBuf = Play(self.world)
        self.analyzer = BufferedInput[CustomAnalysis[1024],1024,512](self.world, CustomAnalysis[1024](self.world))
        self.freq = 440.0
        self.m = Messenger(self.world)
        self.which = 0.0

    fn next(mut self) -> SIMD[DType.float64, 2]:

        self.m.update(self.freq,"freq")
        self.m.update(self.which,"which")

        oscs = self.osc.next(self.freq,0,False,[OscType.sine, OscType.saw])
        flute = self.playBuf.next(self.buffer)

        sig = select(self.which,[oscs[0], oscs[1], flute])

        # do the analysis
        self.analyzer.next(sig)

        # get the results
        (frequency, confidence) = (self.analyzer.process.pitch, self.analyzer.process.pitch_conf)
        rms = self.analyzer.process.rms
        centroid = self.analyzer.process.centroid

        # print the results
        self.world[].print("Pitch: ", frequency, " \tHz, Confidence: ", confidence, ", \tRMS: ", rms, ", \tCentroid: ", centroid)

        return sig * 0.1