juicy 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZmVjZDlhNGNmZDc2MmM0NjQ3NjAxOWE4ZWU0NTAzNjYzMDBhYjE0MA==
4
+ NDk5YWVkNTIyMzU4OWQyOGE3NmM4MTllYWMwODFhMTJiNmZmMmU1Nw==
5
5
  data.tar.gz: !binary |-
6
- ZDc4ODNhNzEzZTZiODBlYjRhZjQ3MjU4Y2ZkOGVjNmMyNzVjN2RmMA==
6
+ MGRiOTRkYzRiOWQzYmFhNWUyNjZmOWIzNzNlY2JmNzJjYjA5NTBjOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OTgwNjBkOTlhZDhiOTYyOTgxNTI5ZjNjMjMyNTdkMDQyY2RhMmNjMTJhOGMz
10
- YjE3NmE3ZDQwMzA2YTNhZTFkZTM4YWIzOWEyYjU0NDI0MzM5MWVhNGVjMTg0
11
- ZmQ5NDk1YzM5OTU1NjU4NzRmYjZmNzkxNDQ5OWJkNjI2ZDYxMjQ=
9
+ MzMyOTllNGI2ZmRhODIzYjg4ZjA4ODIwYWMwMGYwNTUyN2Y3MTdiODQ0N2Uw
10
+ N2I0MjJhN2MwZjk1ZjAxMTM4YTdiOGEzNWMxZWUwYmJjNWJjZTAyZjVkN2Uw
11
+ YWRmZjM3ZDUyOTMwZGIyNDY4MjMyOGEyM2M5ZTZhMzY5YzVjYTM=
12
12
  data.tar.gz: !binary |-
13
- YTgyMWY4OTg2M2ZmMzBmMzRkNWQzZmRhZmU1MjMxNzg1MjBlN2MyM2NiZmFi
14
- YzBhYjZlNTBlYzMyYzFiMTc3NDhhMjk2NmYyNWY1OGE2ZWRlYmEwMWE2NTNj
15
- NWRlOTFkMWUzYmY2MmE3ZGE4NzVhMmEwNGU1YjExZDdlZmM2NWU=
13
+ NjlkMjZmNGNlMmUxMzU3NzUyNmFlNGViNzM0MWU4MTMzNjYwNmIwMzk5Yjdl
14
+ YjhmYjdlOWUzYjMzZmRlNmZlMzhkOTBmMjQyZGNlMmNkOTBjNzc1MmZkZjlm
15
+ MzFkYzg4NDU0YjJiNzE3MjYyZGFhYWZkMzQwYTQ1NjMyZTU5MDY=
@@ -1,6 +1,6 @@
1
1
  require_relative '../lib/juicy.rb'
2
2
  include Juicy
3
- play = false
3
+ play = true
4
4
  # To make a pitch, give it a frequency.
5
5
  # By default, the frequency will be tuned to the nearest
6
6
  # frequency in equal temperament.
@@ -9,14 +9,16 @@ pitch = Pitch.new(445)
9
9
  puts pitch
10
10
  pitch.play if play
11
11
 
12
+ sleep 0.3
12
13
  puts "----------"
13
14
  # A Note is a Pitch with a name.
14
15
  # You give it a note name and an octave
15
16
  #
16
- note = Note.new("G#", -1)
17
+ note = Note.new(name: "G#", octave_change: -1)
17
18
  puts note
18
19
  note.play if play
19
20
 
21
+ sleep 0.3
20
22
  puts "----------"
21
23
  # You can also add or subtract from a note to go to the next half step
22
24
  note += 1
@@ -26,9 +28,10 @@ note -= 2
26
28
  puts note
27
29
  note.play if play
28
30
 
31
+ sleep 0.3
29
32
  puts "----------"
30
33
  # With this much, you can make your own scales!
31
- note = Note.new("C")
34
+ note = Note.new(name: "C")
32
35
  major_scale = [2,2,1,2,2,2,1]
33
36
  major_scale.each do |step|
34
37
  puts note
@@ -38,10 +41,11 @@ end
38
41
  puts note
39
42
  note.play if play
40
43
 
44
+ sleep 0.3
41
45
  puts "----------"
42
46
  # Of course, this is cumbersome to do all on our own,
43
47
  # so you have a Scale available to you.
44
- scale = Scale.new(:major, Note.new("D", -1))
48
+ scale = Scale.new(:major, Note.new(name: "D", octave_change: -1))
45
49
  puts scale
46
50
  scale.each_note do |note|
47
51
  note.play if play
@@ -58,8 +62,18 @@ end
58
62
  # current development work is being done on scale objects
59
63
  # and a beat sequencer to bring it all together.
60
64
 
61
- scale = Scale.new(:major, Note.new("G", -1))
65
+ sleep 0.3
66
+ scale = Scale.new(:major, Note.new(name: "G", octave_change: -1))
62
67
  puts scale
63
68
  scale.each_note do |note|
64
- note.play
65
- end
69
+ note.play if play
70
+ end
71
+
72
+ puts
73
+
74
+ # threads = []
75
+ #
76
+ # [660, 440].each do |tone|
77
+ # threads << Thread.new {Win32::Sound.play_freq(tone, 200)}
78
+ # end
79
+ # threads.each {|t| t.join}
@@ -1,7 +1,6 @@
1
1
  # require 'win32-sound'
