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