music_coder 0.7.2 → 0.9.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.
@@ -214,4 +214,35 @@ end
214
214
  #sets Composer#scale
215
215
  def set_scale sc
216
216
  Composer.scale = sc
217
+ end
218
+ # the typical series of commands at the start of a simple new music coder file.
219
+ def start
220
+ log_level 1
221
+ clear
222
+ set_bpm 128
223
+ end
224
+ ## return first dist in queue
225
+ #def root
226
+ # App.out.snddists[0]
227
+ #end
228
+
229
+
230
+ def random_sound root_f = 440, len = beat(1), f_range = 0.2, tone_num = 1+rand(16), parts = 1+rand(3), max_delay = 0.6
231
+ main = len.Dist
232
+ tone_num.times do |i|
233
+ main << len.Dist
234
+ main.last_born << Snd.new
235
+ snd = main.last_born.snd
236
+ snd.length = len.to_f
237
+ delay = rand*max_delay
238
+ 1.times {delay*=rand}
239
+ snd.toneseq.random(rand(parts), [true,false].sample, delay, 0.333)
240
+ snd.tone.set_freq root_f
241
+ range = root_f * f_range
242
+ snd.tone.set_freq_final root_f - range/2 + rand*range
243
+ snd.toneseq.amp_reduce tone_num
244
+ main.last_born.clear_hits
245
+ main.last_born << delay
246
+ end
247
+ main
217
248
  end
@@ -8,7 +8,49 @@ class Dist < Api
8
8
  @dist=SndDist.new
9
9
  @hits=HitSq.new
10
10
  @hits.add_parent self
11
- persist_hits
11
+ persist_hits(true)
12
+ end
13
+
14
+
15
+ #make children with the hits you need for a bar of dubstep percussion. set their #length to mine
16
+ #this dist has 5 children, in order: bass drum, secondary bass drum, snare, hi hat, second hi hat
17
+ def dubstep_percussion
18
+ self << Dist.new # bass drum
19
+ self.last_born.clear_hits
20
+ self.last_born << [0.0]
21
+ self << Dist.new # 2nd bass drum
22
+ self.last_born.clear_hits
23
+ self.last_born << [0.75, 0.875] #two last drum hits
24
+ self << Dist.new # snare
25
+ self.last_born.clear_hits
26
+ self.last_born << [0.5] # third beat
27
+ self << Dist.new # hi hat
28
+ self.last_born.clear_hits
29
+ self.last_born << [0.375,0.625] # off beats
30
+ self << Dist.new # 2nd hi hat
31
+ self.last_born.clear_hits
32
+ self.last_born << [0.125,0.875] # off beats
33
+ set_child_len length
34
+ self
35
+ end
36
+
37
+
38
+ #evenly disperse each child across len by modifying the length and hits. uses #length
39
+ def space_children_across
40
+ len = self.length
41
+ branches.times do |i|
42
+ hit = i.to_f / branches
43
+ self[i].length = len #(1/branches)* len + hit * len
44
+ self[i].clear_hits
45
+ self[i] << hit
46
+ end
47
+ end
48
+
49
+ #sets the length of all children Dist to val
50
+ def set_child_len val
51
+ branches.times do |i|
52
+ self[i].length = val
53
+ end
12
54
  end
13
55
 
14
56
  #add num TonePart to Snd at i's ToneSeq, with my #length as max.
@@ -16,6 +58,10 @@ class Dist < Api
16
58
  snd(i).toneseq.make(num)
17
59
  end
18
60
 
61
+ #shortcut to create a Mapper, with Mapper#map_to= me
62
+ def Mapper
63
+ Mapper.new(self)
64
+ end
19
65
  # delete another Dist
20
66
  def del todel
21
67
  @dist.snd.delete todel.dist
@@ -106,9 +152,9 @@ class Dist < Api
106
152
  @dist.tss.count
107
153
  end
108
154
  # (internal use only) copy our hits down to the underlining object
