{
   "author" : "Dindoléon",
   "name" : "luce : standalone synth and custom slider GUI demo",
   "description" : "luce is a standalone synth module driven by a custom 2D slider GUI. It is easy to integrate and to modify. It also serves the purpose of understanding the bases of developping modules with SuperCollider for beginners.",
   "ancestor_list" : [],
   "labels" : [
      "gui",
      "beginner",
      "slider",
      "module"
   ],
   "code" : "(\r\n/* luce module for SuperCollider, licensed under GNU GPL v3 (see end of file),\r\n\r\nLuce is a surname, which means 'Light'; (latin origin: \"Lux\", see Genesis 1:3 for more informations).\r\n\r\nThis software was written by Simon Deplat (aka Dindoleon), but should be considered as a production of the whole SuperCollider community.\r\n\r\nThis demonstrates how to :\r\n- Create a standalone module which opens inside a new window,\r\n- Create a custom graphical 2d slider. Be careful, it only responds to mouse action, not to keyboard instructions,\r\n- Write a simple SC code for beginners, as I tried to comment each step of the software. This covers creating variables, instanciating a SynthDef, creating a GUI, and creating functions which allows this GUI to interact with the playing synth,\r\n\r\nThis is a part of a music station I'm currently working on. The idea is that you can integrate this little module inside your own GUI, by passing a view as display argument to the slider, instead of the window (see line 59 - 60).\r\nAlso, you can easily create new modules by following those steps :\r\n- Change the SynthDef by another SynthDef,\r\n- Change the 'amount' mapping according to your needs,\r\n- Finally (optionnal), change the name of the window and the colors of the displays.\r\n\r\nSee http://sccode.org/1-5aZ for an uncommented alternate version.\r\n\r\nSpecial thanks to Bruno Ruviaro for his GUI examples which inspired (and allowed) this piece of code.\r\n*/\r\n\r\n// Variables we're going to use :\r\nvar synth, synth_amount_range, win, window_size, slider, margin, value, knob_size, knob_outline_size, stroke_size, frame_color, frame_border_color, gradient_color, diamond_color, diamond_outline_color;\r\n\r\n// Size of items\r\nwindow_size = [ 400, 300 ];\r\n\r\nknob_size = 8;\r\nknob_outline_size = 15;\r\nstroke_size = 2;\r\n\r\nmargin = 3;\r\n\r\n// Colors\r\nframe_color = Color.new( 0.7, 0.4, 0 );\r\nframe_border_color = Color.new( 1, 0.4, 0 );\r\ngradient_color = Color.new( 0.9, 0.5, 0.3 );\r\ndiamond_color = Color.new( 1, 0.75, 0.25 );\r\ndiamond_outline_color = Color.new( 0.8, 0.55, 0.05 );\r\n\r\n// Mapping ( Y axis doesn't require further variable mapping as amplitude goes from 0 to 1 )\r\nvalue = [ 0, 1 ];\r\nsynth_amount_range = [ 24, 16000 ];\r\n\r\n// Synthdef; this a really simple sound inspired by the amazing Tour of UGen documentation file.\r\nsynth = SynthDef(\\luce, {\r\n\t|out = 0, freq = 440, amp = 0, amount = 24|\r\n\tvar snd;\r\n\r\n\tsnd = Resonz.ar( Saw.ar(freq), amount, 0.2 ); // Change this mul: variable if you want to increase the max amplitude of the synth, but go easy on it because you could easily harm your ears !\r\n\r\n\tOut.ar(out, Pan2.ar(snd, 0, amp));\r\n}).play;\r\n\r\n// GUI management:\r\nwin = Window(\"luce\", Rect(0, 0, window_size[0], window_size[1]), false);\r\nwin.background_( frame_color );\r\n\r\n// Change the 'win' argument for a View if you want to integrate it inside a custom GUI. You will also need to change the window_size variable according to the size you want the module to have.\r\nslider = UserView( win, Rect( margin, margin, window_size[0] - ( margin * 2 ), window_size[1] - ( margin * 2 ) ));\r\n\r\n// Here is the function used to draw the custom slider:\r\nslider.drawFunc = {\r\n\tPen.width = stroke_size;\r\n\r\n\t// First, draw the background frame:\r\n\tPen.addRect( Rect(0,0, slider.bounds.width,slider.bounds.height) );\r\n\tPen.fillAxialGradient( slider.bounds.leftBottom, slider.bounds.rightTop, Color.black, gradient_color );\r\n\r\n\t// Draw the diamond itself:\r\n\tPen.moveTo( ( slider.bounds.width * value[0] - knob_size ) @ ( slider.bounds.height * value[1] ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] - knob_size ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] + knob_size ) @ (( slider.bounds.height * value[1] ) ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] + knob_size ) );\r\n\t// Fourth line isn't needed as we fill the shape.\r\n\r\n\tPen.fillColor_( diamond_color );\r\n\tPen.fill;\r\n\r\n\t// Draw the diamond outline:\r\n\tPen.moveTo( 0 @ ( slider.bounds.height * value[1] ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] - knob_outline_size ) @ ( slider.bounds.height * value[1] ) );\r\n\r\n\tPen.moveTo( ( slider.bounds.width * value[0] ) @ 0 );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] - knob_outline_size ) );\r\n\r\n\tPen.moveTo( ( slider.bounds.width * value[0] + knob_outline_size ) @ (( slider.bounds.height * value[1] ) ) );\r\n\tPen.lineTo( slider.bounds.width @ (( slider.bounds.height * value[1] ) ) );\r\n\tPen.moveTo( ( slider.bounds.width * value[0] ) @ (( slider.bounds.height * value[1] + knob_outline_size ) ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] ) @  slider.bounds.height );\r\n\r\n\tPen.moveTo( ( slider.bounds.width * value[0] - knob_outline_size ) @ ( slider.bounds.height * value[1] ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] - knob_outline_size ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] + knob_outline_size ) @ (( slider.bounds.height * value[1] ) ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] ) @ (( slider.bounds.height * value[1] + knob_outline_size ) ) );\r\n\tPen.lineTo( ( slider.bounds.width * value[0] - knob_outline_size ) @ ( slider.bounds.height * value[1] ) );\r\n\r\n\tPen.strokeColor_( diamond_outline_color );\r\n\tPen.stroke;\r\n\r\n\t// Draw the frame border:\r\n\tPen.addRect( Rect(0,0, slider.bounds.width,slider.bounds.height) );\r\n\tPen.strokeColor_( frame_border_color );\r\n\tPen.stroke;\r\n};\r\n\r\n// Set the default action\r\nslider.action = {\r\n\tsynth.set(\\amp, 1 - value[1]); // Requires to invert the value, as the Y axis from GUI goes from top to bottom, and sliders usually goes from bottom to top.\r\n\tsynth.set(\\amount, linexp(value[0], 0, 1, synth_amount_range[0], synth_amount_range[1])); // Exponential mapping between the 0 -> 1 value and the amount range. Change this settings according to your needs.\r\n\r\n\tslider.refresh // Call the drawFunc of the slider to update graphics\r\n};\r\n\r\n// Define mouse actions\r\nslider.mouseDownAction = { arg slider, x = 0.5, y = 0.5, m;\r\n\t([256, 0].includes(m)).if{ // restrict to no modifier\r\n\t\tvalue[0] = (x).linlin(0,slider.bounds.width,0,1); // Linear mapping between the slider size and 0 -> 1\r\n\t\tvalue[1] = (y).linlin(0,slider.bounds.height,0,1);\r\n\t\tslider.doAction};\r\n};\r\n\r\nslider.mouseMoveAction = slider.mouseDownAction; // Map the mouse action to its function\r\n\r\nwin.front; // Start the software by giving life to the window\r\n\r\n/* LICENSE:\r\n\r\nThis program is free software: you can redistribute it and/or modify\r\n    it under the terms of the GNU General Public License as published by\r\n    the Free Software Foundation, either version 3 of the License, or\r\n    (at your option) any later version.\r\n\r\n    This program is distributed in the hope that it will be useful,\r\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n    GNU General Public License for more details.\r\n\r\n    You should have received a copy of the GNU General Public License\r\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\r\n\r\n*/\r\n)",
   "id" : "1-5aY",
   "is_private" : null
}
