Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,22 @@ public String getHardwareModel() {
public void listAutoDetectionMidiPortNames(final AutoDetectionMidiPortNamesList list,
final PlatformType platformType) {
if (platformType == PlatformType.WINDOWS) {
list.add(new String[] {PORT_NAME_MIDI}, new String[] {PORT_NAME_MIDI});
list.add(new String[] {PORT_NAME}, new String[] {PORT_NAME});
appendRenamedPorts(4, list);
for (int i = 1; i < 5; i++) {
appendWinPrefix(list, i);
}
} else if (platformType == PlatformType.MAC) {
list.add(new String[] {PORT_NAME_MIDI}, new String[] {PORT_NAME_MIDI});
} else if (platformType == PlatformType.LINUX) {
list.add(new String[] {PORT_NAME_LINUX}, new String[] {PORT_NAME_LINUX});
}
}

private void appendRenamedPorts(final int count, final AutoDetectionMidiPortNamesList list) {
for (int i = 2; i < count + 2; i++) {
list.add(getRenamedPorts(i), getRenamedPorts(i));
}
}

private String[] getRenamedPorts(final int index) {
return new String[] {"%d- %s".formatted(index, PORT_NAME_MIDI)};
private void appendWinPrefix(final AutoDetectionMidiPortNamesList list, final int index) {
final String prefix = index > 1 ? "%d- ".formatted(index) : "";
list.add(
new String[] {"%s%s MIDI".formatted(prefix, PORT_NAME)},
new String[] {"%s%s MIDI".formatted(prefix, PORT_NAME)});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.bitwig.extensions.controllers.softube.console1;

@FunctionalInterface
public interface ConnectionListener {
void isConnected(boolean connected);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.bitwig.extensions.controllers.softube.console1;

import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;

import com.bitwig.extension.controller.ControllerExtension;
import com.bitwig.extension.controller.api.Application;
import com.bitwig.extension.controller.api.ControllerHost;
import com.bitwig.extension.controller.api.HardwareSurface;
import com.bitwig.extension.controller.api.Transport;
import com.bitwig.extensions.controllers.softube.console1.definition.Console1ExtensionDefinition;
import com.bitwig.extensions.framework.di.Context;

public class Console1Extension extends ControllerExtension {
private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern("hh:mm:ss SSS");

private HardwareSurface surface;
private final Console1ExtensionDefinition definition;
private ViewControl viewControl;
private TrackControl trackSlotControl;
private ConsoleMidiProcessor midiProcessor;

// public static void println(final String format, final Object... args) {
// if (debugHost != null) {
// final LocalDateTime now = LocalDateTime.now();
// debugHost.println(now.format(DF) + " > " + String.format(format, args));
// }
// }

public Console1Extension(final Console1ExtensionDefinition definition, final ControllerHost host) {
super(definition, host);
this.definition = definition;
}

@Override
public void init() {
final Context diContext = new Context(this);
midiProcessor = new ConsoleMidiProcessor(getHost(), definition.getNumMidiInPorts(), definition.getName());

surface = diContext.getService(HardwareSurface.class);
final Application application = diContext.getService(Application.class);

viewControl = diContext.getService(ViewControl.class);

trackSlotControl = new TrackControl(viewControl.getTrackBank(), getHost(), midiProcessor,
diContext.getService(Transport.class));
midiProcessor.setTrackSlotControl(trackSlotControl);
application.hasActiveEngine().addValueObserver(active -> midiProcessor.handleEngineSwitch(active));

midiProcessor.startHandshake();
}

@Override
public void exit() {
final CompletableFuture<Boolean> shutdown = new CompletableFuture<>();
Executors.newSingleThreadExecutor().execute(() -> {
trackSlotControl.resetAll();
midiProcessor.invokeReset();
try {
Thread.sleep(100);
}
catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
shutdown.complete(true);
});
try {
shutdown.get();
}
catch (final InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
}
}

@Override
public void flush() {
surface.updateHardware();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package com.bitwig.extensions.controllers.softube.console1;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import com.bitwig.extension.controller.api.ControllerHost;
import com.bitwig.extension.controller.api.MidiIn;
import com.bitwig.extension.controller.api.MidiOut;

public class ConsoleMidiProcessor {
private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern("hh:mm:ss SSS");

private MidiOut midiOut = null;
private final ControllerHost host;
private boolean connectionInit = false;

private TrackControl trackSlotControl;
private final int portCount;
private final List<ConnectionListener> connectionListeners = new ArrayList<>();

private boolean engineActiveState = false;
private final String name;

public ConsoleMidiProcessor(final ControllerHost host, final int ports, final String name) {
this.host = host;
this.name = name;
this.portCount = ports;
for (int i = 0; i < ports; i++) {
final int portIndex = i;
final MidiIn midiIn = host.getMidiInPort(i);
midiIn.setSysexCallback(data -> handleSysEx(portIndex, data));
}
}

private void println(final String format, final Object... args) {
host.println(" " + LocalDateTime.now().format(DF) + " " + format.formatted(args));
}

public void startHandshake() {
for (int port = 0; port < portCount; port++) {
println(" ######### Init Handshake %d ###### ", port);
host.getMidiOutPort(port).sendSysex(SysexUtil.toJsonSysEx(SysexUtil.INIT_HANDSHAKE));
}
}

public void addConnectionListener(final ConnectionListener listener) {
this.connectionListeners.add(listener);
}

public void invokeReset() {
// println(" Daw-> Console : Reset");
for (int i = 0; i < portCount; i++) {
host.getMidiOutPort(i).sendSysex(SysexUtil.toJsonSysEx(SysexUtil.RESET_CMD));
}
}

public void setTrackSlotControl(final TrackControl trackSlotControl) {
this.trackSlotControl = trackSlotControl;
}

public void sendJsonSysEx(final String jsonString) {
if (midiOut == null) {
return;
}
midiOut.sendSysex(SysexUtil.toJsonSysEx(jsonString));
}

private void handleSysEx(final int port, final String data) {
final String json = SysexUtil.toJson(data);
if (json != null) {
handleJson(port, json);
} else {
println("NON JSON : \n" + data);
}
}

private void handleJson(final int port, final String json) {
final JsonParser parser = new JsonParser(json);
final JsonObject obj = parser.parse();
//host.println(" JSON " + json);
if (obj.contains("trackId")) {
trackSlotControl.update(obj);
} else if (obj.contains("handshake")) {
final boolean acknowledge = obj.getJsonObject("handshake").getBool("ack");
handleHandshake(port, acknowledge);
} else if (obj.contains("cmd")) {
final String cmdValue = obj.getString("cmd");
if ("RESET".equals(cmdValue)) {
handleResetReceived();
} else if ("ENABLE".equals(cmdValue)) {
println(" ENABLE <%s>", name);
} else if ("DISABLE".equals(cmdValue)) {
println(" DISABLE <%s>", name);
connectionListeners.forEach(listener -> listener.isConnected(false));
} else {
println(" Unknown COMMAND %s", cmdValue);
}
} else if (obj.contains("activeMeters")) {
trackSlotControl.meterActivate(obj.getStringList("activeMeters"));
} else {
host.println("OTHER JSON : \n" + json);
}
}

private void handleResetReceived() {
println(" RESET received INIT=%s", connectionInit);
if (!connectionInit) {
trackSlotControl.setBlockMidi(false);
startHandshake();
} else {
trackSlotControl.updateAllTracks();
}
}

private void handleHandshake(final int port, final boolean acknowledged) {
println(
" C1 => Handshake Acknowledge=%s con_init=%s> send reset => PORT=%d ", acknowledged, connectionInit, port);
if (acknowledged) {
connectToDevice(port);
} else {
if (connectionInit) {
println(" Let us ignore this ");
} else {
connectionInit = false;
host.getMidiOutPort(port).sendSysex(SysexUtil.toJsonSysEx(SysexUtil.RESET_CMD));
connectionListeners.forEach(listener -> listener.isConnected(false));
}
}
}

private void connectToDevice(final int port) {
// println(" ####### Connect to Device %d ########### ", port);
midiOut = host.getMidiOutPort(port);

host.scheduleTask(this::launchTrackUpdate, 500);
if (!connectionInit) {
connectionInit = true;
connectionListeners.forEach(listener -> listener.isConnected(true));
}

}

private void launchTrackUpdate() {
trackSlotControl.setBlockMidi(false);
trackSlotControl.updateAllTracks();
}

public void handleEngineSwitch(final boolean engineActive) {
if (engineActiveState == engineActive) {
return;
}
this.engineActiveState = engineActive;
if (!connectionInit) {
return;
}
// println(" Engine Active %s init = %s", engineActive, connectionInit);
if (connectionInit && engineActive) {
trackSlotControl.resetAll();
invokeReset();
trackSlotControl.updateAllTracks();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.bitwig.extensions.controllers.softube.console1;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class JsonObject {
public static final Pattern DIGIT = Pattern.compile("^-?\\d+$");
public static final Pattern FLOAT = Pattern.compile("^[-+]?[0-9]*\\.?[0-9]*$");
private final Map<String, Object> data = new HashMap<>();

public Object getValue(final String key) {
return data.get(key);
}

public Optional<String> getStringValue(final String key) {
if (data.get(key) instanceof final String stringValue) {
return Optional.of(stringValue);
}
return Optional.empty();
}

public String getString(final String key) {
if (data.get(key) instanceof final String stringValue) {
return stringValue;
}
return null;
}

public boolean getBool(final String key) {
if (data.get(key) instanceof final Boolean boolValue) {
return boolValue;
}
return false;
}

public List<String> getStringList(final String key) {
if (data.get(key) instanceof final List list) {
return list;
}
return List.of();
}

public void set(final String key, final Object value) {
if (value instanceof final String strValue) {
if (strValue.startsWith("\"") && strValue.endsWith("\"")) {
data.put(key, strValue.substring(1, strValue.length() - 1));
} else if ("true".equals(strValue)) {
data.put(key, Boolean.TRUE);
} else if ("false".equals(strValue)) {
data.put(key, Boolean.FALSE);
} else if (DIGIT.matcher(strValue).matches()) { // strValue.matches("-?\\d+")
data.put(key, Integer.parseInt(strValue));
} else if (FLOAT.matcher(strValue).matches()) {
data.put(key, Double.parseDouble(strValue));
} else {
data.put(key, strValue);
}
} else {
data.put(key, value);
}
}

public JsonObject getJsonObject(final String key) {
final Object value = data.get(key);
if (value instanceof final JsonObject jsonObject) {
return jsonObject;
}
return null;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (final var entry : data.entrySet()) {
sb.append(entry.getKey() + " = <" + entry.getValue() + ">").append("\n");
}
return sb.toString();
}

public boolean contains(final String key) {
return data.containsKey(key);
}

public Stream<Map.Entry<String, Object>> stream() {
return data.entrySet().stream();
}
}

Loading
Loading