2
- # require_relative 'juicy/win32/sound.rb'
3
- require_relative 'win32/sound'
4
- require_relative 'win32/wave_out_play_freq'
2
+ #require 'pry'
3
+ require 'sound'
5
4
 
6
5
  require_relative 'juicy/pitch' #toned (or not) frequency in a chosen pitch space (temperament/intonation)
7
6
  require_relative 'juicy/note' #named pitch
@@ -10,17 +10,18 @@ module Juicy
10
10
 
11
11
  }
12
12
 
13
- attr_reader :duration
13
+ attr_reader :duration, :notes
14
14
  attr_accessor :sum_of_queued_chord_durations, :how_far_into_the_song_you_are
15
15
 
16
- def initialize(options = {root: Note.new(:C), quality: :major, inversion: 0, context: :none, duration: Duration.new("quarter")})
17
- @root = (options[:root].kind_of?(Note) ? options[:root] : Note.new(options[:root])) || Note.new(:C)
16
+ def initialize(options = {root: Note.new(name: :C), quality: :major, inversion: 0, context: :none, duration: Duration.new("quarter")})
17
+ #binding.pry
18
+ @root = (options[:root].kind_of?(Note) ? options[:root] : Note.new(name: options[:root])) || Note.new(name: :C)
18
19
  @quality = options[:quality] || :major
19
20
  @inversion = options[:inversion] || 0
20
21
  @context = options[:context] || :none
21
22
  @duration = options[:duration] || Duration.new("quarter")
22
23
  @type = :triad
23
- @notes = [@root, @root+2, @root+4]
24
+ @notes = [@root + QUALITIES[@quality][0], @root + QUALITIES[@quality][1], @root + QUALITIES[@quality][2]]
24
25
  end
25
26
 
26
27
  def to_s
@@ -47,7 +48,7 @@ module Juicy
47
48
  end
48
49
 
49
50
  pitches.each do |interval|
50
- notes << Note.new(PITCHES.key((PITCHES[@root.name]+interval) % 12))
51
+ notes << Note.new(name: PITCHES.key((PITCHES[@root.name]+interval) % 12))
51
52
  end
52
53
 
53
54
  case style
@@ -4,12 +4,22 @@ module Juicy
4
4
 
5
5
  attr_accessor :chords
6
6
 
7
- def initialize
8
- @chords = []
9
- @chords << Chord.new(root: :C)
10
- @chords << Chord.new(root: :F, inversion: 2)
11
- @chords << Chord.new(root: :G, inversion: 1)
12
- @chords << Chord.new(root: :C)
7
+ def initialize(key, mode, numerals = [1,4,1,5])
8
+
9
+ @numerals = numerals
10
+
11
+ #given a key and a mode, a number can tell me what chord.
12
+
13
+ @chords = [
14
+ Chord.new(root: "A", quality: :major),
15
+ Chord.new(root: "D", quality: :major),
16
+ Chord.new(root: "A", quality: :major),
17
+ Chord.new(root: "E", quality: :major)
18
+ ]
19
+ #@numerals.each do |numeral|
20
+ # @chords << Chord.new(numeral)
21
+ #end
22
+
13
23
  end
14
24
 
15
25
  def inspect
@@ -2,8 +2,22 @@ module Juicy
2
2
 
3
3
  class Duration
4
4
 
5
+ DURATIONS = ["whole", "half", "quarter", "eighth", "sixteenth", "thirty-second", "sixty-fourth"]
6
+ MULTIPLIER = {:"" => 0, :triplet => -1, :dotted => 1}
7
+
8
+ attr_reader :duration
9
+
5
10
  def initialize(duration)
6
- @duration = parse_duration(duration)
11
+ # @duration is a Rational number which represents the length of
12
+ # a note relative to a quarter note
13
+ # ex. Duration.new("dotted eighth")
14
+ # @duration = Rational(3,4)
15
+ #
16
+ if duration.kind_of? Rational
17
+ @duration = duration
18
+ else
19
+ @duration = parse_duration(duration)
20
+ end
7
21
 
8
22
  end
9
23
 
@@ -22,39 +36,41 @@ module Juicy
22
36
  def to_s
23
37
  @duration.to_s
24
38
  end
39
+
40
+ def to_f
41
+ @duration.to_f
42
+ end
43
+
44
+ def +(other_duration)
45
+ Duration.new(@duration + other_duration.duration)
46
+ end
47
+
48
+ def *(scalar)
49
+ Duration.new(@duration*scalar)
50
+ end
25
51
 
26
52
  private
27
53
 
28
54
  def beats_of_given_type_per_quarter_note
29
- case @duration
30
- when "quarter"
31
- 1.0
32
- when "half"
33
- 2.0
34
- when "whole"
35
- 4.0
36
- when "eighth"
37
- 0.5
38
- when "sixteenth"
39
- 0.25
40
- else
41
- 1.0
42
- end
55
+ @duration.to_f
43
56
  end
44
57
 
58
+ # takes user input and constructs a Rational number
59
+ # ex. "dotted eighth"
60
+ # Rational(3**
61
+ #
45
62
  def parse_duration(duration)
