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
17 changes: 13 additions & 4 deletions vst/source/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ tresult PLUGIN_API Controller::initialize(FUnknown* context) {
IPlugView* PLUGIN_API Controller::createView(FIDString name) {
if (strcmp(name, ViewType::kEditor) == 0) {
auto* view = new PlugView(this);
activeView = view;
setActiveView(view);
return view;
}
return nullptr;
Expand All @@ -107,12 +107,21 @@ tresult PLUGIN_API Controller::notify(IMessage* message) {
return kInvalidArgument;

if (strcmp(message->getMessageID(), "MidiActivity") == 0) {
if (activeView) {
PlugView* view = nullptr;
{
std::lock_guard<std::mutex> lock(activeViewMutex);
view = activeView;
if (view)
view->addRef();
}

if (view) {
int64 val = 0;
if (message->getAttributes()->getInt("input", val) == kResultOk && val)
activeView->flashMidiInput();
view->flashMidiInput();
if (message->getAttributes()->getInt("output", val) == kResultOk && val)
activeView->flashMidiOutput();
view->flashMidiOutput();
view->release();
}
return kResultOk;
}
Expand Down
8 changes: 7 additions & 1 deletion vst/source/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "public.sdk/source/vst/vsteditcontroller.h"

#include <mutex>

namespace tram8 {

class PlugView;
Expand All @@ -18,7 +20,10 @@ class Controller : public Steinberg::Vst::EditController, public Steinberg::Vst:
Steinberg::tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override;
Steinberg::tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override;

void setActiveView(PlugView* view) { activeView = view; }
void setActiveView(PlugView* view) {
std::lock_guard<std::mutex> lock(activeViewMutex);
activeView = view;
}

Steinberg::tresult PLUGIN_API getMidiControllerAssignment(Steinberg::int32 busIndex,
Steinberg::int16 channel,
Expand All @@ -32,6 +37,7 @@ class Controller : public Steinberg::Vst::EditController, public Steinberg::Vst:
REFCOUNT_METHODS(EditController)

private:
std::mutex activeViewMutex;
PlugView* activeView = nullptr;
};

Expand Down
67 changes: 47 additions & 20 deletions vst/source/plugview.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,33 @@ @interface Tram8WebBridge : NSObject <WKScriptMessageHandler>
@property(assign) tram8::PlugView* plugView;
@end

static void EvaluateNativeEvent(WKWebView* webView, NSDictionary* event) {
if (!webView || ![NSJSONSerialization isValidJSONObject:event])
return;

NSData* json = [NSJSONSerialization dataWithJSONObject:event options:0 error:nil];
if (!json)
return;

NSString* jsonStr = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
if (!jsonStr)
return;

NSString* js = [NSString stringWithFormat:@"typeof tram8 !== 'undefined' && tram8.handleNativeEvent(%@)", jsonStr];
[webView evaluateJavaScript:js completionHandler:nil];
[jsonStr release];
}

@implementation Tram8WebBridge

- (void)userContentController:(WKUserContentController*)uc didReceiveScriptMessage:(WKScriptMessage*)message {
if (![message.body isKindOfClass:[NSDictionary class]])
return;

NSDictionary* body = message.body;
NSString* type = body[@"type"];
if (![type isKindOfClass:[NSString class]])
return;

if ([type isEqualToString:@"ready"]) {
[self pushMidiPorts];
Expand Down Expand Up @@ -134,14 +156,11 @@ - (void)pushMidiPorts {
[ports addObject:[NSString stringWithFormat:@"Port %lu", i]];
}
}
NSData* json = [NSJSONSerialization dataWithJSONObject:ports options:0 error:nil];
NSString* jsonStr = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
NSString* js = [NSString stringWithFormat:@"tram8.setMidiPorts(%@)", jsonStr];
[_webView evaluateJavaScript:js completionHandler:nil];
[jsonStr release];
EvaluateNativeEvent(_webView, @{@"type" : @"midiPorts", @"ports" : ports});
}

- (void)pushState {
NSMutableArray* gates = [NSMutableArray arrayWithCapacity:8];
for (int i = 0; i < 8; i++) {
double chNorm = _controller->getParamNormalized(tram8::kGateChannelBase + i);
int chStep = (int)(chNorm * 16 + 0.5);
Expand All @@ -161,10 +180,16 @@ - (void)pushState {
double ccNorm = _controller->getParamNormalized(tram8::kCcNumBase + i);
int ccN = (int)(ccNorm * 127 + 0.5);

NSString* js =
[NSString stringWithFormat:@"tram8.setGateState(%d, %d, %d, %d, %d, %d)", i, channel, note, mode, dacCh, ccN];
[_webView evaluateJavaScript:js completionHandler:nil];
[gates addObject:@{
@"gate" : @(i),
@"channel" : @(channel),
@"note" : @(note),
@"mode" : @(mode),
@"dacChannel" : @(dacCh),
@"ccNum" : @(ccN)
}];
}
EvaluateNativeEvent(_webView, @{@"type" : @"state", @"gates" : gates});
}

@end
Expand Down Expand Up @@ -314,21 +339,23 @@ - (void)pushState {
}

void PlugView::flashMidiInput() {
WKWebView* wv = webView;
if (wv) {
dispatch_async(dispatch_get_main_queue(), ^{
[wv evaluateJavaScript:@"tram8.flashInput()" completionHandler:nil];
});
}
PlugView* self = this;
self->addRef();
dispatch_async(dispatch_get_main_queue(), ^{
if (WKWebView* wv = self->webView)
EvaluateNativeEvent(wv, @{@"type" : @"activity", @"input" : @(YES)});
self->release();
});
}

void PlugView::flashMidiOutput() {
WKWebView* wv = webView;
if (wv) {
dispatch_async(dispatch_get_main_queue(), ^{
[wv evaluateJavaScript:@"tram8.flashOutput()" completionHandler:nil];
});
}
PlugView* self = this;
self->addRef();
dispatch_async(dispatch_get_main_queue(), ^{
if (WKWebView* wv = self->webView)
EvaluateNativeEvent(wv, @{@"type" : @"activity", @"output" : @(YES)});
self->release();
});
}

} // namespace tram8
44 changes: 38 additions & 6 deletions vst/source/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,44 @@
}
},

setGateState(gate, ch, note, mode, dacCh, ccN) {
Object.assign(this.gates[gate], {channel:ch, note, mode,
dacChannel: dacCh !== undefined ? dacCh : this.gates[gate].dacChannel,
ccNum: ccN !== undefined ? ccN : this.gates[gate].ccNum});
this.renderGates();
this.renderPanel();
applyGateState(gate, state) {
if (!Number.isInteger(gate) || gate < 0 || gate >= this.gates.length) return;
const current = this.gates[gate];
const intIn = (value, fallback, min, max) =>
Number.isInteger(value) && value >= min && value <= max ? value : fallback;
Object.assign(this.gates[gate], {
channel: intIn(state.channel, current.channel, -1, 15),
note: intIn(state.note, current.note, -1, 127),
mode: intIn(state.mode, current.mode, 0, MODES.length - 1),
dacChannel: intIn(state.dacChannel, current.dacChannel, -1, 15),
ccNum: intIn(state.ccNum, current.ccNum, 0, 127)
});
},

handleNativeEvent(event) {
if (!event || typeof event.type !== 'string') return;

if (event.type === 'midiPorts') {
this.setMidiPorts(Array.isArray(event.ports) ? event.ports : []);
return;
}

if (event.type === 'state') {
if (Array.isArray(event.gates)) {
event.gates.forEach(g => {
if (!g) return;
this.applyGateState(g.gate, g);
});
this.renderGates();
this.renderPanel();
}
return;
}

if (event.type === 'activity') {
if (event.input) this.flashInput();
if (event.output) this.flashOutput();
}
},

post(msg) {
Expand Down
Loading