musicality 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +13 -3
  3. data/README.md +8 -8
  4. data/bin/auditions +241 -0
  5. data/bin/collidify +4 -2
  6. data/bin/musicality +13 -2
  7. data/examples/composition/auto_counterpoint.rb +4 -4
  8. data/examples/composition/part_generator.rb +6 -6
  9. data/examples/composition/scale_exercise.rb +5 -5
  10. data/examples/notation/scores.rb +2 -2
  11. data/examples/notation/twinkle.rb +6 -6
  12. data/examples/notation/twinkle.score +3 -3
  13. data/lib/musicality.rb +6 -4
  14. data/lib/musicality/composition/dsl/part_methods.rb +12 -0
  15. data/lib/musicality/composition/dsl/score_dsl.rb +4 -4
  16. data/lib/musicality/composition/dsl/score_methods.rb +8 -2
  17. data/lib/musicality/notation/model/audition.rb +16 -0
  18. data/lib/musicality/notation/model/score.rb +31 -30
  19. data/lib/musicality/performance/supercollider/conductor.rb +2 -2
  20. data/lib/musicality/performance/supercollider/synthdefs.rb +30 -29
  21. data/lib/musicality/project/auditions_task.rb +28 -0
  22. data/lib/musicality/project/create_tasks.rb +15 -9
  23. data/lib/musicality/project/file_cleaner.rb +22 -5
  24. data/lib/musicality/project/file_raker.rb +29 -21
  25. data/lib/musicality/project/project.rb +218 -32
  26. data/lib/musicality/version.rb +1 -1
  27. data/musicality.gemspec +6 -4
  28. data/spec/composition/dsl/part_methods_spec.rb +24 -0
  29. data/spec/notation/conversion/score_conversion_spec.rb +2 -1
  30. data/spec/notation/conversion/score_converter_spec.rb +23 -23
  31. data/spec/notation/model/score_spec.rb +66 -46
  32. data/spec/performance/conversion/score_collator_spec.rb +29 -29
  33. data/spec/performance/midi/score_sequencing_spec.rb +5 -5
  34. metadata +10 -8
  35. data/lib/musicality/project/load_config.rb +0 -58
@@ -0,0 +1,28 @@
1
+ module Musicality
2
+ module Tasks
3
+
4
+ class Auditions < Rake::TaskLib
5
+ attr_reader :auditions_dirs
6
+
7
+ TEMPO_SAMPLE_RATE = 200
8
+ AUDITIONS_DIR = "auditions"
9
+ AUDITIONS_EXT = ".auditions"
10
+
11
+ def initialize yaml_filelist, audio_format = nil
12
+ @auditions_dirs = yaml_filelist.pathmap("%d/#{AUDITIONS_DIR}")
13
+ @auditions_dirs.each { |auditions_dir| directory auditions_dir }
14
+
15
+ format_flag = audio_format.nil? ? "" : "--format=#{audio_format}"
16
+ subtask = audio_format.nil? ? "" : ":#{audio_format}"
17
+
18
+ task "auditions#{subtask}" => yaml_filelist + @auditions_dirs do
19
+ yaml_filelist.each_with_index do |yaml_fname,i|
20
+ auditions_dir = @auditions_dirs[i]
21
+ `auditions #{yaml_fname} --outdir="#{auditions_dir}" #{format_flag}`
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -1,31 +1,37 @@
1
1
  module Musicality
2
+ class Project
2
3
 
3
- class Project
4
4
  def self.create_tasks config
5
- score_files = Rake::FileList[config[:scores]]
6
-
5
+ score_files = Rake::FileList[File.join(SCORES_DIR,"*"+SCORE_EXT)]
6
+
7
7
  yaml_task = Tasks::FileRaker::YAML.new(score_files)
8
-
8
+
9
9
  tempo_sample_rate = config[:tempo_sample_rate]
10
10
  lilypond_task = Tasks::FileRaker::LilyPond.new(yaml_task.files)
11
11
  midi_task = Tasks::FileRaker::MIDI.new(yaml_task.files, tempo_sample_rate)
12
12
  supercollider_task = Tasks::FileRaker::SuperCollider.new(yaml_task.files, tempo_sample_rate)
13
-
13
+
14
14
  sample_rate, sample_format = config[:audio_sample_rate], config[:audio_sample_format]
