music_coder 0.7.0 → 0.7.1

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.
@@ -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: