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
@@ -0,0 +1,22 @@
1
+ module Musicality
2
+
3
+ class PitchClass
4
+ def self.to_lilypond pc, sharpit = false
5
+ case pc
6
+ when 0 then "c"
7
+ when 1 then sharpit ? "cis" : "des"
8
+ when 2 then "d"
9
+ when 3 then sharpit ? "dis" : "ees"
10
+ when 4 then "e"
11
+ when 5 then "f"
12
+ when 6 then sharpit ? "fis" : "ges"
13
+ when 7 then "g"
14
+ when 8 then sharpit ? "gis" : "aes"
15
+ when 9 then "a"
16
+ when 10 then sharpit ? "ais" : "bes"
17
+ when 11 then "b"
18
+ end
19
+ end
20
+ end
21
+
22
+ end
@@ -2,21 +2,8 @@ module Musicality
2
2
 
3
3
  class Pitch
4
4
  def to_lilypond sharpit = false
5
- output = case semitone
6
- when 0 then "c"
7
- when 1 then sharpit ? "cis" : "des"
8
- when 2 then "d"
9
- when 3 then sharpit ? "dis" : "ees"
10
- when 4 then "e"
11
- when 5 then "f"
12
- when 6 then sharpit ? "fis" : "ges"
13
- when 7 then "g"
14
- when 8 then sharpit ? "gis" : "aes"
15
- when 9 then "a"
16
- when 10 then sharpit ? "ais" : "bes"
17
- when 11 then "b"
18
- end
19
-
5
+ output = PitchClass.to_lilypond(semitone, sharpit)
6
+
20
7
  if octave > 3
21
8
  output += "'"*(octave - 3)
22
9
  elsif octave < 3
@@ -2,104 +2,75 @@ module Musicality
2
2
 
3
3
  class ScoreEngraver
4
4
  LILYPOND_VERSION = "2.18.2"
5
- MAX_LINE_LEN = 76
6
5
 
7
6
  def initialize score
8
- case score
9
- when Score::Measured
10
- @start_meter = score.start_meter
11
- when Score::Unmeasured
12
- @start_meter = Meters::FOUR_FOUR
13
- else
14
- raise TypeError, "Only tempo-based score support Lilypond conversion"
15
- end
7
+ @start_meter = score.start_meter
8
+ @meter_changes = score.meter_changes
9
+ @start_key = score.start_key
10
+ @key_changes = score.key_changes
16
11
 
17
12
  @parts = score.collated? ? score.parts : ScoreCollator.new(score).collate_parts
18
- @title = score.title
19
- @composer = score.composer
13
+ @parts.each do |part_name, part|
14
+ unless part.lilypond_settings
15
+ part.settings.push LilypondSettings.new(part_name)
16
+ end
17
+ end
18
+
19
+ @header = ScoreEngraver.header score.title, score.composer
20
+ @part_titles = ScoreEngraver.figure_part_titles @parts
21
+ end
22
+
23
+ def self.figure_part_titles parts
24
+ instr_name_totals = Hash.new(0)
25
+ instr_name_used = Hash.new(0)
26
+ part_titles = Hash[
27
+ parts.map do |part_name, part|
28
+ instr_name = part.lilypond_settings.instrument_name
29
+ instr_name_totals[instr_name] += 1
30
+ [part_name, instr_name]
31
+ end
32
+ ]
33
+
34
+ needs_number = parts.keys.select {|part_name| instr_name_totals[part_titles[part_name]] > 1 }
35
+ needs_number.each do |part_name|
36
+ title = part_titles[part_name]
37
+ part_titles[part_name] = "#{title} #{instr_name_used[title] += 1}"
38
+ end
39
+
40
+ return part_titles
20
41
  end
21
42
 
22
43
  # Generate a Lilypond header for the score
23
- def header
44
+ def self.header title, composer
24
45
  output = "\\version \"#{LILYPOND_VERSION}\"\n"
25
46
  output += "\\header {\n"
26
- if @title
27
- output += " title = \"#{@title}\"\n"
47
+ if title
48
+ output += " title = \"#{title}\"\n"
28
49
  end
29
- if @composer
30
- output += " composer = \"#{@composer}\"\n"
50
+ if composer
51
+ output += " composer = \"#{composer}\"\n"
31
52
  end
32
53
  output += "}\n"
