juicy 0.0.8
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 +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: []
|