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/api/api.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#base class for most of the things the user will touch directly
|
2
|
+
class Api
|
3
|
+
def initialize
|
4
|
+
@parents=[]
|
5
|
+
end
|
6
|
+
# Add things to me
|
7
|
+
def << toadd
|
8
|
+
# puts "type: #{toadd.class}"
|
9
|
+
case toadd
|
10
|
+
when Array
|
11
|
+
# puts "recognised array"
|
12
|
+
toadd.each do |ta|
|
13
|
+
r = add_single ta
|
14
|
+
raise "This type is not recongnised." if r ==false
|
15
|
+
end
|
16
|
+
else
|
17
|
+
r=add_single toadd
|
18
|
+
raise "This type is not recongnised." if r==false
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
# Delete things from me
|
23
|
+
def >> todel
|
24
|
+
# puts "type: #{todel.class}"
|
25
|
+
case todel
|
26
|
+
when Array
|
27
|
+
# puts "deleting array #{todel}"
|
28
|
+
todel.each do |ta|
|
29
|
+
r = del_single ta
|
30
|
+
raise "This type is not recongnised." if r ==false
|
31
|
+
end
|
32
|
+
else
|
33
|
+
r=del_single todel
|
34
|
+
raise "This type is not recongnised." if r==false
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def parent index=0
|
40
|
+
@parents[index]
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
def add_parent obj
|
45
|
+
@parents << obj
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
end#class
|
50
|
+
|
51
|
+
def load_state
|
52
|
+
f=File.open("../output/text/saved.rb",'r')
|
53
|
+
content=f.read
|
54
|
+
# puts content
|
55
|
+
App.out= YAML.load(content)
|
56
|
+
f.close
|
57
|
+
end
|
58
|
+
|
59
|
+
def save_state
|
60
|
+
App.out.write_text_files
|
61
|
+
end
|
62
|
+
|
63
|
+
def render
|
64
|
+
App.out.render
|
65
|
+
end
|
66
|
+
|
67
|
+
def make
|
68
|
+
App.out.make_audio_file
|
69
|
+
end
|
70
|
+
|
71
|
+
def beat i=1
|
72
|
+
z=Composer.beat i
|
73
|
+
z.to_i
|
74
|
+
end
|
75
|
+
def bar i=1
|
76
|
+
z=Composer.beat i*4
|
77
|
+
z.to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def queue dist
|
81
|
+
App.out.snddists<<dist.instance_variable_get(:@dist)
|
82
|
+
end
|
83
|
+
|
84
|
+
# _rendered files
|
85
|
+
def clear
|
86
|
+
App.clear_ready
|
87
|
+
end
|
88
|
+
|
89
|
+
def bpm_set val
|
90
|
+
Composer.bpm = val
|
91
|
+
end
|
92
|
+
def bpm
|
93
|
+
Composer.bpm
|
94
|
+
end
|
95
|
+
def compute
|
96
|
+
render
|
97
|
+
save_state
|
98
|
+
make
|
99
|
+
end
|
100
|
+
# send str to be logged.
|
101
|
+
# level:: it won't be logged unless Logger.level is at or above this level.
|
102
|
+
def log str, level=3
|
103
|
+
App.logger.log str, level
|
104
|
+
end
|
105
|
+
# set Logger.level. Higher means more logging. 0 is silent.
|
106
|
+
def log_level set
|
107
|
+
App.logger.level = set
|
108
|
+
end
|
data/lib/api/dist.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# a consecutave sequence of morphable tones (TonePart)
|
2
|
+
# of varying lengths, and rate of morphs, played without gaps.
|
3
|
+
class Dist < Api
|
4
|
+
attr_accessor :dist
|
5
|
+
def initialize
|
6
|
+
super
|
7
|
+
@dist=SndDist.new
|
8
|
+
@hits=HitSq.new
|
9
|
+
@hits.add_parent self
|
10
|
+
persist_hits
|
11
|
+
end
|
12
|
+
# delete another Dist
|
13
|
+
def del todel
|
14
|
+
@dist.snd.delete todel.dist
|
15
|
+
self
|
16
|
+
end
|
17
|
+
# set the total length in frames
|
18
|
+
def length= set
|
19
|
+
@dist.len = set
|
20
|
+
@dist.tss.each {|tss| tss.len = set }
|
21
|
+
self
|
22
|
+
end
|
23
|
+
# get the total length in frames
|
24
|
+
def length
|
25
|
+
@dist.len
|
26
|
+
end
|
27
|
+
# delete all hits
|
28
|
+
def clear_hits
|
29
|
+
@hits.hits = []
|
30
|
+
persist_hits
|
31
|
+
self
|
32
|
+
end
|
33
|
+
# get a child
|
34
|
+
def [] i
|
35
|
+
child=@dist.get_children[i]
|
36
|
+
raise "This Dist has no child at index #{i}. " +
|
37
|
+
"It has #{branches} children." if child.nil?
|
38
|
+
d=Dist.new
|
39
|
+
d.dist=child
|
40
|
+
d.make_hits
|
41
|
+
d
|
42
|
+
end
|
43
|
+
# count children
|
44
|
+
def branches
|
45
|
+
@dist.get_children.count
|
46
|
+
end
|
47
|
+
# getter for HitSq. Will persist any changes you make to it.
|
48
|
+
def hits
|
49
|
+
@hits
|
50
|
+
end
|
51
|
+
# getter for Snd. Will persist any changes you make to it.
|
52
|
+
def snd i=0
|
53
|
+
snd=Snd.new
|
54
|
+
snd.add_parent self
|
55
|
+
new=@dist.tss[i]
|
56
|
+
raise "Dist has no sound at index #{i}. It has #{sounds} sounds." if new.nil?
|
57
|
+
snd.snd = new
|
58
|
+
snd
|
59
|
+
end
|
60
|
+
# make a new sound with len
|
61
|
+
def make_sound
|
62
|
+
snd=Snd.new
|
63
|
+
snd.snd.len = @dist.len
|
64
|
+
self<<snd
|
65
|
+
self
|
66
|
+
end
|
67
|
+
# count sounds
|
68
|
+
def sounds
|
69
|
+
@dist.tss.count
|
70
|
+
end
|
71
|
+
# (internal use only) copy our hits down to the underlining object
|
72
|
+
def persist_hits
|
73
|
+
@dist.hits = hits.hits
|
74
|
+
@dist.hits = [0.0] if hits.hits.count == 0
|
75
|
+
self
|
76
|
+
end
|
77
|
+
protected
|
78
|
+
# (internal use only) create our hits from the underlining object
|
79
|
+
def make_hits
|
80
|
+
@hits.hits = @dist.hits
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
# adds a sound if it's valid to do so
|
86
|
+
def validate_snd! val
|
87
|
+
raise "This Dist can't have sounds, it has children. " if branches > 0
|
88
|
+
@dist.tss << val
|
89
|
+
val.toneparts.each {|tp|
|
90
|
+
# puts "RUNNING #{length / val.toneparts.count.to_f}"
|
91
|
+
tp.max_frames = length / val.toneparts.count }
|
92
|
+
end
|
93
|
+
# adds a dist if it's valid to do so
|
94
|
+
def validate_dist! val
|
95
|
+
raise "This Dist can't have children, it has sounds. " if sounds > 0
|
96
|
+
@dist.add val
|
97
|
+
end
|
98
|
+
# Add hits, sounds or other dists to me.
|
99
|
+
def add_single toadd
|
100
|
+
case toadd
|
101
|
+
when HitSq, Float, Integer
|
102
|
+
@hits << toadd
|
103
|
+
persist_hits
|
104
|
+
when Snd
|
105
|
+
validate_snd! toadd.snd
|
106
|
+
when Dist
|
107
|
+
validate_dist! toadd.dist
|
108
|
+
else
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
true
|
112
|
+
end
|
113
|
+
# Delete hits, sounds or other dists from my lists.
|
114
|
+
def del_single todel
|
115
|
+
case todel
|
116
|
+
when HitSq, Float, Integer
|
117
|
+
@hits >> todel
|
118
|
+
persist_hits
|
119
|
+
when Snd
|
120
|
+
@dist.tss.delete todel.snd
|
121
|
+
when Dist
|
122
|
+
@dist.snd.delete (todel.dist)
|
123
|
+
else
|
124
|
+
return false
|
125
|
+
end
|
126
|
+
true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# shortcut e.g. 10.Dist for a dist with length of 10 frames
|
131
|
+
class Integer
|
132
|
+
def Dist
|
133
|
+
h=Dist.new
|
134
|
+
h.length = self.to_i
|
135
|
+
h
|
136
|
+
end
|
137
|
+
end
|
138
|
+
# shortcut e.g. 0.0.Dist for the most used, a single hit at 0.0
|
139
|
+
class Float
|
140
|
+
def Dist
|
141
|
+
h=Dist.new
|
142
|
+
h<<self
|
143
|
+
h
|
144
|
+
end
|
145
|
+
end
|
data/lib/api/hit_sq.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# a pattern of hits to time a Snd
|
2
|
+
class HitSq < Api
|
3
|
+
attr_accessor :hits
|
4
|
+
def initialize
|
5
|
+
@hits=[]
|
6
|
+
super
|
7
|
+
end
|
8
|
+
private
|
9
|
+
# delete value
|
10
|
+
def del_single todel
|
11
|
+
@hits.delete todel.to_f
|
12
|
+
self
|
13
|
+
end
|
14
|
+
def validate! input
|
15
|
+
val = input.to_f
|
16
|
+
raise "Hit is too high #{input}. Range is 0 to 1." if val > 1.0
|
17
|
+
raise "Hit is too low #{input}. Range is 0 to 1." if val < 0.0
|
18
|
+
|
19
|
+
#dup?
|
20
|
+
a = hits.dup
|
21
|
+
a << val
|
22
|
+
if a.uniq.length != a.length
|
23
|
+
log "Warning: skipping duplicate hit.", 3
|
24
|
+
else
|
25
|
+
@hits << val
|
26
|
+
end
|
27
|
+
@parents.each {|par| par.persist_hits}
|
28
|
+
end
|
29
|
+
# Add hits.
|
30
|
+
def add_single toadd
|
31
|
+
case toadd
|
32
|
+
when Float, Integer
|
33
|
+
validate! toadd
|
34
|
+
when HitSq
|
35
|
+
toadd.hits.each { |val| validate! val }
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
public
|
41
|
+
#Adds into #hits.
|
42
|
+
#possible_hits:: number of hits that can occur. Must be int
|
43
|
+
#chance:: chance a hit will be included. range: 0 to 1
|
44
|
+
#ignore_first:: skip the first n possible hits
|
45
|
+
#ignore_first:: skip the last n possible hits
|
46
|
+
#e.g. disperse_hits(16,1,4,4) makes this pattern [-|-|-|-|+|+|+|+|+|+|+|+|-|-|-|-|]
|
47
|
+
def eqly_spaced(possible_hits = 4, chance = 1, ignore_first=0, ignore_last=0)
|
48
|
+
possible_hits.times do |i|
|
49
|
+
if ignore_first <= i && possible_hits - ignore_last > i
|
50
|
+
delay = i/possible_hits.to_f
|
51
|
+
@hits.push delay if (rand + chance >= 1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
#return number of hits
|
57
|
+
def count
|
58
|
+
hits.count
|
59
|
+
end
|
60
|
+
# Shift all hits by val, no validation atm
|
61
|
+
def move(val=0.5)
|
62
|
+
self.hits.collect! {|x|
|
63
|
+
z = (x+val)
|
64
|
+
z = (z*1000).round / 1000.0 # rounding
|
65
|
+
x = z
|
66
|
+
} # delay all
|
67
|
+
self
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# shortcut e.g. 0.HitSq for the most used, a single hit at 0
|
71
|
+
class Integer
|
72
|
+
def HitSq
|
73
|
+
h=HitSq.new
|
74
|
+
h<<self
|
75
|
+
h
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# shortcut e.g. 0.5.HitSq
|
79
|
+
class Float
|
80
|
+
def HitSq
|
81
|
+
h=HitSq.new
|
82
|
+
h<<self
|
83
|
+
h
|
84
|
+
end
|
85
|
+
end
|
86
|
+
class Array
|
87
|
+
def HitSq
|
88
|
+
h=HitSq.new
|
89
|
+
h<<self
|
90
|
+
h
|
91
|
+
end
|
92
|
+
|
93
|
+
#return a new HitSq. a shortcut for HitSq.new.move(val)
|
94
|
+
def move(val)
|
95
|
+
self.collect! {|x| x=x+val} # delay all
|
96
|
+
h=HitSq.new
|
97
|
+
h<<self
|
98
|
+
h
|
99
|
+
end
|
100
|
+
end
|
101
|
+
class Integer
|
102
|
+
#a shortcut. e.g. 4.eqly_spaced gives you HitSq.new.eqly_spaced(4)
|
103
|
+
def eqly_spaced(chance = 1, ignore_first=0, ignore_last=0)
|
104
|
+
h=HitSq.new
|
105
|
+
possible_hits=self
|
106
|
+
h.eqly_spaced(possible_hits, chance, ignore_first, ignore_last)
|
107
|
+
end
|
108
|
+
end
|
data/lib/api/note.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# A musical note.
|
2
|
+
# E.g. A sharp in octave 3.
|
3
|
+
class Note
|
4
|
+
# note without octave. from 0 to 11 starting at A ending in G#
|
5
|
+
attr_accessor :note
|
6
|
+
# octave, starts at 0, an 'A' in '5' is 440 hz
|
7
|
+
attr_accessor :octave
|
8
|
+
def initialize(note = 0, octave = 4)
|
9
|
+
if note.class == String
|
10
|
+
@note = Composer.note_m(note)
|
11
|
+
else
|
12
|
+
@note = note
|
13
|
+
end
|
14
|
+
@octave = octave
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns a value up n semitones. (changes octave where necessary)
|
18
|
+
def +(n = 1)
|
19
|
+
i=0
|
20
|
+
out = deep_copy
|
21
|
+
while i < n do
|
22
|
+
out = Note.new(out.note+1, out.octave)
|
23
|
+
out = Note.new(0, out.octave+1) if out.note==12
|
24
|
+
i+=1
|
25
|
+
end
|
26
|
+
out
|
27
|
+
end
|
28
|
+
# returns a value down n semitones. (changes octave where necessary)
|
29
|
+
def -(n = 1)
|
30
|
+
i=0
|
31
|
+
out = deep_copy
|
32
|
+
while i < n do
|
33
|
+
out = Note.new(out.note-1, out.octave)
|
34
|
+
out = Note.new(11, out.octave-1) if out.note==-1
|
35
|
+
i+=1
|
36
|
+
end
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
#return the frequency (HZ) of a note.
|
41
|
+
def freq
|
42
|
+
raise "no note or oct given" if (!note || !octave)
|
43
|
+
a=2 ** (octave.to_f-1)
|
44
|
+
b=1.059463 ** note.to_f
|
45
|
+
out = 27.5*a*b
|
46
|
+
return out
|
47
|
+
end
|
48
|
+
|
49
|
+
# return true if this note has the same values as another note.
|
50
|
+
def is_eql(other)
|
51
|
+
vars_eql? other
|
52
|
+
end
|
53
|
+
|
54
|
+
# create a sound based off this note.
|
55
|
+
def Snd
|
56
|
+
s=freq.Snd
|
57
|
+
s
|
58
|
+
end
|
59
|
+
end
|
data/lib/api/snd.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# a consecutave sequence of morphable tones (TonePart)
|
2
|
+
# of varying lengths, and rate of morphs, played without gaps.
|
3
|
+
class Snd < Api
|
4
|
+
def initialize
|
5
|
+
@snd=ToneSeq.new
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def tonepart i=0
|
10
|
+
@snd.tonepart i
|
11
|
+
end
|
12
|
+
|
13
|
+
def tone i=0, j=0
|
14
|
+
@snd.tonepart(j).tone(i)
|
15
|
+
end
|
16
|
+
# set length
|
17
|
+
def length= val
|
18
|
+
@snd.frames=val
|
19
|
+
self
|
20
|
+
end
|
21
|
+
def t i=0
|
22
|
+
child = @snd.toneparts[i]
|
23
|
+
raise "This Snd has no tone at index #{i}. " +
|
24
|
+
"It has #{count} tones." if child.nil?
|
25
|
+
child
|
26
|
+
end
|
27
|
+
# number of tones
|
28
|
+
def count
|
29
|
+
snd.toneparts.count
|
30
|
+
end
|
31
|
+
def fade
|
32
|
+
snd.fade
|
33
|
+
end
|
34
|
+
attr_accessor :snd
|
35
|
+
private
|
36
|
+
# Add hits, sounds or other dists to me.
|
37
|
+
def add_single toadd
|
38
|
+
case toadd
|
39
|
+
when Snd
|
40
|
+
@snd = toadd.instance_variable_get(:@snd)
|
41
|
+
else
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class Fixnum
|
50
|
+
def Snd
|
51
|
+
a = Snd.new
|
52
|
+
a.t.freq = self
|
53
|
+
a
|
54
|
+
end
|
55
|
+
end
|
56
|
+
class Float
|
57
|
+
def Snd
|
58
|
+
a = Snd.new
|
59
|
+
a.t.freq = self
|
60
|
+
a
|
61
|
+
end
|
62
|
+
end
|