/*******************************************************************************
*  Copyright 2007 - 2013, Philip S. Considine                                  *
*  This program is free software: you can redistribute it and/or modify        *
*  it under the terms of the GNU General Public License as published by        *
*  the Free Software Foundation, either version 3 of the License, or           *
*  (at your option) any later version.                                         *
*                                                                              *
*  This program is distributed in the hope that it will be useful,             *
*  but WITHOUT ANY WARRANTY; without even the implied warranty of              *
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                *
*  GNU General Public License (http://www.gnu.org/licenses/)for more details.  *
*******************************************************************************/

desc: Modified MIDI modification gizmo.

slider1:0<0,16,1{Any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}>Input Channel
slider2:0<0,127,1>Note Min
slider3:127<0,127,1>Note Max

// Note min/max (names) (disabled)
//slider2:0<0,127,1{C0,C#0,D0,D#0,E0,F0,F#0,G0,G#0,A0,A#0,B0,C1,C#1,D1,D#1,E1,F1,F#1,G1,G#1,A1,A#1,B1,C2,C#2,D2,D#2,E2,F2,F#2,G2,G#2,A2,A#2,B2,C3,C#3,D3,D#3,E3,F3,F#3,G3,G#3,A3,A#3,B3,C4,C#4,D4,D#4,E4,F4,F#4,G4,G#4,A4,A#4,B4,C5,C#5,D5,D#5,E5,F5,F#5,G5,G#5,A5,A#5,B5,C6,C#6,D6,D#6,E6,F6,F#6,G6,G#6,A6,A#6,B6,C7,C#7,D7,D#7,E7,F7,F#7,G7,G#7,A7,A#7,B7,C8,C#8,D8,D#8,E8,F8,F#8,G8,G#8,A8,A#8,B8,C9,C#9,D9,D#9,E9,F9,F#9,G9,G#9,A9,A#9,B9,C10,C#10,D10,D#10,E10,F10,F#10,G10}>Note Min
//slider3:127<0,127,1{C0,C#0,D0,D#0,E0,F0,F#0,G0,G#0,A0,A#0,B0,C1,C#1,D1,D#1,E1,F1,F#1,G1,G#1,A1,A#1,B1,C2,C#2,D2,D#2,E2,F2,F#2,G2,G#2,A2,A#2,B2,C3,C#3,D3,D#3,E3,F3,F#3,G3,G#3,A3,A#3,B3,C4,C#4,D4,D#4,E4,F4,F#4,G4,G#4,A4,A#4,B4,C5,C#5,D5,D#5,E5,F5,F#5,G5,G#5,A5,A#5,B5,C6,C#6,D6,D#6,E6,F6,F#6,G6,G#6,A6,A#6,B6,C7,C#7,D7,D#7,E7,F7,F#7,G7,G#7,A7,A#7,B7,C8,C#8,D8,D#8,E8,F8,F#8,G8,G#8,A8,A#8,B8,C9,C#9,D9,D#9,E9,F9,F#9,G9,G#9,A9,A#9,B9,C10,C#10,D10,D#10,E10,F10,F#10,G10}>Note Max

slider4:0<0,127,1>Input Velocity Min
slider5:127<0,127,1>Input Velocity Max
slider6:0<0,1,1{Clamp To Range,Ignore Outside Range}>Input Velocity Mode
slider7:100<0,1000,1>Velocity Scaling(%)
slider8:0<0,100,1>Random Velocity (%)
slider9:0<0,127,1>Output Velocity Min
slider10:127<0,127,1>Output Velocity Max
slider11:0<-60,60,1>Transpose (semitones)
slider12:0<0,100,1>Random Pitch (%)
slider13:1<0,1,1{No,Yes}>Pitch Reset
slider14:0<0,16,1{Original,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}>Output Channel
slider15:0<0,2,1{Original,Destination,Both}>Controller Routing

////////////////////////////////////////////////////////////////////////////////////////////
@init

statNoteOff = $x80;
statNoteOn = $x90;
statPitch = $xE0;
pitchCentre = 16384;

////////////////////////////////////////////////////////////////////////////////////////////
@slider