46
- case duration
47
- when "quarter"
48
- return "quarter"
49
- when "half"
50
- return "half"
51
- when "eighth"
52
- return "eighth"
53
- when "sixteenth"
54
- return "sixteenth"
55
- else
56
- return "quarter"
57
- end
63
+
64
+ # parses note name input
65
+ # user should be able to say "dotted sixteenth" or "quarter" or "triplet eighth"
66
+ groups = duration.to_s.match(/^((dotted|triplet)( |_))?(.*)$/)
67
+ puts "duration: #{duration}" if groups.nil?
68
+
69
+ d = DURATIONS.index(groups[4])
70
+ multiplier = MULTIPLIER[groups[2].to_s.to_sym]
71
+ #binding.pry
72
+
73
+ Rational(4*(3**multiplier),(2**(multiplier+d)))
58
74
  end
59
75
 
60
76
  end
@@ -5,7 +5,7 @@ module Juicy
5
5
 
6
6
  def initialize(time_signature)
7
7
  @beats_per_measure = time_signature[0]
8
- @beat_type = Rational(1/time_signature[1])
8
+ @beat_type = Rational(1,time_signature[1])
9
9
  end
10
10
 
11
11
  end
@@ -5,38 +5,72 @@ module Juicy
5
5
 
6
6
  def initialize(chord_progression = ChordProgression.new, song = Song.new)
7
7
 
8
- @notes = [
9
- Note.new("C", "half", 1),Note.new("E", "", 1),Note.new("G", "", 1),
10
- Note.new("F", "", 1),Note.new("F", "eighth", 1),Note.new("G", "eighth", 1),Note.new("F", "half", 1),
11
- Note.new("C", "half", 1),Note.new("E", "", 1),Note.new("G", "", 1),
12
- Note.new("D", "", 1),Note.new("D", "eighth", 1),Note.new("E", "eighth", 1),Note.new("D", "half", 1)
13
- ]
14
-
8
+ @song = song
15
9
  @notes = []
16
10
 
17
11
  # given the chord progression, make the first note of every chord change a chord tone within 1 octave of the previous.
18
12
  # then add in notes which over all step to the next, but don't have to be chord tones OR simply jump to the next chord tone
19
- song.measures.each do |measure|
13
+ #song.measures.each do |measure|
20
14
  #until
21
- end
22
- (10).times do
23
- @notes << Note.new((["A", "C", "E"]).sample + [""].sample, ["eighth"].sample, [*(-1)..1].sample)
24
- end
25
- (10).times do
26
- @notes << Note.new((["E", "G#", "B"]).sample + [""].sample, ["eighth"].sample, [*(-1)..1].sample)
27
- end
28
- (10).times do
29
- @notes << Note.new((["A", "C", "E"]).sample + [""].sample, ["eighth"].sample, [*(-1)..1].sample)
30
- end
15
+ #end
16
+
17
+ # given a chord progression, make the first note of every measure a chord tone within 1 octave of the previous
18
+
19
+ # while distance_between_notes > 1
20
+ # insert notes between
21
+ # end
31
22
 
23
+ scale = Scale.new(@song.mode, Note.new(name: @song.key))
32
24
 
33
- sum_of_durations = 0
34
- @notes.each do |note|
35
- sum_of_durations += note.duration_in_milliseconds(song.tempo)
36
- note.distance_from_beat_in_milliseconds = (sum_of_durations.round % Duration.duration_of_quarter_note_in_milliseconds(song.tempo).round)
37
- note.plays_during(sum_of_durations.round / Duration.duration_of_quarter_note_in_milliseconds(song.tempo).round + 1)
38
- #puts note.distance_from_beat_in_milliseconds
25
+ #new_note = 0
26
+ #old_note = 0
27
+ @song.measures.each_with_index do |measure, index|
28
+ @notes << Note.new(name: chord_progression.chords[index].notes[0].name, duration: :whole)
29
+ #new_note = Note.new(name: chord_progression.chords[index].notes.sample.name, duration: :whole, octave_change: [*(-1)..(-1)].sample)
30
+ #measure.insert_at(0, Note.new(name: "C"))
31
+ #old_note = new_note
39
32
  end
33
+ #binding.pry
34
+
35
+ #song.measures.each_with_index do |measure, index|
36
+ # @notes[index].duration = "half"
37
+ # note = Note.new(name: chord_progression.chords[measure].notes.sample.name, duration: :half, octave_change: [*(-1)..(-1)].sample)
38
+ # direction = (@notes[measure] <=> @notes[measure+1])
39
+ #
40
+ # insert = false
41
+ #
42
+ # if direction == -1
43
+ # until note >= @notes[measure] && note <= @notes[measure+1]
44
+ # #binding.pry
45
+ # note = Note.new(name: [*"A".."G"].sample, duration: :half, octave_change: [*(-1)..(0)].sample)
46
+ # end
47
+ # insert = true
48
+ # elsif direction == 1
49
+ # until note <= @notes[measure] && note >= @notes[measure+1]
50
+ # #binding.pry
51
+ # note = Note.new(name: [*"A".."G"].sample, duration: :half, octave_change: [*(-1)..(0)].sample)
52
+ # end
53
+ # insert = true
54
+ # end
55
+ # @notes.insert(measure+1, note) if insert
56
+ #end
57
+
58
+ #binding.pry
59
+
60
+ #duration = ["half"]
61
+ #number_of_measures = 1
62
+ #number_of_measures.times do
63
+ # chord_progression.chords.each do |chord|
64
+ # number_of_beats_per_measure = 2
65
+ # number_of_beats_per_measure.times do
66
+ # puts chord.notes.inspect
67
+ # @notes << Note.new(chord.notes.sample.name, [duration].sample, [*(-2)..0].sample)
68
+ # end
69
+ # end
70
+ #end
71
+
72
+
73
+
40
74
 
