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 CHANGED
@@ -1,3 +1,4 @@
1
1
  pkg/*
2
2
  *.gem
3
3
  .bundle
4
+ *.rbc
data/Gemfile CHANGED
@@ -2,6 +2,4 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in niki.gemspec
4
4
  gemspec
5
-
6
- gem 'unimidi'
7
- # gem 'midiator', :path => '../midiator'
5
+ gem 'rake'
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- niki (0.0.1)
5
- midilib
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
- midilib (2.0.0)
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
- unimidi
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
- It leverages Giles Bowkett's [Archaeopteryx](
6
- https://github.com/gilesbowkett/archaeopteryx) to send MIDI output to other
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
- ## Caveats
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
- * 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
+ 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" (the port you just created).
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
 
@@ -1,4 +1,3 @@
1
- require File.expand_path(File.dirname(__FILE__)) + "/../vendor/archaeopteryx/archaeopteryx"
2
1
  require 'core_ext/fixnum'
3
2
  require 'core_ext/array'
4
3
  require 'niki/chords'
@@ -40,5 +40,6 @@ module Niki
40
40
  end
41
41
 
42
42
  end
43
+
43
44
  end
44
45
  end
@@ -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
- def initialize(options, &block)
8
- @clock = Archaeopteryx::Midi::Clock.new(options[:tempo] || 127)
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).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)
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
@@ -1,3 +1,3 @@
1
1
  module Niki
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -14,10 +14,10 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "niki"
16
16
 
17
- s.add_runtime_dependency 'midilib'
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", "vendor"]
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
- hash: 29
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
- 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
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
- hash: 3
29
- segments:
30
- - 0
31
- version: "0"
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
32
23
  type: :runtime
33
- version_requirements: *id001
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
- - 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
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
- - vendor
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
- hash: 3
93
- segments:
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ segments:
94
62
  - 0
95
- version: "0"
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
- hash: 3
102
- segments:
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ segments:
103
71
  - 0
104
- version: "0"
72
+ hash: -752574128219649690
105
73
  requirements: []
106
-
107
74
  rubyforge_project: niki
108
- rubygems_version: 1.8.6
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,8 +0,0 @@
1
- class Message < Struct.new(:midi_channel, :controller_number, :value)
2
- end
3
-
4
- class TapTempo
5
- def midi_channel; 15 ; end
6
- def controller_number ; 7 ; end
7
- def value ; 127 ; end
8
- 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,9 +0,0 @@
1
- module Archaeopteryx
2
- module Midi
3
- class Note < Struct.new(:channel, :number, :duration, :velocity)
4
- def to_code
5
- "Note.new(#{self.channel}, #{self.number}, #{self.duration}, #{self.velocity})"
6
- end
7
- end
8
- end
9
- end
@@ -1,7 +0,0 @@
1
- module CoreFoundation
2
- require 'dl/import'
3
- extend DL::Importable
4
- dlload '/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation'
5
-
6
- extern "void * CFStringCreateWithCString (void *, char *, int)"
7
- 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,8 +0,0 @@
1
- module Archaeopteryx
2
- module Midi
3
- module PracticalRubyProjects
4
- class NoMIDIDestinations < Exception
5
- end
6
- end
7
- end
8
- 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
@@ -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