music_coder 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # All disc operations.
2
+ class App
3
+
4
+ def App.open_w_audiofile file
5
+ raise "no file name to open for writting. " if file.nil? || App.fileoptions.nil?
6
+ # puts "#{file} #{App.fileoptions}"
7
+ Sndfile::File.open(file, App.fileoptions)
8
+ end
9
+ def self.m_from_array data
10
+ write_matrix = GSLng::Matrix.from_array(data)
11
+ write_matrix.transpose
12
+ end
13
+
14
+ def App.write_to_audiofile fout, array
15
+ fout.write App.m_from_array array
16
+ GC.start
17
+ end
18
+
19
+ def App.open_write(filen)
20
+ File.open(filen, 'w')
21
+ end
22
+
23
+ def App.close_write(fi)
24
+ fi.close
25
+ end
26
+
27
+ def App.split_array(array, size)
28
+ upto=0
29
+ ammont=array.count
30
+ jump=size
31
+ out = []
32
+ while upto < ammont do
33
+ # puts "upto #{upto} amount #{ammont}"
34
+ jump = ammont - upto if upto+jump >= ammont
35
+ # puts "array range #{upto} to #{upto+jump-1}"
36
+ out << array[upto..upto+jump-1]
37
+ upto+=jump
38
+ end
39
+ out
40
+ end
41
+ def App.read_chunk_of(array, start=0, chunk_size)
42
+ array[start..start+chunk_size-1]
43
+ end
44
+
45
+
46
+ end
47
+
48
+ require 'sndfile'
@@ -0,0 +1,110 @@
1
+ class AudioOutput
2
+ # add all other fls to this
3
+ attr_accessor :filelist
4
+ #this didn't need to be an array, but maybe use it for track mixing?
5
+ attr_accessor :snddists
6
+ attr_accessor :outfile
7
+ # of file write
8
+ attr_accessor :percent_complete
9
+
10
+ def initialize()
11
+ @snddists = []
12
+ @outfile = nil
13
+ @filelist= FileList.new
14
+ @percent_complete = 0
15
+ end
16
+ def render
17
+ log "phase 1 of 2: render sounds", 2
18
+ App.done=0
19
+ App.total=0
20
+ snddists.each {|snddist| App.total+=snddist.tally_frames}
21
+ # puts "App.total #{App.total}" # Only for progress bar
22
+ snddists.each {|snddist| self.filelist.addlist snddist.render }
23
+ log "phase 1 complete.", 2
24
+ end
25
+ def make_audio_file
26
+ log "phase 2 of 2: merging all tmp files into #{App::EXT} file. == #{App.time_since}", 2
27
+ frames_index = 0
28
+ len = 0
29
+ # get max len
30
+ snddists.each { |sn| len = sn.len if sn.len > len }
31
+ len = len.round
32
+ fout = App.open_w_audiofile outfile
33
+ App.checks = 0 # for performance testing
34
+ last_jump=0
35
+ while frames_index < len
36
+
37
+ if frames_index + App::CHUNK_SIZE >= len
38
+ # set jump to finish it off since it's under chunk size
39
+ rest = len - frames_index #1 min, chunk_size max.
40
+ jump = rest
41
+ log "capping chunk size at #{jump}", 4
42
+ else
43
+ jump=App::CHUNK_SIZE
44
+ end
45
+
46
+ values_for_write = []
47
+ # while values_for_write.count < jump
48
+ # if frames_index-last_jump >= jump
49
+ # puts "frames index was #{frames_index} jump was #{jump}"
50
+ # puts "jump neeeds to be > #{frames_index-last_jump}"
51
+ # jump = App::CHUNK_SIZE
52
+ # end
53
+
54
+ # jump=frames_index+10000
55
+
56
+ while frames_index-last_jump < jump
57
+ # minimize if too big
58
+ if frames_index-last_jump+jump > len
59
+ jump = len-frames_index-last_jump
60
+ log "minimizing this chunk to #{jump}", 4
61
+ end
62
+ log "CHUNK_@#{frames_index} asked for #{jump}. total: #{len}", 4
63
+ values = filelist.root_get_value(frames_index,jump)#, jump)+
64
+ # if values.count==0
65
+ # puts "VALUES RETURNED 0"
66
+ # frames_index = len # end this shit
67
+ # end
68
+ # puts "CHUNK recieved #{values.count} "
69
+ # puts "--> read #{values.join(', ')}"
70
+ values_for_write+= values
71
+ # puts "____#{found_vals}"
72
+ # values.add_e found_vals
73
+ # raise " couldn't find a value... fuck." if found_val.nil?
74
+ # print percent
75
+ frames_index += values.count
76
+ App.logger.print_loading_bar(frames_index, len, percent_complete)
77
+ self.percent_complete = ((frames_index.to_f/len)*100)
78
+ # values=nil
79
+ # GC.start
80
+ end
81
+ last_jump=frames_index
82
+ # puts "--> to write: #{values_for_write.count} #{frames_index} #{len}"
83
+ App.write_to_audiofile fout, values_for_write if values_for_write.count>0
84
+ # values_for_write=nil
85
+ # GC.start
86
+ end
87
+ App.close_write fout
88
+ log "performed #{App.checks} lookup checks.", 4
89
+ print_tail_info len
90
+ end
91
+ def write_text_files file=nil
92
+ file = App.outpath + "save.rb" if file.nil?
93
+ log "state save to file #{file}", 2
94
+ File.open(file, 'w') do |file|
95
+ # snddists.each do |snddist|
96
+ # file.puts YAML::dump(snddist)
97
+ # end
98
+ file.puts YAML::dump(self)
99
+ end
100
+ end
101
+ def print_tail_info total_frames
102
+ time_taken = App.time_since
103
+ log 'wrote file: ' + outfile, 1
104
+ log 'seconds: ' + (total_frames.to_f / Composer.samplerate).round(2).to_s +
105
+ " (frames: #{(total_frames/1000).round},000)", 2
106
+ log 'time taken: ' + time_taken.round(2).to_s +
107
+ " seconds (fps: #{(total_frames/time_taken/1000).round()},000)", 2
108
+ end
109
+
110
+ end#class
@@ -0,0 +1,164 @@
1
+ # contains the functions a music composer needs.
2
+ # all functions are related to music instead of audio.
3
+ class Composer
4
+ class << self
5
+ #beats per minute of the track. (Set this first thing in your input file)
6
+ attr_accessor :bpm
7
+ #fames per second
8
+ attr_accessor :samplerate
9
+ end
10
+ def self.chords
11
+ all = Hash.new
12
+ #Triads
13
+ all["maj"] = [4, 7] # major
14
+ all["min"] = [3, 7] # minor
15
+ all["aug"] = [4, 8] # augmented
16
+ all["dim"] = [3, 6] # diminished
17
+
18
+ #Seventh chords
19
+ all["dim7"] = [3, 6, 9] # diminished 7th
20
+ all["mm7"] = [3, 7, 11] # major minor 7th
21
+ all["min7"] = [3, 7, 10] # minor 7th
22
+ all["dom7"] = [4, 7, 10] # dominant 7th
23
+ all["maj7"] = [4, 7, 11] # major 7th
24
+ # half diminished
25
+ # augmented
26
+ # augmented major
27
+
28
+ all["sus4"] = [5, 7]
29
+ all["sus2"] = [2, 7]
30
+ all["6"] = [4, 7, 9]
31
+ all["m13"] = [2, 4, 7, 9, 11]
32
+ all["9#11"] = [2, 4, 6, 7, 10]
33
+ all["tonic"] = [6]
34
+ all
35
+ end
36
+
37
+ def self.scales
38
+ all = Hash.new
39
+ all["major"] = [2,4,5,7,9,11] # 7 total
40
+ all["minor"] = [2,3,5,7,8,10]
41
+ all["chromatic"] = *(1..10) #splat
42
+ #all["ionian"] = [2,4,5,7,9,11]
43
+ all["dorian"] = [2,3,5,7,9,10]
44
+ all["phygian"] = [1,3,5,7,8,10]
45
+ all["lydian"] = [2,4,6,7,9,11]
46
+ all["mixolydian"] = [2,4,5,7,9,10]
47
+ #all["aeolian"] = [2,3,5,7,8,10]
48
+ all["locrian"] = [1,3,5,6,8,10]
49
+ all
50
+ end
51
+
52
+ #outputs the midi notes of the chord in an array
53
+ #note:: midi value of root note in chord. range: 0 to 11
54
+ def self.chord_notes(note, name = "dim7")
55
+ set=[]
56
+ out=[]
57
+ all=chords
58
+ # handle all selected
59
+ if name=="all"
60
+ all.keys.each { |val| out.push chord_notes(note, val) }
61
+ else #normal
62
+ set = all[name]
63
+ raise "Unknown scale name" if set.nil?
64
+ out = [note] # always root
65
+ set.each do |val|
66
+ out.push note+val
67
+ end
68
+ end
69
+
70
+ out
71
+ end
72
+
73
+ #outputs the scale name and midi notes in hash
74
+ #note:: midi value of root note. range: 0 to 11
75
+ def self.scale_notes(note, name = "major")
76
+ set=[]
77
+ out=[]
78
+ all=scales
79
+ # handle all selected
80
+ if name=="all"
81
+ all.keys.each { |val| out.push scale_notes(note, val) }
82
+ else #normal
83
+ set = all[name]
84
+ raise "Unknown scale name" if set.nil?
85
+ out = [note] # always root
86
+ set.each do |val|
87
+ out.push note+ val
88
+ end
89
+ end
90
+ out
91
+ end
92
+
93
+ # return set of chords that fit in the scale
94
+ def self.matching_chords(scale = "major", offset = 0)
95
+ all = chords
96
+ scale_n = [0] + scales[scale]
97
+ out = []
98
+ all.each do |chord|
99
+ name = chord[0]
100
+ notes=get_notes all[name], offset # lookup chord with name
101
+ out.push name if (scale_n&notes).sort==notes.sort # if all notes are in scale
102
+ end
103
+ out
104
+ end
105
+
106
+ # return an array of notes from a array of notes with an offset, adding the root note at the start.
107
+ # e.g. ([1,10,2],2) outputs [2,3,0,4] (root note is 2, 10 + 2 becomes 0, 1 + 2 is 3 etc)
108
+ def self.get_notes(notes_ar, offset = 0)
109
+ notes = [0] + notes_ar
110
+ #offset
111
+ notes.collect! do |val|
112
+ if offset >= 0
113
+ val+offset>11 ? val+offset-12 : val+offset
114
+ else
115
+ val+offset<0 ? val+offset+12 : val+offset
116
+ end
117
+ end
118
+ notes
119
+ end
120
+
121
+ # convert beats to seconds
122
+ # @beat: the beat denominator. ie 4 means 4 beats, .125 means 1 8th beat.
123
+ def self.beat(beats)
124
+ bps = (self.bpm/60.0)
125
+ return (beats) * (1.0/bps) * self.samplerate.to_f
126
+ end
127
+
128
+ #convert a note as a string to midi value
129
+ #note:: range: "A" to "G#". No a flats.
130
+ def self.note_m(note)
131
+ val=nil
132
+ note.upcase!
133
+ case note
134
+ when 'A'
135
+ val=0
136
+ when 'A#'
137
+ val=1
138
+ when 'B'
139
+ val=2
140
+ when 'C'
141
+ val=3
142
+ when 'C#'
143
+ val=4
144
+ when 'D'
145
+ val=5
146
+ when 'D#'
147
+ val=6
148
+ when 'E'
149
+ val=7
150
+ when 'F'
151
+ val=8
152
+ when 'F#'
153
+ val=9
154
+ when 'G'
155
+ val=10
156
+ when 'G#'
157
+ val=11
158
+ else
159
+ raise "Unknown note name recieved."
160
+ end
161
+ val
162
+ end
163
+
164
+ end
@@ -0,0 +1,37 @@
1
+ # An initial value, a
2
+ class Fader
3
+ # The initial value.
4
+ attr_accessor :start
5
+ # The final value.
6
+ attr_accessor :final
7
+ # The exponential rate it fades from initial to final.
8
+ # range:: +0
9
+ # 0 is linear.
10
+ # higher than 1 means it reachs the final later than linear.
11
+ # less than 1 means it reachs the final sooner than linear.
12
+ attr_accessor :exp
13
+ def initialize(start=nil,final=nil,exp=0)
14
+ @start = start
15
+ @final = final
16
+ @exp = exp
17
+ end
18
+ # set #final to a percentage of start
19
+ def %(percent)
20
+ self.final = start.to_f*(percent/100.0)
21
+ end
22
+ # operate on both start and final
23
+ def *(mul=0.5)
24
+ self.start *= mul
25
+ self.final *= mul
26
+ end
27
+ # getter
28
+ def exp
29
+ @exp==0 ? nil : @exp
30
+ end
31
+ def exp_no_nil
32
+ @exp.nil? ? 0 : @exp
33
+ end
34
+ def is_eql(other)
35
+ vars_eql?(other)
36
+ end
37
+ end
@@ -0,0 +1,202 @@
1
+
2
+ # tree
3
+ class FileList
4
+ # ONLY WITH NO CHILDREN
5
+ attr_accessor :files
6
+ attr_accessor :file_content_lens
7
+ attr_accessor :loaded_file_index
8
+ attr_accessor :loaded_file_data
9
+
10
+ # N CHILDREN
11
+ attr_accessor :filelists
12
+ # delays of children
13
+ attr_accessor :filelist_delays
14
+ # I want each child in the fileslist to fill len frames from my start
15
+ attr_accessor :child_len, :mark_of_death
16
+ def initialize()
17
+ @files = []
18
+ @loaded_file_index = nil
19
+ @filelists = []
20
+ @filelist_delays = []
21
+ @child_len = 0
22
+ @file_content_lens=[]
23
+ @loaded_file_data = nil
24
+ @mark_of_death = false
25
+ end
26
+
27
+ def root_get_value(index,size=1)
28
+ out=get_value(index,size)
29
+ if out.count < 1
30
+ # raise "Error looking up a value at that index. fatal. index #{index}, size #{size}."
31
+ out=Array.new(size,0)
32
+ end
33
+ out
34
+ end
35
+ # return the value, index frames into your files. recursive.
36
+ def get_value(index,size=1)
37
+ # puts "requested #{size}"
38
+ out = []
39
+
40
+ if files.empty? # has children
41
+ out=lookup_children(index,size)
42
+ # raise "Child filelist failed at index #{index}, size #{size}." if out.count < 1
43
+ else # no children, just files
44
+ # puts "looked something up"
45
+ out = get(index,size)
46
+ # puts "retrieved #{out.count} frames from object files on disk"
47
+ # puts "got #{out.count}"
48
+ # puts "=>> no children, real files found value #{out}"
49
+ end
50
+ # puts "valuse #{out}"
51
+ out
52
+ end
53
+
54
+ def lookup_child(index,size,i)
55
+ out=[]
56
+ fl=filelists[i]
57
+ App.checks+=1
58
+ delay = filelist_delays[i]
59
+ # delay = 0 if delay.nil?
60
+ # puts "deciding if we should go into filelist #{i+1} of #{filelists.count},"+
61
+ # " is #{delay} <= #{index} ? "
62
+ # puts "child len #{@child_len}"
63
+ needed_until_in = delay - index
64
+ if needed_until_in <= 0
65
+ # puts "child #{i}"# #{index-delay}"
66
+ if fl.mark_of_death
67
+ # puts "old count: #{filelists.count}"
68
+ # fl.loaded_file_data=nil
69
+ # GC.start
70
+ self.filelists.delete_at i
71
+ self.filelist_delays.delete_at i
72
+ # puts "#{i} is dead! new count: #{filelists.count}"
73
+ else
74
+ # normal result
75
+ result =fl.get_value(index-delay,size)
76
+ # puts "normal #{result.count}"
77
+ out= result
78
+ end
79
+ else
80
+ spoof_req_len = size - needed_until_in
81
+ if spoof_req_len > 0
82
+ # Read a smaller chunk in the futre that will be missed if we increment by size.
83
+ index_to_get_in = needed_until_in + index
84
+ delay_on_future_chunk = Array.new(needed_until_in, 0)
85
+ future_chunk = fl.get_value(index_to_get_in-delay, spoof_req_len)
86
+ # puts future_chunk.count
87
+ combo = [] + delay_on_future_chunk + future_chunk
88
+ # puts combo.count
89
+ out= combo # with resizing, but will be chunk len anyway.
90
+ else
91
+ # Will be handled in future chunks.
92
+ # out.add_e Array.new(2,0)
93
+ # puts "not above delay"
94
+ # puts "c #{delay-index}"
95
+ end
96
+ end
97
+ out
98
+ end
99
+
100
+ def lookup_children(index,size)
101
+ out = []
102
+ if filelists.count==0
103
+ #no file lists, so fill with 0s to len
104
+ sil_amount = size
105
+ sil_amount = child_len if child_len < sil_amount && child_len != 0 #limit silence to my length
106
+ sil_amount
107
+ out = Array.new(sil_amount-index,0) if sil_amount-index > 0
108
+ ### puts "set mark of death, returning silence #{sil_amount}, my length: #{child_len}"
109
+ self.mark_of_death = true
110
+ return []
111
+ end
112
+ filelists.each_with_index do |fl,i|
113
+ out.add_e lookup_child(index,size,i)
114
+ end
115
+ out
116
+ end
117
+
118
+ def get(index, size=1)
119
+ # out=Array.new(1,0)
120
+ out=[]
121
+ # not opened yet
122
+ if loaded_file_index.nil?
123
+ # App.logger.print_and_flush "|t|"
124
+ load_from_file(0)
125
+ end
126
+
127
+ #still got data to write in THIS file
128
+ if current_index_fits_opended_file?(index)
129
+ out = (read_data_at index, size)
130
+ # puts "fl-no-child: normal read #{out}"
131
+
132
+ #need to load next file (index is higher than current)
133
+ else
134
+ # theres more files to load
135
+ if files.count > loaded_file_index+1
136
+ load_from_file(loaded_file_index+1)
137
+ out = (read_data_at index, size)
138
+ # puts "fl-no-child: file #{loaded_file_index} is finished. load next. found val #{out}"
139
+ else # no more files, index is past
140
+ self.mark_of_death =true
141
+ # puts "fl-no-child: marking for dead!!!"
142
+ end
143
+ end
144
+ out
145
+ end
146
+
147
+ #return:: total len of this TrackSection
148
+ #nil is error
149
+ def get_content_len
150
+ child_len.nil? ? file_content_lens.reduce(:+) : child_len
151
+ end
152
+
153
+ def current_index_fits_opended_file?(index) # all that have been opened are tallied
154
+ index < file_content_lens[0..loaded_file_index].reduce(:+)
155
+ end
156
+
157
+ # returning array of size
158
+ def read_data_at (index,size=2)
159
+ lookup=loaded_file_index==0 ? index : index-file_content_lens[0..loaded_file_index-1].reduce(:+)
160
+ #index - all other lengths, so we get a relative index.
161
+ rb = loaded_file_data.count-1 # right bound
162
+ # puts "largest possible #{rb}, lookup #{lookup}"
163
+ jump = lookup+size-1
164
+ jump = rb if jump > rb
165
+ result=loaded_file_data[lookup..jump]
166
+ # puts "#{lookup } #{jump} #{result}"
167
+ # puts "giving data size #{result.count}"
168
+ result
169
+ end
170
+
171
+ #index
172
+ def load_from_file(fi)
173
+ f=File.new(files[fi], "r")
174
+ data=f.gets(nil)
175
+ f.close
176
+ self.loaded_file_index = fi
177
+ parse_loaded data
178
+ end
179
+
180
+ def parse_loaded data
181
+ self.loaded_file_data=Marshal.load(data)
182
+ end
183
+
184
+ def addlist list, delay=0
185
+ self.filelists.push list
186
+ self.filelist_delays.push delay.to_i
187
+ end
188
+
189
+ def write(wave_data,delay=0)
190
+ full=wave_data.dps
191
+ # puts "--> values: #{full.join(', ')}"
192
+ spli=App.split_array(full, App::CHUNK_SIZE)
193
+ spli.each_with_index do |array,i|
194
+ self.files.push "../tmp/#{object_id}_#{i}"
195
+ self.file_content_lens.push array.count
196
+ File.open(self.files.last, 'w') do |fout|
197
+ fout.puts Marshal.dump(array)
198
+ end
199
+ end
200
+ # puts "written #{spli.count} tmp files"
201
+ end
202
+ end