I would like to ask everyone about an issue regarding MSP~ external object development

Darling Lee's icon

Darling Lee

9月 07 2024 | 4:38 午前

I intend to develop an external object using the C language. Below is my source code. The problem is that the compiled object cannot receive audio signals through the inlet. Why is that? How should I modify my source code?Thanks!

#include "ext.h"
#include "ext_obex.h"
#include "z_dsp.h"
#include <math.h>
#include <stdlib.h>
#include <fftw3.h>

#define MAX_PEAK_COUNT 24
#define BUFFER_SIZE_OPTIONS {512, 1024, 2048, 4096}

// Spectrum peak structure
typedef struct {
    float frequency;
    float amplitude;
    float Q;
} Peak;

typedef struct _fpeaksm {
    t_pxobject ob;          
    double *buffer;         // Signal buffer
    fftw_complex *fft_output; // FFT output
    long buffer_size;       // Buffer size
    long peak_count;        // Number of spectrum peaks
    long buffer_index;      // Buffer index
    t_atom *output_atoms;   // Output data
    void *outlet;           // Max/MSP outlet
    fftw_plan fft_plan;     // FFT plan
    float threshold;        // Threshold
} t_fpeaksm;

t_class *fpeaksm_class; // Class pointer

void *fpeaksm_new(t_symbol *s, long argc, t_atom *argv);
void fpeaksm_free(t_fpeaksm *x);
void fpeaksm_assist(t_fpeaksm *x, void *b, long m, long a, char *s);
void fpeaksm_dsp(t_fpeaksm *x, t_signal **sp, short *count);
void fpeaksm_perform64(t_fpeaksm *x, t_object *output, double **ins, long n, long flags, void *userparam);
void analyze_peaks(t_fpeaksm *x);
void output_peaks(t_fpeaksm *x, Peak *peaks, long count);
void fpeaksm_size(t_fpeaksm *x, long size);
void fpeaksm_peak(t_fpeaksm *x, long count);
void fpeaksm_threshold(t_fpeaksm *x, float threshold); // New threshold processing function

void ext_main(void *r) {
    fpeaksm_class = class_new("fpeaksm~", (method)fpeaksm_new, (method)fpeaksm_free, sizeof(t_fpeaksm), 0L, A_GIMME, 0);
    
    // Define attributes
    CLASS_ATTR_LONG(fpeaksm_class, "size", 0, t_fpeaksm, buffer_size);
    CLASS_ATTR_LONG(fpeaksm_class, "peak", 0, t_fpeaksm, peak_count);
    CLASS_ATTR_FLOAT(fpeaksm_class, "threshold", 0, t_fpeaksm, threshold); // New threshold attribute

    // Default values for attributes
    CLASS_ATTR_DEFAULT(fpeaksm_class, "size", 0, "1024");
    CLASS_ATTR_DEFAULT(fpeaksm_class, "peak", 0, "12");
    CLASS_ATTR_DEFAULT(fpeaksm_class, "threshold", 0, "0.01"); // Default value for threshold

    // Add methods
    class_addmethod(fpeaksm_class, (method)fpeaksm_dsp, "dsp", A_CANT, 0);
    class_addmethod(fpeaksm_class, (method)fpeaksm_assist, "assist", A_CANT, 0);
    class_addmethod(fpeaksm_class, (method)fpeaksm_size, "size", A_LONG, 0);
    class_addmethod(fpeaksm_class, (method)fpeaksm_peak, "peak", A_LONG, 0);
    class_addmethod(fpeaksm_class, (method)fpeaksm_threshold, "threshold", A_FLOAT, 0); // New method for threshold processing

    class_register(CLASS_BOX, fpeaksm_class);
}

