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
@@ -1,7 +1,7 @@
1
1
  module Musicality
2
2
 
3
3
  class PitchClass
4
- MOD = Pitch::SEMITONES_PER_OCTAVE
4
+ MOD = 12 # semitones per octave
5
5
 
6
6
  def self.from_i i
7
7
  i % MOD
@@ -1,18 +1,18 @@
1
1
  module Musicality
2
2
 
3
3
  module PitchClasses
4
- C = 0
5
- Db = 1
4
+ C = Bs = 0
5
+ Cs = Db = 1
6
6
  D = 2
7
- Eb = 3
8
- E = 4
9
- F = 5
10
- Gb = 6
7
+ Ds = Eb = 3
8
+ E = Fb = 4
9
+ Es = F = 5
10
+ Fs = Gb = 6
11
11
  G = 7
12
- Ab = 8
12
+ Gs = Ab = 8
13
13
  A = 9
14
- Bb = 10
15
- B = 11
14
+ As = Bb = 10
15
+ B = Cb = 11
16
16
  end
17
17
 
18
18
  PITCH_CLASSES = PitchClasses.constants.map do |sym|
@@ -0,0 +1,12 @@
1
+ module Musicality
2
+
3
+ module Clef
4
+ TREBLE = :treble
5
+ BASS = :bass
6
+ TENOR = :tenor
7
+ ALTO = :alto
8
+ end
9
+
10
+ CLEFS = [Clef::TREBLE, Clef::ALTO, Clef::BASS, Clef::TENOR]
11
+
12
+ end
@@ -0,0 +1,9 @@
1
+ module Musicality
2
+
3
+ class Key
4
+ def to_lilypond
5
+ "\\key #{PitchClass.to_lilypond(@tonic_pc, sharp?)} \\#{@triad}"
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,105 @@
1
+ module Musicality
2
+
3
+ class LilypondSettings
4
+ include Packable
5
+
6
+ attr_reader :instrument_name, :clefs, :transpose_interval
7
+
8
+ def initialize instrument_name, clefs: [Clef::TREBLE, Clef::BASS], transpose_interval: 0
9
+ raise ArgumentError unless (clefs & CLEFS) == clefs
10
+ @instrument_name = instrument_name
11
+ @clefs = clefs
12
+ @transpose_interval = transpose_interval
13
+ end
14
+
15
+ def self.treble_bass(instrument_name, transpose_interval: 0)
16
+ new(instrument_name, clefs: [Clef::TREBLE, Clef::BASS], transpose_interval: transpose_interval)
17
+ end
18
+
19
+ def self.treble(instrument_name, transpose_interval: 0)
20
+ new(instrument_name, clefs: [Clef::TREBLE], transpose_interval: transpose_interval)
21
+ end
22
+
23
+ def self.bass(instrument_name, transpose_interval: 0)
24
+ new(instrument_name, clefs: [Clef::BASS], transpose_interval: transpose_interval)
25
+ end
26
+
27
+ def self.guitar(instrument_name)
28
+ new(instrument_name, clefs: [Clef::TENOR], transpose_interval: 12)
29
+ end
30
+
31
+ def self.bass_guitar(instrument_name)
32
+ new(instrument_name, clefs: [Clef::BASS], transpose_interval: 12)
33
+ end
34
+
35
+ ACOUSTIC_GRAND_PIANO = LilypondSettings.treble_bass("Acoustic Grand Piano")
36
+ BRIGHT_ACOUSTIC_PIANO = LilypondSettings.treble_bass("Bright Acoustic Piano")
37
+ ELECTRIC_GRAND_PIANO = LilypondSettings.treble_bass("Electric Grand Piano")
38
+ HONKY_TONK_PIANO = LilypondSettings.treble_bass("Honky Tonk Piano")
39
+ ELECTRIC_PIANO = LilypondSettings.treble_bass("Electric Piano")
40
+ HARPSICHORD = LilypondSettings.treble_bass("Harpsichord")
41
+ CLAVINET = LilypondSettings.treble_bass("Clavinet")
42
+ CELESTA = LilypondSettings.treble_bass("Celesta", transpose_interval: -12)
43
+
44
+ GLOCKENSPIEL = LilypondSettings.treble_bass("Glockenspiel", transpose_interval: -24)
45
+ MUSIC_BOX = LilypondSettings.treble_bass("Music Box")
46
+ VIBRAPHONE = LilypondSettings.treble_bass("Virbaphone")
47
+ MARIMBA = LilypondSettings.treble_bass("Marimba")
48
+ XYLOPHONE = LilypondSettings.treble_bass("Xylophone", transpose_interval: -12)
49
+ TUBULAR_BELLS = LilypondSettings.treble_bass("Tubular Bells")
50
+ DULCIMER = LilypondSettings.treble("Dulcimer")
51
+
52
+ DRAWBAR_ORGAN = LilypondSettings.treble_bass("Drawbar Organ")
53
+ PERCUSSIVE_ORGAN = LilypondSettings.treble_bass("Percussive Organ")
54
+ ROCK_ORGAN = LilypondSettings.treble_bass("Rock Organ")
55
+ CHURCH_ORGAN = LilypondSettings.treble_bass("Church Organ")
56
+ REED_ORGAN = LilypondSettings.treble_bass("Reed Organ")
57
+ ACCORDIAN = LilypondSettings.treble_bass("Accordion")
58
+ HARMONICA = LilypondSettings.treble("Harmonica")
59
+ TANGO_ACCORDIAN = LilypondSettings.treble_bass("Tango Accordion")
60
+
61
+ ACOUSTIC_GUITAR = LilypondSettings.guitar('Acoustic Guitar')
62
+ ELECTRIC_GUITAR = LilypondSettings.guitar('Electric Guitar')
63
+ OVERDRIVEN_GUITAR = LilypondSettings.guitar('Overdriven Guitar')
64
+ DISTORTION_GUITAR = LilypondSettings.guitar('Distortion Guitar')
65
+ GUITAR_HARMONICS = LilypondSettings.guitar('Guitar Harmonics')
66
+
67
+ ACOUSTIC_BASS = LilypondSettings.bass_guitar('Acoustic Bass')
68
+ ELECTRIC_BASS = LilypondSettings.bass_guitar('Electric Bass')
69
+ FRETLESS_BASS = LilypondSettings.bass_guitar('Fretless Bass')
70
+ SLAP_BASS = LilypondSettings.bass_guitar('Slap Bass')
71
+ SYNTH_BASS = LilypondSettings.bass_guitar('Synth Bass')
72
+
73
+ VIOLIN = LilypondSettings.treble('Violin')
74
+ VIOLA = LilypondSettings.new('Viola', clefs: [Clef::TREBLE, Clef::ALTO])
75
+ CELLO = LilypondSettings.new('Cello', clefs: [Clef::BASS, Clef::TENOR])
76
+ CONTRABASS = LilypondSettings.bass('Contrabass', transpose_interval: 12)
77
+ TREMOLO_STRINGS = LilypondSettings.treble_bass('Tremolo Strings')
78
+ PIZZICATO_STRINGS = LilypondSettings.treble_bass('Pizzicato Strings')
79
+ ORCHESTRAL_HARP = LilypondSettings.treble_bass('Orchestral Harp')
80
+ TIMPANI = LilypondSettings.bass('Timpani')
81
+ STRING_ENSEMBLE = LilypondSettings.treble_bass('String Ensemble')
82
+
83
+ TRUMPET = LilypondSettings.treble('Trumpet', transpose_interval: 2)
84
+ TROMBONE = LilypondSettings.new('Trombone', clefs: [Clef::BASS, Clef::TENOR])
85
+ TUBA = LilypondSettings.bass('Tuba')
86
+ FRENCH_HORN = LilypondSettings.bass('French Horn')
87
+
88
+ SOPRANO_SAX = LilypondSettings.treble('Soprano Sax', transpose_interval: 2)
89
+ ALTO_SAX = LilypondSettings.treble('Alto Sax', transpose_interval: 9)
90
+ TENOR_SAX = LilypondSettings.treble('Tenor Sax', transpose_interval: 14)
91
+ BARITONE_SAX = LilypondSettings.treble('Baritone Sax', transpose_interval: 21)
92
+ OBOE = LilypondSettings.treble('Oboe')
93
+ ENGLISH_HORN = LilypondSettings.treble('English Horn', transpose_interval: 7)
94
+ BASSOON = LilypondSettings.bass('Bassoon')
95
+ CLARINET = LilypondSettings.treble('Clarinet', transpose_interval: 2)
96
+ PICCOLO = LilypondSettings.treble('Piccolo', transpose_interval: -12)
97
+ end
98
+
99
+ class Part
100
+ def lilypond_settings
101
+ find_settings(LilypondSettings)
102
+ end
103
+ end
104
+
105
+ end
@@ -4,7 +4,7 @@ class Meter
4
4
  def to_lilypond
