123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- // FM - frequency modulator
- // 2016 Tomasz Sulej, generateme.blog@gmail.com, http://generateme.tumblr.com
- // Licence: http://unlicense.org/
-
- // Frequency modulation and demodulation of the image
- // process goes in following way:
- // - convert RGB into desired colorspace (GS - grayscale)
- // For every channel
- // - modulate signal
- // - quantize and dequantize signal (if quantval > 0)
- // - demodulate signal using derivative method
- // - apply 3 lowpass filters in a chain to remove carrier (if possible)
- // Combine channels and convert back to RGB
-
- // Usage:
- // * move mouse X axis - change the carrier wave frequency
- // * move mouse Y axis - change bandwidth
- // * click mouse to fix setup (click again to release)
- // * press N to negate image
- // * press SPACE to save
-
-
- class FM extends Shader {
- // configuration
- int colorspace = RGB;
- final static boolean first_channel_only = false; // for L.. or Y.. colorspaces set true to modulate only luma;
- final static int quantval = 30; // 0 - off, less - more glitch, more - more precision
- final static boolean lowpass1_on = true; // on/off of first low pass filter
- final static boolean lowpass2_on = true; // on/off of second low pass filter
- final static boolean lowpass3_on = true; // on/off of third low pass filter
-
- // better don't touch it, lowpass filters are run in cascade
- float lowpass1_cutoff = 0.25; // percentage of rate
- float lowpass2_cutoff = 0.1;
- float lowpass3_cutoff = 0.05;
-
- boolean do_blend = true; // blend image after process
- int blend_mode = OVERLAY; // blend type
-
- // working buffer
- PGraphics buffer;
-
- // image
- //PImage img;
-
- // local variables
- float min_omega, max_omega;
- float min_phase_mult=0.05;
- float max_phase_mult=50.0;
- LowpassFilter lpf1, lpf2, lpf3;
- int[][] pxls;
- boolean negate = false;
-
- FM() {
- name = "fxFM";
- //img = loadImage(foldername+filename+fileext);
- params.add(new Param ("blend_mode", 11, new int[]{RANDOM}));
- params.add(new Param ("omega", 0, 1, new int[]{SIN, LINE}));
- params.add(new Param ("phase", 0, 1, new int[]{LINE, SIN, TAN, RAMPUPDOWN, RAMP}));
- params.get(0).randomize();
- params.get(1).randomize();
- params.get(2).randomize();
-
- buffer = createGraphics(renderer.width, renderer.height);
- buffer.beginDraw();
- buffer.noStroke();
- //buffer.smooth(8);
- //buffer.background(0);
- // buffer.image(renderer, 0, 0);
- buffer.endDraw();
-
- //img.loadPixels();
-
- min_omega = TWO_PI/(0.05*renderer.width);
- max_omega = TWO_PI/(300.0*renderer.width);
-
- float rate = 100000.0;
-
- lpf1 = new LowpassFilter(rate, lowpass1_cutoff*rate);
- lpf2 = new LowpassFilter(rate, lowpass2_cutoff*rate);
- lpf3 = new LowpassFilter(rate, lowpass3_cutoff*rate);
-
- rw = renderer.width;
- prepareData();
- }
- //float inc1, inc2;
- //float playSpeed = 4;
- /*
- void animate() {
- blend_mode = (int)params.get(0).value + 2;
- inc1+=playSpeed/400;
- inc2+=playSpeed/300;
- if ((frameCount % int(map(playSpeed, 0, 4, 15, 120))) == 0) {
- for (int i = 0; i < params.size(); i++)
- params.get(i).randomize();
- }
- params.get(1).setValue((sin(inc1)+1)/2);
- params.get(2).setValue((sin(inc2)+1)/2);
- }
- */
- void prepareData() {
- pxls = new int[3][renderer.pixels.length];
- for (int i=0; i<renderer.pixels.length; i++) {
- int cl = toColorspace(renderer.pixels[i], colorspace);
- pxls[0][i] = (cl >> 16) & 0xff;
- pxls[1][i] = (cl >> 8) & 0xff;
- pxls[2][i] = (cl) & 0xff;
- }
- }
-
- float omega, min_phase, max_phase;
-
-
- int rw, rh;
- void apply() {
- if (rw != renderer.width || rh != renderer.height) {
- rw = renderer.width;
- rh = renderer.height;
- min_omega = TWO_PI/(0.05*renderer.width);
- max_omega = TWO_PI/(300.0*renderer.width);
- prepareData();
- }
- buffer.setSize(renderer.width, renderer.height);
-
- omega = map(sqrt(params.get(1).value), 0, 1, min_omega, max_omega);
-
- float phase = map(sq(params.get(2).value), 0, 1, min_phase_mult, max_phase_mult);
- max_phase = phase * omega;
- min_phase = -max_phase;
-
- processImage();
- }
-
- void processImage() {
- buffer.beginDraw();
- buffer.loadPixels();
-
- int [][] dest_pxls = new int[3][renderer.pixels.length];
-
- if (first_channel_only) {
- arrayCopy(pxls[1], dest_pxls[1]);
- arrayCopy(pxls[2], dest_pxls[2]);
- }
-
- for (int i=0; i< (first_channel_only?1:3); i++) {
- for (int y=0; y<renderer.height; y++) {
- int off = y * renderer.width;
-
- //reset filters each line
- lpf1.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
- lpf2.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
- lpf3.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
-
- float sig_int = 0; // integral of the signal
- float pre_m = 0; // previous value of modulated signal
-
- for (int x=0; x<renderer.width; x++) {
-
- /////////////////////////
- // FM part starts here
- /////////////////////////
-
- float sig = map(pxls[i][x+off], 0, 255, min_phase, max_phase); // current signal value
- sig_int += sig; // current value of signal integral
-
- float m = cos(omega * x + sig_int); // modulate signal
-
- if ( quantval > 0) {
- m = map((int)map(m, -1, 1, 0, quantval), 0, quantval, -1, 1); // quantize
- }
-
- float dem = abs(m-pre_m); // demodulate signal, derivative
- pre_m = m; // remember current value
-
- // lowpass filter chain
- if (lowpass1_on) dem = lpf1.lowpass(dem);
- if (lowpass2_on) dem = lpf2.lowpass(dem);
- if (lowpass3_on) dem = lpf3.lowpass(dem);
-
- // remap signal back to channel value
- int v = constrain( (int)map(2*(dem-omega), min_phase, max_phase, 0, 255), 0, 255);
-
- //////////////////////
- // FM part ends here
- //////////////////////
-
- dest_pxls[i][x+off] = negate?255-v:v;
- }
- }
- }
-
- for (int i=0; i<buffer.pixels.length; i++) {
- buffer.pixels[i] = fromColorspace(0xff000000 | (dest_pxls[0][i] << 16) | (dest_pxls[1][i] << 8) | (dest_pxls[2][i]), colorspace);
- }
-
- buffer.updatePixels();
-
- if (do_blend)
- buffer.blend(renderer, 0, 0, renderer.width, renderer.height, 0, 0, buffer.width, buffer.height, blend_mode);
-
- buffer.endDraw();
- renderer.beginDraw();
- renderer.image(buffer, 0, 0, renderer.width, renderer.height);
- renderer.endDraw();
- }
-
-
- //
-
- final int[] blends = {
- ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN
- };
-
- class LowpassFilter {
- float alpha;
- float prev;
-
- public LowpassFilter(float rate, float hz) {
- alpha = 0.0;
- prev = 0.0;
- setFilter(rate, hz);
- }
-
- void setFilter(float rate, float hz) {
- float timeInterval = 1.0/rate;
- float tau = 1.0 / (hz * TWO_PI);
- alpha = timeInterval / (tau + timeInterval);
- }
-
- void resetFilter(float val) {
- prev = val;
- }
-
- void resetFilter() {
- resetFilter(0);
- }
-
- float lowpass(float sample) {
- float stage1 = sample * alpha;
- float stage2 = prev - (prev * alpha);
- prev = (stage1 + stage2);
- return prev;
- }
-
- float highpass(float sample) {
- return sample - lowpass(sample);
- }
- }
- }
|