musicality 0.8.0 → 0.9.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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +27 -1
  3. data/README.md +153 -10
  4. data/bin/collidify +102 -0
  5. data/bin/lilify +57 -29
  6. data/bin/midify +64 -24
  7. data/bin/musicality +39 -0
  8. data/examples/composition/auto_counterpoint.rb +4 -5
  9. data/examples/composition/part_generator.rb +8 -2
  10. data/examples/composition/scale_exercise.rb +1 -1
  11. data/examples/notation/notes.rb +27 -0
  12. data/examples/notation/parts.rb +51 -0
  13. data/examples/notation/scores.rb +38 -0
  14. data/examples/notation/twinkle.rb +34 -0
  15. data/examples/notation/twinkle.score +33 -0
  16. data/lib/musicality.rb +46 -11
  17. data/lib/musicality/composition/dsl/score_dsl.rb +2 -2
  18. data/lib/musicality/composition/dsl/score_methods.rb +10 -7
  19. data/lib/musicality/notation/conversion/change_conversion.rb +1 -1
  20. data/lib/musicality/notation/conversion/note_time_converter.rb +6 -23
  21. data/lib/musicality/notation/conversion/score_conversion.rb +15 -15
  22. data/lib/musicality/notation/conversion/score_converter.rb +50 -67
  23. data/lib/musicality/notation/model/articulations.rb +3 -2
  24. data/lib/musicality/notation/model/change.rb +15 -6
  25. data/lib/musicality/notation/model/dynamics.rb +11 -8
  26. data/lib/musicality/notation/model/instrument.rb +61 -0
  27. data/lib/musicality/notation/model/instruments.rb +111 -0
  28. data/lib/musicality/notation/model/key.rb +137 -0
  29. data/lib/musicality/notation/model/keys.rb +37 -0
  30. data/lib/musicality/notation/model/link.rb +6 -19
  31. data/lib/musicality/notation/model/mark.rb +43 -0
  32. data/lib/musicality/notation/model/marks.rb +11 -0
  33. data/lib/musicality/notation/model/meter.rb +4 -0
  34. data/lib/musicality/notation/model/note.rb +42 -28
  35. data/lib/musicality/notation/model/part.rb +18 -5
  36. data/lib/musicality/notation/model/pitch.rb +13 -4
  37. data/lib/musicality/notation/model/score.rb +104 -66
  38. data/lib/musicality/notation/model/symbols.rb +16 -11
  39. data/lib/musicality/notation/parsing/articulation_parsing.rb +38 -38
  40. data/lib/musicality/notation/parsing/articulation_parsing.treetop +14 -14
  41. data/lib/musicality/notation/parsing/link_parsing.rb +6 -6
  42. data/lib/musicality/notation/parsing/link_parsing.treetop +3 -3
  43. data/lib/musicality/notation/parsing/mark_parsing.rb +138 -0
  44. data/lib/musicality/notation/parsing/mark_parsing.treetop +31 -0
  45. data/lib/musicality/notation/parsing/note_node.rb +19 -12
  46. data/lib/musicality/notation/parsing/note_parsing.rb +218 -87
  47. data/lib/musicality/notation/parsing/note_parsing.treetop +9 -5
  48. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +7 -2
  49. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +1 -1
  50. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +6 -4
  51. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +1 -1
  52. data/lib/musicality/notation/util/function.rb +41 -18
  53. data/lib/musicality/packable.rb +156 -0
  54. data/lib/musicality/performance/conversion/glissando_converter.rb +2 -2
  55. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +223 -70
  56. data/lib/musicality/performance/conversion/portamento_converter.rb +5 -2
  57. data/lib/musicality/performance/conversion/score_collator.rb +70 -64
  58. data/lib/musicality/performance/midi/midi_events.rb +3 -3
  59. data/lib/musicality/performance/midi/midi_settings.rb +127 -0
  60. data/lib/musicality/performance/midi/midi_util.rb +8 -2
  61. data/lib/musicality/performance/midi/part_sequencer.rb +19 -18
  62. data/lib/musicality/performance/midi/score_sequencer.rb +13 -9
  63. data/lib/musicality/performance/midi/score_sequencing.rb +5 -5
  64. data/lib/musicality/performance/model/attack.rb +8 -0
  65. data/lib/musicality/performance/model/duration_functions.rb +23 -0
  66. data/lib/musicality/performance/model/note_sequence.rb +52 -95
  67. data/lib/musicality/performance/model/separation.rb +10 -0
  68. data/lib/musicality/performance/supercollider/add_actions.rb +13 -0
  69. data/lib/musicality/performance/supercollider/bundle.rb +18 -0
  70. data/lib/musicality/performance/supercollider/conductor.rb +125 -0
  71. data/lib/musicality/performance/supercollider/group.rb +71 -0
  72. data/lib/musicality/performance/supercollider/message.rb +26 -0
  73. data/lib/musicality/performance/supercollider/node.rb +122 -0
  74. data/lib/musicality/performance/supercollider/performer.rb +123 -0
  75. data/lib/musicality/performance/supercollider/score_conducting.rb +17 -0
  76. data/lib/musicality/performance/supercollider/server.rb +8 -0
  77. data/lib/musicality/performance/supercollider/synth.rb +43 -0
  78. data/lib/musicality/performance/supercollider/synthdef.rb +57 -0
  79. data/lib/musicality/performance/supercollider/synthdef_settings.rb +23 -0
  80. data/lib/musicality/performance/supercollider/synthdefs.rb +1654 -0
  81. data/lib/musicality/{composition/model/pitch_class.rb → pitch_class.rb} +1 -1
  82. data/lib/musicality/{composition/model/pitch_classes.rb → pitch_classes.rb} +9 -9
  83. data/lib/musicality/printing/lilypond/clef.rb +12 -0
  84. data/lib/musicality/printing/lilypond/key_engraving.rb +9 -0
  85. data/lib/musicality/printing/lilypond/lilypond_settings.rb +105 -0
  86. data/lib/musicality/printing/lilypond/meter_engraving.rb +1 -1
  87. data/lib/musicality/printing/lilypond/note_engraving.rb +112 -30
  88. data/lib/musicality/printing/lilypond/part_engraver.rb +114 -3
  89. data/lib/musicality/printing/lilypond/pitch_class_engraving.rb +22 -0
  90. data/lib/musicality/printing/lilypond/pitch_engraving.rb +2 -15
  91. data/lib/musicality/printing/lilypond/score_engraver.rb +44 -73
  92. data/lib/musicality/printing/lilypond/score_engraving.rb +3 -3
  93. data/lib/musicality/project/create_tasks.rb +31 -0
  94. data/lib/musicality/project/file_cleaner.rb +19 -0
  95. data/lib/musicality/project/file_raker.rb +107 -0
  96. data/lib/musicality/project/load_config.rb +43 -0
  97. data/lib/musicality/project/project.rb +64 -0
  98. data/lib/musicality/version.rb +1 -1
  99. data/musicality.gemspec +3 -0
  100. data/spec/composition/util/random_sampler_spec.rb +1 -1
  101. data/spec/notation/conversion/measure_note_map_spec.rb +1 -1
  102. data/spec/notation/conversion/note_time_converter_spec.rb +5 -85
  103. data/spec/notation/conversion/score_conversion_spec.rb +6 -41
  104. data/spec/notation/conversion/score_converter_spec.rb +19 -137
  105. data/spec/notation/model/change_spec.rb +55 -0
  106. data/spec/notation/model/key_spec.rb +171 -0
  107. data/spec/notation/model/link_spec.rb +34 -5
  108. data/spec/notation/model/meter_spec.rb +15 -0
  109. data/spec/notation/model/note_spec.rb +33 -27
  110. data/spec/notation/model/part_spec.rb +53 -4
  111. data/spec/notation/model/pitch_spec.rb +15 -0
  112. data/spec/notation/model/score_spec.rb +64 -72
  113. data/spec/notation/parsing/link_nodes_spec.rb +3 -3
  114. data/spec/notation/parsing/link_parsing_spec.rb +6 -6
  115. data/spec/notation/parsing/note_node_spec.rb +34 -9
  116. data/spec/notation/parsing/note_parsing_spec.rb +11 -9
  117. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +4 -0
  118. data/spec/notation/parsing/pitch_node_spec.rb +0 -1
  119. data/spec/notation/util/value_computer_spec.rb +2 -2
  120. data/spec/performance/conversion/glissando_converter_spec.rb +9 -9
  121. data/spec/performance/conversion/note_sequence_extractor_spec.rb +48 -53
  122. data/spec/performance/conversion/portamento_converter_spec.rb +11 -9
  123. data/spec/performance/conversion/score_collator_spec.rb +59 -63
  124. data/spec/performance/midi/midi_util_spec.rb +22 -8
  125. data/spec/performance/midi/part_sequencer_spec.rb +2 -2
  126. data/spec/performance/midi/score_sequencer_spec.rb +12 -10
  127. data/spec/performance/midi/score_sequencing_spec.rb +2 -2
  128. data/spec/performance/model/note_sequence_spec.rb +41 -134
  129. data/spec/printing/note_engraving_spec.rb +204 -0
  130. data/spec/printing/score_engraver_spec.rb +40 -0
  131. data/spec/spec_helper.rb +1 -0
  132. metadata +69 -23
  133. data/examples/notation/hip.rb +0 -32
  134. data/examples/notation/missed_connection.rb +0 -26
  135. data/examples/notation/song1.rb +0 -33
  136. data/examples/notation/song2.rb +0 -32
  137. data/lib/musicality/notation/model/links.rb +0 -11
  138. data/lib/musicality/notation/packing/change_packing.rb +0 -56
  139. data/lib/musicality/notation/packing/part_packing.rb +0 -31
  140. data/lib/musicality/notation/packing/score_packing.rb +0 -123
  141. data/lib/musicality/performance/model/note_attacks.rb +0 -19
  142. data/lib/musicality/performance/util/note_linker.rb +0 -28
  143. data/spec/notation/packing/change_packing_spec.rb +0 -304
  144. data/spec/notation/packing/part_packing_spec.rb +0 -66
  145. data/spec/notation/packing/score_packing_spec.rb +0 -255
  146. data/spec/performance/util/note_linker_spec.rb +0 -68
