music_coder 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,14 @@ class Api
3
3
  def initialize
4
4
  @parents=[]
5
5
  end
6
- # Add things to me
6
+ #Add things to me. This can be applied for many classes. Can handles arrays of excepted types.
7
+ #object Dist can add Dist to children.
8
+ #object Dist can add Snd to list of sounds.
9
+ #object Dist and HitSq can add HitSq to list of hits.
10
+ #object Dist and HitSq can add Numbers to list of hits.
11
+ #object Snd can repalce ToneSeq.
12
+ #object Snd can add Snd to repalce its ToneSeq.
13
+ #object Snd can add TonePart to add it to its ToneSeq.
7
14
  def << toadd
8
15
  # puts "type: #{toadd.class}"
9
16
  case toadd
@@ -48,61 +55,163 @@ class Api
48
55
 
49
56
  end#class
50
57
 
51
- def load_state
52
- f=File.open("../output/text/saved.rb",'r')
58
+ #load program state from a saved file.
59
+ def load_state file=nil
60
+ file=App.outpath + "save.rb" if file.nil?
61
+ f=File.open(file,'r')
53
62
  content=f.read
54
63
  # puts content
55
64
  App.out= YAML.load(content)
56
65
  f.close
57
66
  end
58
67
 
59
- def save_state
60
- App.out.write_text_files
68
+ #save program state to a file.
69
+ def save_state file=nil
70
+ App.out.write_text_files file
61
71
  end
62
72
 
73
+ #generate the audio data for everything in the queue. see #queue method.
63
74
  def render
64
75
  App.out.render
65
76
  end
66
77
 
78
+ #make an audio file based off generated data.
67
79
  def make
68
80
  App.out.make_audio_file
69
81
  end
70
82
 
83
+ #return the frames in i beats. used for setting lengths when they are needed in frames.
71
84
  def beat i=1
72
85
  z=Composer.beat i
73
86
  z.to_i
74
87
  end
88
+ #return the frames in i bars. used for setting lengths when they are needed in frames.
75
89
  def bar i=1
76
90
  z=Composer.beat i*4
77
91
  z.to_i
78
92
  end
79
93
 
94
+ # add a Dist to the queue of things to be rendered when you call render. see #render.
80
95
  def queue dist
81
96
  App.out.snddists<<dist.instance_variable_get(:@dist)
82
97
  end
83
98
 
84
- # _rendered files
99
+ #clear already rendered files.
100
+ #usually you will want to do this at the start of the program or temp files
101
+ #created from the last use will overlap the current ones when you run #make.
85
102
  def clear
86
103
  App.clear_ready
87
104
  end
88
105
 
89
- def bpm_set val
106
+ #set the beats per minute of all following commands. now helper methods like #beat will be usefull.
107
+ def set_bpm val
90
108
  Composer.bpm = val
91
109
  end
110
+ #return the current beats per minute.
92
111
  def bpm
93
112
  Composer.bpm
94
113
  end
114
+ #shortcut for #render #save_state #make
95
115
  def compute
96
116
  render
97
117
  save_state
98
118
  make
99
119
  end
100
- # send str to be logged.
101
- # level:: it won't be logged unless Logger.level is at or above this level.
120
+ #send String str to be logged.
121
+ #level:: it won't be logged unless Logger.level is at or above this level.
102
122
  def log str, level=3
103
123
  App.logger.log str, level
104
124
  end
105
125
  # set Logger.level. Higher means more logging. 0 is silent.
106
126
  def log_level set
107
127
  App.logger.level = set
