musicality 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +4 -0
  5. data/ChangeLog.md +11 -0
  6. data/README.md +3 -0
  7. data/Rakefile +11 -3
  8. data/lib/musicality/composition/model/rhythm.rb +33 -0
  9. data/lib/musicality/composition/model/rhythm_class.rb +30 -0
  10. data/lib/musicality/composition/sequencing/drum_machine/drum_kit.rb +18 -0
  11. data/lib/musicality/composition/sequencing/drum_machine/drum_machine.rb +59 -0
  12. data/lib/musicality/composition/sequencing/drum_machine/drum_parts.rb +21 -0
  13. data/lib/musicality/composition/sequencing/drum_machine/drum_pattern.rb +66 -0
  14. data/lib/musicality/composition/sequencing/drum_machine/drum_patterns/pop_drum_patterns.rb +146 -0
  15. data/lib/musicality/composition/sequencing/note_array.rb +33 -0
  16. data/lib/musicality/composition/sequencing/note_fifo.rb +73 -0
  17. data/lib/musicality/composition/sequencing/sequenceable.rb +9 -0
  18. data/lib/musicality/composition/sequencing/sequencer.rb +35 -0
  19. data/lib/musicality/errors.rb +2 -2
  20. data/lib/musicality/notation/model/dynamics.rb +2 -2
  21. data/lib/musicality/notation/model/key.rb +42 -91
  22. data/lib/musicality/notation/model/keys.rb +35 -34
  23. data/lib/musicality/notation/model/note.rb +31 -9
  24. data/lib/musicality/notation/model/pitch.rb +2 -2
  25. data/lib/musicality/notation/parsing/convenience_methods.rb +23 -12
  26. data/lib/musicality/notation/parsing/duration_parsing.rb +3 -3
  27. data/lib/musicality/notation/parsing/key_parsing.rb +150 -0
  28. data/lib/musicality/notation/parsing/key_parsing.treetop +37 -0
  29. data/lib/musicality/notation/parsing/meter_parsing.rb +3 -3
  30. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +3 -1
  31. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +1 -0
  32. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +1 -1
  33. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +4 -1
  34. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +1 -1
  35. data/lib/musicality/notation/parsing/parseable.rb +13 -17
  36. data/lib/musicality/notation/parsing/pitch_parsing.rb +7 -0
  37. data/lib/musicality/notation/parsing/segment_parsing.rb +3 -0
  38. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +82 -134
  39. data/lib/musicality/performance/model/note_sequence.rb +22 -3
  40. data/lib/musicality/performance/supercollider/performer.rb +2 -2
  41. data/lib/musicality/performance/supercollider/sc_drum_kits.rb +29 -0
  42. data/lib/musicality/performance/supercollider/synthdefs/bass.rb +211 -0
  43. data/lib/musicality/performance/supercollider/synthdefs/claps.rb +80 -0
  44. data/lib/musicality/performance/supercollider/synthdefs/cymbals.rb +57 -0
  45. data/lib/musicality/performance/supercollider/synthdefs/hihats.rb +67 -0
  46. data/lib/musicality/performance/supercollider/synthdefs/kicks.rb +158 -0
  47. data/lib/musicality/performance/supercollider/synthdefs/mario.rb +49 -0
  48. data/lib/musicality/performance/supercollider/{synthdefs.rb → synthdefs/other.rb} +0 -767
  49. data/lib/musicality/performance/supercollider/synthdefs/pianos.rb +46 -0
  50. data/lib/musicality/performance/supercollider/synthdefs/snares.rb +169 -0
  51. data/lib/musicality/performance/supercollider/synthdefs/toms.rb +25 -0
  52. data/lib/musicality/performance/supercollider/synthdefs/volume.rb +20 -0
  53. data/lib/musicality/pitch_class.rb +1 -1
  54. data/lib/musicality/pitch_classes.rb +3 -5
  55. data/lib/musicality/version.rb +1 -1
  56. data/lib/musicality.rb +25 -1
  57. data/musicality.gemspec +3 -2
  58. data/spec/composition/convenience_methods_spec.rb +8 -8
  59. data/spec/composition/generation/random_rhythm_generator_spec.rb +5 -5
  60. data/spec/composition/model/pitch_class_spec.rb +22 -16
  61. data/spec/composition/model/pitch_classes_spec.rb +5 -5
  62. data/spec/composition/model/rhythm_class_spec.rb +42 -0
  63. data/spec/composition/model/rhythm_spec.rb +43 -0
  64. data/spec/composition/model/scale_class_spec.rb +26 -26
  65. data/spec/composition/model/scale_spec.rb +38 -38
  66. data/spec/composition/sequencing/drum_machine/drum_machine_spec.rb +67 -0
  67. data/spec/composition/sequencing/drum_machine/drum_pattern_spec.rb +58 -0
  68. data/spec/composition/sequencing/note_array_spec.rb +94 -0
  69. data/spec/composition/sequencing/note_fifo_spec.rb +183 -0
  70. data/spec/composition/sequencing/sequencer_spec.rb +76 -0
  71. data/spec/composition/util/adding_sequence_spec.rb +33 -33
  72. data/spec/composition/util/compound_sequence_spec.rb +6 -6
  73. data/spec/composition/util/note_generation_spec.rb +34 -34
  74. data/spec/composition/util/probabilities_spec.rb +7 -7
  75. data/spec/composition/util/random_sampler_spec.rb +3 -3
  76. data/spec/composition/util/repeating_sequence_spec.rb +28 -28
  77. data/spec/musicality_spec.rb +1 -1
  78. data/spec/notation/conversion/change_conversion_spec.rb +87 -87
  79. data/spec/notation/conversion/note_time_converter_spec.rb +22 -22
  80. data/spec/notation/conversion/score_conversion_spec.rb +1 -1
  81. data/spec/notation/conversion/score_converter_spec.rb +31 -31
  82. data/spec/notation/conversion/tempo_conversion_spec.rb +11 -11
  83. data/spec/notation/model/change_spec.rb +80 -80
  84. data/spec/notation/model/key_spec.rb +135 -69
  85. data/spec/notation/model/link_spec.rb +27 -27
  86. data/spec/notation/model/meter_spec.rb +28 -28
  87. data/spec/notation/model/note_spec.rb +68 -47
  88. data/spec/notation/model/part_spec.rb +19 -19
  89. data/spec/notation/model/pitch_spec.rb +69 -68
  90. data/spec/notation/model/score_spec.rb +50 -47
  91. data/spec/notation/parsing/articulation_parsing_spec.rb +4 -4
  92. data/spec/notation/parsing/convenience_methods_spec.rb +49 -10
  93. data/spec/notation/parsing/duration_nodes_spec.rb +13 -13
  94. data/spec/notation/parsing/duration_parsing_spec.rb +10 -10
  95. data/spec/notation/parsing/key_parsing_spec.rb +19 -0
  96. data/spec/notation/parsing/link_nodes_spec.rb +7 -7
  97. data/spec/notation/parsing/link_parsing_spec.rb +4 -4
  98. data/spec/notation/parsing/meter_parsing_spec.rb +5 -5
  99. data/spec/notation/parsing/note_node_spec.rb +19 -19
  100. data/spec/notation/parsing/note_parsing_spec.rb +4 -4
  101. data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +8 -8
  102. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +2 -2
  103. data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +1 -1
  104. data/spec/notation/parsing/numbers/positive_float_spec.rb +8 -8
  105. data/spec/notation/parsing/numbers/positive_integer_spec.rb +6 -6
  106. data/spec/notation/parsing/numbers/positive_rational_spec.rb +6 -6
  107. data/spec/notation/parsing/pitch_node_spec.rb +7 -7
  108. data/spec/notation/parsing/pitch_parsing_spec.rb +2 -2
  109. data/spec/notation/parsing/segment_parsing_spec.rb +3 -3
  110. data/spec/notation/util/function_spec.rb +15 -15
  111. data/spec/notation/util/transition_spec.rb +12 -12
  112. data/spec/notation/util/value_computer_spec.rb +35 -36
  113. data/spec/performance/conversion/glissando_converter_spec.rb +24 -24
  114. data/spec/performance/conversion/note_sequence_extractor_spec.rb +39 -39
  115. data/spec/performance/conversion/portamento_converter_spec.rb +23 -23
  116. data/spec/performance/midi/midi_util_spec.rb +41 -41
  117. data/spec/performance/midi/part_sequencer_spec.rb +10 -10
  118. data/spec/performance/midi/score_sequencer_spec.rb +15 -15
  119. data/spec/performance/midi/score_sequencing_spec.rb +2 -2
  120. data/spec/performance/util/optimization_spec.rb +9 -9
  121. data/spec/printing/note_engraving_spec.rb +16 -16
  122. data/spec/printing/score_engraver_spec.rb +5 -5
  123. data/spec/spec_helper.rb +5 -0
  124. metadata +85 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 33617569378085114609e5d277d898ca57e95a98
