Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flute synth (incl. gated version) #3365

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions app/server/ruby/lib/sonicpi/synths/synthinfo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Binary file not shown.
Binary file added etc/synthdefs/compiled/sonic-pi-flute.scsyndef
Binary file not shown.
114 changes: 114 additions & 0 deletions etc/synthdefs/designs/supercollider/flute.scd
Original file line number Diff line number Diff line change
@@ -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/");
)
Loading
Loading