128
+ end
129
+
130
+
131
+ #outputs the midi notes of the chord in an array
132
+ #note:: midi value of root note in chord. range: 0 to 11
133
+ #name:: anything from Composer.chords
134
+ def chord_notes(note, name = "dim7")
135
+ set=[]
136
+ out=[]
137
+ all=Composer.chords
138
+ # handle all selected
139
+ if name=="all"
140
+ all.keys.each { |val| out.push chord_notes(note, val) }
141
+ else #normal
142
+ set = all[name]
143
+ raise "Unknown scale name" if set.nil?
144
+ out = [note] # always root
145
+ set.each do |val|
146
+ out.push note+val
147
+ end
148
+ end
149
+
150
+ out
151
+ end
152
+
153
+ #return array of notes in the Composer#scale
154
+ #note:: midi value of root note. range: 0 to 11
155
+ #name:: anything from Composer.scales.
156
+ def scale_notes(note=0,is_all=false)
157
+ set=[]
158
+ out=[]
159
+ all=Composer.scales
160
+ # handle all selected
161
+ if is_all
162
+ all.keys.each { |val| out.push scale_notes(val,note) }
163
+ else #normal
164
+ set = all[Composer.scale]
165
+ raise "Unknown scale name" if set.nil?
166
+ out = [note] # always root
167
+ # add set to out
168
+ set.each do |val|
169
+ out.push note+ val
170
+ end
171
+ end
172
+ out
173
+ end
174
+
175
+ #return an Array of String of chords that could be the i number chord for notes in the Composer#scale.
176
+ #scale:: anything from Composer.scales.
177
+ def scale_chord(i=0)
178
+ notes = scale_notes
179
+ raise "#{i} is out of range for that scale, it only has #{notes.count} notes." if notes[i].nil?
180
+ Composer.matching_chords(notes[i])
181
+ end
182
+
183
+ #random scale
184
+ def rand_scale
185
+ Composer.scales.keys.sample # allow for random.
186
+ end
187
+
188
+ #return an Array of chords (each chord is an Array of Integer notes)
189
+ #that fit each note consecutively in Composer#scale.
190
+ #The chord is randomly sampled from those availiable.
191
+ def get_chords
192
+ out = []
193
+ notes = scale_notes
194
+ notes.count.times do |i|
195
+ begin
196
+ chord = scale_chord(i).sample #rand
197
+ if chord == []
198
+ out << [notes[i]]
199
+ else
200
+ out << chord_notes(notes[i], chord)
201
+ end
202
+
203
+ rescue Exception
204
+ out << [notes[i]]
205
+ end
206
+ end
207
+ out
208
+ end
209
+
210
+ #returns how far the given note (Integer) is into the scale.
211
+ def note_index(note)
212
+ scale_notes.index(note)
213
+ end
214
+ #sets Composer#scale
215
+ def set_scale sc
216
+ Composer.scale = sc
108
217
  end
@@ -2,6 +2,7 @@
2
2
  # of varying lengths, and rate of morphs, played without gaps.
3
3
  class Dist < Api
4
4
  attr_accessor :dist
5
+ # will have a defult hit at 0 if it has a sound and no hits have been made.
5
6
  def initialize
6
7
  super
7
8
  @dist=SndDist.new
@@ -9,6 +10,12 @@ class Dist < Api
9
10
  @hits.add_parent self
10
11
  persist_hits
11
12
  end
13
+
14
+ #add num TonePart to Snd at i's ToneSeq, with my #length as max.
15
+ def make(num=1, i=0)
16
+ snd(i).toneseq.make(num)
17
+ end
18
+
12
19
  # delete another Dist
13
20
  def del todel
14
21
  @dist.snd.delete todel.dist
@@ -30,7 +37,7 @@ class Dist < Api
30
37
  persist_hits
31
38
  self
32
39
  end
33
- # get a child
40
+ # get a Dist child
34
41
  def [] i
35
42
  child=@dist.get_children[i]
36
43
  raise "This Dist has no child at index #{i}. " +
@@ -40,7 +47,23 @@ class Dist < Api
40
47
  d.make_hits
41
48
  d
42
49
  end
43
- # count children
50
+ # get last child
51
+ def last_born
52
+ child=@dist.get_children.last
53
+ d=Dist.new
54
+ d.dist=child
55
+ d.make_hits
56
+ d
57
+ end
58
+ # get first child
59
+ def first_born
60
+ child=@dist.get_children.first
61
+ d=Dist.new
62
+ d.dist=child
63
+ d.make_hits
64
+ d
65
+ end
66
+ # count children (Dists only)
44
67
  def branches
45
68
  @dist.get_children.count
46
69
  end
@@ -54,14 +77,28 @@ class Dist < Api
54
77
  snd.add_parent self
55
78
  new=@dist.tss[i]
56
79
  raise "Dist has no sound at index #{i}. It has #{sounds} sounds." if new.nil?
57
- snd.snd = new
80
+ snd<< new
58
81
  snd
59
82
  end
60
- # make a new sound with len
61
- def make_sound
83
+ # getter for Snd. Will persist any changes you make to it.
84
+ def snd i=0
62
85
  snd=Snd.new
