musicality 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.coveralls.yml +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +4 -0
- data/ChangeLog.md +11 -0
- data/README.md +3 -0
- data/Rakefile +11 -3
- data/lib/musicality/composition/model/rhythm.rb +33 -0
- data/lib/musicality/composition/model/rhythm_class.rb +30 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_kit.rb +18 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_machine.rb +59 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_parts.rb +21 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_pattern.rb +66 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_patterns/pop_drum_patterns.rb +146 -0
- data/lib/musicality/composition/sequencing/note_array.rb +33 -0
- data/lib/musicality/composition/sequencing/note_fifo.rb +73 -0
- data/lib/musicality/composition/sequencing/sequenceable.rb +9 -0
- data/lib/musicality/composition/sequencing/sequencer.rb +35 -0
- data/lib/musicality/errors.rb +2 -2
- data/lib/musicality/notation/model/dynamics.rb +2 -2
- data/lib/musicality/notation/model/key.rb +42 -91
- data/lib/musicality/notation/model/keys.rb +35 -34
- data/lib/musicality/notation/model/note.rb +31 -9
- data/lib/musicality/notation/model/pitch.rb +2 -2
- data/lib/musicality/notation/parsing/convenience_methods.rb +23 -12
- data/lib/musicality/notation/parsing/duration_parsing.rb +3 -3
- data/lib/musicality/notation/parsing/key_parsing.rb +150 -0
- data/lib/musicality/notation/parsing/key_parsing.treetop +37 -0
- data/lib/musicality/notation/parsing/meter_parsing.rb +3 -3
- data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +3 -1
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +1 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +1 -1
- data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +4 -1
- data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +1 -1
- data/lib/musicality/notation/parsing/parseable.rb +13 -17
- data/lib/musicality/notation/parsing/pitch_parsing.rb +7 -0
- data/lib/musicality/notation/parsing/segment_parsing.rb +3 -0
- data/lib/musicality/performance/conversion/note_sequence_extractor.rb +82 -134
- data/lib/musicality/performance/model/note_sequence.rb +22 -3
- data/lib/musicality/performance/supercollider/performer.rb +2 -2
- data/lib/musicality/performance/supercollider/sc_drum_kits.rb +29 -0
- data/lib/musicality/performance/supercollider/synthdefs/bass.rb +211 -0
- data/lib/musicality/performance/supercollider/synthdefs/claps.rb +80 -0
- data/lib/musicality/performance/supercollider/synthdefs/cymbals.rb +57 -0
- data/lib/musicality/performance/supercollider/synthdefs/hihats.rb +67 -0
- data/lib/musicality/performance/supercollider/synthdefs/kicks.rb +158 -0
- data/lib/musicality/performance/supercollider/synthdefs/mario.rb +49 -0
- data/lib/musicality/performance/supercollider/{synthdefs.rb → synthdefs/other.rb} +0 -767
- data/lib/musicality/performance/supercollider/synthdefs/pianos.rb +46 -0
- data/lib/musicality/performance/supercollider/synthdefs/snares.rb +169 -0
- data/lib/musicality/performance/supercollider/synthdefs/toms.rb +25 -0
- data/lib/musicality/performance/supercollider/synthdefs/volume.rb +20 -0
- data/lib/musicality/pitch_class.rb +1 -1
- data/lib/musicality/pitch_classes.rb +3 -5
- data/lib/musicality/version.rb +1 -1
- data/lib/musicality.rb +25 -1
- data/musicality.gemspec +3 -2
- data/spec/composition/convenience_methods_spec.rb +8 -8
- data/spec/composition/generation/random_rhythm_generator_spec.rb +5 -5
- data/spec/composition/model/pitch_class_spec.rb +22 -16
- data/spec/composition/model/pitch_classes_spec.rb +5 -5
- data/spec/composition/model/rhythm_class_spec.rb +42 -0
- data/spec/composition/model/rhythm_spec.rb +43 -0
- data/spec/composition/model/scale_class_spec.rb +26 -26
- data/spec/composition/model/scale_spec.rb +38 -38
- data/spec/composition/sequencing/drum_machine/drum_machine_spec.rb +67 -0
- data/spec/composition/sequencing/drum_machine/drum_pattern_spec.rb +58 -0
- data/spec/composition/sequencing/note_array_spec.rb +94 -0
- data/spec/composition/sequencing/note_fifo_spec.rb +183 -0
- data/spec/composition/sequencing/sequencer_spec.rb +76 -0
- data/spec/composition/util/adding_sequence_spec.rb +33 -33
- data/spec/composition/util/compound_sequence_spec.rb +6 -6
- data/spec/composition/util/note_generation_spec.rb +34 -34
- data/spec/composition/util/probabilities_spec.rb +7 -7
- data/spec/composition/util/random_sampler_spec.rb +3 -3
- data/spec/composition/util/repeating_sequence_spec.rb +28 -28
- data/spec/musicality_spec.rb +1 -1
- data/spec/notation/conversion/change_conversion_spec.rb +87 -87
- data/spec/notation/conversion/note_time_converter_spec.rb +22 -22
- data/spec/notation/conversion/score_conversion_spec.rb +1 -1
- data/spec/notation/conversion/score_converter_spec.rb +31 -31
- data/spec/notation/conversion/tempo_conversion_spec.rb +11 -11
- data/spec/notation/model/change_spec.rb +80 -80
- data/spec/notation/model/key_spec.rb +135 -69
- data/spec/notation/model/link_spec.rb +27 -27
- data/spec/notation/model/meter_spec.rb +28 -28
- data/spec/notation/model/note_spec.rb +68 -47
- data/spec/notation/model/part_spec.rb +19 -19
- data/spec/notation/model/pitch_spec.rb +69 -68
- data/spec/notation/model/score_spec.rb +50 -47
- data/spec/notation/parsing/articulation_parsing_spec.rb +4 -4
- data/spec/notation/parsing/convenience_methods_spec.rb +49 -10
- data/spec/notation/parsing/duration_nodes_spec.rb +13 -13
- data/spec/notation/parsing/duration_parsing_spec.rb +10 -10
- data/spec/notation/parsing/key_parsing_spec.rb +19 -0
- data/spec/notation/parsing/link_nodes_spec.rb +7 -7
- data/spec/notation/parsing/link_parsing_spec.rb +4 -4
- data/spec/notation/parsing/meter_parsing_spec.rb +5 -5
- data/spec/notation/parsing/note_node_spec.rb +19 -19
- data/spec/notation/parsing/note_parsing_spec.rb +4 -4
- data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +8 -8
- data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +2 -2
- data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +1 -1
- data/spec/notation/parsing/numbers/positive_float_spec.rb +8 -8
- data/spec/notation/parsing/numbers/positive_integer_spec.rb +6 -6
- data/spec/notation/parsing/numbers/positive_rational_spec.rb +6 -6
- data/spec/notation/parsing/pitch_node_spec.rb +7 -7
- data/spec/notation/parsing/pitch_parsing_spec.rb +2 -2
- data/spec/notation/parsing/segment_parsing_spec.rb +3 -3
- data/spec/notation/util/function_spec.rb +15 -15
- data/spec/notation/util/transition_spec.rb +12 -12
- data/spec/notation/util/value_computer_spec.rb +35 -36
- data/spec/performance/conversion/glissando_converter_spec.rb +24 -24
- data/spec/performance/conversion/note_sequence_extractor_spec.rb +39 -39
- data/spec/performance/conversion/portamento_converter_spec.rb +23 -23
- data/spec/performance/midi/midi_util_spec.rb +41 -41
- data/spec/performance/midi/part_sequencer_spec.rb +10 -10
- data/spec/performance/midi/score_sequencer_spec.rb +15 -15
- data/spec/performance/midi/score_sequencing_spec.rb +2 -2
- data/spec/performance/util/optimization_spec.rb +9 -9
- data/spec/printing/note_engraving_spec.rb +16 -16
- data/spec/printing/score_engraver_spec.rb +5 -5
- data/spec/spec_helper.rb +5 -0
- metadata +85 -30
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Musicality
|
|
2
|
+
module SuperCollider
|
|
3
|
+
module SynthDefs
|
|
4
|
+
|
|
5
|
+
CHEAP_PIANO = SynthDef.new(name: "cheappiano", params: { :out => 0, :freq => 440, :amp => 1, :dur => 1, :gate => 1, :pan => 0 },
|
|
6
|
+
body: <<-SCLANG,
|
|
7
|
+
var sig, in, n = 6, max = 0.04, min = 0.01, delay, pitch, detune, hammer;
|
|
8
|
+
freq = freq.cpsmidi;
|
|
9
|
+
hammer = Decay2.ar(Impulse.ar(0.001), 0.008, 0.04, LFNoise2.ar([2000,4000].asSpec.map(amp), 0.25));
|
|
10
|
+
sig = Mix.ar(Array.fill(3, { arg i;
|
|
11
|
+
detune = #[-0.04, 0, 0.03].at(i);
|
|
12
|
+
delay = (1/(freq + detune).midicps);
|
|
13
|
+
CombL.ar(hammer, delay, delay, 50 * amp)
|
|
14
|
+
}) );
|
|
15
|
+
|
|
16
|
+
sig = HPF.ar(sig,50) * EnvGen.ar(Env.perc(0.0001,dur, amp * 4, -1), gate: gate, doneAction:2);
|
|
17
|
+
Out.ar(out, Pan2.ar(sig, pan));
|
|
18
|
+
SCLANG
|
|
19
|
+
credit: "based on something posted 2008-06-17 by jeff, based on an old example by james mcc",
|
|
20
|
+
source: "https://github.com/supercollider-quarks/SynthDefPool",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
EVERYTHING_RHODES = SynthDef.new(name: "everythingrhodes", params: { :out => 0, :freq => 440, :amp => 0.1, :gate => 1,
|
|
24
|
+
:lforate => 1.85, :lfowidth => 0.5, :cutoff => 2000, :rq => 0.2, :pan => 0.0 },
|
|
25
|
+
body: <<-SCLANG,
|
|
26
|
+
var pulse, filter, env;
|
|
27
|
+
|
|
28
|
+
pulse = Pulse.ar(freq*[1,33.5.midiratio],[0.2,0.1],[0.7,0.3]);
|
|
29
|
+
env = EnvGen.ar(Env.adsr(0.0,1.0,0.8,3.0),gate,doneAction:2);
|
|
30
|
+
//keyboard tracking filter cutoff
|
|
31
|
+
filter = BLowPass4.ar(pulse,(cutoff*(env.squared))+200+freq,rq);
|
|
32
|
+
|
|
33
|
+
Out.ar(out,Pan2.ar(Mix(filter)*env*amp,pan));
|
|
34
|
+
SCLANG
|
|
35
|
+
credit: <<-EOS,
|
|
36
|
+
Sound recipes from:
|
|
37
|
+
Mitchell Sigman (2011) Steal this Sound. Milwaukee, WI: Hal Leonard Books
|
|
38
|
+
adapted for SuperCollider and elaborated by Nick Collins (http://www.sussex.ac.uk/Users/nc81/index.html)
|
|
39
|
+
under GNU GPL 3 as per SuperCollider license
|
|
40
|
+
EOS
|
|
41
|
+
source: "https://github.com/acarabott/roundhouse-synth-design-course-2014",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
module Musicality
|
|
2
|
+
module SuperCollider
|
|
3
|
+
module SynthDefs
|
|
4
|
+
|
|
5
|
+
SNARE1 = SynthDef.new(name: "snare1", params: { :out => 0, :amp => 0.8 },
|
|
6
|
+
body: <<-SCLANG,
|
|
7
|
+
var env0, env1, env2, env1m, oscs, noise, sig;
|
|
8
|
+
|
|
9
|
+
env0 = EnvGen.ar(Env([0.5, 1, 0.5, 0], [0.005, 0.03, 0.10], [-4, -2, -4]));
|
|
10
|
+
env1 = EnvGen.ar(Env([110, 60, 49], [0.005, 0.1], [-4, -5]));
|
|
11
|
+
env1m = env1.midicps;
|
|
12
|
+
env2 = EnvGen.ar(Env([1, 0.4, 0], [0.05, 0.13], [-2, -2]), doneAction:2);
|
|
13
|
+
|
|
14
|
+
oscs = LFPulse.ar(env1m, 0, 0.5, 1, -0.5) +
|
|
15
|
+
LFPulse.ar(env1m * 1.6, 0, 0.5, 0.5, -0.25);
|
|
16
|
+
oscs = LPF.ar(oscs, env1m * 1.2, env0);
|
|
17
|
+
oscs = oscs + SinOsc.ar(env1m, 0.8, env0);
|
|
18
|
+
|
|
19
|
+
noise = WhiteNoise.ar(0.2);
|
|
20
|
+
noise = HPF.ar(noise, 200, 2);
|
|
21
|
+
noise = BPF.ar(noise, 6900, 0.6, 3) + noise;
|
|
22
|
+
noise = noise * env2;
|
|
23
|
+
|
|
24
|
+
sig = oscs + noise;
|
|
25
|
+
sig = sig.clip2(1) * amp;
|
|
26
|
+
|
|
27
|
+
Out.ar(out, sig.dup);
|
|
28
|
+
SCLANG
|
|
29
|
+
source: "https://github.com/acarabott/roundhouse-synth-design-course-2014",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
SNARE2 = SynthDef.new(name: "snare2", params: { :sfreq => 1500, :out => 0 },
|
|
33
|
+
body: <<-SCLANG,
|
|
34
|
+
var tri = Mix([LFTri.ar([111, 175, 224])]) * 0.5;
|
|
35
|
+
var sine = Mix([SinOsc.ar([330, 180])]) * 0.5;
|
|
36
|
+
var env = EnvGen.ar(Env.perc(0.01, 0.2), doneAction:2);
|
|
37
|
+
var snares = WhiteNoise.ar(1);
|
|
38
|
+
var snareEnv = EnvGen.ar(Env.perc(0.01, 0.2));
|
|
39
|
+
|
|
40
|
+
snares = HPF.ar(snares, sfreq);
|
|
41
|
+
snares = snares * snareEnv;
|
|
42
|
+
|
|
43
|
+
Out.ar(out, Mix([tri, sine, snares]) * env);
|
|
44
|
+
SCLANG
|
|
45
|
+
credit: "Based on Sound on Sound Synth Secrets 35, by Arthur Carabott",
|
|
46
|
+
source: "https://github.com/acarabott/roundhouse-synth-design-course-2014",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
SOS_SNARE = SynthDef.new(name: "SOSsnare", params: { :out => 0, :decay => 0.12, :drum_mode_level => 0.25, :snare_level => 40, :snare_tightness => 3000, :freq => 405, :amp => 0.8 },
|
|
50
|
+
body: <<-SCLANG,
|
|
51
|
+
var drum_mode_sin_1, drum_mode_sin_2, drum_mode_pmosc, drum_mode_mix,
|
|
52
|
+
drum_mode_env;
|
|
53
|
+
var snare_noise, snare_brf_1, snare_brf_2, snare_brf_3, snare_brf_4,
|
|
54
|
+
snare_reson;
|
|
55
|
+
var snare_env;
|
|
56
|
+
var snare_drum_mix;
|
|
57
|
+
|
|
58
|
+
drum_mode_env = EnvGen.ar(Env.perc(0.005, decay), 1.0, doneAction: 2);
|
|
59
|
+
drum_mode_sin_1 = SinOsc.ar(freq*0.53, 0, drum_mode_env * 0.5);
|
|
60
|
+
drum_mode_sin_2 = SinOsc.ar(freq, 0, drum_mode_env * 0.5);
|
|
61
|
+
drum_mode_pmosc = PMOsc.ar( Saw.ar(freq*0.85), 184, 0.5/1.3, mul: drum_mode_env*5, add: 0);
|
|
62
|
+
drum_mode_mix = Mix.new([drum_mode_sin_1, drum_mode_sin_2, drum_mode_pmosc]) * drum_mode_level;
|
|
63
|
+
|
|
64
|
+
// choose either noise source below
|
|
65
|
+
// snare_noise = Crackle.ar(2.01, 1);
|
|
66
|
+
snare_noise = LFNoise0.ar(20000, 0.1);
|
|
67
|
+
snare_env = EnvGen.ar(Env.perc(0.005, decay, curve:-5), 1.0, doneAction: 2);
|
|
68
|
+
snare_brf_1 = BRF.ar(in: snare_noise, freq: 8000, mul: 0.5, rq: 0.1);
|
|
69
|
+
snare_brf_2 = BRF.ar(in: snare_brf_1, freq: 5000, mul: 0.5, rq: 0.1);
|
|
70
|
+
snare_brf_3 = BRF.ar(in: snare_brf_2, freq: 3600, mul: 0.5, rq: 0.1);
|
|
71
|
+
snare_brf_4 = BRF.ar(in: snare_brf_3, freq: 2000, mul: snare_env, rq: 0.0001);
|
|
72
|
+
snare_reson = Resonz.ar(snare_brf_4, snare_tightness, mul: snare_level) ;
|
|
73
|
+
snare_drum_mix = Mix.new([drum_mode_mix, snare_reson]) * 5 * amp;
|
|
74
|
+
Out.ar(out, [snare_drum_mix, snare_drum_mix])
|
|
75
|
+
SCLANG
|
|
76
|
+
credit: "recipe basically from Gordon Reid
|
|
77
|
+
http://www.soundonsound.com/sos/Mar02/articles/synthsecrets0302.asp
|
|
78
|
+
programmed by Renick Bell, renick_at_gmail.com",
|
|
79
|
+
source: "https://github.com/willieavendano/SC-SynthDefs/blob/master/DrumMachines",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
SNARE_OTO_309 = SynthDef.new(name: "snare_oto309", params: { :out => 0, :amp => 0.1, :pan => 0 },
|
|
83
|
+
body: <<-SCLANG,
|
|
84
|
+
var env0, env1, env2, env1m, oscs, noise, son;
|
|
85
|
+
|
|
86
|
+
env0 = EnvGen.ar(Env.new([0.5, 1, 0.5, 0], [0.005, 0.03, 0.10], [-4, -2, -4]));
|
|
87
|
+
env1 = EnvGen.ar(Env.new([110, 60, 49], [0.005, 0.1], [-4, -5]));
|
|
88
|
+
env1m = env1.midicps;
|
|
89
|
+
env2 = EnvGen.ar(Env.new([1, 0.4, 0], [0.05, 0.13], [-2, -2]), doneAction:2);
|
|
90
|
+
|
|
91
|
+
oscs = LFPulse.ar(env1m, 0, 0.5, 1, -0.5) + LFPulse.ar(env1m * 1.6, 0, 0.5, 0.5, -0.25);
|
|
92
|
+
oscs = LPF.ar(oscs, env1m*1.2, env0);
|
|
93
|
+
oscs = oscs + SinOsc.ar(env1m, 0.8, env0);
|
|
94
|
+
|
|
95
|
+
noise = WhiteNoise.ar(0.2);
|
|
96
|
+
noise = HPF.ar(noise, 200, 2);
|
|
97
|
+
noise = BPF.ar(noise, 6900, 0.6, 3) + noise;
|
|
98
|
+
noise = noise * env2;
|
|
99
|
+
|
|
100
|
+
son = oscs + noise;
|
|
101
|
+
son = son.clip2(1) * amp;
|
|
102
|
+
|
|
103
|
+
Out.ar(out, Pan2.ar(son, pan));
|
|
104
|
+
SCLANG
|
|
105
|
+
credit: "from 08091500Acid309 by_otophilia",
|
|
106
|
+
source: "https://github.com/supercollider-quarks/SynthDefPool",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
SNARE_STEIN = SynthDef.new(name: "snare_stein", params: { :out => 0, :amp => 0.1, :pan => 0 },
|
|
110
|
+
body: <<-SCLANG,
|
|
111
|
+
var snare, filtWhite;
|
|
112
|
+
|
|
113
|
+
filtWhite = LPF.ar(WhiteNoise.ar(1), 7040, 1);
|
|
114
|
+
|
|
115
|
+
snare = (SinOsc.ar(330,0,0.25) * EnvGen.ar(Env.perc(0.0005,0.055))) + (SinOsc.ar(185,0,0.25) * EnvGen.ar(Env.perc(0.0005,0.075))) + (filtWhite * EnvGen.ar(Env.perc(0.0005,0.2), doneAction: 2) * 0.2) + (HPF.ar(filtWhite, 523, 1) * EnvGen.ar(Env.perc(0.0005,0.183)) * 0.2);
|
|
116
|
+
Out.ar(out, Pan2.ar(snare * amp * 10, pan));
|
|
117
|
+
SCLANG
|
|
118
|
+
credit: "Snare written by Esben Stein, I believe",
|
|
119
|
+
source: "https://github.com/supercollider-quarks/SynthDefPool",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
SNARE3 = SynthDef.new(name: "snare", params: { :amp => 1, :dur => 0.05, :out => 0 },
|
|
123
|
+
body: <<-SCLANG,
|
|
124
|
+
dur = dur * 16;
|
|
125
|
+
Out.ar(out, amp * XLine.ar(2,1/1000,dur) * BPF.ar(PinkNoise.ar(0.8), XLine.ar(20000,1000,dur, doneAction:2), 0.8).dup);
|
|
126
|
+
SCLANG
|
|
127
|
+
source: "https://github.com/bwestergard/supercollider-experiments",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
SNARE_909 = SynthDef.new(name: "snare909", params: { :out => 0, :lpFreq => 1000, :vol => 1, :gate => 1 },
|
|
131
|
+
body: <<-SCLANG,
|
|
132
|
+
var sig1, sig2;
|
|
133
|
+
var triEnv;
|
|
134
|
+
var shifted1;
|
|
135
|
+
var shifted2;
|
|
136
|
+
var sinEnv;
|
|
137
|
+
var sin1, sin2;
|
|
138
|
+
var mixed;
|
|
139
|
+
var sig3;
|
|
140
|
+
var noiseEnv;
|
|
141
|
+
|
|
142
|
+
// tri -> final mixer
|
|
143
|
+
triEnv = Env.adsr(0, 0.4, 0, 0, curve: -4, peakLevel: 0.5);
|
|
144
|
+
sig1 = LFTri.ar(111, 0, 0.5) * EnvGen.kr(triEnv, gate: gate, doneAction: 2);
|
|
145
|
+
shifted1 = FreqShift.ar(sig1, 175);
|
|
146
|
+
shifted2 = FreqShift.ar(sig1, 224);
|
|
147
|
+
sig1 = Mix.new([shifted1, shifted2]);
|
|
148
|
+
|
|
149
|
+
// sines -> final mixer
|
|
150
|
+
sin1 = SinOsc.ar(330, mul: 0.2);
|
|
151
|
+
sin2 = SinOsc.ar(180, mul: 0.2);
|
|
152
|
+
sinEnv = Env.adsr(0, 0.2, 0, 0);
|
|
153
|
+
sig2 = Mix.new([sin1, sin2]) * EnvGen.kr(sinEnv, gate: gate, doneAction: 2);
|
|
154
|
+
|
|
155
|
+
// noise -> final mixer
|
|
156
|
+
noiseEnv = Env.adsr(0, 0.3, 0, 0);
|
|
157
|
+
sig3 = LPF.ar(WhiteNoise.ar() * EnvGen.kr(noiseEnv, gate: gate, doneAction: 2), 1000);
|
|
158
|
+
sig3 = HPF.ar(sig3, 600);
|
|
159
|
+
|
|
160
|
+
mixed = Mix.new([sig1, sig2, sig3]);
|
|
161
|
+
mixed = LPF.ar(mixed, lpFreq) * vol;
|
|
162
|
+
Out.ar(out, mixed ! 2);
|
|
163
|
+
SCLANG
|
|
164
|
+
source: "https://github.com/mattvears/supercollider-stuff",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Musicality
|
|
2
|
+
module SuperCollider
|
|
3
|
+
module SynthDefs
|
|
4
|
+
|
|
5
|
+
TOM1 = SynthDef.new(name: "tom1", params: { :out => 0, :freq => 200, :amp => 1, :pan => 0.0 },
|
|
6
|
+
body: <<-SCLANG,
|
|
7
|
+
var env, tom;
|
|
8
|
+
env = EnvGen.kr(Env.perc(0.001, 0.1, 1, -5), 1, doneAction:2);
|
|
9
|
+
tom = SinOsc.ar(freq) * env;
|
|
10
|
+
Out.ar(out, Pan2.ar(tom, pan, amp));
|
|
11
|
+
SCLANG
|
|
12
|
+
source: "http://superdupercollider.blogspot.com/2009/02/simple-drum-machine.html"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
FM_TOM = SynthDef.new(name: "fmtom", params: { :out => 0, :freq => 200, :gate => 1, :amp => 1 },
|
|
16
|
+
body: <<-SCLANG,
|
|
17
|
+
var tom = PMOsc.ar(freq, 280, Line.kr(0.0, 12, 1), mul: EnvGen.ar(Env.adsr(0.003,0.2,0,0), gate, levelScale: 0.3, doneAction: 2));
|
|
18
|
+
Out.ar(out, tom * amp ! 2);
|
|
19
|
+
SCLANG
|
|
20
|
+
source: "https://github.com/mattvears/supercollider-stuff",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Musicality
|
|
2
|
+
module SuperCollider
|
|
3
|
+
module SynthDefs
|
|
4
|
+
|
|
5
|
+
VOLUME_CONTROL = SynthDef.new(name: "volume_control", params: { :in => nil, :out => nil, :control => nil },
|
|
6
|
+
body: <<-SCLANG,
|
|
7
|
+
var sig = In.ar([in,in+1]) * In.kr(control);
|
|
8
|
+
Out.ar(out,sig);
|
|
9
|
+
SCLANG
|
|
10
|
+
credit: "James Tunnell",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
VOLUME_CHANGE = SynthDef.new(name: "volume_change", params: { :vol_bus => nil, :vol => nil, :dur => nil },
|
|
14
|
+
body: " Out.kr(vol_bus, Line.kr(In.kr(vol_bus), vol, dur));",
|
|
15
|
+
credit: "James Tunnell",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Musicality
|
|
2
2
|
|
|
3
|
-
module PitchClasses
|
|
3
|
+
module PitchClasses
|
|
4
4
|
C = Bs = 0
|
|
5
5
|
Cs = Db = 1
|
|
6
6
|
D = 2
|
|
@@ -15,8 +15,6 @@ module PitchClasses
|
|
|
15
15
|
B = Cb = 11
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
PITCH_CLASSES = PitchClasses.constants.map
|
|
19
|
-
PitchClasses.const_get(sym)
|
|
20
|
-
end.sort
|
|
18
|
+
PITCH_CLASSES = PitchClasses.constants.map { |sym| PitchClasses.const_get(sym) }.sort
|
|
21
19
|
|
|
22
|
-
end
|
|
20
|
+
end
|
data/lib/musicality/version.rb
CHANGED
data/lib/musicality.rb
CHANGED
|
@@ -38,6 +38,7 @@ require 'musicality/notation/parsing/numbers/nonnegative_float_parsing'
|
|
|
38
38
|
require 'musicality/notation/parsing/numbers/nonnegative_rational_parsing'
|
|
39
39
|
require 'musicality/notation/parsing/pitch_parsing'
|
|
40
40
|
require 'musicality/notation/parsing/pitch_node'
|
|
41
|
+
require 'musicality/notation/parsing/key_parsing'
|
|
41
42
|
require 'musicality/notation/parsing/duration_parsing'
|
|
42
43
|
require 'musicality/notation/parsing/duration_nodes'
|
|
43
44
|
require 'musicality/notation/parsing/articulation_parsing'
|
|
@@ -78,6 +79,18 @@ require 'musicality/composition/util/note_generation'
|
|
|
78
79
|
require 'musicality/composition/model/scale'
|
|
79
80
|
require 'musicality/composition/model/scale_class'
|
|
80
81
|
require 'musicality/composition/model/scale_classes'
|
|
82
|
+
require 'musicality/composition/model/rhythm_class'
|
|
83
|
+
require 'musicality/composition/model/rhythm'
|
|
84
|
+
|
|
85
|
+
require 'musicality/composition/sequencing/sequenceable'
|
|
86
|
+
require 'musicality/composition/sequencing/note_fifo'
|
|
87
|
+
require 'musicality/composition/sequencing/sequencer'
|
|
88
|
+
require 'musicality/composition/sequencing/note_array'
|
|
89
|
+
require 'musicality/composition/sequencing/drum_machine/drum_parts'
|
|
90
|
+
require 'musicality/composition/sequencing/drum_machine/drum_pattern'
|
|
91
|
+
require 'musicality/composition/sequencing/drum_machine/drum_patterns/pop_drum_patterns'
|
|
92
|
+
require 'musicality/composition/sequencing/drum_machine/drum_kit'
|
|
93
|
+
require 'musicality/composition/sequencing/drum_machine/drum_machine'
|
|
81
94
|
|
|
82
95
|
require 'musicality/composition/generation/counterpoint_generator'
|
|
83
96
|
require 'musicality/composition/generation/random_rhythm_generator'
|
|
@@ -121,10 +134,21 @@ require 'musicality/performance/supercollider/node'
|
|
|
121
134
|
require 'musicality/performance/supercollider/synth'
|
|
122
135
|
require 'musicality/performance/supercollider/group'
|
|
123
136
|
require 'musicality/performance/supercollider/synthdef'
|
|
124
|
-
require 'musicality/performance/supercollider/synthdefs'
|
|
137
|
+
require 'musicality/performance/supercollider/synthdefs/bass'
|
|
138
|
+
require 'musicality/performance/supercollider/synthdefs/claps'
|
|
139
|
+
require 'musicality/performance/supercollider/synthdefs/cymbals'
|
|
140
|
+
require 'musicality/performance/supercollider/synthdefs/hihats'
|
|
141
|
+
require 'musicality/performance/supercollider/synthdefs/kicks'
|
|
142
|
+
require 'musicality/performance/supercollider/synthdefs/mario'
|
|
143
|
+
require 'musicality/performance/supercollider/synthdefs/other'
|
|
144
|
+
require 'musicality/performance/supercollider/synthdefs/pianos'
|
|
145
|
+
require 'musicality/performance/supercollider/synthdefs/snares'
|
|
146
|
+
require 'musicality/performance/supercollider/synthdefs/toms'
|
|
147
|
+
require 'musicality/performance/supercollider/synthdefs/volume'
|
|
125
148
|
require 'musicality/performance/supercollider/performer'
|
|
126
149
|
require 'musicality/performance/supercollider/conductor'
|
|
127
150
|
require 'musicality/performance/supercollider/score_conducting'
|
|
151
|
+
require 'musicality/performance/supercollider/sc_drum_kits'
|
|
128
152
|
|
|
129
153
|
#
|
|
130
154
|
# Printing
|
data/musicality.gemspec
CHANGED
|
@@ -22,10 +22,11 @@ SuperCollider."
|
|
|
22
22
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
23
23
|
spec.require_paths = ["lib"]
|
|
24
24
|
|
|
25
|
-
spec.add_development_dependency "bundler", "~>
|
|
25
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
26
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
|
27
|
-
spec.add_development_dependency "rspec", "~>
|
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.5"
|
|
28
28
|
spec.add_development_dependency "pry"
|
|
29
|
+
spec.add_development_dependency "coveralls", '~> 0.8'
|
|
29
30
|
|
|
30
31
|
spec.add_dependency "treetop", "~> 1.5"
|
|
31
32
|
spec.add_dependency 'midilib', '~> 2.0'
|
|
@@ -5,12 +5,12 @@ describe 'transpose' do
|
|
|
5
5
|
notes = "/4A2,C2 /4D2,F2,Gb2 /8 /8E4".to_notes
|
|
6
6
|
semitones = 3
|
|
7
7
|
notes2 = transpose(notes,semitones)
|
|
8
|
-
|
|
9
|
-
notes2.size.
|
|
8
|
+
|
|
9
|
+
expect(notes2.size).to eq(notes.size)
|
|
10
10
|
notes2.each_index do |i|
|
|
11
11
|
notes2[i].pitches.each_with_index do |pitch2,j|
|
|
12
12
|
pitch = notes[i].pitches[j]
|
|
13
|
-
pitch2.diff(pitch).
|
|
13
|
+
expect(pitch2.diff(pitch)).to eq(semitones)
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -30,19 +30,19 @@ end
|
|
|
30
30
|
describe method do
|
|
31
31
|
context 'given no args' do
|
|
32
32
|
it 'should produce an empty array' do
|
|
33
|
-
send(method).
|
|
33
|
+
expect(send(method)).to eq([])
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
context 'given one pitch' do
|
|
38
38
|
it 'should produce an array with one note, with proper duration' do
|
|
39
|
-
send(method,*[C2]).
|
|
39
|
+
expect(send(method,*[C2])).to eq([Note.new(dur,C2)])
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
context 'given one pitch group' do
|
|
44
44
|
it 'should produce an array with one note, given pitch group, and with proper duration' do
|
|
45
|
-
send(method,*[[C2,E2,G2]]).
|
|
45
|
+
expect(send(method,*[[C2,E2,G2]])).to eq([Note.new(dur,[C2,E2,G2])])
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -51,10 +51,10 @@ end
|
|
|
51
51
|
pg1 = A3
|
|
52
52
|
pg2 = [B3,G3]
|
|
53
53
|
pg3 = F4
|
|
54
|
-
send(method,*[pg1,pg2,pg3]).
|
|
54
|
+
expect(send(method,*[pg1,pg2,pg3])).to eq([
|
|
55
55
|
Note.new(dur,pg1), Note.new(dur,pg2), Note.new(dur,pg3)
|
|
56
56
|
])
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
|
-
end
|
|
60
|
+
end
|
|
@@ -13,27 +13,27 @@ describe RandomRhythmGenerator do
|
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
before :all do
|
|
18
18
|
@rrgs = [
|
|
19
19
|
{ 1/8.to_r => 0.25, 1/4.to_r => 0.5, 1/2.to_r => 0.25 },
|
|
20
20
|
{ 1/6.to_r => 0.25, 1/4.to_r => 0.25, 1/3.to_r => 0.25, 1/12.to_r => 0.25 }
|
|
21
21
|
].map {|durs_w_probs| RandomRhythmGenerator.new(durs_w_probs) }
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
describe '#random_rhythm' do
|
|
25
25
|
it 'should return durations that add to given total dur' do
|
|
26
26
|
@rrgs.each do |rrg|
|
|
27
27
|
[3,1,1/2.to_r,5/8.to_r,15/16.to_r].each do |total_dur|
|
|
28
28
|
20.times do
|
|
29
29
|
rhythm = rrg.random_rhythm(total_dur)
|
|
30
|
-
rhythm.inject(0,:+).
|
|
30
|
+
expect(rhythm.inject(0,:+)).to eq(total_dur)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
describe '#random_dur' do
|
|
38
38
|
it 'should return a random duration, according to the probabilities given at initialization' do
|
|
39
39
|
@rrgs.each do |rrg|
|
|
@@ -42,7 +42,7 @@ describe RandomRhythmGenerator do
|
|
|
42
42
|
rrg.durations.each_with_index do |dur,i|
|
|
43
43
|
count = counts[dur]
|
|
44
44
|
tgt_prob = rrg.probabilities[i]
|
|
45
|
-
(count / 1000.to_f).
|
|
45
|
+
expect((count / 1000.to_f)).to be_within(0.05).of(tgt_prob)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -4,48 +4,54 @@ include PitchClasses
|
|
|
4
4
|
|
|
5
5
|
describe PitchClass do
|
|
6
6
|
it 'should define the MOD constant' do
|
|
7
|
-
PitchClass.constants.
|
|
7
|
+
expect(PitchClass.constants).to include(:MOD)
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
describe '.from_i' do
|
|
11
11
|
it 'should return the given integer % PitchClass::MOD' do
|
|
12
|
-
PitchClass.from_i(-1).
|
|
13
|
-
PitchClass.from_i(12).
|
|
14
|
-
PitchClass.from_i(2).
|
|
15
|
-
PitchClass.from_i(16).
|
|
12
|
+
expect(PitchClass.from_i(-1)).to eq(11)
|
|
13
|
+
expect(PitchClass.from_i(12)).to eq(0)
|
|
14
|
+
expect(PitchClass.from_i(2)).to eq(2)
|
|
15
|
+
expect(PitchClass.from_i(16)).to eq(4)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
it 'should add the #to_pc method to the
|
|
20
|
-
5.methods.
|
|
19
|
+
it 'should add the #to_pc method to the Integer class' do
|
|
20
|
+
expect(5.methods).to include(:to_pc)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it 'should add the #to_pc method to the Pitch class' do
|
|
24
|
-
Pitch.new.methods.
|
|
24
|
+
expect(Pitch.new.methods).to include(:to_pc)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
it 'should add the #to_pcs method to Enumerable classes, like Array' do
|
|
28
|
-
[1,2,3].methods.
|
|
28
|
+
expect([1,2,3].methods).to include(:to_pcs)
|
|
29
29
|
end
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
describe 'Pitch#to_pc' do
|
|
32
32
|
it 'should send semitone through PitchClass.from_i' do
|
|
33
33
|
[ C4, D3, E5, G5,
|
|
34
34
|
Pitch.new(semitone: 4),
|
|
35
35
|
Pitch.new(semitone: 13),
|
|
36
36
|
].each do |pitch|
|
|
37
|
-
pitch.to_pc.
|
|
37
|
+
expect(pitch.to_pc).to eq(PitchClass.from_i(pitch.semitone))
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
describe '
|
|
42
|
+
describe 'Integer#to_pc' do
|
|
43
43
|
it 'should pass self to PitchClass.from_i' do
|
|
44
44
|
[-1,12,2,16].each do |i|
|
|
45
|
-
i.to_pc.
|
|
45
|
+
expect(i.to_pc).to eq(PitchClass.from_i(i))
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
describe 'Integer#to_pcs' do
|
|
51
|
+
it 'should pass self to PitchClass.from_i' do
|
|
52
|
+
expect([-1,12,2,16].to_pcs).to eq([-1.to_pc,12.to_pc,2.to_pc,16.to_pc])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
49
55
|
|
|
50
56
|
describe '.invert' do
|
|
51
57
|
before :all do
|
|
@@ -62,13 +68,13 @@ describe PitchClass do
|
|
|
62
68
|
|
|
63
69
|
it 'should produce a pitch class' do
|
|
64
70
|
@cases.each do |input_pc, output_pc|
|
|
65
|
-
PitchClass.invert(input_pc).
|
|
71
|
+
expect(PitchClass.invert(input_pc)).to eq(output_pc)
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
it 'should produce a pitch class that when inverted again produces the original pitch class' do
|
|
70
76
|
@cases.each do |input_pc, output_pc|
|
|
71
|
-
PitchClass.invert(output_pc).
|
|
77
|
+
expect(PitchClass.invert(output_pc)).to eq(input_pc)
|
|
72
78
|
end
|
|
73
79
|
end
|
|
74
80
|
end
|
|
@@ -5,7 +5,7 @@ pc_syms = [:C, :Db, :D, :Eb, :E, :F, :Gb, :G, :Ab, :A, :Bb, :B]
|
|
|
5
5
|
describe PitchClasses do
|
|
6
6
|
it 'should include pitch-class constants for C, Db, D, ...' do
|
|
7
7
|
pc_syms.each do |sym|
|
|
8
|
-
PitchClasses.constants.
|
|
8
|
+
expect(PitchClasses.constants).to include(sym)
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -13,12 +13,12 @@ end
|
|
|
13
13
|
|
|
14
14
|
describe 'PITCH_CLASSES' do
|
|
15
15
|
it 'should be in the Musicality module namespace' do
|
|
16
|
-
Musicality.constants.
|
|
16
|
+
expect(Musicality.constants).to include(:PITCH_CLASSES)
|
|
17
17
|
end
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
it 'should have each constant value in PitchClasses' do
|
|
20
20
|
PitchClasses.constants.each do |sym|
|
|
21
|
-
PITCH_CLASSES.
|
|
21
|
+
expect(PITCH_CLASSES).to include(PitchClasses.const_get(sym))
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
|
-
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe RhythmClass do
|
|
4
|
+
describe '.new' do
|
|
5
|
+
it 'should raise ArgumentError if given portions contains any zero(s)' do
|
|
6
|
+
expect { RhythmClass.new([1,0,2]) }.to raise_error(ArgumentError)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe '#portions' do
|
|
11
|
+
it 'should return the same portions given initially' do
|
|
12
|
+
portions = [4,2,-2,-1]
|
|
13
|
+
rc = RhythmClass.new(portions)
|
|
14
|
+
expect(rc.portions).to eq(portions)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#portions_sum' do
|
|
19
|
+
it 'should return the sum of portions using their absolute value' do
|
|
20
|
+
rc = RhythmClass.new([4,2,-2,-1])
|
|
21
|
+
expect(rc.portions_sum).to eq(9)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#to_rhythm' do
|
|
26
|
+
it 'should produce the expected Rhythm object' do
|
|
27
|
+
portions = [5,1,-7,3,-1,2]
|
|
28
|
+
rhythm_class = RhythmClass.new(portions)
|
|
29
|
+
portions_sum = rhythm_class.portions_sum
|
|
30
|
+
rhythm_duration = 2
|
|
31
|
+
rhythm = rhythm_class.to_rhythm(rhythm_duration)
|
|
32
|
+
|
|
33
|
+
expect(rhythm).to be_a(Rhythm)
|
|
34
|
+
expect(rhythm.durations.size).to eq(portions.size)
|
|
35
|
+
portions.each_with_index do |portion, idx|
|
|
36
|
+
dur = rhythm.durations[idx]
|
|
37
|
+
expect(dur).to eq(rhythm_duration * Rational(portion, portions_sum))
|
|
38
|
+
end
|
|
39
|
+
expect(rhythm.durations_sum).to eq(rhythm_duration)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe Rhythm do
|
|
4
|
+
describe '.new' do
|
|
5
|
+
it 'should raise ArgumentError if given durations contains any zero(s)' do
|
|
6
|
+
expect { Rhythm.new([1,0,2]) }.to raise_error(ArgumentError)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe '#durations' do
|
|
11
|
+
it 'should return the same portions given initially' do
|
|
12
|
+
durations = [4,2,-2,-1]
|
|
13
|
+
rc = Rhythm.new(durations)
|
|
14
|
+
expect(rc.durations).to eq(durations)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#durations_sum' do
|
|
19
|
+
it 'should return the sum of durations using their absolute value' do
|
|
20
|
+
rc = Rhythm.new([4,2,-2,-1])
|
|
21
|
+
expect(rc.durations_sum).to eq(9)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#to_notes' do
|
|
26
|
+
it 'should produce the expected Note objects' do
|
|
27
|
+
durations = [Rational(1,2), Rational(2,1), Rational(2,3)]
|
|
28
|
+
rhythm = Rhythm.new(durations)
|
|
29
|
+
durations_sum = rhythm.durations_sum
|
|
30
|
+
pitch = Pitches::C4
|
|
31
|
+
notes = rhythm.to_notes(pitch)
|
|
32
|
+
|
|
33
|
+
expect(notes.size).to eq(durations.size)
|
|
34
|
+
notes.each do |note|
|
|
35
|
+
expect(note).to be_a(Note)
|
|
36
|
+
end
|
|
37
|
+
durations.each_with_index do |dur, idx|
|
|
38
|
+
dur2 = notes[idx].duration
|
|
39
|
+
expect(dur).to eq(dur2)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|