41
75
  end
42
76
 
@@ -4,16 +4,19 @@ module Juicy
4
4
 
5
5
  include Comparable
6
6
 
7
- @@default_octave = 4
7
+ @@default_octave = 5
8
8
  attr_reader :name, :pitch, :duration, :octave, :occupying_beat
9
9
  attr_accessor :sum_of_queued_note_durations, :how_far_into_the_song_you_are
10
10
  attr_accessor :distance_from_beat_in_milliseconds
11
11
 
12
- def initialize(name = "A", duration = "quarter", octave_change = 0)
13
- @name = parse_note_name(name)
12
+ def initialize(options = {name: "A", duration: :quarter, octave_change: 0})
13
+ options[:name] ||= "A"
14
+ options[:duration] ||= :quarter
15
+ options[:octave_change] ||= 0
16
+ @name = parse_note_name(options[:name])
14
17
  @pitch = Pitch.new(@name)
15
- @duration = Duration.new(duration)
16
- @octave = @@default_octave + octave_change
18
+ @duration = Duration.new(options[:duration])
19
+ @octave = @@default_octave + options[:octave_change]
17
20
  end
18
21
 
19
22
  def to_s
@@ -22,7 +25,7 @@ module Juicy
22
25
  name += "b" if @name=~/flat/
23
26
  "#{name}#{@octave}"
24
27
  end
25
-
28
+
26
29
  def inspect
27
30
  "#{@name}"
28
31
  end
@@ -66,24 +69,31 @@ module Juicy
66
69
  end
67
70
 
68
71
  def +(interval)
69
- step = PITCHES[@name]+interval
70
- octave_change = step/12 #mathy stuff to figure out how many octaves were traversed (cant assume just one was
71
- name = PITCHES.key((PITCHES[@name]+interval) % 12)
72
- Note.new(name, @octave-@@default_octave + octave_change)
72
+ step(interval)
73
+ Note.new(name: new_name, octave_change: octave_change)
73
74
  end
74
75
 
75
76
  def -(interval)
76
- Note.new(PITCHES.key((PITCHES[@name]-interval) % 12))
77
+ step(-1*interval)
78
+ Note.new(name: new_name, octave_change: octave_change)
77
79
  end
78
80
 
79
81
  def <=>(other_note)
80
- if same_octave
82
+ if same_octave(other_note)
81
83
  self.pitch <=> other_note.pitch
82
84
  else
83
85
  self.octave <=> other_note.octave
84
86
  end
85
87
  end
86
88
 
89
+ def succ
90
+ return (self+1)
91
+ end
92
+
93
+ def prev
94
+ return (self-1)
95
+ end
96
+
87
97
  def length
88
98
  duration
89
99
  end
@@ -101,7 +111,6 @@ module Juicy
101
111
  end
102
112
 
103
113
  def duration_in_milliseconds(tempo)
104
- #puts tempo
105
114
  @duration.duration_in_milliseconds(tempo)
106
115
  end
107
116
 
@@ -117,12 +126,29 @@ module Juicy
117
126
  beat == @occupying_beat
118
127
  end
119
128
 
129
+ def duration=(duration)
130
+ @duration = Duration.new(duration)
131
+ end
132
+
120
133
  private
121
134
 
135
+ def octave_change
136
+ @octave - @@default_octave + @step/12
137
+ end
138
+
139
+ def step(interval)
140
+ @step = PITCHES[@name]+interval
141
+ end
142
+
143
+ def new_name
144
+ PITCHES.key((@step) % 12)
145
+ end
146
+
122
147
  def parse_note_name(name)
123
148
  # parses note name input
124
149
  # user should be able to say "A#" or "a#" or "a sharp" or "A_sharp" or "a_s"
125
150
  groups = name.to_s.match(/([a-gA-G])( |_)?(.*)/)
151
+ #binding.pry
126
152
  puts "name: #{name}" if groups.nil?
127
153
  if name.to_s.match "rest"
128
154
  note_name = "_"
@@ -136,13 +162,14 @@ module Juicy
136
162
  "_flat"
137
163
  else
138
164
  puts "Unknown note modifier: '#{groups[3]}'"
165
+ ""
139
166
  end
140
167
  end
141
168
  end
142
169
  note_name.to_sym
143
170
  end
144
171
 
145
- def same_octave
172
+ def same_octave(other_note)
146
173
  (self.octave <=> other_note.octave) == 0
147
174
  end
148
175
 
@@ -25,14 +25,16 @@ module Juicy
25
25
  A_flat: 11
26
26
  }
27
27
 
28
+ # This class encapsulates all of the pitch mechanics for a given temperament.
29
+ #
28
30
  class Pitch
29
-
31
+
30
32
  include Comparable
31
33
  @@temperament = :equal
32
34
  @@pitch_standard = 440.0
33
-
35
+
34
36
  attr_reader :frequency, :confidence
35
-
37
+
36
38
  def initialize(pitch = @@pitch_standard, tune_now = true)
37
39
 
38
40
  if pitch.kind_of? Numeric
@@ -46,11 +48,11 @@ module Juicy
46
48
  end
47
49
 
48
50
  end
49
-
51
+
50
52
  def to_s
51
53
  "#{@frequency}"
52
54
  end