63
- snd.snd.len = @dist.len
64
- self<<snd
86
+ snd.add_parent self
87
+ new=@dist.tss[i]
88
+ raise "Dist has no sound at index #{i}. It has #{sounds} sounds." if new.nil?
89
+ snd<< new
90
+ snd
91
+ end
92
+ # delete all Snd attached to this Dist
93
+ def clear_snd
94
+ @dist.tss = []
95
+ self
96
+ end
97
+ # run on all sounds
98
+ def snd_each
99
+ sounds.times {|i|
100
+ yield(snd i)
101
+ }
65
102
  self
66
103
  end
67
104
  # count sounds
@@ -82,27 +119,29 @@ class Dist < Api
82
119
  end
83
120
 
84
121
  private
85
- # adds a sound if it's valid to do so
122
+ # adds a sound if it's valid to do so.
86
123
  def validate_snd! val
87
124
  raise "This Dist can't have sounds, it has children. " if branches > 0
88
125
  @dist.tss << val
89
126
  val.toneparts.each {|tp|
90
127
  # puts "RUNNING #{length / val.toneparts.count.to_f}"
91
- tp.max_frames = length / val.toneparts.count }
128
+ tp.max_frames = length / val.toneparts.count
129
+
130
+ }
92
131
  end
93
132
  # adds a dist if it's valid to do so
94
133
  def validate_dist! val
95
134
  raise "This Dist can't have children, it has sounds. " if sounds > 0
96
135
  @dist.add val
97
136
  end
98
- # Add hits, sounds or other dists to me.
137
+ # Add hits, sounds or other Dist to me.
99
138
  def add_single toadd
100
139
  case toadd
101
140
  when HitSq, Float, Integer
102
141
  @hits << toadd
103
142
  persist_hits
104
143
  when Snd
105
- validate_snd! toadd.snd
144
+ validate_snd! toadd.toneseq
106
145
  when Dist
107
146
  validate_dist! toadd.dist
108
147
  else
@@ -127,16 +166,17 @@ class Dist < Api
127
166
  end
128
167
  end
129
168
 
130
- # shortcut e.g. 10.Dist for a dist with length of 10 frames
131
169
  class Integer
170
+ #shortcut to create a Dist with length. e.g. 10.Dist returns a new Dist with length of 10 frames already set.
132
171
  def Dist
133
172
  h=Dist.new
134
173
  h.length = self.to_i
135
174
  h
136
175
  end
137
176
  end
138
- # shortcut e.g. 0.0.Dist for the most used, a single hit at 0.0
139
177
  class Float
178
+ #shortcut to create a Dist with one hit in its #hits.
179
+ #e.g. 0.1.Dist returns a new Dist with, a single hit at 0.1.
140
180
  def Dist
141
181
  h=Dist.new
142
182
  h<<self
@@ -99,7 +99,7 @@ class Array
99
99
  end
100
100
  end
101
101
  class Integer
102
- #a shortcut. e.g. 4.eqly_spaced gives you HitSq.new.eqly_spaced(4)
102
+ #a shortcut. e.g. 4.eqly_spaced gives you HitSq#eqly_spaced(4)
103
103
  def eqly_spaced(chance = 1, ignore_first=0, ignore_last=0)
104
104
  h=HitSq.new
105
105
  possible_hits=self
@@ -14,8 +14,40 @@ class Note
14
14
  @octave = octave
15
15
  end
16
16
 
17
- # returns a value up n semitones. (changes octave where necessary)
17
+ #increment by n notes in the scale set in Composer#scale
18
+ def inc(n)
19
+ notes = scale_notes
20
+ ind = note_index(self.note)
21
+ semis = 0
22
+ n.times do
23
+ ind_old = ind
24
+ ind += 1
25
+ if ind >= notes.count
26
+ diff = 12 - notes[ind_old]
27
+ ind = 0
28
+ else
29
+ diff = notes[ind] - notes[ind_old]
30
+ end
31
+ semis += diff
32
+ end
33
+ (-1*n).times do
34
+ ind_old = ind
35
+ ind -= 1
36
+ if ind < 0
37
+ ind=notes.count-1
38
+ diff = notes[ind] - 12
39
+ else
40
+ diff = notes[ind] - notes[ind_old]
41
+ end
42
+ semis += diff
43
+ end
44
+ self+semis
45
+ end
46
+
47
+ #returns a value up some semitones. (changes octave where necessary)
48
+ #n:: number of semitones, can be pos or neg Integer.
18
49
  def +(n = 1)
50
+ return self-(n*-1) if n < 0
19
51
  i=0
20
52
  out = deep_copy
21
53
  while i < n do
@@ -51,7 +83,7 @@ class Note
51
83
  vars_eql? other
52
84
  end
53
85
 