15
15
  wav_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :wav, sample_rate, sample_format)
16
16
  aiff_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :aiff, sample_rate, sample_format)
17
17
  flac_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :flac, sample_rate, sample_format)
18
-
18
+
19
19
  pdf_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :pdf)
20
20
  png_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :png)
21
21
  ps_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :ps)
22
22
 
23
+ auditions_default_task = Tasks::Auditions.new yaml_task.files
24
+ auditions_flac_task = Tasks::Auditions.new yaml_task.files, "flac"
25
+ auditions_wav_task = Tasks::Auditions.new yaml_task.files, "wav"
26
+ auditions_aiff_task = Tasks::Auditions.new yaml_task.files, "aiff"
27
+
23
28
  outfiles = (
24
- yaml_task.files + lilypond_task.files + midi_task.files + supercollider_task.files +
25
- pdf_task.files + png_task.files + ps_task.files +
29
+ yaml_task.files + lilypond_task.files + midi_task.files + supercollider_task.files +
30
+ pdf_task.files + png_task.files + ps_task.files +
26
31
  wav_task.files + aiff_task.files + flac_task.files
27
32
  ).select {|fname| File.exists? fname }
28
- clean_task = Tasks::FileCleaner.new(outfiles)
33
+ outdirs = auditions_default_task.auditions_dirs + yaml_task.subdirs + [Project::OUT_DIR]
34
+ clean_task = Tasks::FileCleaner.new(outfiles, outdirs)
29
35
  end
30
36
  end
31
37
 
@@ -1,19 +1,36 @@
1
+ require 'fileutils'
2
+
1
3
  module Musicality
2
4
  module Tasks
3
5
 
4
6
  class FileCleaner < Rake::TaskLib
5
- def initialize outfiles
7
+ def initialize files, dirs
6
8
  task :clean do
7
- if outfiles.any?
8
- puts "Deleting output files:"
9
- outfiles.each do |fname|
9
+ if files.any?
10
+ puts "Deleting files:"
11
+ files.each do |fname|
10
12
  puts " " + fname
11
13
  File.delete fname
12
14
  end
13
15
  end
16
+
17
+ existing_dirs = dirs.select {|dir| Dir.exist?(dir) }
18
+
19
+ if existing_dirs.any?
20
+ puts "Deleting dirs:"
21
+ existing_dirs.each do |dirname|
22
+ puts " " + dirname
23
+ begin
24
+ FileUtils::rm Dir.glob(File.join(dirname, "*"))
25
+ FileUtils::rmdir dirname
26
+ rescue => e
27
+ puts "Error while trying to delete #{dirname}: #{e}"
28
+ end
29
+ end
30
+ end
14
31
  end
15
32
  end
16
33
  end
17
34
 
18
35
  end
19
- end
36
+ end
@@ -2,26 +2,34 @@ module Musicality
2
2
  module Tasks
3
3
 
4
4
  class FileRaker < Rake::TaskLib
5
- attr_reader :files, :task_name, :file_ext
6
-
5
+ attr_reader :files, :task_name, :file_ext, :subdirs
6
+
7
7
  def initialize parent_filelist, task_name, file_ext, &rule_block
8
8
  raise ArgumentError, "parent filelist is empty" if parent_filelist.empty?
9
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
-
10
+
16
11
  parent_exts = parent_filelist.map {|str| File.extname(str) }.uniq
17
12
  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|
13
+
14
+ @task_name, @file_ext = task_name, file_ext
15
+ @subdirs = parent_filelist.pathmap("#{Project::OUT_DIR}/%n")
16
+ @files = parent_filelist.pathmap("#{Project::OUT_DIR}/%n/%n#{file_ext}")
17
+
18
+ directory Project::OUT_DIR
19
+ @subdirs.each { |subdir| directory subdir }
20
+ task task_name => [Project::OUT_DIR] + @subdirs + @files
21
+
22
+ find_parent_file = lambda do |f|
23
+ parent_filelist.detect do |f2|
24
+ File.basename(f2.ext("")) == File.basename(f.ext(""))
25
+ end
26
+ end
27
+
28
+ rule file_ext => find_parent_file do |t|
21
29
  rule_block.call(t)
22
30
  end
23
31
  end
24
-
32
+
25
33
  class YAML < FileRaker
