1*b290403dSRicardo Garcia /* Sonic library
2*b290403dSRicardo Garcia Copyright 2016
3*b290403dSRicardo Garcia Bill Cox
4*b290403dSRicardo Garcia This file is part of the Sonic Library.
5*b290403dSRicardo Garcia
6*b290403dSRicardo Garcia This file is licensed under the Apache 2.0 license.
7*b290403dSRicardo Garcia */
8*b290403dSRicardo Garcia
9*b290403dSRicardo Garcia #ifdef KISS_FFT
10*b290403dSRicardo Garcia #include <stddef.h> /* kiss_fft.h failes to load this */
11*b290403dSRicardo Garcia #include <kiss_fft.h>
12*b290403dSRicardo Garcia #include <kiss_fft_impl.h>
13*b290403dSRicardo Garcia #else
14*b290403dSRicardo Garcia #include <fftw3.h>
15*b290403dSRicardo Garcia #endif
16*b290403dSRicardo Garcia #include <float.h>
17*b290403dSRicardo Garcia #include <math.h>
18*b290403dSRicardo Garcia #include <stdio.h>
19*b290403dSRicardo Garcia #include <stdlib.h>
20*b290403dSRicardo Garcia #include "sonic.h"
21*b290403dSRicardo Garcia #ifndef M_PI
22*b290403dSRicardo Garcia #define M_PI 3.14159265358979323846
23*b290403dSRicardo Garcia #endif
24*b290403dSRicardo Garcia #ifndef M_E
25*b290403dSRicardo Garcia #define M_E 2.7182818284590452354
26*b290403dSRicardo Garcia #endif
27*b290403dSRicardo Garcia
28*b290403dSRicardo Garcia struct sonicSpectrumStruct;
29*b290403dSRicardo Garcia typedef struct sonicSpectrumStruct* sonicSpectrum;
30*b290403dSRicardo Garcia
31*b290403dSRicardo Garcia struct sonicSpectrogramStruct {
32*b290403dSRicardo Garcia sonicSpectrum* spectrums;
33*b290403dSRicardo Garcia double minPower, maxPower;
34*b290403dSRicardo Garcia int numSpectrums;
35*b290403dSRicardo Garcia int allocatedSpectrums;
36*b290403dSRicardo Garcia int sampleRate;
37*b290403dSRicardo Garcia int totalSamples;
38*b290403dSRicardo Garcia };
39*b290403dSRicardo Garcia
40*b290403dSRicardo Garcia struct sonicSpectrumStruct {
41*b290403dSRicardo Garcia sonicSpectrogram spectrogram;
42*b290403dSRicardo Garcia double* power;
43*b290403dSRicardo Garcia int numFreqs; /* Number of frequencies */
44*b290403dSRicardo Garcia int numSamples;
45*b290403dSRicardo Garcia int startingSample;
46*b290403dSRicardo Garcia };
47*b290403dSRicardo Garcia
48*b290403dSRicardo Garcia /* Print out spectrum data for debugging. */
dumpSpectrum(sonicSpectrum spectrum)49*b290403dSRicardo Garcia static void dumpSpectrum(sonicSpectrum spectrum) {
50*b290403dSRicardo Garcia printf("spectrum numFreqs:%d numSamples:%d startingSample:%d\n",
51*b290403dSRicardo Garcia spectrum->numFreqs, spectrum->numSamples, spectrum->startingSample);
52*b290403dSRicardo Garcia printf(" ");
53*b290403dSRicardo Garcia int i;
54*b290403dSRicardo Garcia for (i = 0; i < spectrum->numFreqs; i++) {
55*b290403dSRicardo Garcia printf(" %.1f", spectrum->power[i]);
56*b290403dSRicardo Garcia }
57*b290403dSRicardo Garcia printf("\n");
58*b290403dSRicardo Garcia }
59*b290403dSRicardo Garcia
60*b290403dSRicardo Garcia /* Print out spectrogram data for debugging. */
dumpSpectrogram(sonicSpectrogram spectrogram)61*b290403dSRicardo Garcia void dumpSpectrogram(sonicSpectrogram spectrogram) {
62*b290403dSRicardo Garcia printf(
63*b290403dSRicardo Garcia "spectrogram minPower:%f maxPower:%f numSpectrums:%d totalSamples:%d\n",
64*b290403dSRicardo Garcia spectrogram->minPower, spectrogram->maxPower, spectrogram->numSpectrums,
65*b290403dSRicardo Garcia spectrogram->totalSamples);
66*b290403dSRicardo Garcia int i;
67*b290403dSRicardo Garcia for (i = 0; i < spectrogram->numSpectrums; i++) {
68*b290403dSRicardo Garcia dumpSpectrum(spectrogram->spectrums[i]);
69*b290403dSRicardo Garcia }
70*b290403dSRicardo Garcia }
71*b290403dSRicardo Garcia
72*b290403dSRicardo Garcia /* Create an new spectrum. */
sonicCreateSpectrum(sonicSpectrogram spectrogram)73*b290403dSRicardo Garcia static sonicSpectrum sonicCreateSpectrum(sonicSpectrogram spectrogram) {
74*b290403dSRicardo Garcia sonicSpectrum spectrum =
75*b290403dSRicardo Garcia (sonicSpectrum)calloc(1, sizeof(struct sonicSpectrumStruct));
76*b290403dSRicardo Garcia if (spectrum == NULL) {
77*b290403dSRicardo Garcia return NULL;
78*b290403dSRicardo Garcia }
79*b290403dSRicardo Garcia if (spectrogram->numSpectrums == spectrogram->allocatedSpectrums) {
80*b290403dSRicardo Garcia spectrogram->allocatedSpectrums <<= 1;
81*b290403dSRicardo Garcia spectrogram->spectrums = (sonicSpectrum*)realloc(
82*b290403dSRicardo Garcia spectrogram->spectrums,
83*b290403dSRicardo Garcia spectrogram->allocatedSpectrums * sizeof(sonicSpectrum));
84*b290403dSRicardo Garcia if (spectrogram->spectrums == NULL) {
85*b290403dSRicardo Garcia return NULL;
86*b290403dSRicardo Garcia }
87*b290403dSRicardo Garcia }
88*b290403dSRicardo Garcia spectrogram->spectrums[spectrogram->numSpectrums++] = spectrum;
89*b290403dSRicardo Garcia spectrum->spectrogram = spectrogram;
90*b290403dSRicardo Garcia return spectrum;
91*b290403dSRicardo Garcia }
92*b290403dSRicardo Garcia
93*b290403dSRicardo Garcia /* Destroy the spectrum. */
sonicDestroySpectrum(sonicSpectrum spectrum)94*b290403dSRicardo Garcia static void sonicDestroySpectrum(sonicSpectrum spectrum) {
95*b290403dSRicardo Garcia if (spectrum == NULL) {
96*b290403dSRicardo Garcia return;
97*b290403dSRicardo Garcia }
98*b290403dSRicardo Garcia if (spectrum->power != NULL) {
99*b290403dSRicardo Garcia free(spectrum->power);
100*b290403dSRicardo Garcia }
101*b290403dSRicardo Garcia free(spectrum);
102*b290403dSRicardo Garcia }
103*b290403dSRicardo Garcia
104*b290403dSRicardo Garcia /* Create an empty spectrogram. */
sonicCreateSpectrogram(int sampleRate)105*b290403dSRicardo Garcia sonicSpectrogram sonicCreateSpectrogram(int sampleRate) {
106*b290403dSRicardo Garcia sonicSpectrogram spectrogram =
107*b290403dSRicardo Garcia (sonicSpectrogram)calloc(1, sizeof(struct sonicSpectrogramStruct));
108*b290403dSRicardo Garcia if (spectrogram == NULL) {
109*b290403dSRicardo Garcia return NULL;
110*b290403dSRicardo Garcia }
111*b290403dSRicardo Garcia spectrogram->allocatedSpectrums = 32;
112*b290403dSRicardo Garcia spectrogram->spectrums = (sonicSpectrum*)calloc(
113*b290403dSRicardo Garcia spectrogram->allocatedSpectrums, sizeof(sonicSpectrum));
114*b290403dSRicardo Garcia if (spectrogram->spectrums == NULL) {
115*b290403dSRicardo Garcia sonicDestroySpectrogram(spectrogram);
116*b290403dSRicardo Garcia return NULL;
117*b290403dSRicardo Garcia }
118*b290403dSRicardo Garcia spectrogram->sampleRate = sampleRate;
119*b290403dSRicardo Garcia spectrogram->minPower = DBL_MAX;
120*b290403dSRicardo Garcia spectrogram->maxPower = DBL_MIN;
121*b290403dSRicardo Garcia return spectrogram;
122*b290403dSRicardo Garcia }
123*b290403dSRicardo Garcia
124*b290403dSRicardo Garcia /* Destroy the spectrotram. */
sonicDestroySpectrogram(sonicSpectrogram spectrogram)125*b290403dSRicardo Garcia void sonicDestroySpectrogram(sonicSpectrogram spectrogram) {
126*b290403dSRicardo Garcia if (spectrogram != NULL) {
127*b290403dSRicardo Garcia if (spectrogram->spectrums != NULL) {
128*b290403dSRicardo Garcia int i;
129*b290403dSRicardo Garcia for (i = 0; i < spectrogram->numSpectrums; i++) {
130*b290403dSRicardo Garcia sonicSpectrum spectrum = spectrogram->spectrums[i];
131*b290403dSRicardo Garcia sonicDestroySpectrum(spectrum);
132*b290403dSRicardo Garcia }
133*b290403dSRicardo Garcia free(spectrogram->spectrums);
134*b290403dSRicardo Garcia }
135*b290403dSRicardo Garcia free(spectrogram);
136*b290403dSRicardo Garcia }
137*b290403dSRicardo Garcia }
138*b290403dSRicardo Garcia
139*b290403dSRicardo Garcia /* Create a new bitmap. This takes ownership of data. */
sonicCreateBitmap(unsigned char * data,int numRows,int numCols)140*b290403dSRicardo Garcia sonicBitmap sonicCreateBitmap(unsigned char* data, int numRows, int numCols) {
141*b290403dSRicardo Garcia sonicBitmap bitmap = (sonicBitmap)calloc(1, sizeof(struct sonicBitmapStruct));
142*b290403dSRicardo Garcia if (bitmap == NULL) {
143*b290403dSRicardo Garcia return NULL;
144*b290403dSRicardo Garcia }
145*b290403dSRicardo Garcia bitmap->data = data;
146*b290403dSRicardo Garcia bitmap->numRows = numRows;
147*b290403dSRicardo Garcia bitmap->numCols = numCols;
148*b290403dSRicardo Garcia return bitmap;
149*b290403dSRicardo Garcia }
150*b290403dSRicardo Garcia
151*b290403dSRicardo Garcia /* Destroy the bitmap. */
sonicDestroyBitmap(sonicBitmap bitmap)152*b290403dSRicardo Garcia void sonicDestroyBitmap(sonicBitmap bitmap) {
153*b290403dSRicardo Garcia if (bitmap == NULL) {
154*b290403dSRicardo Garcia return;
155*b290403dSRicardo Garcia }
156*b290403dSRicardo Garcia if (bitmap->data != NULL) {
157*b290403dSRicardo Garcia free(bitmap->data);
158*b290403dSRicardo Garcia }
159*b290403dSRicardo Garcia free(bitmap);
160*b290403dSRicardo Garcia }
161*b290403dSRicardo Garcia
162*b290403dSRicardo Garcia /* Overlap-add the two pitch periods using a Hann window. Caller must free the
163*b290403dSRicardo Garcia * result. */
computeOverlapAdd(short * samples,int period,int numChannels,double * ola_samples)164*b290403dSRicardo Garcia static void computeOverlapAdd(short* samples, int period, int numChannels,
165*b290403dSRicardo Garcia double* ola_samples) {
166*b290403dSRicardo Garcia int i;
167*b290403dSRicardo Garcia for (i = 0; i < period; i++) {
168*b290403dSRicardo Garcia double weight = (1.0 - cos(M_PI * i / period)) / 2.0;
169*b290403dSRicardo Garcia short sample1, sample2;
170*b290403dSRicardo Garcia if (numChannels == 1) {
171*b290403dSRicardo Garcia sample1 = samples[i];
172*b290403dSRicardo Garcia sample2 = samples[i + period];
173*b290403dSRicardo Garcia } else {
174*b290403dSRicardo Garcia /* Average the samples */
175*b290403dSRicardo Garcia int total1 = 0;
176*b290403dSRicardo Garcia int total2 = 0;
177*b290403dSRicardo Garcia int j;
178*b290403dSRicardo Garcia for (j = 0; j < numChannels; j++) {
179*b290403dSRicardo Garcia total1 += samples[i * numChannels + j];
180*b290403dSRicardo Garcia total2 += samples[(i + period) * numChannels + j];
181*b290403dSRicardo Garcia }
182*b290403dSRicardo Garcia sample1 = (total1 + (numChannels >> 1)) / numChannels;
183*b290403dSRicardo Garcia sample2 = (total2 + (numChannels >> 1)) / numChannels;
184*b290403dSRicardo Garcia }
185*b290403dSRicardo Garcia ola_samples[i] = weight * sample1 + (1.0 - weight) * sample2;
186*b290403dSRicardo Garcia }
187*b290403dSRicardo Garcia }
188*b290403dSRicardo Garcia
189*b290403dSRicardo Garcia #ifdef KISS_FFT
190*b290403dSRicardo Garcia /* Compute the amplitude of the kiss_complex number. */
magnitude(kiss_fft_cpx c)191*b290403dSRicardo Garcia static double magnitude(kiss_fft_cpx c) {
192*b290403dSRicardo Garcia return sqrt(c.r * c.r + c.i * c.i);
193*b290403dSRicardo Garcia }
194*b290403dSRicardo Garcia #else
195*b290403dSRicardo Garcia /* Compute the amplitude of the fftw_complex number. */
magnitude(fftw_complex c)196*b290403dSRicardo Garcia static double magnitude(fftw_complex c) {
197*b290403dSRicardo Garcia return sqrt(c[0] * c[0] + c[1] * c[1]);
198*b290403dSRicardo Garcia }
199*b290403dSRicardo Garcia #endif
200*b290403dSRicardo Garcia
201*b290403dSRicardo Garcia /* Add two pitch periods worth of samples to the spectrogram. There must be
202*b290403dSRicardo Garcia 2*period samples. Time should advance one pitch period for each call to
203*b290403dSRicardo Garcia this function. */
sonicAddPitchPeriodToSpectrogram(sonicSpectrogram spectrogram,short * samples,int numSamples,int numChannels)204*b290403dSRicardo Garcia void sonicAddPitchPeriodToSpectrogram(sonicSpectrogram spectrogram,
205*b290403dSRicardo Garcia short* samples, int numSamples,
206*b290403dSRicardo Garcia int numChannels) {
207*b290403dSRicardo Garcia int i;
208*b290403dSRicardo Garcia sonicSpectrum spectrum = sonicCreateSpectrum(spectrogram);
209*b290403dSRicardo Garcia spectrum->startingSample = spectrogram->totalSamples;
210*b290403dSRicardo Garcia spectrogram->totalSamples += numSamples;
211*b290403dSRicardo Garcia /* TODO: convert to fixed-point */
212*b290403dSRicardo Garcia double* in = calloc(numSamples, sizeof(double));
213*b290403dSRicardo Garcia int numFreqs = numSamples / 2 + 1;
214*b290403dSRicardo Garcia spectrum->numFreqs = numFreqs;
215*b290403dSRicardo Garcia spectrum->numSamples = numSamples;
216*b290403dSRicardo Garcia spectrum->power = (double*)calloc(spectrum->numFreqs, sizeof(double));
217*b290403dSRicardo Garcia computeOverlapAdd(samples, numSamples, numChannels, in);
218*b290403dSRicardo Garcia #ifdef KISS_FFT
219*b290403dSRicardo Garcia kiss_fft_cpx* cin = calloc(numFreqs, sizeof(kiss_fft_cpx));
220*b290403dSRicardo Garcia for (i=0; i<numFreqs; i++) {
221*b290403dSRicardo Garcia cin[i].r = in[i];
222*b290403dSRicardo Garcia }
223*b290403dSRicardo Garcia kiss_fft_cpx* out = calloc(numFreqs, sizeof(kiss_fft_cpx));
224*b290403dSRicardo Garcia kiss_fft_cfg kiss_plan = kiss_fft_alloc(numFreqs, 0, NULL, NULL);
225*b290403dSRicardo Garcia kiss_fft(kiss_plan, cin, out);
226*b290403dSRicardo Garcia free(cin);
227*b290403dSRicardo Garcia #else
228*b290403dSRicardo Garcia fftw_complex* out = calloc(numFreqs, sizeof(fftw_complex));
229*b290403dSRicardo Garcia fftw_plan p = fftw_plan_dft_r2c_1d(numSamples, in, out, FFTW_ESTIMATE);
230*b290403dSRicardo Garcia fftw_execute(p);
231*b290403dSRicardo Garcia fftw_destroy_plan(p);
232*b290403dSRicardo Garcia #endif /* FFTW */
233*b290403dSRicardo Garcia /* Set the DC power to 0. */
234*b290403dSRicardo Garcia spectrum->power[0] = 0.0;
235*b290403dSRicardo Garcia for (i = 1; i < numFreqs; ++i) {
236*b290403dSRicardo Garcia double power = magnitude(out[i]) / numSamples;
237*b290403dSRicardo Garcia spectrum->power[i] = power;
238*b290403dSRicardo Garcia if (power > spectrogram->maxPower) {
239*b290403dSRicardo Garcia spectrogram->maxPower = power;
240*b290403dSRicardo Garcia }
241*b290403dSRicardo Garcia if (power < spectrogram->minPower) {
242*b290403dSRicardo Garcia spectrogram->minPower = power;
243*b290403dSRicardo Garcia }
244*b290403dSRicardo Garcia }
245*b290403dSRicardo Garcia free(in);
246*b290403dSRicardo Garcia free(out);
247*b290403dSRicardo Garcia }
248*b290403dSRicardo Garcia
249*b290403dSRicardo Garcia /* Linearly interpolate the power at a given position in the spectrogram. */
interpolateSpectrum(sonicSpectrum spectrum,int row,int numRows)250*b290403dSRicardo Garcia static double interpolateSpectrum(sonicSpectrum spectrum, int row,
251*b290403dSRicardo Garcia int numRows) {
252*b290403dSRicardo Garcia /* Flip the row so that we show lowest frequency on the bottom. */
253*b290403dSRicardo Garcia row = numRows - row - 1;
254*b290403dSRicardo Garcia /* We want the max row to be 1/2 the Niquist frequency, or 4 samples worth. */
255*b290403dSRicardo Garcia double spectrumFreqSpacing =
256*b290403dSRicardo Garcia (double)spectrum->spectrogram->sampleRate / spectrum->numSamples;
257*b290403dSRicardo Garcia double rowFreqSpacing = SONIC_MAX_SPECTRUM_FREQ / (numRows - 1);
258*b290403dSRicardo Garcia double targetFreq = row * rowFreqSpacing;
259*b290403dSRicardo Garcia int bottomIndex = targetFreq / spectrumFreqSpacing;
260*b290403dSRicardo Garcia double bottomPower = spectrum->power[bottomIndex];
261*b290403dSRicardo Garcia double topPower = spectrum->power[bottomIndex + 1];
262*b290403dSRicardo Garcia double position =
263*b290403dSRicardo Garcia (targetFreq - bottomIndex * spectrumFreqSpacing) / spectrumFreqSpacing;
264*b290403dSRicardo Garcia return (1.0 - position) * bottomPower + position * topPower;
265*b290403dSRicardo Garcia }
266*b290403dSRicardo Garcia
267*b290403dSRicardo Garcia /* Linearly interpolate the power at a given position in the spectrogram. */
interpolateSpectrogram(sonicSpectrum leftSpectrum,sonicSpectrum rightSpectrum,int row,int numRows,int colTime)268*b290403dSRicardo Garcia static double interpolateSpectrogram(sonicSpectrum leftSpectrum,
269*b290403dSRicardo Garcia sonicSpectrum rightSpectrum, int row,
270*b290403dSRicardo Garcia int numRows, int colTime) {
271*b290403dSRicardo Garcia double leftPower = interpolateSpectrum(leftSpectrum, row, numRows);
272*b290403dSRicardo Garcia double rightPower = interpolateSpectrum(rightSpectrum, row, numRows);
273*b290403dSRicardo Garcia if (rightSpectrum->startingSample !=
274*b290403dSRicardo Garcia leftSpectrum->startingSample + leftSpectrum->numSamples) {
275*b290403dSRicardo Garcia fprintf(stderr, "Invalid sample spacing\n");
276*b290403dSRicardo Garcia exit(1);
277*b290403dSRicardo Garcia }
278*b290403dSRicardo Garcia int remainder = colTime - leftSpectrum->startingSample;
279*b290403dSRicardo Garcia double position = (double)remainder / leftSpectrum->numSamples;
280*b290403dSRicardo Garcia return (1.0 - position) * leftPower + position * rightPower;
281*b290403dSRicardo Garcia }
282*b290403dSRicardo Garcia
283*b290403dSRicardo Garcia /* Add one column of data to the output bitmap data. */
addBitmapCol(unsigned char * data,int col,int numCols,int numRows,sonicSpectrogram spectrogram,sonicSpectrum spectrum,sonicSpectrum nextSpectrum,int colTime)284*b290403dSRicardo Garcia static void addBitmapCol(unsigned char* data, int col, int numCols, int numRows,
285*b290403dSRicardo Garcia sonicSpectrogram spectrogram, sonicSpectrum spectrum,
286*b290403dSRicardo Garcia sonicSpectrum nextSpectrum, int colTime) {
287*b290403dSRicardo Garcia double minPower = spectrogram->minPower;
288*b290403dSRicardo Garcia double maxPower = spectrogram->maxPower;
289*b290403dSRicardo Garcia int row;
290*b290403dSRicardo Garcia for (row = 0; row < numRows; row++) {
291*b290403dSRicardo Garcia double power =
292*b290403dSRicardo Garcia interpolateSpectrogram(spectrum, nextSpectrum, row, numRows, colTime);
293*b290403dSRicardo Garcia if (power < minPower && power > maxPower) {
294*b290403dSRicardo Garcia fprintf(stderr, "Power outside min/max range\n");
295*b290403dSRicardo Garcia exit(1);
296*b290403dSRicardo Garcia }
297*b290403dSRicardo Garcia double range = maxPower - minPower;
298*b290403dSRicardo Garcia /* Use log scale such that log(min) = 0, and log(max) = 255. */
299*b290403dSRicardo Garcia int value =
300*b290403dSRicardo Garcia 256.0 * sqrt(sqrt(log((M_E - 1.0) * (power - minPower) / range + 1.0)));
301*b290403dSRicardo Garcia /* int value = (unsigned char)(((power - minPower)/range)*256); */
302*b290403dSRicardo Garcia if (value >= 256) {
303*b290403dSRicardo Garcia value = 255;
304*b290403dSRicardo Garcia }
305*b290403dSRicardo Garcia data[row * numCols + col] = 255 - value;
306*b290403dSRicardo Garcia }
307*b290403dSRicardo Garcia }
308*b290403dSRicardo Garcia
309*b290403dSRicardo Garcia /* Convert the spectrogram to a bitmap. The returned array must be freed by
310*b290403dSRicardo Garcia the caller. It will be rows*cols in size. The pixels are written top row
311*b290403dSRicardo Garcia to bottom, and each row is left to right. So, the pixel in the 5th row from
312*b290403dSRicardo Garcia the top, in the 18th column from the left in a 32x128 array would be in
313*b290403dSRicardo Garcia position 128*4 + 18. NULL is returned if calloc fails to allocate the
314*b290403dSRicardo Garcia memory. */
sonicConvertSpectrogramToBitmap(sonicSpectrogram spectrogram,int numRows,int numCols)315*b290403dSRicardo Garcia sonicBitmap sonicConvertSpectrogramToBitmap(sonicSpectrogram spectrogram,
316*b290403dSRicardo Garcia int numRows, int numCols) {
317*b290403dSRicardo Garcia /* dumpSpectrogram(spectrogram); */
318*b290403dSRicardo Garcia unsigned char* data =
319*b290403dSRicardo Garcia (unsigned char*)calloc(numRows * numCols, sizeof(unsigned char));
320*b290403dSRicardo Garcia if (data == NULL) {
321*b290403dSRicardo Garcia return NULL;
322*b290403dSRicardo Garcia }
323*b290403dSRicardo Garcia int xSpectrum = 0; /* xSpectrum is index of nextSpectrum */
324*b290403dSRicardo Garcia sonicSpectrum spectrum = spectrogram->spectrums[xSpectrum++];
325*b290403dSRicardo Garcia sonicSpectrum nextSpectrum = spectrogram->spectrums[xSpectrum];
326*b290403dSRicardo Garcia int totalTime =
327*b290403dSRicardo Garcia spectrogram->spectrums[spectrogram->numSpectrums - 1]->startingSample;
328*b290403dSRicardo Garcia int col;
329*b290403dSRicardo Garcia for (col = 0; col < numCols; col++) {
330*b290403dSRicardo Garcia /* There must be at least two spectrums for this to work right. */
331*b290403dSRicardo Garcia double colTime = (double)totalTime * col / (numCols - 1);
332*b290403dSRicardo Garcia while (xSpectrum + 1 < spectrogram->numSpectrums &&
333*b290403dSRicardo Garcia colTime >= nextSpectrum->startingSample) {
334*b290403dSRicardo Garcia spectrum = nextSpectrum;
335*b290403dSRicardo Garcia nextSpectrum = spectrogram->spectrums[++xSpectrum];
336*b290403dSRicardo Garcia }
337*b290403dSRicardo Garcia addBitmapCol(data, col, numCols, numRows, spectrogram, spectrum,
338*b290403dSRicardo Garcia nextSpectrum, colTime);
339*b290403dSRicardo Garcia }
340*b290403dSRicardo Garcia return sonicCreateBitmap(data, numRows, numCols);
341*b290403dSRicardo Garcia }
342*b290403dSRicardo Garcia
343*b290403dSRicardo Garcia /* Write a PGM image file, which is 8-bit grayscale and looks like:
344*b290403dSRicardo Garcia P2
345*b290403dSRicardo Garcia # CREATOR: libsonic
346*b290403dSRicardo Garcia 640 400
347*b290403dSRicardo Garcia 255
348*b290403dSRicardo Garcia ...
349*b290403dSRicardo Garcia */
sonicWritePGM(sonicBitmap bitmap,char * fileName)350*b290403dSRicardo Garcia int sonicWritePGM(sonicBitmap bitmap, char* fileName) {
351*b290403dSRicardo Garcia printf("Writing PGM to %s\n", fileName);
352*b290403dSRicardo Garcia FILE* file = fopen(fileName, "w");
353*b290403dSRicardo Garcia if (file == NULL) {
354*b290403dSRicardo Garcia return 0;
355*b290403dSRicardo Garcia }
356*b290403dSRicardo Garcia if (fprintf(file, "P2\n# CREATOR: libsonic\n%d %d\n255\n", bitmap->numCols,
357*b290403dSRicardo Garcia bitmap->numRows) < 0) {
358*b290403dSRicardo Garcia fclose(file);
359*b290403dSRicardo Garcia return 0;
360*b290403dSRicardo Garcia }
361*b290403dSRicardo Garcia int i;
362*b290403dSRicardo Garcia int numPixels = bitmap->numRows * bitmap->numCols;
363*b290403dSRicardo Garcia unsigned char* p = bitmap->data;
364*b290403dSRicardo Garcia for (i = 0; i < numPixels; i++) {
365*b290403dSRicardo Garcia if (fprintf(file, "%d\n", 255 - *p++) < 0) {
366*b290403dSRicardo Garcia fclose(file);
367*b290403dSRicardo Garcia return 0;
368*b290403dSRicardo Garcia }
369*b290403dSRicardo Garcia }
370*b290403dSRicardo Garcia fclose(file);
371*b290403dSRicardo Garcia return 1;
372*b290403dSRicardo Garcia }
373