void *fpeaksm_new(t_symbol *s, long argc, t_atom *argv) {
    t_fpeaksm *x = (t_fpeaksm *)object_alloc(fpeaksm_class);

    // Call dsp_setup to inform Max that this object has 1 audio signal inlet
    dsp_setup((t_pxobject *)x, 1);  // 1 indicates there is 1 signal input inlet

    x->buffer_size = 1024; // Default buffer size
    x->peak_count = 12;    // Default peak count
    x->threshold = 0.01;   // Default threshold

    // Parse command line arguments
    for (long i = 0; i < argc; i++) {
        if (atom_gettype(argv + i) == A_LONG) {
            if (i == 0) x->buffer_size = atom_getlong(argv + i);
            if (i == 1) x->peak_count = atom_getlong(argv + i);
        } else if (atom_gettype(argv + i) == A_FLOAT) {
            if (i == 2) x->threshold = atom_getfloat(argv + i);
        }
    }

    // Ensure buffer_size and peak_count are valid
    long valid_sizes[] = BUFFER_SIZE_OPTIONS;
    for (long i = 0; i < sizeof(valid_sizes) / sizeof(long); i++) {
        if (x->buffer_size == valid_sizes[i]) break;
        if (i == sizeof(valid_sizes) / sizeof(long) - 1) {
            x->buffer_size = 1024; // Set to default value
        }
    }

    x->peak_count = x->peak_count < 1 ? 1 : (x->peak_count > MAX_PEAK_COUNT ? MAX_PEAK_COUNT : x->peak_count);
    
    // Create FFT plan
    x->buffer = (double *)malloc(x->buffer_size * sizeof(double));
    x->fft_output = (fftw_complex *)malloc(x->buffer_size * sizeof(fftw_complex));
    x->output_atoms = (t_atom *)malloc(x->peak_count * 3 * sizeof(t_atom)); // 3 parameters for each peak
    x->fft_plan = fftw_plan_dft_r2c_1d(x->buffer_size, x->buffer, x->fft_output, FFTW_ESTIMATE);

    // Create outlet
    x->outlet = outlet_new(&x->ob, NULL);

    return (x);
}

void fpeaksm_free(t_fpeaksm *x) {
    dsp_free((t_pxobject *)x);  // Free DSP resources
    fftw_destroy_plan(x->fft_plan);  // Destroy FFT plan
    free(x->buffer);  // Free buffer
    free(x->fft_output);  // Free FFT output
    free(x->output_atoms);  // Free output data
}

void fpeaksm_assist(t_fpeaksm *x, void *b, long m, long a, char *s) {
    if (m == ASSIST_INLET) {
        sprintf(s, "Audio Input / Control Messages (size, peak, threshold)");
    } else {
        sprintf(s, "Output Peaks Data");
    }
}

void fpeaksm_dsp(t_fpeaksm *x, t_signal **sp, short *count) {
    dsp_add64((t_object *)x, (t_object *)x, (t_perfroutine64)fpeaksm_perform64, 0, NULL);
}

void fpeaksm_perform64(t_fpeaksm *x, t_object *dsp64, double **ins, long sampleframes, long flags, void *userparam) {
    double *in = ins[0];  // Get audio input signal

    for (long i = 0; i < sampleframes; i++) {
        x->buffer[x->buffer_index++] = in[i];  // Write signal to buffer

        // If buffer is full, perform FFT
        if (x->buffer_index >= x->buffer_size) {
            x->buffer_index = 0;  // Reset buffer index
            fftw_execute(x->fft_plan);  // Execute FFT
            analyze_peaks(x);  // Analyze peaks and output
        }
    }
}

void fpeaksm_size(t_fpeaksm *x, long size) {
    // Set buffer size
    if (size == 512 || size == 1024 || size == 2048 || size == 4096) {
        x->buffer_size = size;
        // Re-plan FFT plan
        x->fft_plan = fftw_plan_dft_r2c_1d(x->buffer_size, x->buffer, x->fft_output, FFTW_ESTIMATE);
    }
}

void fpeaksm_peak(t_fpeaksm *x, long count) {
    // Set peak count
    if (count >= 1 && count <= MAX_PEAK_COUNT) {
        x->peak_count = count;
    }
}

void fpeaksm_threshold(t_fpeaksm *x, float threshold) {
    // Set threshold
    x->threshold = threshold;
}

