mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -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;
|
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
|
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
||||||
* separated by ':'.
|
* separated by ':'.
|
||||||
|
|||||||
@@ -58,11 +58,10 @@ export class Cyclist {
|
|||||||
|
|
||||||
// query the pattern for events
|
// query the pattern for events
|
||||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' });
|
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' });
|
||||||
|
|
||||||
haps.forEach((hap) => {
|
haps.forEach((hap) => {
|
||||||
if (hap.hasOnset()) {
|
if (hap.hasOnset() || hap.context.processParts) {
|
||||||
const targetTime =
|
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;
|
const duration = hap.duration / this.cps;
|
||||||
// the following line is dumb and only here for backwards compatibility
|
// the following line is dumb and only here for backwards compatibility
|
||||||
// see https://codeberg.org/uzu/strudel/pulls/1004
|
// 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
|
then the whole will be returned as None, in which case the given
|
||||||
value will have been sampled from the point halfway between the
|
value will have been sampled from the point halfway between the
|
||||||
start and end of the 'part' timespan.
|
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
|
The word 'Event' is more or less a reserved word in javascript, hence this
|
||||||
class is named called 'Hap'.
|
class is named called 'Hap'.
|
||||||
@@ -161,6 +161,10 @@ export class Hap {
|
|||||||
return new Hap(this.whole, this.part, this.value, context);
|
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() {
|
ensureObjectValue() {
|
||||||
/* if (isNote(hap.value)) {
|
/* if (isNote(hap.value)) {
|
||||||
// supports primitive hap values that look like notes
|
// supports primitive hap values that look like notes
|
||||||
|
|||||||
@@ -62,7 +62,12 @@ export class Pattern {
|
|||||||
this.__steps = steps === undefined ? undefined : Fraction(steps);
|
this.__steps = steps === undefined ? undefined : Fraction(steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_processParts() {
|
||||||
|
return this.withHap((hap) => hap.withContext((c) => ({ ...c, processParts: true })));
|
||||||
|
}
|
||||||
|
|
||||||
setSteps(steps) {
|
setSteps(steps) {
|
||||||
|
// TODO shouldn't this be pure and return a new pattern?
|
||||||
this._steps = steps;
|
this._steps = steps;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-7
@@ -52,6 +52,7 @@ export function parseControlsFromHap(hap, cps) {
|
|||||||
controls.unit === 'c' && controls.speed != null && (controls.speed = controls.speed / cps);
|
controls.unit === 'c' && controls.speed != null && (controls.speed = controls.speed / cps);
|
||||||
const channels = controls.channels;
|
const channels = controls.channels;
|
||||||
channels != undefined && (controls.channels = JSON.stringify(channels));
|
channels != undefined && (controls.channels = JSON.stringify(channels));
|
||||||
|
|
||||||
return controls;
|
return controls;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,16 +64,42 @@ export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
|
|||||||
const keyvals = Object.entries(controls).flat();
|
const keyvals = Object.entries(controls).flat();
|
||||||
const ts = collator.calculateTimestamp(currentTime, targetTime) * 1000;
|
const ts = collator.calculateTimestamp(currentTime, targetTime) * 1000;
|
||||||
const msg = { address: '/dirt/play', args: keyvals, timestamp: ts };
|
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));
|
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.
|
* 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
|
* @returns Pattern
|
||||||
*/
|
*/
|
||||||
export const osc = register('osc', (pat) => pat.onTrigger(oscTrigger));
|
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';
|
import osc from 'osc';
|
||||||
|
|
||||||
const WS_PORT = 8080; // WebSocket server port
|
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({
|
const udpPort = new osc.UDPPort({
|
||||||
localAddress: '0.0.0.0',
|
localAddress: '0.0.0.0',
|
||||||
localPort: 0,
|
localPort: 0,
|
||||||
remoteAddress: OSC_REMOTE_IP,
|
|
||||||
remotePort: OSC_REMOTE_PORT,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
udpPort.open();
|
udpPort.open();
|
||||||
console.log(`[Sending OSC] ${OSC_REMOTE_IP}:${OSC_REMOTE_PORT}`);
|
|
||||||
|
|
||||||
udpPort.on('error', (e) => {
|
udpPort.on('error', (e) => {
|
||||||
console.log('Error: ', e);
|
console.log('Error: ', e);
|
||||||
@@ -36,23 +31,24 @@ wss.on('connection', (ws) => {
|
|||||||
console.log('New WebSocket connection');
|
console.log('New WebSocket connection');
|
||||||
|
|
||||||
ws.on('message', (message) => {
|
ws.on('message', (message) => {
|
||||||
let osc_host = '127.0.0.1';
|
let oschost = '127.0.0.1';
|
||||||
let osc_port = 57120;
|
let oscport = 57120;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(message);
|
const data = JSON.parse(message);
|
||||||
if ('host' in data) {
|
if ('oschost' in data) {
|
||||||
osc_host = data['host'];
|
oschost = data['oschost'];
|
||||||
|
delete data['oschost'];
|
||||||
}
|
}
|
||||||
if ('port' in data) {
|
if ('oscport' in data) {
|
||||||
osc_port = data['port'];
|
oscport = data['oscport'];
|
||||||
|
delete data['oscport'];
|
||||||
}
|
}
|
||||||
let msg = { address: data['address'], args: data['args'] };
|
let msg = { address: data['address'], args: data['args'] };
|
||||||
if ('timestamp' in data) {
|
if ('timestamp' in data) {
|
||||||
msg = { timeTag: osc.timeTag(0, data['timestamp']), packets: [msg] };
|
msg = { timeTag: osc.timeTag(0, data['timestamp']), packets: [msg] };
|
||||||
}
|
}
|
||||||
|
udpPort.send(msg, oschost, oscport);
|
||||||
udpPort.send(msg, osc_host, osc_port);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error parsing message:', err);
|
console.error('Error parsing message:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user