54
- # create a sound based off this note.
86
+ #return a new Snd with its frequency set to this note.
55
87
  def Snd
56
88
  s=freq.Snd
57
89
  s
@@ -6,10 +6,15 @@ class Snd < Api
6
6
  super
7
7
  end
8
8
 
9
+ # return its TonePart.
9
10
  def tonepart i=0
10
- @snd.tonepart i
11
+ child = @snd.tonepart i
12
+ raise "This Snd has no tone at index #{i}. " +
13
+ "It has #{count} tones." if child.nil?
14
+ child
11
15
  end
12
16
 
17
+ # return its TonePart at j and thats Tone at i.
13
18
  def tone i=0, j=0
14
19
  @snd.tonepart(j).tone(i)
15
20
  end
@@ -18,26 +23,30 @@ class Snd < Api
18
23
  @snd.frames=val
19
24
  self
20
25
  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
26
+ # get length
27
+ def length
28
+ @snd.frames end
27
29
  # number of tones
28
30
  def count
29
- snd.toneparts.count
31
+ toneseq.toneparts.count
30
32
  end
33
+ #ensure all tones fade out to 0 as the final volume.
34
+ #note: re-run after changing amp.
31
35
  def fade
32
- snd.fade
36
+ toneseq.fade
37
+ end
38
+ #return my ToneSeq
39
+ def toneseq
40
+ @snd
33
41
  end
34
- attr_accessor :snd
35
42
  private
36
43
  # Add hits, sounds or other dists to me.
37
44
  def add_single toadd
38
45
  case toadd
39
46
  when Snd
40
- @snd = toadd.instance_variable_get(:@snd)
47
+ @snd = toadd.toneseq
48
+ when ToneSeq
49
+ @snd = toadd
41
50
  else
42
51
  return false
43
52
  end
@@ -46,17 +55,20 @@ class Snd < Api
46
55
 
47
56
  end
48
57
 
49
- class Fixnum
58
+ class Integer
59
+ #same as Float#Snd.
50
60
  def Snd
51
61
  a = Snd.new
52
- a.t.freq = self
62
+ a.tonepart.freq = self
53
63
  a
54
64
  end
55
65
  end
56
66
  class Float
67
+ #return a new Snd with its frequency set to the value.
68
+ #e.g. 500.4.Snd
57
69
  def Snd
58
70
  a = Snd.new
59
- a.t.freq = self
71
+ a.tonepart.freq = self
60
72
  a
61
73
  end
62
74
  end
@@ -6,6 +6,8 @@ class << self
6
6
  attr_accessor :bpm
7
7
  #fames per second
8
8
  attr_accessor :samplerate
9
+ #the current scale to be used by many commands that require it. String. def: "major".
10
+ attr_accessor :scale
9
11
  end
10
12
  def self.chords
11
13
  all = Hash.new
@@ -49,62 +51,8 @@ def self.scales
49
51
  all
50
52
  end
51
53
 
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)
54
+ #return an array of notes from a array of notes with an offset, adding the root note at the start.
55
+ #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
56
  def self.get_notes(notes_ar, offset = 0)
109
57
  notes = [0] + notes_ar
110
58
  #offset
@@ -125,6 +73,19 @@ def self.beat(beats)
125
73
  return (beats) * (1.0/bps) * self.samplerate.to_f
126
74
  end
127
75
 
76
+ #return set of chord names of chords that fit in the scale, offset upward by offset notes.
77
+ def self.matching_chords(offset = 0)
78
+ all = Composer.chords
79
+ scale_n = [0] + Composer.scales[scale] # not offset at all
80
+ out = []
81
+ all.each do |chord|
82
+ name = chord[0]
83
+ notes=Composer.get_notes all[name], offset # lookup chord with name and offset
84
+ out.push name if (scale_n&notes).sort==notes.sort # if all notes are in scale
85
+ end
86
+ out
87
+ end
88
+
128
89
  #convert a note as a string to midi value
129
90
  #note:: range: "A" to "G#". No a flats.
130
91
  def self.note_m(note)
@@ -160,5 +121,5 @@ def self.note_m(note)
160
121
  end
161
122
  val
162
123
  end
163
-
124
+ Composer.scale = "major"
164
125
  end
@@ -191,7 +191,7 @@ out=[]
191
191
  # puts "--> values: #{full.join(', ')}"
192
192
  spli=App.split_array(full, App::CHUNK_SIZE)
193
193
  spli.each_with_index do |array,i|