109
- def persist_hits
155
+ def persist_hits(is_def = false)
110
156
  @dist.hits = hits.hits
111
- @dist.hits = [0.0] if hits.hits.count == 0
157
+ @dist.hits = [0.0] if is_def && hits.hits.count == 0
112
158
  self
113
159
  end
114
160
  protected
@@ -38,6 +38,66 @@ class HitSq < Api
38
38
  end
39
39
  end
40
40
  public
41
+
42
+ def delete_random chance = 0.5
43
+ to_del = []
44
+ hits.each do |h|
45
+ if rand < chance
46
+ to_del << h
47
+ end
48
+ end
49
+ to_del.each {|d| self.hits.delete d }
50
+ persist
51
+ self
52
+ end
53
+
54
+ def trim_start portion=0.25
55
+ save = []
56
+ hits.count.times do |i|
57
+ upto = i.to_f / hits.count
58
+ if upto >= portion
59
+ save << hits[i]
60
+ end
61
+ end
62
+ self.hits = save
63
+ persist
64
+ end
65
+
66
+ def trim_end portion=0.25
67
+ save = []
68
+ hits.count.times do |i|
69
+ upto = i.to_f / hits.count
70
+ if upto < 1.0 - portion
71
+ save << hits[i]
72
+ end
73
+ end
74
+ self.hits = save
75
+ persist
76
+ end
77
+
78
+ def trim_both portion=0.25
79
+ save = []
80
+ hits.count.times do |i|
81
+ upto = i.to_f / hits.count
82
+ if upto < 1.0 - portion
83
+ save << hits[i]
84
+ end
85
+ end
86
+
87
+ save2 = []
88
+ hits.count.times do |i|
89
+ upto = i.to_f / hits.count
90
+ if upto >= portion
91
+ save2 << hits[i]
92
+ end
93
+ end
94
+ self.hits = save&save2
95
+ persist
96
+ end
97
+ #(internal use only)
98
+ def persist
99
+ parent.dist.hits = hits if parent
100
+ end
41
101
  #Adds into #hits.
42
102
  #possible_hits:: number of hits that can occur. Must be int
43
103
  #chance:: chance a hit will be included. range: 0 to 1
@@ -0,0 +1,80 @@
1
+ #a mapper is a collection of Dist all as long as a Dist being mapped too,
2
+ #each of these Dist have a child who is getting "mapped" with hits across that length.
3
+ #everything that you need a mapper for can be
4
+ #done without one but it is handy, since it is a common patten.
5
+ class Mapper < Api
6
+ #the main Dist containing all mappings
7
+ attr_accessor :dist
8
+ def initialize(map_to_dist=nil)
9
+ @dist=Dist.new
10
+ @len=0
11
+ self.map_to= map_to_dist if !map_to_dist.nil?
12
+ end
13
+ #set the right hits to mix all children one after eachother.
14
+ #(this can create a dj mix)
15
+ #mixed_ammount:: the ammount of overlap between children
16
+ def mix mixed_ammount = 0.25
17
+ last_start = 0.0
18
+ dist.branches.times do |i|
19
+ self[i].clear_hits
20
+ self[i] << last_start
21
+ last_start = last_start + (1.0 - mixed_ammount) * (1.0/dist.branches)
22
+ end
23
+ end
24
+ def count
25
+ dist.branches
26
+ end
27
+ #length of each child is set to length of val, and we get added to val
28
+ #val:: a Dist
29
+ def map_to= val
30
+ @len=val.length
31
+ dist.set_child_len @len
32
+ val << dist #add me to him
33
+ end
34
+ #get a mapper at i from #dist
35
+ def [] i=0
36
+ dist[i]
37
+ end
38
+ #return last child
39
+ def last
40
+ dist.last_born
41
+ end
42
+ #return last #mapee
43
+ def mapee_last
44
+ last[0]
45
+ end
46
+ #get the child of a mapper (the dist to being mapped) at i
47
+ def mapee i=0
48
+ dist[i][0]
49
+ end
50
+
51
+ #add dists with the hits you need for a bar of techno percussion.
52
+ #len:: length of bar
53
+ #3 children, in order: bass drum, snare, hi hat
54
+ def techno_percussion len
55
+ self << len.Dist # bass drum
56
+ mapee_last.clear_hits
57
+ mapee_last << 4.eqly_spaced
58
+ self << len.Dist # snare
59
+ mapee_last.clear_hits
60
+ mapee_last << [0.25,0.75]
61
+ self << len.Dist # hi
62
+ mapee_last.clear_hits
63
+ mapee_last << 4.eqly_spaced
64
+ mapee_last.hits.move(0.125) # offbeats
65
+ self
66
+ end
67
+
68
+ private
69
+ # Add Dist to me.
70
+ def add_single toadd
71
+ case toadd
72
+ when Dist
73
+ dist << @len.Dist
74
+ dist.last_born << toadd
75
+ else
76
+ return false
77
+ end
78
+ true
79
+ end
80
+ end
@@ -73,7 +73,7 @@ class AudioOutput
73
73
  # raise " couldn't find a value... fuck." if found_val.nil?