@@ -12,11 +12,14 @@ class PortamentoConverter
12
12
  end
13
13
  end
14
14
 
15
- def self.portamento_elements(start_pitch, target_pitch, cents_per_step, duration, accented)
15
+ def self.portamento_elements(start_pitch, target_pitch, cents_per_step, duration, attack)
16
16
  pitches = portamento_pitches(start_pitch, target_pitch, cents_per_step)
17
17
  subdur = Rational(duration, pitches.size)
18
+ first = true
18
19
  pitches.map do |pitch|
19
- SlurredElement.new(subdur, pitch, accented)
20
+ el = NoteSequence::Element.new(subdur, pitch, first ? attack : Attack::NONE)
21
+ first = false
22
+ el
20
23
  end
21
24
  end
22
25
  end
@@ -10,88 +10,96 @@ class ScoreCollator
10
10
  raise ScoreNotValidError, "errors found in score: #{score.errors}"
11
11
  end
12
12
  @score = score
13
+ @program = score.program.any? ? score.program : [0...score.duration]
13
14
  end
14
15
 
15
16
  def collate_parts
16
- segments = @score.program
17
-
18
17
  Hash[
19
18
  @score.parts.map do |name, part|
20
- new_dcs = collate_changes(part.start_dynamic,
21
- part.dynamic_changes, segments)
22
- new_notes = collate_notes(part.notes, segments)
23
- new_part = Part.new(part.start_dynamic,
24
- dynamic_changes: new_dcs, notes: new_notes)
25
- [ name, new_part ]
19
+ dyn_comp = ValueComputer.new(part.start_dynamic,part.dynamic_changes)
20
+
21
+ new_part = part.clone
22
+ new_part.notes = collate_notes(part.notes, @program)
23
+ new_part.start_dynamic, new_part.dynamic_changes = collate_changes(
24
+ part.start_dynamic, part.dynamic_changes, @program
25
+ )
26
+ [ name, new_part ]
26
27
  end
27
28
  ]
