jsound 0.1.0-java → 0.1.2-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -1
- data/INTRO.md +159 -0
- data/README.md +22 -46
- data/examples/harmonize.rb +25 -0
- data/examples/harmonize2.rb +23 -0
- data/examples/launchpad/launchpad_generator.rb +37 -35
- data/examples/launchpad/{launchpad.rb → launchpad_monitor.rb} +12 -19
- data/examples/list_devices.rb +4 -3
- data/examples/monitor.rb +9 -9
- data/examples/play_notes.rb +20 -0
- data/examples/transpose.rb +21 -0
- data/lib/jsound/midi.rb +0 -1
- data/lib/jsound/midi/device.rb +9 -4
- data/lib/jsound/midi/device_list.rb +19 -0
- data/lib/jsound/midi/devices/generator.rb +1 -1
- data/lib/jsound/midi/devices/recorder.rb +4 -0
- data/lib/jsound/midi/devices/transformer.rb +35 -9
- data/lib/jsound/midi/message.rb +1 -1
- data/lib/jsound/type_from_class_name.rb +1 -1
- data/spec/jsound/midi/device_spec.rb +12 -1
- data/spec/jsound/midi/devices/recorder_spec.rb +12 -0
- data/spec/jsound/midi/devices/transformer_spec.rb +29 -0
- data/spec/jsound/midi/devlice_list_spec.rb +28 -0
- data/spec/jsound/type_from_class_name_spec.rb +1 -1
- metadata +15 -15
- data/examples/harmonizer.rb +0 -25
- data/examples/notes.rb +0 -90
- data/examples/transposer.rb +0 -20
- data/lib/jsound/midi/devices/repeater.rb +0 -28
- data/spec/jsound/midi/devices/repeater_device_spec.rb +0 -19
data/.yardopts
CHANGED
data/INTRO.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
Synopsis
|
2
|
+
--------
|
3
|
+
|
4
|
+
JSound provides cross-platform [MIDI](http://en.wikipedia.org/wiki/Musical_Instrument_Digital_Interface) support for Ruby.
|
5
|
+
|
6
|
+
Java's cross-platform standard library includes a
|
7
|
+
[sound API for MIDI and sampled sound](http://download.oracle.com/javase/tutorial/sound/index.html).
|
8
|
+
JSound builds on this API and provides a Ruby interface via [JRuby](http://jruby.org).
|
9
|
+
|
10
|
+
For now, JSound is focusd on use of the [javax.sound.midi API](http://download.oracle.com/javase/7/docs/api/javax/sound/midi/package-summary.html)
|
11
|
+
in Ruby scripts and applications.
|
12
|
+
Support for the [javax.sound.samples API](http://download.oracle.com/javase/7/docs/api/javax/sound/sampled/package-summary.html)
|
13
|
+
may arrive in the future.
|
14
|
+
|
15
|
+
<br>
|
16
|
+
project info @ http://github.com/adamjmurray/jsound
|
17
|
+
|
18
|
+
<br>
|
19
|
+
Core Concepts
|
20
|
+
-------------
|
21
|
+
|
22
|
+
### Messages ###
|
23
|
+
|
24
|
+
Messages repesent musical events, such as a note being played or a musical parameter changing (vibrato, FX level, pitch bend, etc).
|
25
|
+
|
26
|
+
JSound provides direct support for various MIDI message, which can be found in the {JSound::Midi::Messages} module.
|
27
|
+
|
28
|
+
Generic support for other message types is provided by the {JSound::Midi::Message} class.
|
29
|
+
|
30
|
+
<br>
|
31
|
+
### Devices ###
|
32
|
+
|
33
|
+
Devices send messages to one another to communicate musical information. There are 3 general categories:
|
34
|
+
|
35
|
+
* __Input Devices__ receive messages from external hardware (e.g. keyboards, digital drums) or other software
|
36
|
+
* __Output Devices__ send messages to external hardware (e.g. synthesizers) or other software
|
37
|
+
* __Custom Devices__ process messages in arbitrary ways (e.g. generate, record, transform, route)
|
38
|
+
|
39
|
+
JSound provides the input and output devices via the {JSound::Midi::INPUTS} and {JSound::Midi::OUTPUTS} constants.
|
40
|
+
INPUTS and OUTPUTS are {JSound::Midi::DeviceList}s, which provide methods for looking up specific devices.
|
41
|
+
|
42
|
+
You make your own custom devices with Ruby by subclassing the {JSound::Midi::Device} class,
|
43
|
+
or use some of the custom devices provided in the {JSound::Midi::Devices} module.
|
44
|
+
|
45
|
+
<br>
|
46
|
+
### Device Graphs ###
|
47
|
+
|
48
|
+
In order to do something useful, devices must be connected together. Input and Output devices interact with the
|
49
|
+
outside world but don't do anything useful by themselves. Custom devices have practically unlimited
|
50
|
+
possibilities, but are useless without input or output.
|
51
|
+
|
52
|
+
To connect devices together to form a device graph, we use the {JSound::Midi::Device#>>} operator.
|
53
|
+
|
54
|
+
Examples:
|
55
|
+
|
56
|
+
record input:
|
57
|
+
input >> recorder
|
58
|
+
|
59
|
+
generate output:
|
60
|
+
generator >> output
|
61
|
+
|
62
|
+
transform a MIDI stream:
|
63
|
+
input >> transformer >> output
|
64
|
+
|
65
|
+
|
66
|
+
<br>
|
67
|
+
Using JSound
|
68
|
+
------------
|
69
|
+
|
70
|
+
0. Install JRuby 1.5 or 1.6, either:
|
71
|
+
- via manual installation
|
72
|
+
- Download and unpack the [current binary version of JRuby](http://jruby.org/download)
|
73
|
+
- Put the JRuby bin folder on your PATH
|
74
|
+
- or via [rvm](https://rvm.beginrescueend.com/) (adjust version number as desired):
|
75
|
+
|
76
|
+
rvm install jruby-1.6.2
|
77
|
+
rvm use jruby-1.6.2
|
78
|
+
|
79
|
+
0. Install JSound
|
80
|
+
|
81
|
+
jgem install jsound
|
82
|
+
|
83
|
+
0. Try the examples (monitor.rb prints any input it receives, try playing a MIDI keyboard):
|
84
|
+
|
85
|
+
mkdir tmp
|
86
|
+
cd tmp
|
87
|
+
jgem unpack jsound
|
88
|
+
cd jsound-{version}
|
89
|
+
jruby examples/list_devices.rb
|
90
|
+
jruby examples/monitor.rb
|
91
|
+
...
|
92
|
+
|
93
|
+
0. Explore this documentation
|
94
|
+
|
95
|
+
|
96
|
+
<br>
|
97
|
+
Examples
|
98
|
+
--------
|
99
|
+
|
100
|
+
* {file:examples/list_devices.rb list_devices.rb}
|
101
|
+
|
102
|
+
* {file:examples/monitor.rb monitor.rb}
|
103
|
+
|
104
|
+
* {file:examples/play_notes.rb play_notes.rb}
|
105
|
+
|
106
|
+
* {file:examples/transpose.rb transpose.rb}
|
107
|
+
|
108
|
+
* {file:examples/harmonize.rb harmonize.rb}
|
109
|
+
|
110
|
+
* {file:examples/harmonize2.rb harmonize2.rb}
|
111
|
+
|
112
|
+
|
113
|
+
<br>
|
114
|
+
Notes
|
115
|
+
-----
|
116
|
+
|
117
|
+
### Opening Devices ###
|
118
|
+
|
119
|
+
Input and output devices will not function until they are opened via {JSound::Midi::Device#open}.
|
120
|
+
If an input or output device does not appear to be working, you probably forgot to open it.
|
121
|
+
|
122
|
+
Some of the {JSound::Midi::DeviceList} methods will automatically open the device for you.
|
123
|
+
The {JSound::Midi::DeviceList#method_missing} behavior for DeviceList provides a convenient way to
|
124
|
+
find and automatically open devices.
|
125
|
+
|
126
|
+
|
127
|
+
<br>
|
128
|
+
### OS X ###
|
129
|
+
|
130
|
+
#### IAC Driver ####
|
131
|
+
By enabling the IAC (inter-application communication) driver, you can easily interface with any MIDI-enabled application:
|
132
|
+
|
133
|
+
0. Run to /Applications/Utilities/Audio MIDI Setup
|
134
|
+
|
135
|
+
0. In the menu, select: Window → Show MIDI Window
|
136
|
+
|
137
|
+
0. Double click IAC Driver
|
138
|
+
|
139
|
+
0. Check the box where it says "Device is online"
|
140
|
+
|
141
|
+
Now JSound should be able to locate an "IAC Driver" input and output device.
|
142
|
+
|
143
|
+
You can also add additional MIDI ports here, to work with multiple applications simultaneously.
|
144
|
+
|
145
|
+
#### Output ####
|
146
|
+
|
147
|
+
OS X does not provide a default MIDI output device, so you will need to take some extra steps to hear anything.
|
148
|
+
|
149
|
+
Typically, you'll want to route MIDI to your music production environment (Logic, Reason, Live, etc) using the IAC driver.
|
150
|
+
|
151
|
+
If you don't have a music production environment, you can use the free program SimpleSynth as an output:
|
152
|
+
|
153
|
+
0. Download SimpleSynth from http://notahat.com/simplesynth
|
154
|
+
|
155
|
+
0. Run SimpleSynth
|
156
|
+
|
157
|
+
0. Select "SimpleSynth virtual input"
|
158
|
+
|
159
|
+
0. Open the SimpleSynth output device in JSound (i.e. Midi::OUTPUTS.SimpleSynth)
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
JSound: a Ruby wrapper for the Java sound API
|
2
2
|
=============================================
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
JSound provides cross-platform [MIDI](http://en.wikipedia.org/wiki/Musical_Instrument_Digital_Interface) support for Ruby.
|
5
|
+
|
6
|
+
This library requires [JRuby](http://jruby.org).
|
6
7
|
|
7
8
|
|
8
9
|
|
@@ -13,65 +14,25 @@ Home: http://github.com/adamjmurray/jsound
|
|
13
14
|
|
14
15
|
Author: Adam Murray (adam@compusition.com)
|
15
16
|
|
16
|
-
License: Distributed under a permissive BSD-style license, see LICENSE.txt
|
17
|
+
License: Distributed under a permissive BSD-style license, [see LICENSE.txt](LICENSE.txt)
|
17
18
|
|
18
19
|
|
19
20
|
|
20
21
|
Getting started
|
21
22
|
---------------
|
22
23
|
|
23
|
-
|
24
|
-
- via manual installation
|
25
|
-
- Download and unpack the [current binary version of JRuby](http://jruby.org/download)
|
26
|
-
- Put the JRuby bin folder on your PATH
|
27
|
-
- or via [rvm](https://rvm.beginrescueend.com/) (adjust version number as desired):
|
28
|
-
|
29
|
-
rvm install jruby-1.6.2
|
30
|
-
rvm use jruby-1.6.2
|
31
|
-
|
32
|
-
0. Install JSound
|
33
|
-
|
34
|
-
jgem install jsound
|
35
|
-
|
36
|
-
0. Try the examples (monitor.rb prints any input it receives, try playing a MIDI keyboard):
|
37
|
-
|
38
|
-
jruby examples/list_devices.rb
|
39
|
-
jruby examples/monitor.rb
|
40
|
-
|
41
|
-
0. Take a look at the comments in examples/notes.rb for info on:
|
42
|
-
- routing MIDI inputs to MIDI outputs
|
43
|
-
- generating MIDI events and sending them to an output
|
24
|
+
See [the documentation's introduction](INTRO.md).
|
44
25
|
|
45
26
|
|
46
27
|
|
47
28
|
Documentation
|
48
29
|
-------------
|
49
30
|
|
50
|
-
Gem: http://rubydoc.info/gems/jsound/0.
|
31
|
+
Gem: http://rubydoc.info/gems/jsound/0.1.2/frames
|
51
32
|
|
52
33
|
Latest for source: http://rubydoc.info/github/adamjmurray/jsound/master/frames
|
53
34
|
|
54
35
|
|
55
|
-
Notes
|
56
|
-
-----
|
57
|
-
|
58
|
-
### OS X ###
|
59
|
-
|
60
|
-
By enabling the IAC (inter-application communication) driver, you can easily interface with any MIDI-enabled application:
|
61
|
-
|
62
|
-
0. Run to /Applications/Utilities/Audio MIDI Setup
|
63
|
-
|
64
|
-
0. In the menu, select: Window → Show MIDI Window
|
65
|
-
|
66
|
-
0. Double click IAC Driver
|
67
|
-
|
68
|
-
0. Check the box where it says "Device is online"
|
69
|
-
|
70
|
-
Now JSound should be able to locate an "IAC Driver" input and output device.
|
71
|
-
|
72
|
-
You can also add additional MIDI ports here, to work with multiple applications simultaneously.
|
73
|
-
|
74
|
-
|
75
36
|
|
76
37
|
Development Notes
|
77
38
|
-----------------
|
@@ -90,7 +51,22 @@ and to quickly check compatibility with multiple JRuby versions via rvm:
|
|
90
51
|
yard
|
91
52
|
open doc/frames.html
|
92
53
|
|
54
|
+
or, to automatically refresh the documentation as you work:
|
55
|
+
|
56
|
+
yard server -r
|
57
|
+
open http://localhost:8808
|
58
|
+
|
93
59
|
|
94
60
|
### Project Roadmap ###
|
95
61
|
|
96
|
-
https://www.pivotaltracker.com/projects/85719
|
62
|
+
https://www.pivotaltracker.com/projects/85719
|
63
|
+
|
64
|
+
|
65
|
+
Changelog
|
66
|
+
---------
|
67
|
+
|
68
|
+
* June 18, 2011, version 0.1.2
|
69
|
+
- added a "Hash of mappings" feature to Transformer, for easy implementation of simple transformation patterns
|
70
|
+
- improved examples
|
71
|
+
- got rid of the Repeater device and built its functionality into the base Device
|
72
|
+
- added #output= and #>> to DeviceList, to construct parallel paths in device graphs
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#
|
2
|
+
# EXAMPLE: Harmonize pitches by adding a second transposed pitch
|
3
|
+
#
|
4
|
+
require 'rubygems'
|
5
|
+
require 'jsound'
|
6
|
+
include JSound
|
7
|
+
|
8
|
+
# open the input & output matching the first & second command line arg, or default to the first available
|
9
|
+
input = ARGV[0] ? Midi::INPUTS / ARGV[0] : Midi::INPUTS.open_first
|
10
|
+
output = ARGV[1] ? Midi::OUTPUTS/ ARGV[1] : Midi::OUTPUTS.open_first
|
11
|
+
|
12
|
+
harmonizer = Midi::Devices::Transformer.new do |message|
|
13
|
+
if message.respond_to? :pitch
|
14
|
+
transposed = message.clone # messages are mutable, so we have to clone them to keep the original intact
|
15
|
+
transposed.pitch += 3 # transpose up a minor third
|
16
|
+
[message, transposed]
|
17
|
+
|
18
|
+
else # pass through everything else
|
19
|
+
message
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
input >> harmonizer >> output
|
24
|
+
|
25
|
+
sleep 5 while true # force script to keep running
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#
|
2
|
+
# EXAMPLE: Harmonize pitches by adding a second transposed pitch
|
3
|
+
#
|
4
|
+
# Alternate implementation, compare with the harmonize.rb example.
|
5
|
+
# This approach simplifies the transformer, but requires a more complex device graph.
|
6
|
+
#
|
7
|
+
require 'rubygems'
|
8
|
+
require 'jsound'
|
9
|
+
include JSound
|
10
|
+
|
11
|
+
# open the input & output matching the first & second command line arg, or default to the first available
|
12
|
+
input = ARGV[0] ? Midi::INPUTS / ARGV[0] : Midi::INPUTS.open_first
|
13
|
+
output = ARGV[1] ? Midi::OUTPUTS/ ARGV[1] : Midi::OUTPUTS.open_first
|
14
|
+
|
15
|
+
pass_through = Midi::Device.new
|
16
|
+
transposer = Midi::Devices::Transformer.new :pitch => lambda{|p| p + 3 } # transpose up a minor third
|
17
|
+
|
18
|
+
input >> Midi.DeviceList(pass_through, transposer) >> output
|
19
|
+
# connecting a custom device list creates a graph of parallel chains:
|
20
|
+
# input ==> pass_through ==> output
|
21
|
+
# input ==> transposer ==> output
|
22
|
+
|
23
|
+
sleep 5 while true # force script to keep running
|
@@ -1,37 +1,11 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require 'examples/launchpad/launchpad_generator.rb'
|
8
|
-
include JSound::Midi
|
9
|
-
lp = OUTPUTS.Launchpad
|
10
|
-
g = LaunchpadGenerator.new
|
11
|
-
g >> lp
|
12
|
-
|
13
|
-
g.all_off
|
14
|
-
|
15
|
-
# Make the top left grid button green:
|
16
|
-
g.grid(0,0,:green)
|
17
|
-
|
18
|
-
# Make the mixer button red:
|
19
|
-
g.mixer(:red)
|
20
|
-
|
21
|
-
# Make the second 'scene launch' (rightmost column of buttons) orange:
|
22
|
-
g.scene_launch(2,:orange)
|
23
|
-
|
24
|
-
# and we can also do yellow:
|
25
|
-
g.grid(1,1,:yellow)
|
26
|
-
|
27
|
-
# and arbitrary mixtures of green and red LEDs (brightness levels for each range from 0-3)
|
28
|
-
g.grid(2,2,[1,1])
|
29
|
-
|
30
|
-
at_exit { g.all_off } # this will turn everything off when you exit irb
|
31
|
-
|
32
|
-
=end
|
1
|
+
#
|
2
|
+
# EXAMPLE: A device for controlling a Novation Launchpad
|
3
|
+
#
|
4
|
+
require 'rubygems'
|
5
|
+
require 'jsound'
|
6
|
+
include JSound
|
33
7
|
|
34
|
-
class LaunchpadGenerator <
|
8
|
+
class LaunchpadGenerator < Midi::Devices::Generator
|
35
9
|
|
36
10
|
def grid(x,y,color=3)
|
37
11
|
x = clip(x,0,15)
|
@@ -107,5 +81,33 @@ class LaunchpadGenerator < JSound::Midi::Devices::Generator
|
|
107
81
|
r = clip(r.to_i,0,3)
|
108
82
|
return 16*g + r
|
109
83
|
end
|
110
|
-
|
111
|
-
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
gen = LaunchpadGenerator.new
|
89
|
+
gen >> Midi::OUTPUTS.Launchpad
|
90
|
+
|
91
|
+
# clear all the lights at the start of the script
|
92
|
+
gen.all_off
|
93
|
+
# ... and at the end
|
94
|
+
at_exit { gen.all_off }
|
95
|
+
|
96
|
+
8.times do |y|
|
97
|
+
8.times do |x|
|
98
|
+
gen.grid x, y, :green
|
99
|
+
sleep 0.02
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
8.times do |n|
|
104
|
+
gen.control_row n, :amber
|
105
|
+
gen.scene_launch n, :red
|
106
|
+
sleep 0.2
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Other things to try:
|
111
|
+
# g.mixer(:red) # make the mixer button red
|
112
|
+
# g.grid(2,2,[1,1]) # arbitrary mixture of green and red LEDs (brightness levels for each range from 0-3)
|
113
|
+
|
@@ -1,16 +1,14 @@
|
|
1
|
-
|
1
|
+
#
|
2
|
+
# EXAMPLE: monitoring Novation Launchpad input using custom Messages
|
3
|
+
#
|
4
|
+
# Note: I have to hit a button on the top row of my Launchpad before I can receive any input.
|
5
|
+
#
|
2
6
|
require 'rubygems'
|
3
7
|
require 'jsound'
|
4
|
-
include JSound
|
5
|
-
|
6
|
-
# A quick prototype of monitoring input from a Novation Launchpad,
|
7
|
-
# after translating it to something more meaningful than the raw MIDI messages.
|
8
|
-
|
9
|
-
# Note: I have to hit a button on the top row of my Launchpad before I can receive any input.
|
10
|
-
|
8
|
+
include JSound
|
11
9
|
|
12
10
|
# A higher-level representation of the messages coming from the Launchpad
|
13
|
-
class LaunchpadMessage <
|
11
|
+
class LaunchpadMessage < Midi::Message
|
14
12
|
def initialize(button_group, position, pressed, channel=0, source=nil)
|
15
13
|
@type = :launchpad_button
|
16
14
|
@button_group = button_group
|
@@ -23,8 +21,8 @@ class LaunchpadMessage < Messages::Message
|
|
23
21
|
end
|
24
22
|
|
25
23
|
# A device that converts the lower-level MIDI messages into LaunchpadMessages
|
26
|
-
class LaunchadTranslator <
|
27
|
-
def
|
24
|
+
class LaunchadTranslator < Midi::Device
|
25
|
+
def message(message)
|
28
26
|
case message.type
|
29
27
|
|
30
28
|
when :control_change
|
@@ -53,13 +51,8 @@ class LaunchadTranslator < Devices::Device
|
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
56
|
-
|
57
|
-
# And now that those classes are defined,
|
54
|
+
# And now that those classes are defined,
|
58
55
|
# just hook up the Launchpad to a monitor, with the translator in between:
|
59
|
-
INPUTS.Launchpad >> LaunchadTranslator.new >> Devices::Monitor.new
|
56
|
+
Midi::INPUTS.Launchpad >> LaunchadTranslator.new >> Midi::Devices::Monitor.new
|
60
57
|
|
61
|
-
|
62
|
-
# force the script to keep running (MIDI devices run in a background thread)
|
63
|
-
while(true)
|
64
|
-
sleep 5
|
65
|
-
end
|
58
|
+
sleep 5 while true # force script to keep running
|
data/examples/list_devices.rb
CHANGED
data/examples/monitor.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
|
1
|
+
#
|
2
|
+
# EXAMPLE: Monitor all MIDI inputs
|
3
|
+
#
|
2
4
|
require 'rubygems'
|
3
5
|
require 'jsound'
|
4
|
-
include JSound
|
6
|
+
include JSound
|
5
7
|
|
6
|
-
|
8
|
+
monitor = Midi::Devices::Monitor.new
|
9
|
+
|
10
|
+
for input in Midi::INPUTS
|
7
11
|
input.open
|
8
|
-
input >>
|
12
|
+
input >> monitor
|
9
13
|
end
|
10
14
|
|
11
|
-
# force
|
12
|
-
while(true)
|
13
|
-
sleep 5
|
14
|
-
end
|
15
|
-
|
15
|
+
sleep 5 while true # force script to keep running
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#
|
2
|
+
# EXAMPLE: generate and play some notes
|
3
|
+
#
|
4
|
+
require 'rubygems'
|
5
|
+
require 'jsound'
|
6
|
+
include JSound
|
7
|
+
|
8
|
+
generator = Midi::Devices::Generator.new
|
9
|
+
|
10
|
+
# open the output matching the first command line arg, or default to the first available
|
11
|
+
output = ARGV[0] ? Midi::OUTPUTS/ ARGV[0] : Midi::OUTPUTS.open_first
|
12
|
+
|
13
|
+
generator >> output
|
14
|
+
|
15
|
+
13.times do |interval|
|
16
|
+
pitch = 60+interval
|
17
|
+
generator.note_on pitch, 100
|
18
|
+
sleep 0.5
|
19
|
+
generator.note_off pitch
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# EXAMPLE: using a Transformer to transpose pitches up 2 octaves
|
3
|
+
#
|
4
|
+
require 'rubygems'
|
5
|
+
require 'jsound'
|
6
|
+
include JSound
|
7
|
+
|
8
|
+
# open the input & output matching the first & second command line arg, or default to the first available
|
9
|
+
input = ARGV[0] ? Midi::INPUTS / ARGV[0] : Midi::INPUTS.open_first
|
10
|
+
output = ARGV[1] ? Midi::OUTPUTS/ ARGV[1] : Midi::OUTPUTS.open_first
|
11
|
+
|
12
|
+
# Using a block like this is most flexible way to implement a tranformer.
|
13
|
+
# This particular transformer has a nice shortcut. See the harmonize2.rb example
|
14
|
+
transposer = Midi::Devices::Transformer.new do |message|
|
15
|
+
message.pitch += 24 if message.respond_to? :pitch # transpose up two octaves (24 semitones)
|
16
|
+
message
|
17
|
+
end
|
18
|
+
|
19
|
+
input >> transposer >> output
|
20
|
+
|
21
|
+
sleep 5 while true # force script to keep running
|
data/lib/jsound/midi.rb
CHANGED
@@ -16,7 +16,6 @@ require 'jsound/midi/devices/input_device'
|
|
16
16
|
require 'jsound/midi/devices/output_device'
|
17
17
|
require 'jsound/midi/devices/monitor'
|
18
18
|
require 'jsound/midi/devices/recorder'
|
19
|
-
require 'jsound/midi/devices/repeater'
|
20
19
|
require 'jsound/midi/devices/transformer'
|
21
20
|
|
22
21
|
require 'jsound/midi/device_list'
|
data/lib/jsound/midi/device.rb
CHANGED
@@ -4,7 +4,7 @@ module JSound
|
|
4
4
|
# A device that can transmit and/or receive messages (typically MIDI messages).
|
5
5
|
# This default implementation simply passes through all messages.
|
6
6
|
class Device
|
7
|
-
include JSound::
|
7
|
+
include JSound::TypeFromClassName
|
8
8
|
|
9
9
|
# Open the device and allocate the needed resources so that it can send and receive messages
|
10
10
|
# @note this operation is typically only relevant for Java-based devices such as {Devices::InputDevice} and {Devices::OutputDevice}
|
@@ -48,12 +48,17 @@ module JSound
|
|
48
48
|
self.output= device
|
49
49
|
end
|
50
50
|
|
51
|
-
# Send a message to this device
|
51
|
+
# Send a message to this device and pass it through to any output(s)
|
52
52
|
# @param [Message]
|
53
53
|
# @see #<=
|
54
54
|
def message(message)
|
55
|
-
|
56
|
-
|
55
|
+
if @output.respond_to? :each
|
56
|
+
for device in @output
|
57
|
+
device.message(message)
|
58
|
+
end
|
59
|
+
elsif @output
|
60
|
+
@output.message(message)
|
61
|
+
end
|
57
62
|
end
|
58
63
|
|
59
64
|
# send a message to this device. shortcut for {#message}
|
@@ -14,6 +14,12 @@ module JSound
|
|
14
14
|
@devices = list
|
15
15
|
end
|
16
16
|
|
17
|
+
def each
|
18
|
+
for device in devices
|
19
|
+
yield device
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
17
23
|
# Find the first {Device} matching the criteria
|
18
24
|
#
|
19
25
|
# @param criteria matches description against for a Regexp argument, matches the device field against the value for a Hash argument, otherwise checks for an equal description
|
@@ -103,6 +109,14 @@ module JSound
|
|
103
109
|
open @devices.first
|
104
110
|
end
|
105
111
|
|
112
|
+
# Connect the output to all devices in this device list
|
113
|
+
def output= output
|
114
|
+
for device in devices
|
115
|
+
device.output = output
|
116
|
+
end
|
117
|
+
end
|
118
|
+
alias :>> :output=
|
119
|
+
|
106
120
|
def to_s
|
107
121
|
@devices.join("\n")
|
108
122
|
end
|
@@ -153,5 +167,10 @@ module JSound
|
|
153
167
|
|
154
168
|
end
|
155
169
|
|
170
|
+
def DeviceList(*devices)
|
171
|
+
DeviceList.new(devices)
|
172
|
+
end
|
173
|
+
module_function :DeviceList
|
174
|
+
|
156
175
|
end
|
157
176
|
end
|
@@ -3,7 +3,7 @@ module JSound
|
|
3
3
|
module Devices
|
4
4
|
|
5
5
|
# A device which provides methods for generating MIDI command messages.
|
6
|
-
#
|
6
|
+
# @see JSound::Midi::MessageBuilder
|
7
7
|
class Generator < Device
|
8
8
|
|
9
9
|
# For all the methods defined in the MessageBuilder module (note_on, pitch_bend, control_change, etc),
|
@@ -4,21 +4,47 @@ module JSound
|
|
4
4
|
|
5
5
|
class Transformer < Device
|
6
6
|
|
7
|
+
# A Hash of mappings, with entries in the format {:message_attribute => lambda{|attribute| ... } }
|
8
|
+
attr_accessor :mappings
|
9
|
+
|
10
|
+
# If true, messages will be automatically cloned when received by this device.
|
11
|
+
# Messages are mutable, so in many cases it's important to clone a message before changing it.
|
12
|
+
attr_accessor :clone
|
13
|
+
|
7
14
|
# The transformation block: a lambda that takes a message and returns
|
8
15
|
# either a transformed message or an Enumerable list of messages
|
9
|
-
attr_accessor :
|
16
|
+
attr_accessor :transform
|
10
17
|
|
11
|
-
|
12
|
-
|
18
|
+
# @param mappings [Hash] a Hash of mappings (see {#mappings}).
|
19
|
+
# When not nil, {#clone} will be automatically set to true.
|
20
|
+
# Set {#clone} to false by including the entry \{:clone => false}
|
21
|
+
# @param &transform see {#transform}
|
22
|
+
def initialize(mappings=nil, &transform)
|
23
|
+
@clone = !!mappings.fetch(:clone, true) if mappings
|
24
|
+
mappings.delete :clone if mappings
|
25
|
+
@mappings = mappings
|
26
|
+
@transform = transform
|
13
27
|
end
|
14
28
|
|
15
29
|
def message(message)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
30
|
+
message = message.clone if @clone
|
31
|
+
|
32
|
+
for attr, mapping in @mappings
|
33
|
+
setter = "#{attr}="
|
34
|
+
message.send setter, mapping[message.send attr] if message.respond_to? attr and message.respond_to? setter
|
35
|
+
end if @mappings
|
36
|
+
|
37
|
+
if @output
|
38
|
+
if @transform
|
39
|
+
message = @transform[message]
|
40
|
+
if message.is_a? Enumerable
|
41
|
+
message.each{|m| @output.message(m) }
|
42
|
+
else
|
43
|
+
super(message) if message
|
44
|
+
end
|
45
|
+
|
46
|
+
elsif @clone or !@mappings.empty?
|
47
|
+
super(message)
|
22
48
|
end
|
23
49
|
end
|
24
50
|
end
|
data/lib/jsound/midi/message.rb
CHANGED
@@ -6,7 +6,7 @@ module JSound
|
|
6
6
|
# See http://www.midi.org/techspecs/midimessages.php
|
7
7
|
# for info on how the MIDI spec defines these messages.
|
8
8
|
class Message
|
9
|
-
include JSound::
|
9
|
+
include JSound::TypeFromClassName
|
10
10
|
|
11
11
|
# The MIDI input {Device} which received this message.
|
12
12
|
attr_reader :source
|
@@ -52,10 +52,21 @@ module JSound::Midi
|
|
52
52
|
output.should_receive(:message).with('the_message')
|
53
53
|
device.message 'the_message'
|
54
54
|
end
|
55
|
+
|
56
|
+
it "should pass send the messages to all connected outputs" do
|
57
|
+
output1 = mock "device1"
|
58
|
+
output2 = mock "device2"
|
59
|
+
device >> [output1, output2]
|
60
|
+
|
61
|
+
output1.should_receive(:message).once.with :the_message
|
62
|
+
output2.should_receive(:message).once.with :the_message
|
63
|
+
device.message :the_message
|
64
|
+
end
|
65
|
+
|
55
66
|
end
|
56
67
|
|
57
68
|
describe '#<=' do
|
58
|
-
it 'should behave like #
|
69
|
+
it 'should behave like #message' do
|
59
70
|
device >> output
|
60
71
|
output.should_receive(:message).with('the_message')
|
61
72
|
device <= 'the_message'
|
@@ -84,5 +84,17 @@ module JSound::Midi::Devices
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
describe "#output=" do
|
88
|
+
it "should raise an error, since outputs cannot have outputs assigned" do
|
89
|
+
lambda{ recorder.output = Device.new }.should raise_error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#>>" do
|
94
|
+
it "should raise an error, since outputs cannot have outputs assigned" do
|
95
|
+
lambda{ device >> Device.new }.should raise_error
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
87
99
|
end
|
88
100
|
end
|
@@ -12,6 +12,35 @@ module JSound::Midi
|
|
12
12
|
output.should_receive(:message).once.with MessageBuilder.note_on(72,100)
|
13
13
|
transformer.message MessageBuilder.note_on(60,100)
|
14
14
|
end
|
15
|
+
|
16
|
+
it "modifies message properties based on the Hash of mappings passed to .new" do
|
17
|
+
transformer = Transformer.new({
|
18
|
+
:attr1 => lambda{|attr| attr+1 },
|
19
|
+
:attr2 => lambda{|attr| attr-1 },
|
20
|
+
})
|
21
|
+
message = mock 'message'
|
22
|
+
message.stub!(:attr1).and_return 1
|
23
|
+
message.stub!(:attr2).and_return 5
|
24
|
+
|
25
|
+
message.should_receive(:attr1=).once.with 2
|
26
|
+
message.should_receive(:attr2=).once.with 4
|
27
|
+
transformer.message message
|
28
|
+
end
|
29
|
+
|
30
|
+
it "automatically clones the message when constructed with a Hash of mappings" do
|
31
|
+
transformer = Transformer.new({ :attr1 => lambda{|attr| attr+1 } })
|
32
|
+
message = mock 'message'
|
33
|
+
message.should_receive :clone
|
34
|
+
transformer.message message
|
35
|
+
end
|
36
|
+
|
37
|
+
it "does not clone the message when constructed with a Hash of mappings that includes {:clone => false}" do
|
38
|
+
transformer = Transformer.new({ :attr1 => lambda{|attr| attr+1 }, :clone => false })
|
39
|
+
message = mock 'message'
|
40
|
+
message.should_not_receive :clone
|
41
|
+
transformer.message message
|
42
|
+
end
|
43
|
+
|
15
44
|
end
|
16
45
|
|
17
46
|
end
|
@@ -20,6 +20,14 @@ module JSound::Midi
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
describe "#each" do
|
24
|
+
it "yields each device" do
|
25
|
+
yielded = []
|
26
|
+
device_list.each{|device| yielded << device }
|
27
|
+
yielded.should == devices
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
23
31
|
describe "#open" do
|
24
32
|
it "acts like #find and also opens the device" do
|
25
33
|
device2.should_receive :open
|
@@ -43,6 +51,26 @@ module JSound::Midi
|
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
54
|
+
describe "#output=" do
|
55
|
+
it "passes the argument to each device#open" do
|
56
|
+
output = mock "output"
|
57
|
+
device1.should_receive(:output=).with output
|
58
|
+
device2.should_receive(:output=).with output
|
59
|
+
device3.should_receive(:output=).with output
|
60
|
+
device_list.output = output
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#>>=" do
|
65
|
+
it "acts like #output=" do
|
66
|
+
output = mock "output"
|
67
|
+
device1.should_receive(:output=).with output
|
68
|
+
device2.should_receive(:output=).with output
|
69
|
+
device3.should_receive(:output=).with output
|
70
|
+
device_list >> output
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
46
74
|
describe "#method_missing" do
|
47
75
|
it "acts like #/ for unimplemented methods" do
|
48
76
|
device1.should_receive(:open)
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: jsound
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.1.
|
5
|
+
version: 0.1.2
|
6
6
|
platform: java
|
7
7
|
authors:
|
8
8
|
- Adam Murray
|
@@ -10,10 +10,10 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-18 00:00:00 Z
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
16
|
+
description: A Ruby interface for Java's javax.sound API. Runs on the JVM via JRuby.
|
17
17
|
email: adam@compusition.com
|
18
18
|
executables: []
|
19
19
|
|
@@ -22,9 +22,10 @@ extensions: []
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
|
24
24
|
files:
|
25
|
-
-
|
25
|
+
- INTRO.md
|
26
26
|
- README.md
|
27
27
|
- LICENSE.txt
|
28
|
+
- Rakefile
|
28
29
|
- .yardopts
|
29
30
|
- lib/jsound/convert.rb
|
30
31
|
- lib/jsound/midi/device.rb
|
@@ -35,7 +36,6 @@ files:
|
|
35
36
|
- lib/jsound/midi/devices/monitor.rb
|
36
37
|
- lib/jsound/midi/devices/output_device.rb
|
37
38
|
- lib/jsound/midi/devices/recorder.rb
|
38
|
-
- lib/jsound/midi/devices/repeater.rb
|
39
39
|
- lib/jsound/midi/devices/transformer.rb
|
40
40
|
- lib/jsound/midi/message.rb
|
41
41
|
- lib/jsound/midi/message_builder.rb
|
@@ -54,7 +54,6 @@ files:
|
|
54
54
|
- spec/jsound/midi/devices/generator_spec.rb
|
55
55
|
- spec/jsound/midi/devices/output_device_spec.rb
|
56
56
|
- spec/jsound/midi/devices/recorder_spec.rb
|
57
|
-
- spec/jsound/midi/devices/repeater_device_spec.rb
|
58
57
|
- spec/jsound/midi/devices/transformer_spec.rb
|
59
58
|
- spec/jsound/midi/devlice_list_spec.rb
|
60
59
|
- spec/jsound/midi/message_builder_spec.rb
|
@@ -65,17 +64,18 @@ files:
|
|
65
64
|
- spec/jsound/midi_spec.rb
|
66
65
|
- spec/jsound/type_from_class_name_spec.rb
|
67
66
|
- spec/spec_helper.rb
|
68
|
-
- examples/
|
69
|
-
- examples/
|
67
|
+
- examples/harmonize.rb
|
68
|
+
- examples/harmonize2.rb
|
70
69
|
- examples/launchpad/launchpad_generator.rb
|
70
|
+
- examples/launchpad/launchpad_monitor.rb
|
71
71
|
- examples/list_devices.rb
|
72
72
|
- examples/monitor.rb
|
73
|
-
- examples/
|
74
|
-
- examples/
|
73
|
+
- examples/play_notes.rb
|
74
|
+
- examples/transpose.rb
|
75
75
|
homepage: http://github.com/adamjmurray/jsound
|
76
|
-
licenses:
|
77
|
-
|
78
|
-
post_install_message:
|
76
|
+
licenses:
|
77
|
+
- BSD
|
78
|
+
post_install_message: "NOTE: this gem requires JRuby 1.5+"
|
79
79
|
rdoc_options: []
|
80
80
|
|
81
81
|
require_paths:
|
@@ -92,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
92
|
- - ">="
|
93
93
|
- !ruby/object:Gem::Version
|
94
94
|
version: "0"
|
95
|
-
requirements:
|
96
|
-
|
95
|
+
requirements:
|
96
|
+
- JRuby 1.5+
|
97
97
|
rubyforge_project:
|
98
98
|
rubygems_version: 1.8.5
|
99
99
|
signing_key:
|
data/examples/harmonizer.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
#!/usr/bin/env jruby
|
2
|
-
require 'rubygems'
|
3
|
-
require 'jsound'
|
4
|
-
include JSound::Midi
|
5
|
-
include Devices
|
6
|
-
|
7
|
-
harmonizer = Transformer.new do |message|
|
8
|
-
if message.respond_to? :pitch
|
9
|
-
transposed = message.clone # messages are mutable, so we have to clone them to keep the original in tact
|
10
|
-
transposed.pitch += 3
|
11
|
-
[message, transposed]
|
12
|
-
else
|
13
|
-
message # pass through everything else
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
# Adjust the INPUTS and OUTPUTS as needed to use the devices you want:
|
18
|
-
INPUTS.open_first >> harmonizer >> OUTPUTS.open_first
|
19
|
-
# For example, to send my Akai keyboard through the harmonizer to the OS X IAC bus, I can do:
|
20
|
-
# INPUTS.Akai >> harmonizer >> OUTPUTS.IAC
|
21
|
-
|
22
|
-
# force the script to keep running (MIDI devices run in a background thread)
|
23
|
-
while(true)
|
24
|
-
sleep 5
|
25
|
-
end
|
data/examples/notes.rb
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
#!/usr/bin/env jruby
|
2
|
-
require 'rubygems'
|
3
|
-
require 'jsound'
|
4
|
-
include JSound::Midi
|
5
|
-
|
6
|
-
puts "ALL DEVICES:"
|
7
|
-
puts DEVICES
|
8
|
-
puts
|
9
|
-
puts "DEVICE VENDORS"
|
10
|
-
puts DEVICES.list_all(:vendor).uniq # list_all will include "unknown" values
|
11
|
-
puts
|
12
|
-
puts "INPUT DESCRIPTIONS:"
|
13
|
-
puts INPUTS.list
|
14
|
-
puts
|
15
|
-
puts "OUTPUT DESCRIPTIONS:"
|
16
|
-
puts OUTPUTS.list
|
17
|
-
puts
|
18
|
-
|
19
|
-
|
20
|
-
# The following examples use SimpleSynth as an output.
|
21
|
-
# It's a free program for OS X available http://notahat.com/simplesynth
|
22
|
-
# I assume SimpleSynth's MIDI Source is set to "SimpleSynth virtual input"
|
23
|
-
|
24
|
-
# On Windows or Linux, I *think* you can just use a built-in MIDI output for your soundcard
|
25
|
-
# If you need to do anything special on Windows/Linux, let me know @ http://github.com/adamjmurray
|
26
|
-
# and I'll update this example.
|
27
|
-
|
28
|
-
|
29
|
-
#######################################################
|
30
|
-
## FINDING INPUTS AND OUTPUTS
|
31
|
-
##
|
32
|
-
## Based on the input and output descriptions,
|
33
|
-
## you can locate the first device matching a description:
|
34
|
-
#> OUTPUTS/'SimpleSynth'
|
35
|
-
#
|
36
|
-
## or just:
|
37
|
-
#> OUTPUTS/:SimpleSynth
|
38
|
-
#
|
39
|
-
## the / operator is a shortcut for
|
40
|
-
#> OUTPUTS.find /SimpleSynth/
|
41
|
-
#
|
42
|
-
## which has some more advanced options:
|
43
|
-
#> OUTPUTS.find :vendor => 'M-Audio'
|
44
|
-
#
|
45
|
-
## Use Strings for exact matches and Regexp for partial matches
|
46
|
-
## The find method returns the first match, find_all will return all of them:
|
47
|
-
#> OUTPUTS.find_all :name => /(Novation|M-Audio)/
|
48
|
-
#
|
49
|
-
## All of these lookup options work for the INPUTS collection too:
|
50
|
-
#> INPUTS/'M-Audio'
|
51
|
-
|
52
|
-
|
53
|
-
######################################################
|
54
|
-
## ROUTING INPUTS TO OUTPUTS
|
55
|
-
##
|
56
|
-
## Once you have an input and an output, just connect them like so:
|
57
|
-
#> input = INPUTS/:Akai
|
58
|
-
#> output = OUTPUTS/:SimpleSynth
|
59
|
-
#> input >> output
|
60
|
-
#
|
61
|
-
## If you are sure the inputs and outputs exist, you can do it altogether:
|
62
|
-
#> INPUTS/:Akai >> OUTPUTS/:SimpleSynth
|
63
|
-
|
64
|
-
|
65
|
-
######################################################
|
66
|
-
## MONITORING INPUT
|
67
|
-
##
|
68
|
-
## Route to an instance of JSound::Midi::Devices::Monitor
|
69
|
-
#> input >> Devcies::Monitor.new
|
70
|
-
|
71
|
-
|
72
|
-
######################################################
|
73
|
-
## GENERATING NOTES
|
74
|
-
##
|
75
|
-
## See message_builder.rb for list of messages currently supported,
|
76
|
-
## including pitch_bend, control_change, channel_pressure, and more
|
77
|
-
#
|
78
|
-
# include JSound::Midi::Messages::Builder
|
79
|
-
# output = OUTPUTS/:SimpleSynth
|
80
|
-
# output.open
|
81
|
-
# while(true)
|
82
|
-
# output <= note_on(60,70)
|
83
|
-
# sleep 1
|
84
|
-
# output <= note_off(60)
|
85
|
-
# sleep 1
|
86
|
-
# end
|
87
|
-
#
|
88
|
-
## Note that connecting to an output with >> will open the output automatically,
|
89
|
-
## but passing in Messages with << does not.
|
90
|
-
## That's why I explicitly call output.open in the above example.
|
data/examples/transposer.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
#!/usr/bin/env jruby
|
2
|
-
require 'rubygems'
|
3
|
-
require 'jsound'
|
4
|
-
include JSound::Midi
|
5
|
-
include Devices
|
6
|
-
|
7
|
-
transposer = Transformer.new do |message|
|
8
|
-
message.pitch += 24 if message.respond_to? :pitch # transpose up two octaves
|
9
|
-
message
|
10
|
-
end
|
11
|
-
|
12
|
-
# Adjust the INPUTS and OUTPUTS as needed to use the devices you want:
|
13
|
-
INPUTS.open_first >> transposer >> OUTPUTS.open_first
|
14
|
-
# For example, to send my Akai keyboard through the harmonizer to the OS X IAC bus, I can do:
|
15
|
-
# INPUTS.Akai >> transposer >> OUTPUTS.IAC
|
16
|
-
|
17
|
-
# force the script to keep running (MIDI devices run in a background thread)
|
18
|
-
while(true)
|
19
|
-
sleep 5
|
20
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module JSound
|
2
|
-
module Midi
|
3
|
-
module Devices
|
4
|
-
|
5
|
-
# A device which repeats the input message to multiple outputs.
|
6
|
-
class Repeater < Device
|
7
|
-
|
8
|
-
# connect device(s) as the outputs for this device
|
9
|
-
# @param [Enumberable, Device] the device or devices to connect, or nil to disconnect the currently connected device
|
10
|
-
# @see {#>>}
|
11
|
-
def output= device
|
12
|
-
device = [device] if not device.is_a? Enumerable
|
13
|
-
super
|
14
|
-
end
|
15
|
-
|
16
|
-
def message(message)
|
17
|
-
if @output
|
18
|
-
for device in @output
|
19
|
-
device.message(message)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module JSound::Midi::Devices
|
4
|
-
describe Repeater do
|
5
|
-
|
6
|
-
let(:repeater) { Repeater.new }
|
7
|
-
let(:output1) { mock 'device1' }
|
8
|
-
let(:output2) { mock 'device1' }
|
9
|
-
|
10
|
-
before { repeater >> [output1, output2] }
|
11
|
-
|
12
|
-
it "should pass all messages to its outputs" do
|
13
|
-
output1.should_receive(:message).once.with :the_message
|
14
|
-
output2.should_receive(:message).once.with :the_message
|
15
|
-
repeater.message :the_message
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|