niki 0.0.1
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/.gitignore +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +22 -0
- data/Rakefile +2 -0
- data/Readme.md +55 -0
- data/examples/my_song.rb +267 -0
- data/examples/my_song.rns +0 -0
- data/lib/core_ext/array.rb +13 -0
- data/lib/core_ext/fixnum.rb +8 -0
- data/lib/niki.rb +9 -0
- data/lib/niki/chords.rb +44 -0
- data/lib/niki/part.rb +69 -0
- data/lib/niki/song.rb +81 -0
- data/lib/niki/version.rb +3 -0
- data/niki.gemspec +23 -0
- data/vendor/archaeopteryx/archaeopteryx.rb +28 -0
- data/vendor/archaeopteryx/clip.rb +37 -0
- data/vendor/archaeopteryx/core_ext/array.rb +11 -0
- data/vendor/archaeopteryx/core_ext/struct.rb +7 -0
- data/vendor/archaeopteryx/drum.rb +24 -0
- data/vendor/archaeopteryx/live_hacks.rb +8 -0
- data/vendor/archaeopteryx/loop.rb +32 -0
- data/vendor/archaeopteryx/midi/clock.rb +23 -0
- data/vendor/archaeopteryx/midi/file_output/file_midi.rb +44 -0
- data/vendor/archaeopteryx/midi/note.rb +9 -0
- data/vendor/archaeopteryx/midi/practical_ruby_projects/core_foundation.rb +7 -0
- data/vendor/archaeopteryx/midi/practical_ruby_projects/core_midi.rb +16 -0
- data/vendor/archaeopteryx/midi/practical_ruby_projects/live_midi.rb +117 -0
- data/vendor/archaeopteryx/midi/practical_ruby_projects/no_midi_destinations.rb +8 -0
- data/vendor/archaeopteryx/midi/practical_ruby_projects/practical_ruby_projects.rb +30 -0
- data/vendor/archaeopteryx/midi/practical_ruby_projects/timer.rb +35 -0
- data/vendor/archaeopteryx/mix.rb +27 -0
- data/vendor/archaeopteryx/rhythm.rb +29 -0
- data/vendor/archaeopteryx/track.rb +16 -0
- metadata +113 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
niki (0.0.1)
|
5
|
+
midilib
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ffi (1.0.9)
|
11
|
+
ffi-coremidi (0.0.8)
|
12
|
+
ffi (>= 1.0)
|
13
|
+
midilib (2.0.0)
|
14
|
+
unimidi (0.2.1)
|
15
|
+
ffi-coremidi
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
ruby
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
niki!
|
22
|
+
unimidi
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Niki
|
2
|
+
|
3
|
+
Niki is a Ruby DSL to describe and play musical pieces.
|
4
|
+
|
5
|
+
It leverages Giles Bowkett's [Archaeopteryx](
|
6
|
+
https://github.com/gilesbowkett/archaeopteryx) to send MIDI output to other
|
7
|
+
programs accepting MIDI inputs (Ableton Live, Reason, Garage Band...), which
|
8
|
+
will actually play the song.
|
9
|
+
|
10
|
+
To see what the DSL looks like, take a look at the [example song](https://github.com/txus/niki/blob/master/examples/my_song.rb)
|
11
|
+
|
12
|
+
https://github.com/gilesbowkett/archaeopteryx/blob/master/db_drum_definition.rb
|
13
|
+
|
14
|
+
## Caveats
|
15
|
+
|
16
|
+
* It runs only in Ruby 1.8.7 (due to the dependency on Archaeopteryx) :(
|
17
|
+
* I've tested this only in Snow Leopard, but it should run in more platforms.
|
18
|
+
If your platform is not supported it's because Archaeopteryx
|
19
|
+
doesn't support it yet.
|
20
|
+
|
21
|
+
## Run the example song
|
22
|
+
|
23
|
+
First of all, get the example song files (both the Reason file and the Ruby file)
|
24
|
+
|
25
|
+
$ curl -o my_song.rns https://raw.github.com/txus/niki/master/examples/my_song.rns
|
26
|
+
$ curl -o my_song.rb https://raw.github.com/txus/niki/master/examples/my_song.rb
|
27
|
+
|
28
|
+
Now [download a demo version](
|
29
|
+
http://www.propellerheads.se/download/index.cfm?fuseaction=get_article&article=reason) of Propellerhead's
|
30
|
+
Reason 5 and open up `my_song.rns`.
|
31
|
+
|
32
|
+
### Configuring the midi interface
|
33
|
+
|
34
|
+
This is how you configure the midi interface in Mac OSX (it should not be
|
35
|
+
difficult in other operating systems):
|
36
|
+
|
37
|
+
* Make sure the IAC driver is activated in Mac OSX's Audio/MIDI Setup (inside the
|
38
|
+
MIDI section) (activate the "Device is online" checkbox if it is not)
|
39
|
+
* In this section, make sure at least one port is created (under the Ports
|
40
|
+
section). If not, add one clicking the + button.
|
41
|
+
* Go to the Reason options panel, go to Advanced, and set your first
|
42
|
+
MIDI bus (Bus A) to "IAC Driver IAC Bus 1" (the port you just created).
|
43
|
+
|
44
|
+
### Install the gem and run!
|
45
|
+
|
46
|
+
Now you should install the gem and run the example!
|
47
|
+
|
48
|
+
$ gem install niki
|
49
|
+
$ ruby my_song.rb
|
50
|
+
|
51
|
+
# TODO
|
52
|
+
|
53
|
+
* Tests + refactor
|
54
|
+
* Documentation
|
55
|
+
* Expand this README
|
data/examples/my_song.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'niki'
|
3
|
+
|
4
|
+
song = Niki::Song.new :tempo => 127 do
|
5
|
+
|
6
|
+
# Configure midi channels
|
7
|
+
channel :basses, 1
|
8
|
+
channel :drums, 10
|
9
|
+
channel :chords, 2
|
10
|
+
channel :melodies, 3
|
11
|
+
|
12
|
+
configure :drums do |drums|
|
13
|
+
drums[:kick] = c1
|
14
|
+
drums[:snare] = c1.sharp
|
15
|
+
drums[:hh] = g1
|
16
|
+
drums[:ohh] = g1.sharp
|
17
|
+
end
|
18
|
+
|
19
|
+
part :intro do
|
20
|
+
# Drums
|
21
|
+
2.times do
|
22
|
+
drum [:kick, :hh], 8
|
23
|
+
drum :hh, 8
|
24
|
+
drum [:snare, :hh], 8
|
25
|
+
drum :hh, 8
|
26
|
+
|
27
|
+
drum [:kick, :hh], 8
|
28
|
+
drum :hh, 8
|
29
|
+
drum [:snare, :hh], 8
|
30
|
+
drum [:kick, :hh], 8
|
31
|
+
|
32
|
+
drum :hh, 16
|
33
|
+
drum :hh, 16
|
34
|
+
drum :hh, 16
|
35
|
+
drum :hh, 16
|
36
|
+
drum [:snare, :hh], 8
|
37
|
+
drum :hh, 8
|
38
|
+
|
39
|
+
drum [:kick, :hh], 8
|
40
|
+
drum [:kick, :hh], 8
|
41
|
+
drum [:snare, :hh], 8
|
42
|
+
drum :ohh, 8
|
43
|
+
end
|
44
|
+
|
45
|
+
# Bass
|
46
|
+
bass f2, 4
|
47
|
+
|
48
|
+
bass f2, 8
|
49
|
+
bass e2, 8
|
50
|
+
bass f2, 8
|
51
|
+
bass e2, 8
|
52
|
+
bass f2, 8
|
53
|
+
|
54
|
+
bass e3, 4
|
55
|
+
bass c3, 4
|
56
|
+
bass b2, 8
|
57
|
+
bass c2, 8
|
58
|
+
bass c2, 8
|
59
|
+
bass g2, 4
|
60
|
+
|
61
|
+
bass a2, 4
|
62
|
+
|
63
|
+
bass a2, 8
|
64
|
+
bass g2, 8
|
65
|
+
bass a2, 8
|
66
|
+
bass g2, 8
|
67
|
+
bass a2, 8
|
68
|
+
|
69
|
+
bass e3, 4
|
70
|
+
bass c3, 4
|
71
|
+
bass b2, 8
|
72
|
+
bass c2, 8
|
73
|
+
bass c2, 8
|
74
|
+
bass g2, 4
|
75
|
+
end
|
76
|
+
|
77
|
+
part :intro_ending do
|
78
|
+
# Drums
|
79
|
+
drum [:kick, :hh], 8
|
80
|
+
drum :hh, 8
|
81
|
+
drum [:snare, :hh], 8
|
82
|
+
drum :hh, 8
|
83
|
+
|
84
|
+
drum [:kick, :hh], 8
|
85
|
+
drum :hh, 8
|
86
|
+
drum [:snare, :hh], 8
|
87
|
+
drum [:kick, :hh], 8
|
88
|
+
|
89
|
+
drum :hh, 16
|
90
|
+
drum :hh, 16
|
91
|
+
drum :hh, 16
|
92
|
+
drum :hh, 16
|
93
|
+
drum [:snare, :hh], 8
|
94
|
+
drum :hh, 8
|
95
|
+
|
96
|
+
drum [:kick, :hh], 8
|
97
|
+
drum [:kick, :hh], 8
|
98
|
+
drum [:snare, :hh], 8
|
99
|
+
drum :ohh, 8
|
100
|
+
|
101
|
+
# Drum break
|
102
|
+
drum [:kick, :hh], 8
|
103
|
+
drum :hh, 8
|
104
|
+
drum [:kick, :hh], 8
|
105
|
+
drum :hh, 8
|
106
|
+
drum [:kick, :hh], 8
|
107
|
+
drum :hh, 8
|
108
|
+
drum [:kick, :hh], 8
|
109
|
+
drum :hh, 8
|
110
|
+
|
111
|
+
drum [:snare, :ooh], 4
|
112
|
+
drum [:snare, :ooh], 4
|
113
|
+
drum :snare, 16
|
114
|
+
drum :snare, 16
|
115
|
+
drum :kick, 16
|
116
|
+
drum :kick, 16
|
117
|
+
drum :snare, 16
|
118
|
+
drum :snare, 16
|
119
|
+
drum [:snare, :ooh], 16
|
120
|
+
drum :snare, 16
|
121
|
+
|
122
|
+
# Bass
|
123
|
+
bass f2, 4
|
124
|
+
|
125
|
+
bass f2, 8
|
126
|
+
bass e2, 8
|
127
|
+
bass f2, 8
|
128
|
+
bass e2, 8
|
129
|
+
bass f2, 8
|
130
|
+
|
131
|
+
bass e3, 4
|
132
|
+
bass c3, 4
|
133
|
+
bass b2, 8
|
134
|
+
bass c2, 8
|
135
|
+
bass c2, 8
|
136
|
+
bass g2, 4
|
137
|
+
|
138
|
+
bass a2, 4
|
139
|
+
bass a2, 4
|
140
|
+
bass a2, 4
|
141
|
+
bass a2, 4
|
142
|
+
|
143
|
+
bass b2, 8
|
144
|
+
bass b2, 8
|
145
|
+
bass b2, 8
|
146
|
+
bass c2, 8
|
147
|
+
bass c2, 8
|
148
|
+
bass c2, 8
|
149
|
+
bass d3, 8
|
150
|
+
bass e3, 8
|
151
|
+
end
|
152
|
+
|
153
|
+
part :pre_chorus do
|
154
|
+
bass :from => :intro
|
155
|
+
drum :from => :intro
|
156
|
+
|
157
|
+
melody a2, 4
|
158
|
+
melody a2, 4
|
159
|
+
melody f3, 8
|
160
|
+
melody g3, 8
|
161
|
+
melody a3, 8
|
162
|
+
melody g3, 4
|
163
|
+
melody f3, 8
|
164
|
+
melody e3, 8
|
165
|
+
melody d3, 4
|
166
|
+
melody c3, 8
|
167
|
+
melody e3, 4
|
168
|
+
|
169
|
+
melody a2, 2
|
170
|
+
melody a2, 8
|
171
|
+
melody g2, 4
|
172
|
+
melody c2, 4
|
173
|
+
|
174
|
+
melody c3, 8
|
175
|
+
melody d3, 8
|
176
|
+
melody e3, 8
|
177
|
+
melody f3, 8
|
178
|
+
melody e3, 8
|
179
|
+
melody d3, 8
|
180
|
+
melody c3, 8
|
181
|
+
end
|
182
|
+
|
183
|
+
repeat :pre_chorus
|
184
|
+
|
185
|
+
part :chorus do
|
186
|
+
melody :from => :pre_chorus
|
187
|
+
|
188
|
+
# Bass
|
189
|
+
2.times do
|
190
|
+
bass f2, 8
|
191
|
+
bass f3, 8
|
192
|
+
bass f2, 8
|
193
|
+
bass f3, 8
|
194
|
+
end
|
195
|
+
bass c2, 8
|
196
|
+
bass c3, 8
|
197
|
+
bass c2, 8
|
198
|
+
bass c3, 8
|
199
|
+
|
200
|
+
bass g2, 8
|
201
|
+
bass g3, 8
|
202
|
+
bass f2, 8
|
203
|
+
bass d2, 8
|
204
|
+
|
205
|
+
2.times do
|
206
|
+
bass a1, 8
|
207
|
+
bass a2, 8
|
208
|
+
bass a1, 8
|
209
|
+
bass a2, 8
|
210
|
+
end
|
211
|
+
bass c1, 8
|
212
|
+
bass c2, 8
|
213
|
+
bass c1, 8
|
214
|
+
bass c2, 8
|
215
|
+
|
216
|
+
bass g1, 8
|
217
|
+
bass g2, 8
|
218
|
+
bass f1, 8
|
219
|
+
bass d2, 8
|
220
|
+
|
221
|
+
# Drums
|
222
|
+
7.times do
|
223
|
+
drum [:kick, :hh], 8
|
224
|
+
drum :hh, 8
|
225
|
+
drum [:snare, :kick, :hh], 8
|
226
|
+
drum :hh, 8
|
227
|
+
end
|
228
|
+
drum :kick, 8
|
229
|
+
drum :hh, 16
|
230
|
+
drum :hh, 16
|
231
|
+
drum [:snare, :kick], 16
|
232
|
+
drum :snare, 16
|
233
|
+
drum [:kick, :ohh], 8
|
234
|
+
|
235
|
+
# Chords
|
236
|
+
chord a3MIN(2), 2, :base => f3
|
237
|
+
chord a3MIN(2), 8, :base => f3
|
238
|
+
chord a3MIN(2), 8, :base => f3
|
239
|
+
chord a3MIN(2), 8, :base => f3
|
240
|
+
chord c3MAJ, 4
|
241
|
+
chord c3MAJ, 4
|
242
|
+
chord c3MAJ, 8
|
243
|
+
chord g3MAJ, 2
|
244
|
+
chord [a3, b3, e4]
|
245
|
+
chord c3MAJ, 4
|
246
|
+
chord c3MAJ, 4
|
247
|
+
chord g3MAJ, 8
|
248
|
+
chord g3MAJ, 8
|
249
|
+
chord g3MAJ, 8, :base => f4
|
250
|
+
chord g3MAJ, 8, :base => e4
|
251
|
+
end
|
252
|
+
|
253
|
+
repeat :chorus
|
254
|
+
|
255
|
+
part :outro do
|
256
|
+
drum :from => :intro
|
257
|
+
bass :from => :intro
|
258
|
+
chord :from => :chorus
|
259
|
+
end
|
260
|
+
|
261
|
+
part :outro_ending do
|
262
|
+
bass :from => :intro
|
263
|
+
chord :from => :chorus
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
song.play
|
Binary file
|
data/lib/niki.rb
ADDED
data/lib/niki/chords.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Niki
|
2
|
+
module Chords
|
3
|
+
|
4
|
+
(1..5).to_a.each do |octave|
|
5
|
+
|
6
|
+
%w(c d e f g a b).each_with_index do |c, idx|
|
7
|
+
rest = 0
|
8
|
+
if idx > 2
|
9
|
+
rest = 1
|
10
|
+
end
|
11
|
+
base = 24 + (12 * octave) + (idx * 2) - rest
|
12
|
+
|
13
|
+
define_method("#{c}#{octave}") do
|
14
|
+
base
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method("#{c}#{octave}MAJ") do |*args|
|
18
|
+
inversion = args.first || 1
|
19
|
+
case inversion
|
20
|
+
when 1
|
21
|
+
[base, base + 4, base + 7]
|
22
|
+
when 2
|
23
|
+
[base + 4, base + 7, base + 12]
|
24
|
+
when 3
|
25
|
+
[base + 7, base + 12, base + 16]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
define_method("#{c}#{octave}MIN") do |*args|
|
30
|
+
inversion = args.first || 1
|
31
|
+
case inversion
|
32
|
+
when 1
|
33
|
+
[base, base + 3, base + 7]
|
34
|
+
when 2
|
35
|
+
[base + 3, base + 7, base + 12]
|
36
|
+
when 3
|
37
|
+
[base + 7, base + 12, base + 15]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/niki/part.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Niki
|
2
|
+
class Part
|
3
|
+
attr_reader :name, :chords, :drums, :basses, :melodies
|
4
|
+
|
5
|
+
include Niki::Chords
|
6
|
+
|
7
|
+
def initialize(name, song, &block)
|
8
|
+
@name = name
|
9
|
+
@song = song
|
10
|
+
@drum_notes = @song.drum_notes
|
11
|
+
@chords = []
|
12
|
+
@drums = []
|
13
|
+
@basses = []
|
14
|
+
@melodies = []
|
15
|
+
self.instance_eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def chord note, duration = 1, options = {}
|
19
|
+
if note.is_a?(Hash) && note[:from]
|
20
|
+
copy_from_part(note[:from], :chords)
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
note.push options[:base]
|
25
|
+
@chords << [note, calculate_duration(duration)]
|
26
|
+
end
|
27
|
+
|
28
|
+
def drum note, duration = 1, options = {}
|
29
|
+
if note.is_a?(Hash) && note[:from]
|
30
|
+
copy_from_part(note[:from], :drums)
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
drum_note = [note].flatten.map do |n|
|
35
|
+
@drum_notes[n]
|
36
|
+
end
|
37
|
+
@drums << [drum_note, calculate_duration(duration)]
|
38
|
+
end
|
39
|
+
|
40
|
+
def bass note = nil, duration = 1, options = {}
|
41
|
+
if note.is_a?(Hash) && note[:from]
|
42
|
+
copy_from_part(note[:from], :basses)
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
@basses << [[note], calculate_duration(duration)]
|
47
|
+
end
|
48
|
+
|
49
|
+
def melody note = nil, duration = 1, options = {}
|
50
|
+
if note.is_a?(Hash) && note[:from]
|
51
|
+
copy_from_part(note[:from], :melodies)
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
@melodies << [[note], calculate_duration(duration)]
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def calculate_duration(duration)
|
61
|
+
1.to_f / ((@song.tempo.to_f/120) * duration/2)
|
62
|
+
end
|
63
|
+
|
64
|
+
def copy_from_part(name, type)
|
65
|
+
part = @song.get_part(name)
|
66
|
+
instance_variable_set(:"@#{type}", part.send(type))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/niki/song.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Niki
|
2
|
+
class Song
|
3
|
+
attr_reader :tempo, :drum_notes
|
4
|
+
|
5
|
+
include Niki::Chords
|
6
|
+
|
7
|
+
def initialize(options, &block)
|
8
|
+
@clock = Archaeopteryx::Midi::Clock.new(options[:tempo] || 127)
|
9
|
+
|
10
|
+
@midi = Archaeopteryx::Midi::PracticalRubyProjects::LiveMIDI.new(:clock => @clock )
|
11
|
+
|
12
|
+
@parts = []
|
13
|
+
@channel = {}
|
14
|
+
@drum_notes = {}
|
15
|
+
@tempo = options[:tempo]
|
16
|
+
self.instance_eval &block
|
17
|
+
end
|
18
|
+
|
19
|
+
def channel(instrument, number)
|
20
|
+
@channel[instrument] = number - 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def part(name, &block)
|
24
|
+
raise "#{name} is already defined!" if has_part?(name)
|
25
|
+
@parts << Part.new(name, self, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def configure(instrument, &block)
|
29
|
+
case instrument
|
30
|
+
when :drums
|
31
|
+
block.call @drum_notes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def repeat(name, options = {})
|
36
|
+
(options[:times] || 1).times do
|
37
|
+
@parts << get_part(name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def play
|
42
|
+
@parts.each do |part|
|
43
|
+
[:basses, :chords, :melodies, :drums].map do |instrument_name|
|
44
|
+
Thread.start do
|
45
|
+
play_part(part, instrument_name)
|
46
|
+
end
|
47
|
+
end.map { |thread| thread.join }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def play_part(part, instrument_name)
|
52
|
+
part.send(instrument_name).each do |instrument|
|
53
|
+
notes = [instrument.first].flatten.compact.map do |note|
|
54
|
+
Archaeopteryx::Midi::Note.create(
|
55
|
+
:channel => @channel[instrument_name],
|
56
|
+
:number => note,
|
57
|
+
:duration => instrument.last,
|
58
|
+
:velocity => 127)
|
59
|
+
end
|
60
|
+
notes.each do |note|
|
61
|
+
@midi.note_on(note)
|
62
|
+
end
|
63
|
+
sleep(instrument.last)
|
64
|
+
notes.each do |note|
|
65
|
+
@midi.note_off(note)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_part(name)
|
71
|
+
@parts.detect {|p| p.name == name }
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def has_part?(name)
|
77
|
+
!!@parts.map(&:name).detect {|part_name| part_name == name }
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/niki/version.rb
ADDED
data/niki.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "niki/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "niki"
|
7
|
+
s.version = Niki::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Josep M. Bach"]
|
10
|
+
s.email = ["josep.m.bach@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/txus/niki"
|
12
|
+
s.summary = %q{A DSL to describe and play structured musical pieces, i.e. songs}
|
13
|
+
s.description = %q{A DSL to describe and play structured musical pieces, i.e. songs}
|
14
|
+
|
15
|
+
s.rubyforge_project = "niki"
|
16
|
+
|
17
|
+
s.add_runtime_dependency 'midilib'
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib", "vendor"]
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
alias :L :lambda
|
2
|
+
|
3
|
+
%w{rubygems midilib/sequence midilib/consts}.each {|lib| require lib}
|
4
|
+
|
5
|
+
%w{archaeopteryx/core_ext/struct
|
6
|
+
archaeopteryx/core_ext/array
|
7
|
+
|
8
|
+
archaeopteryx/loop
|
9
|
+
archaeopteryx/drum
|
10
|
+
archaeopteryx/rhythm
|
11
|
+
archaeopteryx/mix
|
12
|
+
|
13
|
+
archaeopteryx/live_hacks
|
14
|
+
archaeopteryx/track
|
15
|
+
archaeopteryx/clip
|
16
|
+
|
17
|
+
archaeopteryx/midi/note
|
18
|
+
archaeopteryx/midi/clock
|
19
|
+
|
20
|
+
archaeopteryx/midi/file_output/file_midi
|
21
|
+
|
22
|
+
archaeopteryx/midi/practical_ruby_projects/no_midi_destinations
|
23
|
+
archaeopteryx/midi/practical_ruby_projects/core_midi
|
24
|
+
archaeopteryx/midi/practical_ruby_projects/core_foundation
|
25
|
+
archaeopteryx/midi/practical_ruby_projects/live_midi
|
26
|
+
archaeopteryx/midi/practical_ruby_projects/timer}.each do |archaeopteryx|
|
27
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../#{archaeopteryx}"
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
class Clip
|
3
|
+
attr_accessor :measures
|
4
|
+
def initialize(attributes)
|
5
|
+
@message = Message.create(attributes.merge(:midi_channel => 0,
|
6
|
+
:value => 127))
|
7
|
+
@measures = attributes[:measures]
|
8
|
+
end
|
9
|
+
def notes(measure)
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
def messages(measure)
|
13
|
+
0 == measure % @measures ? [@message] : []
|
14
|
+
# if 0 == @relative_measure % @measures
|
15
|
+
# [@message]
|
16
|
+
# else
|
17
|
+
# []
|
18
|
+
# @relative_measure += 1
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
def mutate(measure)
|
22
|
+
end
|
23
|
+
# def choose
|
24
|
+
# relative_measure = 1
|
25
|
+
# etc.
|
26
|
+
# end
|
27
|
+
def complete?
|
28
|
+
[true, false][rand(2)]
|
29
|
+
# current_measure == final_measure
|
30
|
+
# BUT! these need to be relative measures in this Clip, not absolute measures in the overall Loop
|
31
|
+
# possible that Loop should be called Engine or some crazy shit
|
32
|
+
# I guess relative_measure is really @current_measure
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# probably rename this to make it drum-specific
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Array
|
2
|
+
def random
|
3
|
+
self[rand(self.size)]
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
# the weird thing is, sometimes this method works and sometimes it doesn't. it happens with other
|
8
|
+
# libs as well as this one. wtf is going on there? gotta investigate that shit.
|
9
|
+
|
10
|
+
# oh fuck I think I know why. I require ActiveSupport in my IRB so I'm assuming Array#rand from
|
11
|
+
# AS is part of Ruby itself. ooops.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# this allows you to create Structs and Struct subclasses with pseudo-keyword options hashes
|
2
|
+
# rather than with long sequences of unnamed args. written by Michael Fellinger.
|
3
|
+
class Struct
|
4
|
+
def self.create(hash)
|
5
|
+
new(*hash.values_at(*members.map{|member| member.to_sym}))
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
class Drum
|
3
|
+
attr_accessor :note, :probabilities, :when, :next, :number_generator
|
4
|
+
def initialize(attributes)
|
5
|
+
%w{note probabilities when next number_generator}.each do |attribute|
|
6
|
+
eval("@#{attribute} = attributes[:#{attribute}]")
|
7
|
+
end
|
8
|
+
@queue = [attributes[:when]]
|
9
|
+
generate
|
10
|
+
end
|
11
|
+
def play?(beat)
|
12
|
+
@when[beat]
|
13
|
+
end
|
14
|
+
def generate
|
15
|
+
beats_on_which_to_play = []
|
16
|
+
@probabilities.each_with_index do |probability, index|
|
17
|
+
beats_on_which_to_play << index if @number_generator[] <= probability
|
18
|
+
end
|
19
|
+
@queue << L{|beat| beats_on_which_to_play.include? beat}
|
20
|
+
@when = @next[@queue]
|
21
|
+
end
|
22
|
+
alias :mutate :generate
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
class Loop
|
3
|
+
attr_reader :midi
|
4
|
+
def initialize(attributes)
|
5
|
+
%w{generator
|
6
|
+
measures
|
7
|
+
beats
|
8
|
+
clock
|
9
|
+
midi
|
10
|
+
evil_timer_offset_wtf
|
11
|
+
infinite}.each do |option|
|
12
|
+
eval("@#{option} = attributes[:#{option}]")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def generate_beats
|
16
|
+
(1..@measures).each do |measure|
|
17
|
+
@generator.mutate(measure)
|
18
|
+
(0..(@beats - 1)).each do |beat|
|
19
|
+
play @generator.notes(beat)
|
20
|
+
@clock.tick
|
21
|
+
end
|
22
|
+
end
|
23
|
+
if @midi.infinite?
|
24
|
+
@midi.timer.at((@clock.start + @clock.time) - @evil_timer_offset_wtf,
|
25
|
+
&(L{generate_beats}))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def play(music)
|
29
|
+
music.each {|note| @midi.play(note)}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# get singleton from std lib
|
2
|
+
module Archaeopteryx
|
3
|
+
module Midi
|
4
|
+
class Clock
|
5
|
+
attr_reader :time, :interval, :start
|
6
|
+
def initialize(bpm)
|
7
|
+
# assumes 16-step step sequencer, 4/4 beat, etc.
|
8
|
+
self.bpm = bpm
|
9
|
+
@start = Time.now.to_f
|
10
|
+
@time = 0
|
11
|
+
end
|
12
|
+
def bpm=(bpm)
|
13
|
+
seconds_in_a_minute = 60.0
|
14
|
+
beats_in_a_measure = 4.0
|
15
|
+
@interval = seconds_in_a_minute / bpm.to_f / beats_in_a_measure
|
16
|
+
end
|
17
|
+
def tick
|
18
|
+
@time += @interval
|
19
|
+
@time
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# a quick note about FAIL. FileMIDI is actually of the same unspecified superclass as LiveMIDI. both
|
2
|
+
# need new, clearer names, but the nonexistent abstract superclass is just a class which takes options
|
3
|
+
# for args and has a method called play() which takes a note argument.
|
4
|
+
|
5
|
+
include MIDI
|
6
|
+
|
7
|
+
# christ this should probably live in some kind of module or something
|
8
|
+
|
9
|
+
class FileMIDI
|
10
|
+
attr_accessor :filename
|
11
|
+
def initialize(options)
|
12
|
+
raise :hell unless options.is_a? Hash
|
13
|
+
@filename = options[:filename]
|
14
|
+
@clock = options[:clock]
|
15
|
+
@events = []
|
16
|
+
|
17
|
+
@sequence = MIDI::Sequence.new
|
18
|
+
@sequence.tracks << (@track = MIDI::Track.new(@sequence))
|
19
|
+
@track.events << Tempo.new(Tempo.bpm_to_mpq(options[:tempo])) if options[:tempo]
|
20
|
+
@track.events << MetaEvent.new(META_SEQ_NAME, options[:name]) if options[:name]
|
21
|
+
|
22
|
+
# I'm not sure if this is actually necessary (?)
|
23
|
+
@track.events << Controller.new(0, CC_VOLUME, 127)
|
24
|
+
@track.events << ProgramChange.new(0, 1, 0)
|
25
|
+
end
|
26
|
+
def infinite?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
def midilib_delta
|
30
|
+
# figuring this shit out was an epic fucking nightmare and to be honest I still have no idea why it works
|
31
|
+
((@sequence.note_to_delta("16th") / @clock.interval) * @clock.time).to_i
|
32
|
+
end
|
33
|
+
def play(note)
|
34
|
+
@track.merge [NoteOnEvent.new(note.channel,
|
35
|
+
note.number,
|
36
|
+
note.velocity,
|
37
|
+
midilib_delta)]
|
38
|
+
end
|
39
|
+
def write
|
40
|
+
File.open(@filename, 'wb') do |file|
|
41
|
+
@sequence.write(file)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CoreMIDI
|
2
|
+
require 'dl/import'
|
3
|
+
extend DL::Importable
|
4
|
+
|
5
|
+
dlload '/System/Library/Frameworks/CoreMIDI.framework/Versions/Current/CoreMIDI'
|
6
|
+
|
7
|
+
extern "int MIDIClientCreate(void *, void *, void *, void *)"
|
8
|
+
extern "int MIDIClientDispose(void *)"
|
9
|
+
extern "int MIDIGetNumberOfDestinations()"
|
10
|
+
extern "void * MIDIGetDestination(int)"
|
11
|
+
extern "int MIDIOutputPortCreate(void *, void *, void *)"
|
12
|
+
extern "void * MIDIPacketListInit(void *)"
|
13
|
+
# http://groups.google.com/group/ruby-midi/browse_thread/thread/85de6ea9373c57a4
|
14
|
+
extern "void * MIDIPacketListAdd(void *, int, void *, int, int, void*)"
|
15
|
+
extern "int MIDISend(void *, void *, void *)"
|
16
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
module Midi
|
3
|
+
module PracticalRubyProjects
|
4
|
+
class LiveMIDI
|
5
|
+
|
6
|
+
# this object is a gateway between the Objective-C extern-ed functions from the CoreMIDI API
|
7
|
+
# and good old-fashioned Ruby. As such some of the code gets weird. This is nearly all from
|
8
|
+
# Topher Cyll's wicked book referenced in the MIT license file (practical_ruby_projects.rb),
|
9
|
+
# but with some refactoring and modification.
|
10
|
+
|
11
|
+
include CoreMIDI
|
12
|
+
include CoreFoundation
|
13
|
+
|
14
|
+
attr_reader :interval # this is a totally misleading variable name! real interval lives on Clock
|
15
|
+
attr_reader :timer
|
16
|
+
ON = 0x90
|
17
|
+
OFF = 0x80
|
18
|
+
PC = 0xC0 # program change, I think; not actually useable in Propellerhead Reason v3
|
19
|
+
CONTROLLER = 0xB0 # arbitrary controller message
|
20
|
+
|
21
|
+
def infinite?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_code
|
26
|
+
"LiveMIDI.new(:clock => @clock = attributes[:clock], :logging => false)"
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(options)
|
30
|
+
@clock = options[:clock]
|
31
|
+
@logging = options[:logging]
|
32
|
+
@midi_destination = options[:midi_destination] || 0
|
33
|
+
if @logging
|
34
|
+
puts <<LOG_PLAYBACK
|
35
|
+
require 'lib/archaeopteryx'
|
36
|
+
@midi = #{self.to_code}
|
37
|
+
LOG_PLAYBACK
|
38
|
+
end
|
39
|
+
@interval = 60.0/120 # this is just a polling interval for the Thread - not a musical one
|
40
|
+
@timer = Timer.new(@interval/1000)
|
41
|
+
open
|
42
|
+
end
|
43
|
+
|
44
|
+
def play(midi_note, on_time = @clock.time)
|
45
|
+
if @logging
|
46
|
+
puts "@midi.play(#{midi_note.to_code}, #{on_time})"
|
47
|
+
end
|
48
|
+
on_time += @clock.start
|
49
|
+
@timer.at(on_time) {note_on(midi_note)}
|
50
|
+
@timer.at(on_time + midi_note.duration) {note_off(midi_note)}
|
51
|
+
end
|
52
|
+
|
53
|
+
def send(message)
|
54
|
+
send_controller_message(message.midi_channel, message.controller_number, message.value)
|
55
|
+
end
|
56
|
+
def send_controller_message(midi_channel, controller_number, value, on_time = @clock.time)
|
57
|
+
on_time += @clock.start
|
58
|
+
puts "scheduling #{controller_number} for #{on_time}" if @logging
|
59
|
+
@timer.at(on_time) do
|
60
|
+
control(midi_channel, controller_number, value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def open
|
65
|
+
client_name = CoreFoundation.cFStringCreateWithCString(nil, "RubyMIDI", 0)
|
66
|
+
@client = DL::PtrData.new(nil)
|
67
|
+
CoreMIDI.mIDIClientCreate(client_name, nil, nil, @client.ref)
|
68
|
+
|
69
|
+
port_name = CoreFoundation.cFStringCreateWithCString(nil, "Output", 0)
|
70
|
+
@outport = DL::PtrData.new(nil)
|
71
|
+
CoreMIDI.mIDIOutputPortCreate(@client, port_name, @outport.ref)
|
72
|
+
|
73
|
+
number_of_destinations = CoreMIDI.mIDIGetNumberOfDestinations()
|
74
|
+
raise NoMIDIDestinations if number_of_destinations < 1
|
75
|
+
@destination = CoreMIDI.mIDIGetDestination(@midi_destination)
|
76
|
+
end
|
77
|
+
|
78
|
+
def close
|
79
|
+
CoreMIDI.mIDIClientDispose(@client)
|
80
|
+
end
|
81
|
+
|
82
|
+
def clear
|
83
|
+
@timer.flush
|
84
|
+
end
|
85
|
+
|
86
|
+
def message(*args)
|
87
|
+
format = "C" * args.size
|
88
|
+
bytes = args.pack(format).to_ptr
|
89
|
+
packet_list = DL.malloc(256)
|
90
|
+
packet_ptr = CoreMIDI.mIDIPacketListInit(packet_list)
|
91
|
+
# http://groups.google.com/group/ruby-midi/browse_thread/thread/85de6ea9373c57a4
|
92
|
+
packet_ptr = CoreMIDI.mIDIPacketListAdd(packet_list, 256, packet_ptr, 0, args.size, bytes)
|
93
|
+
CoreMIDI.mIDISend(@outport, @destination, packet_list)
|
94
|
+
end
|
95
|
+
|
96
|
+
def note_on(midi_note)
|
97
|
+
message(ON | midi_note.channel, midi_note.number, midi_note.velocity)
|
98
|
+
end
|
99
|
+
|
100
|
+
def note_off(midi_note)
|
101
|
+
message(OFF | midi_note.channel, midi_note.number, midi_note.velocity)
|
102
|
+
end
|
103
|
+
|
104
|
+
def program_change(channel, preset)
|
105
|
+
message(PC | channel, preset)
|
106
|
+
end
|
107
|
+
|
108
|
+
def pulse(channel, controller_id, value)
|
109
|
+
# puts "sending now: #{Time.now.to_f}" if @logging
|
110
|
+
# puts "#{[channel, controller_id, value].inspect}" if @logging
|
111
|
+
message(CONTROLLER | channel, controller_id, value)
|
112
|
+
end
|
113
|
+
alias :control :pulse
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
module Midi
|
3
|
+
# most of this code comes from "Practical Ruby Projects" by Topher Cyll, published by
|
4
|
+
# APress, which is a pretty bad-ass book. here's the license:
|
5
|
+
#
|
6
|
+
# The MIT License
|
7
|
+
#
|
8
|
+
# Copyright (c) 2006, 2007 Topher Cyll
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
+
# of this software and associated documentation files (the "Software"), to deal
|
12
|
+
# in the Software without restriction, including without limitation the rights
|
13
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
+
# copies of the Software, and to permit persons to whom the Software is
|
15
|
+
# furnished to do so, subject to the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be included in
|
18
|
+
# all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
26
|
+
# THE SOFTWARE.
|
27
|
+
module PracticalRubyProjects
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
module Midi
|
3
|
+
module PracticalRubyProjects
|
4
|
+
class Timer
|
5
|
+
def initialize(resolution)
|
6
|
+
@resolution = resolution
|
7
|
+
@queue = []
|
8
|
+
|
9
|
+
Thread.new do
|
10
|
+
while true
|
11
|
+
dispatch
|
12
|
+
sleep(@resolution)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def flush
|
18
|
+
@queue = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def at(time, &block)
|
22
|
+
time = time.to_f if time.kind_of?(Time)
|
23
|
+
@queue.push [time, block]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def dispatch
|
28
|
+
now = Time.now.to_f
|
29
|
+
ready, @queue = @queue.partition {|time, proc| time <= now}
|
30
|
+
ready.each {|time, proc| proc.call(time)}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
class Mix
|
3
|
+
attr_accessor :rhythms
|
4
|
+
def initialize(attributes)
|
5
|
+
@rhythms = attributes[:rhythms]
|
6
|
+
end
|
7
|
+
def notes(beat)
|
8
|
+
notes = []
|
9
|
+
@rhythms.each do |rhythm|
|
10
|
+
notes << rhythm.notes(beat)
|
11
|
+
end
|
12
|
+
notes.flatten
|
13
|
+
end
|
14
|
+
def messages(measure) # obviously these should be refactored to inject()s
|
15
|
+
messages = []
|
16
|
+
@rhythms.each do |rhythm|
|
17
|
+
messages << rhythm.messages(measure)
|
18
|
+
end
|
19
|
+
messages.flatten
|
20
|
+
end
|
21
|
+
def mutate(measure)
|
22
|
+
@rhythms.each {|rhythm| rhythm.mutate(measure)}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Mix nearly identical to Rhythm
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
class Rhythm
|
3
|
+
def initialize(attributes)
|
4
|
+
@mutation = attributes[:mutation]
|
5
|
+
@drumfile = attributes[:drumfile]
|
6
|
+
reload
|
7
|
+
end
|
8
|
+
def reload
|
9
|
+
puts "\a" # flash the screen ; only valid on my box and similarly configured machines!
|
10
|
+
@drums = eval(File.read(@drumfile))
|
11
|
+
end
|
12
|
+
def notes(beat)
|
13
|
+
(@drums.collect do |drum|
|
14
|
+
drum.note if drum.play? beat
|
15
|
+
end).compact
|
16
|
+
end
|
17
|
+
def messages(beat)
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
def mutate(measure)
|
21
|
+
if @mutation[measure]
|
22
|
+
reload # reloading can kill mutations!
|
23
|
+
@drums.each {|drum| drum.mutate}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# probably rename this to make it drum-specific
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Archaeopteryx
|
2
|
+
class Track < Mix
|
3
|
+
attr_accessor :current
|
4
|
+
def initialize(attributes)
|
5
|
+
@rhythms = attributes[:rhythms]
|
6
|
+
@measure = 1
|
7
|
+
new_clip
|
8
|
+
end
|
9
|
+
def new_clip
|
10
|
+
@current = @rhythms.random
|
11
|
+
end
|
12
|
+
def messages(measure)
|
13
|
+
@current.messages(measure)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: niki
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Josep M. Bach
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-15 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: midilib
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: A DSL to describe and play structured musical pieces, i.e. songs
|
35
|
+
email:
|
36
|
+
- josep.m.bach@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- Gemfile.lock
|
47
|
+
- Rakefile
|
48
|
+
- Readme.md
|
49
|
+
- examples/my_song.rb
|
50
|
+
- examples/my_song.rns
|
51
|
+
- lib/core_ext/array.rb
|
52
|
+
- lib/core_ext/fixnum.rb
|
53
|
+
- lib/niki.rb
|
54
|
+
- lib/niki/chords.rb
|
55
|
+
- lib/niki/part.rb
|
56
|
+
- lib/niki/song.rb
|
57
|
+
- lib/niki/version.rb
|
58
|
+
- niki.gemspec
|
59
|
+
- vendor/archaeopteryx/archaeopteryx.rb
|
60
|
+
- vendor/archaeopteryx/clip.rb
|
61
|
+
- vendor/archaeopteryx/core_ext/array.rb
|
62
|
+
- vendor/archaeopteryx/core_ext/struct.rb
|
63
|
+
- vendor/archaeopteryx/drum.rb
|
64
|
+
- vendor/archaeopteryx/live_hacks.rb
|
65
|
+
- vendor/archaeopteryx/loop.rb
|
66
|
+
- vendor/archaeopteryx/midi/clock.rb
|
67
|
+
- vendor/archaeopteryx/midi/file_output/file_midi.rb
|
68
|
+
- vendor/archaeopteryx/midi/note.rb
|
69
|
+
- vendor/archaeopteryx/midi/practical_ruby_projects/core_foundation.rb
|
70
|
+
- vendor/archaeopteryx/midi/practical_ruby_projects/core_midi.rb
|
71
|
+
- vendor/archaeopteryx/midi/practical_ruby_projects/live_midi.rb
|
72
|
+
- vendor/archaeopteryx/midi/practical_ruby_projects/no_midi_destinations.rb
|
73
|
+
- vendor/archaeopteryx/midi/practical_ruby_projects/practical_ruby_projects.rb
|
74
|
+
- vendor/archaeopteryx/midi/practical_ruby_projects/timer.rb
|
75
|
+
- vendor/archaeopteryx/mix.rb
|
76
|
+
- vendor/archaeopteryx/rhythm.rb
|
77
|
+
- vendor/archaeopteryx/track.rb
|
78
|
+
homepage: http://github.com/txus/niki
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
- vendor
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project: niki
|
108
|
+
rubygems_version: 1.8.6
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: A DSL to describe and play structured musical pieces, i.e. songs
|
112
|
+
test_files: []
|
113
|
+
|