28
29
  end
29
30
 
30
31
  def collate_tempo_changes
31
- collate_changes(@score.start_tempo,
32
- @score.tempo_changes, @score.program)
32
+ collate_changes(@score.start_tempo, @score.tempo_changes, @program)
33
33
  end
34
34
 
35
35
  def collate_meter_changes
36
- collate_changes(@score.start_meter,
37
- @score.meter_changes, @score.program)
36
+ collate_changes(@score.start_meter, @score.meter_changes, @program)
38
37
  end
39
38
 
39
+ def collate_key_changes
40
+ collate_changes(@score.start_key, @score.key_changes, @program)
41
+ end
42
+
40
43
  private
41
44
 
42
45
  def collate_changes start_value, changes, program_segments
43
46
  new_changes = {}
44
- comp = ValueComputer.new(start_value,changes)
47
+ comp = ValueComputer.new(start_value, changes)
45
48
  segment_start_offset = 0.to_r
46
49
 
47
- program_segments.each do |seg|
50
+ new_start_val = comp.at(program_segments.first.first)
51
+
52
+ program_segments.each_with_index do |seg, i|
48
53
  seg = seg.first...seg.last
49
54
 
50
- # add segment start value
55
+ # add segment start value, but only if it's different than the value at
56
+ # the end of the prev segment
51
57
  value = comp.at seg.first
