juicy 0.1.0 → 0.1.2

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