194
- self.files.push "../tmp/#{object_id}_#{i}"
194
+ self.files.push "#{App::TMP_DIR}#{object_id}_#{i}"
195
195
  self.file_content_lens.push array.count
196
196
  File.open(self.files.last, 'w') do |fout|
197
197
  fout.puts Marshal.dump(array)
@@ -6,9 +6,10 @@ class Logger
6
6
  #2 condensed stats and updative info from a process (loading bars)
7
7
  #3 warnings
8
8
  #4 memory updates and debug info
9
+ #5 raw data
9
10
  attr_accessor :level
10
11
  def initialize
11
- self.level = 4
12
+ self.level = 5
12
13
  end
13
14
 
14
15
  #output something if our level says it's okay
@@ -1,5 +1,9 @@
1
-
2
- # extend all objects
1
+ #These are the main methods in the API to be called from anywhere.
2
+ #Other important API classes to read are:
3
+ #* Dist
4
+ #* Snd
5
+ #* Note
6
+ #* HitSq
3
7
  class Object
4
8
  # initialize all instance vars from a Hash if they are specified in args.
5
9
  def init_hash(args)
@@ -13,6 +17,7 @@ end
13
17
  def deep_copy
14
18
  Marshal.load(Marshal.dump(self))
15
19
  end
20
+ #:nodoc: test if attributes are equal
16
21
  def vars_eql?(other,vars=nil)
17
22
  vars ||= instance_variables
18
23
  vars.each do |var|
@@ -51,6 +56,7 @@ end
51
56
 
52
57
  #require 'debugger'
53
58
  # The top level of the program. This static class contains static methods and static attributes.
59
+ #Gem site: rubygems.org/gems/music_coder
54
60
  class App
55
61
  class << self
56
62
  #file containing input (without .rb extension)
@@ -75,25 +81,25 @@ require 'yaml'
75
81
 
76
82
  # Reload all files in case of update.
77
83
  def self.load_all
78
- load 'tone.rb'
79
- load 'logger.rb'
80
- load 'fader.rb'
81
- load 'composer.rb'
82
- load 'math_utils.rb'
83
- load 'wave.rb'
84
- load 'wave_data.rb'
85
- load 'mixer.rb'
86
- load 'audio_output.rb'
87
- load 'tone_part.rb'
88
- load 'tone_seq.rb'
89
- load 'audio.rb'
90
- load 'file_list.rb'
91
- load 'snd_dist.rb'
92
- load 'api/note.rb'
93
- load 'api/api.rb'
94
- load 'api/dist.rb'
95
- load 'api/hit_sq.rb'
96
- load 'api/snd.rb'
84
+ load File.dirname(__FILE__) + '/tone.rb'
85
+ load File.dirname(__FILE__) + '/logger.rb'
86
+ load File.dirname(__FILE__) + '/fader.rb'
87
+ load File.dirname(__FILE__) + '/composer.rb'
88
+ load File.dirname(__FILE__) + '/math_utils.rb'
89
+ load File.dirname(__FILE__) + '/wave.rb'
90
+ load File.dirname(__FILE__) + '/wave_data.rb'
91
+ load File.dirname(__FILE__) + '/mixer.rb'
92
+ load File.dirname(__FILE__) + '/audio_output.rb'
93
+ load File.dirname(__FILE__) + '/tone_part.rb'
94
+ load File.dirname(__FILE__) + '/tone_seq.rb'
95
+ load File.dirname(__FILE__) + '/audio.rb'
96
+ load File.dirname(__FILE__) + '/file_list.rb'
97
+ load File.dirname(__FILE__) + '/snd_dist.rb'
98
+ load File.dirname(__FILE__) + '/api/note.rb'
99
+ load File.dirname(__FILE__) + '/api/api.rb'
100
+ load File.dirname(__FILE__) + '/api/dist.rb'
101
+ load File.dirname(__FILE__) + '/api/hit_sq.rb'
102
+ load File.dirname(__FILE__) + '/api/snd.rb'
97
103
  end
98
104
 
99
105
  def self.clear_dir dir_path
@@ -102,7 +108,7 @@ end
102
108
 
103
109
  # clears objects ready to write to file.
104
110
  def self.clear_ready
105
- App.clear_dir "../tmp/"
111
+ App.clear_dir App::TMP_DIR
106
112
  App.out.snddists = []
107
113
  App.out.filelist = FileList.new
108
114
  end
@@ -172,6 +178,7 @@ self.load_all # on load
172
178
  # 46,000 of these per mb roughly.