33
54
 
34
55
  return output
35
56
  end
36
57
 
37
- # Generate a Lilypond staff for the given part
38
- def staff part, part_title, master: false
39
- clef = ScoreEngraver.best_clef(part.notes)
40
- output = " \\new Staff {\n"
41
- output += " \\set Staff.instrumentName = \\markup { \"#{part_title}\" }\n"
42
- output += " \\clef #{clef}\n"
43
- if(master)
44
- output += " \\time #{@start_meter.to_lilypond}\n"
45
- end
46
-
47
- line = " "
48
- part.notes.each_index do |i|
49
- note = part.notes[i]
50
- begin
51
- str = note.to_lilypond + " "
52
- rescue UnsupportedDurationError => e
53
- binding.pry
54
- end
55
-
56
- if (line.size + str.size) > MAX_LINE_LEN
57
- output += line + "\n"
58
- line = " "
59
- end
60
- line += str
61
- end
62
- output += line + "\n"
63
-
64
- output += " }\n"
65
- end
66
-
67
- # @param [Hash] part_names A hash for titling parts differently than their names
68
- # @param [Array] selected_parts The names of parts selected for engraving
69
- def make_lilypond selected_parts: @parts.keys, part_titles: {}
70
- (selected_parts - part_titles.keys).each do |part_name|
71
- part_titles[part_name] = part_name
72
- end
73
- output = header
58
+ def make_lilypond selected_parts = @parts.keys
59
+ output = @header
74
60
  output += "{\n <<\n"
75
61
  master = true
76
62
  selected_parts.each do |part_name|
77
63
  part = @parts[part_name]
78
- part_title = part_titles[part_name]
79
- output += staff part, part_title, master: master
64
+ part_title = @part_titles[part_name]
65
+ pe = PartEngraver.new(part, part_title)
66
+ output += pe.make_lilypond(@start_key, @start_meter,
67
+ key_changes: @key_changes, meter_changes: @meter_changes,
68
+ master: master)
80
69
  master = false if master
81
70
  end
82
71
  output += " >>\n}\n"
83
72
  return output
84
73
  end
85
-
86
- def self.best_clef notes
87
- ranges = { "treble" => Pitches::C4..Pitches::A5,
88
- "bass" => Pitches::E2..Pitches::C4,
89
- "tenor" => Pitches::B2..Pitches::G4 }
90
- range_scores = { "treble" => 0, "bass" => 0, "tenor" => 0 }
91
- notes.each do |n|
92
- n.pitches.each do |p|
93
- ranges.each do |name,range|
94
- if p >= range.min && p <= range.max
95
- range_scores[name] += n.duration
96
- end
97
- end
98
- end
99
- end
100
- range_score = range_scores.max_by {|range,score| score}
101
- return range_score[0]
102
- end
103
74
  end
104
75
 
105
76
  end
@@ -1,10 +1,10 @@
1
1
  module Musicality
2
2
 
3
3
  class Score
4
- class TempoBased < Score
4
+ class Tempo < Score
5
5
  # See ScoreEngraver#make_lilypond for details on supported keyword args
