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