173
179
  App::CHUNK_SIZE = 110_000 unless const_defined?(:CHUNK_SIZE)
174
180
  App::EXT = ".aiff" unless const_defined?(:EXT)
181
+ App::TMP_DIR = "tmp/" unless const_defined?(:TMP_DIR)
175
182
  App.outpath = "output/"
176
183
  App.start_t = Time.new
177
184
  self.mixes_num = 1
@@ -182,11 +189,11 @@ self.out = AudioOutput.new
182
189
  # make dirs
183
190
  require 'fileutils'
184
191
  FileUtils.mkdir_p App.outpath+"sound/"
185
- FileUtils.mkdir_p "tmp/"
192
+ FileUtils.mkdir_p App::TMP_DIR
186
193
  #
187
194
  App.clear_dir App.outpath+"sound/"
188
195
  self.out.outfile="#{App.outpath}sound/output#{App::EXT}"
189
196
  self.logger = Logger.new
190
- App.fileoptions={:mode => :WRITE, :format => :RAW, :encoding => :PCM_16, :channels => 1, :samplerate => Composer.samplerate}
197
+ App.fileoptions={:mode => :WRITE, :format => :AIFF, :encoding => :PCM_16, :channels => 1, :samplerate => Composer.samplerate}
191
198
  log "Music Coder", 1
192
199
  end
@@ -1,6 +1,33 @@
1
- require "./app.rb"
1
+ load File.dirname(__FILE__) + "/music_coder.rb"
2
2
  require "test/unit"
3
3
 
4
+ class TestNotes < Test::Unit::TestCase
5
+ def test_ops
6
+ n=Note.new(1,5) + (-3)
7
+ assert_equal(true, Note.new(10,4).is_eql(n))
8
+ assert_equal(true, Note.new(3,5).is_eql(n+5))
9
+ end
10
+ def test_inc
11
+ Composer.scale = "major"
12
+ n=Note.new(0,5)
13
+ assert_equal([0, 2, 4, 5, 7, 9, 11], scale_notes)
14
+ b=n.inc 2
15
+ assert_equal 4, b.note
16
+ b=n.inc 7
17
+ assert_equal 0, b.note
18
+ assert_equal 6, b.octave
19
+ b=n.inc 0
20
+ assert_equal 0, b.note
21
+ assert_equal 5, b.octave
22
+ b=n.inc -2
23
+ assert_equal 9, b.note
24
+ assert_equal 4, b.octave
25
+ b=n.inc -9
26
+ assert_equal 9, b.note
27
+ assert_equal 3, b.octave
28
+ end
29
+ end
30
+
4
31
  class TestHitSq < Test::Unit::TestCase
5
32
  def setup
6
33
  @h=[0.2,0.3,0.4].HitSq
@@ -48,6 +75,20 @@ class TestDists < Test::Unit::TestCase
48
75
  @d3.length = bar
49
76
  end
50
77
 
78
+ def test_dist_child_getters
79
+ @d << (8.Dist << 0.5 << 300.Snd)
80
+ @d.last_born.snd.length= 10
81
+ assert_equal([0.5], @d.last_born.hits.hits)
82
+ assert_equal(1, @d.last_born.snd.count)
83
+ # persist?
84
+ @d.last_born << 0.6
85
+ assert_equal([0.5,0.6], @d.last_born.hits.hits)
86
+ end
87
+ def test_dist_default_hit_0
88
+ dist = 50_000.Dist
89
+ assert_equal(1, dist.dist.hits.count)
90
+ assert_equal(0, dist.hits.count)
91
+ end
51
92
  def test_add_dist
52
93
  assert_equal(0, @d.branches)
53
94
  @d<<@d2
@@ -122,7 +163,7 @@ class TestDists < Test::Unit::TestCase
122
163
 
123
164
  def test_snddefs
124
165
  s=20.Snd
125
- assert_equal(20, s.t.tones.start.freq.start)
166
+ assert_equal(20, s.tone.freq.start)
126
167
  end
127
168
 
128
169
  def test_def_hits
@@ -131,38 +172,66 @@ class TestDists < Test::Unit::TestCase
131
172
  assert_equal([0.0], d.dist.hits) #default
132
173
  end
133
174
 
134
- def test_def_tone
175
+ def test_def_tone_len
135
176
  d=10.Dist
136
- d.make_sound
137
- assert_equal(10, d.snd.t.max_frames)
177
+ d<< Snd.new
178
+ assert_equal(10, d.snd.tonepart.max_frames)
138
179
  end
