mdaines-soxophone 0.2
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.
- data/COPYING +25 -0
- data/README +167 -0
- data/Rakefile +3 -0
- data/examples/boop.rb +11 -0
- data/examples/chords.rb +7 -0
- data/examples/crash.mp3 +0 -0
- data/examples/effects.rb +25 -0
- data/examples/envelope.rb +26 -0
- data/examples/notation.rb +23 -0
- data/examples/samples.rb +23 -0
- data/examples/triangle.mp3 +0 -0
- data/lib/soxophone.rb +22 -0
- data/lib/soxophone/base.rb +39 -0
- data/lib/soxophone/effects.rb +41 -0
- data/lib/soxophone/notation.rb +504 -0
- data/lib/soxophone/notation.rl +152 -0
- data/lib/soxophone/sample.rb +47 -0
- data/lib/soxophone/sound.rb +65 -0
- data/lib/soxophone/tune.rb +44 -0
- data/soxo.rb +40 -0
- metadata +73 -0
data/COPYING
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
"Soxophone" is (c) 2009 Michael Daines, but the concept
|
2
|
+
and notation scanner is based on "Bloopsaphone", which is
|
3
|
+
copyright (c) 2009 why the lucky stiff and is also released
|
4
|
+
under the MIT license.
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
7
|
+
obtaining a copy of this software and associated documentation
|
8
|
+
files (the "Software"), to deal in the Software without restriction,
|
9
|
+
including without limitation the rights to use, copy, modify, merge,
|
10
|
+
publish, distribute, sublicense, and/or sell copies of the Software,
|
11
|
+
and to permit persons to whom the Software is furnished to do so,
|
12
|
+
subject to the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
18
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
19
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
20
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
21
|
+
SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
22
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
23
|
+
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
25
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
!!!SOXOPHONE!!!
|
2
|
+
=========
|
3
|
+
|
4
|
+
This is a small synthesis and
|
5
|
+
sequencing library that runs on
|
6
|
+
top of SoX. You write little
|
7
|
+
Ruby programs and it arranges
|
8
|
+
a bunch of command-line
|
9
|
+
stuff to make your songs.
|
10
|
+
|
11
|
+
The notation and general idea
|
12
|
+
were borrowed from _why's
|
13
|
+
Bloopsaphone.
|
14
|
+
|
15
|
+
|
16
|
+
A quick demo
|
17
|
+
------------
|
18
|
+
|
19
|
+
1. You need to install SoX:
|
20
|
+
|
21
|
+
http://sox.sourceforge.net/
|
22
|
+
|
23
|
+
You can probably use the version
|
24
|
+
provided by your package manager
|
25
|
+
(I use MacPorts).
|
26
|
+
|
27
|
+
2. Run the example:
|
28
|
+
|
29
|
+
ruby soxo.rb
|
30
|
+
|
31
|
+
You'll get an MP3 you can play
|
32
|
+
in your music player.
|
33
|
+
|
34
|
+
open soxo.mp3
|
35
|
+
|
36
|
+
|
37
|
+
The basics of Soxophone
|
38
|
+
-----------------------
|
39
|
+
|
40
|
+
(Follow along with the "boop.rb"
|
41
|
+
example.)
|
42
|
+
|
43
|
+
|
44
|
+
1. Before anything, you need a
|
45
|
+
Soxophone block, like this:
|
46
|
+
|
47
|
+
Soxophone.play do |x|
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
This means that Soxophone will
|
52
|
+
ask SoX to play your song with
|
53
|
+
the "play" command. You can
|
54
|
+
also write your song to a file,
|
55
|
+
like this:
|
56
|
+
|
57
|
+
Soxophone.write("tune.mp3") do |x|
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
2. Then, you define sounds.
|
63
|
+
|
64
|
+
To define a sound, ask the object
|
65
|
+
the block yields for a sound:
|
66
|
+
|
67
|
+
Soxophone.play do |x|
|
68
|
+
boop = x.sound :sine
|
69
|
+
end
|
70
|
+
|
71
|
+
All the SoX synth sounds, like
|
72
|
+
sine waves, square waves, and so
|
73
|
+
on are supported.
|
74
|
+
|
75
|
+
You can set attributes on your
|
76
|
+
sounds to change them. For example,
|
77
|
+
to add a simple envelope:
|
78
|
+
|
79
|
+
boop = x.sound :sine
|
80
|
+
boop.release = 0.75
|
81
|
+
|
82
|
+
This means the "boop" sound will
|
83
|
+
start fading out three quarters
|
84
|
+
of the way from the end.
|
85
|
+
|
86
|
+
You can add a few other effects,
|
87
|
+
like a phaser effect and a
|
88
|
+
tremolo effect. See "effects.rb"
|
89
|
+
for an example.
|
90
|
+
|
91
|
+
Soxophone also allows you to use
|
92
|
+
sampled sound files. To do this,
|
93
|
+
you ask for a sample:
|
94
|
+
|
95
|
+
tri = x.sample "triangle.mp3"
|
96
|
+
|
97
|
+
Samples can have effects too, and
|
98
|
+
you can play chords with them
|
99
|
+
(see below).
|
100
|
+
|
101
|
+
Samples are pitch-shifted relative
|
102
|
+
to middle A. If you're including
|
103
|
+
a drum sample, you want to just
|
104
|
+
write a middle A (or A4, or
|
105
|
+
just "a") so the pitch won't
|
106
|
+
be shifted.
|
107
|
+
|
108
|
+
|
109
|
+
3. Next, you define tunes.
|
110
|
+
|
111
|
+
Just ask to use one of your sounds
|
112
|
+
in a tune:
|
113
|
+
|
114
|
+
x.tune boop, "a b c"
|
115
|
+
|
116
|
+
Here, we're asking Soxophone to
|
117
|
+
play three quarter notes with the
|
118
|
+
"boop" sound.
|
119
|
+
|
120
|
+
You can write as many tunes as you
|
121
|
+
want and Soxophone will mix
|
122
|
+
them all together.
|
123
|
+
|
124
|
+
Notes look like this:
|
125
|
+
|
126
|
+
a (quarter note at middle A)
|
127
|
+
a5 (quarter note one octave higher than middle A)
|
128
|
+
2:c (half note at middle C)
|
129
|
+
1e3 (whole note at E but one octave lower)
|
130
|
+
c# (quarter note at C sharp)
|
131
|
+
eb (quarter note at E flat)
|
132
|
+
eb3 (quarter note at E flat but one octave lower)
|
133
|
+
|
134
|
+
So, a note is written like this:
|
135
|
+
|
136
|
+
- The duration (as the lower half
|
137
|
+
of a fraction of a bar)
|
138
|
+
- An optional ":"
|
139
|
+
- The note name
|
140
|
+
- An optional sharp or flat
|
141
|
+
- The octave
|
142
|
+
|
143
|
+
You can write a plus or minus to
|
144
|
+
move notes up or down:
|
145
|
+
|
146
|
+
- a + a + a (A3, A4, and A5)
|
147
|
+
|
148
|
+
You can also write chords. Just
|
149
|
+
surround notes with parentheses:
|
150
|
+
|
151
|
+
a c e (a c e)
|
152
|
+
|
153
|
+
4. Then, run your composition through
|
154
|
+
Ruby:
|
155
|
+
|
156
|
+
ruby boop.rb
|
157
|
+
|
158
|
+
If you asked Soxophone to play your
|
159
|
+
composition, it'll play through your
|
160
|
+
speakers. If you asked to write
|
161
|
+
a file, the file will appear.
|
162
|
+
|
163
|
+
Make sure you try the examples!
|
164
|
+
|
165
|
+
|
166
|
+
------
|
167
|
+
|
data/Rakefile
ADDED
data/examples/boop.rb
ADDED
data/examples/chords.rb
ADDED
data/examples/crash.mp3
ADDED
Binary file
|
data/examples/effects.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
2
|
+
require "soxophone"
|
3
|
+
|
4
|
+
Soxophone.play do |x|
|
5
|
+
s = x.sound(:square)
|
6
|
+
s.release = 0.5
|
7
|
+
|
8
|
+
x.tune s, "a b c"
|
9
|
+
end
|
10
|
+
|
11
|
+
Soxophone.play do |x|
|
12
|
+
s = x.sound(:square)
|
13
|
+
s.release = 0.5
|
14
|
+
s.tremolo = 10
|
15
|
+
|
16
|
+
x.tune s, "a b c"
|
17
|
+
end
|
18
|
+
|
19
|
+
Soxophone.play do |x|
|
20
|
+
s = x.sound(:square)
|
21
|
+
s.release = 0.5
|
22
|
+
s.phaser = true
|
23
|
+
|
24
|
+
x.tune s, "a b c"
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
2
|
+
require "soxophone"
|
3
|
+
|
4
|
+
Soxophone.play do |x|
|
5
|
+
s = x.sound :sine
|
6
|
+
s.attack = 0.5
|
7
|
+
s.release = 0.5
|
8
|
+
|
9
|
+
x.tune s, "1a 1a 1a 8"
|
10
|
+
end
|
11
|
+
|
12
|
+
Soxophone.play do |x|
|
13
|
+
s = x.sound :sine
|
14
|
+
s.attack = 0
|
15
|
+
s.release = 0.25
|
16
|
+
|
17
|
+
x.tune s, "1a 1a 1a 8"
|
18
|
+
end
|
19
|
+
|
20
|
+
Soxophone.play do |x|
|
21
|
+
s = x.sound :sine
|
22
|
+
s.attack = 1
|
23
|
+
s.release = 0
|
24
|
+
|
25
|
+
x.tune s, "1a 1a 1a 8"
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
2
|
+
require "soxophone"
|
3
|
+
|
4
|
+
def inspect_notation(n)
|
5
|
+
puts "#{n} =>"
|
6
|
+
Soxophone::Notation.instructions(n).each do |ins|
|
7
|
+
puts ins.inspect
|
8
|
+
end
|
9
|
+
puts
|
10
|
+
end
|
11
|
+
|
12
|
+
inspect_notation('a b c')
|
13
|
+
inspect_notation('a 8b 1:c')
|
14
|
+
inspect_notation('a bb c#')
|
15
|
+
inspect_notation('a + b - c')
|
16
|
+
inspect_notation('a5 b2 c3')
|
17
|
+
|
18
|
+
inspect_notation('(a b)')
|
19
|
+
inspect_notation('(a b) c')
|
20
|
+
inspect_notation('(a b) (c d)')
|
21
|
+
inspect_notation('g3 (a b) c (d e) f')
|
22
|
+
|
23
|
+
inspect_notation('a b !!!')
|
data/examples/samples.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
2
|
+
require "soxophone"
|
3
|
+
|
4
|
+
Soxophone.play do |x|
|
5
|
+
c = x.sample("crash.mp3")
|
6
|
+
c.gain = -9
|
7
|
+
|
8
|
+
x.tune c, "1a 4a"
|
9
|
+
end
|
10
|
+
|
11
|
+
Soxophone.play do |x|
|
12
|
+
t = x.sample("triangle.mp3")
|
13
|
+
t.gain = 5
|
14
|
+
|
15
|
+
x.tune t, "1a 16a 16a 16a 16a"
|
16
|
+
end
|
17
|
+
|
18
|
+
Soxophone.play do |x|
|
19
|
+
t = x.sample("triangle.mp3")
|
20
|
+
t.gain = 5
|
21
|
+
|
22
|
+
x.tune t, "a b c d e f g + a - 4 (a c e)"
|
23
|
+
end
|
Binary file
|
data/lib/soxophone.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Soxophone
|
2
|
+
|
3
|
+
def self.play
|
4
|
+
s = Base.new
|
5
|
+
yield s
|
6
|
+
system "play #{"-m" if s.tunes.size > 1} #{s.paths.map { |p| "-t sox #{p} " }.join(" ")}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.write(path)
|
10
|
+
s = Base.new
|
11
|
+
yield s
|
12
|
+
system "sox #{"-m" if s.tunes.size > 1} #{s.paths.map { |p| "-t sox #{p} " }.join(" ")} #{path}"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
require "soxophone/notation"
|
18
|
+
require "soxophone/effects"
|
19
|
+
require "soxophone/sound"
|
20
|
+
require "soxophone/sample"
|
21
|
+
require "soxophone/tune"
|
22
|
+
require "soxophone/base"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
3
|
+
module Soxophone
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@tunes = []
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :tunes
|
12
|
+
|
13
|
+
def sound(kind)
|
14
|
+
Sound.new(kind)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sample(path)
|
18
|
+
Sample.new(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def tune(sound, notation)
|
22
|
+
tune = Tune.new(sound)
|
23
|
+
tune.instructions = Notation.instructions(notation)
|
24
|
+
@tunes << tune
|
25
|
+
end
|
26
|
+
|
27
|
+
def paths
|
28
|
+
@tunes.map do |tune|
|
29
|
+
tmp = Tempfile.new("soxo")
|
30
|
+
system tune.cmd(tmp.path)
|
31
|
+
tmp.close
|
32
|
+
|
33
|
+
tmp.path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Soxophone
|
2
|
+
|
3
|
+
module Effects
|
4
|
+
|
5
|
+
attr_reader :tremolo
|
6
|
+
attr_reader :phaser
|
7
|
+
attr_reader :gain
|
8
|
+
|
9
|
+
def tremolo=(tremolo)
|
10
|
+
unless tremolo.is_a?(Numeric)
|
11
|
+
raise "Tremolo must be a number"
|
12
|
+
end
|
13
|
+
@tremolo = tremolo
|
14
|
+
end
|
15
|
+
|
16
|
+
def phaser=(phaser)
|
17
|
+
unless [true, false].include?(phaser)
|
18
|
+
raise "Phaser must be true or false"
|
19
|
+
end
|
20
|
+
@phaser = phaser
|
21
|
+
end
|
22
|
+
|
23
|
+
def gain=(gain)
|
24
|
+
unless gain.is_a?(Numeric)
|
25
|
+
raise "Gain must be a number"
|
26
|
+
end
|
27
|
+
@gain = gain
|
28
|
+
end
|
29
|
+
|
30
|
+
def effectopts
|
31
|
+
fx = []
|
32
|
+
fx << "tremolo #{tremolo}" if tremolo
|
33
|
+
fx << "phaser 0.6 0.66 3 0.6 2 -t" if phaser
|
34
|
+
fx << "gain #{gain}" if gain
|
35
|
+
|
36
|
+
fx.compact.join(" ")
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,504 @@
|
|
1
|
+
# line 1 "lib/soxophone/notation.rl"
|
2
|
+
# :$: BLOOPSAPHONE :$:
|
3
|
+
# Copyright (c) 2009 why the lucky stiff
|
4
|
+
# Based on sfxr (c) 2007 Tomas Pettersson
|
5
|
+
# (Also released under the MIT license)
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person
|
8
|
+
# obtaining a copy of this software and associated documentation
|
9
|
+
# files (the "Software"), to deal in the Software without restriction,
|
10
|
+
# including without limitation the rights to use, copy, modify, merge,
|
11
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
12
|
+
# and to permit persons to whom the Software is furnished to do so,
|
13
|
+
# subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
19
|
+
# ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
20
|
+
# TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
21
|
+
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
22
|
+
# SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
23
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
24
|
+
# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
26
|
+
# SOFTWARE.
|
27
|
+
|
28
|
+
module Soxophone
|
29
|
+
|
30
|
+
class Rest
|
31
|
+
attr_accessor :duration
|
32
|
+
end
|
33
|
+
|
34
|
+
class Note
|
35
|
+
attr_accessor :tone, :duration, :octave
|
36
|
+
end
|
37
|
+
|
38
|
+
class Chord
|
39
|
+
def initialize
|
40
|
+
@notes = []
|
41
|
+
end
|
42
|
+
def duration
|
43
|
+
notes.map { |n| n.duration }.min
|
44
|
+
end
|
45
|
+
attr_accessor :notes
|
46
|
+
end
|
47
|
+
|
48
|
+
module Notation
|
49
|
+
|
50
|
+
NUMBERS = {
|
51
|
+
'A' => 0,
|
52
|
+
'B' => 2,
|
53
|
+
'C' => 3,
|
54
|
+
'D' => 5,
|
55
|
+
'E' => 7,
|
56
|
+
'F' => 8,
|
57
|
+
'G' => 10
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
# line 62 "lib/soxophone/notation.rb"
|
62
|
+
class << self
|
63
|
+
attr_accessor :_bloopnotes_actions
|
64
|
+
private :_bloopnotes_actions, :_bloopnotes_actions=
|
65
|
+
end
|
66
|
+
self._bloopnotes_actions = [
|
67
|
+
0, 1, 0, 1, 1, 1, 2, 1,
|
68
|
+
3, 1, 5, 1, 6, 1, 7, 1,
|
69
|
+
8, 1, 9, 1, 10, 2, 0, 11,
|
70
|
+
2, 0, 13, 2, 0, 14, 2, 1,
|
71
|
+
13, 2, 2, 14, 2, 3, 12, 2,
|
72
|
+
4, 12, 2, 5, 12
|
73
|
+
]
|
74
|
+
|
75
|
+
class << self
|
76
|
+
attr_accessor :_bloopnotes_key_offsets
|
77
|
+
private :_bloopnotes_key_offsets, :_bloopnotes_key_offsets=
|
78
|
+
end
|
79
|
+
self._bloopnotes_key_offsets = [
|
80
|
+
0, 0, 13, 15, 18, 19, 19, 21,
|
81
|
+
24, 25, 25, 32, 37, 41, 45, 47
|
82
|
+
]
|
83
|
+
|
84
|
+
class << self
|
85
|
+
attr_accessor :_bloopnotes_trans_keys
|
86
|
+
private :_bloopnotes_trans_keys, :_bloopnotes_trans_keys=
|
87
|
+
end
|
88
|
+
self._bloopnotes_trans_keys = [
|
89
|
+
32, 40, 41, 43, 45, 9, 13, 49,
|
90
|
+
57, 65, 71, 97, 103, 49, 57, 58,
|
91
|
+
48, 57, 58, 49, 57, 58, 48, 57,
|
92
|
+
58, 58, 48, 57, 65, 71, 97, 103,
|
93
|
+
58, 65, 71, 97, 103, 65, 71, 97,
|
94
|
+
103, 35, 98, 49, 56, 49, 56, 0
|
95
|
+
]
|
96
|
+
|
97
|
+
class << self
|
98
|
+
attr_accessor :_bloopnotes_single_lengths
|
99
|
+
private :_bloopnotes_single_lengths, :_bloopnotes_single_lengths=
|
100
|
+
end
|
101
|
+
self._bloopnotes_single_lengths = [
|
102
|
+
0, 5, 0, 1, 1, 0, 0, 1,
|
103
|
+
1, 0, 1, 1, 0, 2, 0, 0
|
104
|
+
]
|
105
|
+
|
106
|
+
class << self
|
107
|
+
attr_accessor :_bloopnotes_range_lengths
|
108
|
+
private :_bloopnotes_range_lengths, :_bloopnotes_range_lengths=
|
109
|
+
end
|
110
|
+
self._bloopnotes_range_lengths = [
|
111
|
+
0, 4, 1, 1, 0, 0, 1, 1,
|
112
|
+
0, 0, 3, 2, 2, 1, 1, 0
|
113
|
+
]
|
114
|
+
|
115
|
+
class << self
|
116
|
+
attr_accessor :_bloopnotes_index_offsets
|
117
|
+
private :_bloopnotes_index_offsets, :_bloopnotes_index_offsets=
|
118
|
+
end
|
119
|
+
self._bloopnotes_index_offsets = [
|
120
|
+
0, 0, 10, 12, 15, 17, 18, 20,
|
121
|
+
23, 25, 26, 31, 35, 38, 42, 44
|
122
|
+
]
|
123
|
+
|
124
|
+
class << self
|
125
|
+
attr_accessor :_bloopnotes_trans_targs
|
126
|
+
private :_bloopnotes_trans_targs, :_bloopnotes_trans_targs=
|
127
|
+
end
|
128
|
+
self._bloopnotes_trans_targs = [
|
129
|
+
1, 1, 1, 2, 6, 1, 10, 13,
|
130
|
+
13, 0, 3, 1, 5, 4, 1, 5,
|
131
|
+
1, 1, 7, 1, 9, 8, 1, 9,
|
132
|
+
1, 1, 12, 11, 13, 13, 1, 12,
|
133
|
+
13, 13, 1, 13, 13, 1, 14, 14,
|
134
|
+
15, 1, 15, 1, 1, 1, 1, 1,
|
135
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
136
|
+
1, 1, 1, 0
|
137
|
+
]
|
138
|
+
|
139
|
+
class << self
|
140
|
+
attr_accessor :_bloopnotes_trans_actions
|
141
|
+
private :_bloopnotes_trans_actions, :_bloopnotes_trans_actions=
|
142
|
+
end
|
143
|
+
self._bloopnotes_trans_actions = [
|
144
|
+
19, 15, 17, 0, 0, 19, 0, 0,
|
145
|
+
0, 0, 3, 30, 0, 0, 24, 0,
|
146
|
+
24, 24, 5, 33, 0, 0, 27, 0,
|
147
|
+
27, 27, 0, 0, 1, 1, 21, 0,
|
148
|
+
1, 1, 21, 1, 1, 21, 9, 9,
|
149
|
+
9, 42, 7, 36, 39, 30, 24, 24,
|
150
|
+
24, 33, 27, 27, 27, 21, 21, 21,
|
151
|
+
42, 36, 39, 0
|
152
|
+
]
|
153
|
+
|
154
|
+
class << self
|
155
|
+
attr_accessor :_bloopnotes_to_state_actions
|
156
|
+
private :_bloopnotes_to_state_actions, :_bloopnotes_to_state_actions=
|
157
|
+
end
|
158
|
+
self._bloopnotes_to_state_actions = [
|
159
|
+
0, 11, 0, 0, 0, 0, 0, 0,
|
160
|
+
0, 0, 0, 0, 0, 0, 0, 0
|
161
|
+
]
|
162
|
+
|
163
|
+
class << self
|
164
|
+
attr_accessor :_bloopnotes_from_state_actions
|
165
|
+
private :_bloopnotes_from_state_actions, :_bloopnotes_from_state_actions=
|
166
|
+
end
|
167
|
+
self._bloopnotes_from_state_actions = [
|
168
|
+
0, 13, 0, 0, 0, 0, 0, 0,
|
169
|
+
0, 0, 0, 0, 0, 0, 0, 0
|
170
|
+
]
|
171
|
+
|
172
|
+
class << self
|
173
|
+
attr_accessor :_bloopnotes_eof_trans
|
174
|
+
private :_bloopnotes_eof_trans, :_bloopnotes_eof_trans=
|
175
|
+
end
|
176
|
+
self._bloopnotes_eof_trans = [
|
177
|
+
0, 0, 46, 49, 49, 49, 50, 53,
|
178
|
+
53, 53, 56, 56, 56, 57, 58, 59
|
179
|
+
]
|
180
|
+
|
181
|
+
class << self
|
182
|
+
attr_accessor :bloopnotes_start
|
183
|
+
end
|
184
|
+
self.bloopnotes_start = 1;
|
185
|
+
class << self
|
186
|
+
attr_accessor :bloopnotes_error
|
187
|
+
end
|
188
|
+
self.bloopnotes_error = 0;
|
189
|
+
|
190
|
+
class << self
|
191
|
+
attr_accessor :bloopnotes_en_main
|
192
|
+
end
|
193
|
+
self.bloopnotes_en_main = 1;
|
194
|
+
|
195
|
+
# line 130 "lib/soxophone/notation.rl"
|
196
|
+
|
197
|
+
|
198
|
+
def self.instructions(data)
|
199
|
+
oct = 4
|
200
|
+
len = 4
|
201
|
+
tone = nil
|
202
|
+
mod = nil
|
203
|
+
instructions = []
|
204
|
+
|
205
|
+
data += " "
|
206
|
+
eof = -1
|
207
|
+
|
208
|
+
|
209
|
+
# line 210 "lib/soxophone/notation.rb"
|
210
|
+
begin
|
211
|
+
p ||= 0
|
212
|
+
pe ||= data.length
|
213
|
+
cs = bloopnotes_start
|
214
|
+
ts = nil
|
215
|
+
te = nil
|
216
|
+
act = 0
|
217
|
+
end
|
218
|
+
|
219
|
+
# line 220 "lib/soxophone/notation.rb"
|
220
|
+
begin
|
221
|
+
_klen, _trans, _keys, _acts, _nacts = nil
|
222
|
+
_goto_level = 0
|
223
|
+
_resume = 10
|
224
|
+
_eof_trans = 15
|
225
|
+
_again = 20
|
226
|
+
_test_eof = 30
|
227
|
+
_out = 40
|
228
|
+
while true
|
229
|
+
_trigger_goto = false
|
230
|
+
if _goto_level <= 0
|
231
|
+
if p == pe
|
232
|
+
_goto_level = _test_eof
|
233
|
+
next
|
234
|
+
end
|
235
|
+
if cs == 0
|
236
|
+
_goto_level = _out
|
237
|
+
next
|
238
|
+
end
|
239
|
+
end
|
240
|
+
if _goto_level <= _resume
|
241
|
+
_acts = _bloopnotes_from_state_actions[cs]
|
242
|
+
_nacts = _bloopnotes_actions[_acts]
|
243
|
+
_acts += 1
|
244
|
+
while _nacts > 0
|
245
|
+
_nacts -= 1
|
246
|
+
_acts += 1
|
247
|
+
case _bloopnotes_actions[_acts - 1]
|
248
|
+
when 7 then
|
249
|
+
# line 1 "lib/soxophone/notation.rl"
|
250
|
+
begin
|
251
|
+
ts = p
|
252
|
+
end
|
253
|
+
# line 1 "lib/soxophone/notation.rl"
|
254
|
+
# line 255 "lib/soxophone/notation.rb"
|
255
|
+
end # from state action switch
|
256
|
+
end
|
257
|
+
if _trigger_goto
|
258
|
+
next
|
259
|
+
end
|
260
|
+
_keys = _bloopnotes_key_offsets[cs]
|
261
|
+
_trans = _bloopnotes_index_offsets[cs]
|
262
|
+
_klen = _bloopnotes_single_lengths[cs]
|
263
|
+
_break_match = false
|
264
|
+
|
265
|
+
begin
|
266
|
+
if _klen > 0
|
267
|
+
_lower = _keys
|
268
|
+
_upper = _keys + _klen - 1
|
269
|
+
|
270
|
+
loop do
|
271
|
+
break if _upper < _lower
|
272
|
+
_mid = _lower + ( (_upper - _lower) >> 1 )
|
273
|
+
|
274
|
+
if data[p] < _bloopnotes_trans_keys[_mid]
|
275
|
+
_upper = _mid - 1
|
276
|
+
elsif data[p] > _bloopnotes_trans_keys[_mid]
|
277
|
+
_lower = _mid + 1
|
278
|
+
else
|
279
|
+
_trans += (_mid - _keys)
|
280
|
+
_break_match = true
|
281
|
+
break
|
282
|
+
end
|
283
|
+
end # loop
|
284
|
+
break if _break_match
|
285
|
+
_keys += _klen
|
286
|
+
_trans += _klen
|
287
|
+
end
|
288
|
+
_klen = _bloopnotes_range_lengths[cs]
|
289
|
+
if _klen > 0
|
290
|
+
_lower = _keys
|
291
|
+
_upper = _keys + (_klen << 1) - 2
|
292
|
+
loop do
|
293
|
+
break if _upper < _lower
|
294
|
+
_mid = _lower + (((_upper-_lower) >> 1) & ~1)
|
295
|
+
if data[p] < _bloopnotes_trans_keys[_mid]
|
296
|
+
_upper = _mid - 2
|
297
|
+
elsif data[p] > _bloopnotes_trans_keys[_mid+1]
|
298
|
+
_lower = _mid + 2
|
299
|
+
else
|
300
|
+
_trans += ((_mid - _keys) >> 1)
|
301
|
+
_break_match = true
|
302
|
+
break
|
303
|
+
end
|
304
|
+
end # loop
|
305
|
+
break if _break_match
|
306
|
+
_trans += _klen
|
307
|
+
end
|
308
|
+
end while false
|
309
|
+
end
|
310
|
+
if _goto_level <= _eof_trans
|
311
|
+
cs = _bloopnotes_trans_targs[_trans]
|
312
|
+
if _bloopnotes_trans_actions[_trans] != 0
|
313
|
+
_acts = _bloopnotes_trans_actions[_trans]
|
314
|
+
_nacts = _bloopnotes_actions[_acts]
|
315
|
+
_acts += 1
|
316
|
+
while _nacts > 0
|
317
|
+
_nacts -= 1
|
318
|
+
_acts += 1
|
319
|
+
case _bloopnotes_actions[_acts - 1]
|
320
|
+
when 0 then
|
321
|
+
# line 62 "lib/soxophone/notation.rl"
|
322
|
+
begin
|
323
|
+
len = data[ts..p].to_i end
|
324
|
+
# line 62 "lib/soxophone/notation.rl"
|
325
|
+
when 1 then
|
326
|
+
# line 63 "lib/soxophone/notation.rl"
|
327
|
+
begin
|
328
|
+
len = 1; end
|
329
|
+
# line 63 "lib/soxophone/notation.rl"
|
330
|
+
when 2 then
|
331
|
+
# line 64 "lib/soxophone/notation.rl"
|
332
|
+
begin
|
333
|
+
len = 1; end
|
334
|
+
# line 64 "lib/soxophone/notation.rl"
|
335
|
+
when 3 then
|
336
|
+
# line 65 "lib/soxophone/notation.rl"
|
337
|
+
begin
|
338
|
+
mod = data[p-1..p-1] end
|
339
|
+
# line 65 "lib/soxophone/notation.rl"
|
340
|
+
when 4 then
|
341
|
+
# line 66 "lib/soxophone/notation.rl"
|
342
|
+
begin
|
343
|
+
oct = data[p-1..p-1].to_i end
|
344
|
+
# line 66 "lib/soxophone/notation.rl"
|
345
|
+
when 5 then
|
346
|
+
# line 67 "lib/soxophone/notation.rl"
|
347
|
+
begin
|
348
|
+
tone = data[p-1..p-1] end
|
349
|
+
# line 67 "lib/soxophone/notation.rl"
|
350
|
+
when 8 then
|
351
|
+
# line 72 "lib/soxophone/notation.rl"
|
352
|
+
begin
|
353
|
+
te = p+1
|
354
|
+
begin
|
355
|
+
chord = Chord.new
|
356
|
+
end
|
357
|
+
end
|
358
|
+
# line 72 "lib/soxophone/notation.rl"
|
359
|
+
when 9 then
|
360
|
+
# line 75 "lib/soxophone/notation.rl"
|
361
|
+
begin
|
362
|
+
te = p+1
|
363
|
+
begin
|
364
|
+
if chord
|
365
|
+
instructions << chord
|
366
|
+
chord = nil
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
# line 75 "lib/soxophone/notation.rl"
|
371
|
+
when 10 then
|
372
|
+
# line 125 "lib/soxophone/notation.rl"
|
373
|
+
begin
|
374
|
+
te = p+1
|
375
|
+
end
|
376
|
+
# line 125 "lib/soxophone/notation.rl"
|
377
|
+
when 11 then
|
378
|
+
# line 81 "lib/soxophone/notation.rl"
|
379
|
+
begin
|
380
|
+
te = p
|
381
|
+
p = p - 1; begin
|
382
|
+
unless chord
|
383
|
+
rest = Rest.new
|
384
|
+
rest.duration = len
|
385
|
+
|
386
|
+
instructions << rest
|
387
|
+
end
|
388
|
+
|
389
|
+
mod = nil;
|
390
|
+
tone = nil;
|
391
|
+
len = 4;
|
392
|
+
end
|
393
|
+
end
|
394
|
+
# line 81 "lib/soxophone/notation.rl"
|
395
|
+
when 12 then
|
396
|
+
# line 93 "lib/soxophone/notation.rl"
|
397
|
+
begin
|
398
|
+
te = p
|
399
|
+
p = p - 1; begin
|
400
|
+
note = Note.new
|
401
|
+
|
402
|
+
num = NUMBERS[tone.upcase]
|
403
|
+
if mod == 'b'
|
404
|
+
num -= 1
|
405
|
+
elsif mod == '#'
|
406
|
+
num += 1
|
407
|
+
end
|
408
|
+
|
409
|
+
note.tone = num
|
410
|
+
note.duration = len
|
411
|
+
note.octave = oct
|
412
|
+
|
413
|
+
if chord
|
414
|
+
chord.notes << note
|
415
|
+
else
|
416
|
+
instructions << note
|
417
|
+
end
|
418
|
+
|
419
|
+
mod = nil
|
420
|
+
tone = nil
|
421
|
+
len = 4
|
422
|
+
end
|
423
|
+
end
|
424
|
+
# line 93 "lib/soxophone/notation.rl"
|
425
|
+
when 13 then
|
426
|
+
# line 117 "lib/soxophone/notation.rl"
|
427
|
+
begin
|
428
|
+
te = p
|
429
|
+
p = p - 1; begin
|
430
|
+
oct += 1
|
431
|
+
len = 4
|
432
|
+
end
|
433
|
+
end
|
434
|
+
# line 117 "lib/soxophone/notation.rl"
|
435
|
+
when 14 then
|
436
|
+
# line 121 "lib/soxophone/notation.rl"
|
437
|
+
begin
|
438
|
+
te = p
|
439
|
+
p = p - 1; begin
|
440
|
+
oct -= 1
|
441
|
+
len = 4
|
442
|
+
end
|
443
|
+
end
|
444
|
+
# line 121 "lib/soxophone/notation.rl"
|
445
|
+
# line 446 "lib/soxophone/notation.rb"
|
446
|
+
end # action switch
|
447
|
+
end
|
448
|
+
end
|
449
|
+
if _trigger_goto
|
450
|
+
next
|
451
|
+
end
|
452
|
+
end
|
453
|
+
if _goto_level <= _again
|
454
|
+
_acts = _bloopnotes_to_state_actions[cs]
|
455
|
+
_nacts = _bloopnotes_actions[_acts]
|
456
|
+
_acts += 1
|
457
|
+
while _nacts > 0
|
458
|
+
_nacts -= 1
|
459
|
+
_acts += 1
|
460
|
+
case _bloopnotes_actions[_acts - 1]
|
461
|
+
when 6 then
|
462
|
+
# line 1 "lib/soxophone/notation.rl"
|
463
|
+
begin
|
464
|
+
ts = nil; end
|
465
|
+
# line 1 "lib/soxophone/notation.rl"
|
466
|
+
# line 467 "lib/soxophone/notation.rb"
|
467
|
+
end # to state action switch
|
468
|
+
end
|
469
|
+
if _trigger_goto
|
470
|
+
next
|
471
|
+
end
|
472
|
+
if cs == 0
|
473
|
+
_goto_level = _out
|
474
|
+
next
|
475
|
+
end
|
476
|
+
p += 1
|
477
|
+
if p != pe
|
478
|
+
_goto_level = _resume
|
479
|
+
next
|
480
|
+
end
|
481
|
+
end
|
482
|
+
if _goto_level <= _test_eof
|
483
|
+
if p == eof
|
484
|
+
if _bloopnotes_eof_trans[cs] > 0
|
485
|
+
_trans = _bloopnotes_eof_trans[cs] - 1;
|
486
|
+
_goto_level = _eof_trans
|
487
|
+
next;
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
if _goto_level <= _out
|
492
|
+
break
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
# line 145 "lib/soxophone/notation.rl"
|
497
|
+
|
498
|
+
|
499
|
+
instructions
|
500
|
+
end
|
501
|
+
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# :$: BLOOPSAPHONE :$:
|
2
|
+
# Copyright (c) 2009 why the lucky stiff
|
3
|
+
# Based on sfxr (c) 2007 Tomas Pettersson
|
4
|
+
# (Also released under the MIT license)
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person
|
7
|
+
# obtaining a copy of this software and associated documentation
|
8
|
+
# files (the "Software"), to deal in the Software without restriction,
|
9
|
+
# including without limitation the rights to use, copy, modify, merge,
|
10
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
11
|
+
# and to permit persons to whom the Software is furnished to do so,
|
12
|
+
# subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
18
|
+
# ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
19
|
+
# TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
20
|
+
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
21
|
+
# SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
22
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
23
|
+
# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
25
|
+
# SOFTWARE.
|
26
|
+
|
27
|
+
module Soxophone
|
28
|
+
|
29
|
+
class Rest
|
30
|
+
attr_accessor :duration
|
31
|
+
end
|
32
|
+
|
33
|
+
class Note
|
34
|
+
attr_accessor :tone, :duration, :octave
|
35
|
+
end
|
36
|
+
|
37
|
+
class Chord
|
38
|
+
def initialize
|
39
|
+
@notes = []
|
40
|
+
end
|
41
|
+
def duration
|
42
|
+
notes.map { |n| n.duration }.min
|
43
|
+
end
|
44
|
+
attr_accessor :notes
|
45
|
+
end
|
46
|
+
|
47
|
+
module Notation
|
48
|
+
|
49
|
+
NUMBERS = {
|
50
|
+
'A' => 0,
|
51
|
+
'B' => 2,
|
52
|
+
'C' => 3,
|
53
|
+
'D' => 5,
|
54
|
+
'E' => 7,
|
55
|
+
'F' => 8,
|
56
|
+
'G' => 10
|
57
|
+
}
|
58
|
+
|
59
|
+
%%{
|
60
|
+
machine bloopnotes;
|
61
|
+
|
62
|
+
len = [1-9] [0-9]? ":"? %{ len = data[ts..p].to_i };
|
63
|
+
up = "+" %{ len = 1; } len?;
|
64
|
+
down = "-" %{ len = 1; } len?;
|
65
|
+
mod = [b#] %{ mod = data[p-1..p-1] };
|
66
|
+
oct = [1-8] %{ oct = data[p-1..p-1].to_i };
|
67
|
+
note = len? [a-gA-G] %{ tone = data[p-1..p-1] } mod? oct?;
|
68
|
+
startchord = "(";
|
69
|
+
endchord = ")";
|
70
|
+
|
71
|
+
main := |*
|
72
|
+
startchord => {
|
73
|
+
chord = Chord.new
|
74
|
+
};
|
75
|
+
endchord => {
|
76
|
+
if chord
|
77
|
+
instructions << chord
|
78
|
+
chord = nil
|
79
|
+
end
|
80
|
+
};
|
81
|
+
len => {
|
82
|
+
unless chord
|
83
|
+
rest = Rest.new
|
84
|
+
rest.duration = len
|
85
|
+
|
86
|
+
instructions << rest
|
87
|
+
end
|
88
|
+
|
89
|
+
mod = nil;
|
90
|
+
tone = nil;
|
91
|
+
len = 4;
|
92
|
+
};
|
93
|
+
note => {
|
94
|
+
note = Note.new
|
95
|
+
|
96
|
+
num = NUMBERS[tone.upcase]
|
97
|
+
if mod == 'b'
|
98
|
+
num -= 1
|
99
|
+
elsif mod == '#'
|
100
|
+
num += 1
|
101
|
+
end
|
102
|
+
|
103
|
+
note.tone = num
|
104
|
+
note.duration = len
|
105
|
+
note.octave = oct
|
106
|
+
|
107
|
+
if chord
|
108
|
+
chord.notes << note
|
109
|
+
else
|
110
|
+
instructions << note
|
111
|
+
end
|
112
|
+
|
113
|
+
mod = nil
|
114
|
+
tone = nil
|
115
|
+
len = 4
|
116
|
+
};
|
117
|
+
up => {
|
118
|
+
oct += 1
|
119
|
+
len = 4
|
120
|
+
};
|
121
|
+
down => {
|
122
|
+
oct -= 1
|
123
|
+
len = 4
|
124
|
+
};
|
125
|
+
space;
|
126
|
+
*|;
|
127
|
+
|
128
|
+
write data nofinal;
|
129
|
+
|
130
|
+
}%%
|
131
|
+
|
132
|
+
def self.instructions(data)
|
133
|
+
oct = 4
|
134
|
+
len = 4
|
135
|
+
tone = nil
|
136
|
+
mod = nil
|
137
|
+
instructions = []
|
138
|
+
|
139
|
+
data += " "
|
140
|
+
eof = -1
|
141
|
+
|
142
|
+
%%{
|
143
|
+
write init;
|
144
|
+
write exec;
|
145
|
+
}%%
|
146
|
+
|
147
|
+
instructions
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Soxophone
|
2
|
+
|
3
|
+
class Sample
|
4
|
+
|
5
|
+
include Effects
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
self.path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :path
|
12
|
+
|
13
|
+
def path=(path)
|
14
|
+
unless File.file?(path)
|
15
|
+
raise "Sample path must be a file"
|
16
|
+
end
|
17
|
+
@path = path
|
18
|
+
end
|
19
|
+
|
20
|
+
def notecmd(freq, duration)
|
21
|
+
if duration > length
|
22
|
+
"sox #{path} -r 48k -c1 -p pad 0 #{duration - length} pitch #{freq*100}"
|
23
|
+
else
|
24
|
+
"sox #{path} -r 48k -c1 -p trim 0 #{duration} pitch #{freq*100}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def chordcmd(freqs, duration)
|
29
|
+
cmds = freqs.map { |f| notecmd(f, duration) }
|
30
|
+
"sox -m #{cmds.map { |c| "\"|#{c}\"" }.join(" ")} -p"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def length
|
36
|
+
if instance_variable_defined?(:@length)
|
37
|
+
return @length
|
38
|
+
end
|
39
|
+
|
40
|
+
rate = `soxi -r #{path}`.strip.to_i
|
41
|
+
samples = `soxi -s #{path}`.strip.to_i
|
42
|
+
@length = samples.quo(rate)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Soxophone
|
2
|
+
|
3
|
+
class Sound
|
4
|
+
|
5
|
+
include Effects
|
6
|
+
|
7
|
+
KINDS = %w(
|
8
|
+
sine square triangle sawtooth trapezium
|
9
|
+
exp noise whitenoise pinknoise brownnoise
|
10
|
+
)
|
11
|
+
|
12
|
+
def initialize(kind)
|
13
|
+
self.kind = kind
|
14
|
+
self.attack = 0
|
15
|
+
self.release = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :kind
|
19
|
+
attr_reader :attack
|
20
|
+
attr_reader :release
|
21
|
+
|
22
|
+
def kind=(kind)
|
23
|
+
unless KINDS.include?(kind.to_s)
|
24
|
+
raise "Kind must be one of #{KINDS.join(", ")}"
|
25
|
+
end
|
26
|
+
@kind = kind
|
27
|
+
end
|
28
|
+
|
29
|
+
def attack=(attack)
|
30
|
+
unless attack.is_a?(Numeric)
|
31
|
+
raise "Attack must be a number"
|
32
|
+
end
|
33
|
+
@attack = attack
|
34
|
+
end
|
35
|
+
|
36
|
+
def release=(release)
|
37
|
+
unless release.is_a?(Numeric)
|
38
|
+
raise "Release must be a number"
|
39
|
+
end
|
40
|
+
@release = release
|
41
|
+
end
|
42
|
+
|
43
|
+
def notecmd(freq, duration)
|
44
|
+
env = envelope(duration)
|
45
|
+
"sox -n -p synth #{duration} #{kind} %#{freq} #{env}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def chordcmd(freqs, duration)
|
49
|
+
env = envelope(duration)
|
50
|
+
"sox -n -c1 -p synth #{duration} #{freqs.map { |f| "#{kind} %#{f}" }.join(" ")} #{env}"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def envelope(duration)
|
56
|
+
env = []
|
57
|
+
env << "fade #{attack * duration}" if attack > 0
|
58
|
+
env << "fade 0 #{duration} #{release * duration}" if release > 0
|
59
|
+
|
60
|
+
env.compact.join(" ")
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Soxophone
|
2
|
+
|
3
|
+
class Tune
|
4
|
+
def initialize(sound)
|
5
|
+
@instructions = []
|
6
|
+
@sound = sound
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :instructions, :sound
|
10
|
+
|
11
|
+
def cmd(out_path)
|
12
|
+
"sox #{cmds.map { |c| "\"|#{c.gsub('"', '\"')}\"" }.join(" ")} -t sox #{out_path} #{sound.effectopts}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def cmds
|
18
|
+
@instructions.map do |ins|
|
19
|
+
duration = 1.0 / ins.duration
|
20
|
+
|
21
|
+
case ins
|
22
|
+
when Chord
|
23
|
+
freqs = ins.notes.map { |n| n.tone + ((n.octave - 4) * 12) }
|
24
|
+
|
25
|
+
if freqs.size == 1
|
26
|
+
sound.notecmd(freqs.first, duration)
|
27
|
+
else
|
28
|
+
sound.chordcmd(freqs, duration)
|
29
|
+
end
|
30
|
+
|
31
|
+
when Note
|
32
|
+
freq = ins.tone + ((ins.octave - 4) * 12)
|
33
|
+
sound.notecmd(freq, duration)
|
34
|
+
|
35
|
+
when Rest
|
36
|
+
"sox -n -p trim 0 #{duration}"
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/soxo.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/lib"
|
2
|
+
require "soxophone"
|
3
|
+
|
4
|
+
Soxophone.write("soxo.mp3") do |x|
|
5
|
+
|
6
|
+
toot = x.sound :sine
|
7
|
+
toot.attack = 0
|
8
|
+
toot.release = 0.25
|
9
|
+
toot.tremolo = 100
|
10
|
+
|
11
|
+
squp = x.sound :square
|
12
|
+
squp.gain = -9
|
13
|
+
squp.attack = 0
|
14
|
+
squp.release = 0.5
|
15
|
+
|
16
|
+
pish = x.sound :noise
|
17
|
+
pish.attack = 0
|
18
|
+
pish.release = 0.9
|
19
|
+
|
20
|
+
x.tune toot, %q*
|
21
|
+
8c 8e 8g + 2c 8 4a - (2d f) 4
|
22
|
+
8c 8e 8g + 2b 8 4a - (2c e) 4
|
23
|
+
8c 8e 8g + 2a 8-4g (2b d) 4
|
24
|
+
1c
|
25
|
+
*
|
26
|
+
|
27
|
+
x.tune squp, %q*
|
28
|
+
1c2 1g2
|
29
|
+
1e2 1a3
|
30
|
+
1g2 1d3
|
31
|
+
1c2
|
32
|
+
*
|
33
|
+
|
34
|
+
x.tune pish, %q*
|
35
|
+
1a 4a 4 4a 4
|
36
|
+
1a 4a 4 4a 4
|
37
|
+
1a 4a 4 4a 4
|
38
|
+
*
|
39
|
+
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mdaines-soxophone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.2"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Daines
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-19 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: a little synth and sequencer
|
17
|
+
email: michael@mdaines.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- COPYING
|
25
|
+
files:
|
26
|
+
- COPYING
|
27
|
+
- README
|
28
|
+
- examples/boop.rb
|
29
|
+
- examples/chords.rb
|
30
|
+
- examples/effects.rb
|
31
|
+
- examples/envelope.rb
|
32
|
+
- examples/notation.rb
|
33
|
+
- examples/samples.rb
|
34
|
+
- examples/crash.mp3
|
35
|
+
- examples/triangle.mp3
|
36
|
+
- lib/soxophone.rb
|
37
|
+
- lib/soxophone/base.rb
|
38
|
+
- lib/soxophone/effects.rb
|
39
|
+
- lib/soxophone/notation.rb
|
40
|
+
- lib/soxophone/notation.rl
|
41
|
+
- lib/soxophone/sample.rb
|
42
|
+
- lib/soxophone/sound.rb
|
43
|
+
- lib/soxophone/tune.rb
|
44
|
+
- Rakefile
|
45
|
+
- soxo.rb
|
46
|
+
has_rdoc: false
|
47
|
+
homepage: http://github.com/mdaines/soxophone
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.2.0
|
69
|
+
signing_key:
|
70
|
+
specification_version: 2
|
71
|
+
summary: a little synth and sequencer
|
72
|
+
test_files: []
|
73
|
+
|