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