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