53
-
55
+
54
56
  def tune
55
57
  if out_of_tune
56
58
  step = Math.log(@frequency/440.0,2)*12
@@ -58,27 +60,29 @@ module Juicy
58
60
  @frequency = @@pitch_standard*2**((step.round)/12.0)
59
61
  @tuned = true
60
62
  end
63
+ self
61
64
  end
62
-
65
+
63
66
  def +(interval)
64
67
  change_by (interval)
65
68
  end
66
-
69
+
67
70
  def -(interval)
68
71
  change_by (-interval)
69
72
  end
70
-
73
+
71
74
  def self.play(options = {duration: 200})
72
- Win32::Sound.play_freq(options[:note].pitch.frequency, options[:note].duration)
75
+ binding.pry
76
+ Sound::Out.play_freq(options[:note].pitch.frequency, options[:note].duration)
73
77
  end
74
-
78
+
75
79
  def play(options = {duration: 200, octave: 0, volume: 1})
76
80
  options[:duration] ||= 200
77
81
  options[:octave] ||= 0
78
82
  options[:volume] ||= 1
79
- Win32::Sound.play_freq(@frequency*2**(options[:octave]), options[:duration], options[:volume])
83
+ ::Sound::Out.play_freq(@frequency*2**(options[:octave]), options[:duration], options[:volume])
80
84
  end
81
-
85
+
82
86
  def prepare(options = {duration: 200, octave: 0, volume: 1})
83
87
  options[:duration] ||= 200
84
88
  options[:octave] ||= 0
@@ -86,23 +90,23 @@ module Juicy
86
90
 
87
91
  return Thread.new{Win32::Sound.play_freq(@frequency*2**(options[:octave]), options[:duration], options[:volume], true)}
88
92
  end
89
-
93
+
90
94
  def <=>(other_pitch)
91
95
  self.frequency <=> other_pitch.frequency
92
96
  end
93
-
97
+
94
98
  private
95
-
99
+
96
100
  def out_of_tune
97
101
  !@tuned
98
102
  end
99
-
103
+
100
104
  def change_by (interval)
101
105
  if @@temperament.eql? :equal
102
106
  Pitch.new(@frequency*2**(interval/12.0))
103
107
  end
104
108
  end
105
-
109
+
106
110
  end
107
111
 
108
112
  end
@@ -62,8 +62,14 @@ module Juicy
62
62
  generate_notes
63
63
  end
64
64
 
65
+ #def each
66
+ # yield SCALE_TYPES[@type]
67
+ #end
68
+
65
69
  def each
66
- yield SCALE_TYPES[@type]
70
+ (SCALE_TYPES[@type].size+1).times do
71
+ yield @notes.next.name
72
+ end
67
73
  end
68
74
 
69
75
  def each_note
@@ -72,6 +78,26 @@ module Juicy
72
78
  end
73
79
  end
74
80
 
81
+ def interval_between(note1, note2)
82
+ half_steps = 0
83
+ direction = (note1 <=> note2)
84
+ if direction == 0
85
+ elsif direction == -1
86
+ note = note1.dup
87
+ until (note <=> note2) == 0
88
+ note += 1
89
+ half_steps += 1
90
+ end
91
+ elsif direction == 1
92
+ note = note1.dup
93
+ until (note <=> note2) == 0
94
+ note -= 1
95
+ half_steps -= 1
96
+ end
97
+ end
98
+ half_steps
99
+ end
100
+
75
101
  private
76
102
 
77
103
  def generate_notes
@@ -3,7 +3,7 @@ module Juicy
3
3
 
4
4
  class Song
5
5
 
6
- attr_reader :measures, :tempo
6
+ attr_reader :measures, :tempo, :key, :mode
7
7
 
8
8
  def initialize
9
9
 
@@ -12,47 +12,77 @@ module Juicy
12
12
  @voices << Voice.new
13
13
  @key = :A
14
14
  @mode = :major
15
- @tempo = 150.0
15
+ @tempo = 100.0
16
16
  @time_signature = [4,4]
17
17
  @measures = []
18
18
  4.times {@measures << Measure.new(@time_signature)}
19
-
20
- end
21
-
22
- def play
23
-
19
+
24
20
  # have musical construct yield notes up to a note manager/beat sequencer for each track
25
21
  # chords will eventually have a play style (various types of arpeggiation and such), but
26
22
  # for now they'll all just play all their notes at once for the given duration
27
23
 
28
- tracks = []
24
+ @tracks = []
29
25
 
30
- chord_progression = ChordProgression.new
26
+ key = Key.new
27
+ chord_progression = ChordProgression.new(@key, @mode)
31
28
 
32
- #tracks << Track.new(chord_progression.initial_play_time, chord_progression.to_a, @tempo)
29
+ @tracks << Track.new(0, demo_melody, @tempo)
30
+ number_of_tracks = 1
31
+ number_of_tracks.times do
32
+ melody = Melody.new(chord_progression, self)
33
+ @tracks << Track.new(melody.initial_play_time, melody, @tempo)
34
+ end
33
35
 
