diff --git a/app/server/ruby/lib/sonicpi/synths/synthinfo.rb b/app/server/ruby/lib/sonicpi/synths/synthinfo.rb index 05893c09dc..c3c76a97e6 100644 --- a/app/server/ruby/lib/sonicpi/synths/synthinfo.rb +++ b/app/server/ruby/lib/sonicpi/synths/synthinfo.rb @@ -4927,6 +4927,197 @@ def specific_arg_info end end + class Flute < SonicPiSynth + def name + "Flute" + end + + def introduced + Version.new(4,6,0) + end + + def synth_name + "flute" + end + + def on_start(studio, args_h) + args_h[:rand_buf] = studio.rand_buf_id + end + + def doc + "A synth that can create sounds of different types of flutes or even of organ pipes. Adapted from [Perry Cook's Slide Flute](https://ccrma.stanford.edu/software/clm/compmus/clm-tutorials/pm.html#s-f), this synth is created based on a physical model of a slide flute, a so called waveguide. Unlike most other synths, it does not have a conventional oscillator for sound creation. The sound builds up from feedback. This is also the reason why only a range of notes will produce good sounds, just like playing a real flute. The range of notes producing a clear tonality is from `:g1` (MIDI 31) to `:b5` (MIDI 83), approximately." + end + + def arg_defaults + { + :note => 40, + :note_slide => 0, + :amp => 0.4, + :amp_slide => 0, + :amp_slide_shape => 1, + :amp_slide_curve => 0, + :pan => 0, + :pan_slide => 0, + :pan_slide_shape => 1, + :pan_slide_curve => 0, + + :attack => 0, + :decay => 0, + :sustain => 0, + :release => 1, + :attack_level => 1, + :decay_level => :sustain_level, + :sustain_level => 1, + + :noise_attack => 0.06, + :noise_decay => 0.2, + :noise_sustain => 0, + :noise_release => 0.2, + :noise_attack_level => 0.99, + :noise_decay_level => :noise_sustain_level, + :noise_sustain_level => 0.9, + + :vibrato_attack => 0.5, + :vibrato_decay => 0.5, + :vibrato_sustain => 0, + :vibrato_release => 0.5, + :vibrato_attack_level => 0, + :vibrato_decay_level => :vibrato_sustain_level, + :vibrato_sustain_level => 1, + + :ibreath => 0.13, + :ibreath_slide => 0, + :ibreath_slide_shape => 1, + :ibreath_slide_curve => 0, + + :ifeedbk1 => 0.5, + :ifeedbk1_slide => 0, + :ifeedbk1_slide_shape => 1, + :ifeedbk1_slide_curve => 0, + + :ifeedbk2 => 0.57, + :ifeedbk2_slide => 0, + :ifeedbk2_slide_shape => 1, + :ifeedbk2_slide_curve => 0, + } + end + + def specific_arg_info + { + :noise_attack => + { + :doc => "Attack time for noise level. Amount of time (in beats) for sound to reach full noise value.", + :validations => [v_positive(:noise_attack)], + :modulatable => false, + :bpm_scale => true + }, + :noise_decay => + { + :doc => "Decay time for noise level. Amount of time (in beats) for sound to move from full noise value (noise attack level) to the noise sustain level.", + :validations => [v_positive(:noise_decay)], + :modulatable => false, + :bpm_scale => true + }, + :noise_sustain => + { + :doc => "Amount of time (in beats) for noise value to remain at sustain level.", + :validations => [v_positive(:noise_sustain)], + :modulatable => false, + :bpm_scale => true + }, + :noise_release => + { + :doc => "Amount of time (in beats) for sound to move from noise sustain value to noise zero.", + :validations => [v_positive(:noise_release)], + :modulatable => false, + :bpm_scale => true + }, + :noise_attack_level => + { + :doc => "The peak noise (value of noise at peak of attack).", + :validations => [v_between_inclusive(:noise_attack_level, 0, 1)], + :modulatable => false + }, + :noise_decay_level => + { + :doc => "The level of noise after the decay phase.", + :validations => [v_between_inclusive(:noise_decay_level, 0, 1)], + :modulatable => false + }, + :noise_sustain_level => + { + :doc => "The sustain noise level (value of noise at sustain time).", + :validations => [v_between_inclusive(:noise_sustain_level, 0, 1)], + :modulatable => false + }, + :vibrato_attack => + { + :doc => "Attack time for vibrato level. Amount of time (in beats) for sound to reach full vibrato value.", + :validations => [v_positive(:vibrato_attack)], + :modulatable => false, + :bpm_scale => true + }, + :vibrato_decay => + { + :doc => "Decay time for vibrato level. Amount of time (in beats) for sound to move from full vibrato value (vibrato attack level) to the vibrato sustain level.", + :validations => [v_positive(:vibrato_decay)], + :modulatable => false, + :bpm_scale => true + }, + :vibrato_sustain => + { + :doc => "Amount of time (in beats) for vibrato value to remain at sustain level.", + :validations => [v_positive(:vibrato_sustain)], + :modulatable => false, + :bpm_scale => true + }, + :vibrato_release => + { + :doc => "Amount of time (in beats) for sound to move from vibrato sustain value to vibrato zero.", + :validations => [v_positive(:vibrato_release)], + :modulatable => false, + :bpm_scale => true + }, + :vibrato_attack_level => + { + :doc => "The peak vibrato (value of vibrato at peak of attack).", + :validations => [v_between_inclusive(:vibrato_attack_level, 0, 1)], + :modulatable => false + }, + :vibrato_decay_level => + { + :doc => "The level of vibrato after the decay phase.", + :validations => [v_between_inclusive(:vibrato_decay_level, 0, 1)], + :modulatable => false + }, + :vibrato_sustain_level => + { + :doc => "The sustain vibrato level (value of vibrato at sustain time).", + :validations => [v_between_inclusive(:vibrato_sustain_level, 0, 1)], + :modulatable => false + }, + :ibreath => + { + :doc => "Determines how strongly the airflow can be appreciated.", + :validations => [v_between_inclusive(:ibreath, 0, 1)], + :modulatable => true + }, + :ifeedbk1 => + { + :doc => "`ifeedbk1` and `ifeedbk2` together control the amount of feedback that determines tonality and timbre. If both are set to zero, then there is no feedback at all, resulting in just noise. Driving them up towards 1 results in a rougher timbre. Both values together should not exceed 1.1. If they do `ifeedbk2` is clipped.", + :validations => [v_between_inclusive(:ifeedbk1, 0, 1)], + :modulatable => true + }, + :ifeedbk2 => + { + :doc => "`ifeedbk1` and `ifeedbk2` together control the amount of feedback that determines tonality and timbre. If both are set to zero, then there is no feedback at all, resulting in just noise. Driving them up towards 1 results in a rougher timbre. Both values together should not exceed 1.1. If they do `ifeedbk2` is clipped.", + :validations => [v_between_inclusive(:ifeedbk2, 0, 1)], + :modulatable => true + }, + } + end + end + class StudioInfo < SonicPiSynth def user_facing? false @@ -9644,6 +9835,7 @@ class BaseInfo :sc808_open_hihat => SC808OpenHihat.new, :sc808_cymbal => SC808OpenCymbal.new, :gabberkick => Gabberkick.new, + :flute => Flute.new, :sound_in => SoundIn.new, :sound_in_stereo => SoundInStereo.new, :noise => Noise.new, diff --git a/etc/synthdefs/compiled/gated/sonic-pi-flute_gated.scsyndef b/etc/synthdefs/compiled/gated/sonic-pi-flute_gated.scsyndef new file mode 100644 index 0000000000..b45c242f98 Binary files /dev/null and b/etc/synthdefs/compiled/gated/sonic-pi-flute_gated.scsyndef differ diff --git a/etc/synthdefs/compiled/sonic-pi-flute.scsyndef b/etc/synthdefs/compiled/sonic-pi-flute.scsyndef new file mode 100644 index 0000000000..4facec4b25 Binary files /dev/null and b/etc/synthdefs/compiled/sonic-pi-flute.scsyndef differ diff --git a/etc/synthdefs/designs/supercollider/flute.scd b/etc/synthdefs/designs/supercollider/flute.scd new file mode 100644 index 0000000000..a5238f8b77 --- /dev/null +++ b/etc/synthdefs/designs/supercollider/flute.scd @@ -0,0 +1,114 @@ +// Copyright notices + +// Perry Cook's Slide Flute: A Simple Flute Physical Model +// https://ccrma.stanford.edu/software/clm/compmus/clm-tutorials/pm.html#s-f + +// Source for SuperCollider adapation taken from: +// https://github.com/everythingwillbetakenaway/Synthdefs/blob/master/flute.scd + +// Originally found at http://ecmc.rochester.edu/ecmc/docs/supercollider/scbook/Ch21_Interface_Investigations/ixi%20SC%20tutorial/ixi_SC_tutorial_10.html +//by Wilson, Cottle and Collins +//also available at Bruno Ruviaro Collection https://github.com/brunoruviaro/SynthDefs-for-Patterns/blob/master/flute.scd + +( +SynthDef('sonic-pi-flute', {| + note = 40, note_slide = 0, note_slide_shape = 1, note_slide_curve = 0, + amp = 0.4, amp_slide = 0, amp_slide_shape = 1, amp_slide_curve = 0, + pan = 0, pan_slide = 0, pan_slide_shape = 1, pan_slide_curve = 0, + attack = 0, decay = 0, sustain = 0, release = 1, + attack_level = 1, decay_level = -1, sustain_level = 1, + noise_attack = 0.06, noise_decay = 0.2, noise_sustain = 0, noise_release = 0.2, + noise_attack_level = 0.99, noise_decay_level = -1, noise_sustain_level = 0.9, + vibrato_attack = 0.5, vibrato_decay = 0.5, vibrato_sustain = 0, vibrato_release = 0.5, + vibrato_attack_level = 0, vibrato_decay_level = -1, vibrato_sustain_level = 1, + ibreath = 0.13, ibreath_slide = 0, ibreath_slide_shape = 1, ibreath_slide_curve = 0, + ifeedbk1 = 0.5, ifeedbk1_slide = 0, ifeedbk1_slide_shape = 1, ifeedbk1_slide_curve = 0, + ifeedbk2 = 0.57, ifeedbk2_slide = 0, ifeedbk2_slide_shape = 1, ifeedbk2_slide_curve = 0, + out_bus = 0| + + var kenv1, kenv2, kenvibr, kvibr, sr, cr, block; + var poly, signalOut; + var aflow1, asum1, asum2, afqc, atemp1, ax, apoly, asum3, avalue, atemp2, aflute1; + var ifeedbk2_clipped, fdbckArray; + + sr = SampleRate.ir; + cr = ControlRate.ir; + block = cr.reciprocal; + + note = note.midicps; + note = note.varlag(note_slide, note_slide_curve, note_slide_shape); + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + amp = amp.varlag(amp_slide, amp_slide_curve, amp_slide_shape); + pan = pan.varlag(pan_slide, pan_slide_curve, pan_slide_shape); + + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + noise_decay_level = Select.kr(noise_decay_level < 0, [noise_decay_level, noise_sustain_level]); + vibrato_decay_level = Select.kr(vibrato_decay_level < 0, [vibrato_decay_level, vibrato_sustain_level]); + + ibreath = ibreath.varlag(ibreath_slide, ibreath_slide_curve, ibreath_slide_shape); + ifeedbk1 = ifeedbk1.varlag(ifeedbk1_slide, ifeedbk1_slide_curve, ifeedbk1_slide_shape); + ifeedbk2 = ifeedbk2.varlag(ifeedbk2_slide, ifeedbk2_slide_curve, ifeedbk2_slide_shape); + + ibreath = 0.7 * ibreath; + ifeedbk1 = 0.8 * ifeedbk1; + ifeedbk2 = 0.7 * ifeedbk2; + ifeedbk2_clipped = Select.kr((ifeedbk1 + ifeedbk2) > 1.1, [ifeedbk2, (1.1 - ifeedbk1)]); + + // Noise envelope + kenv1 = EnvGen.kr(Env.new( + [0, noise_attack_level, noise_decay_level, noise_sustain_level, 0], + [noise_attack,noise_decay,noise_sustain,noise_release], + \linear + ) + ); + + // Amp envelope + kenv2 = EnvGen.kr(Env.new( + [0, attack_level, decay_level, sustain_level, 0], + [attack,decay,sustain,release], + \sine + ), + doneAction: 2 + ); + + // vibrato envelope + kenvibr = EnvGen.kr(Env.new( + [0, vibrato_attack_level, vibrato_decay_level, vibrato_sustain_level, 0], + [vibrato_attack,vibrato_decay,vibrato_sustain,vibrato_release], + \linear + ) + ); + + // Create air flow and vibrato + // The actual tone is created by feeding back this noise signal (see LocalOut below). + // Hence, kenv1 determines if one hears anything. + aflow1 = LFClipNoise.ar( sr, kenv1 ); + kvibr = SinOsc.ar( 5, 0, 0.1 * kenvibr ); + + asum1 = ( ibreath * aflow1 ) + kenv1 + kvibr; + afqc = note.reciprocal - ( asum1/20000 ) - ( 9/sr ) + ( note/12000000 ) - block; + + fdbckArray = LocalIn.ar( 1 ); + + aflute1 = fdbckArray; + asum2 = asum1 + ( aflute1 * ifeedbk1 ); + + ax = DelayC.ar( asum2, note.reciprocal - block * 0.5, afqc * 0.5 - ( asum1/note/cr ) + 0.001 ); + + apoly = ax - ( ax.cubed ); + asum3 = apoly + ( aflute1 * ifeedbk2_clipped ); + avalue = LPF.ar( asum3, 2000 ); + + aflute1 = DelayC.ar( avalue, note.reciprocal - block, afqc ); + + fdbckArray = [ aflute1 ]; + + LocalOut.ar( fdbckArray ); + + signalOut = avalue; + + signalOut = Pan2.ar(Mix(signalOut) * kenv2, pan); + + Out.ar( out_bus, signalOut * amp ); +}).writeDefFile("/Users/sam/Development/RPi/sonic-pi/etc/synthdefs/compiled/"); +) \ No newline at end of file diff --git a/etc/synthdefs/designs/supercollider/gated/flute_gated.scd b/etc/synthdefs/designs/supercollider/gated/flute_gated.scd new file mode 100644 index 0000000000..ebe18e6b2d --- /dev/null +++ b/etc/synthdefs/designs/supercollider/gated/flute_gated.scd @@ -0,0 +1,121 @@ +// Copyright notices + +// Perry Cook's Slide Flute: A Simple Flute Physical Model +// https://ccrma.stanford.edu/software/clm/compmus/clm-tutorials/pm.html#s-f + +// Source for SuperCollider adapation taken from: +// https://github.com/everythingwillbetakenaway/Synthdefs/blob/master/flute.scd + +// Originally found at http://ecmc.rochester.edu/ecmc/docs/supercollider/scbook/Ch21_Interface_Investigations/ixi%20SC%20tutorial/ixi_SC_tutorial_10.html +//by Wilson, Cottle and Collins +//also available at Bruno Ruviaro Collection https://github.com/brunoruviaro/SynthDefs-for-Patterns/blob/master/flute.scd + +( +SynthDef('sonic-pi-flute_gated', {| + note = 40, note_slide = 0, note_slide_shape = 1, note_slide_curve = 0, + amp = 0.4, amp_slide = 0, amp_slide_shape = 1, amp_slide_curve = 0, + pan = 0, pan_slide = 0, pan_slide_shape = 1, pan_slide_curve = 0, + attack = 0, decay = 0, sustain = 0, release = 1, + attack_level = 1, decay_level = -1, sustain_level = 1, + noise_attack = 0.06, noise_decay = 0.2, noise_sustain = 0, noise_release = 0.2, + noise_attack_level = 0.99, noise_decay_level = -1, noise_sustain_level = 0.9, + vibrato_attack = 0.5, vibrato_decay = 0.5, vibrato_sustain = 0, vibrato_release = 0.5, + vibrato_attack_level = 0, vibrato_decay_level = -1, vibrato_sustain_level = 1, + ibreath = 0.13, ibreath_slide = 0, ibreath_slide_shape = 1, ibreath_slide_curve = 0, + ifeedbk1 = 0.5, ifeedbk1_slide = 0, ifeedbk1_slide_shape = 1, ifeedbk1_slide_curve = 0, + ifeedbk2 = 0.57, ifeedbk2_slide = 0, ifeedbk2_slide_shape = 1, ifeedbk2_slide_curve = 0, + gate = 1, + out_bus = 0| + + var kenv1, kenv2, kenvibr, kvibr, sr, cr, block; + var poly, signalOut; + var aflow1, asum1, asum2, afqc, atemp1, ax, apoly, asum3, avalue, atemp2, aflute1; + var ifeedbk2_clipped, fdbckArray; + + sr = SampleRate.ir; + cr = ControlRate.ir; + block = cr.reciprocal; + + note = note.midicps; + note = note.varlag(note_slide, note_slide_curve, note_slide_shape); + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + amp = amp.varlag(amp_slide, amp_slide_curve, amp_slide_shape); + pan = pan.varlag(pan_slide, pan_slide_curve, pan_slide_shape); + + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + noise_decay_level = Select.kr(noise_decay_level < 0, [noise_decay_level, noise_sustain_level]); + vibrato_decay_level = Select.kr(vibrato_decay_level < 0, [vibrato_decay_level, vibrato_sustain_level]); + + ibreath = ibreath.varlag(ibreath_slide, ibreath_slide_curve, ibreath_slide_shape); + ifeedbk1 = ifeedbk1.varlag(ifeedbk1_slide, ifeedbk1_slide_curve, ifeedbk1_slide_shape); + ifeedbk2 = ifeedbk2.varlag(ifeedbk2_slide, ifeedbk2_slide_curve, ifeedbk2_slide_shape); + + ibreath = 0.7 * ibreath; + ifeedbk1 = 0.8 * ifeedbk1; + ifeedbk2 = 0.7 * ifeedbk2; + ifeedbk2_clipped = Select.kr((ifeedbk1 + ifeedbk2) > 1.1, [ifeedbk2, (1.1 - ifeedbk1)]); + + // Noise envelope + kenv1 = EnvGen.kr(Env.new( + [0, noise_attack_level, noise_decay_level, noise_sustain_level, 0], + [noise_attack,noise_decay,noise_sustain,noise_release], + \linear, + 3 + ), + gate + ); + + // Amp envelope + kenv2 = EnvGen.kr(Env.new( + [0, attack_level, decay_level, sustain_level, 0], + [attack,decay,sustain,release], + \sine, + 3 + ), + gate, + doneAction: 2 + ); + + // vibrato envelope + kenvibr = EnvGen.kr(Env.new( + [0, vibrato_attack_level, vibrato_decay_level, vibrato_sustain_level, 0], + [vibrato_attack,vibrato_decay,vibrato_sustain,vibrato_release], + \linear, + 3 + ), + gate + ); + + // Create air flow and vibrato + // The actual tone is created by feeding back this noise signal (see LocalOut below). + // Hence, kenv1 determines if one hears anything. + aflow1 = LFClipNoise.ar( sr, kenv1 ); + kvibr = SinOsc.ar( 5, 0, 0.1 * kenvibr ); + + asum1 = ( ibreath * aflow1 ) + kenv1 + kvibr; + afqc = note.reciprocal - ( asum1/20000 ) - ( 9/sr ) + ( note/12000000 ) - block; + + fdbckArray = LocalIn.ar( 1 ); + + aflute1 = fdbckArray; + asum2 = asum1 + ( aflute1 * ifeedbk1 ); + + ax = DelayC.ar( asum2, note.reciprocal - block * 0.5, afqc * 0.5 - ( asum1/note/cr ) + 0.001 ); + + apoly = ax - ( ax.cubed ); + asum3 = apoly + ( aflute1 * ifeedbk2_clipped ); + avalue = LPF.ar( asum3, 2000 ); + + aflute1 = DelayC.ar( avalue, note.reciprocal - block, afqc ); + + fdbckArray = [ aflute1 ]; + + LocalOut.ar( fdbckArray ); + + signalOut = avalue; + + signalOut = Pan2.ar(Mix(signalOut) * kenv2, pan); + + Out.ar( out_bus, signalOut * amp ); +}).writeDefFile("/Users/sam/Development/RPi/sonic-pi/etc/synthdefs/compiled/"); +) \ No newline at end of file