music_coder 0.7.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.
- data/lib/api/api.rb +108 -0
- data/lib/api/dist.rb +145 -0
- data/lib/api/hit_sq.rb +108 -0
- data/lib/api/note.rb +59 -0
- data/lib/api/snd.rb +62 -0
- data/lib/audio.rb +48 -0
- data/lib/audio_output.rb +110 -0
- data/lib/composer.rb +164 -0
- data/lib/fader.rb +37 -0
- data/lib/file_list.rb +202 -0
- data/lib/logger.rb +39 -0
- data/lib/math_utils.rb +64 -0
- data/lib/mixer.rb +14 -0
- data/lib/music_coder.rb +192 -0
- data/lib/snd_dist.rb +104 -0
- data/lib/tests.rb +168 -0
- data/lib/tone.rb +136 -0
- data/lib/tone_part.rb +96 -0
- data/lib/tone_seq.rb +42 -0
- data/lib/wave.rb +77 -0
- data/lib/wave_data.rb +103 -0
- metadata +85 -0
data/lib/logger.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# any output to console runs through this
|
2
|
+
class Logger
|
3
|
+
#higher level means more is logged.
|
4
|
+
#0 means silent.
|
5
|
+
#1 heading and written files.
|
6
|
+
#2 condensed stats and updative info from a process (loading bars)
|
7
|
+
#3 warnings
|
8
|
+
#4 memory updates and debug info
|
9
|
+
attr_accessor :level
|
10
|
+
def initialize
|
11
|
+
self.level = 4
|
12
|
+
end
|
13
|
+
|
14
|
+
#output something if our level says it's okay
|
15
|
+
def log str, lvl
|
16
|
+
raise "Don't write anything to log level 0." if lvl < 1
|
17
|
+
extra = ""
|
18
|
+
(lvl-1).times {extra+="_"}
|
19
|
+
puts extra + str + extra if level >= lvl
|
20
|
+
end
|
21
|
+
|
22
|
+
# a loading bar
|
23
|
+
def print_loading_bar(frames_index, len, percent_complete=nil)
|
24
|
+
percent = ((frames_index.to_f/len)*100).round(4)
|
25
|
+
percent = percent.round if !percent_complete.nil?
|
26
|
+
# puts "percent #{percent}"
|
27
|
+
# puts "fun percent_complete #{percent_complete}"
|
28
|
+
if level > 1 && (percent_complete.nil? || (percent_complete.round < percent))
|
29
|
+
App.logger.print_and_flush("__#{percent}%__")
|
30
|
+
end
|
31
|
+
percent
|
32
|
+
end
|
33
|
+
# puts without newline
|
34
|
+
def print_and_flush(str)
|
35
|
+
print str
|
36
|
+
$stdout.flush
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/math_utils.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# All generic static math functions needed.
|
2
|
+
class MathUtils
|
3
|
+
class << self
|
4
|
+
end
|
5
|
+
# golden ratio
|
6
|
+
self::GR = 1.61803398875 unless const_defined?(:GR)
|
7
|
+
|
8
|
+
#outputs 0 to 1 increasing by the factor
|
9
|
+
#detail:: number of times to apply division. 1 to inf
|
10
|
+
#factor:: the number to divide by.
|
11
|
+
def self.division_fade(detail = 8, factor = nil)
|
12
|
+
factor ||= GR
|
13
|
+
raise "Detail must be >= 1" if detail < 1
|
14
|
+
out = Array.new(detail)
|
15
|
+
old = 1
|
16
|
+
out.each_with_index do |x,i|
|
17
|
+
out[i] = old / factor.to_f
|
18
|
+
out[i] = 1 if i < 1 # first is 1 always
|
19
|
+
old = out[i]
|
20
|
+
end
|
21
|
+
out.reverse
|
22
|
+
end
|
23
|
+
|
24
|
+
#generates data for sin wave in an array (single cycle)
|
25
|
+
#detail:: number of elements in the wave.
|
26
|
+
#default: 2048 very smooth
|
27
|
+
def self.sinwave(detail = 2048, saturation=0)
|
28
|
+
raise "Sinwave frames must be specifed as >= 3." if detail < 3
|
29
|
+
val = Array.new(detail)
|
30
|
+
val.each_with_index do |foo,i|
|
31
|
+
progress=i.to_f/detail
|
32
|
+
val[i] = Math.sin(2.0*Math::PI*progress)
|
33
|
+
val[i] = (val[i]-saturation.to_f)+rand*saturation.to_f*2.0
|
34
|
+
end
|
35
|
+
val
|
36
|
+
end
|
37
|
+
|
38
|
+
#generates wave cycle by duplicating data to fillout the other 3 sections of a wave.
|
39
|
+
#data:: data from 0 to 1 for the first section.
|
40
|
+
def self.filloutwave(data)
|
41
|
+
val = []
|
42
|
+
i=0
|
43
|
+
while i < data.count
|
44
|
+
val.push data[data.count-i-1] if i > 0
|
45
|
+
i+=1
|
46
|
+
end
|
47
|
+
ret = data + val
|
48
|
+
val = []
|
49
|
+
i=0
|
50
|
+
while i < ret.count
|
51
|
+
val.push -ret[ret.count-1-i] if i > 0
|
52
|
+
i+=1
|
53
|
+
end
|
54
|
+
ret += val
|
55
|
+
ret.pop # remove last 0
|
56
|
+
ret
|
57
|
+
end
|
58
|
+
require 'matrix'
|
59
|
+
FIB_MATRIX ||= Matrix[[1,1],[1,0]]
|
60
|
+
#calculate fibonacci number
|
61
|
+
def self.fib(n)
|
62
|
+
(FIB_MATRIX**(n-1))[0,0]
|
63
|
+
end
|
64
|
+
end
|
data/lib/mixer.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# mixes two tracks together or plays one or the other
|
2
|
+
class Mixer
|
3
|
+
# get volumes at a position
|
4
|
+
def self.get(first, second, reduction)
|
5
|
+
reduction2 = reduction > 1 ? 1 : reduction
|
6
|
+
reduction1 = reduction * 2
|
7
|
+
reduction1 = 0 if reduction1 < 1
|
8
|
+
reduction1 -= 1 if reduction1 >= 1
|
9
|
+
f=first * (1.0 - reduction1)
|
10
|
+
s=second * reduction2
|
11
|
+
f+s
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/lib/music_coder.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
|
2
|
+
# extend all objects
|
3
|
+
class Object
|
4
|
+
# initialize all instance vars from a Hash if they are specified in args.
|
5
|
+
def init_hash(args)
|
6
|
+
instance_variables.each do |p|
|
7
|
+
v_name = p[1,p.size]
|
8
|
+
arg = args[v_name.to_sym] if not args.nil?
|
9
|
+
self.instance_variable_set p, arg if not arg.nil?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def deep_copy
|
14
|
+
Marshal.load(Marshal.dump(self))
|
15
|
+
end
|
16
|
+
def vars_eql?(other,vars=nil)
|
17
|
+
vars ||= instance_variables
|
18
|
+
vars.each do |var|
|
19
|
+
# puts instance_variable_get(var)
|
20
|
+
if instance_variable_get(var).respond_to? 'is_eql'
|
21
|
+
# puts 'responds to is_eql'
|
22
|
+
return false if !(instance_variable_get(var).is_eql other.instance_variable_get(var))
|
23
|
+
else
|
24
|
+
# puts "mine: #{instance_variable_get(var)} other: #{other.instance_variable_get(var)}"
|
25
|
+
return false if !(instance_variable_get(var) == other.instance_variable_get(var))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Array
|
33
|
+
# add all elements and fit if i'm too small
|
34
|
+
def add_e (array)
|
35
|
+
if !array.nil? && array.count > 0
|
36
|
+
array.count.times do |i|
|
37
|
+
self<<0 if i > self.count-1
|
38
|
+
self[i] += array[i]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
# add all elements and fit if i'm too small
|
44
|
+
def add_e_no_resize (array)
|
45
|
+
self.count.times do |i|
|
46
|
+
self[i] += array[i]
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#require 'debugger'
|
53
|
+
# The top level of the program. This static class contains static methods and static attributes.
|
54
|
+
class App
|
55
|
+
class << self
|
56
|
+
#file containing input (without .rb extension)
|
57
|
+
attr_accessor :infile
|
58
|
+
#Time of the last output computation
|
59
|
+
attr_accessor :lastgen
|
60
|
+
#write to this to be made
|
61
|
+
attr_accessor :out
|
62
|
+
attr_accessor :start_t
|
63
|
+
attr_accessor :outpath
|
64
|
+
attr_accessor :fileoptions
|
65
|
+
attr_accessor :mixes_num
|
66
|
+
attr_accessor :audio_file_settings
|
67
|
+
# how many hits are done, how many are todo
|
68
|
+
attr_accessor :done, :total
|
69
|
+
# perf testing
|
70
|
+
attr_accessor :checks
|
71
|
+
# Logger
|
72
|
+
attr_accessor :logger
|
73
|
+
end
|
74
|
+
require 'yaml'
|
75
|
+
|
76
|
+
# Reload all files in case of update.
|
77
|
+
def self.load_all
|
78
|
+
load 'tone.rb'
|
79
|
+
load 'logger.rb'
|
80
|
+
load 'fader.rb'
|
81
|
+
load 'composer.rb'
|
82
|
+
load 'math_utils.rb'
|
83
|
+
load 'wave.rb'
|
84
|
+
load 'wave_data.rb'
|
85
|
+
load 'mixer.rb'
|
86
|
+
load 'audio_output.rb'
|
87
|
+
load 'tone_part.rb'
|
88
|
+
load 'tone_seq.rb'
|
89
|
+
load 'audio.rb'
|
90
|
+
load 'file_list.rb'
|
91
|
+
load 'snd_dist.rb'
|
92
|
+
load 'api/note.rb'
|
93
|
+
load 'api/api.rb'
|
94
|
+
load 'api/dist.rb'
|
95
|
+
load 'api/hit_sq.rb'
|
96
|
+
load 'api/snd.rb'
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.clear_dir dir_path
|
100
|
+
Dir.foreach(dir_path) {|f| fn = File.join(dir_path, f); File.delete(fn) if f != '.' && f != '..'}
|
101
|
+
end
|
102
|
+
|
103
|
+
# clears objects ready to write to file.
|
104
|
+
def self.clear_ready
|
105
|
+
App.clear_dir "../tmp/"
|
106
|
+
App.out.snddists = []
|
107
|
+
App.out.filelist = FileList.new
|
108
|
+
end
|
109
|
+
|
110
|
+
# The top level of program calls this
|
111
|
+
def self.main_loop
|
112
|
+
puts "Running application code"
|
113
|
+
self.infile = 'input'
|
114
|
+
App.clear_dir App.outpath+"sound/"
|
115
|
+
self.out = AudioOutput.new
|
116
|
+
# App.clear_ready
|
117
|
+
self.out.outfile="#{App.outpath}sound/output.aiff"
|
118
|
+
while true do
|
119
|
+
if generate_new?
|
120
|
+
load_all
|
121
|
+
puts infile + ".rb change detected."
|
122
|
+
begin
|
123
|
+
load '../'+infile+'.rb'
|
124
|
+
files = *(1..App.mixes_num)
|
125
|
+
files.each {|let|
|
126
|
+
|
127
|
+
self.out.outfile="#{App.outpath}sound/#{let}#{App::EXT}"
|
128
|
+
puts "+++BEGIN #{let}#{App::EXT} +++"
|
129
|
+
App.start_t = Time.new
|
130
|
+
self.generate
|
131
|
+
|
132
|
+
}
|
133
|
+
rescue Exception => ex
|
134
|
+
puts "!!! - Mistake in input file!"
|
135
|
+
puts "!!! - " + ex.message
|
136
|
+
puts ex.backtrace.join("\n")
|
137
|
+
end
|
138
|
+
puts "...waiting for input changes..."
|
139
|
+
end
|
140
|
+
sleep(0.8)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.time_since
|
145
|
+
(Time.new - App.start_t)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Generates audio from input file.
|
149
|
+
def self.generate
|
150
|
+
input
|
151
|
+
end
|
152
|
+
|
153
|
+
# has there been a change in the input file since last generation?
|
154
|
+
# file:: the name without .rb extnesion or ./ before it
|
155
|
+
# lastgen:: the time of last generation
|
156
|
+
def self.generate_new?(file = nil)
|
157
|
+
file = infile if file.nil?
|
158
|
+
now = File.ctime("../"+file+".rb")
|
159
|
+
is_new = (lastgen != now)
|
160
|
+
self.lastgen = now
|
161
|
+
return is_new ? true : false
|
162
|
+
end
|
163
|
+
|
164
|
+
# To copy ruby classes not pass them by reference
|
165
|
+
def self.deep_copy(o)
|
166
|
+
Marshal.load(Marshal.dump(o))
|
167
|
+
end
|
168
|
+
|
169
|
+
# audio files are read this many frames at a time, reduce to save RAM, but will have longer generating times.
|
170
|
+
self.load_all # on load
|
171
|
+
# this is the max elements in an array of data for the write file. higher = less CPU load, more RAM load.
|
172
|
+
# 46,000 of these per mb roughly.
|
173
|
+
App::CHUNK_SIZE = 110_000 unless const_defined?(:CHUNK_SIZE)
|
174
|
+
App::EXT = ".aiff" unless const_defined?(:EXT)
|
175
|
+
App.outpath = "output/"
|
176
|
+
App.start_t = Time.new
|
177
|
+
self.mixes_num = 1
|
178
|
+
Composer.samplerate = 44100
|
179
|
+
Composer.bpm = 128
|
180
|
+
App.checks = 0
|
181
|
+
self.out = AudioOutput.new
|
182
|
+
# make dirs
|
183
|
+
require 'fileutils'
|
184
|
+
FileUtils.mkdir_p App.outpath+"sound/"
|
185
|
+
FileUtils.mkdir_p "tmp/"
|
186
|
+
#
|
187
|
+
App.clear_dir App.outpath+"sound/"
|
188
|
+
self.out.outfile="#{App.outpath}sound/output#{App::EXT}"
|
189
|
+
self.logger = Logger.new
|
190
|
+
App.fileoptions={:mode => :WRITE, :format => :RAW, :encoding => :PCM_16, :channels => 1, :samplerate => Composer.samplerate}
|
191
|
+
log "Music Coder", 1
|
192
|
+
end
|
data/lib/snd_dist.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# the distribution of a ToneSeq, or multiple other SndDists
|
2
|
+
class SndDist
|
3
|
+
#Array of floats containing the delays for each time a bar played. 0 to 1.
|
4
|
+
attr_accessor :hits
|
5
|
+
#Array of SndDist
|
6
|
+
attr_accessor :snd
|
7
|
+
#Array of ToneSeq
|
8
|
+
attr_accessor :tss
|
9
|
+
attr_accessor :len
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@hits = []
|
13
|
+
@snd = []
|
14
|
+
@tss = []
|
15
|
+
@len = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_children
|
19
|
+
@snd
|
20
|
+
end
|
21
|
+
|
22
|
+
# get pointer to the first end node.
|
23
|
+
def end_node
|
24
|
+
if !tss.empty?
|
25
|
+
return self
|
26
|
+
else
|
27
|
+
return snd.first.end_node
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# me and all children. not frames, hits
|
32
|
+
def tally_frames(old=0)
|
33
|
+
if !tss.empty?
|
34
|
+
# puts "returning hits count #{hits.count}"
|
35
|
+
return hits.count
|
36
|
+
else
|
37
|
+
result=0
|
38
|
+
snd.each do |sn|
|
39
|
+
result += hits.count*sn.tally_frames(old)
|
40
|
+
end
|
41
|
+
# puts "all in result #{result}"
|
42
|
+
return result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add sn
|
47
|
+
self.snd<<sn
|
48
|
+
end
|
49
|
+
|
50
|
+
#recursivly create children with 4 hits, or a toneseq with 1 tonepart
|
51
|
+
def populate(depth=0)
|
52
|
+
raise "Error, you ran Dist.populate before seting hits and length first." if hits.count.to_f < 1 || len < 1
|
53
|
+
puts "==|Dep: #{depth}| populating distributed sounds "
|
54
|
+
max_child_len=(len/hits.count.to_f).round
|
55
|
+
if depth==0
|
56
|
+
t=ToneSeq.new
|
57
|
+
self.tss<<t
|
58
|
+
t.make(1,max_child_len)
|
59
|
+
else#not 0 yet, recurse
|
60
|
+
sn=SndDist.new
|
61
|
+
add sn
|
62
|
+
sn.len = max_child_len
|
63
|
+
sn.disperse_hits(4)
|
64
|
+
sn.populate depth-1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# write children
|
69
|
+
def render(parent_hit_index=0)
|
70
|
+
# parent_into = (parent_hit_index+1) / hits.count.to_f #DEP
|
71
|
+
# puts "==#{parent_hit_index} rendering sound #{hits.count} times. (#{App.time_since} secs)"
|
72
|
+
files=FileList.new
|
73
|
+
raise "forgot to put a length of this sound dist" if len.nil?
|
74
|
+
log "Warning: one of your sound distributions have no hits. on purpose? ", 3 if hits.empty?
|
75
|
+
hits.each_with_index do |delay,i|
|
76
|
+
delay_in_frames= (delay*len).round
|
77
|
+
into=(i+1).to_f/hits.count
|
78
|
+
snd.each do |sn|
|
79
|
+
# puts "another snd dist "
|
80
|
+
files.addlist sn.render(into), delay_in_frames
|
81
|
+
end
|
82
|
+
tss.each {|sn| files.addlist sn.render(into), delay_in_frames}
|
83
|
+
App.done += 1 if !tss.empty?
|
84
|
+
App.logger.print_loading_bar(App.done, App.total)
|
85
|
+
end
|
86
|
+
files.child_len = (len)
|
87
|
+
files
|
88
|
+
end
|
89
|
+
#dep
|
90
|
+
#Adds into #hits.
|
91
|
+
#possible_hits:: number of hits that can occur. Must be int
|
92
|
+
#chance:: chance a hit will be included. range: 0 to 1
|
93
|
+
#ignore_first:: skip the first n possible hits
|
94
|
+
#ignore_first:: skip the last n possible hits
|
95
|
+
#e.g. disperse_hits(16,1,4,4) makes this pattern [-|-|-|-|+|+|+|+|+|+|+|+|-|-|-|-|]
|
96
|
+
def disperse_hits(possible_hits = 4, chance = 1, ignore_first=0, ignore_last=0)
|
97
|
+
possible_hits.times do |i|
|
98
|
+
if ignore_first <= i && possible_hits - ignore_last > i
|
99
|
+
delay = i/possible_hits.to_f
|
100
|
+
hits.push delay if (rand + chance >= 1)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/tests.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require "./app.rb"
|
2
|
+
require "test/unit"
|
3
|
+
|
4
|
+
class TestHitSq < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@h=[0.2,0.3,0.4].HitSq
|
7
|
+
@h2=[0,1].HitSq
|
8
|
+
end
|
9
|
+
def test_loaded
|
10
|
+
assert_equal(3, @h.count)
|
11
|
+
assert_equal(true, @h2.hits.eql?([0.0,1.0]))
|
12
|
+
end
|
13
|
+
def test_bounds
|
14
|
+
assert_raise(RuntimeError) {[1.1].HitSq}
|
15
|
+
assert_raise(RuntimeError) {[-0.1].HitSq}
|
16
|
+
assert_raise(RuntimeError) {@h2<<8}
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_ops
|
20
|
+
@h<<1
|
21
|
+
assert_equal(4, @h.count)
|
22
|
+
@h<<@h2
|
23
|
+
assert_equal(5, @h.count)
|
24
|
+
# without the duplicate now
|
25
|
+
@h>>1
|
26
|
+
assert_equal(4, @h.count)
|
27
|
+
|
28
|
+
#
|
29
|
+
assert_equal(2, @h2.count)
|
30
|
+
@h2>>1.0
|
31
|
+
assert_equal(1, @h2.count)
|
32
|
+
# Not chaged
|
33
|
+
assert_equal(4, @h.count)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_hits_move
|
37
|
+
assert_equal(([0.2,0.3,0.4]), @h.hits)
|
38
|
+
@h.move(0.1)
|
39
|
+
assert_equal(([0.3,0.4,0.5]), @h.hits)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class TestDists < Test::Unit::TestCase
|
44
|
+
def setup
|
45
|
+
@d=Dist.new
|
46
|
+
@d2=Dist.new
|
47
|
+
@d3=Dist.new
|
48
|
+
@d3.length = bar
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_add_dist
|
52
|
+
assert_equal(0, @d.branches)
|
53
|
+
@d<<@d2
|
54
|
+
assert_equal(1, @d.branches)
|
55
|
+
d=0.0.Dist
|
56
|
+
assert_equal([0.0], d.hits.hits)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_del_dist
|
60
|
+
assert_equal(0, @d.branches)
|
61
|
+
@d<<@d2
|
62
|
+
@d>>@d2
|
63
|
+
assert_equal(0, @d.branches)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_add_dist_arr
|
67
|
+
assert_equal(0, @d.branches)
|
68
|
+
@d<<[@d3,@d2]
|
69
|
+
assert_equal(2, @d.branches)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_del_dist_arr
|
73
|
+
assert_equal(0, @d.branches)
|
74
|
+
@d<<[@d3,@d2]
|
75
|
+
assert_equal(2, @d.branches)
|
76
|
+
@d>>[@d3,@d2]
|
77
|
+
assert_equal(0, @d.branches)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_persistence
|
81
|
+
assert_equal(0, @d2.hits.count)
|
82
|
+
@d2<<1
|
83
|
+
assert_equal(1, @d2.hits.count)
|
84
|
+
@d2<<[0.7,0.8]
|
85
|
+
assert_equal(3, @d2.hits.count)
|
86
|
+
h=@d2.hits
|
87
|
+
h<<0.1
|
88
|
+
assert_equal(4, @d2.hits.count)
|
89
|
+
h<<0.2
|
90
|
+
assert_equal(5, @d2.hits.count)
|
91
|
+
h>>0.2
|
92
|
+
assert_equal(4, @d2.hits.count)
|
93
|
+
@d2>>[0.1,0.8]
|
94
|
+
# still persists
|
95
|
+
assert_equal(2, @d2.hits.count)
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_children
|
99
|
+
@d2<<1
|
100
|
+
@d<<[@d2]
|
101
|
+
assert_equal(1, @d.branches)
|
102
|
+
assert_equal(1, @d[0].hits.count)
|
103
|
+
# can't have a snd
|
104
|
+
assert_raise(RuntimeError) {@d<<Snd.new}
|
105
|
+
assert_raise(RuntimeError) {@d.snd}
|
106
|
+
@d>>[@d2]
|
107
|
+
assert_raise(RuntimeError) {@d[0].hits.count}
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_snd
|
111
|
+
assert_equal(0, @d.sounds)
|
112
|
+
@d<<Snd.new
|
113
|
+
assert_equal(1, @d.sounds)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_len
|
117
|
+
d=10.Dist
|
118
|
+
assert_equal(10, d.length)
|
119
|
+
d.length = 100
|
120
|
+
assert_equal(100, d.length)
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_snddefs
|
124
|
+
s=20.Snd
|
125
|
+
assert_equal(20, s.t.tones.start.freq.start)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_def_hits
|
129
|
+
d=Dist.new
|
130
|
+
assert_equal(0, d.hits.count)
|
131
|
+
assert_equal([0.0], d.dist.hits) #default
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_def_tone
|
135
|
+
d=10.Dist
|
136
|
+
d.make_sound
|
137
|
+
assert_equal(10, d.snd.t.max_frames)
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_snd_f_def
|
141
|
+
s=20.0.Snd
|
142
|
+
assert_equal(20, s.t.freq)
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_snd_len_dist
|
146
|
+
snd = 155.Snd
|
147
|
+
snd.length= beat
|
148
|
+
assert_equal(beat, snd.t.tones.start.frames)
|
149
|
+
assert_equal(0, snd.t.max_frames)
|
150
|
+
snd.length= 0
|
151
|
+
snd.length= beat
|
152
|
+
@d3<<snd
|
153
|
+
assert_equal(bar, snd.t.max_frames)
|
154
|
+
assert_equal(beat, snd.t.tones.start.frames) # kept, not 0
|
155
|
+
end
|
156
|
+
def test_snd_len_def
|
157
|
+
snd = 155.Snd
|
158
|
+
@d3<<snd
|
159
|
+
assert_equal(bar, snd.t.tones.start.frames) # if 0 uses full
|
160
|
+
end
|
161
|
+
def test_notes
|
162
|
+
snd = Note.new(0,5).Snd
|
163
|
+
assert_equal(440, snd.t.freq)
|
164
|
+
assert_equal(Note.new(0,5).freq, snd.t.note.freq)
|
165
|
+
snd = Note.new("c#",5)
|
166
|
+
assert_equal(4, snd.note)
|
167
|
+
end
|
168
|
+
end
|