74
74
  # print percent
75
75
  frames_index += values.count
76
- App.logger.print_loading_bar(frames_index, len, percent_complete)
76
+ App.logger.print_loading_bar(frames_index, len, percent_complete, 50)
77
77
  self.percent_complete = ((frames_index.to_f/len)*100)
78
78
  # values=nil
79
79
  # GC.start
@@ -100,11 +100,13 @@ class AudioOutput
100
100
  end
101
101
  def print_tail_info total_frames
102
102
  time_taken = App.time_since
103
- log 'wrote file: ' + outfile, 1
103
+ log 'wrote file: ' + outfile, 2
104
104
  log 'seconds: ' + (total_frames.to_f / Composer.samplerate).round(2).to_s +
105
105
  " (frames: #{(total_frames/1000).round},000)", 2
106
106
  log 'time taken: ' + time_taken.round(2).to_s +
107
107
  " seconds (fps: #{(total_frames/time_taken/1000).round()},000)", 2
108
+ #end the very last line
109
+ puts ""
108
110
  end
109
111
 
110
112
  end#class
@@ -15,6 +15,10 @@ def initialize(start=nil,final=nil,exp=0)
15
15
  @final = final
16
16
  @exp = exp
17
17
  end
18
+ # randomize the exp with good values.
19
+ def rand_exp
20
+ self.exp = 0.20 * rand(100)
21
+ end
18
22
  # set #final to a percentage of start
19
23
  def %(percent)
20
24
  self.final = start.to_f*(percent/100.0)
@@ -2,8 +2,8 @@
2
2
  class Logger
3
3
  #higher level means more is logged.
4
4
  #0 means silent.
5
- #1 heading and written files.
6
- #2 condensed stats and updative info from a process (loading bars)
5
+ #1 heading and %.
6
+ #2 condensed stats and written files
7
7
  #3 warnings
8
8
  #4 memory updates and debug info
9
9
  #5 raw data
@@ -21,13 +21,12 @@ class Logger
21
21
  end
22
22
 
23
23
  # a loading bar
24
- def print_loading_bar(frames_index, len, percent_complete=nil)
24
+ def print_loading_bar(frames_index, len, percent_complete=nil, skipped_percent= 0, max= 100)
25
25
  percent = ((frames_index.to_f/len)*100).round(4)
26
26
  percent = percent.round if !percent_complete.nil?
27
- # puts "percent #{percent}"
28
- # puts "fun percent_complete #{percent_complete}"
29
- if level > 1 && (percent_complete.nil? || (percent_complete.round < percent))
30
- App.logger.print_and_flush("__#{percent}%__")
27
+ percent = skipped_percent + (percent.to_f/100.0)*(max-skipped_percent)
28
+ if level > 0 && (percent_complete.nil? || (percent_complete.round < percent))
29
+ App.logger.print_and_flush(" #{percent.round}% ")
31
30
  end
