diamond 0.5.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.
@@ -0,0 +1,165 @@
1
+ module Diamond
2
+
3
+ # Enable the instrument to use MIDI
4
+ module MIDI
5
+
6
+ # Methods dealing with MIDI input
7
+ module Input
8
+
9
+ def self.included(base)
10
+ base.send(:extend, Forwardable)
11
+ base.send(:def_delegators, :@midi,
12
+ :input,
13
+ :inputs,
14
+ :omni_on,
15
+ :rx_channel,
16
+ :receive_channel,
17
+ :rx_channel=,
18
+ :receive_channel=)
19
+ end
20
+
21
+ # Add MIDI input notes
22
+ # @param [Array<MIDIMessage>, MIDIMessage, *MIDIMessage] args
23
+ # @return [Array<MIDIMessage>]
24
+ def add(*args)
25
+ @midi.input << args
26
+ end
27
+ alias_method :<<, :add
28
+
29
+ # Add note offs to cancel input
30
+ # @param [Array<MIDIMessage>, MIDIMessage, *MIDIMessage] args
31
+ # @return [Array<MIDIMessage>]
32
+ def remove(*args)
33
+ messages = MIDIInstrument::Message.to_note_offs(*args)
34
+ @midi.input.add(messages.compact)
35
+ end
36
+
37
+ # Initialize adding and removing MIDI notes from the sequence
38
+ # @param [Sequence] sequence
39
+ # @return [Boolean]
40
+ def enable_note_control(sequence)
41
+ @midi.input.receive(:class => MIDIMessage::NoteOn) do |event|
42
+ message = event[:message]
43
+ if @midi.input.channel.nil? || @midi.input.channel == message.channel
44
+ puts "[DEBUG] MIDI: add note from input #{message.name} channel: #{message.channel}" if @debug
45
+ sequence.add(message)
46
+ end
47
+ end
48
+ @midi.input.receive(:class => MIDIMessage::NoteOff) do |event|
49
+ message = event[:message]
50
+ if @midi.input.channel.nil? || @midi.input.channel == message.channel
51
+ puts "[DEBUG] MIDI: remove note from input #{message.name} channel: #{message.channel}" if @debug
52
+ sequence.remove(message)
53
+ end
54
+ end
55
+ true
56
+ end
57
+
58
+ # Initialize a user-defined map of control change messages
59
+ # @param [SequenceParameters] parameters
60
+ # @param [Array<Hash>] map
61
+ # @return [Boolean]
62
+ def enable_parameter_control(parameters, map)
63
+ from_range = 0..127
64
+ @midi.input.receive(:class => MIDIMessage::ControlChange) do |event|
65
+ message = event[:message]
66
+ if @midi.input.channel.nil? || @midi.input.channel == message.channel
67
+ index = message.index
68
+ mapping = map.find { |mapping| mapping[:index] == index }
69
+ property = mapping[:property]
70
+ to_range = SequenceParameters::RANGE[property]
71
+ value = message.value
72
+ value = Scale.transform(value).from(from_range).to(to_range)
73
+ puts "[DEBUG] MIDI: #{property}= #{value} channel: #{message.channel}" if @debug
74
+ parameters.send("#{property}=", value)
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # @param [Array<UniMIDI::Input>] inputs
82
+ # @param [Hash] options
83
+ # @option options [Fixnum] :channel The receive channel (also: :rx_channel)
84
+ def initialize_input(inputs, options = {})
85
+ @midi.input.devices.concat(inputs)
86
+ @midi.input.channel = options[:receive_channel]
87
+ end
88
+
89
+ end
90
+
91
+ # Methods dealing with MIDI output
92
+ module Output
93
+
94
+ def self.included(base)
95
+ base.send(:extend, Forwardable)
96
+ base.send(:def_delegators, :@midi,
97
+ :mute,
98
+ :mute=,
99
+ :output,
100
+ :outputs,
101
+ :toggle_mute,
102
+ :tx_channel,
103
+ :transmit_channel,
104
+ :tx_channel=,
105
+ :transmit_channel=)
106
+ end
107
+
108
+ # Initialize MIDI output, enabling the sequencer to emit notes
109
+ # @param [Sequencer::Core] sequencer
110
+ # @return [Boolean]
111
+ def enable_output(sequencer)
112
+ sequencer.event.perform << proc do |bucket|
113
+ unless bucket.empty?
114
+ if @debug
115
+ bucket.each do |message|
116
+ puts "[DEBUG] MIDI: output #{message.name} channel: #{message.channel}"
117
+ end
118
+ end
119
+ @midi.output.puts(bucket)
120
+ end
121
+ end
122
+ sequencer.event.stop << proc { emit_pending_note_offs }
123
+ true
124
+ end
125
+
126
+ private
127
+
128
+ # Initialize MIDI output
129
+ # @param [Array<UniMIDI::Output>] outputs
130
+ # @param [Hash] options
131
+ # @option options [Fixnum] :tx_channel The transmit channel
132
+ def initialize_output(outputs, options = {})
133
+ @midi.output.devices.concat(outputs)
134
+ @midi.output.channel = options[:transmit_channel]
135
+ end
136
+
137
+ end
138
+
139
+ # An access point for dealing with all MIDI functionality for the instrument
140
+ class Node
141
+
142
+ include Input
143
+ include Output
144
+
145
+ # Initialize MIDI input and output
146
+ # @param [Hash] devices
147
+ # @param [Hash] options
148
+ # @option options [Fixnum] :channel The receive channel (also: :rx_channel)
149
+ # @option options [Fixnum] :tx_channel The transmit channel
150
+ def initialize(devices, options = {})
151
+ @debug = options.fetch(:debug, false)
152
+ @midi = MIDIInstrument::Node.new
153
+ initialize_input(devices[:input], options)
154
+ initialize_output(devices[:output], options)
155
+ end
156
+
157
+ end
158
+
159
+ # Shortcut to Diamond::MIDI::Node.new
160
+ def self.new(*args)
161
+ Node.new(*args)
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,102 @@
1
+ module Diamond
2
+
3
+ # Enable the instrument to use OSC
4
+ module OSC
5
+
6
+ # An access point for dealing with all OSC functionality for the instrument
7
+ class Node
8
+
9
+ # @param [Hash] options
10
+ # @option options [Boolean] :debug Whether to send debug output
11
+ # @option options [Fixnum] :server_port The port to listen on (default: 8000)
12
+ def initialize(options = {})
13
+ @debug = options.fetch(:debug, false)
14
+ port = options.fetch(:server_port, 8000)
15
+ @server = ::OSC::EMServer.new(port)
16
+ end
17
+
18
+ # Enable controlling the instrument via OSC
19
+ # @param [Object] subject The object to operate on when messages are received
20
+ # @param [Array<Hash>] map
21
+ # @return [Boolean]
22
+ def enable_parameter_control(subject, map)
23
+ start_server
24
+ maps = map.map do |item|
25
+ property = item[:property]
26
+ from_range = item[:value] || (0..1.0)
27
+ to_range = SequenceParameters::RANGE[property]
28
+ @server.add_method(item[:address]) do |message|
29
+ value = message.to_a[0]
30
+ value = Scale.transform(value).from(from_range).to(to_range)
31
+ puts "[DEBUG]: OSC: #{property}= #{value}" if @debug
32
+ subject.send("#{property}=", value)
33
+ true
34
+ end
35
+ true
36
+ end
37
+ maps.any?
38
+ end
39
+
40
+ private
41
+
42
+ # Start the server
43
+ # @return [Thread]
44
+ def start_server
45
+ @thread = Thread.new do
46
+ begin
47
+ EM.epoll
48
+ EM.run { @server.run }
49
+ rescue Exception => exception
50
+ Thread.main.raise(exception)
51
+ end
52
+ end
53
+ @thread.abort_on_exception = true
54
+ @thread
55
+ end
56
+
57
+ end
58
+
59
+ # Shortcut to Diamond::OSC::Node.new
60
+ def self.new(*args)
61
+ Node.new(*args)
62
+ end
63
+
64
+ end
65
+ end
66
+
67
+ # Patch the OSC module
68
+ #
69
+ module OSC
70
+ class EMServer
71
+
72
+ def run
73
+ open
74
+ end
75
+
76
+ def open
77
+ EM::open_datagram_socket("0.0.0.0", @port, Connection)
78
+ end
79
+
80
+ end
81
+ end
82
+
83
+ module EventMachine
84
+ module WebSocket
85
+ def self.start(options, &blk)
86
+ #EM.epoll
87
+ #EM.run {
88
+ trap("TERM") { stop }
89
+ trap("INT") { stop }
90
+
91
+ run(options, &blk)
92
+ #}
93
+ end
94
+
95
+ def self.run(options)
96
+ host, port = options.values_at(:host, :port)
97
+ EM.start_server(host, port, Connection, options) do |c|
98
+ yield c
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,95 @@
1
+ module Diamond
2
+
3
+ # Pattern that the sequence is derived from given the parameters and input
4
+ class Pattern
5
+
6
+ module ClassMethods
7
+
8
+ # All patterns
9
+ # @return [Array<Pattern>]
10
+ def all
11
+ @patterns ||= []
12
+ end
13
+
14
+ # Find a pattern by its name (case insensitive)
15
+ # @param [String, Symbol] name
16
+ # @return [Pattern]
17
+ def find(name)
18
+ all.find { |pattern| pattern.name.to_s.downcase == name.to_s.downcase }
19
+ end
20
+
21
+ # Construct and add a pattern
22
+ # @param [Symbol, String] name
23
+ # @param [Proc] block
24
+ # @return [Array<Pattern>]
25
+ def add(*args, &block)
26
+ all << new(*args, &block)
27
+ end
28
+
29
+ # Add a pattern
30
+ # @param [Pattern] pattern
31
+ # @return [Array<Pattern>]
32
+ def <<(pattern)
33
+ all << pattern
34
+ end
35
+
36
+ # @return [Pattern]
37
+ def first
38
+ all.first
39
+ end
40
+
41
+ # @return [Pattern]
42
+ def last
43
+ all.last
44
+ end
45
+
46
+ end
47
+
48
+ extend ClassMethods
49
+
50
+ attr_reader :name
51
+
52
+ # @param [String, Symbol] name A name to identify the pattern by eg "up/down"
53
+ # @param [Proc] block The pattern procedure, which should return an array of scale degree numbers.
54
+ # For example, given the arguments (3, 7) the "Up" pattern will produce [0, 7, 14, 21]
55
+ def initialize(name, &block)
56
+ @name = name
57
+ @proc = block
58
+ end
59
+
60
+ # Compute scale degrees using the pattern with the given range and interval
61
+ # @param [Fixnum] range
62
+ # @param [Interval] interval
63
+ # @return [Array<Fixnum>]
64
+ def compute(range, interval)
65
+ @proc.call(range, interval)
66
+ end
67
+
68
+ # Standard preset patterns
69
+ module Presets
70
+
71
+ Pattern << Pattern.new("Up") do |range, interval|
72
+ 0.upto(range).map { |num| num * interval }
73
+ end
74
+
75
+ Pattern << Pattern.new("Down") do |range, interval|
76
+ range.downto(0).map { |num| num * interval }
77
+ end
78
+
79
+ Pattern << Pattern.new("UpDown") do |range, interval|
80
+ up = 0.upto(range).map { |num| num * interval }
81
+ down = [(range - 1), 0].max.downto(0).map { |num| num * interval }
82
+ up + down
83
+ end
84
+
85
+ Pattern << Pattern.new("DownUp") do |range, interval|
86
+ down = range.downto(0).map { |num| num * interval }
87
+ up = 1.upto(range).map { |num| num * interval }
88
+ down + up
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,178 @@
1
+ module Diamond
2
+
3
+ # The note event sequence from where the arpeggiator output is derived
4
+ class Sequence
5
+
6
+ extend Forwardable
7
+
8
+ def_delegators :@sequence, :each, :first, :last, :length
9
+
10
+ def initialize
11
+ @parameter = nil
12
+ # realtime
13
+ @changed = false
14
+ @input_queue = []
15
+ @queue = []
16
+ end
17
+
18
+ # The bucket of messages for the given pointer
19
+ # @param [Fixnum] pointer
20
+ # @return [Array<MIDIMessage>]
21
+ def at(pointer)
22
+ if changed? && (pointer % @parameter.rate == 0)
23
+ update
24
+ @changed = false
25
+ end
26
+ enqueue_next(pointer)
27
+ messages = @queue.shift || []
28
+ messages
29
+ end
30
+
31
+ # Has the sequence changed since the last update?
32
+ # @return [Boolean]
33
+ def changed?
34
+ @changed
35
+ end
36
+
37
+ # Add inputted note_messages
38
+ # @param [Array<MIDIMessage::NoteOn>, MIDIMessage::NoteOn, *MIDIMessage::NoteOn] note_messages
39
+ # @return [Boolean]
40
+ def add(*note_messages)
41
+ messages = [note_messages].flatten.compact
42
+ @input_queue.concat(messages)
43
+ mark_changed
44
+ true
45
+ end
46
+
47
+ # Remove input note messages with the same note value
48
+ # @param [Array<MIDIMessage::NoteOn, MIDIMessage::NoteOff>, MIDIMessage::NoteOff, MIDIMessage::NoteOn, *MIDIMessage::NoteOff, *MIDIMessage::NoteOn] note_messages
49
+ # @return [Boolean]
50
+ def remove(*note_messages)
51
+ messages = [note_messages].flatten
52
+ deletion_queue = messages.map(&:note)
53
+ @input_queue.delete_if { |message| deletion_queue.include?(message.note) }
54
+ mark_changed
55
+ true
56
+ end
57
+
58
+ # Remove all input note messages
59
+ # @return [Boolean]
60
+ def remove_all
61
+ @input_queue.clear
62
+ mark_changed
63
+ true
64
+ end
65
+
66
+ # All NoteOff messages in the queue
67
+ # @return [Array<MIDIMessage::NoteOff>]
68
+ def pending_note_offs
69
+ messages = @queue.map do |bucket|
70
+ unless bucket.nil?
71
+ bucket.select { |m| m.class == MIDIMessage::NoteOff }
72
+ end
73
+ end
74
+ messages.flatten.compact
75
+ end
76
+
77
+ # Mark the sequence as changed
78
+ # @return [Boolean]
79
+ def mark_changed
80
+ @changed = true
81
+ end
82
+
83
+ protected
84
+
85
+ # Apply the given parameters object
86
+ # @param [SequenceParameters] parameters
87
+ # @return [SequenceParameters]
88
+ def use_parameters(parameters)
89
+ @parameter = parameters
90
+ update
91
+ end
92
+
93
+ private
94
+
95
+ # Enqueue next bucket for the given pointer
96
+ # @param [Fixnum] pointer
97
+ # @return [Array<NoteEvent>]
98
+ def enqueue_next(pointer)
99
+ bucket = @sequence[pointer]
100
+ enqueue(bucket) unless bucket.nil?
101
+ bucket
102
+ end
103
+
104
+ # Prepare the given event bucket for performance, moving note messages to the queue
105
+ # @param [Array<NoteEvent>] bucket
106
+ # @return [Array<NoteEvent>]
107
+ def enqueue(bucket)
108
+ bucket.map do |event|
109
+ @queue[0] ||= []
110
+ @queue[0] << event.start
111
+ float_length = (event.length.to_f / 100) * @parameter.duration.to_f
112
+ length = float_length.to_i
113
+ @queue[length] ||= []
114
+ @queue[length] << event.finish
115
+ event
116
+ end
117
+ end
118
+
119
+ # Commit changes to the sequence
120
+ # @return [ArpeggiatorSequence]
121
+ def update
122
+ notes = get_note_sequence
123
+ initialize_sequence(notes.length)
124
+ populate_sequence(notes) unless notes.empty?
125
+ @sequence
126
+ end
127
+
128
+ # (Re)initialize the sequence with the given length
129
+ # @param [Fixnum] length
130
+ # @return [Array]
131
+ def initialize_sequence(length)
132
+ sequence_length_in_ticks = length * @parameter.duration
133
+ @sequence = Array.new(sequence_length_in_ticks, [])
134
+ end
135
+
136
+ # Populate the sequence with the given notes
137
+ # @param [Array<MIDIMessage::NoteOn>] notes
138
+ # @return [Array<Array<NoteEvent>>]
139
+ def populate_sequence(notes)
140
+ @parameter.pattern_offset.times { notes.push(notes.shift) }
141
+ notes.each_with_index do |note, i|
142
+ index = i * @parameter.duration
143
+ populate_bucket(index, note) unless @sequence[index].nil?
144
+ end
145
+ @sequence
146
+ end
147
+
148
+ # Populate the bucket for index with the given note message
149
+ # @param [Fixnum] index
150
+ # @param [MIDIMessage::NoteOn] note_message
151
+ # @return [Array<NoteEvent>]
152
+ def populate_bucket(index, note_message)
153
+ @sequence[index] = create_bucket(note_message)
154
+ end
155
+
156
+ # Create a bucket/note event for the given note message
157
+ # @param [MIDIMessage::NoteOn] note_message
158
+ # @return [Array<NoteEvent>]
159
+ def create_bucket(note_message)
160
+ event = MIDIInstrument::NoteEvent.new(note_message, @parameter.gate)
161
+ [event]
162
+ end
163
+
164
+ # The input queue as note messages
165
+ # @return [Array<MIDIMessage::NoteOn>]
166
+ def get_note_sequence
167
+ notes = @parameter.computed_pattern.map do |degree|
168
+ @input_queue.map do |message|
169
+ note = message.note + degree + @parameter.transpose
170
+ MIDIMessage::NoteOn.new(message.channel, note, message.velocity)
171
+ end
172
+ end
173
+ notes.flatten.compact
174
+ end
175
+
176
+ end
177
+
178
+ end