juicy 0.0.8 → 0.1.0

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
- YjQ4MDJlZTZjMTFkOWI5NzJkNTFiN2Y3OWY3YjYwMzdlYmI3NGNkNw==
4
+ ZmVjZDlhNGNmZDc2MmM0NjQ3NjAxOWE4ZWU0NTAzNjYzMDBhYjE0MA==
5
5
  data.tar.gz: !binary |-
6
- OTFkMmQ1ZWEwNzM3MTEwMjVlY2EyY2JkYWY4M2M5MjE4OWM2YzAwNA==
6
+ ZDc4ODNhNzEzZTZiODBlYjRhZjQ3MjU4Y2ZkOGVjNmMyNzVjN2RmMA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzQ3MmZiOTEyOWQ4M2IzZTUyNTIzMDI3NjUzMjQxYjU5ZTcyNDI4M2ExYjFl
10
- N2UxN2FkYjgzNTg4MGJlM2FkOWVmNGIyMThkMzkyYWQ4YjQ1YWFmMjFhZmNm
11
- Mzk2YjgyMWExMmM0ZTY0NDM4NTgxZTFiMTQxMGY4OTNiMzY2YzU=
9
+ OTgwNjBkOTlhZDhiOTYyOTgxNTI5ZjNjMjMyNTdkMDQyY2RhMmNjMTJhOGMz
10
+ YjE3NmE3ZDQwMzA2YTNhZTFkZTM4YWIzOWEyYjU0NDI0MzM5MWVhNGVjMTg0
11
+ ZmQ5NDk1YzM5OTU1NjU4NzRmYjZmNzkxNDQ5OWJkNjI2ZDYxMjQ=
12
12
  data.tar.gz: !binary |-
13
- OGE2ZDNkMDNhMGNmZjYyZjY3OTYzZmJkMzk1ZGQzNGRmM2NhMmE5M2Y0OTkw
14
- ZjIyMDQ2NmY5ZjdiMzU0YmE2OGEzNjNkMjEyMjQ3NmJlOTcwYWMwOGYyZTJi
15
- YjIxOTM3OGU2OTliYzdlOTRkNWZmNGM1MWJjZTc1MGJhYzdiMGE=
13
+ YTgyMWY4OTg2M2ZmMzBmMzRkNWQzZmRhZmU1MjMxNzg1MjBlN2MyM2NiZmFi
14
+ YzBhYjZlNTBlYzMyYzFiMTc3NDhhMjk2NmYyNWY1OGE2ZWRlYmEwMWE2NTNj
15
+ NWRlOTFkMWUzYmY2MmE3ZGE4NzVhMmEwNGU1YjExZDdlZmM2NWU=
@@ -1,27 +1,65 @@
1
1
  require_relative '../lib/juicy.rb'
2
2
  include Juicy
3
+ play = false
4
+ # To make a pitch, give it a frequency.
5
+ # By default, the frequency will be tuned to the nearest
6
+ # frequency in equal temperament.
7
+ #
8
+ pitch = Pitch.new(445)
9
+ puts pitch
10
+ pitch.play if play
3
11
 
4
- p = Pitch.new(445)
5
- puts p
6
- p.play
12
+ puts "----------"
13
+ # A Note is a Pitch with a name.
14
+ # You give it a note name and an octave
15
+ #
16
+ note = Note.new("G#", -1)
17
+ puts note
18
+ note.play if play
7
19
 
8
- note = Note.new("A")
9
- #12.times {(n+=1).play}
20
+ puts "----------"
21
+ # You can also add or subtract from a note to go to the next half step
22
+ note += 1
23
+ puts note
24
+ note.play if play
25
+ note -= 2
26
+ puts note
27
+ note.play if play
10
28
 
11
- ch = Chord.new(root: note)
12
- ch.play
29
+ puts "----------"
30
+ # With this much, you can make your own scales!
31
+ note = Note.new("C")
32
+ major_scale = [2,2,1,2,2,2,1]
33
+ major_scale.each do |step|
34
+ puts note
35
+ note.play if play
36
+ note += step
37
+ end
38
+ puts note
39
+ note.play if play
13
40
 