32
31
  percent
33
32
  end
@@ -1,5 +1,7 @@
1
1
  # mixes two tracks together or plays one or the other
2
2
  class Mixer
3
+
4
+
3
5
  # get volumes at a position
4
6
  def self.get(first, second, reduction)
5
7
  reduction2 = reduction > 1 ? 1 : reduction
@@ -100,6 +100,7 @@ def self.load_all
100
100
  load File.dirname(__FILE__) + '/api/dist.rb'
101
101
  load File.dirname(__FILE__) + '/api/hit_sq.rb'
102
102
  load File.dirname(__FILE__) + '/api/snd.rb'
103
+ load File.dirname(__FILE__) + '/api/mapper.rb'
103
104
  end
104
105
 
105
106
  def self.clear_dir dir_path
@@ -80,8 +80,11 @@ end
80
80
  files.addlist sn.render(into), delay_in_frames
81
81
  end
82
82
  tss.each {|sn| files.addlist sn.render(into), delay_in_frames}
83
- App.done += 1 if !tss.empty?
84
- App.logger.print_loading_bar(App.done, App.total)
83
+ # puts "#{ App.done} #{App.total}"
84
+ if !tss.empty?
85
+ App.done += 1
86
+ App.logger.print_loading_bar(App.done, App.total, nil, 0, 50)
87
+ end
85
88
  end
86
89
  files.child_len = (len)
87
90
  files
@@ -1,6 +1,26 @@
1
1
  load File.dirname(__FILE__) + "/music_coder.rb"
2
2
  require "test/unit"
3
3
 
4
+ class TestTones < Test::Unit::TestCase
5
+
6
+ def test_setters
7
+ t= Tone.new
8
+ t.freq.start = (200)
9
+ assert_equal(200, t.freq.start)
10
+ t.freq.final = (20)
11
+ assert_equal(20, t.freq.final)
12
+ t.set_freq_final (30)
13
+ assert_equal(30, t.freq.final)
14
+ t.set_freq_final(20, false)
15
+ assert_equal(-180, t.freq.final)
16
+ assert_equal(-180, t.freq_final)
17
+ assert_equal(20, t.freq_final(false))
18
+ t.set_freq(250)
19
+ assert_equal(-230, t.freq_final)
20
+ t.set_freq(240)
21
+ assert_equal(-220, t.freq_final)
22
+ end
23
+ end
4
24
  class TestNotes < Test::Unit::TestCase
5
25
  def test_ops
6
26
  n=Note.new(1,5) + (-3)
@@ -60,6 +80,24 @@ class TestHitSq < Test::Unit::TestCase
60
80
  assert_equal(4, @h.count)
61
81
  end
62
82
 
83
+ def test_hits_trim
84
+ h=HitSq.new
85
+ h.eqly_spaced(8)
86
+ h.trim_end(0.25)
87
+ assert_equal(([0.0, 0.125, 0.25, 0.375, 0.5, 0.625]), h.hits)
88
+ h.trim_start(0.25)
89
+ assert_equal(([0.25, 0.375, 0.5, 0.625]), h.hits)
90
+
91
+
92
+ h.hits = []
93
+ h << 4.eqly_spaced
94
+ assert_equal([0.0,0.25,0.5,0.75], h.hits)
95
+ h.trim_start(0.25)
96
+ assert_equal([0.25,0.5,0.75], h.hits)
97
+ h.trim_end(0.34)
98
+ assert_equal([0.25,0.5], h.hits)
99
+ end
100
+
63
101
  def test_hits_move
64
102
  assert_equal(([0.2,0.3,0.4]), @h.hits)
65
103
  @h.move(0.1)
@@ -21,6 +21,88 @@ def initialize(args = nil)
21
21
  init_hash(args)
22
22
  end
23
23
 
