juicy 0.0.8 → 0.1.0

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