musicality 0.11.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 04dc7491236652e7000094a669ca7a0b0d06129ad30d5d4080bbbffbb9a41a34
|
4
|
+
data.tar.gz: 68a8829f64fb94c3bc8a011e35e57d891c4677e392b238e5c30f5c1ddbb88cbc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8f17b715734e22ff85594bea1c0d8a6964468a4f4030e2539bea13a3ed8963942b71635dfa552202091f3997833e7d979cbdd2b2b088a33416d1362fc81e93d
|
7
|
+
data.tar.gz: 3280accf4d3f9bb93337b53ae45ef988d9ef54500fd28c124b3da3a988a1c0959f31e6d75c05b120c6936f80620a9fcb0345a3683297f1b527ee304d70644c32
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.5
|
data/.travis.yml
ADDED
data/ChangeLog.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
### 0.12.0 / 2019-11-23
|
2
|
+
* Raise ParseError if parse+convert convenience method (to_pitch, to_meter, etc.) fails
|
3
|
+
* Simplify Key class
|
4
|
+
* Add parsing support for key signatures
|
5
|
+
|
6
|
+
### 0.11.2 / 2016-03-29
|
7
|
+
* Fix a bug in SuperCollider performance code
|
8
|
+
|
9
|
+
### 0.11.1 / 2016-02-18
|
10
|
+
* Fix gem description string in .gemspec
|
11
|
+
|
1
12
|
### 0.11.0 / 2016-02-18
|
2
13
|
* Fix SuperCollider volume control to output stereo
|
3
14
|
* Add :keep_code keyword arg to SuperCollider::Conductor#perform
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/jamestunnell/musicality.svg?branch=master)](https://travis-ci.org/jamestunnell/musicality)
|
2
|
+
[![Coverage Status](https://coveralls.io/repos/github/jamestunnell/musicality/badge.svg?branch=master)](https://coveralls.io/github/jamestunnell/musicality?branch=master)
|
3
|
+
|
1
4
|
# Musicality
|
2
5
|
|
3
6
|
The library is based around an abstract representation for music notation. From here, functions are built up to make composing elaborate pieces in this notation representation more manageable. Finally, music performance is supported by providing translation to common formats, like MIDI.
|
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ def rb_fname fname
|
|
15
15
|
"#{dirname}/#{basename}.rb"
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
def rebuild_parsers force_rebuild
|
19
19
|
wd = Dir.pwd
|
20
20
|
Dir.chdir "lib/musicality/notation/parsing"
|
21
21
|
parser_files = Dir.glob(["**/*.treetop","**/*.tt"])
|
@@ -27,22 +27,30 @@ task :build_parsers do
|
|
27
27
|
|
28
28
|
build_list = parser_files.select do |fname|
|
29
29
|
rb_name = rb_fname(fname)
|
30
|
-
!File.exists?(rb_name) || (File.mtime(fname) > File.mtime(rb_name))
|
30
|
+
force_rebuild || !File.exists?(rb_name) || (File.mtime(fname) > File.mtime(rb_name))
|
31
31
|
end
|
32
32
|
|
33
33
|
if build_list.any?
|
34
34
|
puts "building parsers:"
|
35
35
|
build_list.each do |fname|
|
36
36
|
puts " #{fname} -> #{rb_fname(fname)}"
|
37
|
-
`tt -f #{fname}`
|
37
|
+
`bundle exec tt -f #{fname}`
|
38
38
|
end
|
39
39
|
else
|
40
40
|
puts "Parsers are up-to-date"
|
41
41
|
end
|
42
42
|
Dir.chdir wd
|
43
43
|
end
|
44
|
+
|
45
|
+
task :build_parsers do
|
46
|
+
rebuild_parsers(false)
|
47
|
+
end
|
44
48
|
task :spec => :build_parsers
|
45
49
|
|
50
|
+
task :rebuild_parsers do
|
51
|
+
rebuild_parsers(true)
|
52
|
+
end
|
53
|
+
|
46
54
|
task :make_examples do
|
47
55
|
current_dir = Dir.getwd
|
48
56
|
examples_dir = File.join(File.dirname(__FILE__), 'examples')
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
# A rhythm based on an array of durations.
|
4
|
+
# @note Rests are represented by negative durations.
|
5
|
+
class Rhythm
|
6
|
+
attr_reader :durations, :durations_sum
|
7
|
+
|
8
|
+
def initialize durations
|
9
|
+
if durations.find {|x| x.zero? }
|
10
|
+
raise ArgumentError, "rhythm contains duration(s) that are zero"
|
11
|
+
end
|
12
|
+
@durations = durations.clone.freeze
|
13
|
+
@durations_sum = @durations.inject(0) {|sum,x| sum + x.abs}
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_notes pitch
|
17
|
+
@durations.map do |dur|
|
18
|
+
if dur.negative?
|
19
|
+
Note.new(-dur)
|
20
|
+
else
|
21
|
+
Note.new(dur, pitch)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class Array
|
30
|
+
def to_rhythm
|
31
|
+
Musicality::Rhythm.new(self)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
# A rhythm pattern based on an array of "portions". These portions encode an
|
4
|
+
# array of fractions that sum to 1 (each portion would be numerator and the
|
5
|
+
# sum of all portions would be the denominator). These fractions can be applied
|
6
|
+
# to a total duration to form a rhythm (an array of durations).
|
7
|
+
# @note Rests are represented by neagtive portions.
|
8
|
+
class RhythmClass
|
9
|
+
attr_reader :portions_sum, :portions
|
10
|
+
def initialize portions
|
11
|
+
if portions.find {|x| x.zero? }
|
12
|
+
raise ArgumentError, "rhythm class contains portion(s) that are zero"
|
13
|
+
end
|
14
|
+
@portions = portions.clone.freeze
|
15
|
+
@portions_sum = @portions.inject(0) {|sum,x| sum + x.abs}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Use the rhythm class to generate a rhtyhm
|
19
|
+
def to_rhythm(total_dur)
|
20
|
+
Rhythm.new @portions.map {|x| Rational(x,@portions_sum) * total_dur }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
class Array
|
27
|
+
def to_rhythm_class
|
28
|
+
Musicality::RhythmClass.new(self)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
# A collection of settings for performing drum parts
|
4
|
+
class DrumKit
|
5
|
+
attr_reader :part_settings
|
6
|
+
def initialize part_settings
|
7
|
+
non_drumpart_names = part_settings.select do |part_name|
|
8
|
+
!DRUM_PARTS.include?(part_name)
|
9
|
+
end
|
10
|
+
if non_drumpart_names.any?
|
11
|
+
raise ArgumentError, "Part-names used that are not drum parts: #{non_drumpart_names.inspect}"
|
12
|
+
end
|
13
|
+
|
14
|
+
@part_settings = part_settings.freeze
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class DrumMachine < Sequencer
|
4
|
+
def initialize drum_patterns
|
5
|
+
raise ArgumentError if drum_patterns.empty?
|
6
|
+
|
7
|
+
prev_durations = []
|
8
|
+
existing_part_notes = {}
|
9
|
+
drum_patterns.each do |drum_pattern|
|
10
|
+
durations = drum_pattern.part_notes.values.map do |notes|
|
11
|
+
notes.inject(0) {|sum, note| sum + note.duration }
|
12
|
+
end
|
13
|
+
if durations.uniq.size != 1
|
14
|
+
raise ArgumentError, "Drum pattern has part notes of differing total duration #{drum_pattern}"
|
15
|
+
end
|
16
|
+
duration = durations.first
|
17
|
+
if duration <= 0
|
18
|
+
raise ArgumentError, "Drum pattern has non-positive part notes"
|
19
|
+
end
|
20
|
+
|
21
|
+
drum_pattern.part_notes.each do |part_name, notes|
|
22
|
+
# Create part with rest notes from all the previous patterns durations
|
23
|
+
unless existing_part_notes.has_key?(part_name)
|
24
|
+
existing_part_notes[part_name] = prev_durations.map { |d| Note.new(d) }
|
25
|
+
end
|
26
|
+
|
27
|
+
existing_part_notes[part_name] += notes
|
28
|
+
end
|
29
|
+
|
30
|
+
# For parts that exist previously but not in the current drum pattern, add a rest note
|
31
|
+
existing_part_notes.each do |part_name, notes|
|
32
|
+
unless drum_pattern.part_notes.has_key?(part_name)
|
33
|
+
existing_part_notes[part_name].push Note.new(duration)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
prev_durations.push duration
|
38
|
+
end
|
39
|
+
|
40
|
+
part_sequenceables = Hash[ existing_part_notes.map do |part_name, note_array|
|
41
|
+
[ part_name, NoteArray.new(note_array) ]
|
42
|
+
end]
|
43
|
+
|
44
|
+
super(part_sequenceables)
|
45
|
+
end
|
46
|
+
|
47
|
+
def make_empty_parts drum_kit, part_dynamics = {}
|
48
|
+
Hash[ part_names.map do |part_name|
|
49
|
+
unless drum_kit.part_settings.has_key?(part_name)
|
50
|
+
raise ArgumentError, "Drum kit does not have settings for part: #{part_name}"
|
51
|
+
end
|
52
|
+
part_dynamic = part_dynamics.has_key?(part_name) ? part_dynamics[part_name] : Dynamics::MF
|
53
|
+
part = Part.new(part_dynamic, settings: [drum_kit.part_settings[part_name]])
|
54
|
+
[ part_name, part ]
|
55
|
+
end]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
module DrumParts
|
4
|
+
ACCENT = "AC"
|
5
|
+
CRASH_CYMBAL = "CCY"
|
6
|
+
RIDE_CYMBAL = "RCY"
|
7
|
+
CLOSED_HI_HAT = "CH"
|
8
|
+
OPEN_HI_HAT = "OH"
|
9
|
+
HI_TOM = "HT"
|
10
|
+
MED_TOM = "MT"
|
11
|
+
LOW_TOM = "LT"
|
12
|
+
SNARE_DRUM = "SD"
|
13
|
+
RIM_SHOT = "RS"
|
14
|
+
CLAPS = "CPS"
|
15
|
+
COW_BELL = "CB"
|
16
|
+
BASS_DRUM = "BD"
|
17
|
+
end
|
18
|
+
|
19
|
+
DRUM_PARTS = DrumParts.constants.map { |sym| DrumParts.const_get(sym) }
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class DrumPattern
|
4
|
+
attr_reader :part_notes, :duration
|
5
|
+
|
6
|
+
DRUM_PART_PITCHES = {
|
7
|
+
DrumParts::BASS_DRUM => Pitches::G1,
|
8
|
+
DrumParts::SNARE_DRUM => Pitches::G3,
|
9
|
+
DrumParts::HI_TOM => Pitches::D3,
|
10
|
+
DrumParts::MED_TOM => Pitches::B2,
|
11
|
+
DrumParts::LOW_TOM => Pitches::G2,
|
12
|
+
}
|
13
|
+
DUMMY_PITCH = Pitches::C4
|
14
|
+
|
15
|
+
def initialize duration, part_portions
|
16
|
+
@duration = duration
|
17
|
+
|
18
|
+
non_drumpart_names = part_portions.select do |part_name|
|
19
|
+
!DRUM_PARTS.include?(part_name)
|
20
|
+
end
|
21
|
+
if non_drumpart_names.any?
|
22
|
+
raise ArgumentError, "Part names used that are not drum parts: #{non_drumpart_names.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
accent_offsets = if part_portions.has_key?(DrumParts::ACCENT)
|
26
|
+
DrumPattern.determine_accent_offsets(
|
27
|
+
part_portions.delete(DrumParts::ACCENT).to_rhythm_class.to_rhythm(duration))
|
28
|
+
else
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
|
32
|
+
@part_notes = Hash[ part_portions.map do |part_name, portions|
|
33
|
+
pitch = DRUM_PART_PITCHES[part_name] || DUMMY_PITCH
|
34
|
+
notes = portions.to_rhythm_class.to_rhythm(duration).to_notes(pitch)
|
35
|
+
DrumPattern.apply_accents(notes, accent_offsets)
|
36
|
+
[ part_name, notes ]
|
37
|
+
end ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.determine_accent_offsets accent_rhythm
|
41
|
+
accent_offsets = []
|
42
|
+
cum_dur = 0
|
43
|
+
|
44
|
+
accent_rhythm.durations.each do |dur|
|
45
|
+
if dur > 0
|
46
|
+
accent_offsets.push cum_dur
|
47
|
+
end
|
48
|
+
cum_dur += dur
|
49
|
+
end
|
50
|
+
|
51
|
+
return accent_offsets
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.apply_accents notes, accent_offsets
|
55
|
+
cum_dur = 0
|
56
|
+
|
57
|
+
notes.each do |note|
|
58
|
+
if note.pitches.any? && accent_offsets.include?(cum_dur)
|
59
|
+
note.mark_accented!
|
60
|
+
end
|
61
|
+
cum_dur += note.duration
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
module DrumPatterns
|
4
|
+
POP_1 = DrumPattern.new(1,
|
5
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
6
|
+
DrumParts::CLOSED_HI_HAT => [1,-1] * 8,
|
7
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
8
|
+
DrumParts::BASS_DRUM => [1,1,-1,1,-1,1,-1,1,1,1,-1,1,-1,1,-1,1]
|
9
|
+
)
|
10
|
+
|
11
|
+
POP_2 = DrumPattern.new(1,
|
12
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
13
|
+
DrumParts::CLOSED_HI_HAT => [1,-1] * 8,
|
14
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
15
|
+
DrumParts::BASS_DRUM => [1,-1,1,-2,1,1,1,1,-1,1,-2,1,1,1]
|
16
|
+
)
|
17
|
+
|
18
|
+
POP_3 = DrumPattern.new(1,
|
19
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
20
|
+
DrumParts::CLOSED_HI_HAT => [1,-1,1,-1,1,-2,1,1,-1,1,-1,1,-2,1],
|
21
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
22
|
+
DrumParts::BASS_DRUM => [1,1,-1,1,-1,1,-2,1,1,-1,1,-4]
|
23
|
+
)
|
24
|
+
|
25
|
+
POP_4 = DrumPattern.new(1,
|
26
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
27
|
+
DrumParts::CLOSED_HI_HAT => [1,-1] * 8,
|
28
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
29
|
+
DrumParts::BASS_DRUM => [1,-1,1,1,-1,1,-1,1,-1,1,-1,1,-1,1,-1,1]
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
POP_5 = DrumPattern.new(1,
|
34
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
35
|
+
DrumParts::CLOSED_HI_HAT => [1,-1] * 8,
|
36
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
37
|
+
DrumParts::BASS_DRUM => [1,1,-1,1,-4,1,1,-1,1,-2,1,1]
|
38
|
+
)
|
39
|
+
|
40
|
+
|
41
|
+
POP_6 = DrumPattern.new(1,
|
42
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
43
|
+
DrumParts::CLOSED_HI_HAT => [1,-1] * 8,
|
44
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
45
|
+
DrumParts::BASS_DRUM => [1,1,-1,1,-4,1,1,-1,1,-4]
|
46
|
+
)
|
47
|
+
|
48
|
+
POP_7 = DrumPattern.new(1,
|
49
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
50
|
+
DrumParts::CLOSED_HI_HAT => [1,-1,1,-3,1,-1,1,-1,1,-3,1,-1],
|
51
|
+
DrumParts::OPEN_HI_HAT => [-4,1,-7,1,-3],
|
52
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
53
|
+
DrumParts::BASS_DRUM => [1,-1,1,1,-2,1,-3,1,1,-2,1,-1]
|
54
|
+
)
|
55
|
+
|
56
|
+
POP_8 = DrumPattern.new(1,
|
57
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
58
|
+
DrumParts::CLOSED_HI_HAT => [1,-3,1,-3,1,-3,1,-3],
|
59
|
+
DrumParts::OPEN_HI_HAT => [-2,1,-3,1,-3,1,-3,1,-1],
|
60
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
61
|
+
DrumParts::BASS_DRUM => [1,-1,1,-3,1,-3,1,-3,1,-1]
|
62
|
+
)
|
63
|
+
|
64
|
+
POP_9 = DrumPattern.new(1,
|
65
|
+
DrumParts::ACCENT => [-6,1,-5,1,-3],
|
66
|
+
DrumParts::CLOSED_HI_HAT => [1,-1,1,-1,1,-1,1,-1,1,-3,1,-3],
|
67
|
+
DrumParts::OPEN_HI_HAT => [-10,1,-3,1,-1],
|
68
|
+
DrumParts::SNARE_DRUM => [-6,1,-5,1,-1,1,-1],
|
69
|
+
DrumParts::BASS_DRUM => [1,-1,1,-7,1,-5]
|
70
|
+
)
|
71
|
+
|
72
|
+
POP_10 = DrumPattern.new(1,
|
73
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
74
|
+
DrumParts::CLOSED_HI_HAT => [1]*12 + [-4],
|
75
|
+
DrumParts::OPEN_HI_HAT => [-13,1,-1,1],
|
76
|
+
DrumParts::SNARE_DRUM => [-4,1,-2,1,-4,1,-3],
|
77
|
+
DrumParts::BASS_DRUM => [1,-1,1,-5,1,1,-1,1,-1,1,-1,1]
|
78
|
+
)
|
79
|
+
|
80
|
+
POP_11 = DrumPattern.new(1,
|
81
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
82
|
+
DrumParts::CLOSED_HI_HAT => [-1,1,1,1]*4,
|
83
|
+
DrumParts::SNARE_DRUM => [1,-3,1,-3,1,-3,1,-3],
|
84
|
+
DrumParts::BASS_DRUM => [-2,1,-2,1,-1,1,-2,1,-2,1,-1,1]
|
85
|
+
)
|
86
|
+
|
87
|
+
POP_12 = DrumPattern.new(1,
|
88
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
89
|
+
DrumParts::CLOSED_HI_HAT => ([1]*4 + [-1,1,-1,1])*2,
|
90
|
+
DrumParts::OPEN_HI_HAT => [-6,1,-7,1,-1],
|
91
|
+
DrumParts::SNARE_DRUM => [-4,1,-7,1,-3],
|
92
|
+
DrumParts::BASS_DRUM => [1,-1,1,-4,1,1,-1,1,-4,1]
|
93
|
+
)
|
94
|
+
|
95
|
+
POP_BREAK_1 = DrumPattern.new(1,
|
96
|
+
DrumParts::HI_TOM => [-8,1,1,-1,1,-4],
|
97
|
+
DrumParts::MED_TOM => [-4,1,-2,1,-8],
|
98
|
+
DrumParts::LOW_TOM => [-12,1,1,1,-1],
|
99
|
+
DrumParts::SNARE_DRUM => [1,1,1,1,-12],
|
100
|
+
DrumParts::BASS_DRUM => [1,1,-6,1,-7],
|
101
|
+
)
|
102
|
+
|
103
|
+
POP_BREAK_2 = DrumPattern.new(1,
|
104
|
+
DrumParts::ACCENT => [1,-2] * 5 + [-1],
|
105
|
+
DrumParts::HI_TOM => [-6,1,-9],
|
106
|
+
DrumParts::MED_TOM => [-3,1,-12],
|
107
|
+
DrumParts::LOW_TOM => [-9,1,-6],
|
108
|
+
DrumParts::SNARE_DRUM => [1,-11,1,-3],
|
109
|
+
DrumParts::BASS_DRUM => [-1,1,1] * 5 + [1],
|
110
|
+
)
|
111
|
+
|
112
|
+
POP_BREAK_3 = DrumPattern.new(1,
|
113
|
+
DrumParts::CRASH_CYMBAL => [1,-15],
|
114
|
+
DrumParts::HI_TOM => [-13,1,1,1],
|
115
|
+
DrumParts::MED_TOM => [-8,1,1,-1,1,-4],
|
116
|
+
DrumParts::SNARE_DRUM => [-2,1,-3,1,-9],
|
117
|
+
DrumParts::BASS_DRUM => [1,-7,1,-7],
|
118
|
+
)
|
119
|
+
|
120
|
+
POP_BREAK_4 = DrumPattern.new(1,
|
121
|
+
DrumParts::ACCENT => [-4,1,-7,1,-3],
|
122
|
+
DrumParts::SNARE_DRUM => [-4,1,-4,1,1,-1,1,-3],
|
123
|
+
DrumParts::BASS_DRUM => [1,-1,1,-2,1,-1,1,-3,1,-4],
|
124
|
+
)
|
125
|
+
|
126
|
+
POP_BREAK_5 = DrumPattern.new(1,
|
127
|
+
DrumParts::CRASH_CYMBAL => [-12,1,1,-2],
|
128
|
+
DrumParts::MED_TOM => [-3,1,-12],
|
129
|
+
DrumParts::LOW_TOM => [-6,1,-9],
|
130
|
+
DrumParts::SNARE_DRUM => [1,-8,1,-6],
|
131
|
+
DrumParts::BASS_DRUM => [-12,1,1,-2],
|
132
|
+
)
|
133
|
+
|
134
|
+
POP_BREAK_6 = DrumPattern.new(1,
|
135
|
+
DrumParts::ACCENT => [1,-2,1,1,-2,1,-6,1,-1],
|
136
|
+
DrumParts::CRASH_CYMBAL => [1,-15],
|
137
|
+
DrumParts::CLOSED_HI_HAT => [-7,1,-8],
|
138
|
+
DrumParts::OPEN_HI_HAT => [-13,1,-1],
|
139
|
+
DrumParts::MED_TOM => [-4,1,1,1,-9],
|
140
|
+
DrumParts::LOW_TOM => [-11,1,1,1,-1],
|
141
|
+
DrumParts::SNARE_DRUM => [-1,1,1,1,-5,1,1,-5],
|
142
|
+
DrumParts::BASS_DRUM => [1,-6,1,-8],
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class NoteArray
|
4
|
+
include Sequenceable
|
5
|
+
|
6
|
+
attr_reader :duration, :notes
|
7
|
+
def initialize notes
|
8
|
+
raise ArgumentError if notes.empty?
|
9
|
+
|
10
|
+
@notes = notes.clone.freeze
|
11
|
+
@notes_idx = 0
|
12
|
+
@notes_count = notes.size
|
13
|
+
|
14
|
+
@duration = @notes.inject(0) {|sum, note| sum + note.duration}
|
15
|
+
end
|
16
|
+
|
17
|
+
def next_note
|
18
|
+
note = @notes[@notes_idx]
|
19
|
+
|
20
|
+
@notes_idx += 1
|
21
|
+
if @notes_idx >= @notes_count
|
22
|
+
@notes_idx = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
return note
|
26
|
+
end
|
27
|
+
|
28
|
+
def reset
|
29
|
+
@notes_idx = 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class NoteFIFO
|
4
|
+
attr_reader :notes, :duration
|
5
|
+
def initialize initial_notes = []
|
6
|
+
@notes = []
|
7
|
+
@duration = 0
|
8
|
+
add_notes(initial_notes) if initial_notes.any?
|
9
|
+
end
|
10
|
+
|
11
|
+
def empty?
|
12
|
+
@notes.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_note note
|
16
|
+
if note.duration <= 0
|
17
|
+
raise ArgumentError, "note have non-positive duration: #{note}"
|
18
|
+
end
|
19
|
+
@notes.push note
|
20
|
+
@duration += note.duration
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_notes notes
|
24
|
+
nonpositive = notes.select {|x| x.duration <= 0}
|
25
|
+
if nonpositive.any?
|
26
|
+
raise ArgumentError, "one or more notes have non-positive duration: #{notes}"
|
27
|
+
end
|
28
|
+
@notes += notes
|
29
|
+
@duration += notes.inject(0) {|sum, note| sum + note.duration }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return a sequence of notes with total duration equal to the given target duration, and remove
|
33
|
+
# the same notes from the accumulator. Any notes beyond the given target duration are left in
|
34
|
+
# the accumulator. Split a note into two tied notes if needed.
|
35
|
+
def remove_notes target_duration
|
36
|
+
raise ArgumentError, "negative target duration #{target_duration}" if target_duration < 0
|
37
|
+
|
38
|
+
if target_duration > duration
|
39
|
+
raise ArgumentError, "target duration #{target_duration} is greater than duration of accumulated notes #{duration}"
|
40
|
+
end
|
41
|
+
|
42
|
+
removed_notes = if target_duration == 0
|
43
|
+
[]
|
44
|
+
elsif target_duration == duration
|
45
|
+
notes.shift(notes.size)
|
46
|
+
else
|
47
|
+
dur_so_far = 0.to_r
|
48
|
+
num_notes_taking = 0
|
49
|
+
@notes.each_with_index do |note, idx|
|
50
|
+
dur_so_far += note.duration
|
51
|
+
num_notes_taking += 1
|
52
|
+
break if dur_so_far >= target_duration
|
53
|
+
end
|
54
|
+
|
55
|
+
notes_taking = notes.shift(num_notes_taking)
|
56
|
+
excess_dur = dur_so_far - target_duration
|
57
|
+
|
58
|
+
if excess_dur > 0
|
59
|
+
@notes.unshift(notes_taking[-1].resize(excess_dur))
|
60
|
+
notes_taking[-1] = notes_taking[-1].resize(notes_taking[-1].duration - excess_dur)
|
61
|
+
notes_taking[-1].pitches.each do |pitch|
|
62
|
+
notes_taking[-1].links[pitch] = Link::Tie.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
notes_taking
|
66
|
+
end
|
67
|
+
|
68
|
+
@duration = @duration - target_duration
|
69
|
+
return removed_notes
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class Sequencer
|
4
|
+
attr_reader :part_names, :part_sequenceables
|
5
|
+
|
6
|
+
def initialize part_sequenceables
|
7
|
+
@part_sequenceables = part_sequenceables.freeze
|
8
|
+
@part_note_fifos = Hash[ part_sequenceables.keys.map {|partname| [ partname, NoteFIFO.new ] } ]
|
9
|
+
@part_names = @part_sequenceables.keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def next_part_notes target_duration
|
13
|
+
if target_duration <= 0
|
14
|
+
raise ArgumentError, "Target duration #{target_duration} is non-positive}"
|
15
|
+
end
|
16
|
+
part_notes = {}
|
17
|
+
|
18
|
+
@part_sequenceables.each do |partname, sequenceable|
|
19
|
+
note_fifo = @part_note_fifos[partname]
|
20
|
+
|
21
|
+
while note_fifo.duration < target_duration
|
22
|
+
note_fifo.add_note(sequenceable.next_note)
|
23
|
+
end
|
24
|
+
part_notes[partname] = note_fifo.remove_notes(target_duration)
|
25
|
+
end
|
26
|
+
|
27
|
+
return part_notes
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset
|
31
|
+
@part_sequenceables.values.each { |s| s.reset }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/musicality/errors.rb
CHANGED