juicy 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/bin/juicy.rb +27 -0
- data/lib/juicy.rb +10 -0
- data/lib/juicy/chord.rb +108 -0
- data/lib/juicy/chord_progression.rb +49 -0
- data/lib/juicy/key.rb +8 -0
- data/lib/juicy/mode.rb +10 -0
- data/lib/juicy/note.rb +52 -0
- data/lib/juicy/pitch.rb +87 -0
- data/lib/juicy/scale.rb +14 -0
- data/lib/juicy/scale_degree.rb +22 -0
- data/lib/juicy/song.rb +34 -0
- data/lib/juicy/voice.rb +8 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjQ4MDJlZTZjMTFkOWI5NzJkNTFiN2Y3OWY3YjYwMzdlYmI3NGNkNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTFkMmQ1ZWEwNzM3MTEwMjVlY2EyY2JkYWY4M2M5MjE4OWM2YzAwNA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MzQ3MmZiOTEyOWQ4M2IzZTUyNTIzMDI3NjUzMjQxYjU5ZTcyNDI4M2ExYjFl
|
10
|
+
N2UxN2FkYjgzNTg4MGJlM2FkOWVmNGIyMThkMzkyYWQ4YjQ1YWFmMjFhZmNm
|
11
|
+
Mzk2YjgyMWExMmM0ZTY0NDM4NTgxZTFiMTQxMGY4OTNiMzY2YzU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGE2ZDNkMDNhMGNmZjYyZjY3OTYzZmJkMzk1ZGQzNGRmM2NhMmE5M2Y0OTkw
|
14
|
+
ZjIyMDQ2NmY5ZjdiMzU0YmE2OGEzNjNkMjEyMjQ3NmJlOTcwYWMwOGYyZTJi
|
15
|
+
YjIxOTM3OGU2OTliYzdlOTRkNWZmNGM1MWJjZTc1MGJhYzdiMGE=
|
data/bin/juicy.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../lib/juicy.rb'
|
2
|
+
include Juicy
|
3
|
+
|
4
|
+
p = Pitch.new(445)
|
5
|
+
puts p
|
6
|
+
p.play
|
7
|
+
|
8
|
+
note = Note.new("A")
|
9
|
+
#12.times {(n+=1).play}
|
10
|
+
|
11
|
+
ch = Chord.new(root: note)
|
12
|
+
ch.play
|
13
|
+
|
14
|
+
ch2 = Chord.new(root: note)
|
15
|
+
ch2.play
|
16
|
+
ch2.play(duration: 500)
|
17
|
+
|
18
|
+
# while there is enough here to write out your own scales,
|
19
|
+
# current development work is being done on scale objects
|
20
|
+
# and a beat sequencer to bring it all together.
|
21
|
+
|
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
|
data/lib/juicy.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative '../../win32-sound/lib/win32/sound.rb'
|
2
|
+
|
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
|
data/lib/juicy/chord.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
module Juicy
|
2
|
+
|
3
|
+
class Chord
|
4
|
+
|
5
|
+
QUALITIES = {
|
6
|
+
:major => [0, 4, 7],
|
7
|
+
:minor => [0, 3, 7],
|
8
|
+
:diminished => [0, 3, 6],
|
9
|
+
:augmented => [0, 4, 8],
|
10
|
+
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(options = {root: Note.new(:C), quality: :major, inversion: 0, context: :none})
|
14
|
+
@root = (options[:root].kind_of?(Note) ? options[:root] : Note.new(options[:root])) || Note.new(:C)
|
15
|
+
@quality = options[:quality] || :major
|
16
|
+
@inversion = options[:inversion] || 0
|
17
|
+
@context = options[:context] || :none
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"#{@root.name} #{@quality} Chord, inversion: #{@inversion}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def play(options = {duration: 200, style: :default})
|
25
|
+
|
26
|
+
#Quick and dirty play function. Not intended for genuine use
|
27
|
+
|
28
|
+
duration = options[:duration] || 200
|
29
|
+
style = options[:style] || :default
|
30
|
+
notes = []
|
31
|
+
pitches = QUALITIES[@quality]
|
32
|
+
inversion = @inversion
|
33
|
+
|
34
|
+
while inversion > 0
|
35
|
+
pitches = pitches[1..-1] + [(pitches[0]+12)]
|
36
|
+
inversion -= 1
|
37
|
+
end
|
38
|
+
pitches.each do |interval|
|
39
|
+
notes << Note.new(PITCHES.key((PITCHES[@root.name]+interval) % 12))
|
40
|
+
end
|
41
|
+
|
42
|
+
case style
|
43
|
+
when :arpeggiate
|
44
|
+
notes.each do |note|
|
45
|
+
Thread.new{note.play(duration: duration)}.join
|
46
|
+
end
|
47
|
+
when :default
|
48
|
+
threads = []
|
49
|
+
notes.each do |note|
|
50
|
+
threads << Thread.new{note.play(duration: duration)}
|
51
|
+
end
|
52
|
+
threads.each {|t| t.join}
|
53
|
+
else
|
54
|
+
puts "Unknown style type"
|
55
|
+
end
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def +(interval)
|
61
|
+
#in the context of a scale, change the base pitch to the new scale
|
62
|
+
# degree and the quality to match
|
63
|
+
|
64
|
+
#in a context of :none, change the base pitch
|
65
|
+
# up a half step times the interval
|
66
|
+
if @context.eql? :none
|
67
|
+
options = {
|
68
|
+
root: (Pitch.new(@root) + interval).frequency,
|
69
|
+
quality: @quality,
|
70
|
+
inversion: @inversion,
|
71
|
+
context: @context
|
72
|
+
}
|
73
|
+
Chord.new(options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def -(interval)
|
78
|
+
|
79
|
+
if @context.eql? :none
|
80
|
+
options = {
|
81
|
+
root: (Pitch.new(@root) - interval).frequency,
|
82
|
+
quality: @quality,
|
83
|
+
inversion: @inversion,
|
84
|
+
context: @context
|
85
|
+
}
|
86
|
+
Chord.new(options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def cycle(direction = :up, amount = 1)
|
91
|
+
#inverts the chord
|
92
|
+
# ex. 1, 3, 5 -> cycle(:up, 2) -> 5, 1, 3
|
93
|
+
case direction
|
94
|
+
when :up
|
95
|
+
@inversion = @inversion + amount
|
96
|
+
when :down
|
97
|
+
@inversion = @inversion - amount
|
98
|
+
else
|
99
|
+
puts "Unknown Direction"
|
100
|
+
end
|
101
|
+
puts self.inspect
|
102
|
+
end
|
103
|
+
|
104
|
+
alias_method :invert, :cycle
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Juicy
|
2
|
+
|
3
|
+
class ChordProgression
|
4
|
+
|
5
|
+
attr_accessor :chords
|
6
|
+
|
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
|
+
@chords = []
|
14
|
+
@chords << Chord.new(root: :C)
|
15
|
+
@chords << Chord.new(root: :F, inversion: 2)-12
|
16
|
+
@chords << Chord.new(root: :G, inversion: 1)-12
|
17
|
+
@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
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
output = ""
|
27
|
+
@chords.each do |chord|
|
28
|
+
output += chord.inspect + ", "
|
29
|
+
end
|
30
|
+
output[0..-3]
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
output = ""
|
35
|
+
@progression.each do |scale_degree|
|
36
|
+
output += scale_degree.to_s + ", "
|
37
|
+
end
|
38
|
+
output[0..-3]
|
39
|
+
end
|
40
|
+
|
41
|
+
def play
|
42
|
+
@chords.each do |chord|
|
43
|
+
4.times {chord.play}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/lib/juicy/key.rb
ADDED
data/lib/juicy/mode.rb
ADDED
data/lib/juicy/note.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Juicy
|
2
|
+
|
3
|
+
class Note
|
4
|
+
|
5
|
+
attr_reader :name, :pitch
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = parse_note_name(name)
|
9
|
+
@pitch = Pitch.new(@name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"#{@name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def play(options = {duration: 200})
|
17
|
+
@pitch.play(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(interval)
|
21
|
+
Note.new(PITCHES.key((PITCHES[@name]+interval) % 12))
|
22
|
+
end
|
23
|
+
|
24
|
+
def -(interval)
|
25
|
+
puts self
|
26
|
+
puts PITCHES.key((PITCHES[@name]-interval) % 12)
|
27
|
+
Note.new(PITCHES.key((PITCHES[@name]-interval) % 12))
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def parse_note_name(name)
|
33
|
+
# parses note name input
|
34
|
+
# user should be able to say "A#" or "a#" or "a sharp" or "A_sharp" or "a_s"
|
35
|
+
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]}'"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
note_name.to_sym
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/juicy/pitch.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module Juicy
|
2
|
+
|
3
|
+
PITCHES = {
|
4
|
+
A: 0,
|
5
|
+
A_sharp: 1,
|
6
|
+
B_flat: 1,
|
7
|
+
B: 2,
|
8
|
+
B_sharp: 3,
|
9
|
+
C_flat: 2,
|
10
|
+
C: 3,
|
11
|
+
C_sharp: 4,
|
12
|
+
D_flat: 4,
|
13
|
+
D: 5,
|
14
|
+
D_sharp: 6,
|
15
|
+
E_flat: 6,
|
16
|
+
E: 7,
|
17
|
+
E_sharp: 8,
|
18
|
+
F_flat: 7,
|
19
|
+
F: 8,
|
20
|
+
F_sharp: 9,
|
21
|
+
G_flat: 9,
|
22
|
+
G: 10,
|
23
|
+
G_sharp: 11,
|
24
|
+
A_flat: 11
|
25
|
+
}
|
26
|
+
|
27
|
+
class Pitch
|
28
|
+
|
29
|
+
@@temperament = :equal
|
30
|
+
@@pitch_standard = 440.0
|
31
|
+
|
32
|
+
attr_reader :frequency, :confidence
|
33
|
+
|
34
|
+
def initialize(pitch = @@pitch_standard, tune_now = true)
|
35
|
+
|
36
|
+
if pitch.kind_of? Numeric
|
37
|
+
@frequency = pitch
|
38
|
+
@tuned = false
|
39
|
+
tune if tune_now
|
40
|
+
else
|
41
|
+
step = PITCHES[pitch.to_sym]
|
42
|
+
@frequency = @@pitch_standard*2**(step/12.0)
|
43
|
+
@tuned = true
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def tune
|
49
|
+
if out_of_tune
|
50
|
+
step = Math.log(@frequency/440.0,2)*12
|
51
|
+
@confidence = (1.0-2*(step - step.round).abs)*100.0
|
52
|
+
@frequency = @@pitch_standard*2**((step.round)/12.0)
|
53
|
+
@tuned = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def +(interval)
|
58
|
+
change_by (interval)
|
59
|
+
end
|
60
|
+
|
61
|
+
def -(interval)
|
62
|
+
change_by (-interval)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.play(options = {duration: 200})
|
66
|
+
Win32::Sound.play_freq(options[:note].pitch.frequency, options[:note].duration)
|
67
|
+
end
|
68
|
+
|
69
|
+
def play(options = {duration: 200})
|
70
|
+
Win32::Sound.play_freq(@frequency, options[:duration])
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def out_of_tune
|
76
|
+
!@tuned
|
77
|
+
end
|
78
|
+
|
79
|
+
def change_by (interval)
|
80
|
+
if @@temperament.eql? :equal
|
81
|
+
Pitch.new(@frequency*2**(interval/12.0))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/lib/juicy/scale.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Juicy
|
3
|
+
|
4
|
+
# some index of a given scale. if scale is [2,4,5,7,9,11,12], then a scale degree of 3 is 4.
|
5
|
+
# scale degree of 1 is 0 by definition. That is, the root of the scale is defined to be
|
6
|
+
# a scale degree of 1.
|
7
|
+
|
8
|
+
class ScaleDegree
|
9
|
+
|
10
|
+
attr_reader :degree
|
11
|
+
|
12
|
+
def initialize(degree)
|
13
|
+
@degree = degree
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
@degree
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/juicy/song.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module Juicy
|
3
|
+
|
4
|
+
class Song
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
|
8
|
+
#a song has a key, a mode, voices, tempo, time signature,
|
9
|
+
@voices = []
|
10
|
+
@voices << Voice.new
|
11
|
+
@key = :A
|
12
|
+
@mode = :major
|
13
|
+
@tempo = 90.0
|
14
|
+
@time_signature = [4,4]
|
15
|
+
|
16
|
+
|
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
|
+
]
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def beat_length_in_milliseconds
|
29
|
+
60*1000/@tempo
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/juicy/voice.rb
ADDED
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: juicy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dominic Muller
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Generates songs
|
14
|
+
email: nicklink483@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- bin/juicy.rb
|
20
|
+
- lib/juicy.rb
|
21
|
+
- lib/juicy/chord.rb
|
22
|
+
- lib/juicy/chord_progression.rb
|
23
|
+
- lib/juicy/key.rb
|
24
|
+
- lib/juicy/mode.rb
|
25
|
+
- lib/juicy/note.rb
|
26
|
+
- lib/juicy/pitch.rb
|
27
|
+
- lib/juicy/scale.rb
|
28
|
+
- lib/juicy/scale_degree.rb
|
29
|
+
- lib/juicy/song.rb
|
30
|
+
- lib/juicy/voice.rb
|
31
|
+
homepage: https://github.com/nicklink483/juicy
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.2.2
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: Song writing tool
|
55
|
+
test_files: []
|