xref: /aosp_15_r20/external/sonic/spectrogram.c (revision b290403dc9d28f89f133eb7e190ea8185d440ecd)
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