139
180
 
140
181
  def test_snd_f_def
141
182
  s=20.0.Snd
142
- assert_equal(20, s.t.freq)
183
+ assert_equal(20, s.tonepart.freq)
143
184
  end
144
185
 
145
186
  def test_snd_len_dist
146
187
  snd = 155.Snd
147
188
  snd.length= beat
148
- assert_equal(beat, snd.t.tones.start.frames)
149
- assert_equal(0, snd.t.max_frames)
189
+ assert_equal(beat, snd.tone.frames)
190
+ assert_equal(0, snd.tonepart.max_frames)
150
191
  snd.length= 0
151
192
  snd.length= beat
152
193
  @d3<<snd
153
- assert_equal(bar, snd.t.max_frames)
154
- assert_equal(beat, snd.t.tones.start.frames) # kept, not 0
194
+ assert_equal(bar, snd.tonepart.max_frames)
195
+ assert_equal(beat, snd.tone.frames) # kept, not 0
155
196
  end
156
197
  def test_snd_len_def
157
198
  snd = 155.Snd
158
199
  @d3<<snd
159
- assert_equal(bar, snd.t.tones.start.frames) # if 0 uses full
200
+ assert_equal(bar, snd.tone.frames) # if 0 uses full
160
201
  end
161
202
  def test_notes
162
203
  snd = Note.new(0,5).Snd
163
- assert_equal(440, snd.t.freq)
164
- assert_equal(Note.new(0,5).freq, snd.t.note.freq)
204
+ assert_equal(440, snd.tonepart.freq)
205
+ assert_equal(Note.new(0,5).freq, snd.tonepart.note.freq)
165
206
  snd = Note.new("c#",5)
166
207
  assert_equal(4, snd.note)
167
208
  end
209
+ def test_toneseq_pushing
210
+ @d3 << Note.new(0,5).Snd
211
+ @d3.snd.length= beat
212
+ assert_equal(beat, @d3.snd.tone.frames)
213
+ @d3.make(1)
214
+ assert_equal(2, @d3.snd.toneseq.toneparts.count)
215
+ assert_equal(beat/2, @d3.snd.tone.frames) # made 2, frames should be half
216
+ @d3.make(2)
217
+ assert_equal(4, @d3.snd.toneseq.toneparts.count)
218
+ assert_equal(beat/4, @d3.snd.tone.frames) # made 4, frames should be 1/4
219
+ end
220
+ def test_scale
221
+ Composer.scale = "mixolydian"
222
+ notes = scale_notes
223
+ assert_equal(7, notes.count)
224
+ assert_equal([0,2,4,5,7,9,10], notes)
225
+
226
+ snd = Snd.new
227
+ snd.length= beat
228
+ @d3<< snd
229
+ @d3.make(6)
230
+ 7.times do |i|
231
+ newn = Note.new(notes[i], 4)
232
+ @d3.snd.tonepart(i).note= newn
233
+ end
234
+ assert_equal(392, @d3.snd.tonepart(6).freq.round)
235
+ assert_equal(277, @d3.snd.tonepart(2).freq.round)
236
+ end
168
237
  end
@@ -21,6 +21,7 @@ def initialize(args = nil)
21
21
  init_hash(args)
22
22
  end
23
23
 
24
+ # set saturation for both start and end
24
25
  def saturation= val
25
26
  wave.saturation= val
26
27
  end
@@ -57,14 +58,15 @@ def chord(note, name, element)
57
58
  out
58
59
  end
59
60
 
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.
61
+ #output the WaveData for a full tone (chirp). All sound created flows into this atm.
62
+ #freq_exp:: default 0. 0 means linear. higher means it reachs the frequency at the end of it's range later.
62
63
  def out
63
64
  # puts "out amp: #{amp.start}"
64
65
  buffer_len = frames
65
66
  inc_x = 0.0
66
67
  data = WaveData.new
67
68
  lfreq = freq.start
69
+ log "tone starting with freq #{lfreq}", 4
68
70
  lamp = amp.start
69
71
  wave_exp = wave.detail.exp
70
72
  freq_exp = freq.exp
@@ -1,11 +1,11 @@
1
- # two tones that can be faded between
1
+ # one tone or two tones that can be morphed between eachother
2
2
  class TonePart
3
3
  #Fader
4
4
  attr_accessor :tones
5
5
  attr_accessor :max_frames
6
6
  attr_accessor :tone_single
7
7
  attr_accessor :tone_count