void analyze_peaks(t_fpeaksm *x) {
    Peak peaks[MAX_PEAK_COUNT];
    long peak_count = 0;

    // Process spectrum data
    for (long i = 0; i < x->buffer_size / 2; i++) {
        float amplitude = sqrt(x->fft_output[i][0] * x->fft_output[i][0] + x->fft_output[i][1] * x->fft_output[i][1]); // Calculate amplitude
        if (amplitude > x->threshold) { // Use threshold for detection
            float frequency = (float)i * (44100.0 / x->buffer_size); // Assume sampling rate is 44100
            float Q = frequency / (amplitude + 1e-5);  // Calculate Q value
            if (peak_count < x->peak_count) {
                peaks[peak_count++] = (Peak){frequency, amplitude, Q};
            } else {
                // Replace lowest amplitude peak
                for (long j = 0; j < peak_count; j++) {
                    if (amplitude > peaks[j].amplitude) {
                        peaks[j] = (Peak){frequency, amplitude, Q};
                        break;
                    }
                }
            }
        }
    }

    // Output results
    output_peaks(x, peaks, peak_count);
}

void output_peaks(t_fpeaksm *x, Peak *peaks, long count) {
    // Sort by frequency
    for (long i = 0; i < count - 1; i++) {
        for (long j = i + 1; j < count; j++) {
            if (peaks[i].frequency > peaks[j].frequency) {
                Peak temp = peaks[i];
                peaks[i] = peaks[j];
                peaks[j] = temp;
            }
        }
    }

    // Prepare output
    long output_size = count * 3;
    for (long i = 0; i < count; i++) {
        atom_setfloat(&x->output_atoms[i * 3], peaks[i].frequency);
        atom_setfloat(&x->output_atoms[i * 3 + 1], peaks[i].amplitude);
        atom_setfloat(&x->output_atoms[i * 3 + 2], peaks[i].Q);
    }

    // Output to outlet
    outlet_list(x->outlet, NULL, output_size, x->output_atoms);
}
volker böhm's icon

volker böhm

9月 07 2024 | 9:26 午前

It seems you are strangely mixing 32bit and 64bit code. Take a look at an example project to get your dsp and perform function signatures right. They are given and you can't change them or omit certain parameters. Also you forgot to call class_dspinit() in your ext_main().
There might be more issues with your code, haven't read everything.
Might also be a good idea to start with a simple in-out project and debug until the template works correctly. An then from there dive into deeper fields like incorporating fftw.

👽'tW∆s ∆lienz👽's icon

👽'tW∆s ∆lienz👽

9月 07 2024 | 3:01 午後

just to add to what Volker said, check out the 'anatomy of MSP objects' page for a quick comparison to what you have in your code:

Darling Lee's icon

Darling Lee

9月 08 2024 | 9:10 午前

Thank you to Volker Böhm and 👽'tW∆s ∆lienz👽 for your answers. Based on your suggestions, I have resolved my issue! Additionally, regarding the question from 👽'tW∆s ∆lienz👽, I'm sorry, I don't have enough knowledge to answer your question because I'm not a programming engineer. Most of the code posted here is generated by GPT to assist me. I can roughly understand the meaning of the code, but I lack programming knowledge, so I apologize. In this case, my intention was to extract data from multiple frequency peaks in real-time audio signals.

👽'tW∆s ∆lienz👽's icon

👽'tW∆s ∆lienz👽

9月 08 2024 | 1:59 午後

glad you figured it out :) and no worries, i erased my original question(for anyone else reading, it was simply whether a 'sliding-DFT' algorithm can be altered to have overlap and windowing to create STFT(?)), for two reasons:

1) the wikipedia page for 'Sliding-DFT' mentions that it "is a recursive algorithm to compute successive STFTs of input data frames that are a single sample apart (hopsize − 1) "

(which explains that it is very similar to what i was thinking)

but also,

2) because according to this repo:

it seems that while 'Sliding-DFT'(which, i think, is what your code might be accomplishing), is indeed similar to STFT if you add windowing and overlap, more optimized forms of STFT involve the 'Cooley-Tukey' algorithm, at which point, my eyes glazed over because that's also above my head 🥸