26
34
  def initialize score_files
27
35
  super(score_files, :yaml, ".yml") do |t|
@@ -31,7 +39,7 @@ class FileRaker < Rake::TaskLib
31
39
  end
32
40
  end
33
41
  end
34
-
42
+
35
43
  class LilyPond < FileRaker
36
44
  def initialize yaml_files
37
45
  super(yaml_files, :lilypond, ".ly") do |t|
@@ -39,7 +47,7 @@ class FileRaker < Rake::TaskLib
39
47
  end
40
48
  end
41
49
  end
42
-
50
+
43
51
  class MIDI < FileRaker
44
52
  def initialize yaml_files, tempo_sample_rate
45
53
  super(yaml_files, :midi, ".mid") do |t|
@@ -47,7 +55,7 @@ class FileRaker < Rake::TaskLib
47
55
  end
48
56
  end
49
57
  end
50
-
58
+
51
59
  class SuperCollider < FileRaker
52
60
  def initialize yaml_files, tempo_sample_rate
53
61
  super(yaml_files, :supercollider, ".osc") do |t|
@@ -55,7 +63,7 @@ class FileRaker < Rake::TaskLib
55
63
  end
56
64
  end
57
65
  end
58
-
66
+
59
67
  class Audio < FileRaker
60
68
  def check_sample_format audio_file_type, sample_format
61
69
  combination_okay = case audio_file_type
@@ -64,7 +72,7 @@ class FileRaker < Rake::TaskLib
64
72
  when :flac
65
73
  !["int32","float","mulaw","alaw"].include?(sample_format)
66
74
  else
67
- true
75
+ true
68
76
  end
69
77
 
70
78
  unless combination_okay
@@ -78,7 +86,7 @@ class FileRaker < Rake::TaskLib
78
86
 
79
87
  osc_fpath = t.sources[0]
80
88
  out_fpath = File.join(File.dirname(osc_fpath), File.basename(osc_fpath, File.extname(osc_fpath)) + ".#{audio_file_type}")
81
-
89
+
82
90
  cmd_line = "scsynth -N \"#{osc_fpath}\" _ \"#{out_fpath}\" #{sample_rate} #{audio_file_type} #{sample_format}"
83
91
  IO.popen(cmd_line) do |pipe|
84
92
  while response = pipe.gets
@@ -93,13 +101,13 @@ class FileRaker < Rake::TaskLib
93
101
  end
94
102
  end
95
103
  end
96
-
104
+
97
105
  class Visual < FileRaker
98
106
  def initialize lilypond_files, visual_file_type
99
107
  super(lilypond_files, visual_file_type, ".#{visual_file_type}") do |t|
100
108
  ly_fpath = t.sources[0]
101
109
  out_dir = File.dirname(ly_fpath)
102
-
110
+
103
111
  sh "lilypond --output=\"#{out_dir}\" \"#{ly_fpath}\" --#{visual_file_type}"
104
112
  end
105
113
  end
@@ -107,4 +115,4 @@ class FileRaker < Rake::TaskLib
107
115
  end
108
116
 
109
117
  end
110
- end
118
+ end
@@ -1,25 +1,71 @@
1
1
  module Musicality
2
2
 
3
3
  class Project
4
+ CONFIG_FILE_NAME = "config.yml"
5
+ SCORES_DIR = "scores"
6
+ SCORE_EXT = ".score"
7
+ OUT_DIR = "output"
8
+ SAMPLE_FORMATS = ["int8", "int16", "int24", "int32", "mulaw", "alaw", "float"]
9
+ DEFAULT_CONFIG = {
10
+ :tempo_sample_rate => 200,
11
+ :audio_sample_rate => 44100,
12
+ :audio_sample_format => "int16"
13
+ }
14
+ GEM_MUSICALITY = "gem 'musicality', '~> #{VERSION}'"
4
15
  USEFUL_MODULES = ['Musicality','Pitches','Meters','Keys','Articulations','Dynamics']
5
16
 
6
- attr_reader :dest_dir
17
+ class ConfigError < RuntimeError
18
+ end
19
+
7
20
  def initialize dest_dir
