diamond 0.5.1 → 0.5.3
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.
- checksums.yaml +4 -4
- data/README.md +18 -18
- data/lib/diamond.rb +1 -1
- data/lib/diamond/arpeggiator.rb +3 -12
- data/lib/diamond/clock.rb +45 -38
- data/lib/diamond/midi.rb +24 -14
- data/lib/diamond/osc.rb +6 -6
- data/lib/diamond/sequence.rb +1 -1
- data/lib/diamond/sequence_parameters.rb +57 -24
- data/test/arpeggiator_test.rb +2 -0
- data/test/clock_test.rb +70 -0
- data/test/sequence_parameters_test.rb +29 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30e670f044ab17c64cedf2c71573a1a510928cd2
|
4
|
+
data.tar.gz: 0b91f502f5952b2df09f311f1569eb5aa6b061bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60f2d5c041101b1bece73255f6cb4416ed5a254afcb971cda4f7de68f1a962f6f1ce8d3ff99fb34fef43b41c404d42581599ee9a45ca74aa71041fa17b5c320f
|
7
|
+
data.tar.gz: d782c0fa28658fd8c8521ae20f17b763e7bc22f9df7a0202ae67711e0494480eee2842ff0f782045c7c3d722fa13d6b39fdc00742f3ed4072f6da74a8e8cdcf4
|
data/README.md
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
#
|
1
|
+
#Diamond
|
2
2
|
|
3
3
|
[MIDI arpeggiator](http://en.wikipedia.org/wiki/Arpeggiator#Arpeggiator) in Ruby
|
4
4
|
|
5
5
|

|
6
6
|
|
7
|
-
##
|
7
|
+
##Features
|
8
8
|
|
9
|
-
* Classic arpeggiator functionality
|
9
|
+
* Classic arpeggiator functionality and patterns
|
10
10
|
* [OSC](http://en.wikipedia.org/wiki/Open_Sound_Control) and MIDI remote control
|
11
11
|
* MIDI clock IO
|
12
|
-
* Suited to [live coding](http://en.wikipedia.org/wiki/Live_coding)
|
13
12
|
* Multiplex clocks and arpeggiators
|
14
|
-
*
|
13
|
+
* Suited to [live coding](http://en.wikipedia.org/wiki/Live_coding)
|
14
|
+
* Generative arpeggio patterns
|
15
15
|
|
16
|
-
##
|
16
|
+
##Installation
|
17
17
|
|
18
18
|
`gem install diamond`
|
19
19
|
|
@@ -21,13 +21,13 @@
|
|
21
21
|
|
22
22
|
`gem "diamond"`
|
23
23
|
|
24
|
-
##
|
24
|
+
##Usage
|
25
25
|
|
26
26
|
```ruby
|
27
27
|
require "diamond"
|
28
28
|
```
|
29
29
|
|
30
|
-
First, select a MIDI output using [unimidi](https://github.com/arirusso/unimidi). ([more about that
|
30
|
+
First, select a MIDI output using [unimidi](https://github.com/arirusso/unimidi). ([more about that here](http://tx81z.blogspot.com/2011/10/selecting-midi-device-with-unimidi.html))
|
31
31
|
|
32
32
|
```ruby
|
33
33
|
@output = UniMIDI::Output.gets
|
@@ -60,7 +60,7 @@ Point the clock to the arpeggiator
|
|
60
60
|
clock << arpeggiator
|
61
61
|
```
|
62
62
|
|
63
|
-
|
63
|
+
The arpeggiator will play based on inputted notes or chords; a MIDI input can be used for that. ([see example](http://github.com/arirusso/diamond/blob/master/examples/midi_note_input.rb)). It's also possible to enter notes in Ruby:
|
64
64
|
|
65
65
|
```ruby
|
66
66
|
chord = ["C3", "G3", "Bb3", "A4"]
|
@@ -73,15 +73,15 @@ arpeggiator.add(chord)
|
|
73
73
|
arpeggiator << "C5"
|
74
74
|
```
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
Note that by default, the clock will run in a background thread. If you're working in a [PRY](http://pryrepl.org)/[IRB](http://en.wikipedia.org/wiki/Interactive_Ruby_Shell)/etc this will allow you to continue to code while the arpeggiator runs. To start in the *foreground*, pass `:focus => true` to `Clock#start`.
|
76
|
+
Starting the clock will also start the arpeggiator:
|
79
77
|
|
80
78
|
```ruby
|
81
79
|
clock.start
|
82
80
|
```
|
83
81
|
|
84
|
-
|
82
|
+
Note that by default, the clock will run in a background thread. If you're working in a [PRY](http://pryrepl.org)/[IRB](http://en.wikipedia.org/wiki/Interactive_Ruby_Shell)/etc this will allow you to continue to code while the arpeggiator runs. To start in the *foreground*, pass `:focus => true` to `Clock#start`.
|
83
|
+
|
84
|
+
All of the [arpeggiator options](http://rubydoc.info/github/arirusso/diamond/master/Diamond/Arpeggiator:initialize) can be controlled while the arpeggiator is running.
|
85
85
|
|
86
86
|
```ruby
|
87
87
|
arpeggiator.rate = 16
|
@@ -93,13 +93,13 @@ arpeggiator.remove("C5", "A4")
|
|
93
93
|
|
94
94
|
This [blog post](http://tx81z.blogspot.com/2011/07/live-coding-with-diamond.html) explains what is happening in the video.
|
95
95
|
|
96
|
-
####
|
96
|
+
####Posts
|
97
97
|
|
98
98
|
* [Introduction](http://tx81z.blogspot.com/2011/07/diamond-midi-arpeggiator-in-ruby.html)
|
99
99
|
* [Live coding Diamond and syncing multiple arpeggiators to each other](http://tx81z.blogspot.com/2011/07/live-coding-with-diamond.html)
|
100
100
|
* [A note about live coding in IRB with OSX](http://tx81z.blogspot.com/2011/09/note-about-live-coding-in-irb-with-osx.html)
|
101
101
|
|
102
|
-
####
|
102
|
+
####Examples
|
103
103
|
|
104
104
|
* [Control via OSC](http://github.com/arirusso/diamond/blob/master/examples/osc_control.rb)
|
105
105
|
* [Define a Pattern](http://github.com/arirusso/diamond/blob/master/examples/define_pattern.rb)
|
@@ -111,15 +111,15 @@ This [blog post](http://tx81z.blogspot.com/2011/07/live-coding-with-diamond.html
|
|
111
111
|
|
112
112
|
[More...](http://github.com/arirusso/diamond/blob/master/examples)
|
113
113
|
|
114
|
-
##
|
114
|
+
##Other Documentation
|
115
115
|
|
116
116
|
* [rdoc](http://rubydoc.info/github/arirusso/diamond)
|
117
117
|
|
118
|
-
##
|
118
|
+
##Author
|
119
119
|
|
120
120
|
* [Ari Russo](http://github.com/arirusso) <ari.russo at gmail.com>
|
121
121
|
|
122
|
-
##
|
122
|
+
##License
|
123
123
|
|
124
124
|
Apache 2.0, See the file LICENSE
|
125
125
|
|
data/lib/diamond.rb
CHANGED
data/lib/diamond/arpeggiator.rb
CHANGED
@@ -51,9 +51,9 @@ module Diamond
|
|
51
51
|
transmit_channel = options[:tx_channel]
|
52
52
|
devices = MIDIInstrument::Device.partition(options[:midi])
|
53
53
|
@midi = MIDI.new(devices, :debug => !!options[:midi_debug], :receive_channel => receive_channel, :transmit_channel => transmit_channel)
|
54
|
-
@midi.enable_output(
|
55
|
-
@midi.enable_note_control(
|
56
|
-
@midi.enable_parameter_control(
|
54
|
+
@midi.enable_output(self)
|
55
|
+
@midi.enable_note_control(self)
|
56
|
+
@midi.enable_parameter_control(self, options[:midi_control]) if !options[:midi_control].nil?
|
57
57
|
@midi
|
58
58
|
end
|
59
59
|
|
@@ -67,15 +67,6 @@ module Diamond
|
|
67
67
|
@osc
|
68
68
|
end
|
69
69
|
|
70
|
-
# Emit any note off messages that are currently pending in the queue. The clock triggers this
|
71
|
-
# when stopping or pausing
|
72
|
-
# @return [Array<MIDIMessage::NoteOff>]
|
73
|
-
def emit_pending_note_offs
|
74
|
-
messages = @sequence.pending_note_offs
|
75
|
-
@midi.output.puts(*messages)
|
76
|
-
messages
|
77
|
-
end
|
78
|
-
|
79
70
|
end
|
80
71
|
|
81
72
|
end
|
data/lib/diamond/clock.rb
CHANGED
@@ -1,18 +1,26 @@
|
|
1
1
|
module Diamond
|
2
2
|
|
3
|
-
# A wrapper for
|
3
|
+
# A wrapper for Topaz::Tempo that's geared towards the arpeggiator
|
4
4
|
class Clock
|
5
5
|
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
-
def_delegators :@clock, :event, :pause, :unpause
|
8
|
+
def_delegators :@clock, :midi_output, :event, :pause, :running?, :tempo, :tempo=, :unpause
|
9
9
|
|
10
10
|
# @param [Fixnum, UniMIDI::Input] tempo_or_input
|
11
11
|
# @param [Hash] options
|
12
|
-
# @option options [Array<UniMIDI::Output>, UniMIDI::Output] :
|
13
|
-
def initialize(
|
12
|
+
# @option options [Array<UniMIDI::Output>, UniMIDI::Output] :output MIDI output device(s) (also: :outputs)
|
13
|
+
def initialize(tempo_or_input, options = {})
|
14
14
|
@arpeggiators = []
|
15
|
-
|
15
|
+
output = options[:output] || options[:outputs] || options[:midi]
|
16
|
+
initialize_clock(tempo_or_input, :output => output)
|
17
|
+
initialize_events
|
18
|
+
end
|
19
|
+
|
20
|
+
# Shortcut to the clock's MIDI output devices
|
21
|
+
# @return [Array<UniMIDI::Output>]
|
22
|
+
def midi_outputs
|
23
|
+
@clock.midi_output.devices
|
16
24
|
end
|
17
25
|
|
18
26
|
# Start the clock
|
@@ -36,53 +44,52 @@ module Diamond
|
|
36
44
|
true
|
37
45
|
end
|
38
46
|
|
39
|
-
# Add
|
47
|
+
# Add arpeggiator(s) to this clock's control
|
40
48
|
# @param [Array<Arpeggiator>, Arpeggiator] arpeggiator
|
41
|
-
# @return [
|
49
|
+
# @return [Array<Arpeggiator>]
|
42
50
|
def add(arpeggiator)
|
43
51
|
arpeggiators = [arpeggiator].flatten
|
44
|
-
|
45
|
-
|
46
|
-
@arpeggiators << arpeggiator
|
47
|
-
reset_tick
|
48
|
-
true
|
49
|
-
else
|
50
|
-
false
|
51
|
-
end
|
52
|
-
end
|
53
|
-
result.any?
|
52
|
+
@arpeggiators += arpeggiators
|
53
|
+
@arpeggiators
|
54
54
|
end
|
55
55
|
alias_method :<<, :add
|
56
56
|
|
57
|
-
# Remove
|
58
|
-
# @param [Arpeggiator] arpeggiator
|
59
|
-
# @return [
|
57
|
+
# Remove arpeggiator(s) from this clock's control
|
58
|
+
# @param [Array<Arpeggiator>, Arpeggiator] arpeggiator
|
59
|
+
# @return [Array<Arpeggiator>]
|
60
60
|
def remove(arpeggiator)
|
61
61
|
arpeggiators = [arpeggiator].flatten
|
62
|
-
|
63
|
-
|
64
|
-
@arpeggiators.delete(arpeggiator)
|
65
|
-
reset_tick
|
66
|
-
true
|
67
|
-
else
|
68
|
-
false
|
69
|
-
end
|
70
|
-
end
|
71
|
-
result.any?
|
62
|
+
@arpeggiators.delete_if? { |arpeggiator| arpeggiators.include?(arpeggiator) }
|
63
|
+
@arpeggiators
|
72
64
|
end
|
73
65
|
|
74
66
|
private
|
75
67
|
|
76
|
-
#
|
77
|
-
# @
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
68
|
+
# @param [Fixnum, UniMIDI::Input] tempo_or_input
|
69
|
+
# @param [Hash] options
|
70
|
+
# @option options [Array<UniMIDI::Output>, UniMIDI::Output] :output MIDI output device(s)
|
71
|
+
# @option options [Fixnum] :resolution
|
72
|
+
# @return [Topaz::Clock]
|
73
|
+
def initialize_clock(tempo_or_input, options = {})
|
74
|
+
@clock = Topaz::Clock.new(tempo_or_input, :midi => options[:output])
|
75
|
+
resolution = options.fetch(:resolution, 128)
|
76
|
+
@clock.interval = @clock.interval * (resolution / @clock.interval)
|
77
|
+
@clock
|
78
|
+
end
|
79
|
+
|
80
|
+
# Initialize the tick event
|
81
|
+
# @return [Boolean]
|
82
|
+
def initialize_events
|
83
|
+
@clock.event.tick << proc do
|
84
|
+
@arpeggiators.each do |arpeggiator|
|
85
|
+
arpeggiator.sequencer.exec(arpeggiator.sequence)
|
86
|
+
arpeggiator.sequencer.event.stop { @clock.stop }
|
87
|
+
arpeggiator
|
88
|
+
end
|
84
89
|
end
|
90
|
+
true
|
85
91
|
end
|
92
|
+
|
86
93
|
end
|
87
94
|
|
88
95
|
end
|
data/lib/diamond/midi.rb
CHANGED
@@ -35,43 +35,43 @@ module Diamond
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# Initialize adding and removing MIDI notes from the sequence
|
38
|
-
# @param [
|
38
|
+
# @param [Arpeggiator] arpeggiator
|
39
39
|
# @return [Boolean]
|
40
|
-
def enable_note_control(
|
40
|
+
def enable_note_control(arpeggiator)
|
41
41
|
@midi.input.receive(:class => MIDIMessage::NoteOn) do |event|
|
42
42
|
message = event[:message]
|
43
43
|
if @midi.input.channel.nil? || @midi.input.channel == message.channel
|
44
44
|
puts "[DEBUG] MIDI: add note from input #{message.name} channel: #{message.channel}" if @debug
|
45
|
-
sequence.add(message)
|
45
|
+
arpeggiator.sequence.add(message)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
@midi.input.receive(:class => MIDIMessage::NoteOff) do |event|
|
49
49
|
message = event[:message]
|
50
50
|
if @midi.input.channel.nil? || @midi.input.channel == message.channel
|
51
51
|
puts "[DEBUG] MIDI: remove note from input #{message.name} channel: #{message.channel}" if @debug
|
52
|
-
sequence.remove(message)
|
52
|
+
arpeggiator.sequence.remove(message)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
true
|
56
56
|
end
|
57
57
|
|
58
58
|
# Initialize a user-defined map of control change messages
|
59
|
-
# @param [
|
59
|
+
# @param [Arpeggiator] arpeggiator
|
60
60
|
# @param [Array<Hash>] map
|
61
61
|
# @return [Boolean]
|
62
|
-
def enable_parameter_control(
|
63
|
-
|
62
|
+
def enable_parameter_control(arpeggiator, map)
|
63
|
+
midi_cc_range = 0..127
|
64
64
|
@midi.input.receive(:class => MIDIMessage::ControlChange) do |event|
|
65
65
|
message = event[:message]
|
66
66
|
if @midi.input.channel.nil? || @midi.input.channel == message.channel
|
67
67
|
index = message.index
|
68
68
|
mapping = map.find { |mapping| mapping[:index] == index }
|
69
69
|
property = mapping[:property]
|
70
|
-
|
70
|
+
parameter_range = apeggiator.parameter.constraints(property)
|
71
71
|
value = message.value
|
72
|
-
value = Scale.transform(value).from(
|
72
|
+
value = Scale.transform(value).from(midi_cc_range).to(parameter_range)
|
73
73
|
puts "[DEBUG] MIDI: #{property}= #{value} channel: #{message.channel}" if @debug
|
74
|
-
|
74
|
+
arpeggiator.parameter.send("#{property}=", value)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -106,10 +106,10 @@ module Diamond
|
|
106
106
|
end
|
107
107
|
|
108
108
|
# Initialize MIDI output, enabling the sequencer to emit notes
|
109
|
-
# @param [
|
109
|
+
# @param [Arpeggiator] arpeggiator
|
110
110
|
# @return [Boolean]
|
111
|
-
def enable_output(
|
112
|
-
sequencer.event.perform << proc do |bucket|
|
111
|
+
def enable_output(arpeggiator)
|
112
|
+
arpeggiator.sequencer.event.perform << proc do |bucket|
|
113
113
|
unless bucket.empty?
|
114
114
|
if @debug
|
115
115
|
bucket.each do |message|
|
@@ -119,12 +119,22 @@ module Diamond
|
|
119
119
|
@midi.output.puts(bucket)
|
120
120
|
end
|
121
121
|
end
|
122
|
-
sequencer.event.stop << proc { emit_pending_note_offs }
|
122
|
+
arpeggiator.sequencer.event.stop << proc { emit_pending_note_offs(arpeggiator) }
|
123
123
|
true
|
124
124
|
end
|
125
125
|
|
126
126
|
private
|
127
127
|
|
128
|
+
# Emit any note off messages that are currently pending in the queue. The clock triggers this
|
129
|
+
# when stopping or pausing
|
130
|
+
# @param [Arpeggiator] arpeggiator
|
131
|
+
# @return [Array<MIDIMessage::NoteOff>]
|
132
|
+
def emit_pending_note_offs(arpeggiator)
|
133
|
+
messages = arpeggiator.sequence.pending_note_offs
|
134
|
+
@midi.output.puts(*messages)
|
135
|
+
messages
|
136
|
+
end
|
137
|
+
|
128
138
|
# Initialize MIDI output
|
129
139
|
# @param [Array<UniMIDI::Output>] outputs
|
130
140
|
# @param [Hash] options
|
data/lib/diamond/osc.rb
CHANGED
@@ -16,20 +16,20 @@ module Diamond
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# Enable controlling the instrument via OSC
|
19
|
-
# @param [
|
19
|
+
# @param [Arpeggiator] arpeggiator The arpeggiator to operate on when messages are received
|
20
20
|
# @param [Array<Hash>] map
|
21
21
|
# @return [Boolean]
|
22
|
-
def enable_parameter_control(
|
22
|
+
def enable_parameter_control(arpeggiator, map)
|
23
23
|
start_server
|
24
24
|
maps = map.map do |item|
|
25
25
|
property = item[:property]
|
26
|
-
|
27
|
-
to_range = SequenceParameters::RANGE[property]
|
26
|
+
osc_range = item[:value] || (0..1.0)
|
28
27
|
@server.add_method(item[:address]) do |message|
|
29
28
|
value = message.to_a[0]
|
30
|
-
|
29
|
+
parameter_range = arpeggiator.parameter.constraints(property)
|
30
|
+
value = Scale.transform(value).from(osc_range).to(parameter_range)
|
31
31
|
puts "[DEBUG]: OSC: #{property}= #{value}" if @debug
|
32
|
-
|
32
|
+
arpeggiator.parameter.send("#{property}=", value)
|
33
33
|
true
|
34
34
|
end
|
35
35
|
true
|
data/lib/diamond/sequence.rb
CHANGED
@@ -21,7 +21,6 @@ module Diamond
|
|
21
21
|
def at(pointer)
|
22
22
|
if changed? && (pointer % @parameter.rate == 0)
|
23
23
|
update
|
24
|
-
@changed = false
|
25
24
|
end
|
26
25
|
enqueue_next(pointer)
|
27
26
|
messages = @queue.shift || []
|
@@ -122,6 +121,7 @@ module Diamond
|
|
122
121
|
notes = get_note_sequence
|
123
122
|
initialize_sequence(notes.length)
|
124
123
|
populate_sequence(notes) unless notes.empty?
|
124
|
+
@changed = false
|
125
125
|
@sequence
|
126
126
|
end
|
127
127
|
|
@@ -3,15 +3,6 @@ module Diamond
|
|
3
3
|
# User-controller parameters that are used to formulate the note event sequence
|
4
4
|
class SequenceParameters
|
5
5
|
|
6
|
-
RANGE = {
|
7
|
-
:gate => 1..500,
|
8
|
-
:interval => -48..48,
|
9
|
-
:pattern_offset => -16..16,
|
10
|
-
:range => 0..10,
|
11
|
-
:rate => 0..64,
|
12
|
-
:transpose => -64..64
|
13
|
-
}
|
14
|
-
|
15
6
|
attr_reader :gate,
|
16
7
|
:interval,
|
17
8
|
:pattern,
|
@@ -38,20 +29,41 @@ module Diamond
|
|
38
29
|
sequence.send(:use_parameters, self)
|
39
30
|
end
|
40
31
|
|
41
|
-
#
|
32
|
+
# Dynamically produce an acceptable range for the given param
|
33
|
+
# @param [Symbol] param
|
34
|
+
# @return [Range]
|
35
|
+
def constraints(param)
|
36
|
+
ranges = {
|
37
|
+
:gate => proc { get_lowest_gate..500 },
|
38
|
+
:interval => proc { -48..48 },
|
39
|
+
:pattern_offset => proc { -16..16 },
|
40
|
+
:range => proc { 0..10 },
|
41
|
+
:rate => proc { 0..@resolution },
|
42
|
+
:transpose => proc { -72..72 }
|
43
|
+
}
|
44
|
+
ranges[param].call
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set the gate property. Is constrained to go only as low as the rate and resolution allow.
|
42
48
|
# @param [Fixnum] num
|
49
|
+
# @param [Hash] options
|
50
|
+
# @option options [Boolean] :constrain When false, will skip constraints (default: true)
|
43
51
|
# @return [Fixnum]
|
44
|
-
def gate=(num)
|
45
|
-
|
52
|
+
def gate=(num, options = {})
|
53
|
+
should_constrain = options.fetch(:constrain, true)
|
54
|
+
@gate = should_constrain ? constrain(num, :range => constraints(:gate)) : num
|
46
55
|
mark_changed
|
47
56
|
@gate
|
48
57
|
end
|
49
58
|
|
50
59
|
# Set the interval property
|
51
60
|
# @param [Fixnum] num
|
61
|
+
# @param [Hash] options
|
62
|
+
# @option options [Boolean] :constrain When false, will skip constraints (default: true)
|
52
63
|
# @param [Fixnum]
|
53
|
-
def interval=(num)
|
54
|
-
|
64
|
+
def interval=(num, options = {})
|
65
|
+
should_constrain = options.fetch(:constrain, true)
|
66
|
+
@interval = should_constrain ? constrain(num, :range => constraints(:interval)) : num
|
55
67
|
mark_changed
|
56
68
|
@interval
|
57
69
|
end
|
@@ -67,18 +79,32 @@ module Diamond
|
|
67
79
|
|
68
80
|
# Set the range property
|
69
81
|
# @param [Fixnum] range
|
82
|
+
# @param [Hash] options
|
83
|
+
# @option options [Boolean] :constrain When false, will skip constraints (default: true)
|
70
84
|
# @return [Fixnum]
|
71
|
-
def range=(num)
|
72
|
-
|
85
|
+
def range=(num, options = {})
|
86
|
+
should_constrain = options.fetch(:constrain, true)
|
87
|
+
@range = should_constrain ? constrain(num, :range => constraints(:range)) : num
|
73
88
|
mark_changed
|
74
89
|
@range
|
75
90
|
end
|
76
91
|
|
77
|
-
# Set the rate property
|
92
|
+
# Set the rate property. This will also scale the gate accordingly
|
78
93
|
# @param [Fixnum] num
|
94
|
+
# @param [Hash] options
|
95
|
+
# @option options [Boolean] :constrain When false, will skip constraints (default: true)
|
79
96
|
# @return [Fixnum]
|
80
|
-
def rate=(num)
|
81
|
-
|
97
|
+
def rate=(num, options = {})
|
98
|
+
# Scale the gate according to the new gate
|
99
|
+
change = num / @rate.to_f
|
100
|
+
new_gate = (@gate * change).ceil
|
101
|
+
if options.fetch(:constrain, true)
|
102
|
+
@rate = constrain(num, :range => constraints(:rate))
|
103
|
+
@gate = constrain(new_gate, :range => constraints(:gate))
|
104
|
+
else
|
105
|
+
@rate = num
|
106
|
+
@gate = new_gate
|
107
|
+
end
|
82
108
|
mark_changed
|
83
109
|
@rate
|
84
110
|
end
|
@@ -116,6 +142,13 @@ module Diamond
|
|
116
142
|
|
117
143
|
private
|
118
144
|
|
145
|
+
# Find the lowest gate value possible for the rate and resolution
|
146
|
+
# @return [Float]
|
147
|
+
def get_lowest_gate
|
148
|
+
factor = @resolution.to_f / @rate
|
149
|
+
(100.0 / factor).ceil
|
150
|
+
end
|
151
|
+
|
119
152
|
# Mark that there's been a change in the sequence
|
120
153
|
def mark_changed
|
121
154
|
@callback.call
|
@@ -124,11 +157,11 @@ module Diamond
|
|
124
157
|
# @param [Hash] options
|
125
158
|
# @return [ArpeggiatorSequence::Parameters]
|
126
159
|
def apply_options(options)
|
127
|
-
@interval = constrain((options[:interval] || 12), :range =>
|
128
|
-
@range = constrain((options[:range] || 3), :range =>
|
129
|
-
@pattern_offset = constrain((options[:pattern_offset] || 0),:range =>
|
130
|
-
@rate = constrain((options[:rate] || 8), :range =>
|
131
|
-
@gate = constrain((options[:gate] || 75), :range =>
|
160
|
+
@interval = constrain((options[:interval] || 12), :range => constraints(:interval))
|
161
|
+
@range = constrain((options[:range] || 3), :range => constraints(:range))
|
162
|
+
@pattern_offset = constrain((options[:pattern_offset] || 0),:range => constraints(:pattern_offset))
|
163
|
+
@rate = constrain((options[:rate] || 8), :range => constraints(:rate))
|
164
|
+
@gate = constrain((options[:gate] || 75), :range => constraints(:gate))
|
132
165
|
@pattern = get_pattern(options[:pattern])
|
133
166
|
self
|
134
167
|
end
|
data/test/arpeggiator_test.rb
CHANGED
@@ -8,6 +8,7 @@ class Diamond::ApeggiatorTest < Test::Unit::TestCase
|
|
8
8
|
|
9
9
|
should "have defaults" do
|
10
10
|
@arpeggiator = Diamond::Arpeggiator.new
|
11
|
+
assert_equal 128, @arpeggiator.resolution
|
11
12
|
assert_equal 8, @arpeggiator.rate
|
12
13
|
assert_equal 3, @arpeggiator.range
|
13
14
|
assert_equal 12, @arpeggiator.interval
|
@@ -15,6 +16,7 @@ class Diamond::ApeggiatorTest < Test::Unit::TestCase
|
|
15
16
|
|
16
17
|
should "allow setting params" do
|
17
18
|
@arpeggiator = Diamond::Arpeggiator.new(:interval => 7, :range => 4, :rate => 16)
|
19
|
+
assert_equal 128, @arpeggiator.resolution
|
18
20
|
assert_equal 16, @arpeggiator.rate
|
19
21
|
assert_equal 4, @arpeggiator.range
|
20
22
|
assert_equal 7, @arpeggiator.interval
|
data/test/clock_test.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Diamond::ClockTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Clock" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@clock = Diamond::Clock.new(120)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "Clock#initialize" do
|
12
|
+
|
13
|
+
should "get MIDI output" do
|
14
|
+
output = Object.new
|
15
|
+
@clock = Diamond::Clock.new(120, :output => output)
|
16
|
+
assert_not_nil @clock.midi_outputs
|
17
|
+
assert_not_empty @clock.midi_outputs
|
18
|
+
assert @clock.midi_outputs.include?(output)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
context "Clock#add_midi_output" do
|
24
|
+
|
25
|
+
should "add MIDI output" do
|
26
|
+
output = Object.new
|
27
|
+
refute @clock.midi_outputs.include?(output)
|
28
|
+
@clock.midi_output.devices << output
|
29
|
+
assert_not_nil @clock.midi_outputs
|
30
|
+
assert_not_empty @clock.midi_outputs
|
31
|
+
assert @clock.midi_outputs.include?(output)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
context "Clock#remove_midi_output" do
|
37
|
+
|
38
|
+
should "remove MIDI output" do
|
39
|
+
output = Object.new
|
40
|
+
refute @clock.midi_outputs.include?(output)
|
41
|
+
@clock.midi_output.devices << output
|
42
|
+
assert_not_nil @clock.midi_outputs
|
43
|
+
assert_not_empty @clock.midi_outputs
|
44
|
+
assert @clock.midi_outputs.include?(output)
|
45
|
+
|
46
|
+
@clock.midi_output.devices.delete(output)
|
47
|
+
refute @clock.midi_outputs.include?(output)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
context "Clock#tempo" do
|
53
|
+
|
54
|
+
should "get tempo" do
|
55
|
+
assert_equal 120, @clock.tempo
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context "Clock#tempo=" do
|
61
|
+
|
62
|
+
should "set tempo" do
|
63
|
+
@clock.tempo = 58
|
64
|
+
assert_equal 58, @clock.tempo
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -6,7 +6,7 @@ class Diamond::SequenceParametersTest < Test::Unit::TestCase
|
|
6
6
|
|
7
7
|
setup do
|
8
8
|
@sequence = Diamond::Sequence.new
|
9
|
-
@params = Diamond::SequenceParameters.new(@sequence,
|
9
|
+
@params = Diamond::SequenceParameters.new(@sequence, 128) { @sequence.mark_changed }
|
10
10
|
end
|
11
11
|
|
12
12
|
context "#rate=" do
|
@@ -17,6 +17,28 @@ class Diamond::SequenceParametersTest < Test::Unit::TestCase
|
|
17
17
|
assert_equal 16, @params.rate
|
18
18
|
end
|
19
19
|
|
20
|
+
should "scale gate down" do
|
21
|
+
@params.rate = 16
|
22
|
+
@params.gate = 50
|
23
|
+
assert_equal 16, @params.rate
|
24
|
+
assert_equal 50, @params.gate
|
25
|
+
|
26
|
+
@params.rate = 8
|
27
|
+
assert_equal 8, @params.rate
|
28
|
+
assert_equal 25, @params.gate
|
29
|
+
end
|
30
|
+
|
31
|
+
should "scale gate up" do
|
32
|
+
@params.rate = 8
|
33
|
+
@params.gate = 50
|
34
|
+
assert_equal 8, @params.rate
|
35
|
+
assert_equal 50, @params.gate
|
36
|
+
|
37
|
+
@params.rate = 16
|
38
|
+
assert_equal 16, @params.rate
|
39
|
+
assert_equal 100, @params.gate
|
40
|
+
end
|
41
|
+
|
20
42
|
end
|
21
43
|
|
22
44
|
context "#range=" do
|
@@ -53,6 +75,12 @@ class Diamond::SequenceParametersTest < Test::Unit::TestCase
|
|
53
75
|
assert_equal 125, @params.gate
|
54
76
|
end
|
55
77
|
|
78
|
+
should "constrain gate to what rate and resolution allow for" do
|
79
|
+
@params.rate = 16
|
80
|
+
@params.gate = 10
|
81
|
+
assert_equal 13, @params.gate
|
82
|
+
end
|
83
|
+
|
56
84
|
end
|
57
85
|
|
58
86
|
context "#pattern_offset=" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diamond
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ari Russo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: analog
|
@@ -190,6 +190,7 @@ files:
|
|
190
190
|
- lib/diamond/sequence_parameters.rb
|
191
191
|
- test/api_test.rb
|
192
192
|
- test/arpeggiator_test.rb
|
193
|
+
- test/clock_test.rb
|
193
194
|
- test/helper.rb
|
194
195
|
- test/osc_test.rb
|
195
196
|
- test/pattern_test.rb
|