5
5
  num = beats_per_measure * beat_duration.numerator
6
6
  den = beat_duration.denominator
7
- "#{num}/#{den}"
7
+ "\\time #{num}/#{den}"
8
8
  end
9
9
  end
10
10
 
@@ -1,52 +1,134 @@
1
1
  module Musicality
2
2
 
3
3
  class Note
4
+ SMALLEST_PIECE = Rational(1,256)
5
+
4
6
  def to_lilypond sharpit = false
5
- dur_strs = []
6
- d = duration
7
-
8
- if d > 1
9
- dur_strs += ["1"]*d.to_i
10
- d -= d.to_i
11
- end
7
+ subdurs = [1]*@duration.to_i + fractional_subdurs(SMALLEST_PIECE)
12
8
 
13
- if d > 0
14
- n = Math.log2(d)
15
- if n < -7
16
- raise UnsupportedDurationError, "Note duration #{d} is too short for Lilypond"
9
+ piece_strs = []
10
+ pitches_to_strs = Hash[ pitches.map {|p| [p,p.to_lilypond(sharpit)] }]
11
+ while subdurs.any?
12
+ subdur = subdurs.shift
13
+ dur_str = subdur.denominator.to_s
14
+ if subdurs.any? && subdur == subdurs.first*2
15
+ dur_str += "."
16
+ subdurs.shift
17
17
  end