52
- new_changes[segment_start_offset] = Change::Immediate.new(value)
58
+ if i != 0 && comp.at(program_segments[i-1].last - 1e-5) != value
59
+ new_changes[segment_start_offset] = Change::Immediate.new(value)
60
+ end
53
61
 
54
62
  changes.each do |off,change|
55
- adj_start_off = (off - seg.first) + segment_start_offset
56
-
57
- new_change = case change
58
- when Change::Immediate
59
- change.clone if seg.include?(off)
60
- when Change::Gradual::Trimmed
61
- end_off = off + change.remaining
62
- if off < seg.last && end_off > seg.first
63
- add_preceding = seg.first > off ? seg.first - off : 0
64
- add_trailing = end_off > seg.last ? end_off - seg.last : 0
63
+ adj_start_off = (off - seg.first) + segment_start_offset
65
64
 
66
- if add_preceding == 0 && add_trailing == 0
67
- change.clone
68
- else
69
- adj_start_off += add_preceding
70
- change.untrim.trim(change.preceding + add_preceding,
71
- change.trailing + add_trailing)
72
- end
73
- end
74
- when Change::Gradual
75
- end_off = off + change.duration
76
- if off < seg.last && end_off > seg.first
77
- preceding = seg.first > off ? seg.first - off : 0
78
- trailing = end_off > seg.last ? end_off - seg.last : 0
79
- if preceding == 0 && trailing == 0
80
- change.clone
81
- else
82
- adj_start_off += preceding
83
- change.trim(preceding, trailing)
84
- end
85
- end
86
- end
87
-
88
- unless new_change.nil?
89
- new_changes[adj_start_off] = new_change
90
- end
65
+ new_change = case change
66
+ when Change::Immediate
67
+ change.clone if seg.include?(off)
68
+ when Change::Gradual::Trimmed
69
+ end_off = off + change.remaining
70
+ if off < seg.last && end_off > seg.first
71
+ add_preceding = seg.first > off ? seg.first - off : 0
72
+ add_trailing = end_off > seg.last ? end_off - seg.last : 0
73
+
74
+ if add_preceding == 0 && add_trailing == 0
75
+ change.clone
76
+ else
77
+ adj_start_off += add_preceding
78
+ change.untrim.trim(change.preceding + add_preceding,
79
+ change.trailing + add_trailing)
80
+ end
81
+ end
82
+ when Change::Gradual
83
+ end_off = off + change.duration
84
+ if off < seg.last && end_off > seg.first
85
+ preceding = seg.first > off ? seg.first - off : 0
86
+ trailing = end_off > seg.last ? end_off - seg.last : 0
87
+ if preceding == 0 && trailing == 0
88
+ change.clone
89
+ else
90
+ adj_start_off += preceding
91
+ change.trim(preceding, trailing)
92
+ end
93
+ end
94
+ end
95
+
96
+ unless new_change.nil?
97
+ new_changes[adj_start_off] = new_change
98
+ end
91
99
  end
