// title: luce : standalone synth and custom slider GUI demo // author: Dindoléon // 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. // code: ( /* luce module for SuperCollider, licensed under GNU GPL v3 (see end of file), Luce is a surname, which means 'Light'; (latin origin: "Lux", see Genesis 1:3 for more informations). This software was written by Simon Deplat (aka Dindoleon), but should be considered as a production of the whole SuperCollider community. This demonstrates how to : - Create a standalone module which opens inside a new window, - Create a custom graphical 2d slider. Be careful, it only responds to mouse action, not to keyboard instructions, - 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, This 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). Also, you can easily create new modules by following those steps : - Change the SynthDef by another SynthDef, - Change the 'amount' mapping according to your needs, - Finally (optionnal), change the name of the window and the colors of the displays. See http://sccode.org/1-5aZ for an uncommented alternate version. Special thanks to Bruno Ruviaro for his GUI examples which inspired (and allowed) this piece of code. */ // Variables we're going to use : var 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; // Size of items window_size = [ 400, 300 ]; knob_size = 8; knob_outline_size = 15; stroke_size = 2; margin = 3; // Colors frame_color = Color.new( 0.7, 0.4, 0 ); frame_border_color = Color.new( 1, 0.4, 0 ); gradient_color = Color.new( 0.9, 0.5, 0.3 ); diamond_color = Color.new( 1, 0.75, 0.25 ); diamond_outline_color = Color.new( 0.8, 0.55, 0.05 ); // Mapping ( Y axis doesn't require further variable mapping as amplitude goes from 0 to 1 ) value = [ 0, 1 ]; synth_amount_range = [ 24, 16000 ]; // Synthdef; this a really simple sound inspired by the amazing Tour of UGen documentation file. synth = SynthDef(\luce, { |out = 0, freq = 440, amp = 0, amount = 24| var snd; snd = 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 ! Out.ar(out, Pan2.ar(snd, 0, amp)); }).play; // GUI management: win = Window("luce", Rect(0, 0, window_size[0], window_size[1]), false); win.background_( frame_color ); // 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. slider = UserView( win, Rect( margin, margin, window_size[0] - ( margin * 2 ), window_size[1] - ( margin * 2 ) )); // Here is the function used to draw the custom slider: slider.drawFunc = { Pen.width = stroke_size; // First, draw the background frame: Pen.addRect( Rect(0,0, slider.bounds.width,slider.bounds.height) ); Pen.fillAxialGradient( slider.bounds.leftBottom, slider.bounds.rightTop, Color.black, gradient_color ); // Draw the diamond itself: Pen.moveTo( ( slider.bounds.width * value[0] - knob_size ) @ ( slider.bounds.height * value[1] ) ); Pen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] - knob_size ) ); Pen.lineTo( ( slider.bounds.width * value[0] + knob_size ) @ (( slider.bounds.height * value[1] ) ) ); Pen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] + knob_size ) ); // Fourth line isn't needed as we fill the shape. Pen.fillColor_( diamond_color ); Pen.fill; // Draw the diamond outline: Pen.moveTo( 0 @ ( slider.bounds.height * value[1] ) ); Pen.lineTo( ( slider.bounds.width * value[0] - knob_outline_size ) @ ( slider.bounds.height * value[1] ) ); Pen.moveTo( ( slider.bounds.width * value[0] ) @ 0 ); Pen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] - knob_outline_size ) ); Pen.moveTo( ( slider.bounds.width * value[0] + knob_outline_size ) @ (( slider.bounds.height * value[1] ) ) ); Pen.lineTo( slider.bounds.width @ (( slider.bounds.height * value[1] ) ) ); Pen.moveTo( ( slider.bounds.width * value[0] ) @ (( slider.bounds.height * value[1] + knob_outline_size ) ) ); Pen.lineTo( ( slider.bounds.width * value[0] ) @ slider.bounds.height ); Pen.moveTo( ( slider.bounds.width * value[0] - knob_outline_size ) @ ( slider.bounds.height * value[1] ) ); Pen.lineTo( ( slider.bounds.width * value[0] ) @ ( slider.bounds.height * value[1] - knob_outline_size ) ); Pen.lineTo( ( slider.bounds.width * value[0] + knob_outline_size ) @ (( slider.bounds.height * value[1] ) ) ); Pen.lineTo( ( slider.bounds.width * value[0] ) @ (( slider.bounds.height * value[1] + knob_outline_size ) ) ); Pen.lineTo( ( slider.bounds.width * value[0] - knob_outline_size ) @ ( slider.bounds.height * value[1] ) ); Pen.strokeColor_( diamond_outline_color ); Pen.stroke; // Draw the frame border: Pen.addRect( Rect(0,0, slider.bounds.width,slider.bounds.height) ); Pen.strokeColor_( frame_border_color ); Pen.stroke; }; // Set the default action slider.action = { synth.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. synth.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. slider.refresh // Call the drawFunc of the slider to update graphics }; // Define mouse actions slider.mouseDownAction = { arg slider, x = 0.5, y = 0.5, m; ([256, 0].includes(m)).if{ // restrict to no modifier value[0] = (x).linlin(0,slider.bounds.width,0,1); // Linear mapping between the slider size and 0 -> 1 value[1] = (y).linlin(0,slider.bounds.height,0,1); slider.doAction}; }; slider.mouseMoveAction = slider.mouseDownAction; // Map the mouse action to its function win.front; // Start the software by giving life to the window /* LICENSE: 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 for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ )