musicality 0.10.1 → 0.11.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 +13 -3
- data/README.md +8 -8
- data/bin/auditions +241 -0
- data/bin/collidify +4 -2
- data/bin/musicality +13 -2
- data/examples/composition/auto_counterpoint.rb +4 -4
- data/examples/composition/part_generator.rb +6 -6
- data/examples/composition/scale_exercise.rb +5 -5
- data/examples/notation/scores.rb +2 -2
- data/examples/notation/twinkle.rb +6 -6
- data/examples/notation/twinkle.score +3 -3
- data/lib/musicality.rb +6 -4
- data/lib/musicality/composition/dsl/part_methods.rb +12 -0
- data/lib/musicality/composition/dsl/score_dsl.rb +4 -4
- data/lib/musicality/composition/dsl/score_methods.rb +8 -2
- data/lib/musicality/notation/model/audition.rb +16 -0
- data/lib/musicality/notation/model/score.rb +31 -30
- data/lib/musicality/performance/supercollider/conductor.rb +2 -2
- data/lib/musicality/performance/supercollider/synthdefs.rb +30 -29
- data/lib/musicality/project/auditions_task.rb +28 -0
- data/lib/musicality/project/create_tasks.rb +15 -9
- data/lib/musicality/project/file_cleaner.rb +22 -5
- data/lib/musicality/project/file_raker.rb +29 -21
- data/lib/musicality/project/project.rb +218 -32
- data/lib/musicality/version.rb +1 -1
- data/musicality.gemspec +6 -4
- data/spec/composition/dsl/part_methods_spec.rb +24 -0
- data/spec/notation/conversion/score_conversion_spec.rb +2 -1
- data/spec/notation/conversion/score_converter_spec.rb +23 -23
- data/spec/notation/model/score_spec.rb +66 -46
- data/spec/performance/conversion/score_collator_spec.rb +29 -29
- data/spec/performance/midi/score_sequencing_spec.rb +5 -5
- metadata +10 -8
- data/lib/musicality/project/load_config.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f859d6a80c045507491575cffe0f999847d337bf
|
4
|
+
data.tar.gz: 1f342c1ea5e6402c817850d5589d857d4673541b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 741b7b48887b0015cc58816c5c689e9b3c214a7030ad83e83fbc7948349b1aed60bd606e09cf1de89d545496abfd520f4ccfd7664c767cb29f38c1110b1d6b45
|
7
|
+
data.tar.gz: 8bc2b08df2b6b1d574efe17185463f174c09aef58f3341455ba697a747b4d7ff4538d367cdcd6de0a9f8fb16646fb6ec7d2a3556a17aa01ccc6eb1d264b3083b
|
data/ChangeLog.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
### 0.11.0 / 2016-02-18
|
2
|
+
* Fix SuperCollider volume control to output stereo
|
3
|
+
* Add :keep_code keyword arg to SuperCollider::Conductor#perform
|
4
|
+
* Add convenience method Part#dynamic_change for adding a new dynamic change
|
5
|
+
* Add Project::update method and 'musicality update' subcommand
|
6
|
+
* Allow 'musicality new' to be ran on a non-empty directory. Existing scores directories will ignored, and existing project directories will be updated
|
7
|
+
* Add auditions executable. Add auditions rake tasks: auditions, auditions:wav, etc.
|
8
|
+
* Fix bug in Score#collated?
|
9
|
+
* Support interactive auditions using VLC on Linux
|
10
|
+
* Change Score::Tempo#initialize to only require the start tempo, making start meter an optional keyword argument
|
11
|
+
|
1
12
|
### 0.10.1 / 2016-01-08
|
2
13
|
* Kill sclang process with Process.kill(9,pid) when running on Windows. Otherwise use Process.kill('INT',pid)
|
3
14
|
* Delete .scd after it is processed
|
@@ -29,7 +40,7 @@
|
|
29
40
|
* Add Part#settings. Create settings classes for LilyPond and MIDI
|
30
41
|
* Add Part#lilypond_settings in lilypond_settings.rb and Part#midi_settings in midi_settings.rb
|
31
42
|
* Add support for non-realtime SUperCollider performance
|
32
|
-
* Make dynamic levels increase exponentially, like
|
43
|
+
* Make dynamic levels increase exponentially, like
|
33
44
|
* In ScoreCollator#collate_changes, return new start value as well as dynamic changes, instead of always creating an initial immediate change
|
34
45
|
* Add SynthDef class to be able to include SynthDefs in SuperCollider code
|
35
46
|
* Add musicality project infrastructure
|
@@ -65,7 +76,7 @@
|
|
65
76
|
* Conversion of both tempo-based scores (measured and unmeasured) directly to time-based score
|
66
77
|
* Trimming of gradual changes, useful in collating scores
|
67
78
|
* Refactoring of ValueComputer using Function and Transition utility classes
|
68
|
-
* Add optional start_value for gradual changes, for making them absolute (by default they're relative)
|
79
|
+
* Add optional start_value for gradual changes, for making them absolute (by default they're relative)
|
69
80
|
* Add 'with' kw arg to change pack/unpack methods, for converting start/end values
|
70
81
|
|
71
82
|
### 0.2.0 / 2014-11-24
|
@@ -90,4 +101,3 @@
|
|
90
101
|
* Packing/unpacking to/from hash, using stringizing/parsing to condense, esp. for notes
|
91
102
|
* Convert score to MIDI file via midilib gem
|
92
103
|
* bin/midify command-line utility to run MIDI conversion on score YAML file
|
93
|
-
|
data/README.md
CHANGED
@@ -74,25 +74,25 @@ include Meters
|
|
74
74
|
include Dynamics
|
75
75
|
include Pitches
|
76
76
|
|
77
|
-
score = Score::Tempo.new(
|
77
|
+
score = Score::Tempo.new(120, title: "Twinkle, Twinkle, Little Star") do |s|
|
78
78
|
s.parts["rhand"] = Part.new(MF) do |p|
|
79
79
|
a_notes = q(C4,C4,G4,G4,A4,A4) + h(G4) +
|
80
80
|
q(F4,F4,E4,E4,D4,D4) + h(C4)
|
81
81
|
b_notes = q(G4,G4,F4,F4,E4,E4) + h(D4)
|
82
82
|
p.notes += a_notes + b_notes
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
s.parts["lhand"] = Part.new(MF) do |p|
|
86
86
|
Cmaj = [C3,E3,G3]
|
87
87
|
Fmaj = [F2,A2,C3]
|
88
88
|
Gmaj = [G2,B2,D3]
|
89
|
-
|
90
|
-
a_chords = h(Cmaj,Cmaj,Fmaj,Cmaj) +
|
89
|
+
|
90
|
+
a_chords = h(Cmaj,Cmaj,Fmaj,Cmaj) +
|
91
91
|
h(Fmaj,Cmaj,Gmaj,Cmaj)
|
92
92
|
b_chords = h(Cmaj,Fmaj,Cmaj,Gmaj)
|
93
93
|
p.notes += a_chords + b_chords
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
s.program.push 0...4
|
97
97
|
s.program.push 4...6
|
98
98
|
s.program.push 4...6
|
@@ -131,7 +131,7 @@ The score DSL is an internal DSL (built on Ruby) that consists of a *score* bloc
|
|
131
131
|
|
132
132
|
Here is an example of a score file.
|
133
133
|
```ruby
|
134
|
-
tempo_score
|
134
|
+
tempo_score 120 do
|
135
135
|
title "Twinkle, Twinkle, Little Star"
|
136
136
|
|
137
137
|
Cmaj = [C3,E3,G3]
|
@@ -141,7 +141,7 @@ tempo_score Meters::FOUR_FOUR, 120 do
|
|
141
141
|
notes(
|
142
142
|
"rhand" => q(C4,C4,G4,G4,A4,A4) + h(G4) +
|
143
143
|
q(F4,F4,E4,E4,D4,D4) + h(C4),
|
144
|
-
"lhand" => h(Cmaj,Cmaj,Fmaj,Cmaj) +
|
144
|
+
"lhand" => h(Cmaj,Cmaj,Fmaj,Cmaj) +
|
145
145
|
h(Fmaj,Cmaj,Gmaj,Cmaj)
|
146
146
|
)
|
147
147
|
end
|
@@ -166,7 +166,7 @@ include Pitches
|
|
166
166
|
|
167
167
|
dsl = ScoreDSL.load 'twinkle.score'
|
168
168
|
score = dsl.score
|
169
|
-
```
|
169
|
+
```
|
170
170
|
|
171
171
|
|
172
172
|
## Musicality Projects
|
data/bin/auditions
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
exe_name = File.basename(__FILE__)
|
4
|
+
|
5
|
+
doc = <<DOCOPT
|
6
|
+
Creates audition audio files from the given score's auditions.
|
7
|
+
|
8
|
+
In the default (non-interactive) mode, the audition output files are simply
|
9
|
+
created and then left for the user to play and delete on their own.
|
10
|
+
|
11
|
+
In the interactive audition mode, audition files will be played under control of
|
12
|
+
the user. Each audition file will have a status associated with it, and will be
|
13
|
+
one of:
|
14
|
+
- undecided
|
15
|
+
- rejected
|
16
|
+
- accepted
|
17
|
+
|
18
|
+
Playback will continue as long as there are still undecided auditions.
|
19
|
+
Once an audition is marked accepted/rejected it will be removed from the
|
20
|
+
playlist. When the audition ends, the final status of each audition will be
|
21
|
+
displayed. The user will then have the option of deleting the rejected
|
22
|
+
audition files.
|
23
|
+
|
24
|
+
During the interactive audition mode, the user can enter one of the following
|
25
|
+
commands at any time:
|
26
|
+
- accept: Accept the current audition and start playing the next audition
|
27
|
+
under consideration.
|
28
|
+
- reject: Reject the current audition and start playing the next audition
|
29
|
+
under consideration.
|
30
|
+
- prev: Play the previous audition that is still under consideration.
|
31
|
+
- next: Skip to the next audition that is still under consideration.
|
32
|
+
- quit: Stop the audition.
|
33
|
+
|
34
|
+
Usage:
|
35
|
+
#{exe_name} SCORE_FILE [options]
|
36
|
+
|
37
|
+
Arguments:
|
38
|
+
SCORE_FILE Score YAML file (packed as a Hash or not)
|
39
|
+
|
40
|
+
Options:
|
41
|
+
-i --interactive Enable interactive mode
|
42
|
+
--outdir=OUTD Dir where files will be put (defaults to same dir as
|
43
|
+
audition file). Will be created if needed.
|
44
|
+
--format=FORMAT Audio file format to output. Valid formats are FLAC, WAV, and AIFF [default: FLAC]
|
45
|
+
-h --help Show this screen.
|
46
|
+
--version Show version.
|
47
|
+
DOCOPT
|
48
|
+
|
49
|
+
require 'docopt'
|
50
|
+
begin
|
51
|
+
args = Docopt::docopt(doc)
|
52
|
+
puts args
|
53
|
+
rescue Docopt::Exit => e
|
54
|
+
puts e.message
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
|
58
|
+
require 'musicality'
|
59
|
+
|
60
|
+
if args["--version"]
|
61
|
+
puts "#{exe_name} from musicality v#{Musicality::VERSION}"
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
|
65
|
+
SCORE_FILE = args["SCORE_FILE"]
|
66
|
+
unless File.exists? SCORE_FILE
|
67
|
+
puts "Score file #{SCORE_FILE} does not exist. Aborting."
|
68
|
+
exit
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'fileutils'
|
72
|
+
|
73
|
+
if args["--outdir"]
|
74
|
+
OUTDIR = args["--outdir"]
|
75
|
+
unless Dir.exists? OUTDIR
|
76
|
+
puts "Output directory #{OUTDIR} does not exist. Creating."
|
77
|
+
FileUtils.mkdir_p OUTDIR
|
78
|
+
unless Dir.exists? OUTDIR
|
79
|
+
puts "Output directory #{OUTDIR} could not be created. Aborting."
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
OUTDIR = File.dirname(SCORE_FILE)
|
85
|
+
end
|
86
|
+
|
87
|
+
INTERACTIVE = args["--interactive"]
|
88
|
+
AUDIO_FORMAT = args["--format"].downcase
|
89
|
+
TEMPO_SAMPLE_RATE = 200
|
90
|
+
ALLOWED_FNAME_SYMBOLS = /[A-Za-z0-9_]/
|
91
|
+
|
92
|
+
unless ["flac","wav","aiff"].include?(AUDIO_FORMAT)
|
93
|
+
puts "Unsupported audio format #{AUDIO_FORMAT}. Aborting."
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
|
97
|
+
require 'yaml'
|
98
|
+
include Musicality
|
99
|
+
|
100
|
+
print "Reading file '#{SCORE_FILE}'..."
|
101
|
+
score = YAML.load(File.read(SCORE_FILE))
|
102
|
+
|
103
|
+
if score.is_a? Hash
|
104
|
+
score = score.unpack
|
105
|
+
end
|
106
|
+
puts "done"
|
107
|
+
|
108
|
+
require 'open3'
|
109
|
+
|
110
|
+
score.auditions.each do |audition|
|
111
|
+
puts "Beginning audition for part '#{audition.part_name}'"
|
112
|
+
|
113
|
+
unless score.parts.has_key? audition.part_name
|
114
|
+
puts "Score does not have part #{audition.part_name} for audition. Aborting."
|
115
|
+
exit
|
116
|
+
end
|
117
|
+
|
118
|
+
score.program = audition.program
|
119
|
+
unless score.is_a? Score::Timed
|
120
|
+
print "Converting to timed score..."
|
121
|
+
score = score.to_timed(TEMPO_SAMPLE_RATE)
|
122
|
+
puts "done"
|
123
|
+
end
|
124
|
+
|
125
|
+
unless score.valid?
|
126
|
+
puts "Score is not valid. See errors:"
|
127
|
+
puts score.errors.join("\n")
|
128
|
+
exit
|
129
|
+
end
|
130
|
+
|
131
|
+
base_fpath = "#{OUTDIR}/#{audition.part_name}_"
|
132
|
+
|
133
|
+
output_files = []
|
134
|
+
audition.performers.each do |name, sc_settings|
|
135
|
+
if /[\W]/ =~ name
|
136
|
+
puts "Replacing Non-word characters found in #{name} with underscores."
|
137
|
+
name = name.gsub(/[\W]/,"_")
|
138
|
+
end
|
139
|
+
|
140
|
+
score.parts[audition.part_name].settings = [ sc_settings ]
|
141
|
+
conductor = SuperCollider::Conductor.new(score)
|
142
|
+
|
143
|
+
base_fpath2 = base_fpath + name
|
144
|
+
conductor.perform(base_fpath2, selected_parts: [audition.part_name])
|
145
|
+
|
146
|
+
input_fpath = base_fpath2 + ".osc"
|
147
|
+
output_fpath = base_fpath2 + "." + AUDIO_FORMAT
|
148
|
+
|
149
|
+
print "Rendering #{output_fpath}..."
|
150
|
+
`scsynth -N #{input_fpath} _ #{output_fpath} 44100 #{AUDIO_FORMAT} int16`
|
151
|
+
puts "done"
|
152
|
+
|
153
|
+
File.delete(input_fpath)
|
154
|
+
output_files.push output_fpath
|
155
|
+
end
|
156
|
+
|
157
|
+
if INTERACTIVE
|
158
|
+
def kill_pid pid
|
159
|
+
if OS.windows?
|
160
|
+
Process.kill 9, pid
|
161
|
+
else
|
162
|
+
Process.kill "INT", pid
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
undecided = output_files
|
167
|
+
rejected = []
|
168
|
+
accepted = []
|
169
|
+
|
170
|
+
user_input = ""
|
171
|
+
i = 0
|
172
|
+
|
173
|
+
user_input_thread = Thread.new do
|
174
|
+
while true
|
175
|
+
user_input = STDIN.gets.chomp
|
176
|
+
#STDOUT.puts "got user input \"#{user_input}\""
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# launch VLC with the 'remote control' console interface
|
181
|
+
Open3.popen2("vlc --intf rc #{undecided.join(" ")}") do |stdin, stdout, wait_thread|
|
182
|
+
pid = wait_thread.pid
|
183
|
+
|
184
|
+
vlc_running = true
|
185
|
+
while vlc_running
|
186
|
+
case user_input
|
187
|
+
when "next", "prev"
|
188
|
+
stdin.puts user_input
|
189
|
+
when "accept","reject"
|
190
|
+
begin
|
191
|
+
stdout.read_nonblock(2**16)
|
192
|
+
rescue IO::WaitReadable
|
193
|
+
end
|
194
|
+
stdin.puts "status"
|
195
|
+
|
196
|
+
result = stdout.readline
|
197
|
+
i = undecided.find_index {|fstr| result.include? fstr }
|
198
|
+
raise "Could not find file from status output #{result}" if i.nil?
|
199
|
+
|
200
|
+
if user_input == "accept"
|
201
|
+
accepted.push undecided.delete_at(i)
|
202
|
+
else
|
203
|
+
rejected.push undecided.delete_at(i)
|
204
|
+
end
|
205
|
+
stdin.puts "next"
|
206
|
+
when "quit"
|
207
|
+
vlc_running = false
|
208
|
+
stdin.puts "quit"
|
209
|
+
end
|
210
|
+
user_input = ""
|
211
|
+
|
212
|
+
if undecided.empty?
|
213
|
+
vlc_running = false
|
214
|
+
stdin.puts "quit"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
user_input_thread.kill
|
220
|
+
|
221
|
+
if undecided.any?
|
222
|
+
puts "Undecided:"
|
223
|
+
undecided.each { |f| puts " " + f }
|
224
|
+
puts
|
225
|
+
end
|
226
|
+
|
227
|
+
if rejected.any?
|
228
|
+
puts "Rejected:"
|
229
|
+
rejected.each { |f| puts " " + f }
|
230
|
+
puts
|
231
|
+
end
|
232
|
+
|
233
|
+
if accepted.any?
|
234
|
+
puts "Accepted:"
|
235
|
+
accepted.each { |f| puts " " + f }
|
236
|
+
puts
|
237
|
+
end
|
238
|
+
|
239
|
+
# TODO ask the user if they would like to delete the rejected auditions
|
240
|
+
end
|
241
|
+
end
|
data/bin/collidify
CHANGED
@@ -25,6 +25,7 @@ Options:
|
|
25
25
|
--srate=SRATE Sampling rate for converting tempo-based score to time-based
|
26
26
|
score, and for sampling dynamic change values [default: 200]
|
27
27
|
--leadtime=LT Time before performance begins (must be > 0) [default: 0.1]
|
28
|
+
--keepcode Do not delete the intermediate SuperCollider code file (.scd)
|
28
29
|
-h --help Show this screen.
|
29
30
|
--version Show version.
|
30
31
|
DOCOPT
|
@@ -63,7 +64,8 @@ end
|
|
63
64
|
|
64
65
|
VERBOSE = args["--verbose"]
|
65
66
|
PARTS = args["PART"]
|
66
|
-
|
67
|
+
LEAD_TIME = args["--leadtime"].to_f
|
68
|
+
KEEP_CODE = args["--keepcode"]
|
67
69
|
|
68
70
|
require 'yaml'
|
69
71
|
include Musicality
|
@@ -86,7 +88,7 @@ File.open(SCORE_FILE) do |fin|
|
|
86
88
|
if score.valid?
|
87
89
|
conductor = SuperCollider::Conductor.new(score)
|
88
90
|
|
89
|
-
perform_kwargs = { :verbose => VERBOSE, :lead_time =>
|
91
|
+
perform_kwargs = { :verbose => VERBOSE, :lead_time => LEAD_TIME, :keep_code => KEEP_CODE }
|
90
92
|
if PARTS.any?
|
91
93
|
perform_kwargs[:selected_parts] = PARTS
|
92
94
|
end
|
data/bin/musicality
CHANGED
@@ -3,15 +3,23 @@
|
|
3
3
|
exe_name = File.basename(__FILE__)
|
4
4
|
|
5
5
|
doc = <<DOCOPT
|
6
|
-
The 'new' subcommand wll create a new musicality project.
|
6
|
+
The 'new' subcommand wll create a new musicality project in the given directory.
|
7
|
+
If the given directory does not exist then it will be created. If it exists
|
8
|
+
then any existing project files will be updated as under the update subcommand.
|
9
|
+
|
10
|
+
The 'update' subcommand will update/restore the project files (Gemfile,
|
11
|
+
Rakefile, and config.yml) while preserving any modifications. Unless a
|
12
|
+
directory is specified, the current working directory is assumed to be the
|
13
|
+
project directory.
|
7
14
|
|
8
15
|
Usage:
|
9
16
|
#{exe_name} new PROJECT_DIR [options]
|
17
|
+
#{exe_name} update [PROJECT_DIR] [options]
|
10
18
|
#{exe_name} -h | --help
|
11
19
|
#{exe_name} --version
|
12
20
|
|
13
21
|
Arguments:
|
14
|
-
PROJECT_DIR
|
22
|
+
PROJECT_DIR project root directory
|
15
23
|
|
16
24
|
Options:
|
17
25
|
-h --help Show this screen.
|
@@ -36,4 +44,7 @@ end
|
|
36
44
|
|
37
45
|
if args["new"]
|
38
46
|
Musicality::Project.new(args["PROJECT_DIR"])
|
47
|
+
elsif args["update"]
|
48
|
+
project_dir = args["PROJECT_DIR"] || Dir.pwd
|
49
|
+
Musicality::Project.update(project_dir)
|
39
50
|
end
|
@@ -13,13 +13,13 @@ def random_melody rhythm, pitch_palette
|
|
13
13
|
pitch_sampler = RandomSampler.new(pitches,Probabilities.random(pitches.size))
|
14
14
|
make_notes(rhythm, pitch_sampler.sample(rhythm.size))
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def random_counterpoint rhythm, rhythm_palette, sample_rate, pitch_palette
|
18
18
|
cpg = CounterpointGenerator.new(rhythm,rhythm_palette)
|
19
19
|
counterpoint = cpg.best_solutions(25,0.5,sample_rate).sample
|
20
20
|
pitches = pitch_palette.sample(rand(2..pitch_palette.size))
|
21
21
|
pitch_sampler = RandomSampler.new(pitches,Probabilities.random(pitches.size))
|
22
|
-
make_notes(counterpoint, pitch_sampler.sample(counterpoint.size))
|
22
|
+
make_notes(counterpoint, pitch_sampler.sample(counterpoint.size))
|
23
23
|
end
|
24
24
|
|
25
25
|
2.times do
|
@@ -43,10 +43,10 @@ end
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
score = Score::Tempo.new(
|
46
|
+
score = Score::Tempo.new(120,
|
47
47
|
parts: { "bass" => bass, "guitar" => guitar },
|
48
48
|
program: [ 0...([bass.duration,guitar.duration].min) ]
|
49
49
|
)
|
50
50
|
|
51
51
|
seq = ScoreSequencer.new(score.to_timed(200)).make_midi_seq
|
52
|
-
File.open("./auto_counterpoint.mid", 'wb'){ |fout| seq.write(fout) }
|
52
|
+
File.open("./auto_counterpoint.mid", 'wb'){ |fout| seq.write(fout) }
|
@@ -11,12 +11,12 @@ major = Heptatonic::Prima::MAJOR
|
|
11
11
|
pitch_seqs = {
|
12
12
|
D4 => minor, C4 => major, E4 => minor, F4 => minor
|
13
13
|
}.map do |pitch,scale_class|
|
14
|
-
scale_class.to_pitch_seq(pitch)
|
14
|
+
scale_class.to_pitch_seq(pitch)
|
15
15
|
end
|
16
16
|
|
17
17
|
rhythm_seq2 = RepeatingSequence.new([3/8.to_r]*4)
|
18
18
|
|
19
|
-
score = Score::Tempo.new(SIX_EIGHT
|
19
|
+
score = Score::Tempo.new(90, start_meter: SIX_EIGHT) do |s|
|
20
20
|
s.parts["main"] = Part.new(Dynamics::MF) do |p|
|
21
21
|
p.settings.push MidiSettings::LEAD_SAWTOOTH
|
22
22
|
|
@@ -27,7 +27,7 @@ score = Score::Tempo.new(SIX_EIGHT,90) do |s|
|
|
27
27
|
pitches = pitch_seqs.map { |pseq| pseq.at(poffsets).to_a }.flatten
|
28
28
|
p.notes += make_notes(rhythm,pitches)
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
s.parts["bass"] = Part.new(Dynamics::MP) do |p|
|
32
32
|
p.settings.push MidiSettings::LEAD_SQUARE
|
33
33
|
|
@@ -38,7 +38,7 @@ score = Score::Tempo.new(SIX_EIGHT,90) do |s|
|
|
38
38
|
pitches = pitch_seqs.map { |pseq| pseq.at(poffsets).to_a }.flatten
|
39
39
|
p.notes += make_notes(rhythm,pitches)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
s.parts["pluck"] = Part.new(Dynamics::MP) do |p|
|
43
43
|
p.settings.push MidiSettings::ORCHESTRAL_HARP
|
44
44
|
|
@@ -49,9 +49,9 @@ score = Score::Tempo.new(SIX_EIGHT,90) do |s|
|
|
49
49
|
pitches = pitch_seqs.map { |pseq| pseq.at(poffsets).to_a }.flatten
|
50
50
|
p.notes += make_notes(rhythm,pitches)
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
s.program = [0...s.measures_long]*2
|
54
54
|
end
|
55
55
|
|
56
56
|
seq = ScoreSequencer.new(score.to_timed(200)).make_midi_seq
|
57
|
-
File.open("./part_generator.mid", 'wb'){ |fout| seq.write(fout) }
|
57
|
+
File.open("./part_generator.mid", 'wb'){ |fout| seq.write(fout) }
|