14
- ch2 = Chord.new(root: note)
15
- ch2.play
16
- ch2.play(duration: 500)
41
+ puts "----------"
42
+ # Of course, this is cumbersome to do all on our own,
43
+ # so you have a Scale available to you.
44
+ scale = Scale.new(:major, Note.new("D", -1))
45
+ puts scale
46
+ scale.each_note do |note|
47
+ note.play if play
48
+ end
49
+ #
50
+ # ch = Chord.new(root: note)
51
+ # ch.play
52
+ #
53
+ # ch2 = Chord.new(root: note)
54
+ # ch2.play
55
+ # ch2.play(duration: 500)
17
56
 
18
57
  # while there is enough here to write out your own scales,
19
58
  # current development work is being done on scale objects
20
59
  # and a beat sequencer to bring it all together.
21
60
 
22
- intervals = [0,2,2,1,2,2,2,1]
23
- intervals.each do |interval|
24
- (note+=interval).play
25
- end
26
-
27
- puts ch
61
+ scale = Scale.new(:major, Note.new("G", -1))
62
+ puts scale
63
+ scale.each_note do |note|
64
+ note.play
65
+ end
@@ -1,10 +1,20 @@
1
- require_relative '../../win32-sound/lib/win32/sound.rb'
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
5
 
3
- require_relative 'juicy/pitch.rb' #toned (or not) frequency in a chosen pitch space (temperament/intonation)
4
- require_relative 'juicy/note.rb' #named pitch
5
- require_relative 'juicy/chord.rb' #collection of notes
6
- require_relative 'juicy/chord_progression.rb' #collection of chords in sequence
7
- require_relative 'juicy/scale.rb' #sequence of relative pitch changes ex. chormatic, diatonic, whole-note, pentatonic
8
- require_relative 'juicy/mode.rb' #'flavor' of diatonic scale
9
- require_relative 'juicy/scale_degree.rb' #index of given scale relative to root/tonic
10
- require_relative 'juicy/key.rb' #scale at a given root note, i.e. Juicy::Note
6
+ require_relative 'juicy/pitch' #toned (or not) frequency in a chosen pitch space (temperament/intonation)
7
+ require_relative 'juicy/note' #named pitch
8
+ require_relative 'juicy/duration' #length of note in musical time (quarter note, eighth note, etc.)
9
+ require_relative 'juicy/chord' #collection of notes
10
+ require_relative 'juicy/chord_progression' #collection of chords in sequence
11
+ require_relative 'juicy/melody' #collection of chords in sequence
12
+
13
+ require_relative 'juicy/scale' #sequence of relative pitch changes ex. chromatic, diatonic, whole-note, pentatonic
14
+ require_relative 'juicy/mode' #'flavor' of diatonic scale
15
+ require_relative 'juicy/scale_degree' #index of given scale relative to root/tonic
16
+ require_relative 'juicy/key' #scale at a given root note, i.e. Juicy::Note
17
+ require_relative 'juicy/voice' #an instrument
18
+ require_relative 'juicy/measure' #a measure of beats
19
+ require_relative 'juicy/track' #a track of notes
20
+ require_relative 'juicy/song' #
@@ -10,11 +10,21 @@ module Juicy
10
10
 
11
11
  }
12
12
 
13
- def initialize(options = {root: Note.new(:C), quality: :major, inversion: 0, context: :none})
13
+ attr_reader :duration
14
+ attr_accessor :sum_of_queued_chord_durations, :how_far_into_the_song_you_are
15
+
16
+ def initialize(options = {root: Note.new(:C), quality: :major, inversion: 0, context: :none, duration: Duration.new("quarter")})
14
17
  @root = (options[:root].kind_of?(Note) ? options[:root] : Note.new(options[:root])) || Note.new(:C)
15
18
  @quality = options[:quality] || :major
16
19
  @inversion = options[:inversion] || 0
17
20
  @context = options[:context] || :none
21
+ @duration = options[:duration] || Duration.new("quarter")
22
+ @type = :triad
23
+ @notes = [@root, @root+2, @root+4]
24
+ end
25
+
26
+ def to_s
27
+ "chord type: #{@type}, quality: #{@quality}, root: #{@root}, inversion: #{@inversion}"
18
28
  end
19
29
 
20
30
  def inspect
@@ -35,6 +45,7 @@ module Juicy
35
45
  pitches = pitches[1..-1] + [(pitches[0]+12)]
36
46
  inversion -= 1
37
47
  end
48
+
38
49
  pitches.each do |interval|
39
50
  notes << Note.new(PITCHES.key((PITCHES[@root.name]+interval) % 12))
40
51
  end
@@ -103,6 +114,41 @@ module Juicy
103
114
 
104
115
  alias_method :invert, :cycle
105
116
 
