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