18
+ last = subdurs.empty?
18
19
 
19
- if n.to_i == n # duration is exact power of two
20
- dur_strs.push (2**(-n)).to_i.to_s
21
- else # duration may be dotted
22
- undotted_duration = d/1.5
23
- n = Math.log2(undotted_duration)
24
-
25
- if n.to_i == n # duration (undotted) is exact power of two
26
- if n < -7
27
- raise UnsupportedDurationError, "Undotted note duration #{undotted_duration} is too short for Lilypond"
20
+ piece_str = if pitches.any?
21
+ if last
22
+ # figure if ties are needed on per-pitch basis, based on note links
23
+ if pitches_to_strs.size == 1
24
+ p, p_str = pitches_to_strs.first
25
+ needs_tie = links.include?(p) && links[p].is_a?(Link::Tie)
26
+ p_str + dur_str + (needs_tie ? "~" : "")
27
+ else
28
+ p_strs = pitches_to_strs.map do |p,p_str|
29
+ if links.include?(p) && links[p].is_a?(Link::Tie)
30
+ p_str + "~"
31
+ else
32
+ p_str
33
+ end
34
+ end
35
+ "<#{p_strs.join(" ")}>" + dur_str
28
36
  end
29
-
30
- dur_strs.push (2**(-n)).to_i.to_s + "."
31
37
  else
