music_coder 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/tone.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# a sound to be played.
|
2
|
+
# * a Chord is made of these.
|
3
|
+
class Tone
|
4
|
+
# duration of the tone in seconds.
|
5
|
+
attr_accessor :frames
|
6
|
+
# Wave for one single wave in the tone. (Repeated for #frames)
|
7
|
+
attr_accessor :wave
|
8
|
+
# Fader for amplitude or volume (loudness), 0 to 1. 0 is silent.
|
9
|
+
# Fader.final is relative to Fader.start.
|
10
|
+
attr_accessor :amp
|
11
|
+
# Fader for tone frequency (HZ, cycles/second).
|
12
|
+
# Fader.final is relative to Fader.start.
|
13
|
+
attr_accessor :freq
|
14
|
+
|
15
|
+
# args:: a Hash containing attributes
|
16
|
+
def initialize(args = nil)
|
17
|
+
@frames = 0
|
18
|
+
@wave = Wave.new
|
19
|
+
@amp = Fader.new(0.5,0,MathUtils::GR)
|
20
|
+
@freq = Fader.new(220,0,MathUtils::GR)
|
21
|
+
init_hash(args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def saturation= val
|
25
|
+
wave.saturation= val
|
26
|
+
end
|
27
|
+
def saturations
|
28
|
+
wave.saturations
|
29
|
+
end
|
30
|
+
def detail
|
31
|
+
wave.detail
|
32
|
+
end
|
33
|
+
# returns a buffer with a chord (collection of Tone whos collective amplitude equals
|
34
|
+
# the amplitude set in tone) in #wd. one tone on each element
|
35
|
+
# name:: String containing the chord name. Range, strings in Composer.chords
|
36
|
+
# tone:: Tone settings to use for each tone within the chord. Tone#freq#start
|
37
|
+
# will be ignored since we are using the note.
|
38
|
+
# element:: Element to save the used tones and notes to.
|
39
|
+
def chord(note, name, element)
|
40
|
+
out=Buffer.new
|
41
|
+
chrs=Composer.chord_notes note.note, name
|
42
|
+
notes_total = chrs.count
|
43
|
+
chrs.each_with_index do |v,i|
|
44
|
+
ltone = deep_copy
|
45
|
+
lamp = 1.0/notes_total # lower vol of each tone
|
46
|
+
ltone.amp.start *= lamp # lower vol of each tone
|
47
|
+
ltone.amp.final *= lamp # lower range of vol
|
48
|
+
# use a reduced amplitude. (by tones in chord).
|
49
|
+
# mulitplied by givin amplitude
|
50
|
+
realnote = note+v
|
51
|
+
ltone.note= realnote # set to right freq
|
52
|
+
|
53
|
+
out.push ltone.out(i,notes_total)
|
54
|
+
element.add_t ltone
|
55
|
+
element.notes.push realnote
|
56
|
+
end
|
57
|
+
out
|
58
|
+
end
|
59
|
+
|
60
|
+
# output the WaveData for a full tone (chirp). All sound created flows into this atm.
|
61
|
+
# freq_exp:: default 0. 0 means linear. higher means it reachs the frequency at the end of it's range later.
|
62
|
+
def out
|
63
|
+
# puts "out amp: #{amp.start}"
|
64
|
+
buffer_len = frames
|
65
|
+
inc_x = 0.0
|
66
|
+
data = WaveData.new
|
67
|
+
lfreq = freq.start
|
68
|
+
lamp = amp.start
|
69
|
+
wave_exp = wave.detail.exp
|
70
|
+
freq_exp = freq.exp
|
71
|
+
amp_exp = amp.exp
|
72
|
+
wave_into=0
|
73
|
+
while data.dps.count < buffer_len do
|
74
|
+
wave_data = wave.out(lfreq, lamp, wave_into)
|
75
|
+
data + wave_data
|
76
|
+
inc_x += wave_data.count
|
77
|
+
x = (inc_x.to_f / buffer_len)
|
78
|
+
|
79
|
+
#freq
|
80
|
+
freq_multiplier = x # when exp is off
|
81
|
+
#fade exponentially if on.
|
82
|
+
freq_multiplier = x ** (1.0 /((1.0/freq_exp)*x)) if freq_exp
|
83
|
+
lfreq = freq.start + freq_multiplier*freq.final
|
84
|
+
|
85
|
+
#amp
|
86
|
+
amp_multiplier = x # when exp is off
|
87
|
+
#fade exponentially if on.
|
88
|
+
amp_multiplier = x ** (1.0 /((1.0/amp_exp)*x)) if amp_exp
|
89
|
+
lamp = amp.start + amp_multiplier*amp.final
|
90
|
+
|
91
|
+
#wave
|
92
|
+
wave_into = x # when exp is off
|
93
|
+
#fade exponentially if on.
|
94
|
+
wave_into = x ** (1.0 /((1.0/wave_exp)*x)) if wave_exp
|
95
|
+
|
96
|
+
end
|
97
|
+
data.fit_to buffer_len
|
98
|
+
data
|
99
|
+
end
|
100
|
+
|
101
|
+
# setting it absolute
|
102
|
+
def set_freq_final_no_relative(final)
|
103
|
+
dif=final - freq.start
|
104
|
+
freq.final=dif
|
105
|
+
end
|
106
|
+
|
107
|
+
# set #freq based off a Note
|
108
|
+
def note=(note)
|
109
|
+
freq.start = note.freq
|
110
|
+
end
|
111
|
+
def note_end=(note)
|
112
|
+
# freq.final = note.freq
|
113
|
+
end
|
114
|
+
|
115
|
+
# return the note closest to the set frequency
|
116
|
+
def note
|
117
|
+
out = Note.new
|
118
|
+
fre = freq.start
|
119
|
+
linear_frequency = Math.log(fre/220.0,2.0) + 4
|
120
|
+
# puts "linear_frequency #{linear_frequency}"
|
121
|
+
out.octave= ( linear_frequency ).floor
|
122
|
+
cents = 1200.0 * (linear_frequency - out.octave)
|
123
|
+
not_wrap = (cents / 99.0)
|
124
|
+
# puts "note no wrap #{not_wrap}"
|
125
|
+
out.note = not_wrap.floor % 12
|
126
|
+
out
|
127
|
+
end
|
128
|
+
|
129
|
+
# make it end with an amlitute of 0 (complete fade out).
|
130
|
+
def fade
|
131
|
+
amp.final = -amp.start
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
end
|
data/lib/tone_part.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# two tones that can be faded between
|
2
|
+
class TonePart
|
3
|
+
#Fader
|
4
|
+
attr_accessor :tones
|
5
|
+
attr_accessor :max_frames
|
6
|
+
attr_accessor :tone_single
|
7
|
+
attr_accessor :tone_count
|
8
|
+
def initialize(m,t1=Tone.new,t2=Tone.new)
|
9
|
+
@tones = Fader.new(t1,t2,0)
|
10
|
+
@max_frames = m
|
11
|
+
self.frames = m
|
12
|
+
@tone_count = 1
|
13
|
+
@tone_single = t1
|
14
|
+
end
|
15
|
+
|
16
|
+
def tone i=0
|
17
|
+
return tone_single if tone_count == 1
|
18
|
+
i==1 ? @tones.final : @tones.start
|
19
|
+
end
|
20
|
+
|
21
|
+
def max_frames= m
|
22
|
+
@max_frames = m
|
23
|
+
# set if none
|
24
|
+
self.frames = m if @tones.start.frames == 0 && @tones.final.frames == 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def frames= val
|
28
|
+
@tones.start.frames = val
|
29
|
+
@tones.final.frames = val
|
30
|
+
end
|
31
|
+
|
32
|
+
# get main freq
|
33
|
+
def freq
|
34
|
+
tones.start.freq.start
|
35
|
+
end
|
36
|
+
#get main note
|
37
|
+
def note
|
38
|
+
tones.start.note
|
39
|
+
end
|
40
|
+
|
41
|
+
def freq= val
|
42
|
+
@tones.start.freq.start = val
|
43
|
+
@tones.final.freq.start = val
|
44
|
+
end
|
45
|
+
|
46
|
+
def amp_mult(factor)
|
47
|
+
tones.start.amp.start *= factor
|
48
|
+
#puts "amp: #{tone.tones.start.amp.start}"
|
49
|
+
tones.start.amp.final *= factor
|
50
|
+
tones.final.amp.start *= factor
|
51
|
+
tones.final.amp.final *= factor
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
#return tone with mixed settings of tones#start with tones#final.
|
56
|
+
#into:: how much of the final tone is mixed in. 0 is none. Range: 0 to 1
|
57
|
+
def out(into)
|
58
|
+
return tone_single if tone_count == 1
|
59
|
+
|
60
|
+
out = tones.start.deep_copy
|
61
|
+
|
62
|
+
#wave
|
63
|
+
range = tones.final.wave.detail.start - tones.start.wave.detail.start
|
64
|
+
out.wave.detail.start += range*into
|
65
|
+
range = tones.final.wave.detail.final - tones.start.wave.detail.final
|
66
|
+
out.wave.detail.final += range*into
|
67
|
+
range = tones.final.wave.saturations.start - tones.start.wave.saturations.start
|
68
|
+
out.wave.saturations.start += range*into
|
69
|
+
range = tones.final.wave.saturations.final - tones.start.wave.saturations.final
|
70
|
+
out.wave.saturations.final += range*into
|
71
|
+
|
72
|
+
#frames
|
73
|
+
range = tones.final.frames - tones.start.frames
|
74
|
+
out.frames += range*into
|
75
|
+
# puts "frames range #{out.frames}"
|
76
|
+
|
77
|
+
#freq
|
78
|
+
range = tones.final.freq.start - tones.start.freq.start
|
79
|
+
out.freq.start += range*into
|
80
|
+
range = tones.final.freq.final - tones.start.freq.final
|
81
|
+
out.freq.final += range*into
|
82
|
+
range = tones.final.freq.exp_no_nil - tones.start.freq.exp_no_nil
|
83
|
+
out.freq.exp = out.freq.exp_no_nil + range*into
|
84
|
+
|
85
|
+
#amp
|
86
|
+
range = tones.final.amp.start - tones.start.amp.start
|
87
|
+
out.amp.start += range*into
|
88
|
+
range = tones.final.amp.final - tones.start.amp.final
|
89
|
+
out.amp.final += range*into
|
90
|
+
range = tones.final.amp.exp_no_nil - tones.start.amp.exp_no_nil
|
91
|
+
out.amp.exp = out.amp.exp_no_nil + range*into
|
92
|
+
|
93
|
+
out
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/lib/tone_seq.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# a sequence of TonePart that will play sequentially. These are highest level sounds without timing.
|
2
|
+
class ToneSeq
|
3
|
+
#Array of TonePart
|
4
|
+
attr_accessor :toneparts
|
5
|
+
def initialize()
|
6
|
+
@toneparts = []
|
7
|
+
make(1)
|
8
|
+
end
|
9
|
+
def tonepart i=0
|
10
|
+
@toneparts[i]
|
11
|
+
end
|
12
|
+
def frames= val
|
13
|
+
@toneparts.each {|tp| tp.frames = val}
|
14
|
+
end
|
15
|
+
def fade
|
16
|
+
@toneparts.each {|tp|
|
17
|
+
tp.tones.start.fade
|
18
|
+
tp.tones.final.fade
|
19
|
+
}
|
20
|
+
self
|
21
|
+
end
|
22
|
+
def len= set
|
23
|
+
@toneparts.each {|tp|
|
24
|
+
tp.max_frames = set / toneparts.count
|
25
|
+
tp.frames = set / toneparts.count
|
26
|
+
}
|
27
|
+
end
|
28
|
+
# add num TonePart to self, with it's max allowable frames as len.
|
29
|
+
def make(num,len=0)
|
30
|
+
# puts "ToneSeq: making #{num} parts in tone"
|
31
|
+
num.times { self.toneparts.push TonePart.new((len.to_f/num).round) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def render(parent_hit_index=nil)
|
35
|
+
files=FileList.new
|
36
|
+
toneparts.each do |tp|
|
37
|
+
files.write tp.out(parent_hit_index).out
|
38
|
+
end
|
39
|
+
files
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/wave.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#a waveform. this is looped to create a Tone.
|
2
|
+
class Wave
|
3
|
+
# stores the detail amount in a #Fader.start. nil means use sin wave during generation.
|
4
|
+
# Fader.start is the main
|
5
|
+
# Fader.final is at the end. nil means same as wave start
|
6
|
+
attr_accessor :detail
|
7
|
+
#saturation effect (a random fluctuation on each data point to the cycle).
|
8
|
+
#degree:: The ammount of saturation. Higher is more, 0 is none. range: 0 to 1
|
9
|
+
attr_accessor :saturations
|
10
|
+
# hack to dramatically speed it up when on.
|
11
|
+
attr_accessor :cache_wave, :old_wave
|
12
|
+
|
13
|
+
def initialize(start=512,final=512,exp=0)
|
14
|
+
@detail = Fader.new(start, final, exp)
|
15
|
+
@saturations = Fader.new(0,0,0)
|
16
|
+
@cache_wave=Array.new(2)
|
17
|
+
@old_wave = nil#Wave.new # so false first time with is_eql
|
18
|
+
end
|
19
|
+
|
20
|
+
def saturation= val
|
21
|
+
saturations.start = val
|
22
|
+
saturations.final = val
|
23
|
+
end
|
24
|
+
|
25
|
+
# return WaveData of data for a single wave cycle
|
26
|
+
# len:: length in frames
|
27
|
+
# amp:: amplitude or volume (loudness), 0 to 1. 0 is silent
|
28
|
+
# wave_into:: how much of the final wave data is used this time. range: 0 to 1.
|
29
|
+
#
|
30
|
+
def out(freq, amp = 1, wave_into = 1)
|
31
|
+
final=WaveData.new
|
32
|
+
start=WaveData.new
|
33
|
+
if is_eql old_wave
|
34
|
+
# puts "using cahce"
|
35
|
+
start = cache_wave[0]
|
36
|
+
final = cache_wave[1]
|
37
|
+
else
|
38
|
+
# puts "not using cahce"
|
39
|
+
self.cache_wave=[nil,nil]
|
40
|
+
self.old_wave=self.dup
|
41
|
+
self.cache_wave[0] =WaveData.new(MathUtils.sinwave(detail.start, saturations.start))
|
42
|
+
self.cache_wave[1] =WaveData.new(MathUtils.sinwave(detail.final, saturations.final))
|
43
|
+
start = cache_wave[0]
|
44
|
+
final = cache_wave[1]
|
45
|
+
end
|
46
|
+
|
47
|
+
data = []
|
48
|
+
len = Composer.samplerate / freq
|
49
|
+
|
50
|
+
len.to_i.times do |i|
|
51
|
+
progress=i.to_f/len # from 0 to 1
|
52
|
+
if (detail.start > 0)
|
53
|
+
val = start.interpolate progress
|
54
|
+
# merging two waveforms
|
55
|
+
if (detail.final > 0)
|
56
|
+
val2 = final.interpolate progress
|
57
|
+
val_range = val2-val
|
58
|
+
val = val + val_range*wave_into
|
59
|
+
end
|
60
|
+
else # normal sign wave
|
61
|
+
raise "Error, wave detail isn't > 0"
|
62
|
+
# val = Math.sin(2.0*Math::PI*progress)
|
63
|
+
end
|
64
|
+
# puts "amp #{amp}, val #{val}"
|
65
|
+
val *= amp # reduce volume by this
|
66
|
+
data[i] = val
|
67
|
+
end
|
68
|
+
# puts "--> values: #{data.join(', ')}"
|
69
|
+
result=WaveData.new(data)
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
|
73
|
+
def is_eql(other)
|
74
|
+
vars_eql?(other, ['@detail','@saturations'])
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/lib/wave_data.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# basically an array of data of a fully rendered down sound.
|
2
|
+
class WaveData
|
3
|
+
# the data points in an Array of Float. range -1 to 1 unless you like distortion.
|
4
|
+
attr_accessor :dps
|
5
|
+
# Array containing blanks frames at the start as Integer at [0], end at [1]
|
6
|
+
attr_accessor :blanks
|
7
|
+
attr_accessor :out_file
|
8
|
+
def initialize(dps=Array.new,file=nil)
|
9
|
+
@dps = dps.dup
|
10
|
+
@blanks = [0,0]
|
11
|
+
@out_file=file
|
12
|
+
end
|
13
|
+
|
14
|
+
#TODO
|
15
|
+
def included_blanks
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
#return the datapoints within a range of indexs.
|
20
|
+
def get(start, final=nil)
|
21
|
+
final ||= dps.count
|
22
|
+
WaveData.new dps.slice(start..final)
|
23
|
+
end
|
24
|
+
|
25
|
+
# fit to the size of #dps to len by removing or adding tailing points
|
26
|
+
# fade_frames:: stop the annoying popping at end of sound by fading out this many frames
|
27
|
+
def fit_to(len, fade_frames=250)
|
28
|
+
meant_to_be = len
|
29
|
+
self.dps.pop(dps.count- meant_to_be) if meant_to_be < dps.count
|
30
|
+
while meant_to_be > dps.count # too short
|
31
|
+
self.dps.push 0
|
32
|
+
end
|
33
|
+
# stop the annoying popping
|
34
|
+
if dps.count > fade_frames
|
35
|
+
fade_frames.times do |i|
|
36
|
+
dps[dps.count-1-i] *= i.to_f / fade_frames
|
37
|
+
end
|
38
|
+
end
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
#duplicate the current data n times.
|
43
|
+
def dup(n=1)
|
44
|
+
n.times{dps.concat(dps)}
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
#return the value of a wave at the specifed progress 0 to 1
|
49
|
+
def interpolate(progress)
|
50
|
+
progress *= dps.count # range from 0 to wavedata length.
|
51
|
+
val = dps[progress.to_i] # truncate progress.
|
52
|
+
#puts progress.to_i
|
53
|
+
# now add the interpolation to the next point
|
54
|
+
if (progress < dps.count-1) # avoid error on last point
|
55
|
+
dif_x = progress.to_i+1 - progress # how far to next datapoint? 0 to 1
|
56
|
+
dif_y = dps[progress.to_i+1] - dps[progress.to_i]
|
57
|
+
interpolation = dif_x * dif_y
|
58
|
+
val += interpolation
|
59
|
+
end
|
60
|
+
return val
|
61
|
+
end
|
62
|
+
|
63
|
+
# return num of #dps
|
64
|
+
def count
|
65
|
+
dps.count
|
66
|
+
end
|
67
|
+
|
68
|
+
def add(array)
|
69
|
+
self.dps+= array
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
#appends Array data to dps
|
74
|
+
def +(data)
|
75
|
+
self.dps+= data.dps
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
#append silence to #dps.
|
80
|
+
#frames:: duration of the silence in seconds
|
81
|
+
#default: one beat.
|
82
|
+
#val:: constant middle value in buffer.
|
83
|
+
#default: 0 Range: -1 to 1
|
84
|
+
def silence(frames, val = 0)
|
85
|
+
d=WaveData.new(Array.new(frames, val))
|
86
|
+
self+d if frames > 0
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# combine my dps with WaveData wave.
|
91
|
+
# adding length if necessary.
|
92
|
+
def mix(wave)
|
93
|
+
other = wave.dps
|
94
|
+
longest = dps.count > other.count ? dps : other
|
95
|
+
longest.each_with_index do |a,i|
|
96
|
+
my_v = (dps[i].nil? ? 0 : dps[i])
|
97
|
+
oth_v = (other[i].nil? ? 0 : other[i])
|
98
|
+
dps[i] = my_v + oth_v
|
99
|
+
end
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|