117
+ def prepare(options = {duration: 200, octave: (@octave-Note.default_octave)})
118
+ @prepared_notes = []
119
+ @notes.each do |note|
120
+ options[:duration] = options[:duration] || 200
121
+ options[:octave] = options[:octave] || (note.octave-Note.default_octave)
122
+
123
+ @prepared_notes << note.prepare(options)
124
+ end
125
+ end
126
+
127
+ def play_prepared
128
+ th = []
129
+ #puts @prepared_notes.inspect
130
+ @prepared_notes.each do |note|
131
+ th << Thread.new {
132
+ note.play_prepared.join
133
+
134
+
135
+ }
136
+ end
137
+ th.each {|t| t.join}
138
+ end
139
+
140
+ def initial_play_time=(time)
141
+ @initial_play_time = time
142
+ end
143
+
144
+ def initial_play_time
145
+ @initial_play_time
146
+ end
147
+
148
+ def duration_in_milliseconds(tempo)
149
+ @duration.duration_in_milliseconds(tempo)
150
+ end
151
+
106
152
  end
107
153
 
108
154
  end
@@ -5,21 +5,11 @@ module Juicy
5
5
  attr_accessor :chords
6
6
 
7
7
  def initialize
8
- #@chords = []
9
- #@chords << Chord.new(root: :C)
10
- #@chords << Chord.new(root: :G, inversion: 1)-12
11
- #@chords << Chord.new(root: :A, quality: :minor)
12
- #@chords << Chord.new(root: :F, inversion: 1)-12
13
8
  @chords = []
14
9
  @chords << Chord.new(root: :C)
15
- @chords << Chord.new(root: :F, inversion: 2)-12
16
- @chords << Chord.new(root: :G, inversion: 1)-12
10
+ @chords << Chord.new(root: :F, inversion: 2)
11
+ @chords << Chord.new(root: :G, inversion: 1)
17
12
  @chords << Chord.new(root: :C)
18
- #@progression = []
19
- #@progression << ScaleDegree.new("I")
20
- #@progression << ScaleDegree.new("IV")
21
- #@progression << ScaleDegree.new("V")
22
- #@progression << ScaleDegree.new("I")
23
13
  end
24
14
 
25
15
  def inspect
@@ -38,6 +28,15 @@ module Juicy
38
28
  output[0..-3]
39
29
  end
40
30
 
31
+ def to_a
32
+ [Chord.new(root: "C")]
33
+ @chords
34
+ end
35
+
36
+ def initial_play_time
37
+ 0
38
+ end
39
+
41
40
  def play
42
41
  @chords.each do |chord|
43
42
  4.times {chord.play}
@@ -0,0 +1,62 @@
1
+ module Juicy
2
+
3
+ class Duration
4
+
5
+ def initialize(duration)
6
+ @duration = parse_duration(duration)
7
+
8
+ end
9
+
10
+ def self.duration_of_quarter_note_in_milliseconds(tempo)
11
+ 60_000.0/tempo
12
+ end
13
+
14
+ def duration_in_milliseconds(tempo)
15
+ # how long a note is depends on the tempo and the musical duration
16
+ # e.g. at 120 bpm, and a duration of an eighth note, the duration
17
+ # in milliseconds would be 60_000.0/120/2
18
+ # milliseconds_per_second*seconds_per_minute/bpm/beats_of_given_type_per_quarter_note
19
+ 60_000.0/tempo*beats_of_given_type_per_quarter_note
20
+ end
21
+
22
+ def to_s
23
+ @duration.to_s
24
+ end
25
+
26
+ private
27
+
28
+ 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
43
+ end
44
+
45
+ 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
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,13 @@
1
+
2
+ module Juicy
3
+
4
+ class Measure
5
+
6
+ def initialize(time_signature)
7
+ @beats_per_measure = time_signature[0]
8
+ @beat_type = Rational(1/time_signature[1])
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,53 @@
1
+
2
+ module Juicy
3
+
4
+ class Melody
5
+
6
+ def initialize(chord_progression = ChordProgression.new, song = Song.new)
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
+
15
+ @notes = []
16
+
17
+ # given the chord progression, make the first note of every chord change a chord tone within 1 octave of the previous.
18
+ # 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|
20
+ #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
31
+
32
+
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
39
+ end
40
+
41
+ end
42
+
43
+ def to_a
44
+ @notes
45
+ end
46
+
47
+ def initial_play_time
48
+ 0
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -5,6 +5,24 @@ module Juicy
5
5
 
