├── .travis.yml ├── launchpad.jpg ├── lp4j-api ├── README.md ├── src │ ├── test │ │ └── java │ │ │ └── net │ │ │ └── thecodersbreakfast │ │ │ └── lp4j │ │ │ └── api │ │ │ ├── BrightnessTest.java │ │ │ ├── BufferTest.java │ │ │ ├── LaunchpadExceptionTest.java │ │ │ ├── ScrollSpeedTest.java │ │ │ ├── LaunchpadListenerAdapterTest.java │ │ │ ├── PadTest.java │ │ │ ├── ButtonTest.java │ │ │ └── ColorTest.java │ └── main │ │ └── java │ │ └── net │ │ └── thecodersbreakfast │ │ └── lp4j │ │ └── api │ │ ├── LightIntensity.java │ │ ├── BackBufferOperation.java │ │ ├── LaunchpadListenerAdapter.java │ │ ├── LaunchpadException.java │ │ ├── Buffer.java │ │ ├── Launchpad.java │ │ ├── LaunchpadListener.java │ │ ├── ScrollSpeed.java │ │ ├── Pad.java │ │ ├── Brightness.java │ │ ├── LaunchpadClient.java │ │ ├── Color.java │ │ └── Button.java └── pom.xml ├── lp4j-midi ├── src │ ├── test │ │ └── java │ │ │ └── net.thecodersbreakfast.lp4j.midi │ │ │ ├── MidiLaunchpadTest.java │ │ │ ├── MidiDeviceConfigurationTest.java │ │ │ ├── DefaultMidiProtocolReceiverTest.java │ │ │ ├── DefaultMidiProtocolListenerTest.java │ │ │ ├── DefaultMidiProtocolClientTest.java │ │ │ └── MidiLaunchpadClientTest.java │ └── main │ │ └── java │ │ └── net │ │ └── thecodersbreakfast │ │ └── lp4j │ │ └── midi │ │ ├── protocol │ │ ├── MidiProtocolListener.java │ │ ├── DefaultMidiProtocolListener.java │ │ ├── DefaultMidiProtocolReceiver.java │ │ ├── MidiProtocolClient.java │ │ └── DefaultMidiProtocolClient.java │ │ ├── MidiDeviceConfiguration.java │ │ ├── MidiLaunchpad.java │ │ └── MidiLaunchpadClient.java └── pom.xml ├── lp4j-emu-web ├── src │ └── main │ │ ├── resources │ │ └── web │ │ │ ├── index.html │ │ │ ├── vertxbus.min.js │ │ │ ├── Launchpad.js │ │ │ ├── emulator.js │ │ │ └── sockjs-0.3.min.js │ │ └── java │ │ └── net │ │ └── thecodersbreakfast │ │ └── lp4j │ │ └── emulator │ │ ├── EmulatorLaunchpadClient.java │ │ └── EmulatorLaunchpad.java └── pom.xml ├── README.md ├── pom.xml └── LICENCE.TXT /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk11 4 | 5 | -------------------------------------------------------------------------------- /launchpad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OlivierCroisier/LP4J/HEAD/launchpad.jpg -------------------------------------------------------------------------------- /lp4j-api/README.md: -------------------------------------------------------------------------------- 1 | lp4j 2 | ==== 3 | 4 | LP4J is a client API for the Novation Launchpad S DJ pad. 5 | It provides high- and low-level APIs to send commands to the device, and react when its keys and buttons are pressed. 6 | 7 | The high-level API is MIDI-agnostic, so it can be used with a software emulator. 8 | The low-level API is MIDI-specific, and can be used to send and decode raw MIDI messages. 9 | 10 | 11 | -------------------------------------------------------------------------------- /lp4j-midi/src/test/java/net.thecodersbreakfast.lp4j.midi/MidiLaunchpadTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | public class MidiLaunchpadTest { 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/BrightnessTest.java: -------------------------------------------------------------------------------- 1 | package net.thecodersbreakfast.lp4j.api; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertNotNull; 7 | 8 | public class BrightnessTest { 9 | 10 | @Test 11 | public void valueOf() { 12 | Brightness brightness = Brightness.of(Brightness.MIN_VALUE); 13 | assertNotNull(brightness); 14 | assertEquals(Brightness.MIN_VALUE, brightness.getBrightness()); 15 | } 16 | 17 | @Test(expected = IllegalArgumentException.class) 18 | public void valueOf_tooLow() { 19 | Brightness.of(Brightness.MIN_VALUE - 1); 20 | } 21 | 22 | @Test(expected = IllegalArgumentException.class) 23 | public void valueOf_tooHigh() { 24 | Brightness.of(Brightness.MAX_VALUE + 1); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/LightIntensity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents a light intensity used during led testing (see {@link net.thecodersbreakfast.lp4j.api.LaunchpadClient#testLights(LightIntensity)}. 21 | * 22 | * @author Olivier Croisier (olivier.croisier@gmail.com) 23 | */ 24 | public enum LightIntensity { 25 | /** Low light intensity */ 26 | LOW, 27 | /** Medium light intensity */ 28 | MEDIUM, 29 | /** High light intensity */ 30 | HIGH 31 | } 32 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/BufferTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | 23 | public class BufferTest { 24 | 25 | @Test 26 | public void other_0() { 27 | Buffer other = Buffer.BUFFER_0.other(); 28 | Assert.assertEquals(Buffer.BUFFER_1, other); 29 | } 30 | 31 | @Test 32 | public void other_1() { 33 | Buffer other = Buffer.BUFFER_1.other(); 34 | Assert.assertEquals(Buffer.BUFFER_0, other); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/LaunchpadExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | import org.junit.Test; 20 | 21 | public class LaunchpadExceptionTest { 22 | 23 | @Test 24 | public void testLaunchpadException_noargs() { 25 | new LaunchpadException(); 26 | } 27 | 28 | @Test 29 | public void testLaunchpadException_message() { 30 | new LaunchpadException("message"); 31 | } 32 | 33 | @Test 34 | public void testLaunchpadException_cause() { 35 | new LaunchpadException(new RuntimeException()); 36 | } 37 | 38 | @Test 39 | public void testLaunchpadException_both() { 40 | new LaunchpadException("message", new RuntimeException()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/BackBufferOperation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Describes how the backbuffer is impacted by the operation currently applied to the write buffer. 21 | * 22 | *

The backbuffer is defined as "the buffer that is not the current write buffer". It has nothing to do with which 23 | * buffer is currently visible. 24 | * 25 | * @author Olivier Croisier (olivier.croisier@gmail.com) 26 | */ 27 | public enum BackBufferOperation { 28 | /** Ths operation is applied to the write buffer only, the back buffer is not modified. */ 29 | NONE, 30 | /** The operation is applied to the write buffer and to backbuffer. */ 31 | COPY, 32 | /** The operation is applied to the write buffer, and the corresponding location on the backbuffer is cleared */ 33 | CLEAR 34 | } 35 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/LaunchpadListenerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Convenient empty implementation of a {@link net.thecodersbreakfast.lp4j.api.LaunchpadListener}. 21 | * 22 | * @author Olivier Croisier (olivier.croisier@gmail.com) 23 | */ 24 | public abstract class LaunchpadListenerAdapter implements LaunchpadListener { 25 | 26 | @Override 27 | public void onPadPressed(Pad pad, long timestamp) { 28 | } 29 | 30 | @Override 31 | public void onPadReleased(Pad pad, long timestamp) { 32 | } 33 | 34 | @Override 35 | public void onButtonPressed(Button button, long timestamp) { 36 | } 37 | 38 | @Override 39 | public void onButtonReleased(Button button, long timestamp) { 40 | } 41 | 42 | @Override 43 | public void onTextScrolled(long timestamp) { 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/ScrollSpeedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | import org.junit.Test; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertNotNull; 23 | 24 | public class ScrollSpeedTest { 25 | 26 | @Test 27 | public void valueOf() { 28 | ScrollSpeed scrollSpeed = ScrollSpeed.of(ScrollSpeed.MIN_VALUE); 29 | assertNotNull(scrollSpeed); 30 | assertEquals(ScrollSpeed.MIN_VALUE, scrollSpeed.getScrollSpeed()); 31 | } 32 | 33 | @Test(expected = IllegalArgumentException.class) 34 | public void valueOf_tooLow() { 35 | ScrollSpeed.of(ScrollSpeed.MIN_VALUE - 1); 36 | } 37 | 38 | @Test(expected = IllegalArgumentException.class) 39 | public void valueOf_tooHigh() { 40 | ScrollSpeed.of(ScrollSpeed.MAX_VALUE + 1); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/resources/web/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | Launchpad emulator 20 | 21 | 41 | 42 | 43 |

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lp4j-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 4.0.0 24 | 25 | 26 | net.thecodersbreakfast 27 | lp4j-parent 28 | 1.0 29 | 30 | 31 | lp4j-api 32 | jar 33 | 34 | 35 | 36 | junit 37 | junit 38 | 39 | 40 | org.mockito 41 | mockito-core 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lp4j-emu-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 4.0.0 24 | 25 | 26 | net.thecodersbreakfast 27 | lp4j-parent 28 | 1.0 29 | 30 | 31 | lp4j-emu-web 32 | jar 33 | 34 | 35 | 36 | net.thecodersbreakfast 37 | lp4j-api 38 | 1.0 39 | 40 | 41 | io.vertx 42 | vertx-platform 43 | 2.0.2-final 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/LaunchpadListenerAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | 22 | public class LaunchpadListenerAdapterTest { 23 | 24 | private LaunchpadListenerAdapter adapter; 25 | 26 | @Before 27 | public void init() { 28 | adapter = new LaunchpadListenerAdapter() { 29 | }; 30 | } 31 | 32 | @Test 33 | public void testOnPadPressed() throws Exception { 34 | adapter.onPadPressed(Pad.at(0, 0), -1L); 35 | } 36 | 37 | @Test 38 | public void testOnPadReleased() throws Exception { 39 | adapter.onPadReleased(Pad.at(0, 0), -1L); 40 | } 41 | 42 | @Test 43 | public void testOnButtonPressed() throws Exception { 44 | adapter.onButtonPressed(Button.UP, -1L); 45 | } 46 | 47 | @Test 48 | public void testOnButtonReleased() throws Exception { 49 | adapter.onButtonReleased(Button.UP, -1L); 50 | 51 | } 52 | 53 | @Test 54 | public void testOnTextScrolled() throws Exception { 55 | adapter.onTextScrolled(-1L); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/LaunchpadException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents any error that could occur while using the Launchpad API. 21 | * 22 | * @author Olivier Croisier (olivier.croisier@gmail.com) 23 | */ 24 | public class LaunchpadException extends RuntimeException { 25 | 26 | /** 27 | * Constructor. 28 | */ 29 | public LaunchpadException() { 30 | } 31 | 32 | /** 33 | * Constructor. 34 | * 35 | * @param message The error message. 36 | */ 37 | public LaunchpadException(String message) { 38 | super(message); 39 | } 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * @param message The error message. 45 | * @param cause The root cause of the exception. 46 | */ 47 | public LaunchpadException(String message, Throwable cause) { 48 | super(message, cause); 49 | } 50 | 51 | /** 52 | * Constructor. 53 | * 54 | * @param cause The root cause of the exception. 55 | */ 56 | public LaunchpadException(Throwable cause) { 57 | super(cause); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /lp4j-midi/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 4.0.0 24 | 25 | 26 | net.thecodersbreakfast 27 | lp4j-parent 28 | 1.0 29 | 30 | 31 | lp4j-midi 32 | jar 33 | 34 | 35 | 36 | net.thecodersbreakfast 37 | lp4j-api 38 | 1.0 39 | 40 | 41 | junit 42 | junit 43 | 44 | 45 | org.mockito 46 | mockito-core 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lp4j-midi/src/test/java/net.thecodersbreakfast.lp4j.midi/MidiDeviceConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import org.junit.Assert; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.mockito.Mock; 24 | import org.mockito.runners.MockitoJUnitRunner; 25 | 26 | import javax.sound.midi.MidiDevice; 27 | 28 | @RunWith(MockitoJUnitRunner.class) 29 | public class MidiDeviceConfigurationTest { 30 | 31 | @Mock 32 | private MidiDevice inputDevice; 33 | 34 | @Mock 35 | private MidiDevice outputDevice; 36 | 37 | private MidiDeviceConfiguration configuration; 38 | 39 | @Before 40 | public void init() { 41 | configuration = new MidiDeviceConfiguration(inputDevice, outputDevice); 42 | } 43 | 44 | @Test 45 | public void testGetInputDevice() throws Exception { 46 | MidiDevice device = configuration.getInputDevice(); 47 | Assert.assertEquals(inputDevice, device); 48 | } 49 | 50 | @Test 51 | public void testGetOutputDevice() throws Exception { 52 | MidiDevice device = configuration.getOutputDevice(); 53 | Assert.assertEquals(outputDevice, device); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/Buffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * The Launchpad handles two buffers internally. It is up to the user to select which one is written to, and which one 21 | * is currently displayed (can be the same). 22 | * 23 | *

In addition of the current write buffer, most operations can (depending on a {@link 24 | * net.thecodersbreakfast.lp4j.api.BackBufferOperation} parameter) impact a "backbuffer", defined as "the buffer that is 25 | * not the current write buffer". 26 | * 27 | * @author Olivier Croisier (olivier.croisier@gmail.com) 28 | */ 29 | public enum Buffer { 30 | /** The first buffer */ 31 | BUFFER_0() { 32 | @Override 33 | public Buffer other() { 34 | return BUFFER_1; 35 | } 36 | }, 37 | /** The second buffer */ 38 | BUFFER_1() { 39 | @Override 40 | public Buffer other() { 41 | return BUFFER_0; 42 | } 43 | }; 44 | 45 | /** 46 | * Returns the other buffer ({@link Buffer#BUFFER_1} if the current buffer is {@link Buffer#BUFFER_0}, and 47 | * vice-versa). 48 | * 49 | * @return The other buffer 50 | */ 51 | public abstract Buffer other(); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/Launchpad.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | import java.io.Closeable; 20 | 21 | /** 22 | * Describes a generic Launchpad interface. 23 | * 24 | *

Implementations are responsible for providing a {@link net.thecodersbreakfast.lp4j.api.LaunchpadClient} which allows 25 | * to send commands to the Launchpad, and to accept a {@link net.thecodersbreakfast.lp4j.api.LaunchpadListener} through 26 | * which the API user will be notified of events such as pad or button presses. 27 | * 28 | * @author Olivier Croisier (olivier.croisier@gmail.com) 29 | */ 30 | public interface Launchpad extends Closeable { 31 | 32 | /** 33 | * Returns an implementation of {@link net.thecodersbreakfast.lp4j.api.LaunchpadClient} suitable to send commands to 34 | * a Launchpad. 35 | * 36 | * @return a client 37 | */ 38 | public LaunchpadClient getClient(); 39 | 40 | /** 41 | * Accepts a {@link net.thecodersbreakfast.lp4j.api.LaunchpadListener}, which will be notified of any 42 | * Launchpad-related event such as pad or button presses. 43 | * 44 | * @param listener The listener to be notified 45 | */ 46 | public void setListener(LaunchpadListener listener); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/PadTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | import org.junit.Test; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertNotNull; 23 | 24 | 25 | public class PadTest { 26 | 27 | @Test 28 | public void at() { 29 | Pad pad = Pad.at(Pad.X_MIN, Pad.Y_MIN); 30 | assertNotNull(pad); 31 | assertEquals(Pad.X_MIN, pad.getX()); 32 | assertEquals(Pad.Y_MIN, pad.getY()); 33 | } 34 | 35 | @Test(expected = IllegalArgumentException.class) 36 | public void at_xTooLow() { 37 | Pad pad = Pad.at(Pad.X_MIN - 1, Pad.Y_MIN); 38 | } 39 | 40 | @Test(expected = IllegalArgumentException.class) 41 | public void at_xTooHigh() { 42 | Pad pad = Pad.at(Pad.X_MAX + 1, Pad.Y_MIN); 43 | } 44 | 45 | @Test(expected = IllegalArgumentException.class) 46 | public void at_yTooLow() { 47 | Pad pad = Pad.at(Pad.X_MIN, Pad.Y_MIN - 1); 48 | } 49 | 50 | @Test(expected = IllegalArgumentException.class) 51 | public void at_yTooHigh() { 52 | Pad pad = Pad.at(Pad.X_MIN, Pad.Y_MAX + 1); 53 | } 54 | 55 | @Test 56 | public void tostring() { 57 | Pad pad = Pad.at(Pad.X_MIN, Pad.Y_MIN); 58 | assertEquals("Pad[0,0]", pad.toString()); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /lp4j-api/src/test/java/net/thecodersbreakfast/lp4j/api/ButtonTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | 22 | import static org.junit.Assert.*; 23 | 24 | public class ButtonTest { 25 | 26 | @Test 27 | public void at_topButton() { 28 | Button button = Button.atTop(0); 29 | assertEquals(Button.UP, button); 30 | Assert.assertTrue(button.isTopButton()); 31 | } 32 | 33 | @Test 34 | public void at_rightButton() { 35 | Button button = Button.atRight(0); 36 | assertEquals(Button.VOL, button); 37 | Assert.assertTrue(button.isRightButton()); 38 | } 39 | 40 | @Test(expected = IllegalArgumentException.class) 41 | public void at_coordinateTooLow() { 42 | Button button = Button.atTop(Button.MIN_COORD - 1); 43 | } 44 | 45 | @Test(expected = IllegalArgumentException.class) 46 | public void at_coordinateTooHigh() { 47 | Button button = Button.atTop(Button.MAX_COORD + 1); 48 | } 49 | 50 | @Test 51 | public void isTopOrRight() { 52 | assertTrue(Button.UP.isTopButton()); 53 | assertFalse(Button.UP.isRightButton()); 54 | assertTrue(Button.VOL.isRightButton()); 55 | assertFalse(Button.VOL.isTopButton()); 56 | } 57 | 58 | @Test 59 | public void tostring() { 60 | assertEquals("Button[UP(top,0)]", Button.UP.toString()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/resources/web/vertxbus.min.js: -------------------------------------------------------------------------------- 1 | var vertx=vertx||{}; 2 | !function(j){"function"===typeof define&&define.amd?define("vertxbus",["sockjs"],j):j(SockJS)}(function(j){vertx.EventBus=function(p,q){function o(){e.send(JSON.stringify({type:"ping"}))}function l(b,a,c,f){g("address","string",a);g("replyHandler","function",f,!0);k();b={type:b,address:a,body:c};d.sessionID&&(b.sessionID=d.sessionID);f&&(a=r(),b.replyAddress=a,m[a]=f);f=JSON.stringify(b);e.send(f)}function k(){if(h!=vertx.EventBus.OPEN)throw Error("INVALID_STATE_ERR");}function g(b,a,c,f){if(!f&& 3 | !c)throw Error("Parameter "+b+" must be specified");if(c&&typeof c!=a)throw Error("Parameter "+b+" must be of type "+a);}function r(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(b,a){return a=16*Math.random(),("y"==b?a&3|8:a|0).toString(16)})}var d=this,e=new j(p,void 0,q),i={},m={},h=vertx.EventBus.CONNECTING,n=null;d.onopen=null;d.onclose=null;d.login=function(b,a,c){l("send","vertx.basicauthmanager.login",{username:b,password:a},function(a){"ok"===a.status&&(d.sessionID= 4 | a.sessionID);c&&(delete a.sessionID,c(a))})};d.send=function(b,a,c){l("send",b,a,c)};d.publish=function(b,a,c){l("publish",b,a,c)};d.registerHandler=function(b,a){g("address","string",b);g("handler","function",a);k();var c=i[b];c?c[c.length]=a:(c=[a],i[b]=c,e.send(JSON.stringify({type:"register",address:b})))};d.unregisterHandler=function(b,a){g("address","string",b);g("handler","function",a);k();var c=i[b];if(c){var d=c.indexOf(a);-1!=d&&c.splice(d,1);0==c.length&&(e.send(JSON.stringify({type:"unregister", 5 | address:b})),delete i[b])}};d.close=function(){k();n&&clearInterval(n);h=vertx.EventBus.CLOSING;e.close()};d.readyState=function(){return h};e.onopen=function(){o();n=setInterval(o,5E3);h=vertx.EventBus.OPEN;if(d.onopen)d.onopen()};e.onclose=function(){h=vertx.EventBus.CLOSED;if(d.onclose)d.onclose()};e.onmessage=function(b){var a=JSON.parse(b.data),b=a.body,c=a.replyAddress,a=a.address,f;c&&(f=function(a,b){d.send(c,a,b)});var e=i[a];if(e){a=e.slice(0);for(e=0;eSuch a listener is usually provided to a Launchpad implementation via the {@link 24 | * net.thecodersbreakfast.lp4j.api.Launchpad#setListener(LaunchpadListener)} method. 25 | * 26 | * @author Olivier Croisier (olivier.croisier@gmail.com) 27 | */ 28 | public interface LaunchpadListener { 29 | 30 | /** 31 | * Called when a pad has been pressed. 32 | * 33 | * @param pad The pad that was pressed 34 | * @param timestamp When the event occurred 35 | */ 36 | void onPadPressed(Pad pad, long timestamp); 37 | 38 | /** 39 | * Called when a pad has been released. 40 | * 41 | * @param pad The pad that was released 42 | * @param timestamp When the event occurred 43 | */ 44 | void onPadReleased(Pad pad, long timestamp); 45 | 46 | /** 47 | * Called when a button has been pressed. 48 | * 49 | * @param button The buttonthat was pressed 50 | * @param timestamp When the event occurred 51 | */ 52 | void onButtonPressed(Button button, long timestamp); 53 | 54 | /** 55 | * Called when a button has been released. 56 | * 57 | * @param button The button that was released 58 | * @param timestamp When the event occurred 59 | */ 60 | void onButtonReleased(Button button, long timestamp); 61 | 62 | /** 63 | * Called after text has been successfully displayed by scrolled through the display. This may happen multiple times 64 | * if the {@link net.thecodersbreakfast.lp4j.api.LaunchpadClient#scrollText(String, Color, ScrollSpeed, boolean, 65 | * BackBufferOperation)} method has been instructed to repeat the loop endlessly. 66 | * 67 | * @param timestamp When the event occurred 68 | */ 69 | void onTextScrolled(long timestamp); 70 | } 71 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/ScrollSpeed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents the speed at which the text scrolls through the Launchpad grid (see {@link 21 | * net.thecodersbreakfast.lp4j.api.LaunchpadClient#scrollText}). 22 | * 23 | *

{@code ScrollSpeed} instances are immutable and cached. 24 | * 25 | * @author Olivier Croisier (olivier.croisier@gmail.com) 26 | */ 27 | public class ScrollSpeed { 28 | 29 | /** Minimum value for a scrolling speed */ 30 | public static final int MIN_VALUE = 1; 31 | /** Maximum value for a scrolling speed */ 32 | public static final int MAX_VALUE = 7; 33 | 34 | /** Cache of all possible scrolling speeds */ 35 | private static final ScrollSpeed[] CACHE; 36 | 37 | static { 38 | CACHE = new ScrollSpeed[MAX_VALUE - MIN_VALUE + 1]; 39 | for (int i = MIN_VALUE; i <= MAX_VALUE; i++) { 40 | CACHE[i - MIN_VALUE] = new ScrollSpeed(i); 41 | } 42 | } 43 | 44 | /** Slowest scrolling speed */ 45 | public static final ScrollSpeed SPEED_MIN = of(MIN_VALUE); 46 | /** Fastest scrolling speed */ 47 | public static final ScrollSpeed SPEED_MAX = of(MAX_VALUE); 48 | 49 | /** 50 | * Factory method. 51 | * 52 | * @param speed The desired scrolling speed. Must be in range [{@link net.thecodersbreakfast.lp4j.api.ScrollSpeed#MIN_VALUE},{@link 53 | * net.thecodersbreakfast.lp4j.api.ScrollSpeed#MAX_VALUE}]. 54 | * @return The ScrollSpeed instance 55 | * @throws java.lang.IllegalArgumentException If the requested speed is out of acceptable range. 56 | */ 57 | public static ScrollSpeed of(int speed) { 58 | if (speed < MIN_VALUE || speed > MAX_VALUE) { 59 | throw new IllegalArgumentException("Invalid speed value : " + speed + ". Acceptable values are in range [1..7]."); 60 | } 61 | return CACHE[speed - MIN_VALUE]; 62 | } 63 | 64 | /** The speed value. */ 65 | private final int scrollSpeed; 66 | 67 | /** 68 | * Constructor 69 | * 70 | * @param scrollSpeed The scrolling speed 71 | */ 72 | private ScrollSpeed(int scrollSpeed) { 73 | this.scrollSpeed = scrollSpeed; 74 | } 75 | 76 | /** 77 | * Returns the speed value. 78 | * 79 | * @return the speed value 80 | */ 81 | public int getScrollSpeed() { 82 | return scrollSpeed; 83 | } 84 | 85 | @Override 86 | public boolean equals(Object o) { 87 | if (this == o) { 88 | return true; 89 | } 90 | if (o == null || getClass() != o.getClass()) { 91 | return false; 92 | } 93 | ScrollSpeed that = (ScrollSpeed) o; 94 | return scrollSpeed == that.scrollSpeed; 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | return scrollSpeed; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/Pad.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents a square pad on the Launchpad. 21 | * 22 | *

{@code Pad} instances are immutable and cached. 23 | * 24 | * @author Olivier Croisier (olivier.croisier@gmail.com) 25 | */ 26 | public class Pad { 27 | 28 | /** Minimal X coordinate for a pad */ 29 | public static final int X_MIN = 0; 30 | /** Maximal X coordinate for a pad */ 31 | public static final int X_MAX = 7; 32 | /** Minimal Y coordinate for a pad */ 33 | public static final int Y_MIN = 0; 34 | /** Maximal Y coordinate for a pad */ 35 | public static final int Y_MAX = 7; 36 | 37 | /** Cache of all pads */ 38 | private static final Pad[][] PADS = new Pad[8][8]; 39 | 40 | static { 41 | for (int i = X_MIN; i <= X_MAX; i++) { 42 | for (int j = Y_MIN; j <= Y_MAX; j++) { 43 | PADS[i][j] = new Pad(i, j); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Factory method. 50 | * 51 | * @param x The X coordinate of the pad. Must be in range [{@link Pad#X_MIN},{@link Pad#X_MAX}]. 52 | * @param y The Y coordinate of the pad. Must be in range [{@link Pad#Y_MIN},{@link Pad#Y_MAX}]. 53 | * @return The pad. 54 | * @throws java.lang.IllegalArgumentException If the coordinates are invalid. 55 | */ 56 | public static Pad at(int x, int y) { 57 | if (x < X_MIN || x > X_MAX || y < Y_MIN || y > Y_MAX) { 58 | throw new IllegalArgumentException("Illegal pad coordinates : (" + x + "," + y + "). Acceptable values are in [0..7] on both axis."); 59 | } 60 | return PADS[x][y]; 61 | } 62 | 63 | /** The X coordinate */ 64 | private final int x; 65 | /** The Y coordinate */ 66 | private final int y; 67 | 68 | /** 69 | * Constructor. 70 | * 71 | * @param x The X coordinate of the pad. 72 | * @param y The Y coordinate of the pad. 73 | */ 74 | private Pad(int x, int y) { 75 | this.x = x; 76 | this.y = y; 77 | } 78 | 79 | /** 80 | * Returns the X coordinate. 81 | * 82 | * @return The X coordinate. 83 | */ 84 | public int getX() { 85 | return x; 86 | } 87 | 88 | /** 89 | * Returns the Y coordinate. 90 | * 91 | * @return The Y coordinate 92 | */ 93 | public int getY() { 94 | return y; 95 | } 96 | 97 | @Override 98 | public boolean equals(Object o) { 99 | if (this == o) { 100 | return true; 101 | } 102 | if (o == null || getClass() != o.getClass()) { 103 | return false; 104 | } 105 | Pad pad = (Pad) o; 106 | return x == pad.x && y == pad.y; 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | int result = x; 112 | result = 31 * result + y; 113 | return result; 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return "Pad[" + x + ',' + y + ']'; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/protocol/DefaultMidiProtocolListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi.protocol; 18 | 19 | import net.thecodersbreakfast.lp4j.api.Button; 20 | import net.thecodersbreakfast.lp4j.api.LaunchpadListener; 21 | import net.thecodersbreakfast.lp4j.api.Pad; 22 | 23 | /** 24 | * Parses low-level messages and notifies a high-level {@link net.thecodersbreakfast.lp4j.api.LaunchpadListener}. 25 | * 26 | * @author Olivier Croisier (olivier.croisier@gmail.com) 27 | */ 28 | public class DefaultMidiProtocolListener implements MidiProtocolListener { 29 | 30 | /** The high-level LaunchpadListener to notify. */ 31 | private final LaunchpadListener listener; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param listener The high-level LaunchpadListener to notify. 37 | */ 38 | public DefaultMidiProtocolListener(LaunchpadListener listener) { 39 | this.listener = listener; 40 | } 41 | 42 | /** {@inheritDoc} */ 43 | @Override 44 | public void onNoteOn(int note, long timestamp) { 45 | if (listener == null) { 46 | return; 47 | } 48 | int x = note % 16; 49 | int y = note / 16; 50 | if (x >= 8) { 51 | Button button = Button.atRight(y); 52 | listener.onButtonPressed(button, timestamp); 53 | } else { 54 | if (x < 0 || y < 0 || y > 7) { 55 | throw new IllegalArgumentException("Invalid pad coordinates : (" + x + "," + y + "). Acceptable values on either axis are in range [0..7]."); 56 | } 57 | listener.onPadPressed(Pad.at(x, y), timestamp); 58 | } 59 | } 60 | 61 | /** {@inheritDoc} */ 62 | @Override 63 | public void onNoteOff(int note, long timestamp) { 64 | if (listener == null) { 65 | return; 66 | } 67 | int x = note % 16; 68 | int y = note / 16; 69 | if (x >= 8) { 70 | Button button = Button.atRight(y); 71 | listener.onButtonReleased(button, timestamp); 72 | } else { 73 | if (x < 0 || y < 0 || y > 7) { 74 | throw new IllegalArgumentException("Invalid pad coordinates : (" + x + "," + y + "). Acceptable values on either axis are in range [0..7]."); 75 | } 76 | listener.onPadReleased(Pad.at(x, y), timestamp); 77 | } 78 | } 79 | 80 | /** {@inheritDoc} */ 81 | @Override 82 | public void onButtonOn(int note, long timestamp) { 83 | if (listener == null) { 84 | return; 85 | } 86 | int value = note - 104; 87 | Button button = Button.atTop(value); 88 | listener.onButtonPressed(button, timestamp); 89 | } 90 | 91 | /** {@inheritDoc} */ 92 | @Override 93 | public void onButtonOff(int note, long timestamp) { 94 | if (listener == null) { 95 | return; 96 | } 97 | int value = note - 104; 98 | Button button = Button.atTop(value); 99 | listener.onButtonReleased(button, timestamp); 100 | } 101 | 102 | /** {@inheritDoc} */ 103 | @Override 104 | public void onTextScrolled(long timestamp) { 105 | listener.onTextScrolled(timestamp); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/Brightness.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Describes the level of brightness of the pads and buttons lights. 21 | * 22 | *

{@code Brightness} instances are immutable and cached. 23 | * 24 | * @author Olivier Croisier (olivier.croisier@gmail.com) 25 | */ 26 | public final class Brightness { 27 | 28 | /** Minimum level of brightness */ 29 | public static final int MIN_VALUE = 0; 30 | /** Maximum level of brightness */ 31 | public static final int MAX_VALUE = 15; 32 | 33 | /** Cache for all levels of brightness */ 34 | private static final Brightness[] CACHE = new Brightness[16]; 35 | 36 | static { 37 | for (int i = 0; i < 16; i++) { 38 | CACHE[i] = new Brightness(i); 39 | } 40 | } 41 | 42 | /** Minimum brightness */ 43 | public static final Brightness BRIGHTNESS_MIN = of(MIN_VALUE); 44 | /** Maximum brightness */ 45 | public static final Brightness BRIGHTNESS_MAX = of(MAX_VALUE); 46 | 47 | /** 48 | * Factory method. 49 | * 50 | * @param brightness The desired level of brightess. Must be in range [{@link net.thecodersbreakfast.lp4j.api.Brightness#MIN_VALUE},{@link 51 | * net.thecodersbreakfast.lp4j.api.Brightness#MAX_VALUE}] 52 | * @return The Brightness instance. 53 | * @throws java.lang.IllegalArgumentException If the requested brightness level is out of acceptable range. 54 | */ 55 | public static Brightness of(int brightness) { 56 | if (brightness < MIN_VALUE || brightness > MAX_VALUE) { 57 | throw new IllegalArgumentException("Invalid brightness level : " + brightness + ". Acceptable values are in range [0..15]."); 58 | } 59 | return CACHE[brightness - MIN_VALUE]; 60 | } 61 | 62 | /** Level of brightness */ 63 | private final int brightness; 64 | 65 | /** 66 | * Constructor. 67 | * 68 | * @param brightness The desired level of brightess. 69 | */ 70 | private Brightness(int brightness) { 71 | this.brightness = brightness; 72 | } 73 | 74 | /** 75 | * Returns the level of brightness 76 | * 77 | * @return the level of brightness 78 | */ 79 | public int getBrightness() { 80 | return brightness; 81 | } 82 | 83 | /** 84 | * Returns a brighter Brightness instance, if the maximum level of brightness is not already reached. 85 | * 86 | * @return a brighter Brightness instance, or the same instance if it was already the brightest. 87 | */ 88 | public Brightness more() { 89 | return brightness < MAX_VALUE ? of(brightness + 1) : this; 90 | } 91 | 92 | /** 93 | * Returns a less bright Brightness instance, if the minimum level of brightness is not already reached. 94 | * 95 | * @return a less bright Brightness instance, or the same instance if it was already the least bright. 96 | */ 97 | public Brightness less() { 98 | return brightness > MIN_VALUE ? of(brightness - 1) : this; 99 | } 100 | 101 | @Override 102 | public boolean equals(Object o) { 103 | if (this == o) { 104 | return true; 105 | } 106 | if (o == null || getClass() != o.getClass()) { 107 | return false; 108 | } 109 | Brightness that = (Brightness) o; 110 | return brightness == that.brightness; 111 | } 112 | 113 | @Override 114 | public int hashCode() { 115 | return brightness; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lp4j-midi/src/test/java/net.thecodersbreakfast.lp4j.midi/DefaultMidiProtocolReceiverTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import net.thecodersbreakfast.lp4j.api.LaunchpadException; 20 | import net.thecodersbreakfast.lp4j.midi.protocol.DefaultMidiProtocolReceiver; 21 | import net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolListener; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.mockito.Mockito; 25 | 26 | import javax.sound.midi.*; 27 | 28 | public class DefaultMidiProtocolReceiverTest { 29 | 30 | private static final int VELOCITY_PRESSED = 127; 31 | private static final int VELOCITY_RELEASED = 0; 32 | private static final int VELOCITY_TEXT_SCROLLED = 3; 33 | private static final long TIMESTAMP = -1; 34 | 35 | private Receiver receiver; 36 | private MidiProtocolListener listener; 37 | 38 | @Before 39 | public void init() { 40 | listener = Mockito.mock(MidiProtocolListener.class); 41 | receiver = new DefaultMidiProtocolReceiver(listener); 42 | } 43 | 44 | @Test(expected = LaunchpadException.class) 45 | public void send_sysexMessage() { 46 | MidiMessage message = new SysexMessage(); 47 | receiver.send(message, TIMESTAMP); 48 | } 49 | 50 | @Test(expected = LaunchpadException.class) 51 | public void send_metaMessage() { 52 | MidiMessage message = new MetaMessage(); 53 | receiver.send(message, TIMESTAMP); 54 | } 55 | 56 | @Test(expected = LaunchpadException.class) 57 | public void send_shortMessage_unknown() throws Exception { 58 | ShortMessage message = new ShortMessage(); 59 | message.setMessage(ShortMessage.STOP, 0, 0); 60 | receiver.send(message, TIMESTAMP); 61 | } 62 | 63 | @Test 64 | public void send_noteOn_pressed() throws Exception { 65 | ShortMessage message = new ShortMessage(); 66 | message.setMessage(ShortMessage.NOTE_ON, 42, VELOCITY_PRESSED); 67 | receiver.send(message, TIMESTAMP); 68 | Mockito.verify(listener).onNoteOn(42, TIMESTAMP); 69 | } 70 | 71 | @Test 72 | public void send_noteOn_released() throws Exception { 73 | ShortMessage message = new ShortMessage(); 74 | message.setMessage(ShortMessage.NOTE_ON, 42, VELOCITY_RELEASED); 75 | receiver.send(message, TIMESTAMP); 76 | Mockito.verify(listener).onNoteOff(42, TIMESTAMP); 77 | } 78 | 79 | @Test 80 | public void send_controlChange_pressed() throws Exception { 81 | ShortMessage message = new ShortMessage(); 82 | message.setMessage(ShortMessage.CONTROL_CHANGE, 42, VELOCITY_PRESSED); 83 | receiver.send(message, TIMESTAMP); 84 | Mockito.verify(listener).onButtonOn(42, TIMESTAMP); 85 | } 86 | 87 | @Test 88 | public void send_controlChange_released() throws Exception { 89 | ShortMessage message = new ShortMessage(); 90 | message.setMessage(ShortMessage.CONTROL_CHANGE, 42, VELOCITY_RELEASED); 91 | receiver.send(message, TIMESTAMP); 92 | Mockito.verify(listener).onButtonOff(42, TIMESTAMP); 93 | } 94 | 95 | @Test 96 | public void send_controlChange_textScrolled() throws Exception { 97 | ShortMessage message = new ShortMessage(); 98 | message.setMessage(ShortMessage.CONTROL_CHANGE, 0, VELOCITY_TEXT_SCROLLED); 99 | receiver.send(message, TIMESTAMP); 100 | Mockito.verify(listener).onTextScrolled(TIMESTAMP); 101 | } 102 | 103 | @Test 104 | public void close() throws Exception { 105 | receiver.close(); 106 | } 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /lp4j-midi/src/test/java/net.thecodersbreakfast.lp4j.midi/DefaultMidiProtocolListenerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import net.thecodersbreakfast.lp4j.api.Button; 20 | import net.thecodersbreakfast.lp4j.api.LaunchpadListener; 21 | import net.thecodersbreakfast.lp4j.api.Pad; 22 | import net.thecodersbreakfast.lp4j.midi.protocol.DefaultMidiProtocolListener; 23 | import net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolListener; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.mockito.Mockito; 27 | 28 | public class DefaultMidiProtocolListenerTest { 29 | 30 | private static final long TIMESTAMP = -1; 31 | 32 | private static final int BUTTON_UP = 104; 33 | private static final int BUTTON_UNKNOWN = 100; 34 | private static final int NOTE_VOL = 8; 35 | private static final int NOTE_PAD00 = 0; 36 | private static final int NOTE_UNKNOWN = -1; 37 | 38 | private MidiProtocolListener midiProtocolListener; 39 | private LaunchpadListener listener; 40 | 41 | @Before 42 | public void init() { 43 | this.listener = Mockito.mock(LaunchpadListener.class); 44 | this.midiProtocolListener = new DefaultMidiProtocolListener(listener); 45 | } 46 | 47 | @Test 48 | public void onButtonOn_up() { 49 | midiProtocolListener.onButtonOn(BUTTON_UP, TIMESTAMP); 50 | Mockito.verify(listener).onButtonPressed(Button.UP, TIMESTAMP); 51 | } 52 | 53 | @Test(expected = IllegalArgumentException.class) 54 | public void onButtonOn_unknown() { 55 | midiProtocolListener.onButtonOn(BUTTON_UNKNOWN, TIMESTAMP); 56 | Mockito.verify(listener).onButtonPressed(Button.UP, TIMESTAMP); 57 | } 58 | 59 | @Test 60 | public void onButtonOff_up() { 61 | midiProtocolListener.onButtonOff(BUTTON_UP, TIMESTAMP); 62 | Mockito.verify(listener).onButtonReleased(Button.UP, TIMESTAMP); 63 | } 64 | 65 | @Test(expected = IllegalArgumentException.class) 66 | public void onButtonOff_unknown() { 67 | midiProtocolListener.onButtonOff(BUTTON_UNKNOWN, TIMESTAMP); 68 | Mockito.verify(listener).onButtonReleased(Button.UP, TIMESTAMP); 69 | } 70 | 71 | @Test 72 | public void onNoteOn_vol() { 73 | midiProtocolListener.onNoteOn(NOTE_VOL, TIMESTAMP); 74 | Mockito.verify(listener).onButtonPressed(Button.VOL, TIMESTAMP); 75 | } 76 | 77 | @Test 78 | public void onNoteOff_vol() { 79 | midiProtocolListener.onNoteOff(NOTE_VOL, TIMESTAMP); 80 | Mockito.verify(listener).onButtonReleased(Button.VOL, TIMESTAMP); 81 | } 82 | 83 | @Test 84 | public void onNoteOn_pad00() { 85 | midiProtocolListener.onNoteOn(NOTE_PAD00, TIMESTAMP); 86 | Mockito.verify(listener).onPadPressed(Pad.at(0, 0), TIMESTAMP); 87 | } 88 | 89 | @Test 90 | public void onNoteOff_pad00() { 91 | midiProtocolListener.onNoteOff(NOTE_PAD00, TIMESTAMP); 92 | Mockito.verify(listener).onPadReleased(Pad.at(0, 0), TIMESTAMP); 93 | } 94 | 95 | @Test(expected = IllegalArgumentException.class) 96 | public void onNoteOn_unknown() { 97 | midiProtocolListener.onNoteOn(NOTE_UNKNOWN, TIMESTAMP); 98 | Mockito.verify(listener).onPadPressed(Pad.at(0, 0), TIMESTAMP); 99 | } 100 | 101 | @Test(expected = IllegalArgumentException.class) 102 | public void onNoteOff_unknown() { 103 | midiProtocolListener.onNoteOff(NOTE_UNKNOWN, TIMESTAMP); 104 | Mockito.verify(listener).onPadReleased(Pad.at(0, 0), TIMESTAMP); 105 | } 106 | 107 | @Test 108 | public void onTextScrolled() { 109 | midiProtocolListener.onTextScrolled(TIMESTAMP); 110 | Mockito.verify(listener).onTextScrolled(TIMESTAMP); 111 | } 112 | 113 | 114 | } 115 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/resources/web/Launchpad.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Abstract Launchpad definition. 18 | function Launchpad() { 19 | this.colors = [ 20 | ["#CCC", "#060", "#0C2", "#0F4"], 21 | ["#600", "#660", "#6C2", "#6F4"], 22 | ["#C00", "#C60", "#CC2", "#CF4"], 23 | ["#F00", "#F60", "#FC2", "#FF4"] 24 | ]; 25 | this.buffers = new Array(2); 26 | this.visibleBuffer = 0; 27 | this.writeBuffer = 0; 28 | this.backBuffer = 1; 29 | this.brightness = 1; 30 | this.listener = null; 31 | this.reset(); 32 | } 33 | 34 | Launchpad.prototype.setListener = function (launchpadListener) { 35 | this.listener = launchpadListener; 36 | }; 37 | 38 | // ---------------------------------------- 39 | // Client methods 40 | // ---------------------------------------- 41 | 42 | Launchpad.prototype.reset = function () { 43 | this.buffers[0] = new Array(9); 44 | this.buffers[1] = new Array(9); 45 | for (var i = 0; i < 9; i++) { 46 | this.buffers[0][i] = new Array(9); 47 | this.buffers[1][i] = new Array(9); 48 | } 49 | }; 50 | 51 | Launchpad.prototype.testLights = function (intensity) { 52 | this.setBrightness(intensity); 53 | var self = this; 54 | this.buffers[this.writeBuffer].map(function (a) { 55 | a.fill(self.colors[3][3]); 56 | }); 57 | }; 58 | 59 | Launchpad.prototype.setLights = function (colors, operation) { 60 | // Not implemented yet 61 | }; 62 | 63 | Launchpad.prototype.setPadLight = function (x, y, color, operation) { 64 | this.buffers[this.writeBuffer][x][y + 1] = this.colors[color.r][color.g]; 65 | switch (operation) { 66 | case 'NONE' : 67 | break; 68 | case 'COPY' : 69 | this.buffers[this.backBuffer][x][y + 1] = this.colors[color.r][color.g]; 70 | break; 71 | case 'CLEAR' : 72 | this.buffers[this.backBuffer][x][y + 1] = this.colors[0][0]; 73 | break; 74 | } 75 | }; 76 | 77 | Launchpad.prototype.setButtonLight = function (t, i, color, operation) { 78 | var x = 0; 79 | var y = 0; 80 | if (t === true) { 81 | x = i; 82 | y = -1; 83 | } else { 84 | x = 8; 85 | y = i; 86 | } 87 | this.setPadLight(x, y, color, operation); 88 | }; 89 | 90 | Launchpad.prototype.setBrightness = function (level) { 91 | this.brightness = 0.1 + 0.06 * level; 92 | }; 93 | 94 | Launchpad.prototype.setBuffers = function (visibleBuffer, writeBuffer, copyVisibleBufferToWriteBuffer, autoSwap) { 95 | this.visibleBuffer = this.bufIdx(visibleBuffer); 96 | this.writeBuffer = this.bufIdx(writeBuffer); 97 | this.backBuffer = 1 - this.writeBuffer; 98 | if (copyVisibleBufferToWriteBuffer === true) { 99 | this.buffers[this.writeBuffer] = this.buffers[this.visibleBuffer].clone; 100 | } 101 | }; 102 | 103 | Launchpad.prototype.bufIdx = function (buffer) { 104 | return (buffer === 'BUFFER_0' ? 0 : 1); 105 | }; 106 | 107 | Launchpad.prototype.scrollText = function (text, color, speed, loop, operation) { 108 | // Not implemented yet 109 | }; 110 | 111 | // ---------------------------------------- 112 | // Listener Methods 113 | // ---------------------------------------- 114 | 115 | // Definition of a listener allowing to be notified when the user interacts with the Launchpad interface 116 | function LaunchpadListener() { 117 | } 118 | 119 | LaunchpadListener.prototype.onPadPressed = function (x, y) { 120 | }; 121 | 122 | LaunchpadListener.prototype.onPadReleased = function (x, y) { 123 | }; 124 | 125 | LaunchpadListener.prototype.onButtonPressed = function (x, y) { 126 | }; 127 | 128 | LaunchpadListener.prototype.onButtonReleased = function (x, y) { 129 | }; 130 | 131 | LaunchpadListener.prototype.onTextScrolled = function () { 132 | }; 133 | 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LP4J - A Launchpad API for Java ! 2 | ==== 3 | 4 | [![Build Status](https://travis-ci.org/OlivierCroisier/LP4J.svg)](https://travis-ci.org/OlivierCroisier/LP4J) 5 | 6 | Launchpad is a midi device built by Novation, used to drive computer-assisted music creation applications such as Ableton Live. 7 | 8 | The device is a square board with a 8x8 pads grid, surrounded by round command buttons. Pads and buttons can be lit in various tints of yellow, red and green. 9 | This makes the Launchpad an interesting general-purpose I/O device, where a user can press pads and buttons to send commands to an application, and provide feedback with its 8x8 matrix of colored leds. 10 | 11 | ![Launchpad](launchpad.jpg) 12 | 13 | LP4J is a lightweight Java API allowing any Java application to interact with a Launchpad S (second generation). 14 | It also provides a embedded, web-based emulator to allow developers to design and test applications should they not have access to a physical device. 15 | 16 | The API is split in 3 submodules : 17 | - lp4j-api : an abstract, high-level API defining how to interact with a Launchpad (LaunchpadClient / LaunchpadListener) 18 | - lp4j-midi : a MIDI implementation of the API, suitable to interact with a physical device. 19 | - lp4j-emulator : a web-based emulator, using SVG and websockets. 20 | 21 | How to use LP4J 22 | ==== 23 | 24 | From a developper's perspective, all interaction with LP4J are done through the high-level API. 25 | 26 | First, you need to get a reference to a Launchpad : 27 | 28 | ```java 29 | // Physical device (with auto-detected ports configuration) 30 | Launchpad launchpad = new MidiLaunchpad(MidiDeviceConfiguration.autodetect()); 31 | 32 | // Or start the emulator on http://localhost:9000 33 | Launchpad launchpad = new EmulatorLaunchpad(9000); 34 | ``` 35 | 36 | From this Launchpad instance, you can : 37 | - retrieve a LaunchpadClient, used to send commands TO the device or emulator (mostly to turn on/off the pads or buttons lights), 38 | - set up a LaunchpadListener to react to events. 39 | 40 | ```java 41 | LaunchpadClient client = launchpad.getClient(); 42 | launchpad.setListener(new MyListener()); 43 | ``` 44 | 45 | A very simple debugging listener can look like this : 46 | 47 | ```java 48 | public static class MyListener extends LaunchpadListenerAdapter { 49 | 50 | @Override 51 | public void onPadPressed(Pad pad, long timestamp) { 52 | System.out.println("Pad pressed : "+pad); 53 | } 54 | 55 | } 56 | ``` 57 | 58 | A more complex example 59 | ==== 60 | 61 | Listeners can be more complex. 62 | For example, the following Listener sends commands back to the Launchpad to put a yellow light under pads when they are pressed, lighting them off when they are released. 63 | 64 | Please also note the use of a CountDownLatch to wait for the user to press the "STOP" button before exiting the application. 65 | 66 | ```java 67 | public class Example { 68 | 69 | private static CountDownLatch stop = new CountDownLatch(1); 70 | 71 | public static void main(String[] args) throws Exception { 72 | 73 | Launchpad launchpad = new EmulatorLaunchpad(9000); 74 | LaunchpadClient client = launchpad.getClient(); 75 | 76 | MyListener myListener = new MyListener(client); 77 | launchpad.setListener(myListener); 78 | 79 | // Set a red light under the STOP button 80 | client.reset(); 81 | client.setButtonLight(Button.STOP, Color.RED, BackBufferOperation.NONE); 82 | 83 | stop.await(); 84 | client.reset(); 85 | launchpad.close(); 86 | } 87 | 88 | public static class MyListener extends LaunchpadListenerAdapter { 89 | 90 | private final LaunchpadClient client; 91 | 92 | public MyListener(LaunchpadClient client) { 93 | this.client = client; 94 | } 95 | 96 | @Override 97 | public void onPadPressed(Pad pad, long timestamp) { 98 | client.setPadLight(pad, Color.YELLOW, BackBufferOperation.NONE); 99 | } 100 | 101 | @Override 102 | public void onPadReleased(Pad pad, long timestamp) { 103 | client.setPadLight(pad, Color.BLACK, BackBufferOperation.NONE); 104 | } 105 | 106 | @Override 107 | public void onButtonReleased(Button button, long timestamp) { 108 | client.setButtonLight(button, Color.BLACK, BackBufferOperation.NONE); 109 | switch (button) { 110 | case STOP: 111 | stop.countDown(); 112 | break; 113 | } 114 | } 115 | } 116 | 117 | } 118 | ``` 119 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/LaunchpadClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents a client through which the API user can send commands to a particular Launchpad implementation. Usually 21 | * obtained by calling {@link Launchpad#getClient()}. 22 | * 23 | * @author Olivier Croisier (olivier.croisier@gmail.com) 24 | */ 25 | public interface LaunchpadClient { 26 | 27 | /** 28 | * Reset the state of the Launchpad. 29 | */ 30 | public void reset(); 31 | 32 | /** 33 | * Lights up all the pads and buttons with the given light intensity. 34 | * 35 | * @param intensity The desired light intensity. 36 | */ 37 | public void testLights(LightIntensity intensity); 38 | 39 | /** 40 | * Bulk-set the colors of all pads and buttons. 41 | * 42 | * The length of colors array passed as a parameter may vary. Lights will be lit line by line, starting from the 43 | * upper-left pad down to the bottom-right pad, then under the upper-row buttons, and finally under the right-side 44 | * buttons. 45 | * 46 | * @param colors The colors to be set 47 | * @param operation What to do on the backbuffer 48 | */ 49 | public void setLights(Color[] colors, BackBufferOperation operation); 50 | 51 | /** 52 | * Lights up the given pad with the given color. 53 | * 54 | * @param pad The pad to light up. 55 | * @param color The color to use. Use {@link net.thecodersbreakfast.lp4j.api.Color#BLACK} to switch the light off. 56 | * @param operation What to do on the backbuffer 57 | */ 58 | public void setPadLight(Pad pad, Color color, BackBufferOperation operation); 59 | 60 | /** 61 | * Lights up the given button with the given color. 62 | * 63 | * @param button The button to light up. 64 | * @param color The color to use. Use {@link net.thecodersbreakfast.lp4j.api.Color#BLACK} to switch the light off. 65 | * @param operation What to do on the backbuffer 66 | */ 67 | public void setButtonLight(Button button, Color color, BackBufferOperation operation); 68 | 69 | /** 70 | * Set the overall brightness of the pad and button lights. 71 | * 72 | * @param brightness The desired brightness. 73 | */ 74 | public void setBrightness(Brightness brightness); 75 | 76 | /** 77 | * Sets the current write and visible buffers. 78 | * 79 | * @param visibleBuffer The buffer to display 80 | * @param writeBuffer The buffer to which the commands are applied. 81 | * @param copyVisibleBufferToWriteBuffer Tells if the visible buffer state should be copied to the write buffer. 82 | * This operation occurs after the new write and visible buffers have been set. 83 | * @param autoSwap Set to {@code true} to make the visible buffer quickly swap between the two buffers. This can be 84 | * used to create a blinking effect. Set back to {@code false} to stop the blinking. 85 | */ 86 | public void setBuffers(Buffer visibleBuffer, Buffer writeBuffer, boolean copyVisibleBufferToWriteBuffer, boolean autoSwap); 87 | 88 | /** 89 | * Starts scrolling a text across the Launchpad, using the 8x8 pad grid as a font grid. Beware, this operation may 90 | * be blocking ! 91 | * 92 | * After the text has been displayed (or after each loop if {@code loop} is set to {@code true}), the listener's 93 | * {@link net.thecodersbreakfast.lp4j.api.LaunchpadListener#onTextScrolled(long)} is notified. 94 | * 95 | * @param text The text to display. 96 | * @param color The color to use 97 | * @param speed The speed to use 98 | * @param loop Tells if the text should loop endlessly. In that case, set to {@code false} to stop the scrolling. 99 | * @param operation What to do on the backbuffer 100 | */ 101 | public void scrollText(String text, Color color, ScrollSpeed speed, boolean loop, BackBufferOperation operation); 102 | } 103 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/protocol/DefaultMidiProtocolReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi.protocol; 18 | 19 | import net.thecodersbreakfast.lp4j.api.LaunchpadException; 20 | 21 | import javax.sound.midi.MidiMessage; 22 | import javax.sound.midi.Receiver; 23 | import javax.sound.midi.ShortMessage; 24 | 25 | /** 26 | * A MIDI Receiver, to which the Launchpad sends low-level commands. 27 | * Those commands are parsed and transmitted (still in a close-to-the-metal format) to a 28 | * {@link net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolListener} for further processing. 29 | * 30 | * @author Olivier Croisier (olivier.croisier@gmail.com) 31 | */ 32 | public class DefaultMidiProtocolReceiver implements Receiver { 33 | 34 | /** The MidiProtocolListener to to notify when commands are received. */ 35 | private MidiProtocolListener midiProtocolListener; 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param listener The MidiProtocolListener to to notify when commands are received. Must not be null. 41 | */ 42 | public DefaultMidiProtocolReceiver(MidiProtocolListener listener) { 43 | if (listener == null) { 44 | throw new IllegalArgumentException("Listener must not be null."); 45 | } 46 | this.midiProtocolListener = listener; 47 | } 48 | 49 | /** 50 | * {@inheritDoc} 51 | * 52 | * THIS METHOD SHOULD ONLY BE CALLED BY THE LAUNCHPAD DEVICE. 53 | */ 54 | @Override 55 | public void send(MidiMessage message, long timestamp) { 56 | if (message instanceof ShortMessage) { 57 | handleShortMessage((ShortMessage) message, timestamp); 58 | } else { 59 | throw new LaunchpadException("Unknown event : " + message); 60 | } 61 | } 62 | 63 | /** 64 | * Parses and routes MIDI short messages to adequate sub-handlers. 65 | * 66 | * @param message The inconming message. 67 | * @param timestamp When the message arrived. 68 | */ 69 | protected void handleShortMessage(ShortMessage message, long timestamp) { 70 | int status = message.getStatus(); 71 | int note = message.getData1(); 72 | int velocity = message.getData2(); 73 | 74 | if (status == ShortMessage.NOTE_ON) { 75 | handleNoteOnMessage(note, velocity, timestamp); 76 | } else if (status == ShortMessage.CONTROL_CHANGE) { 77 | handleControlChangeMessage(note, velocity, timestamp); 78 | } else { 79 | throw new LaunchpadException("Unknown event : " + message); 80 | } 81 | } 82 | 83 | /** 84 | * Parses "note on" messages and notifies the to the higher-level {@code midiProtocolListener} 85 | * 86 | * @param note The activated note. 87 | * @param velocity The note velocity. 88 | * @param timestamp When the note was activated. 89 | */ 90 | protected void handleNoteOnMessage(int note, int velocity, long timestamp) { 91 | if (velocity == 0) { 92 | midiProtocolListener.onNoteOff(note, timestamp); 93 | } else { 94 | midiProtocolListener.onNoteOn(note, timestamp); 95 | } 96 | } 97 | 98 | /** 99 | * Parses "control" messages and notifies the to the higher-level {@code midiProtocolListener} 100 | * 101 | * @param note The activated note. 102 | * @param velocity The note velocity. 103 | * @param timestamp When the note was activated. 104 | */ 105 | protected void handleControlChangeMessage(int note, int velocity, long timestamp) { 106 | if (note == 0 && velocity == 3) { 107 | midiProtocolListener.onTextScrolled(timestamp); 108 | } else if (velocity == 0) { 109 | midiProtocolListener.onButtonOff(note, timestamp); 110 | } else { 111 | midiProtocolListener.onButtonOn(note, timestamp); 112 | } 113 | } 114 | 115 | @Override 116 | public void close() { 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/Color.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents the color of a pad or button on the Launchpad. The Launchpad has a red light and a green light under each 21 | * pad or button. The actual color is obtained by adjusting their respective intensities. 22 | * 23 | *

{@code Color} instances are immutable and cached. 24 | * 25 | * @author Olivier Croisier (olivier.croisier@gmail.com) 26 | */ 27 | public final class Color { 28 | 29 | /** Minimal red or green component intensity */ 30 | public static final int MIN_INTENSITY = 0; 31 | /** Maximal red or green component intensity */ 32 | public static final int MAX_INTENSITY = 3; 33 | 34 | // Color cache 35 | private static final Color[][] CACHE = new Color[4][4]; 36 | 37 | static { 38 | for (int r = 0; r < 4; r++) { 39 | for (int g = 0; g < 4; g++) { 40 | CACHE[r][g] = new Color(r, g); 41 | } 42 | } 43 | } 44 | 45 | // Most used colors 46 | /** Black (red 0, green 0) */ 47 | public static final Color BLACK = CACHE[0][0]; 48 | /** Red (red 3, green 0) */ 49 | public static final Color RED = CACHE[3][0]; 50 | /** Green (red 0, green 3) */ 51 | public static final Color GREEN = CACHE[0][3]; 52 | /** Orange (red 3, green 2) */ 53 | public static final Color ORANGE = CACHE[3][2]; 54 | /** Amber (red 3, green 3) */ 55 | public static final Color AMBER = CACHE[3][3]; 56 | /** Yellow (red 2, green 3) */ 57 | public static final Color YELLOW = CACHE[2][3]; 58 | 59 | /** 60 | * Factory method 61 | * 62 | * @param red The red component. Acceptable values are in [{@link net.thecodersbreakfast.lp4j.api.Color#MIN_INTENSITY},{@link 63 | * net.thecodersbreakfast.lp4j.api.Color#MAX_INTENSITY}]. 64 | * @param green The green component. Acceptable values are in [{@link net.thecodersbreakfast.lp4j.api.Color#MIN_INTENSITY},{@link 65 | * net.thecodersbreakfast.lp4j.api.Color#MAX_INTENSITY}]. 66 | * @return The Color obtained by mixing the given red and green values. 67 | * @throws java.lang.IllegalArgumentException If the red or green parameters are out of acceptable range. 68 | */ 69 | public static Color of(int red, int green) { 70 | if (red < MIN_INTENSITY || red > MAX_INTENSITY) { 71 | throw new IllegalArgumentException("Invalid red value : " + red + ". Acceptable values are in range [0..3]."); 72 | } 73 | if (green < MIN_INTENSITY || green > MAX_INTENSITY) { 74 | throw new IllegalArgumentException("Invalid green value : " + green + ". Acceptable values are in range [0..3]."); 75 | } 76 | return CACHE[red][green]; 77 | } 78 | 79 | /** The red component intensity */ 80 | private final int red; 81 | /** The green component intensity */ 82 | private final int green; 83 | 84 | /** 85 | * Constructor 86 | * 87 | * @param red The red component 88 | * @param green The green component 89 | */ 90 | private Color(int red, int green) { 91 | this.red = red; 92 | this.green = green; 93 | } 94 | 95 | /** 96 | * Returns the red intensity 97 | * 98 | * @return the red intensity 99 | */ 100 | public int getRed() { 101 | return red; 102 | } 103 | 104 | /** 105 | * Returns the green intensity 106 | * 107 | * @return the green intensity 108 | */ 109 | public int getGreen() { 110 | return green; 111 | } 112 | 113 | @Override 114 | public boolean equals(Object o) { 115 | if (this == o) { 116 | return true; 117 | } 118 | if (o == null || getClass() != o.getClass()) { 119 | return false; 120 | } 121 | Color color = (Color) o; 122 | return green == color.green && red == color.red; 123 | } 124 | 125 | @Override 126 | public int hashCode() { 127 | int result = red; 128 | result = 31 * result + green; 129 | return result; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 4.0.0 24 | 25 | net.thecodersbreakfast 26 | lp4j-parent 27 | 1.0 28 | pom 29 | 30 | 31 | 32 | Apache License, Version 2.0 33 | http://www.apache.org/licenses/LICENSE-2.0.html 34 | 35 | 36 | 37 | 38 | 1.6 39 | UTF-8 40 | 41 | 4.13.1 42 | 1.9.5 43 | 44 | 3.1 45 | 2.2.1 46 | 2.9.1 47 | 48 | 49 | 50 | 51 | ocroisier 52 | Olivier Croisier 53 | olivier.croisier@gmail.com 54 | 55 | 56 | 57 | 58 | lp4j-api 59 | lp4j-midi 60 | lp4j-emu-web 61 | 62 | 63 | 64 | 65 | 66 | junit 67 | junit 68 | ${lib.junit.version} 69 | test 70 | 71 | 72 | org.mockito 73 | mockito-core 74 | ${lib.mockito.version} 75 | test 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-compiler-plugin 86 | ${plugin.compiler.version} 87 | 88 | ${java.version} 89 | ${java.version} 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-source-plugin 96 | ${plugin.source.version} 97 | 98 | 99 | attach-sources 100 | 101 | jar 102 | 103 | 104 | 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-javadoc-plugin 110 | ${plugin.javadoc.version} 111 | 112 | 113 | attach-javadocs 114 | 115 | jar 116 | 117 | 118 | 119 | 120 | 7 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/MidiDeviceConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import javax.sound.midi.MidiDevice; 20 | import javax.sound.midi.MidiSystem; 21 | import javax.sound.midi.MidiUnavailableException; 22 | 23 | /** 24 | * Configuration for MIDI I/O. 25 | * 26 | *

Requires both an inbound and outbound MidiDevices, because data is sent from (pad events) and to (lights, etc.) 27 | * the Launchpad. 28 | * 29 | *

In most cases, it should suffice to call the {@link MidiDeviceConfiguration#autodetect()} factory method to get 30 | * a working configuration. 31 | * 32 | * @author Olivier Croisier (olivier.croisier@gmail.com) 33 | */ 34 | public class MidiDeviceConfiguration { 35 | 36 | /** Device signature of a Launchpad S, used for autodetection. */ 37 | public static final String DEVICE_SIGNATURE = "Launchpad S"; 38 | 39 | /** Inbound communication channel. */ 40 | private final MidiDevice inputDevice; 41 | /** Outbound communication channel. */ 42 | private final MidiDevice outputDevice; 43 | 44 | /** 45 | * Tries to auto-detect a MIDI Launchpad based on its device signature. 46 | * 47 | * @return The auto-detected configuration. 48 | * @throws MidiUnavailableException If an error occurs during device probing. 49 | */ 50 | public static MidiDeviceConfiguration autodetect() throws MidiUnavailableException { 51 | MidiDevice inputDevice = autodetectInputDevice(); 52 | MidiDevice outputDevice = autodetectOutputDevice(); 53 | return new MidiDeviceConfiguration(inputDevice, outputDevice); 54 | } 55 | 56 | /** 57 | * Constructor. 58 | * 59 | * @param inputDevice The inbound Midi channel. 60 | * @param outputDevice The outbound midi channel. 61 | */ 62 | public MidiDeviceConfiguration(MidiDevice inputDevice, MidiDevice outputDevice) { 63 | this.inputDevice = inputDevice; 64 | this.outputDevice = outputDevice; 65 | } 66 | 67 | /** 68 | * Returns the inbound communication channel. 69 | * 70 | * @return the inbound communication channel. 71 | */ 72 | public MidiDevice getInputDevice() { 73 | return inputDevice; 74 | } 75 | 76 | /** 77 | * Returns the outbound communication channel. 78 | * 79 | * @return the outbound communication channel. 80 | */ 81 | public MidiDevice getOutputDevice() { 82 | return outputDevice; 83 | } 84 | 85 | /** 86 | * Tries to detect a valid outbound communication channel, based on a known device signature 87 | * (see {@link net.thecodersbreakfast.lp4j.midi.MidiDeviceConfiguration#DEVICE_SIGNATURE}). 88 | * 89 | * @return A valid outbound communication channel, or {@code null} if non was found. 90 | * @throws MidiUnavailableException if the requested device is not available due to resource restrictions 91 | */ 92 | public static MidiDevice autodetectOutputDevice() throws MidiUnavailableException { 93 | MidiDevice.Info[] midiDeviceInfo = MidiSystem.getMidiDeviceInfo(); 94 | for (MidiDevice.Info info : midiDeviceInfo) { 95 | if (info.getDescription().contains(DEVICE_SIGNATURE) || info.getName().contains(DEVICE_SIGNATURE)) { 96 | MidiDevice device = MidiSystem.getMidiDevice(info); 97 | if (device.getMaxReceivers() == -1) { 98 | return device; 99 | } 100 | } 101 | } 102 | return null; 103 | } 104 | 105 | /** 106 | * Tries to detect a valid inbound communication channel, based on a known device signature 107 | * (see {@link net.thecodersbreakfast.lp4j.midi.MidiDeviceConfiguration#DEVICE_SIGNATURE}). 108 | * 109 | * @return A valid outbound communication channel, or {@code null} if non was found. 110 | * @throws MidiUnavailableException if the requested device is not available due to resource restrictions 111 | */ 112 | public static MidiDevice autodetectInputDevice() throws MidiUnavailableException { 113 | MidiDevice.Info[] midiDeviceInfo = MidiSystem.getMidiDeviceInfo(); 114 | for (MidiDevice.Info info : midiDeviceInfo) { 115 | if (info.getDescription().contains(DEVICE_SIGNATURE) || info.getName().contains(DEVICE_SIGNATURE)) { 116 | MidiDevice device = MidiSystem.getMidiDevice(info); 117 | if (device.getMaxTransmitters() == -1) { 118 | return device; 119 | } 120 | device.close(); 121 | } 122 | } 123 | return null; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/MidiLaunchpad.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import net.thecodersbreakfast.lp4j.api.Launchpad; 20 | import net.thecodersbreakfast.lp4j.api.LaunchpadClient; 21 | import net.thecodersbreakfast.lp4j.api.LaunchpadException; 22 | import net.thecodersbreakfast.lp4j.api.LaunchpadListener; 23 | import net.thecodersbreakfast.lp4j.midi.protocol.DefaultMidiProtocolClient; 24 | import net.thecodersbreakfast.lp4j.midi.protocol.DefaultMidiProtocolListener; 25 | import net.thecodersbreakfast.lp4j.midi.protocol.DefaultMidiProtocolReceiver; 26 | import net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolListener; 27 | 28 | import javax.sound.midi.MidiDevice; 29 | import javax.sound.midi.MidiUnavailableException; 30 | import javax.sound.midi.Receiver; 31 | import javax.sound.midi.Transmitter; 32 | import java.io.IOException; 33 | 34 | /** 35 | * Represents a physical MIDI Launchpad device. 36 | * 37 | * @author Olivier Croisier (olivier.croisier@gmail.com) 38 | */ 39 | public class MidiLaunchpad implements Launchpad { 40 | 41 | /** The Launchpad's input channel (Device -> LP4J). */ 42 | private final Receiver receiver; 43 | /** The Launchpad's output channel (LP4J -> Device). */ 44 | private final Transmitter transmitter; 45 | /** The MIDI configuration holder. */ 46 | private MidiDeviceConfiguration configuration; 47 | 48 | /** Indicates that the output channel has been successfully opened. */ 49 | private boolean openedOutputDevice = false; 50 | /** Indicates that the input channel has been successfully opened. */ 51 | private boolean openedInputDevice = false; 52 | 53 | /** 54 | * Constructor. 55 | * 56 | * @param configuration The MIDI configuration to use. Must not be null. 57 | * @throws MidiUnavailableException If the input or output channels cannot be opened. 58 | */ 59 | public MidiLaunchpad(MidiDeviceConfiguration configuration) throws MidiUnavailableException { 60 | if (configuration == null) { 61 | throw new IllegalArgumentException("Configuration must not be null"); 62 | } 63 | this.configuration = configuration; 64 | 65 | MidiDevice outputDevice = configuration.getOutputDevice(); 66 | if (outputDevice != null) { 67 | if (!outputDevice.isOpen()) { 68 | outputDevice.open(); 69 | } 70 | openedOutputDevice = true; 71 | this.receiver = outputDevice.getReceiver(); 72 | } else { 73 | this.receiver = null; 74 | } 75 | 76 | MidiDevice inputDevice = configuration.getInputDevice(); 77 | if (inputDevice != null) { 78 | if (!inputDevice.isOpen()) { 79 | inputDevice.open(); 80 | } 81 | openedInputDevice = true; 82 | this.transmitter = inputDevice.getTransmitter(); 83 | } else { 84 | this.transmitter = null; 85 | 86 | } 87 | } 88 | 89 | /** {@inheritDoc} */ 90 | @Override 91 | public LaunchpadClient getClient() { 92 | if (this.receiver == null) { 93 | throw new LaunchpadException("Unable to provide a client, because no Receiver or Output Device have been configured."); 94 | } 95 | return new MidiLaunchpadClient(new DefaultMidiProtocolClient(this.receiver)); 96 | } 97 | 98 | /** {@inheritDoc} */ 99 | @Override 100 | public void setListener(LaunchpadListener listener) { 101 | if (transmitter == null) { 102 | throw new LaunchpadException("Unable to set the listener, because no Transmitter or Input Device have been configured."); 103 | } 104 | MidiProtocolListener midiProtocolListener = new DefaultMidiProtocolListener(listener); 105 | Receiver midiReceiver = new DefaultMidiProtocolReceiver(midiProtocolListener); 106 | transmitter.setReceiver(midiReceiver); 107 | } 108 | 109 | /** {@inheritDoc} */ 110 | @Override 111 | public void close() throws IOException { 112 | if (configuration == null) { 113 | return; 114 | } 115 | if (openedOutputDevice) { 116 | MidiDevice outputDevice = configuration.getOutputDevice(); 117 | if (outputDevice != null && outputDevice.isOpen()) { 118 | outputDevice.close(); 119 | } 120 | } 121 | if (openedInputDevice) { 122 | MidiDevice inputDevice = configuration.getInputDevice(); 123 | if (inputDevice != null && inputDevice.isOpen()) { 124 | inputDevice.close(); 125 | } 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/protocol/MidiProtocolClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi.protocol; 18 | 19 | import javax.sound.midi.InvalidMidiDataException; 20 | 21 | /** 22 | * A low-level client used to send raw MIDI commands to a physical Launchpad device. 23 | * 24 | *

Please note that some that the Launchpad considers some of the (round) buttons as (square) pads, whereas some 25 | * other buttons are treated as, well, buttons. This explains some oddities in the method signatures. 26 | * 27 | *

For the full specification and details about the meaning of each command and their acceptable parameter values, 28 | * please refer to Novation's online documentation. 29 | * 30 | * @author Olivier Croisier (olivier.croisier@gmail.com) 31 | */ 32 | public interface MidiProtocolClient { 33 | 34 | /** 35 | * Sends a "reset" command. 36 | * 37 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 38 | */ 39 | void reset() throws InvalidMidiDataException; 40 | 41 | /** 42 | * Sends a "lights on" command, lightning all the lights at once. Useful to test the LEDs. 43 | * 44 | * @param intensity The desired light intensity. 45 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 46 | */ 47 | void lightsOn(int intensity) throws InvalidMidiDataException; 48 | 49 | /** 50 | * Sends a "note on" command, to light up the LED under a pad or button. 51 | * 52 | * @param note The "note" identifying the target pad or button. 53 | * @param color The color to display. 54 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 55 | */ 56 | void noteOn(int note, int color) throws InvalidMidiDataException; 57 | 58 | /** 59 | * Sends a "note off" command, to turn off the LED under a pad or button. 60 | * 61 | * @param note The "note" identifying the target pad or button. 62 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 63 | */ 64 | void noteOff(int note) throws InvalidMidiDataException; 65 | 66 | /** 67 | * A batch version of the "light on" command. Multiple lights can be set at once, starting from the upper-left one. 68 | * 69 | * @param colors The colors to display. 70 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 71 | */ 72 | void notesOn(int... colors) throws InvalidMidiDataException; 73 | 74 | /** 75 | * Toggles between a X-Y layout and "note-oriented" one. 76 | * 77 | * @param mode The layout to switch to. 78 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 79 | */ 80 | void layout(int mode) throws InvalidMidiDataException; 81 | 82 | /** 83 | * Sends a "button on" command, to light up the LED under a button. 84 | * 85 | * @param button The button to light up. 86 | * @param color The desired color. 87 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 88 | */ 89 | void buttonOn(int button, int color) throws InvalidMidiDataException; 90 | 91 | /** 92 | * Sets the overall brightness of the LEDs, expressed as a ratio between a numerator and a denominator. 93 | * 94 | * @param numerator The numerator. 95 | * @param denominator The denominator. 96 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 97 | */ 98 | void brightness(int numerator, int denominator) throws InvalidMidiDataException; 99 | 100 | /** 101 | * Scrolls the given text across the board, using an embedded 8x8 dot matrix font. 102 | * 103 | * @param text The text to display. 104 | * @param color The text color. 105 | * @param speed The scrolling speed. 106 | * @param loop Whether to display the text once, or to loop until a new "text" command is emitted. 107 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 108 | */ 109 | void text(String text, int color, int speed, boolean loop) throws InvalidMidiDataException; 110 | 111 | /** 112 | * Sets which buffer is written to, and which one is currently displayed (can be the same). 113 | * The "autoswap" parameter allows a "blinking" effect, where the Launchpad keeps swapping the visible and 114 | * non-visible buffers until autoswapping is turned off again. 115 | * 116 | * @param visibleBuffer The buffer to set as the visible buffer. 117 | * @param writeBuffer The buffer to set as the write buffer. 118 | * @param copyVisibleBufferToWriteBuffer Whether the visible buffer should be copied to the back buffer during the 119 | * process. 120 | * @param autoSwap Whether to trigger the "blinking" effect. Set to {@code false} to disable blinking. 121 | * @throws InvalidMidiDataException If a MIDI communication error occurs. 122 | */ 123 | void doubleBufferMode(int visibleBuffer, int writeBuffer, boolean copyVisibleBufferToWriteBuffer, boolean autoSwap) throws InvalidMidiDataException; 124 | } 125 | -------------------------------------------------------------------------------- /lp4j-api/src/main/java/net/thecodersbreakfast/lp4j/api/Button.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.api; 18 | 19 | /** 20 | * Represents the Launchpad's 16 round buttons (8 at the top, 8 on the right side) 21 | * 22 | * @author Olivier Croisier (olivier.croisier@gmail.com) 23 | */ 24 | public enum Button { 25 | 26 | /** The UP button (1st position from the left, on the top row) */ 27 | UP(0, true), 28 | /** The DOWN button (2nd position from the left, on the top row) */ 29 | DOWN(1, true), 30 | /** The LEFT button (3rd position from the left, on the top row) */ 31 | LEFT(2, true), 32 | /** The RIGHT button (4th position from the left, on the top row) */ 33 | RIGHT(3, true), 34 | /** The SESSION button (5th position from the left, on the top row) */ 35 | SESSION(4, true), 36 | /** The USER_1 button (6th position from the left, on the top row) */ 37 | USER_1(5, true), 38 | /** The USER_2 button (7th position from the left, on the top row) */ 39 | USER_2(6, true), 40 | /** The MIXER button (8th position from the left, on the top row) */ 41 | MIXER(7, true), 42 | /** The VOL button (1st position from the top, on the right side) */ 43 | VOL(0, false), 44 | /** The PAN button (2nd position from the top, on the right side) */ 45 | PAN(1, false), 46 | /** The SND_A button (3rd position from the top, on the right side) */ 47 | SND_A(2, false), 48 | /** The SND_B button (4th position from the top, on the right side) */ 49 | SND_B(3, false), 50 | /** The STOP button (5th position from the top, on the right side) */ 51 | STOP(4, false), 52 | /** The TRACK_ON button (6th position from the top, on the right side) */ 53 | TRACK_ON(5, false), 54 | /** The SOLO button (7th position from the top, on the right side) */ 55 | SOLO(6, false), 56 | /** The ARM button (8th position from the top, on the right side) */ 57 | ARM(7, false); 58 | 59 | /** Minimal coordinate for a button */ 60 | public static final int MIN_COORD = 0; 61 | /** Maximal coordinate for a button */ 62 | public static final int MAX_COORD = 7; 63 | 64 | /** Top-row buttons, in left-to-right order */ 65 | public static final Button[] BUTTONS_TOP = {UP, DOWN, LEFT, RIGHT, SESSION, USER_1, USER_2, MIXER}; 66 | /** Right-side buttons, in top-to-bottom order */ 67 | public static final Button[] BUTTONS_RIGHT = {VOL, PAN, SND_A, SND_B, STOP, TRACK_ON, SOLO, ARM}; 68 | 69 | /** 70 | * Factory method for a top-row button. 71 | * 72 | * @param isTopButton {@code true} if the button is on the top row, {@code false} if it is on the right side 73 | * @param c The coordinate of the button. Must be in range [{@link Button#MIN_COORD},{@link Button#MAX_COORD}]. 74 | * @return The button 75 | * @throws IllegalArgumentException if the coordinates are invalid. 76 | */ 77 | private static Button at(boolean isTopButton, int c) { 78 | if (c < MIN_COORD || c > MAX_COORD) { 79 | throw new IllegalArgumentException(String.format("Invalid button coordinates : %d. Value must be between %d and %d inclusive.", c, MIN_COORD, MAX_COORD)); 80 | } 81 | return isTopButton ? BUTTONS_TOP[c] : BUTTONS_RIGHT[c]; 82 | } 83 | 84 | /** 85 | * Factory method for a top-row button. 86 | * 87 | * @param c The coordinate of the button. Must be in range [{@link Button#MIN_COORD},{@link Button#MAX_COORD}]. 88 | * @return The button 89 | * @throws IllegalArgumentException if the coordinates are invalid. 90 | */ 91 | public static Button atTop(int c) { 92 | return at(true, c); 93 | } 94 | 95 | /** 96 | * Factory method for a top-row button. 97 | * 98 | * @param c The coordinate of the button. Must be in range [{@link Button#MIN_COORD},{@link Button#MAX_COORD}]. 99 | * @return The button 100 | * @throws IllegalArgumentException if the coordinates are invalid. 101 | */ 102 | public static Button atRight(int c) { 103 | return at(false, c); 104 | } 105 | 106 | /** The button coordinates */ 107 | private final int coordinate; 108 | /** Tells if it is a top-row button ({@code true}), as opposed to a righ-side button ({@code false}) */ 109 | private final boolean topButton; 110 | 111 | /** 112 | * Constructor. 113 | * 114 | * @param coordinate The coordinate of the button. 115 | * @param topButton {@code true} for a top-row button, {@code false} otherwise. 116 | */ 117 | private Button(int coordinate, boolean topButton) { 118 | this.coordinate = coordinate; 119 | this.topButton = topButton; 120 | } 121 | 122 | /** 123 | * Returns the button coordinate. 124 | * 125 | * @return The coordinate. 126 | */ 127 | public int getCoordinate() { 128 | return coordinate; 129 | } 130 | 131 | /** 132 | * Tells if this button is located on the top row. 133 | * 134 | * @return {@code true} if this button belongs to the top row, {@code false} otherwise. 135 | */ 136 | public boolean isTopButton() { 137 | return topButton; 138 | } 139 | 140 | /** 141 | * Tells if this button is located on the right side. 142 | * 143 | * @return {@code true} if this button belongs to the right side, {@code false} otherwise. 144 | */ 145 | public boolean isRightButton() { 146 | return !topButton; 147 | } 148 | 149 | @Override 150 | public String toString() { 151 | return String.format("Button[%s(%s,%d)]", name(), topButton ? "top" : "right", coordinate); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/protocol/DefaultMidiProtocolClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi.protocol; 18 | 19 | import net.thecodersbreakfast.lp4j.api.LaunchpadException; 20 | 21 | import javax.sound.midi.*; 22 | import java.nio.charset.Charset; 23 | 24 | /** 25 | * Default implementation of a {@link net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolClient}. 26 | * 27 | * @author Olivier Croisier (olivier.croisier@gmail.com) 28 | */ 29 | public class DefaultMidiProtocolClient implements MidiProtocolClient { 30 | 31 | /** The Launchpad's Receiver, to which commands are sent. */ 32 | private final Receiver receiver; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param receiver The Launchpad's MIDI Receiver. Must not be null. 38 | */ 39 | public DefaultMidiProtocolClient(Receiver receiver) { 40 | if (receiver == null) { 41 | throw new IllegalArgumentException("Receiver must not be null."); 42 | } 43 | this.receiver = receiver; 44 | } 45 | 46 | // ================================================================================ 47 | // Low-level MIDI output API 48 | // ================================================================================ 49 | 50 | /** {@inheritDoc} */ 51 | @Override 52 | public void reset() throws InvalidMidiDataException { 53 | sendShortMessage(ShortMessage.CONTROL_CHANGE, 0, 0); 54 | } 55 | 56 | /** {@inheritDoc} */ 57 | @Override 58 | public void lightsOn(int intensity) throws InvalidMidiDataException { 59 | sendShortMessage(ShortMessage.CONTROL_CHANGE, 0, intensity); 60 | } 61 | 62 | /** {@inheritDoc} */ 63 | @Override 64 | public void noteOn(int note, int color) throws InvalidMidiDataException { 65 | sendShortMessage(ShortMessage.NOTE_ON, note, color); 66 | } 67 | 68 | /** {@inheritDoc} */ 69 | @Override 70 | public void noteOff(int note) throws InvalidMidiDataException { 71 | sendShortMessage(ShortMessage.NOTE_OFF, note, 0); 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | * 77 | * @param colors {@inheritDoc} Must not be null. 78 | */ 79 | @Override 80 | public void notesOn(int... colors) throws InvalidMidiDataException { 81 | if (colors == null) { 82 | throw new IllegalArgumentException("Colors should not be null."); 83 | } 84 | int nbMessages = colors.length / 2; 85 | for (int i = 0; i < nbMessages; i++) { 86 | sendShortMessage(ShortMessage.NOTE_ON, 3, colors[i * 2], colors[i * 2 + 1]); 87 | } 88 | } 89 | 90 | /** {@inheritDoc} */ 91 | @Override 92 | public void layout(int mode) throws InvalidMidiDataException { 93 | sendShortMessage(ShortMessage.CONTROL_CHANGE, 0, mode); 94 | } 95 | 96 | /** {@inheritDoc} */ 97 | @Override 98 | public void buttonOn(int button, int color) throws InvalidMidiDataException { 99 | sendShortMessage(ShortMessage.CONTROL_CHANGE, button, color); 100 | } 101 | 102 | /** {@inheritDoc} */ 103 | @Override 104 | public void brightness(int numerator, int denominator) throws InvalidMidiDataException { 105 | if (numerator < 9) { 106 | sendShortMessage(ShortMessage.CONTROL_CHANGE, 30, 16 * (numerator - 1) + (denominator - 3)); 107 | } else { 108 | sendShortMessage(ShortMessage.CONTROL_CHANGE, 31, 16 * (numerator - 9) + (denominator - 3)); 109 | } 110 | } 111 | 112 | /** {@inheritDoc} */ 113 | @Override 114 | public void text(String text, int color, int speed, boolean loop) throws InvalidMidiDataException { 115 | if (loop) { 116 | color += 64; 117 | } 118 | byte[] header = {(byte) 240, 0, 32, 41, 9, (byte) color}; 119 | byte[] chars = text == null ? new byte[]{} : text.getBytes(Charset.forName("ASCII")); 120 | byte[] message = new byte[chars.length + 8]; 121 | System.arraycopy(header, 0, message, 0, header.length); 122 | message[header.length] = (byte) speed; 123 | System.arraycopy(chars, 0, message, header.length + 1, chars.length); 124 | message[message.length - 1] = (byte) 247; 125 | sendSysExMessage(message); 126 | } 127 | 128 | /** {@inheritDoc} */ 129 | @Override 130 | public void doubleBufferMode(int displayBuffer, int writeBuffer, boolean copyVisibleBufferToWriteBuffer, boolean autoSwap) throws InvalidMidiDataException { 131 | int mode = 32 + 4 * writeBuffer + displayBuffer; 132 | if (copyVisibleBufferToWriteBuffer) { 133 | mode |= 16; 134 | } 135 | if (autoSwap) { 136 | mode |= 8; 137 | } 138 | sendShortMessage(ShortMessage.CONTROL_CHANGE, 0, mode); 139 | } 140 | 141 | 142 | // ================================================================================ 143 | // Utils 144 | // ================================================================================ 145 | 146 | private void sendShortMessage(int command, int controller, int data) throws LaunchpadException, InvalidMidiDataException { 147 | ShortMessage message = new ShortMessage(); 148 | message.setMessage(command, controller, data); 149 | send(message); 150 | } 151 | 152 | private void sendShortMessage(int command, int channel, int controller, int data) throws LaunchpadException, InvalidMidiDataException { 153 | ShortMessage message = new ShortMessage(); 154 | message.setMessage(command, channel, controller, data); 155 | send(message); 156 | } 157 | 158 | private void sendSysExMessage(byte[] data) throws InvalidMidiDataException { 159 | SysexMessage message = new SysexMessage(); 160 | message.setMessage(data, data.length); 161 | send(message); 162 | } 163 | 164 | private void send(MidiMessage message) { 165 | this.receiver.send(message, -1); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/resources/web/emulator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // The standart SVG namespace 18 | var SVGNS = "http://www.w3.org/2000/svg"; 19 | 20 | // The Launchpad emulator 21 | var launchpad; 22 | 23 | // The Vertx event bus 24 | var eventbus; 25 | 26 | // Eventbus IDs of the client (browser-side) and server (application-side) emulator parts 27 | var CLIENT_EVENTBUS_ID = 'lp4j:client'; 28 | var SERVER_EVENTBUS_ID = 'lp4j:server'; 29 | 30 | 31 | // Sends the given message on the event bus, to the server-side part of the emulator 32 | function sendToServer(params) { 33 | if (eventbus) { 34 | eventbus.send(SERVER_EVENTBUS_ID, params); 35 | } 36 | } 37 | 38 | 39 | // Initializes the launchpad instance and its listener 40 | function configureLaunchpad() { 41 | 42 | var listener = new LaunchpadListener(); 43 | listener.onPadPressed = function (x, y) { 44 | sendToServer({"evt": "PP", "x": x, "y": y}); 45 | }; 46 | listener.onPadReleased = function (x, y) { 47 | sendToServer({"evt": "PR", "x": x, "y": y}); 48 | }; 49 | listener.onButtonPressed = function (x, y) { 50 | sendToServer({"evt": "BP", "x": x, "y": y}); 51 | }; 52 | listener.onButtonReleased = function (x, y) { 53 | sendToServer({"evt": "BR", "x": x, "y": y}); 54 | }; 55 | 56 | launchpad = new Launchpad(); 57 | launchpad.setListener(listener); 58 | } 59 | 60 | 61 | // Handles commands send to the launchpad 62 | function handleClientCommand(event) { 63 | var eventType = event.evt; 64 | // Event types defined in EmulatorClient$OutputEventType enum 65 | switch (eventType) { 66 | case "RST" : 67 | launchpad.reset(); 68 | break; 69 | case "PADLGT" : 70 | launchpad.setPadLight(event.x, event.y, event.c, event.o); 71 | break; 72 | case "BTNLGT" : 73 | launchpad.setButtonLight(event.t, event.i, event.c, event.o); 74 | break; 75 | case "BRGHT" : 76 | launchpad.setBrightness(event.b); 77 | break; 78 | case "BUF" : 79 | launchpad.setBuffers(event.v, event.w, event.c, event.a); 80 | break; 81 | case "TST" : 82 | launchpad.testLights(event.i); 83 | break; 84 | } 85 | } 86 | 87 | 88 | $(document).ready(function () { 89 | 90 | configureLaunchpad(); 91 | initDisplay(document.getElementById("launchpad")); 92 | 93 | eventbus = new vertx.EventBus('/eventbus'); 94 | eventbus.onopen = function () { 95 | eventbus.registerHandler(CLIENT_EVENTBUS_ID, function (event) { 96 | handleClientCommand(event); 97 | updateDisplay(launchpad); 98 | }); 99 | } 100 | 101 | }); 102 | 103 | 104 | // Creates the emulator SVG display 105 | function initDisplay(container) { 106 | 107 | // Root SVG node 108 | var svg = document.createElementNS(SVGNS, "svg"); 109 | svg.setAttribute("xmlns", SVGNS); 110 | svg.setAttribute("viewBox", "0 0 110 110"); // 10px per pad/button, 2px gutter 111 | svg.setAttribute("preserveAspectRatio", "xMinYMin meet"); 112 | container.appendChild(svg); 113 | 114 | // Launchpad background 115 | var bg = document.createElementNS(SVGNS, "rect"); 116 | bg.setAttribute("width", "110"); 117 | bg.setAttribute("height", "110"); 118 | bg.setAttribute("fill", "#444"); 119 | bg.setAttribute("stroke", "#000"); 120 | bg.setAttribute("stroke-width", "2"); 121 | svg.appendChild(bg); 122 | 123 | var listener = launchpad.listener || new LaunchpadListener(); 124 | 125 | var btnNames = ["^", "v", "<", ">", "SES", "USR1", "USR2", "MIX", "VOL", "PAN", "SNDA", "SNDB", "STOP","TRCK", "SOLO", "ARM"]; 126 | 127 | for (var x = 0; x < 9; x++) { 128 | for (var y = 0; y < 9; y++) { 129 | if (x == 8 && y == 0) continue; 130 | var pad; 131 | 132 | // Round buttons on top and right sides 133 | if (x == 8 || y == 0) { 134 | var centerX = (2 + 12 * x + 5).toString(); 135 | var centerY = (2 + 12 * y + 5).toString(); 136 | pad = document.createElementNS(SVGNS, "circle"); 137 | pad.setAttribute("cx", centerX); 138 | pad.setAttribute("cy", centerY); 139 | pad.setAttribute("r", "4"); 140 | pad.onmousedown = (function (pX, pY) { 141 | return function (e) { 142 | e.preventDefault(); 143 | if (pX == 8) pX = -1; 144 | listener.onButtonPressed(pX, pY - 1); 145 | } 146 | })(x, y); 147 | pad.onmouseup = (function (pX, pY) { 148 | return function (e) { 149 | e.preventDefault(); 150 | // Ctrl-clicking skips the "button released" event 151 | if (e.ctrlKey) return; 152 | if (pX == 8) pX = -1; 153 | listener.onButtonReleased(pX, pY - 1); 154 | } 155 | })(x, y); 156 | pad.setAttribute("id", "key" + x + y); 157 | pad.style.fill = launchpad.colors[0][0]; 158 | svg.appendChild(pad); 159 | 160 | // Button text 161 | var btnName; 162 | if (y==0) { 163 | btnName = btnNames[x] 164 | } else { 165 | btnName = btnNames[7+y]; 166 | } 167 | var txtEl = document.createElementNS(SVGNS, "text"); 168 | txtEl.setAttribute("text-anchor","middle"); 169 | txtEl.setAttribute("x",centerX); 170 | txtEl.setAttribute("y",centerY); 171 | txtEl.setAttribute("font-size","12%"); 172 | txtEl.setAttribute("font-family","sans-serif"); 173 | txtEl.setAttribute("font-weight","bold"); 174 | txtEl.setAttribute("pointer-events","none"); 175 | var txt = document.createTextNode(btnName); 176 | txtEl.appendChild(txt); 177 | svg.appendChild(txtEl); 178 | } 179 | 180 | // Square pads 181 | else { 182 | pad = document.createElementNS(SVGNS, "rect"); 183 | pad.setAttribute("x", (2 + 12 * x).toString()); 184 | pad.setAttribute("y", (2 + 12 * y).toString()); 185 | pad.setAttribute("width", "10"); 186 | pad.setAttribute("height", "10"); 187 | pad.onmousedown = (function (pX, pY) { 188 | return function (e) { 189 | e.preventDefault(); 190 | listener.onPadPressed(pX, pY - 1); 191 | } 192 | })(x, y); 193 | pad.onmouseup = (function (pX, pY) { 194 | return function (e) { 195 | e.preventDefault(); 196 | // Ctrl-clicking skips the "button released" event 197 | if (e.ctrlKey) return; 198 | listener.onPadReleased(pX, pY - 1); 199 | } 200 | })(x, y); 201 | pad.setAttribute("id", "key" + x + y); 202 | pad.style.fill = launchpad.colors[0][0]; 203 | svg.appendChild(pad); 204 | } 205 | } 206 | } 207 | } 208 | 209 | 210 | // Updates the SVG display with the latest Launchpad state 211 | function updateDisplay(launchpad) { 212 | for (var x = 0; x < 9; x++) { 213 | for (var y = 0; y < 9; y++) { 214 | if (x == 8 && y == 0) continue; 215 | var color = launchpad.buffers[launchpad.visibleBuffer][x][y] || launchpad.colors[0][0]; 216 | var shape = document.getElementById("key" + x + y); 217 | // Do not apply brightness correction to non-lit pads and buttons 218 | shape.style.opacity = (color == launchpad.colors[0][0]) ? 1 : launchpad.brightness; 219 | shape.style.fill = color; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/java/net/thecodersbreakfast/lp4j/emulator/EmulatorLaunchpadClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.emulator; 18 | 19 | import net.thecodersbreakfast.lp4j.api.*; 20 | import org.vertx.java.core.Vertx; 21 | import org.vertx.java.core.json.JsonObject; 22 | 23 | /** 24 | * A client to communicate with the Launchpad emulator 25 | * 26 | * @author Olivier Croisier (olivier.croisier@gmail.com) 27 | */ 28 | public class EmulatorLaunchpadClient implements LaunchpadClient { 29 | 30 | /** Eventbus ID of the emulator, on the browser side */ 31 | private static final String EVENTBUS_CLIENT_HANDLER_ID = "lp4j:client"; 32 | 33 | /** Types of events sent to the emulator, on the browser side */ 34 | private static enum OutputEventType { 35 | /** Reset */ 36 | RST, 37 | /** Padlight */ 38 | PADLGT, 39 | /** Button light */ 40 | BTNLGT, 41 | /** Test */ 42 | TST, 43 | /** Brightness */ 44 | BRGHT, 45 | /** SetBuffers */ 46 | BUF 47 | } 48 | 49 | /** The Vertx engine that powers the emulator on the server side */ 50 | private final Vertx vertx; 51 | 52 | /** 53 | * Constructor 54 | * 55 | * @param vertx The Vertx engine to use 56 | */ 57 | public EmulatorLaunchpadClient(Vertx vertx) { 58 | this.vertx = vertx; 59 | } 60 | 61 | @Override 62 | public void reset() { 63 | publishEvent(OutputEventType.RST); 64 | } 65 | 66 | /** 67 | * {@inheritDoc} 68 | * 69 | * @param intensity The desired light intensity. Must not be null. 70 | */ 71 | @Override 72 | public void testLights(LightIntensity intensity) { 73 | if (intensity == null) { 74 | throw new IllegalArgumentException("Light intensity must not be null."); 75 | } 76 | int brightness = 0; 77 | if (intensity == LightIntensity.LOW) { 78 | brightness = 5; 79 | } 80 | if (intensity == LightIntensity.MEDIUM) { 81 | brightness = 10; 82 | } 83 | if (intensity == LightIntensity.HIGH) { 84 | brightness = 15; 85 | } 86 | JsonObject params = new JsonObject() 87 | .putNumber("i", brightness); 88 | publishEvent(OutputEventType.TST, params); 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | * 94 | *

NOT IMPLEMENTED YET ON THE EMULATOR 95 | */ 96 | @Override 97 | public void setLights(Color[] colors, BackBufferOperation operation) { 98 | if (colors == null) { 99 | throw new IllegalArgumentException("Colors must not be null"); 100 | } 101 | if (operation == null) { 102 | throw new IllegalArgumentException("BackBuffer operation must not be null."); 103 | } 104 | } 105 | 106 | /** 107 | * {@inheritDoc} 108 | * 109 | * @param pad The pad to light up. Must not be null. 110 | * @param color The color to use. Use {@link net.thecodersbreakfast.lp4j.api.Color#BLACK} to switch the light off. Must not be null. 111 | * @param operation What to do on the backbuffer. Must not be null. 112 | */ 113 | @Override 114 | public void setPadLight(Pad pad, Color color, BackBufferOperation operation) { 115 | if (pad == null) { 116 | throw new IllegalArgumentException("Pad must not be null"); 117 | } 118 | if (color == null) { 119 | throw new IllegalArgumentException("Color must not be null."); 120 | } 121 | if (operation == null) { 122 | throw new IllegalArgumentException("BackBuffer operation must not be null."); 123 | } 124 | JsonObject params = new JsonObject() 125 | .putNumber("x", pad.getX()) 126 | .putNumber("y", pad.getY()) 127 | .putObject("c", new JsonObject().putNumber("r", color.getRed()).putNumber("g", color.getGreen())) 128 | .putString("o", operation.name()); 129 | publishEvent(OutputEventType.PADLGT, params); 130 | } 131 | 132 | /** 133 | * {@inheritDoc} 134 | * 135 | * @param button The button to light up. Must not be null. 136 | * @param color The color to use. Use {@link net.thecodersbreakfast.lp4j.api.Color#BLACK} to switch the light off. Must not be null. 137 | * @param operation What to do on the backbuffer. Must not be null. 138 | */ 139 | @Override 140 | public void setButtonLight(Button button, Color color, BackBufferOperation operation) { 141 | if (button == null) { 142 | throw new IllegalArgumentException("Button must not be null."); 143 | } 144 | if (color == null) { 145 | throw new IllegalArgumentException("Color must not be null."); 146 | } 147 | if (operation == null) { 148 | throw new IllegalArgumentException("BackBuffer operation must not be null."); 149 | } 150 | JsonObject params = new JsonObject() 151 | .putBoolean("t", button.isTopButton()) 152 | .putNumber("i", button.getCoordinate()) 153 | .putObject("c", new JsonObject().putNumber("r", color.getRed()).putNumber("g", color.getGreen())) 154 | .putString("o", operation.name()); 155 | publishEvent(OutputEventType.BTNLGT, params); 156 | } 157 | 158 | /** 159 | * {@inheritDoc} 160 | * 161 | * @param brightness The desired brightness. Must not be null. 162 | */ 163 | @Override 164 | public void setBrightness(Brightness brightness) { 165 | if (brightness == null) { 166 | throw new IllegalArgumentException("Brightness must not be null"); 167 | } 168 | JsonObject params = new JsonObject() 169 | .putNumber("b", brightness.getBrightness()); 170 | publishEvent(OutputEventType.BRGHT, params); 171 | } 172 | 173 | /** 174 | * {@inheritDoc} 175 | * 176 | * @param visibleBuffer The buffer to display. Must not be null. 177 | * @param writeBuffer The buffer to which the commands are applied. Must not be null. 178 | */ 179 | @Override 180 | public void setBuffers(Buffer visibleBuffer, Buffer writeBuffer, boolean copyVisibleBufferToWriteBuffer, boolean autoSwap) { 181 | if (visibleBuffer == null) { 182 | throw new IllegalArgumentException("Visible buffer must not be null."); 183 | } 184 | if (writeBuffer == null) { 185 | throw new IllegalArgumentException("Write buffer must not be null."); 186 | } 187 | JsonObject params = new JsonObject() 188 | .putString("v", visibleBuffer.name()) 189 | .putString("w", writeBuffer.name()) 190 | .putBoolean("c", copyVisibleBufferToWriteBuffer) 191 | .putBoolean("a", autoSwap); 192 | publishEvent(OutputEventType.BUF, params); 193 | } 194 | 195 | /** 196 | * {@inheritDoc} 197 | * 198 | *

NOT IMPLEMENTED YET ON THE EMULATOR 199 | */ 200 | @Override 201 | public void scrollText(String text, Color color, ScrollSpeed speed, boolean loop, BackBufferOperation operation) { 202 | if (color == null) { 203 | throw new IllegalArgumentException("Color must not be null."); 204 | } 205 | } 206 | 207 | /** 208 | * Sends the given event to the emulator, with no additional parameters 209 | * 210 | * @param outputEventType The event to send 211 | */ 212 | private void publishEvent(OutputEventType outputEventType) { 213 | publishEvent(outputEventType, null); 214 | } 215 | 216 | /** 217 | * Sends the given event to the emulator, with the given additional parameters 218 | * 219 | * @param outputEventType The event to send 220 | * @param params The additional parameters 221 | */ 222 | private void publishEvent(OutputEventType outputEventType, JsonObject params) { 223 | JsonObject payload = new JsonObject(); 224 | payload.putString("evt", outputEventType.name()); 225 | if (params != null) { 226 | payload.mergeIn(params); 227 | } 228 | vertx.eventBus().publish(EVENTBUS_CLIENT_HANDLER_ID, payload); 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/java/net/thecodersbreakfast/lp4j/emulator/EmulatorLaunchpad.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.emulator; 18 | 19 | import net.thecodersbreakfast.lp4j.api.*; 20 | import org.vertx.java.core.Handler; 21 | import org.vertx.java.core.Vertx; 22 | import org.vertx.java.core.VertxFactory; 23 | import org.vertx.java.core.buffer.Buffer; 24 | import org.vertx.java.core.eventbus.Message; 25 | import org.vertx.java.core.http.HttpServer; 26 | import org.vertx.java.core.http.HttpServerRequest; 27 | import org.vertx.java.core.http.HttpServerResponse; 28 | import org.vertx.java.core.json.JsonArray; 29 | import org.vertx.java.core.json.JsonObject; 30 | 31 | import java.io.ByteArrayOutputStream; 32 | import java.io.IOException; 33 | import java.io.InputStream; 34 | 35 | /** 36 | * A web-based (HTML/SVG/Sebsockets) Launchpad emulator. 37 | * 38 | * @author Olivier Croisier (olivier.croisier@gmail.com) 39 | */ 40 | public class EmulatorLaunchpad implements Launchpad { 41 | 42 | /** Directory to serve static files from. */ 43 | public static final String WEB_RESOURCES_PREFIX = "/web"; 44 | /** URL of the Vertx eventbus bridge */ 45 | public static final String EVENTBUS_ADDRESS = "/eventbus"; 46 | /** Eventbus ID of the emulator, on the server side */ 47 | public static final String EVENTBUS_SERVER_HANDLER_ID = "lp4j:server"; 48 | /** Handler for Vertx eventbus messages. */ 49 | private final EventBusHandler eventBusHandler = new EventBusHandler(); 50 | /** Vertx engine instance. */ 51 | private final Vertx vertx; 52 | 53 | /** 54 | * Constructor. 55 | * 56 | * @param httpPort The HTTP port on which the emulator should run. 57 | */ 58 | public EmulatorLaunchpad(int httpPort) { 59 | 60 | vertx = VertxFactory.newVertx(); 61 | 62 | // Static files 63 | HttpServer httpServer = vertx.createHttpServer(); 64 | httpServer.requestHandler(new WebResourceHandler()); 65 | 66 | // Eventbus bridge 67 | JsonObject bridgeConfig = new JsonObject().putString("prefix", EVENTBUS_ADDRESS); 68 | JsonArray credentialsPermitAll = new JsonArray().add(new JsonObject()); 69 | vertx.createSockJSServer(httpServer).bridge(bridgeConfig, credentialsPermitAll, credentialsPermitAll); 70 | vertx.eventBus().registerLocalHandler(EVENTBUS_SERVER_HANDLER_ID, eventBusHandler); 71 | 72 | System.out.println("Launchpad emulator is ready on http://localhost:" + httpPort + "/"); 73 | httpServer.listen(httpPort); 74 | } 75 | 76 | /** {@inheritDoc} */ 77 | @Override 78 | public LaunchpadClient getClient() { 79 | return new EmulatorLaunchpadClient(vertx); 80 | } 81 | 82 | /** {@inheritDoc} */ 83 | @Override 84 | public void setListener(LaunchpadListener listener) { 85 | this.eventBusHandler.setListener(listener); 86 | } 87 | 88 | /** {@inheritDoc} */ 89 | @Override 90 | public void close() throws IOException { 91 | vertx.stop(); 92 | } 93 | 94 | /** 95 | * Handler for standart HTTP requests, used to serve static files. 96 | */ 97 | private static class WebResourceHandler implements Handler { 98 | @Override 99 | public void handle(HttpServerRequest req) { 100 | String resourcePath = req.path(); 101 | HttpServerResponse response = req.response(); 102 | 103 | if (shouldRedirectToIndexHtml(resourcePath)) { 104 | redirectToIndexHtml(response); 105 | return; 106 | } 107 | 108 | if (!isLegalResource(resourcePath)) { 109 | sendResponseForbidden(response); 110 | return; 111 | } 112 | 113 | if (!writeResourceToResponse(response, resourcePath)) { 114 | sendResponseNotFound(response); 115 | } 116 | 117 | } 118 | 119 | private boolean shouldRedirectToIndexHtml(String resourcePath) { 120 | return "/".equals(resourcePath); 121 | } 122 | 123 | private void redirectToIndexHtml(HttpServerResponse response) { 124 | response.headers().add("Location", WEB_RESOURCES_PREFIX + "/index.html"); 125 | response.setStatusCode(301).end(); 126 | } 127 | 128 | private boolean isLegalResource(String resourcePath) { 129 | return resourcePath.startsWith(WEB_RESOURCES_PREFIX); 130 | } 131 | 132 | private void sendResponseForbidden(HttpServerResponse response) { 133 | response.setStatusCode(403).end(); 134 | } 135 | 136 | private void setResponseContentType(HttpServerResponse response, String resourcePath) { 137 | String contentType = findContentType(resourcePath); 138 | response.headers().add("Content-Type", contentType); 139 | } 140 | 141 | private String findContentType(String resourcePath) { 142 | if (resourcePath.endsWith(".js")) { 143 | return "application/javascript"; 144 | } else { 145 | return "text/html"; 146 | } 147 | } 148 | 149 | private boolean writeResourceToResponse(HttpServerResponse response, String resourcePath) { 150 | setResponseContentType(response, resourcePath); 151 | byte[] bytes = readResource(resourcePath); 152 | if (bytes == null) { 153 | return false; 154 | } 155 | Buffer buffer = new Buffer(bytes); 156 | response.end(buffer); 157 | return true; 158 | } 159 | 160 | private static byte[] readResource(String path) { 161 | try { 162 | InputStream is = EmulatorLaunchpad.class.getResourceAsStream(path); 163 | if (is == null) { 164 | return null; 165 | } 166 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 167 | int reads = is.read(); 168 | while (reads != -1) { 169 | baos.write(reads); 170 | reads = is.read(); 171 | } 172 | return baos.toByteArray(); 173 | } catch (IOException ex) { 174 | ex.printStackTrace(); 175 | return null; 176 | } 177 | } 178 | 179 | private void sendResponseNotFound(HttpServerResponse response) { 180 | response.setStatusCode(404).end(); 181 | } 182 | } 183 | 184 | /** 185 | * Handler for Vertx eventbus messages. 186 | */ 187 | private static class EventBusHandler implements Handler { 188 | 189 | private static enum InputEventType { 190 | /** Pad pressed */ 191 | PP, 192 | /** Pad released */ 193 | PR, 194 | /** Button pressed */ 195 | BP, 196 | /** Button released */ 197 | BR, 198 | /** Text scrolled */ 199 | TS 200 | } 201 | 202 | private LaunchpadListener listener; 203 | 204 | @Override 205 | public void handle(Message message) { 206 | if (listener == null) { 207 | return; 208 | } 209 | 210 | long timestamp = System.currentTimeMillis(); 211 | JsonObject body = (JsonObject) message.body(); 212 | InputEventType inputEventType = InputEventType.valueOf(body.getString("evt")); 213 | switch (inputEventType) { 214 | case PP: { 215 | Integer x = body.getInteger("x"); 216 | Integer y = body.getInteger("y"); 217 | listener.onPadPressed(Pad.at(x, y), timestamp); 218 | break; 219 | } 220 | case PR: { 221 | Integer x = body.getInteger("x"); 222 | Integer y = body.getInteger("y"); 223 | listener.onPadReleased(Pad.at(x, y), timestamp); 224 | break; 225 | } 226 | case BP: { 227 | int x = body.getInteger("x"); 228 | int y = body.getInteger("y"); 229 | int c = x == -1 ? y : x; 230 | Button button = (x != -1) ? Button.atTop(c) : Button.atRight(c); 231 | listener.onButtonPressed(button, timestamp); 232 | break; 233 | } 234 | case BR: { 235 | int x = body.getInteger("x"); 236 | int y = body.getInteger("y"); 237 | int c = x == -1 ? y : x; 238 | Button button = (x != -1) ? Button.atTop(c) : Button.atRight(c); 239 | listener.onButtonReleased(button, timestamp); 240 | break; 241 | } 242 | case TS: { 243 | listener.onTextScrolled(timestamp); 244 | break; 245 | } 246 | default: { 247 | throw new IllegalArgumentException("Unknown input event type " + inputEventType.name()); 248 | } 249 | } 250 | 251 | } 252 | 253 | public void setListener(LaunchpadListener listener) { 254 | this.listener = listener; 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /lp4j-midi/src/test/java/net.thecodersbreakfast.lp4j.midi/DefaultMidiProtocolClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import net.thecodersbreakfast.lp4j.midi.protocol.DefaultMidiProtocolClient; 20 | import net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolClient; 21 | import org.junit.Assert; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.mockito.ArgumentCaptor; 26 | import org.mockito.Mock; 27 | import org.mockito.runners.MockitoJUnitRunner; 28 | 29 | import javax.sound.midi.Receiver; 30 | import javax.sound.midi.ShortMessage; 31 | import javax.sound.midi.SysexMessage; 32 | 33 | import static org.mockito.Matchers.eq; 34 | import static org.mockito.Mockito.times; 35 | import static org.mockito.Mockito.verify; 36 | 37 | @RunWith(MockitoJUnitRunner.class) 38 | public class DefaultMidiProtocolClientTest { 39 | 40 | public static final int LIGHT_INTENSITY = 125; 41 | public static final int COLOR_RED = 3; 42 | public static final int COLOR_BLACK = 0; 43 | public static final int NOTE_1_1 = 17; 44 | public static final int LAYOUT_XY = 1; 45 | public static final int BUTTON_UP = 104; 46 | 47 | @Mock 48 | private Receiver receiver; 49 | private MidiProtocolClient midiProtocolClient; 50 | private ArgumentCaptor shortMessage; 51 | private ArgumentCaptor sysexMessage; 52 | 53 | @Before 54 | public void init() { 55 | midiProtocolClient = new DefaultMidiProtocolClient(receiver); 56 | shortMessage = ArgumentCaptor.forClass(ShortMessage.class); 57 | sysexMessage = ArgumentCaptor.forClass(SysexMessage.class); 58 | } 59 | 60 | /* 61 | ================================================================================ 62 | reset 63 | ================================================================================ 64 | */ 65 | 66 | @Test 67 | public void testReset() throws Exception { 68 | midiProtocolClient.reset(); 69 | 70 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 71 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, 0); 72 | } 73 | 74 | /* 75 | ================================================================================ 76 | lightsOn 77 | ================================================================================ 78 | */ 79 | 80 | @Test 81 | public void testLightsOn() throws Exception { 82 | midiProtocolClient.lightsOn(LIGHT_INTENSITY); 83 | 84 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 85 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, LIGHT_INTENSITY); 86 | } 87 | 88 | /* 89 | ================================================================================ 90 | noteOn 91 | ================================================================================ 92 | */ 93 | 94 | @Test 95 | public void testNoteOn() throws Exception { 96 | midiProtocolClient.noteOn(NOTE_1_1, COLOR_RED); 97 | 98 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 99 | checkShortMessage(shortMessage.getValue(), ShortMessage.NOTE_ON, NOTE_1_1, COLOR_RED); 100 | } 101 | 102 | /* 103 | ================================================================================ 104 | noteOff 105 | ================================================================================ 106 | */ 107 | 108 | @Test 109 | public void testNoteOff() throws Exception { 110 | midiProtocolClient.noteOff(NOTE_1_1); 111 | 112 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 113 | checkShortMessage(shortMessage.getValue(), ShortMessage.NOTE_OFF, NOTE_1_1, COLOR_BLACK); 114 | } 115 | 116 | /* 117 | ================================================================================ 118 | notesOn 119 | ================================================================================ 120 | */ 121 | 122 | @Test 123 | public void testNotesOn() throws Exception { 124 | midiProtocolClient.notesOn(42, 42, 42, 42); 125 | 126 | verify(receiver, times(2)).send(shortMessage.capture(), eq(-1L)); 127 | checkShortMessage(shortMessage.getValue(), ShortMessage.NOTE_ON, 42, 42); 128 | Assert.assertEquals(3, shortMessage.getValue().getChannel()); 129 | } 130 | 131 | /* 132 | ================================================================================ 133 | layout 134 | ================================================================================ 135 | */ 136 | 137 | @Test 138 | public void testLayout() throws Exception { 139 | midiProtocolClient.layout(LAYOUT_XY); 140 | 141 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 142 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, LAYOUT_XY); 143 | } 144 | 145 | /* 146 | ================================================================================ 147 | buttonOn 148 | ================================================================================ 149 | */ 150 | 151 | @Test 152 | public void testButtonOn() throws Exception { 153 | midiProtocolClient.buttonOn(BUTTON_UP, COLOR_RED); 154 | 155 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 156 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, BUTTON_UP, COLOR_RED); 157 | } 158 | 159 | /* 160 | ================================================================================ 161 | brightness 162 | ================================================================================ 163 | */ 164 | 165 | @Test 166 | public void testBrightness_low() throws Exception { 167 | midiProtocolClient.brightness(1, 3); 168 | 169 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 170 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 30, 0); 171 | } 172 | 173 | @Test 174 | public void testBrightness_high() throws Exception { 175 | midiProtocolClient.brightness(9, 3); 176 | 177 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 178 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 31, 0); 179 | } 180 | 181 | /* 182 | ================================================================================ 183 | text 184 | ================================================================================ 185 | */ 186 | 187 | @Test 188 | public void testText() throws Exception { 189 | midiProtocolClient.text("Hello", COLOR_BLACK, 1, false); 190 | 191 | verify(receiver).send(sysexMessage.capture(), eq(-1L)); 192 | checkSysexMessage(sysexMessage.getValue(), new byte[]{0, 32, 41, 9, 0, 1, 72, 101, 108, 108, 111, (byte) 247}); 193 | } 194 | 195 | @Test 196 | public void testText_loop() throws Exception { 197 | midiProtocolClient.text("Hello", COLOR_BLACK, 1, true); 198 | 199 | verify(receiver).send(sysexMessage.capture(), eq(-1L)); 200 | checkSysexMessage(sysexMessage.getValue(), new byte[]{0, 32, 41, 9, 64, 1, 72, 101, 108, 108, 111, (byte) 247}); 201 | } 202 | 203 | /* 204 | ================================================================================ 205 | doubleBufferMode 206 | ================================================================================ 207 | */ 208 | 209 | @Test 210 | public void testDoubleBufferMode_0_1() throws Exception { 211 | midiProtocolClient.doubleBufferMode(0, 1, false, false); 212 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 213 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, 36); 214 | } 215 | 216 | @Test 217 | public void testDoubleBufferMode_1_0() throws Exception { 218 | midiProtocolClient.doubleBufferMode(1, 0, false, false); 219 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 220 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, 33); 221 | } 222 | 223 | @Test 224 | public void testDoubleBufferMode_COPY() throws Exception { 225 | midiProtocolClient.doubleBufferMode(0, 0, true, false); 226 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 227 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, 32 + 16); 228 | } 229 | 230 | @Test 231 | public void testDoubleBufferMode_LOOP() throws Exception { 232 | midiProtocolClient.doubleBufferMode(0, 0, false, true); 233 | verify(receiver).send(shortMessage.capture(), eq(-1L)); 234 | checkShortMessage(shortMessage.getValue(), ShortMessage.CONTROL_CHANGE, 0, 32 + 8); 235 | } 236 | 237 | /* 238 | ================================================================================ 239 | UTILS 240 | ================================================================================ 241 | */ 242 | 243 | private void checkShortMessage(ShortMessage message, int command, int data1, int data2) { 244 | Assert.assertEquals(command, message.getCommand()); 245 | Assert.assertEquals(data1, message.getData1()); 246 | Assert.assertEquals(data2, message.getData2()); 247 | } 248 | 249 | private void checkSysexMessage(SysexMessage message, byte[] data) { 250 | Assert.assertArrayEquals(data, message.getData()); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /lp4j-midi/src/main/java/net/thecodersbreakfast/lp4j/midi/MidiLaunchpadClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import net.thecodersbreakfast.lp4j.api.*; 20 | import net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolClient; 21 | 22 | import javax.sound.midi.InvalidMidiDataException; 23 | 24 | /** 25 | * A client to communicate with a MIDI Launchpad device. 26 | * 27 | *

This class serves as an adapter between the high-level LP4J API and the low-level MIDI communication layer 28 | * 29 | * @author Olivier Croisier (olivier.croisier@gmail.com) 30 | */ 31 | public class MidiLaunchpadClient implements LaunchpadClient { 32 | 33 | /** Low-level MIDI client to communicate with the Launchpad. */ 34 | private final MidiProtocolClient midiProtocolClient; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param midiProtocolClient The low-level client to adapt. Must not be null. 40 | */ 41 | public MidiLaunchpadClient(MidiProtocolClient midiProtocolClient) { 42 | if (midiProtocolClient == null) { 43 | throw new IllegalArgumentException("MidiClient must not be null."); 44 | } 45 | this.midiProtocolClient = midiProtocolClient; 46 | } 47 | 48 | /* 49 | ================================================================================ 50 | Launchpad API 51 | ================================================================================ 52 | */ 53 | 54 | /** {@inheritDoc} */ 55 | @Override 56 | public void reset() { 57 | try { 58 | midiProtocolClient.reset(); 59 | } catch (InvalidMidiDataException e) { 60 | throw new LaunchpadException(e); 61 | } 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | * 67 | * @param intensity {@inheritDoc} Must not be null. 68 | */ 69 | @Override 70 | public void testLights(LightIntensity intensity) { 71 | if (intensity == null) { 72 | throw new IllegalArgumentException("Light intensity must not be null."); 73 | } 74 | 75 | int value; 76 | switch (intensity) { 77 | case LOW: 78 | value = 125; 79 | break; 80 | case MEDIUM: 81 | value = 126; 82 | break; 83 | case HIGH: 84 | value = 127; 85 | break; 86 | default: 87 | throw new IllegalArgumentException("Unknown intensity value : " + intensity.name()); 88 | } 89 | 90 | try { 91 | midiProtocolClient.lightsOn(value); 92 | } catch (InvalidMidiDataException e) { 93 | throw new LaunchpadException(e); 94 | } 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | * 100 | * @param colors {@inheritDoc} Must be of even size. 101 | * @param operation {@inheritDoc} Must not be null. 102 | */ 103 | @Override 104 | public void setLights(Color[] colors, BackBufferOperation operation) { 105 | if (colors == null) { 106 | throw new IllegalArgumentException("Colors must not be null"); 107 | } 108 | int nbColors = colors.length; 109 | if ((nbColors & 1) != 0) { 110 | throw new IllegalArgumentException("The number of colors for a batch update must be even."); 111 | } 112 | if (operation == null) { 113 | throw new IllegalArgumentException("BackBuffer operation must not be null."); 114 | } 115 | int[] rawColors = new int[nbColors]; 116 | for (int i = 0; i < nbColors; i++) { 117 | rawColors[i] = toRawColor(colors[i], operation); 118 | } 119 | 120 | try { 121 | midiProtocolClient.notesOn(rawColors); 122 | } catch (InvalidMidiDataException e) { 123 | throw new LaunchpadException(e); 124 | } 125 | } 126 | 127 | /** 128 | * {@inheritDoc} 129 | * 130 | * @param pad {@inheritDoc} Must not be null. 131 | * @param color {@inheritDoc} Must not be null. 132 | * @param operation {@inheritDoc} Must not be null. 133 | */ 134 | @Override 135 | public void setPadLight(Pad pad, Color color, BackBufferOperation operation) { 136 | if (pad == null) { 137 | throw new IllegalArgumentException("Pad must not be null."); 138 | } 139 | if (color == null) { 140 | throw new IllegalArgumentException("Color must not be null."); 141 | } 142 | if (operation == null) { 143 | throw new IllegalArgumentException("BackBuffer operation must not be null."); 144 | } 145 | 146 | int rawCoords = toRawCoords(pad.getX(), pad.getY()); 147 | int rawColor = toRawColor(color, operation); 148 | 149 | try { 150 | midiProtocolClient.noteOn(rawCoords, rawColor); 151 | } catch (InvalidMidiDataException e) { 152 | throw new LaunchpadException(e); 153 | } 154 | } 155 | 156 | /** 157 | * {@inheritDoc} 158 | * 159 | * @param button {@inheritDoc} Must not be null. 160 | * @param color {@inheritDoc} Must not be null. 161 | * @param operation {@inheritDoc} Must not be null. 162 | */ 163 | @Override 164 | public void setButtonLight(Button button, Color color, BackBufferOperation operation) { 165 | if (button == null) { 166 | throw new IllegalArgumentException("Button must not be null."); 167 | } 168 | if (color == null) { 169 | throw new IllegalArgumentException("Color must not be null."); 170 | } 171 | if (operation == null) { 172 | throw new IllegalArgumentException("BackBuffer operation must not be null."); 173 | } 174 | 175 | try { 176 | int rawColor = toRawColor(color, operation); 177 | if (button.isTopButton()) { 178 | int rawCoords = 104 + button.getCoordinate(); 179 | midiProtocolClient.buttonOn(rawCoords, rawColor); 180 | } else { 181 | int rawCoords = toRawCoords(8, button.getCoordinate()); 182 | midiProtocolClient.noteOn(rawCoords, rawColor); 183 | } 184 | } catch (InvalidMidiDataException e) { 185 | throw new LaunchpadException(e); 186 | } 187 | } 188 | 189 | /** 190 | * {@inheritDoc} 191 | * 192 | * @param brightness {@inheritDoc} Must not be null. 193 | */ 194 | @Override 195 | public void setBrightness(Brightness brightness) { 196 | if (brightness == null) { 197 | throw new IllegalArgumentException("Brightness must not be null"); 198 | } 199 | 200 | int level = brightness.getBrightness(); 201 | try { 202 | midiProtocolClient.brightness(1, 18 - level); 203 | } catch (InvalidMidiDataException e) { 204 | throw new LaunchpadException(e); 205 | } 206 | } 207 | 208 | /** 209 | * {@inheritDoc} 210 | * 211 | * @param visibleBuffer {@inheritDoc} Must not be null. 212 | * @param writeBuffer {@inheritDoc} Must not be null. 213 | */ 214 | @Override 215 | public void setBuffers(Buffer visibleBuffer, Buffer writeBuffer, boolean copyVisibleBufferToWriteBuffer, boolean autoSwap) { 216 | if (visibleBuffer == null) { 217 | throw new IllegalArgumentException("Visible buffer must not be null."); 218 | } 219 | if (writeBuffer == null) { 220 | throw new IllegalArgumentException("Write buffer must not be null."); 221 | } 222 | 223 | try { 224 | midiProtocolClient.doubleBufferMode(getBufferValue(visibleBuffer), getBufferValue(writeBuffer), copyVisibleBufferToWriteBuffer, autoSwap); 225 | } catch (InvalidMidiDataException e) { 226 | throw new LaunchpadException(e); 227 | } 228 | } 229 | 230 | /** 231 | * Converts an abstract Buffer into its Launchpad-specific low-level representation. 232 | * 233 | * @param buffer The buffer to convert. Must not be null. 234 | * @return The buffer's low-level representation. 235 | */ 236 | private int getBufferValue(Buffer buffer) { 237 | return buffer == Buffer.BUFFER_0 ? 0 : 1; 238 | } 239 | 240 | /** 241 | * {@inheritDoc} 242 | * 243 | * @param color {@inheritDoc} Must not be null. 244 | * @param speed {@inheritDoc} Must not be null. 245 | * @param operation {@inheritDoc} Must not be null. 246 | */ 247 | @Override 248 | public void scrollText(String text, Color color, ScrollSpeed speed, boolean loop, BackBufferOperation operation) { 249 | if (color == null) { 250 | throw new IllegalArgumentException("Color must not be null."); 251 | } 252 | if (speed == null) { 253 | throw new IllegalArgumentException("Speed must not be null."); 254 | } 255 | if (operation == null) { 256 | throw new IllegalArgumentException("Operation must not be null."); 257 | } 258 | 259 | int rawColor = toRawColor(color, operation); 260 | 261 | try { 262 | midiProtocolClient.text(text, rawColor, speed.getScrollSpeed(), loop); 263 | } catch (InvalidMidiDataException e) { 264 | throw new LaunchpadException(e); 265 | } 266 | } 267 | 268 | /* 269 | ================================================================================ 270 | Utils 271 | ================================================================================ 272 | */ 273 | 274 | /** 275 | * Converts a Color into its Launchpad-specific low-level representation 276 | * 277 | * @param color The Color to convert. Must not be null. 278 | * @param operation What to do on the backbuffer. Must not be null. 279 | * @return A binary representation of the color and how it should be applied to the Launchpad's buffers. 280 | */ 281 | private byte toRawColor(Color color, BackBufferOperation operation) { 282 | int flags = 0; 283 | switch (operation) { 284 | case NONE: 285 | flags = 0; 286 | break; 287 | case CLEAR: 288 | flags = 8; 289 | break; 290 | case COPY: 291 | flags = 12; 292 | break; 293 | } 294 | return (byte) (flags + color.getRed() + (16 * color.getGreen())); 295 | } 296 | 297 | /** 298 | * Converts an X-Y coordinates into its Launchpad-specific low-level representation. 299 | * 300 | * @param x The X coordinate. 301 | * @param y The Y corrdinate. 302 | * @return The low-level representation of those coordinates. 303 | */ 304 | private int toRawCoords(int x, int y) { 305 | return x + 16 * y; 306 | } 307 | 308 | } 309 | -------------------------------------------------------------------------------- /LICENCE.TXT: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 Olivier Croisier 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /lp4j-midi/src/test/java/net.thecodersbreakfast.lp4j.midi/MidiLaunchpadClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Olivier Croisier (thecodersbreakfast.net) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.thecodersbreakfast.lp4j.midi; 18 | 19 | import net.thecodersbreakfast.lp4j.api.*; 20 | import net.thecodersbreakfast.lp4j.midi.protocol.MidiProtocolClient; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.mockito.Mock; 25 | import org.mockito.runners.MockitoJUnitRunner; 26 | 27 | import javax.sound.midi.InvalidMidiDataException; 28 | 29 | import static org.mockito.Matchers.anyBoolean; 30 | import static org.mockito.Matchers.anyInt; 31 | import static org.mockito.Matchers.anyString; 32 | import static org.mockito.Mockito.anyVararg; 33 | import static org.mockito.Mockito.*; 34 | 35 | @RunWith(MockitoJUnitRunner.class) 36 | public class MidiLaunchpadClientTest { 37 | 38 | private LaunchpadClient launchpadClient; 39 | 40 | @Mock 41 | private MidiProtocolClient midiProtocolClient; 42 | 43 | @Before 44 | public void init() { 45 | midiProtocolClient = mock(MidiProtocolClient.class); 46 | launchpadClient = new MidiLaunchpadClient(midiProtocolClient); 47 | } 48 | 49 | /* 50 | ================================================================================ 51 | setBrightness 52 | ================================================================================ 53 | */ 54 | 55 | @Test 56 | public void setBrightness() { 57 | launchpadClient.setBrightness(Brightness.BRIGHTNESS_MIN); 58 | } 59 | 60 | @Test(expected = IllegalArgumentException.class) 61 | public void setBrightness_tooLow() { 62 | launchpadClient.setBrightness(Brightness.of(Brightness.MIN_VALUE - 1)); 63 | } 64 | 65 | @Test(expected = IllegalArgumentException.class) 66 | public void setBrightness_tooHigh() { 67 | launchpadClient.setBrightness(Brightness.of(Brightness.MAX_VALUE + 1)); 68 | } 69 | 70 | @Test(expected = LaunchpadException.class) 71 | public void setBrightness_exception() throws InvalidMidiDataException { 72 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).brightness(anyInt(), anyInt()); 73 | launchpadClient.setBrightness(Brightness.BRIGHTNESS_MIN); 74 | } 75 | 76 | /* 77 | ================================================================================ 78 | setBuffers 79 | ================================================================================ 80 | */ 81 | 82 | @Test 83 | public void setBuffers() throws InvalidMidiDataException { 84 | launchpadClient.setBuffers(Buffer.BUFFER_0, Buffer.BUFFER_1, false, false); 85 | verify(midiProtocolClient).doubleBufferMode(0, 1, false, false); 86 | } 87 | 88 | @Test(expected = LaunchpadException.class) 89 | public void setBuffers_exception() throws InvalidMidiDataException { 90 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).doubleBufferMode(anyInt(), anyInt(), anyBoolean(), anyBoolean()); 91 | launchpadClient.setBuffers(Buffer.BUFFER_0, Buffer.BUFFER_1, false, false); 92 | } 93 | 94 | /* 95 | ================================================================================ 96 | setButtonLight 97 | ================================================================================ 98 | */ 99 | 100 | @Test(expected = LaunchpadException.class) 101 | public void setButtonLight_exception() throws InvalidMidiDataException { 102 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).buttonOn(anyInt(), anyInt()); 103 | launchpadClient.setButtonLight(Button.UP, Color.BLACK, BackBufferOperation.COPY); 104 | } 105 | 106 | @Test 107 | public void setButtonLight_topButton_NONE() throws InvalidMidiDataException { 108 | launchpadClient.setButtonLight(Button.UP, Color.BLACK, BackBufferOperation.NONE); 109 | verify(midiProtocolClient).buttonOn(104, 0); 110 | } 111 | 112 | @Test 113 | public void setButtonLight_topButton_CLEAR() throws InvalidMidiDataException { 114 | launchpadClient.setButtonLight(Button.UP, Color.BLACK, BackBufferOperation.CLEAR); 115 | verify(midiProtocolClient).buttonOn(104, 8); 116 | } 117 | 118 | @Test 119 | public void setButtonLight_topButton_COPY() throws InvalidMidiDataException { 120 | launchpadClient.setButtonLight(Button.UP, Color.BLACK, BackBufferOperation.COPY); 121 | verify(midiProtocolClient).buttonOn(104, 12); 122 | } 123 | 124 | @Test 125 | public void setButtonLight_rightButton() throws InvalidMidiDataException { 126 | launchpadClient.setButtonLight(Button.VOL, Color.BLACK, BackBufferOperation.COPY); 127 | verify(midiProtocolClient).noteOn(8, 12); 128 | } 129 | 130 | @Test 131 | public void setButtonLight_rightButton_COPY() throws InvalidMidiDataException { 132 | launchpadClient.setButtonLight(Button.VOL, Color.BLACK, BackBufferOperation.COPY); 133 | verify(midiProtocolClient).noteOn(8, 12); 134 | } 135 | 136 | @Test(expected = IllegalArgumentException.class) 137 | public void setButtonLight_XY_illegal_COPY() { 138 | launchpadClient.setButtonLight(Button.atTop(-1), Color.BLACK, BackBufferOperation.COPY); 139 | } 140 | 141 | @Test 142 | public void setButtonLight_topButton_XY_COPY() throws InvalidMidiDataException { 143 | launchpadClient.setButtonLight(Button.atTop(0), Color.BLACK, BackBufferOperation.COPY); // UP 144 | verify(midiProtocolClient).buttonOn(104, 12); 145 | } 146 | 147 | @Test 148 | public void setButtonLight_rightButton_XY_COPY() throws InvalidMidiDataException { 149 | launchpadClient.setButtonLight(Button.atRight(0), Color.BLACK, BackBufferOperation.COPY); // VOL 150 | verify(midiProtocolClient).noteOn(8, 12); 151 | } 152 | 153 | /* 154 | ================================================================================ 155 | setPadLight 156 | ================================================================================ 157 | */ 158 | 159 | @Test 160 | public void setPadLight_COPY() throws InvalidMidiDataException { 161 | launchpadClient.setPadLight(Pad.at(0, 0), Color.BLACK, BackBufferOperation.COPY); 162 | verify(midiProtocolClient).noteOn(0, 12); 163 | } 164 | 165 | @Test(expected = IllegalArgumentException.class) 166 | public void setPadLight_illegal() { 167 | launchpadClient.setPadLight(Pad.at(-1, -1), Color.BLACK, BackBufferOperation.COPY); 168 | } 169 | 170 | @Test(expected = LaunchpadException.class) 171 | public void setPadLight_exception() throws InvalidMidiDataException { 172 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).noteOn(anyInt(), anyInt()); 173 | launchpadClient.setPadLight(Pad.at(1, 1), Color.BLACK, BackBufferOperation.COPY); 174 | } 175 | 176 | /* 177 | ================================================================================ 178 | reset 179 | ================================================================================ 180 | */ 181 | 182 | @Test 183 | public void reset() throws InvalidMidiDataException { 184 | launchpadClient.reset(); 185 | verify(midiProtocolClient).reset(); 186 | } 187 | 188 | @Test(expected = LaunchpadException.class) 189 | public void reset_exception() throws InvalidMidiDataException { 190 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).reset(); 191 | launchpadClient.reset(); 192 | verify(midiProtocolClient).reset(); 193 | } 194 | 195 | /* 196 | ================================================================================ 197 | testLights 198 | ================================================================================ 199 | */ 200 | 201 | @Test 202 | public void testLights() throws InvalidMidiDataException { 203 | launchpadClient.testLights(LightIntensity.LOW); 204 | verify(midiProtocolClient).lightsOn(125); 205 | launchpadClient.testLights(LightIntensity.MEDIUM); 206 | verify(midiProtocolClient).lightsOn(126); 207 | launchpadClient.testLights(LightIntensity.HIGH); 208 | verify(midiProtocolClient).lightsOn(127); 209 | } 210 | 211 | @Test(expected = LaunchpadException.class) 212 | public void testLights_exception() throws InvalidMidiDataException { 213 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).lightsOn(anyInt()); 214 | launchpadClient.testLights(LightIntensity.LOW); 215 | } 216 | 217 | /* 218 | ================================================================================ 219 | setLights 220 | ================================================================================ 221 | */ 222 | 223 | @Test(expected = IllegalArgumentException.class) 224 | public void setLights_null() { 225 | launchpadClient.setLights(null, BackBufferOperation.NONE); 226 | } 227 | 228 | @Test(expected = IllegalArgumentException.class) 229 | public void setLights_odd() { 230 | launchpadClient.setLights(new Color[1], BackBufferOperation.NONE); 231 | } 232 | 233 | @Test(expected = LaunchpadException.class) 234 | public void setLights_exception() throws InvalidMidiDataException { 235 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).notesOn((int[]) anyVararg()); 236 | launchpadClient.setLights(new Color[]{Color.BLACK, Color.BLACK}, BackBufferOperation.COPY); 237 | } 238 | 239 | @Test 240 | public void setLights_COPY() throws InvalidMidiDataException { 241 | launchpadClient.setLights(new Color[]{Color.BLACK, Color.BLACK}, BackBufferOperation.COPY); 242 | verify(midiProtocolClient).notesOn(12, 12); 243 | } 244 | 245 | /* 246 | ================================================================================ 247 | scrollText 248 | ================================================================================ 249 | */ 250 | 251 | @Test(expected = IllegalArgumentException.class) 252 | public void scrollText_speedTooLow() { 253 | launchpadClient.scrollText("Hello", Color.BLACK, ScrollSpeed.of(ScrollSpeed.MIN_VALUE - 1), false, BackBufferOperation.COPY); 254 | } 255 | 256 | @Test(expected = IllegalArgumentException.class) 257 | public void scrollText_speedTooHIgh() { 258 | launchpadClient.scrollText("Hello", Color.BLACK, ScrollSpeed.of(ScrollSpeed.MAX_VALUE + 1), false, BackBufferOperation.COPY); 259 | } 260 | 261 | @Test(expected = IllegalArgumentException.class) 262 | public void scrollText_nullColor() { 263 | launchpadClient.scrollText("Hello", null, ScrollSpeed.SPEED_MIN, false, BackBufferOperation.COPY); 264 | } 265 | 266 | @Test 267 | public void scrollText_COPY() throws InvalidMidiDataException { 268 | launchpadClient.scrollText("Hello", Color.BLACK, ScrollSpeed.SPEED_MIN, false, BackBufferOperation.COPY); 269 | verify(midiProtocolClient).text("Hello", 12, 1, false); 270 | } 271 | 272 | @Test(expected = LaunchpadException.class) 273 | public void scrollText_exception() throws InvalidMidiDataException { 274 | doThrow(new InvalidMidiDataException()).when(midiProtocolClient).text(anyString(), anyInt(), anyInt(), anyBoolean()); 275 | launchpadClient.scrollText("Hello", Color.BLACK, ScrollSpeed.SPEED_MIN, false, BackBufferOperation.COPY); 276 | } 277 | 278 | 279 | } 280 | -------------------------------------------------------------------------------- /lp4j-emu-web/src/main/resources/web/sockjs-0.3.min.js: -------------------------------------------------------------------------------- 1 | /* SockJS client, version 0.3.4, http://sockjs.org, MIT License 2 | 3 | Copyright (c) 2011-2012 VMware, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | // JSON2 by Douglas Crockford (minified). 25 | var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c1?this._listeners[a]=d.slice(0,e).concat(d.slice(e+1)):delete this._listeners[a];return}return},d.prototype.dispatchEvent=function(a){var b=a.type,c=Array.prototype.slice.call(arguments,0);this["on"+b]&&this["on"+b].apply(this,c);if(this._listeners&&b in this._listeners)for(var d=0;d=3e3&&a<=4999},c.countRTO=function(a){var b;return a>100?b=3*a:b=a+200,b},c.log=function(){b.console&&console.log&&console.log.apply&&console.log.apply(console,arguments)},c.bind=function(a,b){return a.bind?a.bind(b):function(){return a.apply(b,arguments)}},c.flatUrl=function(a){return a.indexOf("?")===-1&&a.indexOf("#")===-1},c.amendUrl=function(b){var d=a.location;if(!b)throw new Error("Wrong url for SockJS");if(!c.flatUrl(b))throw new Error("Only basic urls are supported in SockJS");return b.indexOf("//")===0&&(b=d.protocol+b),b.indexOf("/")===0&&(b=d.protocol+"//"+d.host+b),b=b.replace(/[/]+$/,""),b},c.arrIndexOf=function(a,b){for(var c=0;c=0},c.delay=function(a,b){return typeof a=="function"&&(b=a,a=0),setTimeout(b,a)};var i=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,j={"\0":"\\u0000","\x01":"\\u0001","\x02":"\\u0002","\x03":"\\u0003","\x04":"\\u0004","\x05":"\\u0005","\x06":"\\u0006","\x07":"\\u0007","\b":"\\b","\t":"\\t","\n":"\\n","\x0b":"\\u000b","\f":"\\f","\r":"\\r","\x0e":"\\u000e","\x0f":"\\u000f","\x10":"\\u0010","\x11":"\\u0011","\x12":"\\u0012","\x13":"\\u0013","\x14":"\\u0014","\x15":"\\u0015","\x16":"\\u0016","\x17":"\\u0017","\x18":"\\u0018","\x19":"\\u0019","\x1a":"\\u001a","\x1b":"\\u001b","\x1c":"\\u001c","\x1d":"\\u001d","\x1e":"\\u001e","\x1f":"\\u001f",'"':'\\"',"\\":"\\\\","\x7f":"\\u007f","\x80":"\\u0080","\x81":"\\u0081","\x82":"\\u0082","\x83":"\\u0083","\x84":"\\u0084","\x85":"\\u0085","\x86":"\\u0086","\x87":"\\u0087","\x88":"\\u0088","\x89":"\\u0089","\x8a":"\\u008a","\x8b":"\\u008b","\x8c":"\\u008c","\x8d":"\\u008d","\x8e":"\\u008e","\x8f":"\\u008f","\x90":"\\u0090","\x91":"\\u0091","\x92":"\\u0092","\x93":"\\u0093","\x94":"\\u0094","\x95":"\\u0095","\x96":"\\u0096","\x97":"\\u0097","\x98":"\\u0098","\x99":"\\u0099","\x9a":"\\u009a","\x9b":"\\u009b","\x9c":"\\u009c","\x9d":"\\u009d","\x9e":"\\u009e","\x9f":"\\u009f","\xad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601","\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f","\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d","\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029","\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d","\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061","\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065","\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069","\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d","\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0","\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4","\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8","\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc","\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"},k=/[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g,l,m=JSON&&JSON.stringify||function(a){return i.lastIndex=0,i.test(a)&&(a=a.replace(i,function(a){return j[a]})),'"'+a+'"'},n=function(a){var b,c={},d=[];for(b=0;b<65536;b++)d.push(String.fromCharCode(b));return a.lastIndex=0,d.join("").replace(a,function(a){return c[a]="\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4),""}),a.lastIndex=0,c};c.quote=function(a){var b=m(a);return k.lastIndex=0,k.test(b)?(l||(l=n(k)),b.replace(k,function(a){return l[a]})):b};var o=["websocket","xdr-streaming","xhr-streaming","iframe-eventsource","iframe-htmlfile","xdr-polling","xhr-polling","iframe-xhr-polling","jsonp-polling"];c.probeProtocols=function(){var a={};for(var b=0;b0&&h(a)};return c.websocket!==!1&&h(["websocket"]),d["xhr-streaming"]&&!c.null_origin?e.push("xhr-streaming"):d["xdr-streaming"]&&!c.cookie_needed&&!c.null_origin?e.push("xdr-streaming"):h(["iframe-eventsource","iframe-htmlfile"]),d["xhr-polling"]&&!c.null_origin?e.push("xhr-polling"):d["xdr-polling"]&&!c.cookie_needed&&!c.null_origin?e.push("xdr-polling"):h(["iframe-xhr-polling","jsonp-polling"]),e};var p="_sockjs_global";c.createHook=function(){var a="a"+c.random_string(8);if(!(p in b)){var d={};b[p]=function(a){return a in d||(d[a]={id:a,del:function(){delete d[a]}}),d[a]}}return b[p](a)},c.attachMessage=function(a){c.attachEvent("message",a)},c.attachEvent=function(c,d){typeof b.addEventListener!="undefined"?b.addEventListener(c,d,!1):(a.attachEvent("on"+c,d),b.attachEvent("on"+c,d))},c.detachMessage=function(a){c.detachEvent("message",a)},c.detachEvent=function(c,d){typeof b.addEventListener!="undefined"?b.removeEventListener(c,d,!1):(a.detachEvent("on"+c,d),b.detachEvent("on"+c,d))};var q={},r=!1,s=function(){for(var a in q)q[a](),delete q[a]},t=function(){if(r)return;r=!0,s()};c.attachEvent("unload",t),c.unload_add=function(a){var b=c.random_string(8);return q[b]=a,r&&c.delay(s),b},c.unload_del=function(a){a in q&&delete q[a]},c.createIframe=function(b,d){var e=a.createElement("iframe"),f,g,h=function(){clearTimeout(f);try{e.onload=null}catch(a){}e.onerror=null},i=function(){e&&(h(),setTimeout(function(){e&&e.parentNode.removeChild(e),e=null},0),c.unload_del(g))},j=function(a){e&&(i(),d(a))},k=function(a,b){try{e&&e.contentWindow&&e.contentWindow.postMessage(a,b)}catch(c){}};return e.src=b,e.style.display="none",e.style.position="absolute",e.onerror=function(){j("onerror")},e.onload=function(){clearTimeout(f),f=setTimeout(function(){j("onload timeout")},2e3)},a.body.appendChild(e),f=setTimeout(function(){j("timeout")},15e3),g=c.unload_add(i),{post:k,cleanup:i,loaded:h}},c.createHtmlfile=function(a,d){var e=new ActiveXObject("htmlfile"),f,g,i,j=function(){clearTimeout(f)},k=function(){e&&(j(),c.unload_del(g),i.parentNode.removeChild(i),i=e=null,CollectGarbage())},l=function(a){e&&(k(),d(a))},m=function(a,b){try{i&&i.contentWindow&&i.contentWindow.postMessage(a,b)}catch(c){}};e.open(),e.write('