92
100
  end
93
101
 
94
- return new_changes
102
+ return new_start_val, new_changes
95
103
  end
96
104
 
97
105
  def collate_notes notes, program_segments
@@ -100,10 +108,10 @@ class ScoreCollator
100
108
  cur_offset = 0
101
109
  cur_notes = []
102
110
 
103
- l = 0
104
- while cur_offset < seg.first && l < notes.size
105
- cur_offset += notes[l].duration
106
- l += 1
111
+ i = 0
112
+ while cur_offset < seg.first && i < notes.size
113
+ cur_offset += notes[i].duration
114
+ i += 1
107
115
  end
108
116
 
109
117
  pre_remainder = cur_offset - seg.first
@@ -112,17 +120,15 @@ class ScoreCollator
112
120
  end
113
121
 
114
122
  # found some notes to add...
115
- if l < notes.size
116
- r = l
117
- while cur_offset < seg.last && r < notes.size
118
- cur_offset += notes[r].duration
119
- r += 1
123
+ if i < notes.size
124
+ while cur_offset < seg.last && i < notes.size
125
+ cur_notes << notes[i].clone
126
+ cur_offset += notes[i].duration
127
+ i += 1
120
128
  end
121
-
122
- cur_notes += Marshal.load(Marshal.dump(notes[l...r]))
123
129
  overshoot = cur_offset - seg.last
124
130
  if overshoot > 0
125
- cur_notes[-1].duration -= overshoot
131
+ cur_notes.last.duration = cur_notes.last.duration - overshoot
126
132
  cur_offset = seg.last
127
133
  end
128
134
  end
@@ -6,9 +6,9 @@ class MidiEvent
6
6
  end
7
7
 
8
8
  class NoteOn < MidiEvent
9
- attr_reader :notenum, :accented
10
- def initialize notenum, accented
11
- @notenum, @accented = notenum, accented
9
+ attr_reader :notenum, :attack
10
+ def initialize notenum, attack
11
+ @notenum, @attack = notenum, attack
12
12
  end
13
13
  end
14
14
 