8
- @dest_dir = dest_dir
21
+ Project.create_project_dir_if_needed(dest_dir)
22
+ Project.create_scores_dir_if_needed(dest_dir)
23
+ Project.update(dest_dir)
24
+ end
25
+
26
+ def self.update dest_dir
27
+ if File.exists?(gemfile_path(dest_dir))
28
+ puts "Updating Gemfile"
29
+ update_gemfile(dest_dir)
30
+ else
31
+ puts "Creating Gemfile"
32
+ create_gemfile(dest_dir)
33
+ end
34
+
35
+ if File.exists?(rakefile_path(dest_dir))
36
+ puts "Updating Rakefile"
37
+ update_rakefile(dest_dir)
38
+ else
39
+ puts "Creating Rakefile"
40
+ create_rakefile(dest_dir)
41
+ end
9
42
 
10
- create_project_dir
11
- create_gemfile
12
- create_rakefile
13
- create_config
14
- create_scores_dir
43
+ if File.exists?(config_path(dest_dir))
44
+ puts "Updating config.yml"
45
+ update_config(dest_dir)
46
+ else
47
+ puts "Creating config.yml"
48
+ create_config(dest_dir)
49
+ end
15
50
  end
16
51
 
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
52
+ def self.config_path(dest_dir)
53
+ File.join(dest_dir,"config.yml")
54
+ end
55
+
56
+ def self.gemfile_path(dest_dir)
57
+ File.join(dest_dir,"Gemfile")
58
+ end
59
+
60
+ def self.rakefile_path(dest_dir)
61
+ File.join(dest_dir,"Rakefile")
62
+ end
63
+
64
+ def self.create_project_dir_if_needed(dest_dir)
65
+ if Dir.exists?(dest_dir)
66
+ puts "Project directory already exists"
22
67
  else
68
+ puts "Creating project directory #{dest_dir}"
23
69
  Dir.mkdir(dest_dir)
24
70
  unless Dir.exists?(dest_dir)
25
71
  raise "directory #{dest_dir} could not be created"
@@ -27,38 +73,178 @@ class Project
27
73
  end
28
74
  end
29
75
 
30
- def create_gemfile
76
+ def self.create_scores_dir_if_needed(dest_dir, scores_dir = Project::SCORES_DIR)
77
+ scores_dir = File.join(dest_dir, scores_dir)
78
+ if Dir.exists? scores_dir
79
+ puts "Scores directory already exists"
80
+ else
81
+ puts "Creating scores directory #{scores_dir}"
82
+ Dir.mkdir(scores_dir)
83
+ unless Dir.exists?(scores_dir)
84
+ raise "directory #{scores_dir} could not be created"
85
+ end
86
+ end
87
+ end
88
+
89
+ #
90
+ # Gemfile
91
+ #
92
+
93
+ def self.create_gemfile(dest_dir)
31
94
  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}'")
95
+ File.new(gemfile_path(dest_dir),"w")
96
+ update_gemfile(dest_dir)
97
+ end
98
+
99
+ def self.update_gemfile(dest_dir)
100
+ pre_lines = []
101
+ lines = File.readlines(gemfile_path(dest_dir)).map {|l| l.chomp }
102
+
103
+ if line = lines.find {|x| x =~ /source/ }
104
+ delete_empty_lines_around lines, line
105
+ pre_lines.push lines.delete(line)
106
+ else
107
+ pre_lines.push("source :rubygems")
108
+ end
109
+
110
+ if line = lines.find {|x| x =~ /gem/ && x =~ /musicality/ }
111
+ delete_empty_lines_around lines, line
112
+ lines.delete(line)
113
+ end
114
+ pre_lines.push GEM_MUSICALITY
115
+
116
+ File.open(gemfile_path(dest_dir),"w") do |f|
117
+ f.puts pre_lines
118
+ if lines.any?
119
+ f.puts [""] + lines
120
+ end
35
121
  end
36
122
  end
37
123
 
38
- def create_rakefile
124
+ #
125
+ # Rakefile
126
+ #
127
+
128
+ def self.create_rakefile(dest_dir)
39
129
  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}")
