mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-15 23:58:33 +00:00
Merge branch 'main' into gamepad-pr
This commit is contained in:
commit
06958a5281
@ -68,6 +68,10 @@ export class Cyclist {
|
|||||||
// see https://github.com/tidalcycles/strudel/pull/1004
|
// see https://github.com/tidalcycles/strudel/pull/1004
|
||||||
const deadline = targetTime - phase;
|
const deadline = targetTime - phase;
|
||||||
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
||||||
|
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
|
||||||
|
this.cps = hap.value.cps;
|
||||||
|
this.num_ticks_since_cps_change = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -152,9 +152,12 @@ export function repl({
|
|||||||
shouldHush && hush();
|
shouldHush && hush();
|
||||||
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
||||||
if (Object.keys(pPatterns).length) {
|
if (Object.keys(pPatterns).length) {
|
||||||
pattern = stack(...Object.values(pPatterns));
|
let patterns = Object.values(pPatterns);
|
||||||
}
|
if (allTransform) {
|
||||||
if (allTransform) {
|
patterns = patterns.map(allTransform);
|
||||||
|
}
|
||||||
|
pattern = stack(...patterns);
|
||||||
|
} else if (allTransform) {
|
||||||
pattern = allTransform(pattern);
|
pattern = allTransform(pattern);
|
||||||
}
|
}
|
||||||
if (!isPattern(pattern)) {
|
if (!isPattern(pattern)) {
|
||||||
|
|||||||
3
packages/mqtt/README.md
Normal file
3
packages/mqtt/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# @strudel/serial
|
||||||
|
|
||||||
|
This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers.
|
||||||
87
packages/mqtt/mqtt.mjs
Normal file
87
packages/mqtt/mqtt.mjs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
mqtt.mjs - for patterning the internet of things from strudel
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/serial/serial.mjs>
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Pattern, isPattern } from '@strudel/core';
|
||||||
|
import Paho from 'paho-mqtt';
|
||||||
|
|
||||||
|
const connections = {};
|
||||||
|
|
||||||
|
// Handle connection loss
|
||||||
|
function onConnectionLost(responseObject) {
|
||||||
|
if (responseObject.errorCode !== 0) {
|
||||||
|
console.error(' mqtt connection lost: ', responseObject.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle received messages
|
||||||
|
function onMessageArrived(message) {
|
||||||
|
console.log('incoming mqtt message: ', message.payloadString); // prettier-ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFailure(err) {
|
||||||
|
console.error('Connection failed: ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern.prototype.mqtt = function (
|
||||||
|
username = undefined,
|
||||||
|
password = undefined,
|
||||||
|
topic = undefined,
|
||||||
|
host = 'wss://localhost:8883/',
|
||||||
|
client = undefined,
|
||||||
|
latency = 0,
|
||||||
|
) {
|
||||||
|
const key = host + '-' + client;
|
||||||
|
let connected = false;
|
||||||
|
if (!client) {
|
||||||
|
client = 'strudel-' + String(Math.floor(Math.random() * 1000000));
|
||||||
|
}
|
||||||
|
function onConnect() {
|
||||||
|
console.log('Connected to mqtt broker');
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cx;
|
||||||
|
if (connections[key]) {
|
||||||
|
cx = connections[key];
|
||||||
|
} else {
|
||||||
|
cx = new Paho.Client(host, client);
|
||||||
|
cx.onConnectionLost = onConnectionLost;
|
||||||
|
cx.onMessageArrived = onMessageArrived;
|
||||||
|
const props = {
|
||||||
|
onSuccess: onConnect,
|
||||||
|
onFailure: onFailure,
|
||||||
|
useSSL: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (username) {
|
||||||
|
props.userName = username;
|
||||||
|
props.password = password;
|
||||||
|
}
|
||||||
|
cx.connect(props);
|
||||||
|
}
|
||||||
|
return this.withHap((hap) => {
|
||||||
|
const onTrigger = (t_deprecate, hap, currentTime, cps, targetTime) => {
|
||||||
|
if (!connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let message = '';
|
||||||
|
if (typeof hap.value === 'object') {
|
||||||
|
message = JSON.stringify(hap.value);
|
||||||
|
} else {
|
||||||
|
message = hap.value;
|
||||||
|
}
|
||||||
|
message = new Paho.Message(message);
|
||||||
|
message.destinationName = topic;
|
||||||
|
|
||||||
|
const offset = (targetTime - currentTime + latency) * 1000;
|
||||||
|
|
||||||
|
window.setTimeout(function () {
|
||||||
|
cx.send(message);
|
||||||
|
}, offset);
|
||||||
|
};
|
||||||
|
return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
38
packages/mqtt/package.json
Normal file
38
packages/mqtt/package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel/mqtt",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"description": "MQTT API for strudel",
|
||||||
|
"main": "mqtt.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"publishConfig": {
|
||||||
|
"main": "dist/index.mjs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"titdalcycles",
|
||||||
|
"strudel",
|
||||||
|
"pattern",
|
||||||
|
"livecoding",
|
||||||
|
"algorave"
|
||||||
|
],
|
||||||
|
"author": "Alex McLean <alex@slab.org>",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@strudel/core": "workspace:*",
|
||||||
|
"paho-mqtt": "^1.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^5.0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
packages/mqtt/vite.config.js
Normal file
19
packages/mqtt/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { dependencies } from './package.json';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [],
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'mqtt.mjs'),
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...Object.keys(dependencies)],
|
||||||
|
},
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -34,6 +34,7 @@
|
|||||||
"@strudel/hydra": "workspace:*",
|
"@strudel/hydra": "workspace:*",
|
||||||
"@strudel/midi": "workspace:*",
|
"@strudel/midi": "workspace:*",
|
||||||
"@strudel/mini": "workspace:*",
|
"@strudel/mini": "workspace:*",
|
||||||
|
"@strudel/mqtt": "workspace:*",
|
||||||
"@strudel/osc": "workspace:*",
|
"@strudel/osc": "workspace:*",
|
||||||
"@strudel/serial": "workspace:*",
|
"@strudel/serial": "workspace:*",
|
||||||
"@strudel/soundfonts": "workspace:*",
|
"@strudel/soundfonts": "workspace:*",
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
---
|
---
|
||||||
title: MIDI & OSC
|
title: MIDI, OSC & MQTT
|
||||||
layout: ../../layouts/MainLayout.astro
|
layout: ../../layouts/MainLayout.astro
|
||||||
---
|
---
|
||||||
|
|
||||||
import { MiniRepl } from '../../docs/MiniRepl';
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
import { JsDoc } from '../../docs/JsDoc';
|
import { JsDoc } from '../../docs/JsDoc';
|
||||||
|
|
||||||
# MIDI and OSC
|
# MIDI, OSC and MQTT
|
||||||
|
|
||||||
The default audio output of Strudel uses the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API).
|
Normally, Strudel is used to pattern sound, using its own '[web audio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)'-based synthesiser called [SuperDough](https://github.com/tidalcycles/strudel/tree/main/packages/superdough).
|
||||||
It is also possible to use Strudel with MIDI and OSC / [SuperDirt](https://github.com/musikinformatik/SuperDirt/) instead.
|
|
||||||
|
|
||||||
# MIDI API
|
It is also possible to pattern other things with Strudel, such as software and hardware synthesisers with MIDI, other software using Open Sound Control/OSC (including the [SuperDirt](https://github.com/musikinformatik/SuperDirt/) synthesiser commonly used with Strudel's sibling [TidalCycles](https://tidalcycles.org/)), or the MQTT 'internet of things' protocol.
|
||||||
|
|
||||||
Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi).
|
# MIDI
|
||||||
|
|
||||||
|
Strudel supports MIDI without any additional software (thanks to [webmidi](https://npmjs.com/package/webmidi)), just by adding methods to your pattern:
|
||||||
|
|
||||||
## midi(outputName?)
|
## midi(outputName?)
|
||||||
|
|
||||||
@ -45,20 +46,22 @@ But you can also control cc messages separately like this:
|
|||||||
$: ccv(sine.segment(16).slow(4)).ccn(74).midi()`}
|
$: ccv(sine.segment(16).slow(4)).ccn(74).midi()`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
# OSC/SuperDirt API
|
# OSC/SuperDirt/StrudelDirt
|
||||||
|
|
||||||
In mainline tidal, the actual sound is generated via [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider.
|
In TidalCycles, sound is usually generated using [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider. Strudel also supports using SuperDirt, although it requires installing some additional software.
|
||||||
Strudel also supports using [SuperDirt](https://github.com/musikinformatik/SuperDirt/) as a backend, although it requires some developer tooling to run.
|
|
||||||
|
There is also [StrudelDirt](https://github.com/daslyfe/StrudelDirt) which is SuperDirt with some optimisations for working with Strudel. (A longer term aim is to merge these optimisations back into mainline SuperDirt)
|
||||||
|
|
||||||
## Prequisites
|
## Prequisites
|
||||||
|
|
||||||
Getting [SuperDirt](https://github.com/musikinformatik/SuperDirt/) to work with Strudel, you need to
|
To get SuperDirt to work with Strudel, you need to
|
||||||
|
|
||||||
1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info.
|
1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info.
|
||||||
2. install [node.js](https://nodejs.org/en/)
|
2. install SuperDirt, or the [StrudelDirt](https://github.com/daslyfe/StrudelDirt) fork which is optimised for use with Strudel
|
||||||
3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed)
|
3. install [node.js](https://nodejs.org/en/)
|
||||||
4. run `pnpm i` in the strudel directory
|
4. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed)
|
||||||
5. run `pnpm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
|
5. run `pnpm i` in the strudel directory
|
||||||
|
6. run `pnpm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
|
||||||
|
|
||||||
Now you're all set!
|
Now you're all set!
|
||||||
|
|
||||||
@ -86,3 +89,67 @@ Please refer to [Tidal Docs](https://tidalcycles.org/) for more info.
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
But can we use Strudel [offline](/learn/pwa)?
|
But can we use Strudel [offline](/learn/pwa)?
|
||||||
|
|
||||||
|
# MQTT
|
||||||
|
|
||||||
|
MQTT is a lightweight network protocol, designed for 'internet of things' devices. For use with strudel, you will
|
||||||
|
need access to an MQTT server known as a 'broker' configured to accept secure 'websocket' connections. You could
|
||||||
|
run one yourself (e.g. by running [mosquitto](https://mosquitto.org/)), although getting an SSL certificate that
|
||||||
|
your web browser will trust might be a bit tricky for those without systems administration experience.
|
||||||
|
Alternatively, you can use [a public broker](https://www.hivemq.com/mqtt/public-mqtt-broker/).
|
||||||
|
|
||||||
|
Strudel does not yet support receiving messages over MQTT, only sending them.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The following example shows how to send a pattern to an MQTT broker:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:only="react"
|
||||||
|
tune={`"hello world"
|
||||||
|
.mqtt(undefined, // username (undefined for open/public servers)
|
||||||
|
undefined, // password
|
||||||
|
'/strudel-pattern', // mqtt 'topic'
|
||||||
|
'wss://mqtt.eclipseprojects.io:443/mqtt', // MQTT server address
|
||||||
|
'mystrudel', // MQTT client id - randomly generated if not supplied
|
||||||
|
0 // latency / delay before sending messages (0 = no delay)
|
||||||
|
)`}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
Other software can then receive the messages. For example using the [mosquitto](https://mosquitto.org/) commandline client tools:
|
||||||
|
|
||||||
|
```
|
||||||
|
> mosquitto_sub -h mqtt.eclipseprojects.io -p 1883 -t "/strudel-pattern"
|
||||||
|
hello
|
||||||
|
world
|
||||||
|
hello
|
||||||
|
world
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Control patterns will be encoded as JSON, for example:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:only="react"
|
||||||
|
tune={`sound("sax(3,8)").speed("2 3")
|
||||||
|
.mqtt(undefined, // username (undefined for open/public servers)
|
||||||
|
undefined, // password
|
||||||
|
'/strudel-pattern', // mqtt 'topic'
|
||||||
|
'wss://mqtt.eclipseprojects.io:443/mqtt', // MQTT server address
|
||||||
|
'mystrudel', // MQTT client id - randomly generated if not supplied
|
||||||
|
0 // latency / delay before sending messages (0 = no delay)
|
||||||
|
)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Will send messages like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"s":"sax","speed":2}
|
||||||
|
{"s":"sax","speed":2}
|
||||||
|
{"s":"sax","speed":3}
|
||||||
|
{"s":"sax","speed":2}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Libraries for receiving MQTT are available for many programming languages.
|
||||||
|
|||||||
@ -82,6 +82,7 @@ export function loadModules() {
|
|||||||
import('@strudel/csound'),
|
import('@strudel/csound'),
|
||||||
import('@strudel/tidal'),
|
import('@strudel/tidal'),
|
||||||
import('@strudel/gamepad'),
|
import('@strudel/gamepad'),
|
||||||
|
import('@strudel/mqtt'),
|
||||||
];
|
];
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
modules = modules.concat([
|
modules = modules.concat([
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user