// No fractions!
slider2 = slider2 | 0;
slider3 = slider3 | 0;
slider4 = slider4 | 0;
slider5 = slider5 | 0;
slider8 = slider8 | 0;
slider9 = slider9 | 0;
slider10 = slider10 | 0;
slider11 = slider11 | 0;
slider12 = slider12 | 0;

// Clamp to respectable values
slider2 = min(slider2, slider3);
slider3 = max(slider2, slider3);
slider4 = min(slider4, slider5);
slider5 = max(slider4, slider5);
slider7 < 0 ? slider7 = 0;
slider8 = min(max(slider8, 0), 100);
slider9 = min(slider9, slider10);
slider10 = max(slider9, slider10);
slider11 = min(max(slider11, -60), 60);
slider12 = min(max(slider12, 0), 100);

inChannel = slider1 - 1; // 0-15 is specific channel, -1 is any.
noteMin = slider2;
noteMax = slider3;
inVelMin = slider4;
inVelMax = slider5;
velMode = slider6;
velScale = slider7 * 0.01;
velocityRand = slider8 * 0.01;
outVelMin = slider9;
outVelMax = slider10;
transpose = slider11;
pitchRand = slider12 * 0.01;
pitchReset = slider13;
outChannel = slider14 - 1; // 0-15 is force specific, -1 is use original
controlMode = slider15 + 1; // +1 necessary for bit masking

////////////////////////////////////////////////////////////////////////////////////////////
@block
while
(
	midirecv(offset, msg1, msg23) ?
	(
		t += 1; // Counter for random stuff
		
		// Extract message type and channel
		status = msg1 & $xF0;
		channel = msg1 & $x0F;
		
		// Is it on a channel we care about?
		(channel == inChannel || inChannel == -1)?
		(
			// Set dest channel if necessary
			outChannel > -1 ? channel = outChannel;
					
			// Is it a note event?
			(status == statNoteOn || status == statNoteOff) ?
			(
				// Extract note number and velocity
				note = msg23 & $x7F;
				velocity = msg23 >> 8;

				inVelRange = 0;

				velMode == 1 ? // Ignore notes outside velocity range
				(
					velocity > 0 ? // Zero velocity is regarded as a note off
					(
						inVelRange = (velocity < inVelMin || velocity > inVelMax) ? 0 : 1;
					);
				)
				: // Clamp input velocity to range
				(
					inVelRange = 1;
					velocity < inVelMin ? velocity = inVelMin;
					velocity > inVelMax ? velocity = inVelMax;
				);

				// Is it within our note/velocity range?
				!(note < noteMin || note > noteMax) && inVelRange ?
				(
					osc = sin(t);  // Used to make random values positive or negative 

					// Do velocity but leave fake note-off (zero velocity) events alone
					velocity > 0 ?
					(                  
						// Scale velocity by percentage, discard fractional values
						velocity = (velocity * velScale) | 0;
						
						// Randomise clamped velocity by percentage, discard fractional values
						velocityRand > 0 ? velocity = (velocity + (rand(velocity * velocityRand) * osc)) | 0;
					
						// Clamp output velocity
						velocity = min(max(velocity, outVelMin), outVelMax);
					);
					
					// Do transpose and clamp result
					note = min(max(note + transpose, 0), 127);
					
					// Generate and send pitch bend message if necessary
					pitchRand > 0 ?
					(
						pitch = pitchCentre + floor(rand(pitchCentre * pitchRand) * osc);
						midisend(offset, statPitch + channel, pitch);
					);
					
					// Pass on modified MIDI message
					midisend(offset, status + channel, note | (velocity << 8));  
				)
				: // Outside note/velocity range, pass thru
				(
					// Send pitch reset if necessary
					pitchRand > 0 && pitchReset == 1 ? midisend(offset, statPitch + channel, pitchCentre);
						
					// Pass MIDI message on
					midisend(offset, msg1, msg23);
				);
			)
			: // Not a note
			(
				controlMode & 1 ? // Pass thru on original channel if necessary
				(
					midisend(offset, msg1, msg23);
				);
				
				controlMode & 2 ? // Route to destination channel if necessary
				(
					midisend(offset, status | channel, msg23);
				);
			);
		)
		:
		(
			midisend(offset, msg1, msg23); // Not on our channel, pass thru
		);
		
		1; // Force loop to continue until all messages have been processed
	);
);

