mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-17 08:06:50 -04:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9dc19fb9bf | |||
| 4a158db9d2 | |||
| 55839e7ee8 | |||
| 9aaa9bee6b | |||
| 1834d689ef | |||
| 77a7a148f0 | |||
| 52931e879d | |||
| d5c9be1c30 | |||
| 2b30ab3008 | |||
| 13cbeb3f67 | |||
| 352d8659ec |
@@ -72,6 +72,15 @@ export function registerControl(names, ...aliases) {
|
||||
return bag;
|
||||
}
|
||||
|
||||
// Constructs a superdirt effect bus control
|
||||
const bus = register('bus', function (busid, control_name, busval, pat) {
|
||||
return pat[control_name]('c' + busid).withValue((val) => {
|
||||
const newval = { ...val };
|
||||
newval['^' + control_name] = [busid, busval];
|
||||
return newval;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
||||
* separated by ':'.
|
||||
|
||||
@@ -58,11 +58,10 @@ export class Cyclist {
|
||||
|
||||
// query the pattern for events
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' });
|
||||
|
||||
haps.forEach((hap) => {
|
||||
if (hap.hasOnset()) {
|
||||
if (hap.hasOnset() || hap.context.processParts) {
|
||||
const targetTime =
|
||||
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
|
||||
(hap.part.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
|
||||
const duration = hap.duration / this.cps;
|
||||
// the following line is dumb and only here for backwards compatibility
|
||||
// see https://codeberg.org/uzu/strudel/pulls/1004
|
||||
|
||||
@@ -16,7 +16,7 @@ export class Hap {
|
||||
then the whole will be returned as None, in which case the given
|
||||
value will have been sampled from the point halfway between the
|
||||
start and end of the 'part' timespan.
|
||||
The context is to store a list of source code locations causing the event.
|
||||
The context is to store metadata, including a list of source code locations causing the event.
|
||||
|
||||
The word 'Event' is more or less a reserved word in javascript, hence this
|
||||
class is named called 'Hap'.
|
||||
@@ -161,6 +161,10 @@ export class Hap {
|
||||
return new Hap(this.whole, this.part, this.value, context);
|
||||
}
|
||||
|
||||
withContext(f) {
|
||||
return new Hap(this.whole, this.part, this.value, f(this.context));
|
||||
}
|
||||
|
||||
ensureObjectValue() {
|
||||
/* if (isNote(hap.value)) {
|
||||
// supports primitive hap values that look like notes
|
||||
|
||||
@@ -62,7 +62,12 @@ export class Pattern {
|
||||
this.__steps = steps === undefined ? undefined : Fraction(steps);
|
||||
}
|
||||
|
||||
_processParts() {
|
||||
return this.withHap((hap) => hap.withContext((c) => ({ ...c, processParts: true })));
|
||||
}
|
||||
|
||||
setSteps(steps) {
|
||||
// TODO shouldn't this be pure and return a new pattern?
|
||||
this._steps = steps;
|
||||
return this;
|
||||
}
|
||||
|
||||
+35
-7
@@ -52,6 +52,7 @@ export function parseControlsFromHap(hap, cps) {
|
||||
controls.unit === 'c' && controls.speed != null && (controls.speed = controls.speed / cps);
|
||||
const channels = controls.channels;
|
||||
channels != undefined && (controls.channels = JSON.stringify(channels));
|
||||
|
||||
return controls;
|
||||
}
|
||||
|
||||
@@ -63,16 +64,42 @@ export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
|
||||
const keyvals = Object.entries(controls).flat();
|
||||
const ts = collator.calculateTimestamp(currentTime, targetTime) * 1000;
|
||||
const msg = { address: '/dirt/play', args: keyvals, timestamp: ts };
|
||||
|
||||
if ('oschost' in hap.value) {
|
||||
msg['host'] = hap.value['oschost'];
|
||||
}
|
||||
if ('oscport' in hap.value) {
|
||||
msg['port'] = hap.value['oscport'];
|
||||
}
|
||||
ws.send(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
export async function superdirtTrigger(hap, currentTime, cps = 1, targetTime) {
|
||||
const ws = await connect();
|
||||
const controls = parseControlsFromHap(hap, cps);
|
||||
const ts = collator.calculateTimestamp(currentTime, targetTime) * 1000;
|
||||
|
||||
let oschost = '127.0.0.1';
|
||||
let oscport = 57120;
|
||||
let bushost = oschost;
|
||||
let busport = 57110;
|
||||
|
||||
for (const [k, v] of Object.entries(controls)) {
|
||||
if (k.startsWith('^')) {
|
||||
const bus_idval = v;
|
||||
const msg = JSON.stringify({
|
||||
oschost: bushost,
|
||||
oscport: busport,
|
||||
address: '/c_set',
|
||||
args: bus_idval,
|
||||
timestamp: ts,
|
||||
});
|
||||
// console.log('bus message', msg);
|
||||
ws.send(msg);
|
||||
// So they aren't sent with trigger messages
|
||||
delete controls[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (hap.hasOnset()) {
|
||||
const keyvals = Object.entries(controls).flat();
|
||||
ws.send(JSON.stringify({ oschost, oscport, address: '/dirt/play', args: keyvals, timestamp: ts }));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
|
||||
@@ -83,3 +110,4 @@ export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
|
||||
* @returns Pattern
|
||||
*/
|
||||
export const osc = register('osc', (pat) => pat.onTrigger(oscTrigger));
|
||||
export const superdirt = register('superdirt', (pat) => pat._processParts().onTrigger(superdirtTrigger));
|
||||
|
||||
+9
-13
@@ -12,18 +12,13 @@ import { WebSocketServer } from 'ws';
|
||||
import osc from 'osc';
|
||||
|
||||
const WS_PORT = 8080; // WebSocket server port
|
||||
const OSC_REMOTE_IP = '127.0.0.1';
|
||||
const OSC_REMOTE_PORT = 57120;
|
||||
|
||||
const udpPort = new osc.UDPPort({
|
||||
localAddress: '0.0.0.0',
|
||||
localPort: 0,
|
||||
remoteAddress: OSC_REMOTE_IP,
|
||||
remotePort: OSC_REMOTE_PORT,
|
||||
});
|
||||
|
||||
udpPort.open();
|
||||
console.log(`[Sending OSC] ${OSC_REMOTE_IP}:${OSC_REMOTE_PORT}`);
|
||||
|
||||
udpPort.on('error', (e) => {
|
||||
console.log('Error: ', e);
|
||||
@@ -36,23 +31,24 @@ wss.on('connection', (ws) => {
|
||||
console.log('New WebSocket connection');
|
||||
|
||||
ws.on('message', (message) => {
|
||||
let osc_host = '127.0.0.1';
|
||||
let osc_port = 57120;
|
||||
let oschost = '127.0.0.1';
|
||||
let oscport = 57120;
|
||||
|
||||
try {
|
||||
const data = JSON.parse(message);
|
||||
if ('host' in data) {
|
||||
osc_host = data['host'];
|
||||
if ('oschost' in data) {
|
||||
oschost = data['oschost'];
|
||||
delete data['oschost'];
|
||||
}
|
||||
if ('port' in data) {
|
||||
osc_port = data['port'];
|
||||
if ('oscport' in data) {
|
||||
oscport = data['oscport'];
|
||||
delete data['oscport'];
|
||||
}
|
||||
let msg = { address: data['address'], args: data['args'] };
|
||||
if ('timestamp' in data) {
|
||||
msg = { timeTag: osc.timeTag(0, data['timestamp']), packets: [msg] };
|
||||
}
|
||||
|
||||
udpPort.send(msg, osc_host, osc_port);
|
||||
udpPort.send(msg, oschost, oscport);
|
||||
} catch (err) {
|
||||
console.error('Error parsing message:', err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user