niki 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile +1 -3
- data/Gemfile.lock +4 -4
- data/Readme.md +12 -13
- data/lib/niki.rb +0 -1
- data/lib/niki/chords.rb +1 -0
- data/lib/niki/song.rb +33 -18
- data/lib/niki/version.rb +1 -1
- data/niki.gemspec +2 -2
- metadata +35 -69
- data/vendor/archaeopteryx/archaeopteryx.rb +0 -28
- data/vendor/archaeopteryx/clip.rb +0 -37
- data/vendor/archaeopteryx/core_ext/array.rb +0 -11
- data/vendor/archaeopteryx/core_ext/struct.rb +0 -7
- data/vendor/archaeopteryx/drum.rb +0 -24
- data/vendor/archaeopteryx/live_hacks.rb +0 -8
- data/vendor/archaeopteryx/loop.rb +0 -32
- data/vendor/archaeopteryx/midi/clock.rb +0 -23
- data/vendor/archaeopteryx/midi/file_output/file_midi.rb +0 -44
- data/vendor/archaeopteryx/midi/note.rb +0 -9
- data/vendor/archaeopteryx/midi/practical_ruby_projects/core_foundation.rb +0 -7
- data/vendor/archaeopteryx/midi/practical_ruby_projects/core_midi.rb +0 -16
- data/vendor/archaeopteryx/midi/practical_ruby_projects/live_midi.rb +0 -117
- data/vendor/archaeopteryx/midi/practical_ruby_projects/no_midi_destinations.rb +0 -8
- data/vendor/archaeopteryx/midi/practical_ruby_projects/practical_ruby_projects.rb +0 -30
- data/vendor/archaeopteryx/midi/practical_ruby_projects/timer.rb +0 -35
- data/vendor/archaeopteryx/mix.rb +0 -27
- data/vendor/archaeopteryx/rhythm.rb +0 -29
- data/vendor/archaeopteryx/track.rb +0 -16
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
niki (0.0
|
5
|
-
|
4
|
+
niki (0.1.0)
|
5
|
+
unimidi
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: http://rubygems.org/
|
@@ -10,7 +10,7 @@ GEM
|
|
10
10
|
ffi (1.0.9)
|
11
11
|
ffi-coremidi (0.0.8)
|
12
12
|
ffi (>= 1.0)
|
13
|
-
|
13
|
+
rake (0.9.2)
|
14
14
|
unimidi (0.2.1)
|
15
15
|
ffi-coremidi
|
16
16
|
|
@@ -19,4 +19,4 @@ PLATFORMS
|
|
19
19
|
|
20
20
|
DEPENDENCIES
|
21
21
|
niki!
|
22
|
-
|
22
|
+
rake
|
data/Readme.md
CHANGED
@@ -2,21 +2,22 @@
|
|
2
2
|
|
3
3
|
Niki is a Ruby DSL to describe and play musical pieces.
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
Its name comes from the [world-famous band Niki](http://niki.fm) which I happen
|
6
|
+
to be part of :D
|
7
|
+
|
8
|
+
It leverages Ari Russo's [Unimidi](
|
9
|
+
https://github.com/arirusso/unimidi) to send MIDI output to other
|
7
10
|
programs accepting MIDI inputs (Ableton Live, Reason, Garage Band...), which
|
8
11
|
will actually play the song.
|
9
12
|
|
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
|
+
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).
|
13
14
|
|
14
|
-
|
15
|
+
Thanks to Unimidi, it works on all major platforms (Linux, OSX and Windows). To
|
16
|
+
ensure Niki will run on your platform (I've personally tested it with OSX only),
|
17
|
+
please look at the [Unimidi repo at Github](https://github.com/arirusso/unimidi)
|
18
|
+
for instructions about your specific platform.
|
15
19
|
|
16
|
-
|
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
|
+
Currently niki runs (at least) on Ruby MRI versions 1.8.7 and 1.9.2.
|
20
21
|
|
21
22
|
## Run the example song
|
22
23
|
|
@@ -36,10 +37,8 @@ difficult in other operating systems):
|
|
36
37
|
|
37
38
|
* Make sure the IAC driver is activated in Mac OSX's Audio/MIDI Setup (inside the
|
38
39
|
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
40
|
* Go to the Reason options panel, go to Advanced, and set your first
|
42
|
-
MIDI bus (Bus A) to "IAC Driver IAC Bus 1"
|
41
|
+
MIDI bus (Bus A) to "IAC Driver IAC Bus 1" or something similar.
|
43
42
|
|
44
43
|
### Install the gem and run!
|
45
44
|
|
data/lib/niki.rb
CHANGED
data/lib/niki/chords.rb
CHANGED
data/lib/niki/song.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
require 'unimidi'
|
2
|
+
|
1
3
|
module Niki
|
2
4
|
class Song
|
3
5
|
attr_reader :tempo, :drum_notes
|
4
6
|
|
5
7
|
include Niki::Chords
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@midi = Archaeopteryx::Midi::PracticalRubyProjects::LiveMIDI.new(:clock => @clock )
|
9
|
+
NOTE_ON = 0x90
|
10
|
+
NOTE_OFF = 0x80
|
11
11
|
|
12
|
+
def initialize(options, &block)
|
13
|
+
@midi = UniMIDI::Output.first
|
12
14
|
@parts = []
|
13
15
|
@channel = {}
|
14
16
|
@drum_notes = {}
|
@@ -45,24 +47,27 @@ module Niki
|
|
45
47
|
play_part(part, instrument_name)
|
46
48
|
end
|
47
49
|
end.map { |thread| thread.join }
|
50
|
+
|
51
|
+
[:basses, :chords, :melodies, :drums].each do |instrument_name|
|
52
|
+
reset(instrument_name)
|
53
|
+
end
|
48
54
|
end
|
49
55
|
end
|
50
56
|
|
51
57
|
def play_part(part, instrument_name)
|
52
|
-
part.send(instrument_name).
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
@midi.note_off(note)
|
58
|
+
return if part.send(instrument_name).length.zero?
|
59
|
+
channel = @channel[instrument_name]
|
60
|
+
|
61
|
+
@midi.open do |out|
|
62
|
+
part.send(instrument_name).each do |instrument|
|
63
|
+
notes = [instrument.first].flatten.compact
|
64
|
+
notes.each do |note|
|
65
|
+
out.puts NOTE_ON + channel, note, 100
|
66
|
+
end
|
67
|
+
sleep(instrument.last)
|
68
|
+
notes.each do |note|
|
69
|
+
out.puts NOTE_OFF + channel, note, 100
|
70
|
+
end
|
66
71
|
end
|
67
72
|
end
|
68
73
|
end
|
@@ -77,5 +82,15 @@ module Niki
|
|
77
82
|
!!@parts.map(&:name).detect {|part_name| part_name == name }
|
78
83
|
end
|
79
84
|
|
85
|
+
def reset(instrument_name)
|
86
|
+
channel = @channel[instrument_name]
|
87
|
+
@midi.open do |out|
|
88
|
+
# Reset all notes
|
89
|
+
95.times do |i|
|
90
|
+
out.puts NOTE_OFF + channel, i, 100
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
80
95
|
end
|
81
96
|
end
|
data/lib/niki/version.rb
CHANGED
data/niki.gemspec
CHANGED
@@ -14,10 +14,10 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.rubyforge_project = "niki"
|
16
16
|
|
17
|
-
s.add_runtime_dependency '
|
17
|
+
s.add_runtime_dependency 'unimidi'
|
18
18
|
|
19
19
|
s.files = `git ls-files`.split("\n")
|
20
20
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
21
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
-
s.require_paths = ["lib"
|
22
|
+
s.require_paths = ["lib"]
|
23
23
|
end
|
metadata
CHANGED
@@ -1,46 +1,35 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: niki
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 1
|
10
|
-
version: 0.0.1
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Josep M. Bach
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
21
|
-
name:
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
12
|
+
date: 2011-08-16 00:00:00.000000000 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: unimidi
|
17
|
+
requirement: &2154214760 !ruby/object:Gem::Requirement
|
24
18
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
segments:
|
30
|
-
- 0
|
31
|
-
version: "0"
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
32
23
|
type: :runtime
|
33
|
-
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2154214760
|
34
26
|
description: A DSL to describe and play structured musical pieces, i.e. songs
|
35
|
-
email:
|
27
|
+
email:
|
36
28
|
- josep.m.bach@gmail.com
|
37
29
|
executables: []
|
38
|
-
|
39
30
|
extensions: []
|
40
|
-
|
41
31
|
extra_rdoc_files: []
|
42
|
-
|
43
|
-
files:
|
32
|
+
files:
|
44
33
|
- .gitignore
|
45
34
|
- Gemfile
|
46
35
|
- Gemfile.lock
|
@@ -56,58 +45,35 @@ files:
|
|
56
45
|
- lib/niki/song.rb
|
57
46
|
- lib/niki/version.rb
|
58
47
|
- niki.gemspec
|
59
|
-
|
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
|
48
|
+
has_rdoc: true
|
78
49
|
homepage: http://github.com/txus/niki
|
79
50
|
licenses: []
|
80
|
-
|
81
51
|
post_install_message:
|
82
52
|
rdoc_options: []
|
83
|
-
|
84
|
-
require_paths:
|
53
|
+
require_paths:
|
85
54
|
- lib
|
86
|
-
|
87
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
56
|
none: false
|
89
|
-
requirements:
|
90
|
-
- -
|
91
|
-
- !ruby/object:Gem::Version
|
92
|
-
|
93
|
-
segments:
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
segments:
|
94
62
|
- 0
|
95
|
-
|
96
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
hash: -752574128219649690
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
65
|
none: false
|
98
|
-
requirements:
|
99
|
-
- -
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
|
102
|
-
segments:
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
segments:
|
103
71
|
- 0
|
104
|
-
|
72
|
+
hash: -752574128219649690
|
105
73
|
requirements: []
|
106
|
-
|
107
74
|
rubyforge_project: niki
|
108
|
-
rubygems_version: 1.
|
75
|
+
rubygems_version: 1.6.2
|
109
76
|
signing_key:
|
110
77
|
specification_version: 3
|
111
78
|
summary: A DSL to describe and play structured musical pieces, i.e. songs
|
112
79
|
test_files: []
|
113
|
-
|
@@ -1,28 +0,0 @@
|
|
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
|
@@ -1,37 +0,0 @@
|
|
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
|
@@ -1,11 +0,0 @@
|
|
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.
|
@@ -1,7 +0,0 @@
|
|
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
|
@@ -1,24 +0,0 @@
|
|
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
|
@@ -1,32 +0,0 @@
|
|
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
|
@@ -1,23 +0,0 @@
|
|
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
|
@@ -1,44 +0,0 @@
|
|
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
|
@@ -1,16 +0,0 @@
|
|
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
|
@@ -1,117 +0,0 @@
|
|
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
|
@@ -1,30 +0,0 @@
|
|
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
|
@@ -1,35 +0,0 @@
|
|
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
|
data/vendor/archaeopteryx/mix.rb
DELETED
@@ -1,27 +0,0 @@
|
|
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
|
@@ -1,29 +0,0 @@
|
|
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
|
@@ -1,16 +0,0 @@
|
|
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
|