4
- data.tar.gz: a1536b83bd10b7f601e42307f3fc59986e6b1f73
2
+ SHA256:
3
+ metadata.gz: 04dc7491236652e7000094a669ca7a0b0d06129ad30d5d4080bbbffbb9a41a34
4
+ data.tar.gz: 68a8829f64fb94c3bc8a011e35e57d891c4677e392b238e5c30f5c1ddbb88cbc
5
5
  SHA512:
6
- metadata.gz: 669f861d645c18becf4d7a0bf794f7a21862730b76cfb71c57cbc25772a7d72258f16f07b0867a174dd28d1cd9e715fe1b3bf8a6ba9802665ec4099ad40b28cd
7
- data.tar.gz: f2ce677c9f05fb3deb00cbc2cac5d8286dc2a5856931c61ff6770e91df3432659ec0ddeb7ddcb6ac853494e91f8c5e9031f3ec31baf6fa7f3f0b9eacdf73c783
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.0.0
1
+ 2.6.5
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.4
4
+ - jruby
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
- task :build_parsers do
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,9 @@
1
+ module Musicality
2
+
3
+ # Requires instance methods:
4
+ # * #next_note Produces note with each call
5
+ # * #reset Reset back to the beginning of the note sequence
6
+ module Sequenceable
7
+ end
8
+
9
+ 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
@@ -10,5 +10,5 @@ module Musicality
10
10
  class DomainError < StandardError; end
11
11
  class EmptyError < StandardError; end
12
12
  class DurationMismatchError < StandardError; end
13
-
14
- end
13
+ class ParseError < StandardError; end
14
+ end
@@ -1,7 +1,7 @@
1
1
  module Musicality
2
2
  module Dynamics
3
- PPP = 0.05
4
- FFF = 0.5
3
+ PPP = 0.1
4
+ FFF = 0.9
5
5
  DYNAMIC_RATIO = (FFF/PPP)**(1.0/7.0) # 7 ratios between the 8 dynamic levels
6
6
 
7
7
  PP = PPP*DYNAMIC_RATIO