32
- raise UnsupportedDurationError, "Leftover note duration #{d}is not power-of-two, and is not dotted power-of-two"
38
+ str = if pitches.size == 1
39
+ pitches_to_strs.values.first
40
+ else
41
+ "<#{pitches_to_strs.values.join(" ")}>"
42
+ end
43
+ str + dur_str + "~"
33
44
  end
45
+ else
46
+ "r" + dur_str
34
47
  end
48
+
49
+ piece_strs.push piece_str
35
50
  end
36
-
51
+
52
+ if pitches.any?
53
+ if articulation != Articulations::NORMAL
54
+ piece_strs[0] += "-" + ARTICULATION_SYMBOLS[articulation]
55
+ end
56
+
57
+ if begins_slur?
58
+ piece_strs[-1] += MARK_SYMBOLS[Mark::Slur::Begin]
59
+ end
60
+
61
+ if ends_slur?
62
+ piece_strs[-1] += MARK_SYMBOLS[Mark::Slur::End]
63
+ end
64
+ end
65
+
66
+ if begins_triplet?
67
+ piece_strs[0].prepend("\\tuplet 3/2 {")
68
+ end
69
+
70
+ if ends_triplet?
71
+ piece_strs[-1].concat("}")
72
+ end
73
+
74
+ return piece_strs.join(" ")
75
+ end
76
+
77
+ def fractional_subdurs smallest_piece
78
+ remaining = @duration - @duration.to_i
79
+ pieces = []
80
+ i = 0
81
+ while((current_dur = Rational(1,2<<i)) >= smallest_piece)
82
+ if remaining >= current_dur
83
+ pieces.push current_dur
84
+ remaining -= current_dur
85
+ end
86
+ i += 1
87
+ end
88
+
89
+ unless remaining.zero?
90
+ raise RuntimeError, "Non-zero remainder #{remaining}"
91
+ end
92
+
93
+ return pieces
94
+ end
95
+
96
+ private
97
+
98
+
99
+ def pieces_to_dur_strs pieces
100
+ dur_strs = []
101
+ while pieces.any?
102
+ piece = pieces.shift
103
+ triplet = piece.denominator % 3 == 0
104
+ piece_str = (triplet ? (piece * 1.5.to_r) : piece).denominator.to_s
105
+ if pieces.any? && piece == pieces.first*2
106
+ piece_str += "."
107
+ pieces.shift
108
+ end
109
+
110
+ if triplet
111
+ piece_str = "\\tuplet 3/2 { #{piece_str} }"
112
+ end
113
+
114
+ dur_strs.push piece_str
115
+ end
116
+ return dur_strs
117
+ end
118
+
119
+ def figure_pitch_and_joipiece_str sharpit
37
120
  if pitches.any?
38
121
  if pitches.size == 1
39
- p_str = pitches.first.to_lilypond
122
+ p_str = pitches.first.to_lilypond(sharpit)
40
123
  else
41
- p_str = "<" + pitches.map {|p| p.to_lilypond}.join(" ") + ">"
124
+ p_str = "<" + pitches.map {|p| p.to_lilypond(sharpit) }.join(" ") + ">"
42
125
  end
43
- join_str = "~ "
126
+ joipiece_str = "~ "
44
127
  else
45
128
  p_str = "r"
46
- join_str = " "
129
+ joipiece_str = " "
47
130
  end
48
-
49
- return dur_strs.map {|dur_str| p_str + dur_str }.join(join_str)
131
+ return [ p_str, joipiece_str ]
50
132
  end
51
133
  end
52
134
 
@@ -1,11 +1,122 @@
1
1
  module Musicality
2
2
 
3
3
  class PartEngraver