6
6
  class Mode
7
7
 
8
+ attr_reader :rotate
9
+
10
+ def initialize(type = :ionian)
11
+ @type = type
12
+ @rotate = case @type
13
+ when :major
14
+ 0
15
+ when :minor
16
+ -2
17
+ else
18
+ 0
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ "#{@type}"
24
+ end
25
+
8
26
  end
9
27
 
10
28
  end
@@ -2,51 +2,150 @@ module Juicy
2
2
 
3
3
  class Note
4
4
 
5
- attr_reader :name, :pitch
5
+ include Comparable
6
+
7
+ @@default_octave = 4
8
+ attr_reader :name, :pitch, :duration, :octave, :occupying_beat
9
+ attr_accessor :sum_of_queued_note_durations, :how_far_into_the_song_you_are
10
+ attr_accessor :distance_from_beat_in_milliseconds
6
11
 
7
- def initialize(name)
12
+ def initialize(name = "A", duration = "quarter", octave_change = 0)
8
13
  @name = parse_note_name(name)
9
14
  @pitch = Pitch.new(@name)
15
+ @duration = Duration.new(duration)
16
+ @octave = @@default_octave + octave_change
10
17
  end
11
18
 
19
+ def to_s
20
+ name = @name[0]
21
+ name += "#" if @name=~/sharp/
22
+ name += "b" if @name=~/flat/
23
+ "#{name}#{@octave}"
24
+ end
25
+
12
26
  def inspect
13
27
  "#{@name}"
14
28
  end
15
29
 
16
- def play(options = {duration: 200})
30
+ def play(options = {duration: 200, octave: (@octave-@@default_octave)})
31
+ if @name == :_
32
+ options[:volume] = 0
33
+ end
17
34
  @pitch.play(options)
18
35
  end
19
36
 
37
+ def prepare(options = {duration: 200, octave: (@octave-@@default_octave)})
38
+ options[:duration] = options[:duration] || 200
39
+ options[:octave] = options[:octave] || (@octave-@@default_octave)
40
+ if @name == :_
41
+ options[:volume] = 0
42
+ end
43
+ Thread.pass
44
+ @prepared_note = @pitch.prepare(options)
45
+ @prepared_note[:sleep_time] = @distance_from_beat_in_milliseconds/1000.0
46
+ #puts @prepared_note.status
47
+ until @prepared_note.status.eql? "sleep"
48
+ sleep 0.001
49
+ #puts @prepared_note.status
50
+ end
51
+ @prepared_note
52
+ self
53
+ end
54
+
55
+ def play_prepared
56
+ #puts @prepared_note.status
57
+ #puts "playing"
58
+ until @prepared_note.status.eql? "sleep"
59
+ sleep 0.001
60
+ #puts @prepared_note.status
61
+ #Thread.pass
62
+ end
63
+ #Thread.pass
64
+ #puts "waking up"
65
+ @prepared_note.wakeup
66
+ end
67
+
20
68
  def +(interval)
21
- Note.new(PITCHES.key((PITCHES[@name]+interval) % 12))
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)
22
73
  end
23
74
 
24
75
  def -(interval)
25
- puts self
26
- puts PITCHES.key((PITCHES[@name]-interval) % 12)
27
76
  Note.new(PITCHES.key((PITCHES[@name]-interval) % 12))
28
77
  end
29
78
 
79
+ def <=>(other_note)
80
+ if same_octave
81
+ self.pitch <=> other_note.pitch
82
+ else
83
+ self.octave <=> other_note.octave
84
+ end
85
+ end
86
+
87
+ def length
88
+ duration
89
+ end
90
+
91
+ def size
92
+ duration
93
+ end
94
+
95
+ def initial_play_time=(time)
96
+ @initial_play_time = time
97
+ end
98
+
99
+ def initial_play_time
100
+ @initial_play_time
101
+ end
102
+
103
+ def duration_in_milliseconds(tempo)
104
+ #puts tempo
105
+ @duration.duration_in_milliseconds(tempo)
106
+ end
107
+
108
+ def self.default_octave
109
+ @@default_octave
110
+ end
111
+
112
+ def plays_during(beat)
113
+ @occupying_beat = beat
114
+ end
115
+
116
+ def plays_during?(beat)
117
+ beat == @occupying_beat
118
+ end
119
+
30
120
  private
31
121
 
32
122
  def parse_note_name(name)
33
123
  # parses note name input