8
- def initialize(m,t1=Tone.new,t2=Tone.new)
8
+ def initialize(m=0,t1=Tone.new,t2=Tone.new)
9
9
  @tones = Fader.new(t1,t2,0)
10
10
  @max_frames = m
11
11
  self.frames = m
@@ -18,37 +18,52 @@ def tone i=0
18
18
  i==1 ? @tones.final : @tones.start
19
19
  end
20
20
 
21
+ # set #max_frames and frames on each Tone if 0.
21
22
  def max_frames= m
22
23
  @max_frames = m
23
24
  # set if none
24
- self.frames = m if @tones.start.frames == 0 && @tones.final.frames == 0
25
+ is_frames_set = true
26
+ is_frames_set = false if tone_count == 1 && tone.frames == 0
27
+ is_frames_set = false if tone_count > 1 && tone(0).frames == 0 && tone(1).frames == 0
28
+ self.frames = m if !is_frames_set
25
29
  end
26
30
 
27
31
  def frames= val
28
- @tones.start.frames = val
29
- @tones.final.frames = val
32
+ tone(0).frames = val
33
+ tone(1).frames = val
30
34
  end
31
35
 
32
36
  # get main freq
33
37
  def freq
34
- tones.start.freq.start
38
+ tone.freq.start
35
39
  end
36
40
  #get main note
37
41
  def note
38
- tones.start.note
42
+ tone.note
39
43
  end
40
44
 
45
+ #set freq of start and final to val
41
46
  def freq= val
42
- @tones.start.freq.start = val
43
- @tones.final.freq.start = val
47
+ tone(0).freq.start = val
48
+ tone(1).freq.start = val
44
49
  end
45
50
 
51
+ #set freq of start and final to Note.freq
52
+ #val:: the note to call freq on
53
+ def note= val
54
+ tone(0).freq.start = val.freq
55
+ tone(1).freq.start = val.freq
56
+ end
57
+
58
+ # multiply all amplitudes (Tone.amp) by factor.
46
59
  def amp_mult(factor)
47
- tones.start.amp.start *= factor
60
+ tone(0).amp.start *= factor
48
61
  #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
62
+ tone(0).amp.final *= factor
63
+ if tone_count > 1
64
+ tone(1).amp.start *= factor
65
+ tone(1).amp.final *= factor
66
+ end
52
67
  end
53
68
 
54
69
 
@@ -6,9 +6,16 @@ class ToneSeq
6
6
  @toneparts = []
7
7
  make(1)
8
8
  end
9
+ #return the total frames of all toneparts combined.
10
+ def frames
11
+ total=0
12
+ toneparts.each {|tp| total+=tp.tone.frames}
13
+ total
14
+ end
9
15
  def tonepart i=0
10
16
  @toneparts[i]
11
17
  end
18
+ #set the frames of each tonepart to val.
12
19
  def frames= val
13
20
  @toneparts.each {|tp| tp.frames = val}
14
21
  end
@@ -19,23 +26,28 @@ class ToneSeq
19
26
  }
20
27
  self
21
28
  end
29
+ #set length of all toneparts to equally add to set when combined.
22
30
  def len= set
23
31
  @toneparts.each {|tp|
24
32
  tp.max_frames = set / toneparts.count
25
33
  tp.frames = set / toneparts.count
26
34
  }
27
35
  end
28
- # add num TonePart to self, with it's max allowable frames as len.
29
- def make(num,len=0)
36
+ #add num TonePart to self, with it's max allowable frames as #frames.
37
+ def make(num)
30
38
  # puts "ToneSeq: making #{num} parts in tone"
31
- num.times { self.toneparts.push TonePart.new((len.to_f/num).round) }
39
+ num.times { self.toneparts.push TonePart.new }
40
+ self.len= (frames.to_f).round
32
41
  end
33
42
 
43
+ #compile all data on all #toneparts, then write it to file(s)
34
44
  def render(parent_hit_index=nil)
35
- files=FileList.new
45
+ data = WaveData.new
36
46
  toneparts.each do |tp|
37
- files.write tp.out(parent_hit_index).out
47
+ data + tp.out(parent_hit_index).out
38
48
  end
49
+ files= FileList.new
50
+ files.write data
39
51
  files
40
52
  end
41
53
 
@@ -22,10 +22,10 @@ def saturation= val
22
22
  saturations.final = val
23
23
  end
24
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.
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
29
  #
30
30
  def out(freq, amp = 1, wave_into = 1)
31
31
  final=WaveData.new
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.0
4
+ version: 0.7.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: