desc: Spectro image stamper (put images into "%APPDATA%\REAPER\Data\images")

/* 
    Stamps an image over the audio spectrogram.
    
    Use any image (with reasonable small size), convert it to grayscale and save it to RAW image file (without any header). 
    Open the RAW file as 8-bit mono unsigned audio file and save it as WAVE file. Put WAVE file into "%APPDATA%\REAPER\Data\images".
    
    Next (after reloading of the effect) you will be able to select your image, represented as WAVE file. 
    Enter the proper image size values. You can freely change every other parameter.
*/

slider1:3<0,5,1{32,64,128,256,512,1024}>Bands
slider2:/images:logo.wav:Image (as 8-bit Mono WAVE file)
slider3:256<1,2000,1>Image width (px)
slider4:256<1,2000,1>Image height (px)
slider5:4<0.1,60,0.1>Duration (s)
slider6:1000<1,44100,1>Minimum frequency (Hz)
slider7:10000<1,44100,1>Maximum frequency (Hz)
slider8:1<0,1,0{Linear,Logarithmic}>Scale
slider9:100<0,100,0.01>Effect level (%)

@init

    halfSize = 0;
    buffer1L = 2048 * 0;
    buffer2L = 2048 * 2;
    buffer1R = 2048 * 4;
    buffer2R = 2048 * 6;
    bandImageYLookup = 2048 * 8; // lookup table to convert band index into Y position inside image
    buffPos = 0; 
    
    imageBuff = 1024 * 2 * 10;
    
@slider

    fftSize = 2 ^ (6 + slider1);
    halfSize = fftSize / 2;
    
    minFreq = slider6; // Hz
    maxFreq = slider7; // Hz
    imageWidth = slider3;
    imageHeight = slider4;
    magnitude = slider9 / 100;

    i = 0;
    loop(halfSize,
        bandImageYLookup[i] = -1;
        bandFreq = (i + 0.5) * 0.5 * srate / halfSize;
        maxFreq > minFreq && bandFreq > minFreq && bandFreq < maxFreq ? (
            slider8 > 0 ? (
                ratio = log10((9 * bandFreq + maxFreq - 10 * minFreq) / (maxFreq - minFreq)); // from 0 at bottom to 1 at top of image
            ) : (
                ratio = (bandFreq - minFreq) / (maxFreq - minFreq); // from 0 at bottom to 1 at top of image
            );
            bandImageYLookup[i] = imageHeight - 1 - floor(0.5 + imageHeight * ratio);
        );
        i += 1;
    );
    
    currFile != slider2 ? (
        currFile = slider2;
        handle = file_open(slider2);
        handle > 0 ? (
            file_mem(handle, imageBuff, file_avail(handle));
            file_close(handle);
        );
    );
    
    imageDuration = slider5; // seconds

@sample
    outL = buffer1L[buffPos] + buffer2L[halfSize + buffPos];
    buffer1L[buffPos] = spl0;
    buffer2L[halfSize + buffPos] = spl0;
    spl0 = outL;
    
    outR = buffer1R[buffPos] + buffer2R[halfSize + buffPos];
    buffer1R[buffPos] = spl1;
    buffer2R[halfSize + buffPos] = spl1;
    spl1 = outR;
    
    buffPos += 1;
    buffPos >= halfSize ? (
        tmpBuffer = buffer1L; buffer1L = buffer2L; buffer2L = tmpBuffer;
        tmpBuffer = buffer1R; buffer1R = buffer2R; buffer2R = tmpBuffer;
    
        mdct(buffer1L, fftSize);
        mdct(buffer1R, fftSize);
        
        i = 0;
        loop(halfSize,
            yOffset = bandImageYLookup[i];
            yOffset >= 0 && yOffset < imageHeight ? (
                
                imageOfs = floor((play_position * imageWidth / imageDuration) % imageWidth);
                gain = 0.5 * (1 + imageBuff[imageOfs + imageWidth * yOffset]);
                gain = gain * magnitude + 1 - magnitude;
                gain >= 0 && gain <= 1 ? (
                    buffer1L[i] *= gain;
                    buffer1R[i] *= gain;
                );
            );
            
            i += 1;
        );
        
        imdct(buffer1L, fftSize);
        imdct(buffer1R, fftSize);
        
        buffPos = 0;
    );