34
124
  # user should be able to say "A#" or "a#" or "a sharp" or "A_sharp" or "a_s"
35
125
  groups = name.to_s.match(/([a-gA-G])( |_)?(.*)/)
36
- note_name = groups[1].upcase
37
- unless groups[3].nil? || groups[3].empty?
38
- note_name += case groups[3]
39
- when /^(s|#)/
40
- "_sharp"
41
- when /^(f|b)/
42
- "_flat"
43
- else
44
- puts "Unknown note modifier: '#{groups[3]}'"
126
+ puts "name: #{name}" if groups.nil?
127
+ if name.to_s.match "rest"
128
+ note_name = "_"
129
+ else
130
+ note_name = groups[1].upcase
131
+ unless groups[3].nil? || groups[3].empty?
132
+ note_name += case groups[3]
133
+ when /^(s|#)/
134
+ "_sharp"
135
+ when /^(f|b)/
136
+ "_flat"
137
+ else
138
+ puts "Unknown note modifier: '#{groups[3]}'"
139
+ end
45
140
  end
46
141
  end
47
142
  note_name.to_sym
48
143
  end
49
144
 
145
+ def same_octave
146
+ (self.octave <=> other_note.octave) == 0
147
+ end
148
+
50
149
  end
51
150
 
52
151
  end
@@ -1,6 +1,7 @@
1
1
  module Juicy
2
2
 
3
3
  PITCHES = {
4
+ _: -1,
4
5
  A: 0,
5
6
  A_sharp: 1,
6
7
  B_flat: 1,
@@ -26,6 +27,7 @@ module Juicy
26
27
 
27
28
  class Pitch
28
29
 
30
+ include Comparable
29
31
  @@temperament = :equal
30
32
  @@pitch_standard = 440.0
31
33
 
@@ -44,6 +46,10 @@ module Juicy
44
46
  end
45
47
 
46
48
  end
49
+
50
+ def to_s
51
+ "#{@frequency}"
52
+ end
47
53
 
48
54
  def tune
49
55
  if out_of_tune
@@ -66,8 +72,23 @@ module Juicy
66
72
  Win32::Sound.play_freq(options[:note].pitch.frequency, options[:note].duration)
67
73
  end
68
74
 
69
- def play(options = {duration: 200})
70
- Win32::Sound.play_freq(@frequency, options[:duration])
75
+ def play(options = {duration: 200, octave: 0, volume: 1})
76
+ options[:duration] ||= 200
77
+ options[:octave] ||= 0
78
+ options[:volume] ||= 1
79
+ Win32::Sound.play_freq(@frequency*2**(options[:octave]), options[:duration], options[:volume])
80
+ end
81
+
82
+ def prepare(options = {duration: 200, octave: 0, volume: 1})
83
+ options[:duration] ||= 200
84
+ options[:octave] ||= 0
85
+ options[:volume] ||= 1
86
+
87
+ return Thread.new{Win32::Sound.play_freq(@frequency*2**(options[:octave]), options[:duration], options[:volume], true)}
88
+ end
89
+
90
+ def <=>(other_pitch)
91
+ self.frequency <=> other_pitch.frequency
71
92
  end
72
93
 
73
94
  private
@@ -1,14 +1,88 @@
1
1
 
2
2
  module Juicy
3
3
 
4
- CHROMATIC = [1,1,1,1,1,1,1,1,1,1,1,1]
5
- WHOLE_NOTE = [2,2,2,2,2,2]
6
- OCTOTONIC = [2,1,2,1,2,1,2,1]
7
- PENTATONIC = [2,2,3,2,3]
8
- DIATONIC = [2,2,1,2,2,2,1]
4
+ SCALE_TYPES = {
5
+ chromatic: [1,1,1,1,1,1,1,1,1,1,1,1],
6
+ whole_note: [2,2,2,2,2,2],
7
+ octotonic: [2,1,2,1,2,1,2,1],
8
+ pentatonic: [2,2,3,2,3],
9
+ diatonic: [2,2,1,2,2,2,1]
10
+ }
11
+ # 0 1 2 3 4 5 6 7 8 9 10 11 12
12
+ # |- |- |- |- |- |- |- |- |- |- |- |- |
13
+ # do di re ri mi fa fi so si la li ti do
14
+ # chromatic |- |- |- |- |- |- |- |- |- |- |- |- |
15
+ # do re mi fa so la ti do
16
+ # diatonic |- - |- - |- |- - |- - |- - |- |
17
+ # do re mi so la do
18
+ # pentatonic |- - |- - |- - - |- - |- - - |
19
+ # do re mi fi si li do
20
+ # whole note |- - |- - |- - |- - |- - |- - |
21
+ # do re ri fa fi si la ti do
22
+ # octotonic |- - |- |- - |- |- - |- |- - |- |
23
+ #
9
24
 
10
- class Scale
11
-
25
+ class Scale
26
+ include Enumerable
27
+
28
+ def initialize(type = :major, root = Note.new)
29
+ case type
30
+ when :major
31
+ @type = :diatonic
32
+ when :minor
33
+ @type = :diatonic
34
+ else
35
+ @type = type
36
+ end
37
+ @mode = Mode.new(type)
38
+ @root = root
39
+ generate_notes
40
+
41
+ end
42
+
43
+ def to_s
44
+ "scale type: #{@type}, mode: #{@mode}, root: #{@root}"
45
+ end
46
+
47
+ def[](element)
48
+
49
+ end
50
+
51
+ def play
52
+
53
+ end
54
+
55
+ def mode=(type)
56
+ @mode = Mode.new(type)
57
+ generate_notes
58
+ end
59
+
60
+ def root=(root)
61
+ @root = root
62
+ generate_notes
63
+ end
64
+
65
+ def each
66
+ yield SCALE_TYPES[@type]
67
+ end
68
+
69
+ def each_note
70
+ (SCALE_TYPES[@type].size+1).times do
71
+ yield @notes.next
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def generate_notes
78
+ @notes = []
79
+ @notes << @root
80
+ SCALE_TYPES[@type].rotate(@mode.rotate).each do |step|
81
+ @notes << @notes[-1]+step
82
+ end
83
+ @notes = @notes.cycle
84
+ end
85
+
12
86
  end
13
87
 
14
88
  end
@@ -3,6 +3,8 @@ module Juicy
3
3
 
4
4
  class Song
5
5
 
6
+ attr_reader :measures, :tempo
7
+
6
8
  def initialize
7
9
 
8
10
  #a song has a key, a mode, voices, tempo, time signature,
@@ -10,23 +12,45 @@ module Juicy
10
12
  @voices << Voice.new
11
13
  @key = :A
12
14
  @mode = :major
13
- @tempo = 90.0
15
+ @tempo = 150.0
14
16
  @time_signature = [4,4]
17
+ @measures = []
18
+ 4.times {@measures << Measure.new(@time_signature)}
15
19
 
20
+ end
21
+
22
+ def play
23
+
24
+ # have musical construct yield notes up to a note manager/beat sequencer for each track
25
+ # chords will eventually have a play style (various types of arpeggiation and such), but
26
+ # for now they'll all just play all their notes at once for the given duration
16
27
 
17
- # play Do Mi So Do
18
- notes = [0, 4, 7, 12]
19
- @voices[0].notes = [
20
- Note.new(Pitch.new(440*2.0**(notes[0]/12)), beat_length_in_milliseconds),
21
- Note.new(Pitch.new(440*2.0**(notes[1]/12)), beat_length_in_milliseconds),
22
- Note.new(Pitch.new(440*2.0**(notes[2]/12)), beat_length_in_milliseconds),
23
- Note.new(Pitch.new(440*2.0**(notes[3]/12)), beat_length_in_milliseconds)
24
- ]
28
+ tracks = []
29
+
30
+ chord_progression = ChordProgression.new
25
31
 
32
+ #tracks << Track.new(chord_progression.initial_play_time, chord_progression.to_a, @tempo)
33
+
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)
46
+
47
+ Track.play_concurrently(tracks, @tempo)
48
+
49
+
26
50
  end
27
51
 
28
52
  def beat_length_in_milliseconds
29
- 60*1000/@tempo
53
+ 60_000.0/@tempo
30
54
  end
31
55
 
32
56
  end
@@ -0,0 +1,205 @@
1
+
2
+ module Juicy
3
+
4
+ class Track
5
+
6
+ attr_accessor :notes
7
+
8
+ def initialize(init_time, notes, tempo)
9
+
10
+ @start_time = init_time
11
+ @notes = notes
12
+ @tempo = tempo
13
+
14
+ end
15
+
16
+ def play
17
+
18
+ # prepare notes ahead of time, and play them at a specified time.
19
+ # each note must be prepared separately in it's own thread.
20
+ # only 20 threads are allowed to be alive at a time in a current track, and each track is preparing its notes seperately
21
+ # th = []
22
+ # while(notes_left_to_prepare)
23
+ # if (th.count {|t| t.alive? }) <= 20
24
+ # note = notes_left_to_prepare.shift
25
+ # th << Thread.new do
26
+ # note.prepare
27
+ # sleep_amount = when_the_note_should_be_played - how_far_into_the_song_you_are
28
+ # unless sleep_amount < 0
29
+ # sleep sleep_amount
30
+ # end
31
+ # note.play_prepared
32
+ # end
33
+ # end
34
+ # end
35
+ # th.each {|t| t.join}
36
+
37
+ notes = []
38
+ if @notes[0].kind_of? Chord
39
+ #notes << Thread.new { @notes.each { |chord| 4.times {chord.play} } }
40
+ chords_left_to_prepare = @notes.dup
41
+ @sum_of_queued_chord_durations = 0
42
+ until chords_left_to_prepare.size == 1
43
+ if (notes.count {|t| t.alive?} <= 20)
44
+ notes << Thread.new do
45
+ chord = chords_left_to_prepare.shift
46
+ puts "#{chord}: #{chord.duration}"
47
+ chord.sum_of_queued_chord_durations = @sum_of_queued_chord_durations
48
+ @sum_of_queued_chord_durations += chord.duration_in_milliseconds(@tempo)
49
+ chord.initial_play_time = @start_time + chord.sum_of_queued_chord_durations
50
+ chord.how_far_into_the_song_you_are = how_far_into_the_song_you_are
51
+ chord.prepare(duration: chord.duration_in_milliseconds(@tempo))
52
+ sleep_amount = (chord.initial_play_time - chord.how_far_into_the_song_you_are)/1000.0
53
+ unless sleep_amount < 0
54
+ sleep sleep_amount
55
+ end
56
+ Thread.pass
57
+ chord.play_prepared.join
58
+ #puts "tehe"
59
+
60
+
61
+ end
62
+ end
63
+ Thread.pass
64
+ end
65
+
66
+ elsif @notes[0].kind_of? Note
67
+
68
+ # @notes.each do |note|
69
+ # note.play
70
+ # end
71
+ notes_left_to_prepare = @notes.dup
72
+ @sum_of_queued_note_durations = 0
73
+ until notes_left_to_prepare.size == 1
74
+ #puts notes_left_to_prepare.size
75
+ if (notes.count {|t| t.alive? }) <= 20
76
+ #Thread.pass
77
+ notes << Thread.new do
78
+ note = notes_left_to_prepare.shift
79
+ puts "#{note}: #{note.duration}"
80
+ note.sum_of_queued_note_durations = @sum_of_queued_note_durations
81
+ @sum_of_queued_note_durations += note.duration_in_milliseconds(@tempo)
82
+ #puts @sum_of_queued_note_durations
83
+ note.initial_play_time = @start_time + note.sum_of_queued_note_durations
84
+ #puts "note.initial_play_time: #{note.initial_play_time}"
85
+ note.how_far_into_the_song_you_are = how_far_into_the_song_you_are
86
+ #puts "note.how_far_into_the_song_you_are: #{note.how_far_into_the_song_you_are}"
87
+ note.prepare(duration: note.duration_in_milliseconds(@tempo))
88
+ # puts "note.initial_play_time: #{note.initial_play_time}"
89
+ # puts "note.how_far_into_the_song_you_are: #{note.how_far_into_the_song_you_are}"
90
+ sleep_amount = (note.initial_play_time - note.how_far_into_the_song_you_are)/1000.0
91
+ #puts sleep_amount
92
+ unless sleep_amount < 0
93
+ sleep sleep_amount
94
+ end
95
+ Thread.pass
96
+ note.play_prepared
97
+ end
98
+
99
+ end
100
+ Thread.pass
101
+ end
102
+ end
103
+ notes.each {|t| t.join}
104
+ end
105
+
106
+ def self.tracks_is_empty(tracks)
107
+ empty = true
108
+ tracks.each do |track|
109
+ #puts track.object_id
110
+ puts track.notes.inspect
111
+ unless track.notes.empty?
112
+ empty = false
113
+ end
114
+ end
115
+ empty
116
+ end
117
+
118
+ def self.play_concurrently(tracks, tempo)
119
+ @song_start_time = Time.now
120
+ threads = []
121
+ # iterate over each track over and over again, preparing notes in the current beat
122
+ # in each iteration, store the notes you've prepared into an array and store that
123
+ # array into the prepared_notes array which the playing thread will play notes from
124
+ # when it has enough to play.
125
+ # 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
127
+ #
128
+ prepared_beats = []
129
+ out_of_notes_to_prepare = false
130
+ threads << Thread.new do
131
+ Thread.current[:name] = "prepare beats thread"
132
+ current_beat = 1
133
+ last_beat = 1
134
+ tracks.each do |track|
135
+ track.notes.each do |note|
136
+ last_beat = note.occupying_beat if note.occupying_beat > last_beat
137
+ end
138
+ end
139
+ until current_beat > last_beat
140
+ if prepared_beats.size <= 20
141
+ this_beats_notes = []
142
+ tracks.each do |track|
143
+ while !track.notes[0].nil? && track.notes[0].plays_during?(current_beat)
144
+ playable_thing = track.notes.shift
145
+ if playable_thing.kind_of?(Note)
146
+ this_beats_notes << playable_thing.prepare(duration: playable_thing.duration_in_milliseconds(tempo))
147
+ elsif playable_thing.kind_of?(Chord)
148
+ playable_thing.notes.each do |note|
149
+ this_beats_notes << note.prepare(duration: note.duration_in_milliseconds(tempo))
150
+ end
151
+ end
152
+ end
153
+ end
154
+ prepared_beats << this_beats_notes
155
+ current_beat += 1
156
+ end
157
+ Thread.pass
158
+ end
159
+ out_of_notes_to_prepare = true
160
+ end
161
+
162
+ threads << Thread.new do
163
+ Thread.current[:name] = "play prepared beats thread"
164
+ # when prepared_beats has at least a few beats to play (a measure's worth?)
165
+ # "start" the song by iterating through each element, waking up each note
166
+ # and then waiting the remainder of a beat's worth of milliseconds until the
167
+ # next beat
168
+ until (prepared_beats.size >= 2) || out_of_notes_to_prepare
169
+ sleep 0.01
170
+ end
171
+ last_note = Thread.new {}
172
+ time = Time.now
173
+ until prepared_beats.empty? && out_of_notes_to_prepare
174
+ time = Time.now
175
+ beat = prepared_beats.shift
176
+ beat.each do |note|
177
+ last_note = note.play_prepared
178
+ end
179
+ sleep rand(100..300)/1000.0
180
+ sleep_amount = Duration.duration_of_quarter_note_in_milliseconds(tempo)/1000.0 - (Time.now - time)
181
+ puts sleep_amount
182
+ sleep sleep_amount unless sleep_amount < 0
183
+ end
184
+ last_note.join
185
+ end
186
+
187
+ #threads.each {|t| puts t[:name] }
188
+ threads.each {|t| t.join}
189
+ end
190
+
191
+ private
192
+
193
+ def total_duration_of_queued_notes_for_this_track
194
+ @sum_of_queued_note_durations
195
+ end
196
+
197
+ def how_far_into_the_song_you_are
198
+ a = (1000*(Time.now - @song_start_time)).round
199
+ #puts "how_far_into_the_song_you_are: #{a}"
200
+ a
201
+ end
202
+
203
+ end
204
+
205
+ end
@@ -0,0 +1,131 @@
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
@@ -0,0 +1,67 @@
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
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: juicy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.1.0
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-03-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2014-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: win32-sound
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.6.0
13
27
  description: Generates songs
14
28
  email: nicklink483@gmail.com
15
29
  executables: []
@@ -20,14 +34,20 @@ files:
20
34
  - lib/juicy.rb
21
35
  - lib/juicy/chord.rb
22
36
  - lib/juicy/chord_progression.rb
37
+ - lib/juicy/duration.rb
23
38
  - lib/juicy/key.rb
39
+ - lib/juicy/measure.rb
40
+ - lib/juicy/melody.rb
24
41
  - lib/juicy/mode.rb
25
42
  - lib/juicy/note.rb
26
43
  - lib/juicy/pitch.rb
27
44
  - lib/juicy/scale.rb
28
45
  - lib/juicy/scale_degree.rb
29
46
  - lib/juicy/song.rb
47
+ - lib/juicy/track.rb
30
48
  - lib/juicy/voice.rb
49
+ - lib/win32/wave_out_play_freq.rb
50
+ - lib/win32/win32-mmlib_structs.rb
31
51
  homepage: https://github.com/nicklink483/juicy
32
52
  licenses:
33
53
  - MIT