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