@@ -0,0 +1,127 @@
1
+ module Musicality
2
+
3
+ class MidiSettings
4
+ include Packable
5
+
6
+ attr_reader :program
7
+
8
+ def initialize program
9
+ @program = program
10
+ end
11
+
12
+ ACOUSTIC_GRAND_PIANO = MidiSettings.new(1)
13
+ BRIGHT_ACOUSTIC_PIANO = MidiSettings.new(2)
14
+ ELECTRIC_GRAND_PIANO = MidiSettings.new(3)
15
+ HONKY_TONK_PIANO = MidiSettings.new(4)
16
+ ELECTRIC_PIANO_1 = MidiSettings.new(5)
17
+ ELECTRIC_PIANO_2 = MidiSettings.new(6)
18
+ HARPSICHORD = MidiSettings.new(7)
19
+ CLAVINET = MidiSettings.new(8)
20
+ CELESTA = MidiSettings.new(9)
21
+
22
+ GLOCKENSPIEL = MidiSettings.new(10)
23
+ MUSIC_BOX = MidiSettings.new(11)
24
+ VIBRAPHONE = MidiSettings.new(12)
25
+ MARIMBA = MidiSettings.new(13)
26
+ XYLOPHONE = MidiSettings.new(14)
27
+ TUBULAR_BELLS = MidiSettings.new(15)
28
+ DULCIMER = MidiSettings.new(16)
29
+
30
+ DRAWBAR_ORGAN = MidiSettings.new(17)
31
+ PERCUSSIVE_ORGAN = MidiSettings.new(18)
32
+ ROCK_ORGAN = MidiSettings.new(19)
33
+ CHURCH_ORGAN = MidiSettings.new(20)
34
+ REED_ORGAN = MidiSettings.new(21)
35
+ ACCORDIAN = MidiSettings.new(22)
36
+ HARMONICA = MidiSettings.new(23)
37
+ TANGO_ACCORDIAN = MidiSettings.new(24)
38
+
39
+ ACOUSTIC_GUITAR_NYLON = MidiSettings.new(25)
40
+ ACOUSTIC_GUITAR_STEEL = MidiSettings.new(26)
41
+ ELECTRIC_GUITAR_JAZZ = MidiSettings.new(27)
42
+ ELECTRIC_GUITAR_CLEAN = MidiSettings.new(28)
43
+ ELECTRIC_GUITAR_MUTED = MidiSettings.new(29)
44
+ OVERDRIVEN_GUITAR = MidiSettings.new(30)
45
+ DISTORTION_GUITAR = MidiSettings.new(31)
46
+ GUITAR_HARMONICS = MidiSettings.new(32)
47
+
48
+ ACOUSTIC_BASS = MidiSettings.new(33)
49
+ ELECTRIC_BASS_FINGER = MidiSettings.new(34)
50
+ ELECTRIC_BASS_PICK = MidiSettings.new(35)
51
+ FRETLESS_BASS = MidiSettings.new(36)
52
+ SLAP_BASS_1 = MidiSettings.new(37)
53
+ SLAP_BASS_2 = MidiSettings.new(38)
54
+ SYNTH_BASS_1 = MidiSettings.new(39)
55
+ SYNTH_BASS_2 = MidiSettings.new(40)
56
+
57
+ VIOLIN = MidiSettings.new(41)
58
+ VIOLA = MidiSettings.new(42)
59
+ CELLO = MidiSettings.new(43)
60
+ CONTRABASS = MidiSettings.new(44)
61
+ TREMOLO_STRINGS = MidiSettings.new(45)
62
+ PIZZICATO_STRINGS = MidiSettings.new(46)
63
+ ORCHESTRAL_HARP = MidiSettings.new(47)
64
+ TIMPANI = MidiSettings.new(48)
65
+ STRING_ENSEMBLE_1 = MidiSettings.new(49)
66
+ STRING_ENSEMBLE_2 = MidiSettings.new(50)
67
+ SYNTH_STRINGS_1 = MidiSettings.new(51)
68
+ SYNTH_STRINGS_2 = MidiSettings.new(52)
69
+
70
+ CHOIR_AAHS = MidiSettings.new(53)
71
+ VOICE_OOHS = MidiSettings.new(54)
72
+ SYNTH_VOICE = MidiSettings.new(55)
73
+ ORCHESTRA_HIT = MidiSettings.new(56)
74
+
75
+ TRUMPET = MidiSettings.new(57)
76
+ TROMBONE = MidiSettings.new(58)
77
+ TUBA = MidiSettings.new(59)
78
+ MUTED_TRUMPET = MidiSettings.new(60)
79
+ FRENCH_HORN = MidiSettings.new(61)
80
+ BRASS_SECTION = MidiSettings.new(62)
81
+ SYNTH_BRASS_1 = MidiSettings.new(63)
82
+ SYNTH_BRASS_2 = MidiSettings.new(64)
83
+
84
+ SOPRANO_SAX = MidiSettings.new(65)
85
+ ALTO_SAX = MidiSettings.new(66)
86
+ TENOR_SAX = MidiSettings.new(67)
87
+ BARITONE_SAX = MidiSettings.new(68)
88
+ OBOE = MidiSettings.new(69)
89
+ ENGLISH_HORN = MidiSettings.new(70)
90
+ BASSOON = MidiSettings.new(71)
91
+ CLARINET = MidiSettings.new(72)
92
+
93
+ PICCOLO = MidiSettings.new(73)
94
+ FLUTE = MidiSettings.new(74)
95
+ RECORDER = MidiSettings.new(75)
96
+ PAN_FLUTE = MidiSettings.new(76)
97
+ BLOWN_BOTTLE = MidiSettings.new(77)
98
+ SHAKUHACHI = MidiSettings.new(78)
99
+ WHISTLE = MidiSettings.new(79)
100
+ OCARINA = MidiSettings.new(80)
101
+
102
+ LEAD_SQUARE = MidiSettings.new(81)
103
+ LEAD_SAWTOOTH = MidiSettings.new(82)
104
+ LEAD_CALLIOPE = MidiSettings.new(83)
105
+ LEAD_CHIFF = MidiSettings.new(84)
106
+ LEAD_CHARANG = MidiSettings.new(85)
107
+ LEAD_VOICE = MidiSettings.new(86)
108
+ LEAD_FIFTHS = MidiSettings.new(87)
109
+ LEAD_PLUS_BASS = MidiSettings.new(88)
110
+
111
+ PAD_NEW_AGE = MidiSettings.new(89)
112
+ PAD_WARM = MidiSettings.new(90)
113
+ PAD_POLYSYNTH = MidiSettings.new(91)
114
+ PAD_CHOIR = MidiSettings.new(92)
115
+ PAD_BOWED = MidiSettings.new(93)
116
+ PAD_METALLIC = MidiSettings.new(94)
117
+ PAD_HALO = MidiSettings.new(95)
118
+ PAD_SWEEP = MidiSettings.new(96)
119
+ end
120
+
121
+ class Part
122
+ def midi_settings
123
+ find_settings(MidiSettings)
124
+ end
125
+ end
126
+
127
+ end
@@ -30,8 +30,14 @@ class MidiUtil
30
30
  (dynamic * 127).round