4
- def initialize part
5
- @part = part
4
+ MAX_LINE_LEN = 76
5
+ INDENT = " "
6
+
7
+ def initialize part, title
8
+ if part.invalid?
9
+ raise ArgumentError, "given part contains errors: #{part.errors}"
10
+ end
11
+
12
+ @transpose_interval = part.lilypond_settings.transpose_interval
13
+ @clefs = part.lilypond_settings.clefs
14
+ @part = (@transpose_interval == 0) ? part : part.transpose(@transpose_interval)
15
+ @title = title
16
+ @indent = INDENT
17
+ end
18
+
19
+ def increase_indent
20
+ @indent += INDENT
21
+ end
22
+
23
+ def decrease_indent
24
+ @indent = @indent[0...-INDENT.size]
25
+ end
26
+
27
+ def make_lilypond start_key, start_meter, key_changes: {}, meter_changes: {}, master: false
28
+ if @transpose_interval != 0
29
+ start_key = transpose_start_key(start_key)
30
+ key_changes = transpose_key_changes(key_changes)
31
+ end
32
+
33
+ sharpit = start_key.sharp?
34
+ return make_preliminary(start_meter, start_key, master) +
35
+ make_body(sharpit) + make_final()
36
+ end
37
+
38
+ def make_preliminary start_meter, start_key, master
39
+ clef = self.class.best_clef(@part.notes, @clefs)
40
+
41
+ output = @indent + "\\new Staff {\n"
42
+ increase_indent
43
+ output += @indent + "\\set Staff.instrumentName = \\markup { \"#{@title}\" }\n"
44
+ output += @indent + "\\clef #{clef}\n"
45
+ if master
46
+ output += @indent + start_meter.to_lilypond + "\n"
47
+ end
48
+ output += @indent + start_key.to_lilypond + "\n"
49
+ return output
50
+ end
51
+
52
+ def make_body sharpit
53
+ pieces = @part.notes.map {|n| n.to_lilypond(sharpit)}
54
+
55
+ output = ""
56
+ while pieces.any?
57
+ line = @indent + pieces.shift
58
+ until pieces.empty? || (line.size + 1 + pieces.first.size) > MAX_LINE_LEN
59
+ line += " " + pieces.shift
60
+ end
61
+ output += line + "\n"
62
+ end
63
+ return output
64
+ end
65
+
66
+ def make_final
67
+ decrease_indent
68
+ return @indent + "}\n"
69
+ end
70
+
71
+ CLEF_RANGES = {
72
+ Clef::TREBLE => Pitches::C4..Pitches::A5,
73
+ Clef::ALTO => Pitches::D3..Pitches::B4,
74
+ Clef::TENOR => Pitches::B2..Pitches::G4,
75
+ Clef::BASS => Pitches::E2..Pitches::C4,
76
+ }
77
+
78
+ def self.best_clef notes, allowed_clefs
79
+ raise ArgumentError unless notes.any?
80
+ raise ArgumentError unless allowed_clefs.any?
81
+
82
+ ranges = CLEF_RANGES.select {|clef,range| allowed_clefs.include?(clef) }
83
+ range_scores = Hash.new(0)
84
+
85
+ in_triplet = false
86
+ notes.each do |note|
87
+ if note.begins_triplet?
88
+ in_triplet = true
89
+ end
90
+
91
+ dur = note.duration * (in_triplet ? Rational(2,3) : 1)
92
+ note.pitches.each do |p|
93
+ ranges.each do |name,range|
94
+ if p >= range.min && p <= range.max
95
+ range_scores[name] += dur
96
+ end
97
+ end
98
+ end
99
+
100
+ if note.ends_triplet?
101
+ in_triplet = false
102
+ end
103
+ end
104
+ range_score = range_scores.max_by {|range,score| score}
105
+ range_score.nil? ? allowed_clefs.first : range_score[0]
106
+ end
107
+
108
+ private
109
+
110
+ def transpose_start_key start_key
111
+ start_key.transpose(@transpose_interval)
6
112
  end
7
113
 
8
- def to_lilypad
114
+ def transpose_key_changes key_changes
115
+ Hash[
116
+ key_changes.map do |off,change|
117
+ change.clone {|v| v.transpose(@transpose_interval) }
118
+ end
119
+ ]
9
120
  end
10
121
  end
11
122