musicality 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +27 -1
- data/README.md +153 -10
- data/bin/collidify +102 -0
- data/bin/lilify +57 -29
- data/bin/midify +64 -24
- data/bin/musicality +39 -0
- data/examples/composition/auto_counterpoint.rb +4 -5
- data/examples/composition/part_generator.rb +8 -2
- data/examples/composition/scale_exercise.rb +1 -1
- data/examples/notation/notes.rb +27 -0
- data/examples/notation/parts.rb +51 -0
- data/examples/notation/scores.rb +38 -0
- data/examples/notation/twinkle.rb +34 -0
- data/examples/notation/twinkle.score +33 -0
- data/lib/musicality.rb +46 -11
- data/lib/musicality/composition/dsl/score_dsl.rb +2 -2
- data/lib/musicality/composition/dsl/score_methods.rb +10 -7
- data/lib/musicality/notation/conversion/change_conversion.rb +1 -1
- data/lib/musicality/notation/conversion/note_time_converter.rb +6 -23
- data/lib/musicality/notation/conversion/score_conversion.rb +15 -15
- data/lib/musicality/notation/conversion/score_converter.rb +50 -67
- data/lib/musicality/notation/model/articulations.rb +3 -2
- data/lib/musicality/notation/model/change.rb +15 -6
- data/lib/musicality/notation/model/dynamics.rb +11 -8
- data/lib/musicality/notation/model/instrument.rb +61 -0
- data/lib/musicality/notation/model/instruments.rb +111 -0
- data/lib/musicality/notation/model/key.rb +137 -0
- data/lib/musicality/notation/model/keys.rb +37 -0
- data/lib/musicality/notation/model/link.rb +6 -19
- data/lib/musicality/notation/model/mark.rb +43 -0
- data/lib/musicality/notation/model/marks.rb +11 -0
- data/lib/musicality/notation/model/meter.rb +4 -0
- data/lib/musicality/notation/model/note.rb +42 -28
- data/lib/musicality/notation/model/part.rb +18 -5
- data/lib/musicality/notation/model/pitch.rb +13 -4
- data/lib/musicality/notation/model/score.rb +104 -66
- data/lib/musicality/notation/model/symbols.rb +16 -11
- data/lib/musicality/notation/parsing/articulation_parsing.rb +38 -38
- data/lib/musicality/notation/parsing/articulation_parsing.treetop +14 -14
- data/lib/musicality/notation/parsing/link_parsing.rb +6 -6
- data/lib/musicality/notation/parsing/link_parsing.treetop +3 -3
- data/lib/musicality/notation/parsing/mark_parsing.rb +138 -0
- data/lib/musicality/notation/parsing/mark_parsing.treetop +31 -0
- data/lib/musicality/notation/parsing/note_node.rb +19 -12
- data/lib/musicality/notation/parsing/note_parsing.rb +218 -87
- data/lib/musicality/notation/parsing/note_parsing.treetop +9 -5
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +7 -2
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +1 -1
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +6 -4
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +1 -1
- data/lib/musicality/notation/util/function.rb +41 -18
- data/lib/musicality/packable.rb +156 -0
- data/lib/musicality/performance/conversion/glissando_converter.rb +2 -2
- data/lib/musicality/performance/conversion/note_sequence_extractor.rb +223 -70
- data/lib/musicality/performance/conversion/portamento_converter.rb +5 -2
- data/lib/musicality/performance/conversion/score_collator.rb +70 -64
- data/lib/musicality/performance/midi/midi_events.rb +3 -3
- data/lib/musicality/performance/midi/midi_settings.rb +127 -0
- data/lib/musicality/performance/midi/midi_util.rb +8 -2
- data/lib/musicality/performance/midi/part_sequencer.rb +19 -18
- data/lib/musicality/performance/midi/score_sequencer.rb +13 -9
- data/lib/musicality/performance/midi/score_sequencing.rb +5 -5
- data/lib/musicality/performance/model/attack.rb +8 -0
- data/lib/musicality/performance/model/duration_functions.rb +23 -0
- data/lib/musicality/performance/model/note_sequence.rb +52 -95
- data/lib/musicality/performance/model/separation.rb +10 -0
- data/lib/musicality/performance/supercollider/add_actions.rb +13 -0
- data/lib/musicality/performance/supercollider/bundle.rb +18 -0
- data/lib/musicality/performance/supercollider/conductor.rb +125 -0
- data/lib/musicality/performance/supercollider/group.rb +71 -0
- data/lib/musicality/performance/supercollider/message.rb +26 -0
- data/lib/musicality/performance/supercollider/node.rb +122 -0
- data/lib/musicality/performance/supercollider/performer.rb +123 -0
- data/lib/musicality/performance/supercollider/score_conducting.rb +17 -0
- data/lib/musicality/performance/supercollider/server.rb +8 -0
- data/lib/musicality/performance/supercollider/synth.rb +43 -0
- data/lib/musicality/performance/supercollider/synthdef.rb +57 -0
- data/lib/musicality/performance/supercollider/synthdef_settings.rb +23 -0
- data/lib/musicality/performance/supercollider/synthdefs.rb +1654 -0
- data/lib/musicality/{composition/model/pitch_class.rb → pitch_class.rb} +1 -1
- data/lib/musicality/{composition/model/pitch_classes.rb → pitch_classes.rb} +9 -9
- data/lib/musicality/printing/lilypond/clef.rb +12 -0
- data/lib/musicality/printing/lilypond/key_engraving.rb +9 -0
- data/lib/musicality/printing/lilypond/lilypond_settings.rb +105 -0
- data/lib/musicality/printing/lilypond/meter_engraving.rb +1 -1
- data/lib/musicality/printing/lilypond/note_engraving.rb +112 -30
- data/lib/musicality/printing/lilypond/part_engraver.rb +114 -3
- data/lib/musicality/printing/lilypond/pitch_class_engraving.rb +22 -0
- data/lib/musicality/printing/lilypond/pitch_engraving.rb +2 -15
- data/lib/musicality/printing/lilypond/score_engraver.rb +44 -73
- data/lib/musicality/printing/lilypond/score_engraving.rb +3 -3
- data/lib/musicality/project/create_tasks.rb +31 -0
- data/lib/musicality/project/file_cleaner.rb +19 -0
- data/lib/musicality/project/file_raker.rb +107 -0
- data/lib/musicality/project/load_config.rb +43 -0
- data/lib/musicality/project/project.rb +64 -0
- data/lib/musicality/version.rb +1 -1
- data/musicality.gemspec +3 -0
- data/spec/composition/util/random_sampler_spec.rb +1 -1
- data/spec/notation/conversion/measure_note_map_spec.rb +1 -1
- data/spec/notation/conversion/note_time_converter_spec.rb +5 -85
- data/spec/notation/conversion/score_conversion_spec.rb +6 -41
- data/spec/notation/conversion/score_converter_spec.rb +19 -137
- data/spec/notation/model/change_spec.rb +55 -0
- data/spec/notation/model/key_spec.rb +171 -0
- data/spec/notation/model/link_spec.rb +34 -5
- data/spec/notation/model/meter_spec.rb +15 -0
- data/spec/notation/model/note_spec.rb +33 -27
- data/spec/notation/model/part_spec.rb +53 -4
- data/spec/notation/model/pitch_spec.rb +15 -0
- data/spec/notation/model/score_spec.rb +64 -72
- data/spec/notation/parsing/link_nodes_spec.rb +3 -3
- data/spec/notation/parsing/link_parsing_spec.rb +6 -6
- data/spec/notation/parsing/note_node_spec.rb +34 -9
- data/spec/notation/parsing/note_parsing_spec.rb +11 -9
- data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +4 -0
- data/spec/notation/parsing/pitch_node_spec.rb +0 -1
- data/spec/notation/util/value_computer_spec.rb +2 -2
- data/spec/performance/conversion/glissando_converter_spec.rb +9 -9
- data/spec/performance/conversion/note_sequence_extractor_spec.rb +48 -53
- data/spec/performance/conversion/portamento_converter_spec.rb +11 -9
- data/spec/performance/conversion/score_collator_spec.rb +59 -63
- data/spec/performance/midi/midi_util_spec.rb +22 -8
- data/spec/performance/midi/part_sequencer_spec.rb +2 -2
- data/spec/performance/midi/score_sequencer_spec.rb +12 -10
- data/spec/performance/midi/score_sequencing_spec.rb +2 -2
- data/spec/performance/model/note_sequence_spec.rb +41 -134
- data/spec/printing/note_engraving_spec.rb +204 -0
- data/spec/printing/score_engraver_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -0
- metadata +69 -23
- data/examples/notation/hip.rb +0 -32
- data/examples/notation/missed_connection.rb +0 -26
- data/examples/notation/song1.rb +0 -33
- data/examples/notation/song2.rb +0 -32
- data/lib/musicality/notation/model/links.rb +0 -11
- data/lib/musicality/notation/packing/change_packing.rb +0 -56
- data/lib/musicality/notation/packing/part_packing.rb +0 -31
- data/lib/musicality/notation/packing/score_packing.rb +0 -123
- data/lib/musicality/performance/model/note_attacks.rb +0 -19
- data/lib/musicality/performance/util/note_linker.rb +0 -28
- data/spec/notation/packing/change_packing_spec.rb +0 -304
- data/spec/notation/packing/part_packing_spec.rb +0 -66
- data/spec/notation/packing/score_packing_spec.rb +0 -255
- 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 =
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
@
|
19
|
-
|
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
|
27
|
-
output += " title = \"#{
|
47
|
+
if title
|
48
|
+
output += " title = \"#{title}\"\n"
|
28
49
|
end
|
29
|
-
if
|
30
|
-
output += " composer = \"#{
|
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
|
-
|
38
|
-
|
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
|
-
|
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
|
4
|
+
class Tempo < Score
|
5
5
|
# See ScoreEngraver#make_lilypond for details on supported keyword args
|
6
|
-
def to_lilypond
|
7
|
-
ScoreEngraver.new(self).make_lilypond(
|
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
|