/* Spectroscopic Toolkit version 1.96 by Pieter Suurmond, november 9, 2011. Copyright (c) 2000-2011 - Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include /* Standard headers. */ #include #include /* For strlen(). */ #include /* For logarithms, etc. */ #include "gt.h" /* Minimal toolkit to write (not read) JPG files. */ /* source gt/gt.c must be included in the ST-project. */ #include "ST_integers.h" #include "ST_wavelets.h" /* DSP-constants, wavetables and wavelet-management. */ /* ST_wavelets.c must be included in ST-project! */ #include "ST_score.h" /* Just 2 prototypes, nothing else. */ #include "ST_engine.h" /* Private definition of ENGINE struct needed here. */ /*--------------------------------------------------------------------------------*/ short scoreWAVELETS(ENGINEp E, /* See hederfile ST_score.h. */ const char* filename, int xpix, int ypix, short envelopeTracking, /* Tracking 0 = off; !0 = on. */ const char* title, FILE* msg) { #define kSTxOffset ((double)41.5) /* Rounding offsets for drawing both */ #define kSTyOffset ((double)17.5) /* the grid and the wavelets. */ #define kSTxOffseti (43) /* To diminish scaling-factors, leave */ #define kSTyOffseti (20) /* space for time- and frequency-axis. */ #define kSTyMargin (1.029302237) /* About quartertone of whitespace above and below. */ RGBPIC PIC; /* Opaque structure pointer. */ unsigned char c, R,G,B; long N; int Y, Xstart, Xend, step1, step2, stepF, err, xx, yy=1, Xcount, envIndex; /* Latter 2 for envelope-track.*/ UNSIGNED32 firstFrame, frame, step3; /* To index gaussTable[]. */ WAVELETp wlet; SIGNED16* e = NULL; /* Set to appropriate table later in case of envelopeTracking, initialised to surpress compiler-warnings (yy also). */ double envTableFact = 1.0; /* Again to surpress compiler-warnings. */ double Xscale, Yscale, Ybase, sum, hz, env = 1.0; short n; /* env initialized at 1.0 in case */ char txtbuf[16]; /* envelopeTracking switched off. */ short Xarrow = 0, Yarrow = 0; /*---------------------------------------- Use/check stats of linked list?: --------------*/ if (!(wlet = E->firstWAVELET)) /* E->WSTAT_number is not used or tested (yet). */ { if (msg) fprintf(msg, "No wavelets in linked list, unable to draw score!\n"); return 1; } if ((xpix < 128) || (ypix < 128)) { if (msg) fprintf(msg, "Sorry, %dx%d is too small!\n", xpix, ypix); return 2; } firstFrame = wlet->startFrame; /* Just handy [more readable] to use extra variable. */ /*-------------------------------------------- Auto-scale: ------------------------------------*/ Xscale = (double)(xpix-kSTxOffseti) / /* 1 extra for downrouding earlier */ (double)((UNSIGNED32)1 + E->WSTAT_lastFrame - firstFrame); /* in addWAVELET(). */ Ybase = (double)E->WSTAT_loOscillStep / kSTyMargin; Yscale = (double)E->WSTAT_hiOscillStep * kSTyMargin / Ybase; Yscale = (double)(ypix-kSTyOffseti) / log10(Yscale); /*-------------------------------------------- Create 24 bit RGB image in memory: -------------*/ err = gtCreateMemRGB(&PIC, xpix, ypix, 255, 255, 255); /* Set dimensions and background. */ if (err) { if (msg) fprintf(msg, "Sorry, gtCreateMemRGB() = %d!\n", err); return 3; } /*------------------------------------------------------------ Y-AXIS (frequency in 1/s): -----*/ n = 0; /* Adapt frequency-grid to scaling. */ if (Yscale >= 360.0) stepF = 12; /* 12 semitones per octave. */ else if (Yscale >= 180.0) stepF = 6; /* Or 6 semitones per octave,.. 4,3,2. */ else if (Yscale >= 120.0) stepF = 4; else if (Yscale >= 90.0) stepF = 3; else if (Yscale >= 60.0) stepF = 2; else stepF = 1; do { /* Very low A of 13.75Hz (a'=440 Hz).*/ hz = 13.75 * pow(2.0, (double)n / (double)stepF); /* Label all octaves of A. */ Y = ypix - (int)(kSTyOffset + (Yscale * log10(hz * E->oneOverSamplerate64 / Ybase))); if (Y <= ypix - 12) { if (!(n % stepF)) { if (hz < 100.0) sprintf(txtbuf, "%.2f", hz); else if (hz < 1000.0) sprintf(txtbuf, "%.1f", hz); else if (hz < 10000.0) sprintf(txtbuf, " %.0f", hz); else sprintf(txtbuf, "%.0f", hz); gtDrawString(PIC, 2, Y-5, /* Object, x,y. */ 31,31,255, /* r,g,b = very dark blue. */ 0, txtbuf); if (!Yarrow) /* Initially 0. */ { if (Y < (ypix - 31)) yy = ypix - 23; /* As much to the bottom as possible. */ else yy -= 16; /* Otherwise less beautiful. */ gtDrawString(PIC, 9, yy, /* Object, x,y. */ 31,31,255, /* r,g,b = very dark blue. */ 0, " -1"); gtDrawString(PIC, 9, yy + 2, /* Object, x,y. */ 31,31,255, /* r,g,b = very dark blue. */ 0, "s"); Yarrow = 1; /* Remember we've done it. */ } c = 63; /* Dark blue. */ } else c = 127; /* Light blue. */ for (xx = 36; xx <= xpix; xx += 2) /* Dashed line, Y constant during loop. */ gtSetPixel(PIC, xx,Y, c,c,255); /* Object, x,y, r,g,b. */ } n++; } while (hz < 0.5*(double)E->samplerate); /* Could be optimized by looking at Y. */ /*------------------------------------------------------------ X-AXIS (time in s): ---------*/ N = 0; frame = 0; /* N is in seconds. */ if (Xscale < 0.00003) { step1 = 20; step2 = 120; } /* Adapt time-grid to scaling. */ else if (Xscale < 0.00004) { step1 = 15; step2 = 60; } else if (Xscale < 0.00006) { step1 = 10; step2 = 30; } else if (Xscale < 0.00012) { step1 = 5; step2 = 20; } else if (Xscale < 0.00015) { step1 = 4; step2 = 20; } else if (Xscale < 0.00030) { step1 = 2; step2 = 10; } else { step1 = 1; step2 = 5; } step3 = E->samplerate * (UNSIGNED32)step1; while (frame < E->WSTAT_lastFrame) { /* Type-cast from unsigned to signed! */ Xstart = (int)(kSTxOffset + (Xscale * (double)((SIGNED32)frame - (SIGNED32)firstFrame))); if (Xstart >= 36) /* Above = bug fix @ version ST1.16. */ { if (!(N % step2)) /* Label all multiples of step2 seconds. */ { sprintf(txtbuf, "%ld", N); xx = Xstart - (strlen(txtbuf) * 3); /* 3 is half font-width. */ yy = ypix - 11; gtDrawString(PIC, xx,yy, 31,31,255, 0, txtbuf); if (!Xarrow) /* Initially 0. */ { if (xx > 54) { xx = 42; yy--; } /* As much to left as possible; lift one pixel. */ else xx = 10 + Xstart + (strlen(txtbuf) * 3); /* Otherwise less beautiful. */ gtDrawString(PIC, xx,yy, 31,31,255, 0, "s"); Xarrow = 1; /* Remember we've done it. */ } c = 63; /* dark blue. */ } else c = 127; /* light blue. */ xx = Xstart; /* X stays constant during loop. */ for (yy = 0; yy <= ypix-12; yy += 2) /* Draw dashed line. */ { /* gtFontSmall 12 pix high. */ gtGetPixel(PIC, xx,yy, &R,&G,&B); /* Do NOT "lighten" dark blue */ if (R == 255) /* dots from frequency-axis. */ gtSetPixel(PIC, xx,yy, c,c,255); } } frame += step3; N += step1; /* Advance step1 seconds. */ } /*-------------------------------------------------------------- Drawing in memory: -----------------*/ while (wlet) /* ALREADY DONE ABOVE: wlet = E->rootWAVELET.nxt; */ { Y = ypix - (int)(kSTyOffset + (Yscale * log10((double)wlet->oscillatorStep / Ybase))); frame = wlet->startFrame - firstFrame; Xstart = (int)(kSTxOffset + (Xscale * (double)frame)); Xend = (int)(kSTxOffset + Xscale * ((long double)frame + (E->two_raise_64 / (long double)wlet->envelopeStep))); if (envelopeTracking) /* All envelope tables have the same length. */ { envTableFact = (double)kEnvTableSize / (double)(Xend - Xstart); e = E->envTables[wlet->envelopeShape]; /* The choosen envelope. Never out of range. */ } /*--------------------------------- Calculate squared sum of left and right channel: ----------*/ sum = (double)wlet->amplitudeLeft * (double)wlet->amplitudeLeft; sum += (double)wlet->amplitudeRight * (double)wlet->amplitudeRight; sum = 1.0 + (0.00001 * sum); /* At least 1 pixel-depth, even for the weakest wavelet! */ Xcount = 0; /* Y stays constant during the following loop. */ while (Xstart <= Xend) /* 0.000008 is an emperically found value, maybe adjust. */ { double D; /*------------------------- Add to pixels already there (root of sum of squares) : --------*/ gtGetPixel(PIC, Xstart,Y, &R,&G,&B); /* Overwrite blue & lightblue grid. */ if (envelopeTracking) /* Otherwise env was initialized to 1.0 above. */ { /* Multiplic every time, no++ accum-errors. */ envIndex = (int)(0.5 + (double)Xcount++ * envTableFact); if (envIndex < kEnvTableSize) /* Prevent index-overflow, keep last value. */ env = (double)e[envIndex] / (double)-32768.0; /* -32768.0 is "top"-value! */ /* env *= env; Theoretically we must square env because amplitude is also squared. */ /* However, the score becomes all too white then, so I DON'T square :-). */ } N = R * R; D = (double)N - (double)(255-(wlet->rgb>>16)) * (env*sum) / 255.0; if (D < 0) D = 0.0; N = (int)(0.5 + sqrt(D)); if (N > 255) N = 255; /* Clip channel. */ R = N; N = G * G; D = (double)N - (double)(255-((wlet->rgb>>8) & 255)) * (env*sum) / 255.0; if (D < 0) D = 0.0; N = (int)(0.5 + sqrt(D)); if (N > 255) N = 255; G = N; N = B * B; /* N is int is 32 bit thus c^2 fits!?! */ D = (double)N - (double)(255-(wlet->rgb & 255)) * (env*sum) / 255.0; if (D < 0) D = 0.0; N = (int)(0.5 + sqrt(D)); if (N > 255) N = 255; B = N; /* If envelopeTracking=0, envelope is not */ /* tracked, only overall amplitude is */ /* displayed: the louder, the blacker. */ gtSetPixel(PIC, Xstart,Y, R,G,B); Xstart++; } wlet = wlet->nxt; } /*----------------------------- print the title-string in red at the top-left corner: --------*/ for (xx=39; xx<=43; xx++) /* First clear area around used pixels. */ for (yy=0; yy<=4; yy++) gtDrawString(PIC, xx,yy, 255,255,255, 0, title); gtDrawString(PIC, 41,2, 127,0,0, 0, title); /* 1=white background; 0=transparent. */ /*---------------------------------------------------- Write JPEG file to disk: -----------------*/ if (gtSaveMemRGB(PIC, filename, 90)) /* Quality 90% for scores seems sufficient. */ { gtDestroyMemRGB(&PIC); if (msg != NULL) fprintf(msg, "Not able to save (overwrite) JPEG-scorefile!\n"); return 4; } if (msg != NULL) fprintf(msg, "Saved %dx%d score '%s' to disk.\n", xpix, ypix, filename); gtDestroyMemRGB(&PIC); /* Cleanup memory. */ return 0; } /*----------------------------------------------------------------------*/ static void tableGrid(RGBPIC pic, int w, int h, int margin) { /* (Object, width, height, margin. ) */ int xx, yy, x0 = margin, x1 = w - margin - 1, y0 = margin, y1 = h >> 1, y2 = h - margin - 1; for (xx = x0; xx <= x1; xx += 2) /* Dark blue. */ { yy = y0; gtSetPixel(pic, xx, yy, 63,63,255); yy = y1; gtSetPixel(pic, xx, yy, 63,63,255); yy = y2; gtSetPixel(pic, xx, yy, 63,63,255); } for (yy = y0; yy <= y2; yy += 2) { xx = x0; gtSetPixel(pic, xx, yy, 63,63,255); xx = x1; gtSetPixel(pic, xx, yy, 63,63,255); } } /*---------------------------------------------------------------------------------------*/ short visualiseTables(ENGINEp E, int width, int height, FILE* msg) { int jpegQuality = 90; /* Quality 90% seems sufficient. */ int xx, yy, margin = 2; /* Number of pixels around all sides. */ int EffWidth = width -1-(margin<<1), /* Remaining width and height. */ EffHeight = height-1-(margin<<1); char buffy[128]; short i, e = 0; UNSIGNED32 n; /* 32 bit to index tables. */ SIGNED16 *envSample; /* To read content from envelope table. */ SIGNED32 *audioSample; double Xfact, Yfact, Ycenter; RGBPIC PIC; /* Specify desired dimensions, */ if (E == NULL) /* Assume the tables are already allocated and filled! */ return 1; if ((width < 128) || (height < 64)) { if (msg != NULL) fprintf(msg, "Sorry, %dx%d is too small!\n", width, height); return 2; } /*---------------------------------------- Create 24 bit RGB image in memory: -------*/ if (gtCreateMemRGB(&PIC, width,height, 255,255,255)) /* Receive RGBPIC-struct in PIC. */ { if (msg != NULL) fprintf(msg, "Sorry, not enough memory!\n"); return 3; } Ycenter = 0.5 * (double)EffHeight; /*---------------------------------------- Draw envelope tables: --------------------*/ Xfact = (double)EffWidth /(double)kEnvTableSize; Yfact = (double)EffHeight/(double)65536.0; /* Envelopes are stored negative. */ for (i=0; i>1); yy += 2) gtSetPixel(PIC, xx, yy, 127,127,255); xx = margin + (int)((0.6 * (double)width) + 0.5); } else if (i == kENV_ExpDecay) xx = margin + (int)(0.5 + ((double)EffWidth / 15.0)); else /* Thus (i == kENV_ExpRise) */ xx = margin + (int)(0.5 + (14.0 * (double)EffWidth / 15.0)); for (yy = margin; yy <= (height>>1); yy += 2) gtSetPixel(PIC, xx, yy, 127,127,255); envSample = E->envTables[i]; for (n=0; n> 1; yy = (3 * height) >> 2; /* String in red. */ gtDrawString(PIC, xx, yy, 127,0,0, 0, buffy); /* 1=white background; 0=transparent. */ sprintf(buffy, "table_envelope%d.jpg", i); if (gtSaveMemRGB(PIC, buffy, jpegQuality)) /* Write JPEG file to disk. */ goto saveFailed; if (msg != NULL) fprintf(msg, "Saved '%s' to disk.\n", buffy); gtErase(PIC, 255,255,255); /* White background. */ } /*---------------------------------------- Draw sinoid table: --------------------*/ Xfact = (double)EffWidth / (double)kSineTableSize; Yfact = (double)EffHeight / (double)-65536.0; /* -32768.0 to +32768.0 inclusive. */ tableGrid(PIC, width, height, margin); audioSample = E->sineTable; for (n=0; n> 2) - 3*strlen(buffy); yy = (3 * height) >> 2; /* String in red. */ gtDrawString(PIC, xx, yy, 127,0,0, 0, buffy); /* 1=white background; 0=transparent. */ sprintf(buffy, "table_sine.jpg"); if (gtSaveMemRGB(PIC, buffy, jpegQuality)) /* Write JPEG file to disk. */ { saveFailed: if (msg != NULL) fprintf(msg, "Failed to save '%s'!\n", buffy); e = 4; } else if (msg != NULL) fprintf(msg, "Saved '%s' to disk.\n", buffy); gtDestroyMemRGB(&PIC); /* Cleanup memory. */ return e; }