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 +8 -8
- data/bin/juicy.rb +54 -16
- data/lib/juicy.rb +19 -9
- data/lib/juicy/chord.rb +47 -1
- data/lib/juicy/chord_progression.rb +11 -12
- data/lib/juicy/duration.rb +62 -0
- data/lib/juicy/measure.rb +13 -0
- data/lib/juicy/melody.rb +53 -0
- data/lib/juicy/mode.rb +18 -0
- data/lib/juicy/note.rb +114 -15
- data/lib/juicy/pitch.rb +23 -2
- data/lib/juicy/scale.rb +81 -7
- data/lib/juicy/song.rb +34 -10
- data/lib/juicy/track.rb +205 -0
- data/lib/win32/wave_out_play_freq.rb +131 -0
- data/lib/win32/win32-mmlib_structs.rb +67 -0
- metadata +23 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZmVjZDlhNGNmZDc2MmM0NjQ3NjAxOWE4ZWU0NTAzNjYzMDBhYjE0MA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZDc4ODNhNzEzZTZiODBlYjRhZjQ3MjU4Y2ZkOGVjNmMyNzVjN2RmMA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OTgwNjBkOTlhZDhiOTYyOTgxNTI5ZjNjMjMyNTdkMDQyY2RhMmNjMTJhOGMz
|
10
|
+
YjE3NmE3ZDQwMzA2YTNhZTFkZTM4YWIzOWEyYjU0NDI0MzM5MWVhNGVjMTg0
|
11
|
+
ZmQ5NDk1YzM5OTU1NjU4NzRmYjZmNzkxNDQ5OWJkNjI2ZDYxMjQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTgyMWY4OTg2M2ZmMzBmMzRkNWQzZmRhZmU1MjMxNzg1MjBlN2MyM2NiZmFi
|
14
|
+
YzBhYjZlNTBlYzMyYzFiMTc3NDhhMjk2NmYyNWY1OGE2ZWRlYmEwMWE2NTNj
|
15
|
+
NWRlOTFkMWUzYmY2MmE3ZGE4NzVhMmEwNGU1YjExZDdlZmM2NWU=
|
data/bin/juicy.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
#
|
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
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
data/lib/juicy.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
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
5
|
|
3
|
-
require_relative 'juicy/pitch
|
4
|
-
require_relative 'juicy/note
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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' #
|
data/lib/juicy/chord.rb
CHANGED
@@ -10,11 +10,21 @@ module Juicy
|
|
10
10
|
|
11
11
|
}
|
12
12
|
|
13
|
-
|
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)
|
16
|
-
@chords << Chord.new(root: :G, inversion: 1)
|
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
|
data/lib/juicy/melody.rb
ADDED
@@ -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
|
data/lib/juicy/mode.rb
CHANGED
@@ -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
|
data/lib/juicy/note.rb
CHANGED
@@ -2,51 +2,150 @@ module Juicy
|
|
2
2
|
|
3
3
|
class Note
|
4
4
|
|
5
|
-
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
note_name
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
data/lib/juicy/pitch.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/juicy/scale.rb
CHANGED
@@ -1,14 +1,88 @@
|
|
1
1
|
|
2
2
|
module Juicy
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
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
|
data/lib/juicy/song.rb
CHANGED
@@ -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 =
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
53
|
+
60_000.0/@tempo
|
30
54
|
end
|
31
55
|
|
32
56
|
end
|
data/lib/juicy/track.rb
ADDED
@@ -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
|
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-
|
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
|