musicality 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +27 -1
  3. data/README.md +153 -10
  4. data/bin/collidify +102 -0
  5. data/bin/lilify +57 -29
  6. data/bin/midify +64 -24
  7. data/bin/musicality +39 -0
  8. data/examples/composition/auto_counterpoint.rb +4 -5
  9. data/examples/composition/part_generator.rb +8 -2
  10. data/examples/composition/scale_exercise.rb +1 -1
  11. data/examples/notation/notes.rb +27 -0
  12. data/examples/notation/parts.rb +51 -0
  13. data/examples/notation/scores.rb +38 -0
  14. data/examples/notation/twinkle.rb +34 -0
  15. data/examples/notation/twinkle.score +33 -0
  16. data/lib/musicality.rb +46 -11
  17. data/lib/musicality/composition/dsl/score_dsl.rb +2 -2
  18. data/lib/musicality/composition/dsl/score_methods.rb +10 -7
  19. data/lib/musicality/notation/conversion/change_conversion.rb +1 -1
  20. data/lib/musicality/notation/conversion/note_time_converter.rb +6 -23
  21. data/lib/musicality/notation/conversion/score_conversion.rb +15 -15
  22. data/lib/musicality/notation/conversion/score_converter.rb +50 -67
  23. data/lib/musicality/notation/model/articulations.rb +3 -2
  24. data/lib/musicality/notation/model/change.rb +15 -6
  25. data/lib/musicality/notation/model/dynamics.rb +11 -8
  26. data/lib/musicality/notation/model/instrument.rb +61 -0
  27. data/lib/musicality/notation/model/instruments.rb +111 -0
  28. data/lib/musicality/notation/model/key.rb +137 -0
  29. data/lib/musicality/notation/model/keys.rb +37 -0
  30. data/lib/musicality/notation/model/link.rb +6 -19
  31. data/lib/musicality/notation/model/mark.rb +43 -0
  32. data/lib/musicality/notation/model/marks.rb +11 -0
  33. data/lib/musicality/notation/model/meter.rb +4 -0
  34. data/lib/musicality/notation/model/note.rb +42 -28
  35. data/lib/musicality/notation/model/part.rb +18 -5
  36. data/lib/musicality/notation/model/pitch.rb +13 -4
  37. data/lib/musicality/notation/model/score.rb +104 -66
  38. data/lib/musicality/notation/model/symbols.rb +16 -11
  39. data/lib/musicality/notation/parsing/articulation_parsing.rb +38 -38
  40. data/lib/musicality/notation/parsing/articulation_parsing.treetop +14 -14
  41. data/lib/musicality/notation/parsing/link_parsing.rb +6 -6
  42. data/lib/musicality/notation/parsing/link_parsing.treetop +3 -3
  43. data/lib/musicality/notation/parsing/mark_parsing.rb +138 -0
  44. data/lib/musicality/notation/parsing/mark_parsing.treetop +31 -0
  45. data/lib/musicality/notation/parsing/note_node.rb +19 -12
  46. data/lib/musicality/notation/parsing/note_parsing.rb +218 -87
  47. data/lib/musicality/notation/parsing/note_parsing.treetop +9 -5
  48. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +7 -2
  49. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +1 -1
  50. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +6 -4
  51. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +1 -1
  52. data/lib/musicality/notation/util/function.rb +41 -18
  53. data/lib/musicality/packable.rb +156 -0
  54. data/lib/musicality/performance/conversion/glissando_converter.rb +2 -2
  55. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +223 -70
  56. data/lib/musicality/performance/conversion/portamento_converter.rb +5 -2
  57. data/lib/musicality/performance/conversion/score_collator.rb +70 -64
  58. data/lib/musicality/performance/midi/midi_events.rb +3 -3
  59. data/lib/musicality/performance/midi/midi_settings.rb +127 -0
  60. data/lib/musicality/performance/midi/midi_util.rb +8 -2
  61. data/lib/musicality/performance/midi/part_sequencer.rb +19 -18
  62. data/lib/musicality/performance/midi/score_sequencer.rb +13 -9
  63. data/lib/musicality/performance/midi/score_sequencing.rb +5 -5
  64. data/lib/musicality/performance/model/attack.rb +8 -0
  65. data/lib/musicality/performance/model/duration_functions.rb +23 -0
  66. data/lib/musicality/performance/model/note_sequence.rb +52 -95
  67. data/lib/musicality/performance/model/separation.rb +10 -0
  68. data/lib/musicality/performance/supercollider/add_actions.rb +13 -0
  69. data/lib/musicality/performance/supercollider/bundle.rb +18 -0
  70. data/lib/musicality/performance/supercollider/conductor.rb +125 -0
  71. data/lib/musicality/performance/supercollider/group.rb +71 -0
  72. data/lib/musicality/performance/supercollider/message.rb +26 -0
  73. data/lib/musicality/performance/supercollider/node.rb +122 -0
  74. data/lib/musicality/performance/supercollider/performer.rb +123 -0
  75. data/lib/musicality/performance/supercollider/score_conducting.rb +17 -0
  76. data/lib/musicality/performance/supercollider/server.rb +8 -0
  77. data/lib/musicality/performance/supercollider/synth.rb +43 -0
  78. data/lib/musicality/performance/supercollider/synthdef.rb +57 -0
  79. data/lib/musicality/performance/supercollider/synthdef_settings.rb +23 -0
  80. data/lib/musicality/performance/supercollider/synthdefs.rb +1654 -0
  81. data/lib/musicality/{composition/model/pitch_class.rb → pitch_class.rb} +1 -1
  82. data/lib/musicality/{composition/model/pitch_classes.rb → pitch_classes.rb} +9 -9
  83. data/lib/musicality/printing/lilypond/clef.rb +12 -0
  84. data/lib/musicality/printing/lilypond/key_engraving.rb +9 -0
  85. data/lib/musicality/printing/lilypond/lilypond_settings.rb +105 -0
  86. data/lib/musicality/printing/lilypond/meter_engraving.rb +1 -1
  87. data/lib/musicality/printing/lilypond/note_engraving.rb +112 -30
  88. data/lib/musicality/printing/lilypond/part_engraver.rb +114 -3
  89. data/lib/musicality/printing/lilypond/pitch_class_engraving.rb +22 -0
  90. data/lib/musicality/printing/lilypond/pitch_engraving.rb +2 -15
  91. data/lib/musicality/printing/lilypond/score_engraver.rb +44 -73
  92. data/lib/musicality/printing/lilypond/score_engraving.rb +3 -3
  93. data/lib/musicality/project/create_tasks.rb +31 -0
  94. data/lib/musicality/project/file_cleaner.rb +19 -0
  95. data/lib/musicality/project/file_raker.rb +107 -0
  96. data/lib/musicality/project/load_config.rb +43 -0
  97. data/lib/musicality/project/project.rb +64 -0
  98. data/lib/musicality/version.rb +1 -1
  99. data/musicality.gemspec +3 -0
  100. data/spec/composition/util/random_sampler_spec.rb +1 -1
  101. data/spec/notation/conversion/measure_note_map_spec.rb +1 -1
  102. data/spec/notation/conversion/note_time_converter_spec.rb +5 -85
  103. data/spec/notation/conversion/score_conversion_spec.rb +6 -41
  104. data/spec/notation/conversion/score_converter_spec.rb +19 -137
  105. data/spec/notation/model/change_spec.rb +55 -0
  106. data/spec/notation/model/key_spec.rb +171 -0
  107. data/spec/notation/model/link_spec.rb +34 -5
  108. data/spec/notation/model/meter_spec.rb +15 -0
  109. data/spec/notation/model/note_spec.rb +33 -27
  110. data/spec/notation/model/part_spec.rb +53 -4
  111. data/spec/notation/model/pitch_spec.rb +15 -0
  112. data/spec/notation/model/score_spec.rb +64 -72
  113. data/spec/notation/parsing/link_nodes_spec.rb +3 -3
  114. data/spec/notation/parsing/link_parsing_spec.rb +6 -6
  115. data/spec/notation/parsing/note_node_spec.rb +34 -9
  116. data/spec/notation/parsing/note_parsing_spec.rb +11 -9
  117. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +4 -0
  118. data/spec/notation/parsing/pitch_node_spec.rb +0 -1
  119. data/spec/notation/util/value_computer_spec.rb +2 -2
  120. data/spec/performance/conversion/glissando_converter_spec.rb +9 -9
  121. data/spec/performance/conversion/note_sequence_extractor_spec.rb +48 -53
  122. data/spec/performance/conversion/portamento_converter_spec.rb +11 -9
  123. data/spec/performance/conversion/score_collator_spec.rb +59 -63
  124. data/spec/performance/midi/midi_util_spec.rb +22 -8
  125. data/spec/performance/midi/part_sequencer_spec.rb +2 -2
  126. data/spec/performance/midi/score_sequencer_spec.rb +12 -10
  127. data/spec/performance/midi/score_sequencing_spec.rb +2 -2
  128. data/spec/performance/model/note_sequence_spec.rb +41 -134
  129. data/spec/printing/note_engraving_spec.rb +204 -0
  130. data/spec/printing/score_engraver_spec.rb +40 -0
  131. data/spec/spec_helper.rb +1 -0
  132. metadata +69 -23
  133. data/examples/notation/hip.rb +0 -32
  134. data/examples/notation/missed_connection.rb +0 -26
  135. data/examples/notation/song1.rb +0 -33
  136. data/examples/notation/song2.rb +0 -32
  137. data/lib/musicality/notation/model/links.rb +0 -11
  138. data/lib/musicality/notation/packing/change_packing.rb +0 -56
  139. data/lib/musicality/notation/packing/part_packing.rb +0 -31
  140. data/lib/musicality/notation/packing/score_packing.rb +0 -123
  141. data/lib/musicality/performance/model/note_attacks.rb +0 -19
  142. data/lib/musicality/performance/util/note_linker.rb +0 -28
  143. data/spec/notation/packing/change_packing_spec.rb +0 -304
  144. data/spec/notation/packing/part_packing_spec.rb +0 -66
  145. data/spec/notation/packing/score_packing_spec.rb +0 -255
  146. data/spec/performance/util/note_linker_spec.rb +0 -68
@@ -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