31
31
  end
32
32
 
33
- def self.note_velocity(accented)
34
- accented ? 112 : 70
33
+ def self.note_velocity(attack)
34
+ case attack
35
+ when Attack::NORMAL, Attack::NONE then 70
36
+ when Attack::TENUTO then 90
37
+ when Attack::ACCENT then 112
38
+ else
39
+ raise ArgumentError
40
+ end
35
41
  end
36
42
  end
37
43
 
@@ -2,10 +2,11 @@ module Musicality
2
2
 
3
3
  class PartSequencer
4
4
  def initialize part, dynamics_sample_rate: 50, cents_per_step: 10
5
- replace_portamento_with_glissando(part.notes)
5
+ notes = part.notes.map {|n| n.clone }
6
+ replace_portamento_with_glissando(notes)
6
7
 
7
- extractor = NoteSequenceExtractor.new(part.notes, cents_per_step)
8
- note_sequences = extractor.extract_sequences
8
+ extractor = NoteSequenceExtractor.new(notes)
9
+ note_sequences = extractor.extract_sequences(cents_per_step)
9
10
  note_events = gather_note_events(note_sequences)
10
11
 
11
12
  dynamic_events = gather_dynamic_events(part.start_dynamic,
@@ -27,7 +28,7 @@ class PartSequencer
27
28
 
28
29
  track.events << case event
29
30
  when MidiEvent::NoteOn
30
- vel = MidiUtil.note_velocity(event.accented)
31
+ vel = MidiUtil.note_velocity(event.attack)
31
32
  MIDI::NoteOn.new(channel, event.notenum, vel, delta)
32
33
  when MidiEvent::NoteOff
33
34
  MIDI::NoteOff.new(channel, event.notenum, 127, delta)
@@ -55,20 +56,17 @@ class PartSequencer
55
56
  def gather_note_events note_sequences
56
57
  note_events = []
57
58
  note_sequences.each do |note_seq|
58
- pitches = note_seq.pitches.sort
59
- pitches.each_index do |i|
60
- offset, pitch = pitches[i]
61
-
62
- accented = false
63
- if note_seq.attacks.has_key?(offset)
64
- accented = note_seq.attacks[offset].accented?
65
- end
59
+ offsets, elements = note_seq.offsets, note_seq.elements
60
+
61
+ elements.each_index do |i|
62
+ offset, element = offsets[i], elements[i]
63
+ pitch, attack = element.pitch, element.attack
66
64
 
67
65
  note_num = MidiUtil.pitch_to_notenum(pitch)
68
66
  on_at = offset
69
- off_at = (i < (pitches.size - 1)) ? pitches[i+1][0] : note_seq.stop
67
+ off_at = (i < (offsets.size - 1)) ? offsets[i+1] : note_seq.stop
70
68
 
71
- note_events.push [on_at, MidiEvent::NoteOn.new(note_num, accented)]
69
+ note_events.push [on_at, MidiEvent::NoteOn.new(note_num, attack)]
72
70
  note_events.push [off_at, MidiEvent::NoteOff.new(note_num)]
73
71
  end
74
72
  end
@@ -99,19 +97,22 @@ class PartSequencer
99
97
  return dynamic_events
100
98
  end
101
99
 
102
- def begin_track midi_sequence, part_name, channel, program
100
+ def begin_track midi_sequence, track_name, channel, program
101
+ raise ArgumentError, "Program number #{program} is not in range 1-128" unless program.between?(1,128)
102
+ program_idx = program - 1 # program numbers start at 1, array indices start at 0
103
+
103
104
  # Track to hold part notes
104
105
  track = MIDI::Track.new(midi_sequence)
105
106
 
106
107
  # Name the track and instrument
107
- track.name = part_name
108
- track.instrument = MIDI::GM_PATCH_NAMES[program]
108
+ track.name = track_name
109
+ track.instrument = MIDI::GM_PATCH_NAMES[program_idx]
109
110
 
110
111
  # Add a volume controller event (optional).
111
112
  track.events << MIDI::Controller.new(channel, MIDI::CC_VOLUME, 127)
112
113
 
113
114
  # Change to particular instrument sound
114
- track.events << MIDI::ProgramChange.new(channel, program)
115
+ track.events << MIDI::ProgramChange.new(channel, program_idx)
115
116
 
116
117
  return track
117
118
  end
@@ -4,11 +4,18 @@ class ScoreSequencer
4
4
  def initialize score
5
5
  unless score.is_a?(Score::Timed)
6
6
  raise ArgumentError, "The given score is not a Score::Timed. \
7
- Convert it first using MeasureScoreConverter or UnmeasuredScoreConverter."
7
+ Convert it first using ScoreConverter."
8
8
  end
9
9
 
10
10
  @parts = score.collated? ? score.parts : ScoreCollator.new(score).collate_parts
11
-
11
+
12
+ # parts should all have MIDI settings, defaults if necessary
13
+ @parts.each do |part_name, part|
14
+ unless part.midi_settings
15
+ part.settings.push MidiSettings.new(1)
16
+ end
17
+ end
18
+
12
19
  # part names should all be strings, because 1) a midi track name needs to
13
20
  # be a string and 2) the instrument map used to map part names to MIDI
14
21
  # program numbers will use part name strings as keys.
@@ -17,7 +24,7 @@ class ScoreSequencer
17
24
 
18
25
  USEC_PER_QUARTER_SEC = 250000
19
26
 
20
- def make_midi_seq instr_map = {}
27
+ def make_midi_seq selected_parts = @parts.keys
21
28
  seq = MIDI::Sequence.new()
22
29
 
23
30
  # first track for the sequence holds time sig and tempo events
@@ -27,12 +34,9 @@ class ScoreSequencer
27
34
  track0.events << MIDI::MetaEvent.new(MIDI::META_SEQ_NAME, 'Sequence Name')
28
35
 
29
36
  channel = 0
30
- @parts.each do |part_name,part|
31
- program = 1
32
- if instr_map.has_key?(part_name)
33
- program = instr_map[part_name]
34
- end
35
-
37
+ selected_parts.each do |part_name|
38
+ part = @parts.fetch(part_name)
39
+ program = part.midi_settings.program
36
40
  pseq = PartSequencer.new(part)
37
41
  seq.tracks << pseq.make_midi_track(seq, part_name, channel, seq.ppqn, program)
38
42
  channel += 1