6
- def to_lilypond **kwargs
7
- ScoreEngraver.new(self).make_lilypond(**kwargs)
6
+ def to_lilypond selected_parts = @parts.keys
7
+ ScoreEngraver.new(self).make_lilypond(selected_parts)
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,31 @@
1
+ module Musicality
2
+
3
+ class Project
4
+ def self.create_tasks config
5
+ score_files = Rake::FileList[File.join(config[:scores_dir],"**/*.score")]
6
+
7
+ yaml_task = Tasks::FileRaker::YAML.new(score_files)
8
+
9
+ tempo_sample_rate = config[:tempo_sample_rate]
10
+ lilypond_task = Tasks::FileRaker::LilyPond.new(yaml_task.files)
11
+ midi_task = Tasks::FileRaker::MIDI.new(yaml_task.files, tempo_sample_rate)
12
+ supercollider_task = Tasks::FileRaker::SuperCollider.new(yaml_task.files, tempo_sample_rate)
13
+
14
+ sample_rate, sample_format = config[:audio_sample_rate], config[:audio_sample_format]
15
+ wav_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :wav, sample_rate, sample_format)
16
+ aiff_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :aiff, sample_rate, sample_format)
17
+ flac_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :flac, sample_rate, sample_format)
18
+
19
+ pdf_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :pdf)
20
+ png_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :png)
21
+ ps_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :ps)
22
+
23
+ outfiles = (
24
+ yaml_task.files + midi_task.files + supercollider_task.files + wav_task.files +
25
+ aiff_task.files + flac_task.files + pdf_task.files + png_task.files + ps_task.files
26
+ ).select {|fname| File.exists? fname }
27
+ clean_task = Tasks::FileCleaner.new(outfiles)
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,19 @@
1
+ module Musicality
2
+ module Tasks
3
+
4
+ class FileCleaner < Rake::TaskLib
5
+ def initialize outfiles
6
+ task :clean do
7
+ if outfiles.any?
8
+ puts "Deleting output files:"
9
+ outfiles.each do |fname|
10
+ puts " " + fname
11
+ File.delete fname
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,107 @@
1
+ module Musicality
2
+ module Tasks
3
+
4
+ class FileRaker < Rake::TaskLib
5
+ attr_reader :files, :task_name, :file_ext
6
+
7
+ def initialize parent_filelist, task_name, file_ext, &rule_block
8
+ raise ArgumentError, "parent filelist is empty" if parent_filelist.empty?
9
+ raise ArgumentError, "no rule block given" unless block_given?
10
+
11
+ @task_name, @file_ext = task_name, file_ext
12
+ @files = parent_filelist.ext(file_ext)
13
+
14
+ task task_name => @files
15
+
16
+ parent_exts = parent_filelist.map {|str| File.extname(str) }.uniq
17
+ raise ArgumentError, "multiple file extensions in parent filelist: #{parent_filelist}" unless parent_exts.one?
18
+ parent_ext = parent_exts.first
19
+
20
+ rule file_ext => parent_ext do |t|
21
+ rule_block.call(t)
22
+ end
23
+ end
24
+
25
+ class YAML < FileRaker
26
+ def initialize score_files
27
+ super(score_files, :yaml, ".yml") do |t|
28
+ puts "#{t.source} -> #{t.name}"
29
+ yml = ScoreDSL.load(t.source).score.pack.to_yaml
30
+ File.write(t.name, yml)
31
+ end
32
+ end
33
+ end
34
+
35
+ class LilyPond < FileRaker
36
+ def initialize yaml_files
37
+ super(yaml_files, :lilypond, ".ly") do |t|
38
+ sh "lilify \"#{t.sources[0]}\""
39
+ end
40
+ end
41
+ end
42
+
43
+ class MIDI < FileRaker
44
+ def initialize yaml_files, tempo_sample_rate
45
+ super(yaml_files, :midi, ".mid") do |t|
46
+ sh "midify \"#{t.sources[0]}\" --srate=#{tempo_sample_rate}"
47
+ end
48
+ end
49
+ end
50
+
51
+ class SuperCollider < FileRaker
52
+ def initialize yaml_files, tempo_sample_rate
53
+ super(yaml_files, :supercollider, ".osc") do |t|
54
+ sh "collidify \"#{t.sources[0]}\" --srate=#{tempo_sample_rate}"
55
+ end
56
+ end
57
+ end
58
+
59
+ class Audio < FileRaker
60
+ def check_sample_format audio_file_type, sample_format
61
+ combination_okay = case audio_file_type
62
+ when :wav
63
+ sample_format != "int8"
64
+ when :flac
65
+ !["int32","float","mulaw","alaw"].include?(sample_format)
66
+ else
67
+ true
68
+ end
69
+
70
+ unless combination_okay
71
+ raise ConfigError, "#{audio_file_type} file format can not be used with #{sample_format} sample format"
72
+ end
73
+ end
74
+
75
+ def initialize osc_files, audio_file_type, sample_rate, sample_format
76
+ super(osc_files, audio_file_type, ".#{audio_file_type}") do |t|
77
+ check_sample_format audio_file_type, sample_format
78
+
79
+ osc_fpath = t.sources[0]
80
+ out_fpath = File.join(File.dirname(osc_fpath), File.basename(osc_fpath, File.extname(osc_fpath)) + ".#{audio_file_type}")
81
+
82
+ cmd_line = "scsynth -N \"#{osc_fpath}\" _ \"#{out_fpath}\" #{sample_rate} #{audio_file_type} #{sample_format}"
83
+ IO.popen(cmd_line) do |pipe|
84
+ while response = pipe.gets
85
+ puts response
86
+ if /Couldn't open non real time command file/ =~ response
87
+ puts "The #{sample_format} sample format is not compatible with file format"
88
+ File.delete(out_fpath)
89
+ break
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ class Visual < FileRaker
98
+ def initialize lilypond_files, visual_file_type
99
+ super(lilypond_files, visual_file_type, ".#{visual_file_type}") do |t|
100
+ sh "lilypond #{t.sources[0]} --#{visual_file_type}"
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,43 @@
1
+ module Musicality
2
+
3
+ class Project
4
+ CONFIG_FILE_NAME = "config.yml"
5
+
6
+ DEFAULT_CONFIG = {
7
+ :scores_dir => "scores",
8
+ :tempo_sample_rate => 200,
9
+ :audio_sample_rate => 44100,
10
+ :audio_sample_format => "int16"
11
+ }
12
+
13
+ SAMPLE_FORMATS = ["int8", "int16", "int24", "int32", "mulaw", "alaw", "float"]
14
+
15
+ class ConfigError < RuntimeError
16
+ end
17
+
18
+ def self.check_config config
19
+ config.each do |k,v|
20
+ case k
21
+ when :audio_sample_format
22
+ raise ConfigError, "#{k} => #{v} is not allowed" unless SAMPLE_FORMATS.include?(v)
23
+ when :tempo_sample_rate, :audio_sample_rate
24
+ raise ConfigError, "#{k} => #{v} is not positive" unless v > 0
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.load_config project_root_dir
30
+ globabl_config_path = File.join(project_root_dir,CONFIG_FILE_NAME)
31
+
32
+ config = if File.exists? globabl_config_path
33
+ global_config = YAML.load(File.read(globabl_config_path))
34
+ DEFAULT_CONFIG.merge global_config
35
+ else
36
+ DEFAULT_CONFIG
37
+ end
38
+ check_config config
39
+ return config
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,64 @@
1
+ module Musicality
2
+
3
+ class Project
4
+ USEFUL_MODULES = ['Musicality','Pitches','Meters','Keys','Articulations','Dynamics']
5
+
6
+ attr_reader :dest_dir
7
+ def initialize dest_dir
8
+ @dest_dir = dest_dir
9
+
10
+ create_project_dir
11
+ create_gemfile
12
+ create_rakefile
13
+ create_config
14
+ create_scores_dir
15
+ end
16
+
17
+ def create_project_dir
18
+ if Dir.exists? dest_dir
19
+ unless Dir.glob(File.join(dest_dir,"*")).empty?
20
+ raise ArgumentError, "existing directory #{dest_dir} is not empty."
21
+ end
22
+ else
23
+ Dir.mkdir(dest_dir)
24
+ unless Dir.exists?(dest_dir)
25
+ raise "directory #{dest_dir} could not be created"
26
+ end
27
+ end
28
+ end
29
+
30
+ def create_gemfile
31
+ gemfile_path = File.join(dest_dir,"Gemfile")
32
+ File.open(gemfile_path,"w") do |f|
33
+ f.puts("source :rubygems")
34
+ f.puts("gem 'musicality', '~> #{VERSION}'")
35
+ end
36
+ end
37
+
38
+ def create_rakefile
39
+ rakefile_path = File.join(dest_dir,"Rakefile")
40
+ File.open(rakefile_path,"w") do |f|
41
+ f.puts("require 'musicality'")
42
+ USEFUL_MODULES.each do |module_name|
43
+ f.puts("include #{module_name}")
44
+ end
45
+ f.puts
46
+ f.puts("config = Project.load_config(File.dirname(__FILE__))")
47
+ f.puts("Project.create_tasks(config)")
48
+ end
49
+ end
50
+
51
+ def create_config
52
+ config_path = File.join(dest_dir, Project::CONFIG_FILE_NAME)
53
+ File.open(config_path,"w") do |f|
54
+ f.write(Project::DEFAULT_CONFIG.to_yaml)
55
+ end
56
+ end
57
+
58
+ def create_scores_dir
59
+ scores_dir = File.join(dest_dir, Project::DEFAULT_CONFIG[:scores_dir])
60
+ Dir.mkdir(scores_dir)
61
+ end
62
+ end
63
+
64
+ end