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.
- 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
|