24
+ # set frequency #freq#start
25
+ def set_freq (val)
26
+ dif = freq.start - val
27
+ freq.start = val
28
+ set_freq_final freq_final(false) + dif, false
29
+ end
30
+
31
+ def freq_final(is_relative=true)
32
+ return is_relative ? freq.final : freq.start + freq.final
33
+ end
34
+
35
+ # random amp.
36
+ def rand_amp_both(max = 0.8, min = 0.0025)
37
+ upper = max - min
38
+
39
+ val = rand * upper
40
+ self.amp.start = min + val
41
+ val = rand * val # can't be more
42
+ self.set_amp_final (min + val), false
43
+ self.amp.rand_exp
44
+ end
45
+
46
+ # random ammount of detail in a wave. weighted toward lower values weight times.
47
+ def rand_detail_both(min = 3, max = 512, weight=2)
48
+ upper = max - min
49
+
50
+ val = rand * upper
51
+ weight.times {val *= rand}
52
+ self.wave.detail.start= min + val
53
+ val = rand * upper
54
+ weight.times {val *= rand}
55
+ self.wave.detail.final= min + val
56
+ # puts "start detail #{val}"
57
+ # puts "final detail #{val}"
58
+ self.wave.detail.rand_exp
59
+ end
60
+
61
+ # random ammount of saturation, weighted toward lower values weight times.
62
+ def rand_sat_both weight=2, max=1.0
63
+ sat = max
64
+ weight.times {sat *= rand}
65
+ self.saturations.start= sat
66
+
67
+ sat = max
68
+ weight.times {sat *= rand}
69
+ self.saturations.final= sat
70
+ self.saturations.rand_exp
71
+ # puts "saturations.start #{saturations.start}"
72
+ # puts "saturations.final #{saturations.final}"
73
+ # puts "saturations.x #{saturations.exp}"
74
+ end
75
+
76
+ # random start frequency in range.
77
+ def rand_freq(min = 12, max = 20_000)
78
+ upper = max - min
79
+ val = min + rand*upper
80
+ self.freq.start = val
81
+ end
82
+
83
+ # random frequencies in range.
84
+ def rand_freq_both(min = 12, max = 20_000)
85
+ upper = max - min
86
+
87
+ rand_freq min, max
88
+ val = min + rand*upper # final val
89
+ self.set_freq_final val, false
90
+ self.freq.rand_exp
91
+ # puts "freq.start #{freq.start}"
92
+ # puts "freq.f #{freq.final}"
93
+ # puts "freq.x #{freq.exp}"
94
+ end
95
+
96
+ #is_relative:: false for setting it absolute
97
+ def set_freq_final fq, is_relative=true
98
+ self.freq.final = is_relative ? fq : fq - freq.start
99
+ end
100
+
101
+ #is_relative:: false for setting it absolute
102
+ def set_amp_final val, is_relative=true
103
+ self.amp.final = is_relative ? val : val - amp.start
104
+ end
105
+
24
106
  # set saturation for both start and end
25
107
  def saturation= val
26
108
  wave.saturation= val
@@ -100,12 +182,6 @@ def out
100
182
  data
101
183
  end
102
184
 
103
- # setting it absolute
104
- def set_freq_final_no_relative(final)
105
- dif=final - freq.start
106
- freq.final=dif
107
- end
108
-
109
185
  # set #freq based off a Note
110
186
  def note=(note)
111
187
  freq.start = note.freq
@@ -1,6 +1,6 @@
1
- # one tone or two tones that can be morphed between eachother
1
+ # one or two Tone that can be morphed between eachother
2
2
  class TonePart
3
- #Fader
3
+ #Fader of Tone
4
4
  attr_accessor :tones
5
5
  attr_accessor :max_frames
6
6
  attr_accessor :tone_single
@@ -2,10 +2,63 @@
2
2
  class ToneSeq
3
3
  #Array of TonePart
4
4
  attr_accessor :toneparts
