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,164 @@
1
+ module Diamond
2
+
3
+ # User-controller parameters that are used to formulate the note event sequence
4
+ class SequenceParameters
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
+ attr_reader :gate,
16
+ :interval,
17
+ :pattern,
18
+ :pattern_offset,
19
+ :range,
20
+ :rate,
21
+ :resolution
22
+
23
+ # @param [Sequence] sequence
24
+ # @param [Fixnum] resolution
25
+ # @param [Hash] options
26
+ # @option options [Fixnum] :gate Duration of the arpeggiated notes. The value is a percentage based on the rate. If the rate is 4, then a gate of 100 is equal to a quarter note. (default: 75). must be 1..500
27
+ # @option options [Fixnum] :interval Increment (pattern) over (interval) scale degrees (range) times. May be positive or negative. (default: 12)
28
+ # @option options [Fixnum] :pattern_offset Begin on the nth note of the sequence (but not omit any notes). (default: 0)
29
+ # @option options [String, Pattern] :pattern Computes the contour of the arpeggiated melody. Can be the name of a pattern or a pattern object.
30
+ # @option options [Fixnum] :range Increment the (pattern) over (interval) scale degrees (range) times. Must be positive (abs will be used). (default: 3)
31
+ # @option options [Fixnum] :rate How fast the arpeggios will be played. Must be positive (abs will be used). (default: 8, eighth note.) must be 0..resolution
32
+ # @param [Proc] callback
33
+ def initialize(sequence, resolution, options = {}, &callback)
34
+ @transpose = 0
35
+ @resolution = resolution
36
+ @callback = callback
37
+ apply_options(options)
38
+ sequence.send(:use_parameters, self)
39
+ end
40
+
41
+ # Set the gate property
42
+ # @param [Fixnum] num
43
+ # @return [Fixnum]
44
+ def gate=(num)
45
+ @gate = constrain(num, :range => RANGE[:gate])
46
+ mark_changed
47
+ @gate
48
+ end
49
+
50
+ # Set the interval property
51
+ # @param [Fixnum] num
52
+ # @param [Fixnum]
53
+ def interval=(num)
54
+ @interval = num
55
+ mark_changed
56
+ @interval
57
+ end
58
+
59
+ # Set the pattern offset property
60
+ # @param [Fixnum] num
61
+ # @return [Fixnum]
62
+ def pattern_offset=(num)
63
+ @pattern_offset = num
64
+ mark_changed
65
+ @pattern_offset
66
+ end
67
+
68
+ # Set the range property
69
+ # @param [Fixnum] range
70
+ # @return [Fixnum]
71
+ def range=(num)
72
+ @range = constrain(num, :range => RANGE[:range])
73
+ mark_changed
74
+ @range
75
+ end
76
+
77
+ # Set the rate property
78
+ # @param [Fixnum] num
79
+ # @return [Fixnum]
80
+ def rate=(num)
81
+ @rate = constrain(num, :range => RANGE[:rate].begin..@resolution)
82
+ mark_changed
83
+ @rate
84
+ end
85
+
86
+ # Set the pattern property
87
+ # @param [Pattern] pattern
88
+ # @return [Pattern]
89
+ def pattern=(pattern)
90
+ @pattern = pattern
91
+ mark_changed
92
+ @pattern
93
+ end
94
+
95
+ # Transpose everything by the given number of scale degrees. Can be used as a getter
96
+ # @param [Fixnum, nil] num
97
+ # @return [Fixnum, nil]
98
+ def transpose(num = nil)
99
+ @transpose = num unless num.nil?
100
+ mark_changed
101
+ @transpose
102
+ end
103
+ alias_method :transpose=, :transpose
104
+
105
+ # The computed pattern given the sequence options
106
+ # @return [Array<Fixnum>]
107
+ def computed_pattern
108
+ @pattern.compute(@range, @interval)
109
+ end
110
+
111
+ # The note duration given the sequence options
112
+ # @return [Numeric]
113
+ def duration
114
+ @resolution / @rate
115
+ end
116
+
117
+ private
118
+
119
+ # Mark that there's been a change in the sequence
120
+ def mark_changed
121
+ @callback.call
122
+ end
123
+
124
+ # @param [Hash] options
125
+ # @return [ArpeggiatorSequence::Parameters]
126
+ def apply_options(options)
127
+ @interval = constrain((options[:interval] || 12), :range => RANGE[:interval])
128
+ @range = constrain((options[:range] || 3), :range => RANGE[:range])
129
+ @pattern_offset = constrain((options[:pattern_offset] || 0),:range => RANGE[:pattern_offset])
130
+ @rate = constrain((options[:rate] || 8), :range => 0..@resolution)
131
+ @gate = constrain((options[:gate] || 75), :range => RANGE[:gate])
132
+ @pattern = get_pattern(options[:pattern])
133
+ self
134
+ end
135
+
136
+ # Derive a pattern from the options or using the default
137
+ # @param [Pattern, String, nil] option
138
+ # @return [Pattern, nil]
139
+ def get_pattern(option)
140
+ @pattern = case option
141
+ when Pattern then option
142
+ when String then Pattern.find(option)
143
+ end
144
+ @pattern ||= Pattern.first
145
+ end
146
+
147
+ # Constrain the given value based on the sequence options
148
+ # @param [Numeric] value
149
+ # @param [Hash] options
150
+ # @option options [Numeric] :min
151
+ # @option options [Numeric] :max
152
+ # @option options [Range] :range
153
+ # @return [Numeric]
154
+ def constrain(value, options = {})
155
+ min = options[:range].nil? ? options[:min] : options[:range].begin
156
+ max = options[:range].nil? ? options[:max] : options[:range].end
157
+ new_value = value
158
+ new_value = min.nil? ? new_value : [new_value, min].max
159
+ new_value = max.nil? ? new_value : [new_value, max].min
160
+ new_value
161
+ end
162
+
163
+ end
164
+ end
data/test/api_test.rb ADDED
@@ -0,0 +1,152 @@
1
+ require "helper"
2
+
3
+ class Diamond::APITest < Test::Unit::TestCase
4
+
5
+ context "API" do
6
+
7
+ context "MIDI" do
8
+
9
+ context "#omni_on" do
10
+
11
+ setup do
12
+ @messages = [
13
+ MIDIMessage::NoteOn["C4"].new(10, 100),
14
+ MIDIMessage::NoteOn["C4"].new(3, 100),
15
+ MIDIMessage::NoteOn["C4"].new(2, 100)
16
+ ]
17
+ @arpeggiator = Diamond::Arpeggiator.new(:rx_channel => 3)
18
+ end
19
+
20
+ should "not acknowledge message with wrong rx channel" do
21
+ @arpeggiator.add(@messages[0])
22
+ assert_empty @arpeggiator.sequence.instance_variable_get("@input_queue")
23
+ end
24
+
25
+ should "acknowledge message with rx channel" do
26
+ @arpeggiator.add(@messages[1])
27
+ @arpeggiator.sequence.instance_variable_get("@input_queue").expects(:concat).once.with([@messages[1]])
28
+ end
29
+
30
+ should "with omni on, acknowledge any rx channel" do
31
+ @arpeggiator.omni_on
32
+ @arpeggiator.add(@messages[2])
33
+ @arpeggiator.sequence.instance_variable_get("@input_queue").expects(:concat).once.with([@messages[2]])
34
+ end
35
+
36
+ end
37
+
38
+ context "#add_midi_source" do
39
+
40
+ setup do
41
+ @input = $test_device[:input]
42
+ @arpeggiator = Diamond::Arpeggiator.new
43
+ refute @arpeggiator.midi_sources.include?(@input)
44
+ end
45
+
46
+ should "add a midi source" do
47
+ @arpeggiator.add_midi_source(@input)
48
+ assert_not_empty @arpeggiator.midi_sources
49
+ assert @arpeggiator.midi_sources.include?(@input)
50
+ end
51
+
52
+ end
53
+
54
+ context "#remove_midi_source" do
55
+
56
+ setup do
57
+ @input = $test_device[:input]
58
+ @arpeggiator = Diamond::Arpeggiator.new
59
+ @arpeggiator.add_midi_source(@input)
60
+ assert_not_empty @arpeggiator.midi_sources
61
+ assert @arpeggiator.midi_sources.include?(@input)
62
+ end
63
+
64
+ should "remove a midi source" do
65
+ @arpeggiator.remove_midi_source(@input)
66
+ refute @arpeggiator.midi_sources.include?(@input)
67
+ end
68
+
69
+ end
70
+
71
+ context "#mute" do
72
+
73
+ setup do
74
+ @arpeggiator = Diamond::Arpeggiator.new
75
+ end
76
+
77
+ should "mute the arpeggiator" do
78
+ refute @arpeggiator.muted?
79
+ @arpeggiator.mute = true
80
+ assert @arpeggiator.muted?
81
+ @arpeggiator.mute = false
82
+ refute @arpeggiator.muted?
83
+ @arpeggiator.toggle_mute
84
+ assert @arpeggiator.muted?
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+
91
+ context "SequenceParameters" do
92
+
93
+ setup do
94
+ @arpeggiator = Diamond::Arpeggiator.new
95
+ end
96
+
97
+ context "#rate=" do
98
+
99
+ should "set the rate" do
100
+ assert_not_equal 16, @arpeggiator.rate
101
+ @arpeggiator.rate = 16
102
+ assert_equal 16, @arpeggiator.rate
103
+ end
104
+
105
+ end
106
+
107
+ context "#range=" do
108
+
109
+ should "set the range" do
110
+ @arpeggiator.range = 4
111
+ assert_equal 4, @arpeggiator.range
112
+ @arpeggiator.range += 1
113
+ assert_equal 5, @arpeggiator.range
114
+ end
115
+
116
+ end
117
+
118
+ context "#interval=" do
119
+
120
+ should "set the interval" do
121
+ assert_not_equal 7, @arpeggiator.interval
122
+ @arpeggiator.interval = 7
123
+ assert_equal 7, @arpeggiator.interval
124
+ end
125
+
126
+ end
127
+
128
+ context "#gate=" do
129
+
130
+ should "set the gate" do
131
+ assert_not_equal 125, @arpeggiator.gate
132
+ @arpeggiator.gate = 125
133
+ assert_equal 125, @arpeggiator.gate
134
+ end
135
+
136
+ end
137
+
138
+ context "#pattern_offset=" do
139
+
140
+ should "set the offset" do
141
+ assert_not_equal 5, @arpeggiator.pattern_offset
142
+ @arpeggiator.pattern_offset = 5
143
+ assert_equal 5, @arpeggiator.pattern_offset
144
+ end
145
+
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,34 @@
1
+ require "helper"
2
+
3
+ class Diamond::ApeggiatorTest < Test::Unit::TestCase
4
+
5
+ context "Arpeggiator" do
6
+
7
+ context "#initialize" do
8
+
9
+ should "have defaults" do
10
+ @arpeggiator = Diamond::Arpeggiator.new
11
+ assert_equal 8, @arpeggiator.rate
12
+ assert_equal 3, @arpeggiator.range
13
+ assert_equal 12, @arpeggiator.interval
14
+ end
15
+
16
+ should "allow setting params" do
17
+ @arpeggiator = Diamond::Arpeggiator.new(:interval => 7, :range => 4, :rate => 16)
18
+ assert_equal 16, @arpeggiator.rate
19
+ assert_equal 4, @arpeggiator.range
20
+ assert_equal 7, @arpeggiator.interval
21
+ end
22
+
23
+ should "allow passing in input" do
24
+ @input = $test_device[:input]
25
+ @arpeggiator = Diamond::Arpeggiator.new(:midi => @input)
26
+ assert_not_empty @arpeggiator.midi_sources
27
+ assert @arpeggiator.midi_sources.include?(@input)
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,23 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + "/../lib"
3
+
4
+ require "test/unit"
5
+ require "mocha/test_unit"
6
+ require "shoulda-context"
7
+ require "diamond"
8
+
9
+ module TestHelper
10
+
11
+ def self.select_devices
12
+ $test_device ||= {}
13
+ { :input => UniMIDI::Input, :output => UniMIDI::Output }.each do |type, klass|
14
+ $test_device[type] = klass.gets
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ TestHelper.select_devices
21
+
22
+ # Stub out OSC networking
23
+ ::EM.stubs(:run).returns(:true)
data/test/osc_test.rb ADDED
@@ -0,0 +1,37 @@
1
+ require "helper"
2
+
3
+ class Diamond::OSCTest < Test::Unit::TestCase
4
+
5
+ context "OSC" do
6
+
7
+ context "#enable_parameter_control" do
8
+
9
+ setup do
10
+ @map = [
11
+ { :property => :interval, :address => "/1/rotaryA", :value => (0..1.0) },
12
+ { :property => :transpose, :address => "/1/rotaryB" }
13
+ ]
14
+ @addresses = @map.map { |mapping| mapping[:address] }
15
+ @osc = Diamond::OSC.new(:server_port => 8000)
16
+ end
17
+
18
+ should "start server" do
19
+ ::OSC::EMServer.any_instance.expects(:run).once
20
+ @osc.enable_parameter_control(Object.new, @map)
21
+ ::OSC::EMServer.any_instance.unstub(:run)
22
+ end
23
+
24
+ should "assign map" do
25
+ ::OSC::EMServer.any_instance.expects(:add_method).times(@map.size).with do |arg|
26
+ assert @addresses.include?(arg)
27
+ end
28
+ @osc.enable_parameter_control(Object.new, @map)
29
+ ::OSC::EMServer.any_instance.unstub(:add_method)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,63 @@
1
+ require "helper"
2
+
3
+ class PatternTest < Test::Unit::TestCase
4
+
5
+ context "Pattern" do
6
+
7
+ setup do
8
+ ::OSC::EMServer.any_instance.stubs(:run).returns(:true)
9
+ end
10
+
11
+ context "#initialize" do
12
+
13
+ should "create usable pattern" do
14
+ pattern = Diamond::Pattern.new("test") do |range, interval|
15
+ 0.upto(range).map { |num| num * interval }
16
+ end
17
+ result = pattern.compute(2, 12)
18
+ assert_equal [0, 12, 24], result
19
+ end
20
+
21
+ end
22
+
23
+ context "#compute" do
24
+
25
+ should "reflect range and interval" do
26
+ pattern = Diamond::Pattern.find("Up")
27
+ result = pattern.compute(3, 7)
28
+ assert_equal 4, result.length
29
+ assert_equal [0, 7, 14, 21], result
30
+ end
31
+
32
+ end
33
+ context "Presets" do
34
+
35
+ should "populate Up" do
36
+ pattern = Diamond::Pattern.find("Up")
37
+ result = pattern.compute(2, 12)
38
+ assert_equal [0, 12, 24], result
39
+ end
40
+
41
+ should "populate Down" do
42
+ pattern = Diamond::Pattern.find("Down")
43
+ result = pattern.compute(2, 12)
44
+ assert_equal [24, 12, 0], result
45
+ end
46
+
47
+ should "populate UpDown" do
48
+ pattern = Diamond::Pattern.find("UpDown")
49
+ result = pattern.compute(3, 12)
50
+ assert_equal [0, 12, 24, 36, 24, 12, 0], result
51
+ end
52
+
53
+ should "populate DownUp" do
54
+ pattern = Diamond::Pattern.find("DownUp")
55
+ result = pattern.compute(3, 12)
56
+ assert_equal [36, 24, 12, 0, 12, 24, 36], result
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ end