I would like to ask everyone about an issue regarding MSP~ external object development
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
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👽
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
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👽
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 🥸