5
+ #when joined, a tone sequence makes the end of each tone
6
+ #the same as the start of the next one.
7
+ #this creates a smooth sound.
8
+ #todo
9
+ def join
10
+ toneparts.count.times do |i|
11
+ # if more after me, do it
12
+ if i+1 < toneparts.count
13
+ me = toneparts[i].tone(0)
14
+ nxt = toneparts[i+1].tone(0)
15
+ me.detail.final = nxt.detail.start
16
+ me.saturations.final = nxt.saturations.start
17
+ me.set_freq_final( nxt.freq.start, false)
18
+ me.set_amp_final( nxt.amp.start, false)
19
+
20
+ me = toneparts[i].tone(1)
21
+ nxt = toneparts[i+1].tone(1)
22
+ me.detail.final = nxt.detail.start
23
+ me.saturations.final = nxt.saturations.start
24
+ me.set_freq_final( nxt.freq.start, false)
25
+ me.set_amp_final( nxt.amp.start, false)
26
+ end
27
+ end
28
+ end
29
+
5
30
  def initialize()
6
31
  @toneparts = []
7
32
  make(1)
8
33
  end
34
+
35
+ #random everything
36
+ def random extra_detail = 5, even = false, delay=0, start_amp = 0.5,
37
+ max_f = 2000, min_f = 120, max_sat=0.2, min_detail=50
38
+ make(extra_detail) # sets the lens evenly.
39
+ frames_left = self.frames - self.frames.to_f*delay #- extra_detail+1 #minus a little so it must have 1 frame
40
+ toneparts.count.times do |i|
41
+ # sets the lens randomly.
42
+ portion = frames_left * rand
43
+ # puts "portion #{portion}"
44
+ if i == toneparts.count
45
+ portion = frames_left
46
+ end
47
+ frames_left -= 1+portion
48
+ toneparts[i].tone.frames = 1+portion if !even
49
+ #
50
+ toneparts[i].tone.rand_sat_both 3, max_sat
51
+ toneparts[i].tone.rand_freq_both(min_f, max_f)
52
+ toneparts[i].tone.rand_detail_both min_detail
53
+ max_amp = start_amp
54
+ if i>0 # it can't be higher than the last amp
55
+ max_amp = toneparts[i-1].tone.amp.start
56
+ end
57
+ toneparts[i].tone.rand_amp_both max_amp, max_amp * 0.75 # not much lower
58
+ end
59
+ join
60
+ end
61
+
9
62
  #return the total frames of all toneparts combined.
10
63
  def frames
11
64
  total=0
@@ -50,5 +103,10 @@ class ToneSeq
50
103
  files.write data
51
104
  files
52
105
  end
106
+
107
+ #reduce amp of all tones by this val
108
+ def amp_reduce val
109
+ toneparts.each { |tp| tp.amp_mult(1.0/val) }
110
+ end
53
111
 
54
112
  end
@@ -5,7 +5,7 @@ class Wave
5
5
  # Fader.final is at the end. nil means same as wave start
6
6
  attr_accessor :detail
7
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
8
+ #the ammount of saturation is higher is more, 0 is none. range: 0 to 1.
9
9
  attr_accessor :saturations
10
10
  # hack to dramatically speed it up when on.
11
11
  attr_accessor :cache_wave, :old_wave
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: music_coder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -48,6 +48,7 @@ files:
48
48
  - lib/audio.rb
49
49
  - lib/fader.rb
50
50
  - lib/api/snd.rb
51
+ - lib/api/mapper.rb
51
52
  - lib/api/hit_sq.rb
52
53
  - lib/api/dist.rb
53
54
  - lib/api/api.rb
@@ -79,7 +80,7 @@ rubyforge_project:
79
80
  rubygems_version: 1.8.23
80
81
  signing_key:
81
82
  specification_version: 3
82
- summary: An application to programmatically create music through code.
83
+ summary: An application to programmatically create music by coding.
83
84
  test_files:
84
85
  - lib/tests.rb
85
86
  has_rdoc: true