130
+ File.new(rakefile_path(dest_dir),"w")
131
+ update_rakefile(dest_dir)
132
+ end
133
+
134
+ def self.update_rakefile(dest_dir)
135
+ pre_lines = []
136
+ lines = File.readlines(rakefile_path(dest_dir)).map {|l| l.chomp }
137
+
138
+ if line = lines.find {|x| x =~ /^[\s]*require[\s]+[\'\"]musicality[\'\"]/}
139
+ delete_empty_lines_around lines, line
140
+ pre_lines.push lines.delete(line)
141
+ else
142
+ pre_lines.push "require 'musicality'"
143
+ end
144
+ pre_lines.push ""
145
+
146
+ USEFUL_MODULES.each do |module_name|
147
+ if line = lines.find {|x| x =~ /^[\s]*include[\s]+#{module_name}/}
148
+ delete_empty_lines_around lines, line
149
+ pre_lines.push lines.delete(line)
150
+ else
151
+ pre_lines.push "include #{module_name}"
152
+ end
153
+ end
154
+
155
+ pre_lines.push ""
156
+ if line = lines.find {|x| x =~ /Project\.load_config/ }
157
+ delete_empty_lines_around lines, line
158
+ pre_lines.push lines.delete(line)
159
+ else
160
+ pre_lines.push "config = Project.load_config(File.dirname(__FILE__))"
161
+ end
162
+
163
+ if line = lines.find {|x| x =~ /Project\.create_tasks/ }
164
+ delete_empty_lines_around lines, line
165
+ pre_lines.push lines.delete(line)
166
+ else
167
+ pre_lines.push "Project.create_tasks(config)"
168
+ end
169
+
170
+ File.open(rakefile_path(dest_dir),"w") do |f|
171
+ f.puts pre_lines
172
+ if lines.any?
173
+ f.puts [""] + lines
44
174
  end
45
- f.puts
46
- f.puts("config = Project.load_config(File.dirname(__FILE__))")
47
- f.puts("Project.create_tasks(config)")
48
175
  end
49
176
  end
50
177
 
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)
178
+ #
179
+ # config.yml
180
+ #
181
+
182
+ def self.check_config config
183
+ config.each do |k,v|
184
+ case k
185
+ when :audio_sample_format
186
+ raise ConfigError, "#{k} => #{v} is not allowed" unless SAMPLE_FORMATS.include?(v)
187
+ when :tempo_sample_rate, :audio_sample_rate
188
+ raise ConfigError, "#{k} => #{v} is not positive" unless v > 0
189
+ end
55
190
  end
56
191
  end
57
192
 
58
- def create_scores_dir
59
- scores_dir = File.join(dest_dir, Project::BASE_SCORES_DIR)
60
- Dir.mkdir(scores_dir)
193
+ def self.load_config project_root_dir
194
+ globabl_config_path = File.join(project_root_dir,CONFIG_FILE_NAME)
195
+
196
+ config = if File.exists? globabl_config_path
197
+ global_config = YAML.load(File.read(globabl_config_path))
198
+ DEFAULT_CONFIG.merge global_config
199
+ else
200
+ DEFAULT_CONFIG
201
+ end
202
+
203
+ # overrides from ENV
204
+ config.keys.each do |k|
205
+ k_str = k.to_s
206
+ if ENV.has_key? k_str
207
+ case k
208
+ when :tempo_sample_rate, :audio_sample_rate
209
+ config[k] = ENV[k_str].to_i
210
+ else
211
+ config[k] = ENV[k_str]
212
+ end
213
+ end
214
+ end
215
+
216
+ check_config config
217
+ return config
218
+ end
219
+
220
+ def self.create_config(dest_dir, config = Project::DEFAULT_CONFIG)
221
+ File.open(config_path(dest_dir),"w") do |f|
222
+ f.write(config.to_yaml)
223
+ end
224
+ end
225
+
226
+ def self.update_config(dest_dir)
227
+ config = Project.load_config(dest_dir)
228
+ config = Project::DEFAULT_CONFIG.merge(config)
229
+ create_config(dest_dir, config)
230
+ end
231
+
232
+ private
233
+
234
+ def self.delete_empty_lines_around lines, line
235
+ # delete lines before
236
+ i = lines.index(line)-1
237
+ while (i >= 0) && lines[i].empty?
238
+ lines.delete_at i
239
+ i -= 1
240
+ end
241
+
242
+ # delete lines after
243
+ i = lines.index(line)+1
244
+ while (i < lines.size) && lines[i].empty?
245
+ lines.delete_at i
246
+ end
61
247
  end
62
248
  end
63
249
 
64
- end
250
+ end