musicality 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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