34
- melody = Melody.new(chord_progression, self)
35
- puts melody.inspect
36
- tracks << Track.new(melody.initial_play_time, melody.to_a, @tempo)
37
- melody = Melody.new(chord_progression, self)
38
- puts melody.inspect
39
- tracks << Track.new(melody.initial_play_time, melody.to_a, @tempo)
40
- melody = Melody.new(chord_progression, self)
41
- puts melody.inspect
42
- tracks << Track.new(melody.initial_play_time, melody.to_a, @tempo)
43
- melody = Melody.new(chord_progression, self)
44
- puts melody.inspect
45
- tracks << Track.new(melody.initial_play_time, melody.to_a, @tempo)
36
+ end
37
+
38
+ def play
46
39
 
47
- Track.play_concurrently(tracks, @tempo)
40
+ Track.play_concurrently(@tracks, @tempo)
48
41
 
49
-
50
42
  end
51
43
 
52
44
  def beat_length_in_milliseconds
53
45
  60_000.0/@tempo
54
46
  end
55
47
 
48
+ def demo_melody
49
+ @demo_melody ||= [
50
+ Note.new(name: "A", duration: :eighth, octave_change: -1),
51
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
52
+ Note.new(name: "C#", duration: :eighth, octave_change: -1),
53
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
54
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
55
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
56
+ Note.new(name: "C#", duration: :eighth, octave_change: -1),
57
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
58
+ Note.new(name: "A", duration: :eighth, octave_change: -1),
59
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
60
+ Note.new(name: "C#", duration: :eighth, octave_change: -1),
61
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
62
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
63
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
64
+ Note.new(name: "C#", duration: :eighth, octave_change: -1),
65
+ Note.new(name: "E", duration: :eighth, octave_change: -1),
66
+
67
+ Note.new(name: "D", duration: :eighth, octave_change: -1),
68
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
69
+ Note.new(name: "F#", duration: :eighth, octave_change: -1),
70
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
71
+ Note.new(name: "D", duration: :eighth, octave_change: 0),
72
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
73
+ Note.new(name: "F#", duration: :eighth, octave_change: -1),
74
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
75
+ Note.new(name: "D", duration: :eighth, octave_change: -1),
76
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
77
+ Note.new(name: "F#", duration: :eighth, octave_change: -1),
78
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
79
+ Note.new(name: "D", duration: :eighth, octave_change: 0),
80
+ Note.new(name: "A", duration: :eighth, octave_change: 0),
81
+ Note.new(name: "F#", duration: :eighth, octave_change: -1),
82
+ Note.new(name: "A", duration: :eighth, octave_change: 0)
83
+ ]
84
+ end
85
+
56
86
  end
57
87
 
58
88
  end
@@ -8,9 +8,24 @@ module Juicy
8
8
  def initialize(init_time, notes, tempo)
9
9
 
10
10
  @start_time = init_time
11
- @notes = notes
11
+ @notes = notes.to_a
12
12
  @tempo = tempo
13
+
14
+ @track_length_in_milliseconds = 0
15
+ @notes.each do |note|
16
+ note.distance_from_beat_in_milliseconds = distance_from_beat_in_milliseconds
17
+ note.plays_during occupying_beat
18
+ @track_length_in_milliseconds += note.duration_in_milliseconds(@tempo)
19
+ end
20
+
21
+ end
13
22
 
23
+ def distance_from_beat_in_milliseconds
24
+ (@track_length_in_milliseconds.round % Duration.duration_of_quarter_note_in_milliseconds(@tempo).round)
25
+ end
26
+
27
+ def occupying_beat
28
+ @track_length_in_milliseconds.round / Duration.duration_of_quarter_note_in_milliseconds(@tempo).round + 1
14
29
  end
15
30
 
16
31
  def play
@@ -116,14 +131,14 @@ module Juicy
116
131
  end
117
132
 
118
133
  def self.play_concurrently(tracks, tempo)
119
- @song_start_time = Time.now
134
+ #@song_start_time = Time.now
120
135
  threads = []
121
136
  # iterate over each track over and over again, preparing notes in the current beat
122
137
  # in each iteration, store the notes you've prepared into an array and store that
123
138
  # array into the prepared_notes array which the playing thread will play notes from
124
139
  # when it has enough to play.
125
140
  # A track is an array of playable musical objects. for now, these are individual
126
- # notes or chords. a chord is an array of individual notes
141
+ # notes or chords. a chord is an array of individual notes to be played simultaneously
127
142
  #
128
143
  prepared_beats = []
129
144
  out_of_notes_to_prepare = false
@@ -131,12 +146,25 @@ module Juicy
131
146
  Thread.current[:name] = "prepare beats thread"
132
147
  current_beat = 1
133
148
  last_beat = 1
149
+ # find the final beat of all tracks combined
134
150
  tracks.each do |track|
135
- track.notes.each do |note|
136
- last_beat = note.occupying_beat if note.occupying_beat > last_beat
151
+ track.notes.each do |playable_thing|
152
+ if playable_thing.kind_of?(Note)
153
+ last_beat = playable_thing.occupying_beat if playable_thing.occupying_beat > last_beat
154
+ elsif playable_thing.kind_of?(Chord)
155
+ playable_thing.notes.each do |note|
156
+ last_beat = note.occupying_beat if note.occupying_beat > last_beat
157
+ end
158
+ end
137
159
  end
138
160
  end
161
+ # until you've prepared all the notes, prepare each note in an array with
162
+ # other notes who occupy the same beat
139
163
  until current_beat > last_beat
164
+ # only add/prepare beats if there are fewer than 20 prepared already
165
+ # so that we don't hit the thread limit. This assumes that a buffer of 20
166
+ # is sufficent and that each beat has, on average, fewer than 30 notes.
167
+ # if there are too many notes, weird things start to happen, so don't do that.
140
168
  if prepared_beats.size <= 20
