// // Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu> // Creation Date: Fri May 12 23:41:37 PDT 2006 // Last Modified: Fri Jun 23 00:33:33 PDT 2006 (subclassed to MazurkaPlugin) // Filename: MzSpectrogramClient.cpp // URL: http://sv.mazurka.org.uk/src/MzSpectrogramClient.cpp // Documentation: http://sv.mazurka.org.uk/MzSpectrogramClient // Syntax: ANSI99 C++; vamp 0.9 plugin // // Description: Demonstration of how to create spectral data from time data // supplied by the host application. // #include "MzSpectrogramClient.h" #include <math.h> /////////////////////////////////////////////////////////////////////////// // // Vamp Interface Functions // /////////////////////////////// // // MzSpectrogramClient::MzSpectrogramClient -- class constructor. // MzSpectrogramClient::MzSpectrogramClient(float samplerate) : MazurkaPlugin(samplerate) { mz_signalbuffer = NULL; mz_windbuffer = NULL; mz_freqbuffer = NULL; mz_minbin = 0; mz_maxbin = 0; } /////////////////////////////// // // MzSpectrogramClient::~MzSpectrogramClient -- class destructor. // MzSpectrogramClient::~MzSpectrogramClient() { delete [] mz_signalbuffer; delete [] mz_windbuffer; delete [] mz_freqbuffer; } //////////////////////////////////////////////////////////// // // required polymorphic functions inherited from PluginBase: // std::string MzSpectrogramClient::getName(void) const { return "mzspectrogramclient"; } std::string MzSpectrogramClient::getMaker(void) const { return "The Mazurka Project"; } std::string MzSpectrogramClient::getCopyright(void) const { return "2006 Craig Stuart Sapp"; } std::string MzSpectrogramClient::getDescription(void) const { return "Client Spectrogram"; } int MzSpectrogramClient::getPluginVersion(void) const { #define P_VER "200606260" #define P_NAME "MzSpectrogramClient" const char *v = "@@VampPluginID@" P_NAME "@" P_VER "@" __DATE__ "@@"; if (v[0] != '@') { std::cerr << v << std::endl; return 0; } return atol(P_VER); } //////////////////////////////////////////////////////////// // // optional polymorphic parameter functions inherited from PluginBase: // // Note that the getParameter() and setParameter() polymorphic functions // are handled in the MazurkaPlugin class. // ////////////////////////////// // // MzSpectrogramClient::getParameterDescriptors -- return a list of // the parameters which can control the plugin. // MzSpectrogramClient::ParameterList MzSpectrogramClient::getParameterDescriptors(void) const { ParameterList pdlist; ParameterDescriptor pd; // first parameter: The minimum spectral bin to display pd.name = "minbin"; pd.description = "Minimum\nfrequency\nbin"; pd.unit = ""; pd.minValue = 0.0; pd.maxValue = 50000.0; pd.defaultValue = 0.0; pd.isQuantized = 1; pd.quantizeStep = 1.0; pdlist.push_back(pd); // second parameter: The maximum spectral bin to display pd.name = "maxbin"; pd.description = "Maximum\nfrequency\nbin"; pd.unit = ""; pd.minValue = -1.0; pd.maxValue = 50000.0; pd.defaultValue = -1.0; pd.isQuantized = 1; pd.quantizeStep = 1.0; pdlist.push_back(pd); return pdlist; } //////////////////////////////////////////////////////////// // // required polymorphic functions inherited from Plugin: // ////////////////////////////// // // MzSpectrogramClient::getInputDomain -- the host application needs // to know if it should send either: // // TimeDomain == Time samples from the audio waveform. // FrequencyDomain == Spectral frequency frames which will arrive // in an array of interleaved real, imaginary // values for the complex spectrum (both positive // and negative frequencies). Zero Hz being the // first frequency sample and negative frequencies // at the far end of the array as is usually done. // Note that frequency data is transmitted from // the host application as floats. The data will // be transmitted via the process() function which // is defined further below. // MzSpectrogramClient::InputDomain MzSpectrogramClient::getInputDomain(void) const { return TimeDomain; } ////////////////////////////// // // MzSpectrogramClient::getOutputDescriptors -- return a list describing // each of the available outputs for the object. OutputList // is defined in the file vamp-sdk/Plugin.h: // // .name == short name of output for computer use. Must not // contain spaces or punctuation. // .description == long name of output for human use. // .unit == the units or basic meaning of the data in the // specified output. // .hasFixedBinCount == true if each output feature (sample) has the // same dimension. // .binCount == when hasFixedBinCount is true, then this is the // number of values in each output feature. // binCount=0 if timestamps are the only features, // and they have no labels. // .binNames == optional description of each bin in a feature. // .hasKnownExtent == true if there is a fixed minimum and maximum // value for the range of the output. // .minValue == range minimum if hasKnownExtent is true. // .maxValue == range maximum if hasKnownExtent is true. // .isQuantized == true if the data values are quantized. Ignored // if binCount is set to zero. // .quantizeStep == if isQuantized, then the size of the quantization, // such as 1.0 for integers. // .sampleType == Enumeration with three possibilities: // OD::OneSamplePerStep -- output feature will be aligned with // the beginning time of the input block data. // OD::FixedSampleRate -- results are evenly spaced according to // .sampleRate (see below). // OD::VariableSampleRate -- output features have individual timestamps. // .sampleRate == samples per second spacing of output features when // sampleType is set toFixedSampleRate. // Ignored if sampleType is set to OneSamplePerStep // since the start time of the input block will be used. // Usually set the sampleRate to 0.0 if VariableSampleRate // is used; otherwise, see vamp-sdk/Plugin.h for what // positive sampleRates would mean. // MzSpectrogramClient::OutputList MzSpectrogramClient::getOutputDescriptors(void) const { OutputList list; OutputDescriptor od; // First and only output channel: od.name = "magnitude"; od.description = "Magnitude Spectrum"; od.unit = "decibels"; od.hasFixedBinCount = true; od.binCount = mz_maxbin - mz_minbin + 1; od.hasKnownExtents = false; // od.minValue = 0.0; // od.maxValue = 0.0; od.isQuantized = false; // od.quantizeStep = 1.0; od.sampleType = OutputDescriptor::OneSamplePerStep; // od.sampleRate = 0.0; list.push_back(od); return list; } ////////////////////////////// // // MzSpectrogramClient::initialise -- this function is called once // before the first call to process(). // #define ISPOWEROFTWO(x) ((x)&&!(((x)-1)&(x))) bool MzSpectrogramClient::initialise(size_t channels, size_t stepsize, size_t blocksize) { if (channels < getMinChannelCount() || channels > getMaxChannelCount()) { return false; } // The signal size/transform size are equivalent for this plugin, and // must be a power of two in order to use the given FFT algorithm. // Give up if the blocksize is not a power of two. if (!ISPOWEROFTWO(blocksize)) { return false; } // step size and block size should never be zero if (stepsize <= 0 || blocksize <= 0) { return false; } setChannelCount(channels); setStepSize(stepsize); setBlockSize(blocksize); mz_minbin = getParameterInt("minbin"); mz_maxbin = getParameterInt("maxbin"); if (mz_minbin >= getBlockSize()/2) { mz_minbin = getBlockSize()/2-1; } if (mz_maxbin >= getBlockSize()/2) { mz_maxbin = getBlockSize()/2-1; } if (mz_maxbin < 0) { mz_maxbin = getBlockSize()/2-1; } if (mz_maxbin > mz_minbin) { std::swap(mz_minbin, mz_maxbin); } delete [] mz_signalbuffer; mz_signalbuffer = new double[getBlockSize()]; // the mz_freqbuffer is twice the length of the input signal because // it will store the complex frequency bins which consist of pairs // of real and imaginary numbers. delete [] mz_freqbuffer; mz_freqbuffer = new double[getBlockSize() * 2]; delete [] mz_windbuffer; mz_windbuffer = new double[getBlockSize()]; // calculate the analysis window which will be applied to the // signal before it is transformed. return true; } ////////////////////////////// // // MzSpectrogramClient::process -- This function is called sequentially on the // input data, block by block. After the sequence of blocks has been // processed with process(), the function getRemainingFeatures() will // be called. // // Here is a reference chart for the Feature struct: // // .hasTimestamp == If the OutputDescriptor.sampleType is set to // VariableSampleRate, then this should be "true". // .timestamp == The time at which the feature occurs in the time stream. // .values == The float values for the feature. Should match // OD::binCount. // .label == Text associated with the feature (for time instants). // #define ZEROLOG -120.0 MzSpectrogramClient::FeatureSet MzSpectrogramClient::process(float **inputbufs, Vamp::RealTime timestamp) { if (getChannelCount() <= 0) { std::cerr << "ERROR: MzSpectrogramClient::process: " << "MzSpectrogramClient has not been initialized" << std::endl; return FeatureSet(); } // first window the input signal frame windowSignal(mz_signalbuffer, mz_windbuffer, inputbufs[0], getBlockSize()); // then calculate the complex DFT spectrum. (note this fft // function will automatically rotate the time buffer 1/2 of // a frame to place the center of the windowed signal at index 0). // Rotate the signal so the first element in the array is in the // middle of the array (or slightly higher for even sizes). // This code only works for even sizes (or size-1). But that is // OK because the initialise() function requires the size to // be a power of two. int i; int halfsize = getBlockSize()/2; for (i=0; i<halfsize; i++) { std::swap(mz_signalbuffer[i], mz_signalbuffer[halfsize+i]); } // Calculate the complex DFT spectrum. fft(getBlockSize(), mz_signalbuffer, NULL, mz_freqbuffer, mz_freqbuffer + getBlockSize()); // return the spectral frame to the host application FeatureSet returnFeatures; Feature feature; feature.hasTimestamp = false; double* real = mz_freqbuffer; double* imag = mz_freqbuffer + getBlockSize()/2; float magnitude; // temporary holding space for magnitude value for (i=mz_minbin; i<=mz_maxbin; i++) { magnitude = real[i] * real[i] + imag[i] * imag[i]; // convert to decibels: if (magnitude <= 0) { magnitude = ZEROLOG; } else { magnitude = 10.0 * log10(magnitude); } feature.values.push_back(magnitude); } // Append new frame of data onto the output channel // specified in the function getOutputDescriptors(): returnFeatures[0].push_back(feature); return returnFeatures; } ////////////////////////////// // // MzSpectrogramClient::getRemainingFeatures -- This function is called // after the last call to process() on the input data stream has // been completed. Features which are non-causal can be calculated // at this point. See the comment above the process() function // for the format of output Features. // MzSpectrogramClient::FeatureSet MzSpectrogramClient::getRemainingFeatures(void) { // no remaining features, so return a dummy feature return FeatureSet(); } ////////////////////////////// // // MzSpectrogramClient::reset -- This function may be called after data // processing has been started with the process() function. It will // be called when processing has been interrupted for some reason and // the processing sequence needs to be restarted (and current analysis // output thrown out). After this function is called, process() will // start at the beginning of the input selection as if initialise() // had just been called. Note, however, that initialise() will NOT // be called before processing is restarted after a reset(). // void MzSpectrogramClient::reset(void) { // no actions necessary to reset this plugin } /////////////////////////////////////////////////////////////////////////// // // Non-Interface Functions // ////////////////////////////// // // MzSpectrogramClient::makeHannWindow -- create a raised cosine (Hann) // window. // void MzSpectrogramClient::makeHannWindow(double* output, int blocksize) { for (int i=0; i<blocksize; i++) { output[i] = 0.5 - 0.5 * cos(2.0 * M_PI * i/blocksize); } } ////////////////////////////// // // MzSpectrogramClient::windowSignal -- multiply the time signal // by the analysis window to prepare for transformation. // void MzSpectrogramClient::windowSignal(double* output, double* window, float* input, int blocksize) { for (int i=0; i<blocksize; i++) { output[i] = window[i] * double(input[i]); } } ////////////////////////////// // // MzSpectrogramClient::fft -- calculate the Fast Fourier Transform. // Modified from the vamp plugin sdk fft() function in // host/vamp-simple-host.cpp which was in turn adapted from the // FFT implementation of Don Cross: // http://www.mathsci.appstate.edu/~wmcb/FFT/Code/fft.p // http://cs.marlboro.edu/term/fall01/computation/fourier/fft_c_code/TEMP/FOURIERD.C // // Note that this fft is about 4 times slower than the // FFTW (http://www.fftw.org) implementation of the FFT, so if you // want speed, you should use FFTW to calculate the DFT as is done // in the Sonic Visualiser host application. // void MzSpectrogramClient::fft(int n, double *ri, double *ii, double *ro, double *io) { if (!ri || !ro || !io) return; int bits; int i, j, k, m; int blockSize, blockEnd; double tr, ti; // Twiddle the time input to move center of window to index 0. if (n & (n-1)) return; double angle = 2.0 * M_PI; for (i = 0; ; ++i) { if (n & (1 << i)) { bits = i; break; } } static int tableSize = 0; static int *table = 0; if (tableSize != n) { delete[] table; table = new int[n]; for (i = 0; i < n; ++i) { m = i; for (j = k = 0; j < bits; ++j) { k = (k << 1) | (m & 1); m >>= 1; } table[i] = k; } tableSize = n; } if (ii) { for (i = 0; i < n; ++i) { ro[table[i]] = ri[i]; io[table[i]] = ii[i]; } } else { for (i = 0; i < n; ++i) { ro[table[i]] = ri[i]; io[table[i]] = 0.0; } } blockEnd = 1; for (blockSize = 2; blockSize <= n; blockSize <<= 1) { double delta = angle / (double)blockSize; double sm2 = -sin(-2 * delta); double sm1 = -sin(-delta); double cm2 = cos(-2 * delta); double cm1 = cos(-delta); double w = 2 * cm1; double ar[3], ai[3]; for (i = 0; i < n; i += blockSize) { ar[2] = cm2; ar[1] = cm1; ai[2] = sm2; ai[1] = sm1; for (j = i, m = 0; m < blockEnd; j++, m++) { ar[0] = w * ar[1] - ar[2]; ar[2] = ar[1]; ar[1] = ar[0]; ai[0] = w * ai[1] - ai[2]; ai[2] = ai[1]; ai[1] = ai[0]; k = j + blockEnd; tr = ar[0] * ro[k] - ai[0] * io[k]; ti = ar[0] * io[k] + ai[0] * ro[k]; ro[k] = ro[j] - tr; io[k] = io[j] - ti; ro[j] += tr; io[j] += ti; } } blockEnd = blockSize; } } |