musicality 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +27 -1
- data/README.md +153 -10
- data/bin/collidify +102 -0
- data/bin/lilify +57 -29
- data/bin/midify +64 -24
- data/bin/musicality +39 -0
- data/examples/composition/auto_counterpoint.rb +4 -5
- data/examples/composition/part_generator.rb +8 -2
- data/examples/composition/scale_exercise.rb +1 -1
- data/examples/notation/notes.rb +27 -0
- data/examples/notation/parts.rb +51 -0
- data/examples/notation/scores.rb +38 -0
- data/examples/notation/twinkle.rb +34 -0
- data/examples/notation/twinkle.score +33 -0
- data/lib/musicality.rb +46 -11
- data/lib/musicality/composition/dsl/score_dsl.rb +2 -2
- data/lib/musicality/composition/dsl/score_methods.rb +10 -7
- data/lib/musicality/notation/conversion/change_conversion.rb +1 -1
- data/lib/musicality/notation/conversion/note_time_converter.rb +6 -23
- data/lib/musicality/notation/conversion/score_conversion.rb +15 -15
- data/lib/musicality/notation/conversion/score_converter.rb +50 -67
- data/lib/musicality/notation/model/articulations.rb +3 -2
- data/lib/musicality/notation/model/change.rb +15 -6
- data/lib/musicality/notation/model/dynamics.rb +11 -8
- data/lib/musicality/notation/model/instrument.rb +61 -0
- data/lib/musicality/notation/model/instruments.rb +111 -0
- data/lib/musicality/notation/model/key.rb +137 -0
- data/lib/musicality/notation/model/keys.rb +37 -0
- data/lib/musicality/notation/model/link.rb +6 -19
- data/lib/musicality/notation/model/mark.rb +43 -0
- data/lib/musicality/notation/model/marks.rb +11 -0
- data/lib/musicality/notation/model/meter.rb +4 -0
- data/lib/musicality/notation/model/note.rb +42 -28
- data/lib/musicality/notation/model/part.rb +18 -5
- data/lib/musicality/notation/model/pitch.rb +13 -4
- data/lib/musicality/notation/model/score.rb +104 -66
- data/lib/musicality/notation/model/symbols.rb +16 -11
- data/lib/musicality/notation/parsing/articulation_parsing.rb +38 -38
- data/lib/musicality/notation/parsing/articulation_parsing.treetop +14 -14
- data/lib/musicality/notation/parsing/link_parsing.rb +6 -6
- data/lib/musicality/notation/parsing/link_parsing.treetop +3 -3
- data/lib/musicality/notation/parsing/mark_parsing.rb +138 -0
- data/lib/musicality/notation/parsing/mark_parsing.treetop +31 -0
- data/lib/musicality/notation/parsing/note_node.rb +19 -12
- data/lib/musicality/notation/parsing/note_parsing.rb +218 -87
- data/lib/musicality/notation/parsing/note_parsing.treetop +9 -5
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +7 -2
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +1 -1
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +6 -4
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +1 -1
- data/lib/musicality/notation/util/function.rb +41 -18
- data/lib/musicality/packable.rb +156 -0
- data/lib/musicality/performance/conversion/glissando_converter.rb +2 -2
- data/lib/musicality/performance/conversion/note_sequence_extractor.rb +223 -70
- data/lib/musicality/performance/conversion/portamento_converter.rb +5 -2
- data/lib/musicality/performance/conversion/score_collator.rb +70 -64
- data/lib/musicality/performance/midi/midi_events.rb +3 -3
- data/lib/musicality/performance/midi/midi_settings.rb +127 -0
- data/lib/musicality/performance/midi/midi_util.rb +8 -2
- data/lib/musicality/performance/midi/part_sequencer.rb +19 -18
- data/lib/musicality/performance/midi/score_sequencer.rb +13 -9
- data/lib/musicality/performance/midi/score_sequencing.rb +5 -5
- data/lib/musicality/performance/model/attack.rb +8 -0
- data/lib/musicality/performance/model/duration_functions.rb +23 -0
- data/lib/musicality/performance/model/note_sequence.rb +52 -95
- data/lib/musicality/performance/model/separation.rb +10 -0
- data/lib/musicality/performance/supercollider/add_actions.rb +13 -0
- data/lib/musicality/performance/supercollider/bundle.rb +18 -0
- data/lib/musicality/performance/supercollider/conductor.rb +125 -0
- data/lib/musicality/performance/supercollider/group.rb +71 -0
- data/lib/musicality/performance/supercollider/message.rb +26 -0
- data/lib/musicality/performance/supercollider/node.rb +122 -0
- data/lib/musicality/performance/supercollider/performer.rb +123 -0
- data/lib/musicality/performance/supercollider/score_conducting.rb +17 -0
- data/lib/musicality/performance/supercollider/server.rb +8 -0
- data/lib/musicality/performance/supercollider/synth.rb +43 -0
- data/lib/musicality/performance/supercollider/synthdef.rb +57 -0
- data/lib/musicality/performance/supercollider/synthdef_settings.rb +23 -0
- data/lib/musicality/performance/supercollider/synthdefs.rb +1654 -0
- data/lib/musicality/{composition/model/pitch_class.rb → pitch_class.rb} +1 -1
- data/lib/musicality/{composition/model/pitch_classes.rb → pitch_classes.rb} +9 -9
- data/lib/musicality/printing/lilypond/clef.rb +12 -0
- data/lib/musicality/printing/lilypond/key_engraving.rb +9 -0
- data/lib/musicality/printing/lilypond/lilypond_settings.rb +105 -0
- data/lib/musicality/printing/lilypond/meter_engraving.rb +1 -1
- data/lib/musicality/printing/lilypond/note_engraving.rb +112 -30
- data/lib/musicality/printing/lilypond/part_engraver.rb +114 -3
- data/lib/musicality/printing/lilypond/pitch_class_engraving.rb +22 -0
- data/lib/musicality/printing/lilypond/pitch_engraving.rb +2 -15
- data/lib/musicality/printing/lilypond/score_engraver.rb +44 -73
- data/lib/musicality/printing/lilypond/score_engraving.rb +3 -3
- data/lib/musicality/project/create_tasks.rb +31 -0
- data/lib/musicality/project/file_cleaner.rb +19 -0
- data/lib/musicality/project/file_raker.rb +107 -0
- data/lib/musicality/project/load_config.rb +43 -0
- data/lib/musicality/project/project.rb +64 -0
- data/lib/musicality/version.rb +1 -1
- data/musicality.gemspec +3 -0
- data/spec/composition/util/random_sampler_spec.rb +1 -1
- data/spec/notation/conversion/measure_note_map_spec.rb +1 -1
- data/spec/notation/conversion/note_time_converter_spec.rb +5 -85
- data/spec/notation/conversion/score_conversion_spec.rb +6 -41
- data/spec/notation/conversion/score_converter_spec.rb +19 -137
- data/spec/notation/model/change_spec.rb +55 -0
- data/spec/notation/model/key_spec.rb +171 -0
- data/spec/notation/model/link_spec.rb +34 -5
- data/spec/notation/model/meter_spec.rb +15 -0
- data/spec/notation/model/note_spec.rb +33 -27
- data/spec/notation/model/part_spec.rb +53 -4
- data/spec/notation/model/pitch_spec.rb +15 -0
- data/spec/notation/model/score_spec.rb +64 -72
- data/spec/notation/parsing/link_nodes_spec.rb +3 -3
- data/spec/notation/parsing/link_parsing_spec.rb +6 -6
- data/spec/notation/parsing/note_node_spec.rb +34 -9
- data/spec/notation/parsing/note_parsing_spec.rb +11 -9
- data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +4 -0
- data/spec/notation/parsing/pitch_node_spec.rb +0 -1
- data/spec/notation/util/value_computer_spec.rb +2 -2
- data/spec/performance/conversion/glissando_converter_spec.rb +9 -9
- data/spec/performance/conversion/note_sequence_extractor_spec.rb +48 -53
- data/spec/performance/conversion/portamento_converter_spec.rb +11 -9
- data/spec/performance/conversion/score_collator_spec.rb +59 -63
- data/spec/performance/midi/midi_util_spec.rb +22 -8
- data/spec/performance/midi/part_sequencer_spec.rb +2 -2
- data/spec/performance/midi/score_sequencer_spec.rb +12 -10
- data/spec/performance/midi/score_sequencing_spec.rb +2 -2
- data/spec/performance/model/note_sequence_spec.rb +41 -134
- data/spec/printing/note_engraving_spec.rb +204 -0
- data/spec/printing/score_engraver_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -0
- metadata +69 -23
- data/examples/notation/hip.rb +0 -32
- data/examples/notation/missed_connection.rb +0 -26
- data/examples/notation/song1.rb +0 -33
- data/examples/notation/song2.rb +0 -32
- data/lib/musicality/notation/model/links.rb +0 -11
- data/lib/musicality/notation/packing/change_packing.rb +0 -56
- data/lib/musicality/notation/packing/part_packing.rb +0 -31
- data/lib/musicality/notation/packing/score_packing.rb +0 -123
- data/lib/musicality/performance/model/note_attacks.rb +0 -19
- data/lib/musicality/performance/util/note_linker.rb +0 -28
- data/spec/notation/packing/change_packing_spec.rb +0 -304
- data/spec/notation/packing/part_packing_spec.rb +0 -66
- data/spec/notation/packing/score_packing_spec.rb +0 -255
- 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,
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
104
|
-
while cur_offset < seg.first &&
|
105
|
-
cur_offset += notes[
|
106
|
-
|
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
|
116
|
-
|
117
|
-
|
118
|
-
cur_offset += notes[
|
119
|
-
|
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
|
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, :
|
10
|
-
def initialize notenum,
|
11
|
-
@notenum, @
|
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(
|
34
|
-
|
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
|
-
|
5
|
+
notes = part.notes.map {|n| n.clone }
|
6
|
+
replace_portamento_with_glissando(notes)
|
6
7
|
|
7
|
-
extractor = NoteSequenceExtractor.new(
|
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.
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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 < (
|
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,
|
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,
|
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 =
|
108
|
-
track.instrument = MIDI::GM_PATCH_NAMES[
|
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,
|
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
|
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
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|