141
169
  this_beats_notes = []
142
170
  tracks.each do |track|
@@ -154,6 +182,7 @@ module Juicy
154
182
  prepared_beats << this_beats_notes
155
183
  current_beat += 1
156
184
  end
185
+ # Pass thread execution over to note playing so that there is no delay in playback
157
186
  Thread.pass
158
187
  end
159
188
  out_of_notes_to_prepare = true
@@ -165,26 +194,27 @@ module Juicy
165
194
  # "start" the song by iterating through each element, waking up each note
166
195
  # and then waiting the remainder of a beat's worth of milliseconds until the
167
196
  # next beat
168
- until (prepared_beats.size >= 2) || out_of_notes_to_prepare
197
+ until (prepared_beats.size >= 4) || out_of_notes_to_prepare
169
198
  sleep 0.01
170
199
  end
171
200
  last_note = Thread.new {}
172
201
  time = Time.now
173
202
  until prepared_beats.empty? && out_of_notes_to_prepare
174
203
  time = Time.now
204
+ # take the next beat's worth of notes
175
205
  beat = prepared_beats.shift
206
+ # start each note in the beat as its own thread
176
207
  beat.each do |note|
177
208
  last_note = note.play_prepared
178
209
  end
179
- sleep rand(100..300)/1000.0
210
+ # to ensure simultaneity, sleep for however much longer a beat lasts
211
+ # at the current tempo
180
212
  sleep_amount = Duration.duration_of_quarter_note_in_milliseconds(tempo)/1000.0 - (Time.now - time)
181
- puts sleep_amount
182
213
  sleep sleep_amount unless sleep_amount < 0
183
214
  end
184
215
  last_note.join
185
216
  end
186
217
 
187
- #threads.each {|t| puts t[:name] }
188
218
  threads.each {|t| t.join}
189
219
  end
190
220
 
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: juicy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominic Muller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-02 00:00:00.000000000 Z
11
+ date: 2014-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: win32-sound
14
+ name: sound
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
17
20
  - - ! '>='
18
21
  - !ruby/object:Gem::Version
19
- version: 0.6.0
22
+ version: 0.0.2
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.0'
24
30
  - - ! '>='
25
31
  - !ruby/object:Gem::Version
26
- version: 0.6.0
32
+ version: 0.0.2
27
33
  description: Generates songs
28
34
  email: nicklink483@gmail.com
29
35
  executables: []
@@ -46,9 +52,7 @@ files:
46
52
  - lib/juicy/song.rb
47
53
  - lib/juicy/track.rb
48
54
  - lib/juicy/voice.rb
49
- - lib/win32/wave_out_play_freq.rb
50
- - lib/win32/win32-mmlib_structs.rb
51
- homepage: https://github.com/nicklink483/juicy
55
+ homepage: https://github.com/RSMP/juicy
52
56
  licenses:
53
57
  - MIT
54
58
  metadata: {}
@@ -73,3 +77,4 @@ signing_key:
73
77
  specification_version: 4
74
78
  summary: Song writing tool
75
79
  test_files: []
80
+ has_rdoc:
@@ -1,131 +0,0 @@
1
-
2
- require_relative 'win32-mmlib_structs'
3
-
4
- module Win32
5
-
6
- class Sound
7
-
8
- # Plays a frequency for a specified duration at a given volume.
9
- # Defaults are 440Hz, 1 second, full volume.
10
- # Result is a single channel, 44100Hz sampled, 16 bit sine wave.
11
- # If multiple instances are plays in simultaneous threads,
12
- # they will be started and played at the same time
13
- #
14
- # ex.: threads = []
15
- # [440, 660].each do |freq|
16
- # threads << Thread.new { Win32::Sound.play_freq(freq) }
17
- # end
18
- # threads.each { |th| th.join }
19
- #
20
- # the first frequency in this array (440) will wait until the
21
- # thread for 660 finished calculating its PCM array and they
22
- # will both start streaming at the same time.
23
- #
24
- def self.play_freq(frequency = 440, duration = 1000, volume = 1, pause_execution = false)
25
-
26
- if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
27
- raise ArgumentError, 'invalid frequency'
28
- end
29
-
30
- if duration < 0 || duration > 5000
31
- raise ArgumentError, 'invalid duration'
32
- end
33
-
34
- stream(pause_execution) { |wfx|
35
- data = generate_pcm_integer_array_for_freq(frequency, duration, volume)
36
- data_buffer = FFI::MemoryPointer.new(:int, data.size)
37
- data_buffer.write_array_of_int data
38
- buffer_length = wfx[:nAvgBytesPerSec]*duration/1000
39
- WAVEHDR.new(data_buffer, buffer_length)
40
- }
41
-
42
- end
43
-
44
- private
45
-
46
- # Sets up a ready-made waveOut stream to push a PCM integer array to.
47
- # It expects a block to be associated with the method call to which
48
- # it will yield an instance of WAVEFORMATEX that the block uses
49
- # to prepare a WAVEHDR to return to the function.
50
- # The WAVEHDR can contain either a self-made PCM integer array
51
- # or an array from a wav file or some other audio file converted
52
- # to PCM.
53
- #
54
- # This function will take the entire PCM array and create one
55
- # giant buffer, so it is not intended for audio streams larger
56
- # than 5 seconds.
57
- #
58
- # In order to play larger audio files, you will have to use the waveOut
59
- # functions and structs to set up a double buffer to incrementally
60
- # push PCM data to.
61
- #
62
- def self.stream(pause_execution)
63
-
64
- hWaveOut = HWAVEOUT.new
65
- wfx = WAVEFORMATEX.new
66
-
67
- if ((error_code = waveOutOpen(hWaveOut.pointer, WAVE_MAPPER, wfx.pointer, 0, 0, 0)) != 0)
68
- raise SystemCallError.new('waveOutOpen', FFI.errno)
69
- end
70
-
71
- header = yield(wfx)
72
-
73
- if ((error_code = waveOutPrepareHeader(hWaveOut[:i], header.pointer, header.size)) != 0)
74
- raise SystemCallError.new('waveOutPrepareHeader', FFI.errno)
75
- end
76
-
77
- if pause_execution
78
- Thread.stop
79
- Thread.current[:sleep_time] ||= 0
80
- sleep Thread.current[:sleep_time]
81
- end
82
- Thread.pass
83
-
84
- if (waveOutWrite(hWaveOut[:i], header.pointer, header.size) != 0)
85
- raise SystemCallError.new('waveOutWrite', FFI.errno)
86
- end
87
-
88
- while (waveOutUnprepareHeader(hWaveOut[:i], header.pointer, header.size) == 33)
89
- sleep 0.1
90
- end
91
-
92
- if ((error_code = waveOutClose(hWaveOut[:i])) != 0)
93
- raise SystemCallError.new('waveOutClose', FFI.errno)
94
- end
95
-
96
- self
97
- end
98
-
99
- # Generates an array of PCM integers to play a particular frequency
100
- # It also ramps up and down the volume in the first and last
101
- # 200 milliseconds to prevent audio clicking.
102
- #
103
- def self.generate_pcm_integer_array_for_freq(freq, duration, volume)
104
-
105
- data = []
106
- ramp = 200.0
107
- samples = (44100/2*duration/1000.0).floor
108
-
109
- samples.times do |sample|
110
-
111
- angle = (2.0*Math::PI*freq) * sample/samples * duration/1000
112
- factor = Math.sin(angle)
113
- x = 32768.0*factor*volume
114
-
115
- if sample < ramp
116
- x *= sample/ramp
117
- end
118
- if samples - sample < ramp
119
- x *= (samples - sample)/ramp
120
- end
121
-
122
- data << x.floor
123
- end
124
-
125
- data
126
-
127
- end
128
-
129
- end
130
-
131
- end
@@ -1,67 +0,0 @@
1
-
2
- module Win32
3
-
4
- # Define an HWAVEOUT struct for use by all the waveOut functions.
5
- # It is a handle to a waveOut stream, so starting up multiple
6
- # streams using different handles allows for simultaneous playback.
7
- # You never need to actually look at the struct, C takes care of
8
- # its value.
9
-
10
- class HWAVEOUT < FFI::Struct
11
-
12
- layout :i, :int
13
-
14
- end
15
-
16
- # define WAVEFORMATEX which defines the format (PCM in this case)
17
- # and various properties like sampling rate, number of channels, etc.
18
-
19
- class WAVEFORMATEX < FFI::Struct
20
-
21
- def initialize(nSamplesPerSec = 44100, wBitsPerSample = 16, nChannels = 1, cbSize = 0)
22
- self[:wFormatTag] = WAVE_FORMAT_PCM
23
- self[:nChannels] = nChannels
24
- self[:nSamplesPerSec] = nSamplesPerSec
25
- self[:wBitsPerSample] = wBitsPerSample
26
- self[:cbSize] = cbSize
27
- self[:nBlockAlign] = (self[:wBitsPerSample] >> 3) * self[:nChannels]
28
- self[:nAvgBytesPerSec] = self[:nBlockAlign] * self[:nSamplesPerSec]
29
- end
30
-
31
- layout :wFormatTag, :ushort,
32
- :nChannels, :ushort,
33
- :nSamplesPerSec, :ulong,
34
- :nAvgBytesPerSec, :ulong,
35
- :nBlockAlign, :ushort,
36
- :wBitsPerSample, :ushort,
37
- :cbSize, :ushort
38
-
39
- end
40
-
41
- #define WAVEHDR which is a header to a block of audio
42
- #lpData is a pointer to the block of native memory that,
43
- # in this case, is an integer array of PCM data
44
-
45
- class WAVEHDR < FFI::Struct
46
-
47
- def initialize(lpData, dwBufferLength, dwFlags = 0, dwLoops = 1)
48
- self[:lpData] = lpData
49
- self[:dwBufferLength] = dwBufferLength
50
- # self[:dwBytesRecorded] = dwBytesRecorded # used for waveIn, not waveOut
51
- # self[:dwUser] = dwUser
52
- self[:dwFlags] = dwFlags
53
- self[:dwLoops] = dwLoops
54
- end
55
-
56
- layout :lpData, :pointer,
57
- :dwBufferLength, :ulong,
58
- :dwBytesRecorded, :ulong,
59
- :dwUser, :ulong,
60
- :dwFlags, :ulong,
61
- :dwLoops, :ulong,
62
- :lpNext, :pointer,
63
- :reserved, :ulong
64
-
65
- end
66
-
67
- end