mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-27 23:15:12 -04:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79875ebc50 | |||
| a1bd905c06 | |||
| 8f24ce223f | |||
| 51648d3e6e | |||
| d4e67e1c20 | |||
| edf4739cc6 | |||
| 3e636bd6f0 | |||
| 809eb7132c | |||
| dd6429007e | |||
| e211d5a6bb | |||
| 91e1cc136e | |||
| 92e3a80e65 | |||
| 4f046ae883 | |||
| 2b575d50b9 | |||
| 7b92874909 | |||
| 10bfe32a2d | |||
| a41b07c67a | |||
| 20027d7208 | |||
| f38792c569 | |||
| 2c2bfd7070 | |||
| 484c5e6cdd | |||
| a036c4f921 | |||
| c0e7971be7 | |||
| b491c42af5 | |||
| ee43d85f29 | |||
| fcd83cce55 | |||
| fddf2ff2a7 | |||
| 6d4787ee33 | |||
| 1a362bd8ea | |||
| 20bbda30f8 | |||
| a8c613b7eb | |||
| fd0a910bfb |
@@ -10,7 +10,7 @@ function getExports(code) {
|
||||
let ast;
|
||||
try {
|
||||
ast = parse(code, {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaVersion: 11,
|
||||
sourceType: 'module',
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -49,7 +49,9 @@ function getExports(code) {
|
||||
}
|
||||
|
||||
function isDocumented(name, docs) {
|
||||
return docs.find((d) => d.name === name || d.synonyms?.includes(name));
|
||||
return docs.find(
|
||||
(d) => d.name === name || d.tags?.find((t) => t.title === 'synonyms' && t.value.split(', ').includes(name)),
|
||||
);
|
||||
}
|
||||
|
||||
async function getUndocumented(path, docs) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import jsdoc from '../../doc.json';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { h } from './html';
|
||||
import { Scale } from '@tonaljs/tonal';
|
||||
import { soundMap } from 'superdough';
|
||||
//TODO: fix tonal scale import
|
||||
// import { Scale } from '@tonaljs/tonal';
|
||||
// import { soundMap } from '@strudel/webaudio';
|
||||
let soundMap = undefined;
|
||||
import { complex } from '@strudel/tonal';
|
||||
|
||||
const escapeHtml = (str) => {
|
||||
@@ -79,7 +81,9 @@ const hasExcludedTags = (doc) =>
|
||||
['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag));
|
||||
|
||||
export function bankCompletions() {
|
||||
const soundDict = soundMap.get();
|
||||
// TODO: FIX IMPORT
|
||||
const soundDict = soundMap?.get() ?? {};
|
||||
|
||||
const banks = new Set();
|
||||
for (const key of Object.keys(soundDict)) {
|
||||
const [bank, suffix] = key.split('_');
|
||||
@@ -90,13 +94,13 @@ export function bankCompletions() {
|
||||
.map((name) => ({ label: name, type: 'bank' }));
|
||||
}
|
||||
|
||||
// Attempt to get all scale names from Tonal
|
||||
// Attempt to get all scale names from Tonal TODO: FIX IMPORT
|
||||
let scaleCompletions = [];
|
||||
try {
|
||||
scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' }));
|
||||
} catch (e) {
|
||||
console.warn('[autocomplete] Could not load scale names from Tonal:', e);
|
||||
}
|
||||
// try {
|
||||
// scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' }));
|
||||
// } catch (e) {
|
||||
// console.warn('[autocomplete] Could not load scale names from Tonal:', e);
|
||||
// }
|
||||
|
||||
// Valid mode values for voicing
|
||||
const modeCompletions = [
|
||||
@@ -268,7 +272,7 @@ function soundHandler(context) {
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragMatch = inside.match(SOUND_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
const soundNames = Object.keys(soundMap.get()).sort();
|
||||
const soundNames = Object.keys(soundMap?.get() ?? {}).sort();
|
||||
const filteredSounds = soundNames.filter((name) => name.includes(fragment));
|
||||
let options = filteredSounds.map((name) => ({ label: name, type: 'sound' }));
|
||||
const from = soundContext.to - fragment.length;
|
||||
|
||||
@@ -1750,17 +1750,64 @@ export const { semitone } = registerControl('semitone');
|
||||
// TODO: synth param
|
||||
export const { voice } = registerControl('voice');
|
||||
// voicings // https://codeberg.org/uzu/strudel/issues/506
|
||||
// chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings
|
||||
/**
|
||||
* The chord to voice
|
||||
* @name chord
|
||||
* @param {string | Pattern} symbols chord symbols to voice e.g., C, Eb, Fm7, G7. The symbols can be defined via addVoicings
|
||||
* @example
|
||||
* chord("<Am C D F Am E Am E>").voicing()
|
||||
**/
|
||||
export const { chord } = registerControl('chord');
|
||||
// which dictionary to use for the voicings
|
||||
/**
|
||||
* Which dictionary to use for the voicings. This falls back to the default dictionary if not provided
|
||||
*
|
||||
* @name dictionary
|
||||
* @param {string} dictionaryName which dictionary (having been defined with `addVoicings`) to use
|
||||
* @example
|
||||
* addVoicings('house', {
|
||||
'': ['7 12 16', '0 7 16', '4 7 12'],
|
||||
'm': ['0 3 7']
|
||||
})
|
||||
chord("<Am C D F Am E Am E>")
|
||||
.dict('house').anchor(66)
|
||||
.voicing().room(.5)
|
||||
**/
|
||||
export const { dictionary, dict } = registerControl('dictionary', 'dict');
|
||||
// the top note to align the voicing to, defaults to c5
|
||||
/** The top note to align the voicing to. Defaults to c5
|
||||
*
|
||||
* @name anchor
|
||||
* @param {string | Pattern} anchorNote the note to align the voicings to
|
||||
* @example
|
||||
* anchor("<c4 g4 c5 g5>").chord("C").voicing()
|
||||
**/
|
||||
export const { anchor } = registerControl('anchor');
|
||||
// how the voicing is offset from the anchored position
|
||||
/**
|
||||
* Sets how the voicing is offset from the anchored position
|
||||
*
|
||||
* @name offset
|
||||
* @param {number | Pattern} shift the amount to shift the voicing up or down
|
||||
* @example
|
||||
* chord("<Am C D F Am E Am E>").offset("<0 1 2 3 4 5>") // alter the voicing each time
|
||||
**/
|
||||
export const { offset } = registerControl('offset');
|
||||
// how many octaves are voicing steps spread apart, defaults to 1
|
||||
/**
|
||||
* How many octaves are voicing steps spread apart, defaults to 1
|
||||
*
|
||||
* @name octaves
|
||||
* @param {number | Pattern} count the number of octaves
|
||||
* @example
|
||||
* chord("<Am C D F Am E Am E>").octaves("<2 4>").voicing()
|
||||
**/
|
||||
export const { octaves } = registerControl('octaves');
|
||||
// below = anchor note will be removed from the voicing, useful for melody harmonization
|
||||
/**
|
||||
* Remove anchor note from the voicing. Useful for melody harmonization
|
||||
*
|
||||
* @name mode
|
||||
* @param {string | Pattern} modeName one of {below | above | duck | root}
|
||||
* @example
|
||||
* mode("<below above duck root>").chord("C").voicing()
|
||||
*
|
||||
**/
|
||||
export const { mode } = registerControl(['mode', 'anchor']);
|
||||
|
||||
/**
|
||||
|
||||
@@ -3289,7 +3289,6 @@ export const striate = register('striate', function (n, pat) {
|
||||
/**
|
||||
* Makes the sample fit the given number of cycles by changing the speed.
|
||||
* @name loopAt
|
||||
* @synonyms loopat
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
@@ -3422,7 +3421,6 @@ export const fit = register('fit', (pat) =>
|
||||
* given by a global clock and this function will be
|
||||
* deprecated/removed.
|
||||
* @name loopAtCps
|
||||
* @synonyms loopatcps
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
|
||||
@@ -10,27 +10,11 @@ import Fraction from './fraction.mjs';
|
||||
|
||||
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
||||
|
||||
/**
|
||||
* A `signal` consisting of a constant value. Similar to `pure`, except that function
|
||||
* creates a pattern with one event per cycle, whereas this pattern doesn't have an intrinsic
|
||||
* structure.
|
||||
*
|
||||
* @param {*} value The constant value of the resulting pattern
|
||||
* @returns Pattern
|
||||
*/
|
||||
export function steady(value) {
|
||||
// A continuous value
|
||||
return new Pattern((state) => [new Hap(undefined, state.span, value)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "signal", an unstructured pattern consisting of a single value that changes
|
||||
* over time.
|
||||
*
|
||||
*
|
||||
* @param {*} func
|
||||
* @returns Pattern
|
||||
*/
|
||||
export const signal = (func) => {
|
||||
const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))];
|
||||
return new Pattern(query);
|
||||
@@ -39,7 +23,7 @@ export const signal = (func) => {
|
||||
/**
|
||||
* A sawtooth signal between 0 and 1.
|
||||
*
|
||||
* @type {Pattern}
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
|
||||
* .clip(saw.slow(2))
|
||||
@@ -173,7 +157,6 @@ export const time = signal(id);
|
||||
/**
|
||||
* The mouse's x position value ranges from 0 to 1.
|
||||
* @name mousex
|
||||
* @synonyms mouseX
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* n(mousex.segment(4).range(0,7)).scale("C:minor")
|
||||
@@ -183,7 +166,6 @@ export const time = signal(id);
|
||||
/**
|
||||
* The mouse's y position value ranges from 0 to 1.
|
||||
* @name mousey
|
||||
* @synonyms mouseY
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* n(mousey.segment(4).range(0,7)).scale("C:minor")
|
||||
@@ -233,6 +215,10 @@ const timeToRandsPrime = (seed, n) => {
|
||||
|
||||
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* A discrete pattern of numbers from 0 to n-1
|
||||
* @example
|
||||
@@ -403,22 +389,16 @@ export const chooseInWith = (pat, xs) => {
|
||||
|
||||
/**
|
||||
* Chooses randomly from the given list of elements.
|
||||
* @synonyms chooseOut
|
||||
* @param {...any} xs values / patterns to choose from.
|
||||
* @returns {Pattern} - a continuous pattern.
|
||||
* @example
|
||||
* note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6"))
|
||||
*/
|
||||
export const choose = (...xs) => chooseWith(rand, xs);
|
||||
export const chooseOut = choose;
|
||||
|
||||
/**
|
||||
* As with {choose}, but the structure comes from the chosen values, rather
|
||||
* than the pattern you're using to choose with.
|
||||
* @param {...any} xs
|
||||
* @returns
|
||||
*/
|
||||
// todo: doc
|
||||
export const chooseIn = (...xs) => chooseInWith(rand, xs);
|
||||
export const chooseOut = choose;
|
||||
|
||||
/**
|
||||
* Chooses from the given list of values (or patterns of values), according
|
||||
@@ -516,7 +496,6 @@ function _perlin(t) {
|
||||
const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb));
|
||||
return v;
|
||||
}
|
||||
|
||||
export const perlinWith = (tpat) => {
|
||||
return tpat.fmap(_perlin);
|
||||
};
|
||||
@@ -562,15 +541,6 @@ export const perlin = perlinWith(time.fmap((v) => Number(v)));
|
||||
*/
|
||||
export const berlin = berlinWith(time.fmap((v) => Number(v)));
|
||||
|
||||
/**
|
||||
* Removes events from a pattern given a signal of numbers and a cutoff
|
||||
* value for interpreting that pattern as true or false.
|
||||
* @param {number} withPat - A numeric pattern for comparing the main pattern
|
||||
* against. Values higher than the cutoff will let events through and lower
|
||||
* values will remove events from the main pattern
|
||||
* @param {number} cutoff - The threshold value to use when interpreting the
|
||||
* `withPat`
|
||||
*/
|
||||
export const degradeByWith = register(
|
||||
'degradeByWith',
|
||||
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
|
||||
|
||||
@@ -40,6 +40,12 @@ const pattern = sequence([
|
||||
- D-Pad
|
||||
- `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase)
|
||||
- Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight`(or `tglU`, `tglD`, `tglL`, `tglR`)
|
||||
- Stick Buttons
|
||||
- `l3`, `r3` (or `ls`, `rs`)
|
||||
- Toggle versions: `tglL3`, `tglR3` (or `tglLS`, `tglRS`)
|
||||
- System Buttons
|
||||
- `start`, `back` (or uppercase `START`, `BACK`)
|
||||
- Toggle versions: `tglStart`, `tglBack` (or `tglSTART`, `tglBACK`)
|
||||
|
||||
### Analog Sticks
|
||||
- Left Stick
|
||||
|
||||
@@ -29,6 +29,10 @@ The gamepad module provides access to buttons and analog sticks as normalized si
|
||||
| | Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT` |
|
||||
| D-Pad | `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase) |
|
||||
| | Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight` (or `tglU`, `tglD`, `tglL`, `tglR`) |
|
||||
| Stick Buttons | `l3`, 'r3' (or `ls`, `rs`) |
|
||||
| | Toggle versions: `tglL3`, 'tglR3' (or `tglLs`, `tglRs`) |
|
||||
| System Buttons | `start`, `back` (or uppercase `START`, `BACK`) |
|
||||
| | Toggle versions: `tglStart`, `tglBack` (or `tglSTART`, `tglBACK`) |
|
||||
|
||||
### Analog Sticks
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ export const buttonMap = {
|
||||
rt: 7,
|
||||
back: 8,
|
||||
start: 9,
|
||||
l3: 10,
|
||||
ls: 10,
|
||||
r3: 11,
|
||||
rs: 11,
|
||||
u: 12,
|
||||
up: 12,
|
||||
d: 13,
|
||||
|
||||
+59
-42
@@ -5,9 +5,10 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import * as _WebMidi from 'webmidi';
|
||||
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
|
||||
import { Pattern, isPattern, logger, ref } from '@strudel/core';
|
||||
import { noteToMidi, getControlName } from '@strudel/core';
|
||||
import { Note } from 'webmidi';
|
||||
import { scheduleAtTime } from '../superdough/helpers.mjs';
|
||||
|
||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||
export const { WebMidi } = _WebMidi;
|
||||
@@ -190,7 +191,7 @@ function mapCC(mapping, value) {
|
||||
}
|
||||
|
||||
// sends a cc message to the given device on the given channel
|
||||
function sendCC(ccn, ccv, device, midichan, timeOffsetString) {
|
||||
function sendCC(ccn, ccv, device, midichan, targetTime) {
|
||||
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
|
||||
throw new Error('expected ccv to be a number between 0 and 1');
|
||||
}
|
||||
@@ -198,19 +199,23 @@ function sendCC(ccn, ccv, device, midichan, timeOffsetString) {
|
||||
throw new Error('expected ccn to be a number or a string');
|
||||
}
|
||||
const scaled = Math.round(ccv * 127);
|
||||
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendControlChange(ccn, scaled, midichan);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
// sends a program change message to the given device on the given channel
|
||||
function sendProgramChange(progNum, device, midichan, timeOffsetString) {
|
||||
function sendProgramChange(progNum, device, midichan, targetTime) {
|
||||
if (typeof progNum !== 'number' || progNum < 0 || progNum > 127) {
|
||||
throw new Error('expected progNum (program change) to be a number between 0 and 127');
|
||||
}
|
||||
device.sendProgramChange(progNum, midichan, { time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendProgramChange(progNum, midichan);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
// sends a sysex message to the given device on the given channel
|
||||
function sendSysex(sysexid, sysexdata, device, timeOffsetString) {
|
||||
function sendSysex(sysexid, sysexdata, device, targetTime) {
|
||||
if (Array.isArray(sysexid)) {
|
||||
if (!sysexid.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
||||
throw new Error('all sysexid bytes must be integers between 0 and 255');
|
||||
@@ -225,11 +230,13 @@ function sendSysex(sysexid, sysexdata, device, timeOffsetString) {
|
||||
if (!sysexdata.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
||||
throw new Error('all sysex bytes must be integers between 0 and 255');
|
||||
}
|
||||
device.sendSysex(sysexid, sysexdata, { time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendSysex(sysexid, sysexdata);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
// sends a NRPN message to the given device on the given channel
|
||||
function sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString) {
|
||||
function sendNRPN(nrpnn, nrpv, device, midichan, targetTime) {
|
||||
if (Array.isArray(nrpnn)) {
|
||||
if (!nrpnn.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
||||
throw new Error('all nrpnn bytes must be integers between 0 and 255');
|
||||
@@ -237,28 +244,34 @@ function sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString) {
|
||||
} else if (!Number.isInteger(nrpv) || nrpv < 0 || nrpv > 255) {
|
||||
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
|
||||
}
|
||||
|
||||
device.sendNRPN(nrpnn, nrpv, midichan, { time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendNRPN(nrpnn, nrpv, midichan);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
// sends a pitch bend message to the given device on the given channel
|
||||
function sendPitchBend(midibend, device, midichan, timeOffsetString) {
|
||||
function sendPitchBend(midibend, device, midichan, targetTime) {
|
||||
if (typeof midibend !== 'number' || midibend < -1 || midibend > 1) {
|
||||
throw new Error('expected midibend to be a number between -1 and 1');
|
||||
}
|
||||
device.sendPitchBend(midibend, midichan, { time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendPitchBend(midibend, midichan);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
// sends a channel aftertouch message to the given device on the given channel
|
||||
function sendAftertouch(miditouch, device, midichan, timeOffsetString) {
|
||||
function sendAftertouch(miditouch, device, midichan, targetTime) {
|
||||
if (typeof miditouch !== 'number' || miditouch < 0 || miditouch > 1) {
|
||||
throw new Error('expected miditouch to be a number between 0 and 1');
|
||||
}
|
||||
device.sendChannelAftertouch(miditouch, midichan, { time: timeOffsetString });
|
||||
|
||||
scheduleAtTime(() => {
|
||||
device.sendChannelAftertouch(miditouch, midichan);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
// sends a note message to the given device on the given channel
|
||||
function sendNote(note, velocity, duration, device, midichan, timeOffsetString) {
|
||||
function sendNote(note, velocity, duration, device, midichan, targetTime) {
|
||||
if (note == null || note === '') {
|
||||
throw new Error('note cannot be null or empty');
|
||||
}
|
||||
@@ -268,12 +281,12 @@ function sendNote(note, velocity, duration, device, midichan, timeOffsetString)
|
||||
if (duration != null && (typeof duration !== 'number' || duration < 0)) {
|
||||
throw new Error('duration must be a positive number');
|
||||
}
|
||||
|
||||
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
|
||||
const midiNote = new Note(midiNumber, { attack: velocity, duration });
|
||||
device.playNote(midiNote, midichan, {
|
||||
time: timeOffsetString,
|
||||
});
|
||||
|
||||
scheduleAtTime(() => {
|
||||
device.playNote(midiNote, midichan);
|
||||
}, targetTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +322,6 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
let midiConfig = {
|
||||
// Default configuration values
|
||||
isController: false, // Disable sending notes for midi controllers
|
||||
latencyMs: 34, // Default latency to get audio engine to line up in ms
|
||||
noteOffsetMs: 10, // Default note-off offset to prevent glitching in ms
|
||||
midichannel: 1, // Default MIDI channel
|
||||
velocity: 0.9, // Default velocity
|
||||
@@ -333,18 +345,13 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
|
||||
});
|
||||
|
||||
return this.onTrigger((hap, currentTime, cps, targetTime) => {
|
||||
return this.onTrigger((hap, _currentTime, cps, targetTime) => {
|
||||
if (!WebMidi.enabled) {
|
||||
logger('Midi not enabled');
|
||||
return;
|
||||
}
|
||||
hap.ensureObjectValue();
|
||||
|
||||
//magic number to get audio engine to line up, can probably be calculated somehow
|
||||
const latencyMs = midiConfig.latencyMs;
|
||||
// passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output
|
||||
const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`;
|
||||
|
||||
// midi event values from hap with configurable defaults
|
||||
let {
|
||||
note,
|
||||
@@ -380,7 +387,7 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
// if midimap is set, send a cc messages from defined controls
|
||||
if (midicontrolMap.has(midimap)) {
|
||||
const ccs = mapCC(midicontrolMap.get(midimap), hap.value);
|
||||
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString));
|
||||
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, targetTime));
|
||||
} else if (midimap !== 'default') {
|
||||
// Add warning when a non-existent midimap is specified
|
||||
logger(`[midi] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`);
|
||||
@@ -392,12 +399,12 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
// try to prevent glitching by subtracting noteOffsetMs from the duration length
|
||||
const duration = (hap.duration.valueOf() / cps) * 1000 - midiConfig.noteOffsetMs;
|
||||
|
||||
sendNote(note, velocity, duration, device, midichan, timeOffsetString);
|
||||
sendNote(note, velocity, duration, device, midichan, targetTime);
|
||||
}
|
||||
|
||||
// Handle program change
|
||||
if (progNum !== undefined) {
|
||||
sendProgramChange(progNum, device, midichan, timeOffsetString);
|
||||
sendProgramChange(progNum, device, midichan, targetTime);
|
||||
}
|
||||
|
||||
// Handle sysex
|
||||
@@ -407,53 +414,63 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
// if sysexid is an array the first byte is 0x00
|
||||
|
||||
if (sysexid !== undefined && sysexdata !== undefined) {
|
||||
sendSysex(sysexid, sysexdata, device, timeOffsetString);
|
||||
sendSysex(sysexid, sysexdata, device, targetTime);
|
||||
}
|
||||
|
||||
// Handle control change
|
||||
if (ccv !== undefined && ccn !== undefined) {
|
||||
sendCC(ccn, ccv, device, midichan, timeOffsetString);
|
||||
sendCC(ccn, ccv, device, midichan, targetTime);
|
||||
}
|
||||
|
||||
// Handle NRPN non-registered parameter number
|
||||
if (nrpnn !== undefined && nrpv !== undefined) {
|
||||
sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString);
|
||||
sendNRPN(nrpnn, nrpv, device, midichan, targetTime);
|
||||
}
|
||||
|
||||
// Handle midibend
|
||||
if (midibend !== undefined) {
|
||||
sendPitchBend(midibend, device, midichan, timeOffsetString);
|
||||
sendPitchBend(midibend, device, midichan, targetTime);
|
||||
}
|
||||
|
||||
// Handle miditouch
|
||||
if (miditouch !== undefined) {
|
||||
sendAftertouch(miditouch, device, midichan, timeOffsetString);
|
||||
sendAftertouch(miditouch, device, midichan, targetTime);
|
||||
}
|
||||
|
||||
// Handle midicmd
|
||||
if (hap.whole.begin + 0 === 0) {
|
||||
// we need to start here because we have the timing info
|
||||
device.sendStart({ time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendStart();
|
||||
}, targetTime);
|
||||
}
|
||||
if (['clock', 'midiClock'].includes(midicmd)) {
|
||||
device.sendClock({ time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendClock();
|
||||
}, targetTime);
|
||||
} else if (['start'].includes(midicmd)) {
|
||||
device.sendStart({ time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendStart();
|
||||
}, targetTime);
|
||||
} else if (['stop'].includes(midicmd)) {
|
||||
device.sendStop({ time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendStop();
|
||||
}, targetTime);
|
||||
} else if (['continue'].includes(midicmd)) {
|
||||
device.sendContinue({ time: timeOffsetString });
|
||||
scheduleAtTime(() => {
|
||||
device.sendContinue();
|
||||
}, targetTime);
|
||||
} else if (Array.isArray(midicmd)) {
|
||||
if (midicmd[0] === 'progNum') {
|
||||
sendProgramChange(midicmd[1], device, midichan, timeOffsetString);
|
||||
sendProgramChange(midicmd[1], device, midichan, targetTime);
|
||||
} else if (midicmd[0] === 'cc') {
|
||||
if (midicmd.length === 2) {
|
||||
sendCC(midicmd[0], midicmd[1] / 127, device, midichan, timeOffsetString);
|
||||
sendCC(midicmd[0], midicmd[1] / 127, device, midichan, targetTime);
|
||||
}
|
||||
} else if (midicmd[0] === 'sysex') {
|
||||
if (midicmd.length === 3) {
|
||||
const [_, id, data] = midicmd;
|
||||
sendSysex(id, data, device, timeOffsetString);
|
||||
sendSysex(id, data, device, targetTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +280,11 @@ export function getVibratoOscillator(param, value, t) {
|
||||
return vibratoOscillator;
|
||||
}
|
||||
}
|
||||
|
||||
export function scheduleAtTime(callback, targetTime, audioContext = getAudioContext()) {
|
||||
const currentTime = audioContext.currentTime;
|
||||
webAudioTimeout(audioContext, callback, currentTime, targetTime);
|
||||
}
|
||||
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
||||
// a bit of a hack, but it works very well :)
|
||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||
|
||||
@@ -41,7 +41,6 @@ Tune.prototype.tonicize = function(newTonic) {
|
||||
this.tonic = newTonic
|
||||
}
|
||||
|
||||
|
||||
/* Return data in the mode you are in (freq, ratio, or midi) */
|
||||
|
||||
Tune.prototype.note = function(input,octave){
|
||||
|
||||
@@ -840,6 +840,31 @@ exports[`runs examples > example "amp" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "anchor" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:E2 ]",
|
||||
"[ 0/1 → 1/1 | note:C3 ]",
|
||||
"[ 0/1 → 1/1 | note:E3 ]",
|
||||
"[ 0/1 → 1/1 | note:G3 ]",
|
||||
"[ 0/1 → 1/1 | note:C4 ]",
|
||||
"[ 1/1 → 2/1 | note:C3 ]",
|
||||
"[ 1/1 → 2/1 | note:G3 ]",
|
||||
"[ 1/1 → 2/1 | note:C4 ]",
|
||||
"[ 1/1 → 2/1 | note:E4 ]",
|
||||
"[ 1/1 → 2/1 | note:G4 ]",
|
||||
"[ 2/1 → 3/1 | note:E3 ]",
|
||||
"[ 2/1 → 3/1 | note:C4 ]",
|
||||
"[ 2/1 → 3/1 | note:E4 ]",
|
||||
"[ 2/1 → 3/1 | note:G4 ]",
|
||||
"[ 2/1 → 3/1 | note:C5 ]",
|
||||
"[ 3/1 → 4/1 | note:C4 ]",
|
||||
"[ 3/1 → 4/1 | note:G4 ]",
|
||||
"[ 3/1 → 4/1 | note:C5 ]",
|
||||
"[ 3/1 → 4/1 | note:E5 ]",
|
||||
"[ 3/1 → 4/1 | note:G5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "apply" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:C3 ]",
|
||||
@@ -1847,6 +1872,31 @@ exports[`runs examples > example "chop" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "chord" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:A3 ]",
|
||||
"[ 0/1 → 1/1 | note:C4 ]",
|
||||
"[ 0/1 → 1/1 | note:E4 ]",
|
||||
"[ 0/1 → 1/1 | note:A4 ]",
|
||||
"[ 0/1 → 1/1 | note:C5 ]",
|
||||
"[ 1/1 → 2/1 | note:E3 ]",
|
||||
"[ 1/1 → 2/1 | note:C4 ]",
|
||||
"[ 1/1 → 2/1 | note:E4 ]",
|
||||
"[ 1/1 → 2/1 | note:G4 ]",
|
||||
"[ 1/1 → 2/1 | note:C5 ]",
|
||||
"[ 2/1 → 3/1 | note:D3 ]",
|
||||
"[ 2/1 → 3/1 | note:A3 ]",
|
||||
"[ 2/1 → 3/1 | note:D4 ]",
|
||||
"[ 2/1 → 3/1 | note:Gb4 ]",
|
||||
"[ 2/1 → 3/1 | note:A4 ]",
|
||||
"[ 3/1 → 4/1 | note:F3 ]",
|
||||
"[ 3/1 → 4/1 | note:C4 ]",
|
||||
"[ 3/1 → 4/1 | note:F4 ]",
|
||||
"[ 3/1 → 4/1 | note:A4 ]",
|
||||
"[ 3/1 → 4/1 | note:C5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "chorus" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:d s:sawtooth chorus:0.5 ]",
|
||||
@@ -2739,6 +2789,23 @@ exports[`runs examples > example "detune" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "dictionary" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:A3 room:0.5 ]",
|
||||
"[ 0/1 → 1/1 | note:C4 room:0.5 ]",
|
||||
"[ 0/1 → 1/1 | note:E4 room:0.5 ]",
|
||||
"[ 1/1 → 2/1 | note:G3 room:0.5 ]",
|
||||
"[ 1/1 → 2/1 | note:C4 room:0.5 ]",
|
||||
"[ 1/1 → 2/1 | note:E4 room:0.5 ]",
|
||||
"[ 2/1 → 3/1 | note:A3 room:0.5 ]",
|
||||
"[ 2/1 → 3/1 | note:D4 room:0.5 ]",
|
||||
"[ 2/1 → 3/1 | note:Gb4 room:0.5 ]",
|
||||
"[ 3/1 → 4/1 | note:A3 room:0.5 ]",
|
||||
"[ 3/1 → 4/1 | note:C4 room:0.5 ]",
|
||||
"[ 3/1 → 4/1 | note:F4 room:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "distort" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh distort:0 ]",
|
||||
@@ -6496,6 +6563,28 @@ exports[`runs examples > example "miditouch" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "mode" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:E3 ]",
|
||||
"[ 0/1 → 1/1 | note:C4 ]",
|
||||
"[ 0/1 → 1/1 | note:E4 ]",
|
||||
"[ 0/1 → 1/1 | note:G4 ]",
|
||||
"[ 0/1 → 1/1 | note:C5 ]",
|
||||
"[ 1/1 → 2/1 | note:C5 ]",
|
||||
"[ 1/1 → 2/1 | note:G5 ]",
|
||||
"[ 1/1 → 2/1 | note:C6 ]",
|
||||
"[ 1/1 → 2/1 | note:E6 ]",
|
||||
"[ 2/1 → 3/1 | note:E3 ]",
|
||||
"[ 2/1 → 3/1 | note:C4 ]",
|
||||
"[ 2/1 → 3/1 | note:E4 ]",
|
||||
"[ 2/1 → 3/1 | note:G4 ]",
|
||||
"[ 3/1 → 4/1 | note:C5 ]",
|
||||
"[ 3/1 → 4/1 | note:G5 ]",
|
||||
"[ 3/1 → 4/1 | note:C6 ]",
|
||||
"[ 3/1 → 4/1 | note:E6 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "morph" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
@@ -6814,6 +6903,31 @@ exports[`runs examples > example "octave" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "octaves" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:A3 ]",
|
||||
"[ 0/1 → 1/1 | note:C4 ]",
|
||||
"[ 0/1 → 1/1 | note:E4 ]",
|
||||
"[ 0/1 → 1/1 | note:A4 ]",
|
||||
"[ 0/1 → 1/1 | note:C5 ]",
|
||||
"[ 1/1 → 2/1 | note:E3 ]",
|
||||
"[ 1/1 → 2/1 | note:C4 ]",
|
||||
"[ 1/1 → 2/1 | note:E4 ]",
|
||||
"[ 1/1 → 2/1 | note:G4 ]",
|
||||
"[ 1/1 → 2/1 | note:C5 ]",
|
||||
"[ 2/1 → 3/1 | note:D3 ]",
|
||||
"[ 2/1 → 3/1 | note:A3 ]",
|
||||
"[ 2/1 → 3/1 | note:D4 ]",
|
||||
"[ 2/1 → 3/1 | note:Gb4 ]",
|
||||
"[ 2/1 → 3/1 | note:A4 ]",
|
||||
"[ 3/1 → 4/1 | note:F3 ]",
|
||||
"[ 3/1 → 4/1 | note:C4 ]",
|
||||
"[ 3/1 → 4/1 | note:F4 ]",
|
||||
"[ 3/1 → 4/1 | note:A4 ]",
|
||||
"[ 3/1 → 4/1 | note:C5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "off" example index 0 1`] = `
|
||||
[
|
||||
"[ -5/24 ⇜ (0/1 → 1/8) | note:62 ]",
|
||||
@@ -6847,6 +6961,15 @@ exports[`runs examples > example "off" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "offset" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | chord:Am offset:0 ]",
|
||||
"[ 1/1 → 2/1 | chord:C offset:1 ]",
|
||||
"[ 2/1 → 3/1 | chord:D offset:2 ]",
|
||||
"[ 3/1 → 4/1 | chord:F offset:3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "often" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh speed:0.5 ]",
|
||||
|
||||
+171
-250
@@ -32,12 +32,7 @@
|
||||
"/packages/codemirror/keybindings.mjs": [
|
||||
"keybindings"
|
||||
],
|
||||
"/packages/codemirror/themes/theme-helper.mjs": [
|
||||
"createTheme"
|
||||
],
|
||||
"/packages/codemirror/themes/strudel-theme.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/strudel-theme.mjs": [],
|
||||
"/packages/codemirror/themes/bluescreen.mjs": [
|
||||
"settings"
|
||||
],
|
||||
@@ -53,103 +48,7 @@
|
||||
"/packages/codemirror/themes/algoboy.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/CutiePi.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/sonic-pink.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/red-text.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/green-text.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/archBtw.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/fruitDaw.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/bluescreenlight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/androidstudio.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/atomone.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/aura.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/darcula.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/dracula.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/duotoneDark.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/eclipse.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/githubDark.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/githubLight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/gruvboxDark.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/gruvboxLight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/materialDark.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/materialLight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/nord.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/monokai.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/solarizedDark.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/solarizedLight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/sublime.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/tokyoNight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/tokioNightStorm.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/tokyoNightDay.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/vscodeDark.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/vscodeLight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/xcodeLight.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/bbedit.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/noctisLilac.mjs": [
|
||||
"/packages/codemirror/themes/terminal.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes.mjs": [
|
||||
@@ -162,12 +61,7 @@
|
||||
"activateTheme"
|
||||
],
|
||||
"/packages/codemirror/slider.mjs": [
|
||||
"sliderValues",
|
||||
"SliderWidget",
|
||||
"setSliderWidgets",
|
||||
"updateSliderWidgets",
|
||||
"sliderPlugin",
|
||||
"sliderWithID"
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/codemirror/widget.mjs": [
|
||||
"addWidget",
|
||||
@@ -184,59 +78,6 @@
|
||||
"StrudelMirror"
|
||||
],
|
||||
"/packages/codemirror/index.mjs": [],
|
||||
"/packages/core/logger.mjs": [
|
||||
"logKey",
|
||||
"errorLogger",
|
||||
"logger"
|
||||
],
|
||||
"/packages/core/util.mjs": [
|
||||
"isNoteWithOctave",
|
||||
"isNote",
|
||||
"tokenizeNote",
|
||||
"noteToMidi",
|
||||
"midiToFreq",
|
||||
"freqToMidi",
|
||||
"valueToMidi",
|
||||
"getEventOffsetMs",
|
||||
"_mod",
|
||||
"averageArray",
|
||||
"nanFallback",
|
||||
"getSoundIndex",
|
||||
"getPlayableNoteValue",
|
||||
"getFrequency",
|
||||
"rotate",
|
||||
"pipe",
|
||||
"compose",
|
||||
"flatten",
|
||||
"id",
|
||||
"constant",
|
||||
"listRange",
|
||||
"curry",
|
||||
"parseNumeral",
|
||||
"mapArgs",
|
||||
"numeralArgs",
|
||||
"parseFractional",
|
||||
"fractionalArgs",
|
||||
"splitAt",
|
||||
"zipWith",
|
||||
"pairs",
|
||||
"clamp",
|
||||
"sol2note",
|
||||
"uniq",
|
||||
"uniqsort",
|
||||
"uniqsortr",
|
||||
"unicodeToBase64",
|
||||
"base64ToUnicode",
|
||||
"code2hash",
|
||||
"hash2code",
|
||||
"objectMap",
|
||||
"cycleToSeconds",
|
||||
"ClockCollator",
|
||||
"getPerformanceTimeSeconds",
|
||||
"keyAlias",
|
||||
"getCurrentKeyboardState",
|
||||
"stringifyValues"
|
||||
],
|
||||
"/packages/core/fraction.mjs": [
|
||||
"gcd",
|
||||
"lcm"
|
||||
@@ -250,6 +91,45 @@
|
||||
"/packages/core/state.mjs": [
|
||||
"State"
|
||||
],
|
||||
"/packages/core/logger.mjs": [
|
||||
"logKey",
|
||||
"logger"
|
||||
],
|
||||
"/packages/core/util.mjs": [
|
||||
"isNoteWithOctave",
|
||||
"isNote",
|
||||
"tokenizeNote",
|
||||
"noteToMidi",
|
||||
"midiToFreq",
|
||||
"freqToMidi",
|
||||
"valueToMidi",
|
||||
"_mod",
|
||||
"nanFallback",
|
||||
"getSoundIndex",
|
||||
"getPlayableNoteValue",
|
||||
"getFrequency",
|
||||
"rotate",
|
||||
"pipe",
|
||||
"compose",
|
||||
"flatten",
|
||||
"constant",
|
||||
"listRange",
|
||||
"curry",
|
||||
"parseNumeral",
|
||||
"mapArgs",
|
||||
"numeralArgs",
|
||||
"parseFractional",
|
||||
"fractionalArgs",
|
||||
"splitAt",
|
||||
"zipWith",
|
||||
"clamp",
|
||||
"sol2note",
|
||||
"unicodeToBase64",
|
||||
"base64ToUnicode",
|
||||
"code2hash",
|
||||
"hash2code",
|
||||
"objectMap"
|
||||
],
|
||||
"/packages/core/value.mjs": [
|
||||
"unionWithObj",
|
||||
"valued",
|
||||
@@ -258,8 +138,10 @@
|
||||
],
|
||||
"/packages/core/drawLine.mjs": [],
|
||||
"/packages/core/pattern.mjs": [
|
||||
"calculateSteps",
|
||||
"setStringParser",
|
||||
"polyrhythm",
|
||||
"pr",
|
||||
"pm",
|
||||
"nothing",
|
||||
"isPattern",
|
||||
"reify",
|
||||
@@ -267,12 +149,8 @@
|
||||
"stackRight",
|
||||
"stackCentre",
|
||||
"stackBy",
|
||||
"bind",
|
||||
"innerBind",
|
||||
"outerBind",
|
||||
"squeezeBind",
|
||||
"stepBind",
|
||||
"polyBind",
|
||||
"fastcat",
|
||||
"_polymeterListSteps",
|
||||
"set",
|
||||
"keep",
|
||||
"keepif",
|
||||
@@ -296,51 +174,96 @@
|
||||
"func",
|
||||
"compressSpan",
|
||||
"compressspan",
|
||||
"fastgap",
|
||||
"focusSpan",
|
||||
"focusspan",
|
||||
"density",
|
||||
"sparsity",
|
||||
"zoomArc",
|
||||
"zoomarc",
|
||||
"inv",
|
||||
"juxby",
|
||||
"echowith",
|
||||
"stutWith",
|
||||
"stutwith",
|
||||
"iterback",
|
||||
"slowchunk",
|
||||
"slowChunk",
|
||||
"chunkback",
|
||||
"fastchunk",
|
||||
"bypass",
|
||||
"hsla",
|
||||
"hsl",
|
||||
"_retime",
|
||||
"_slices",
|
||||
"_fitslice",
|
||||
"_match",
|
||||
"_polymeterListSteps",
|
||||
"shrinklist",
|
||||
"s_cat",
|
||||
"s_alt",
|
||||
"s_polymeter",
|
||||
"s_taper",
|
||||
"s_taperlist",
|
||||
"s_add",
|
||||
"s_sub",
|
||||
"s_expand",
|
||||
"s_extend",
|
||||
"s_contract",
|
||||
"s_tour",
|
||||
"s_zip",
|
||||
"steps",
|
||||
"_morph"
|
||||
"loopat",
|
||||
"loopatcps"
|
||||
],
|
||||
"/packages/core/controls.mjs": [
|
||||
"createParam",
|
||||
"isControlName",
|
||||
"registerControl",
|
||||
"sound",
|
||||
"src",
|
||||
"att",
|
||||
"fmi",
|
||||
"fmrelease",
|
||||
"fmvelocity",
|
||||
"analyze",
|
||||
"fft",
|
||||
"dec",
|
||||
"sus",
|
||||
"rel",
|
||||
"hold",
|
||||
"duck",
|
||||
"bandf",
|
||||
"bp",
|
||||
"bandq",
|
||||
"loopb",
|
||||
"loope",
|
||||
"ch",
|
||||
"phaserrate",
|
||||
"phasr",
|
||||
"ph",
|
||||
"phs",
|
||||
"phc",
|
||||
"phd",
|
||||
"phasdp",
|
||||
"cutoff",
|
||||
"ctf",
|
||||
"lp",
|
||||
"lpe",
|
||||
"hpe",
|
||||
"bpe",
|
||||
"lpa",
|
||||
"hpa",
|
||||
"bpa",
|
||||
"lpd",
|
||||
"hpd",
|
||||
"bpd",
|
||||
"lps",
|
||||
"hps",
|
||||
"bps",
|
||||
"lpr",
|
||||
"hpr",
|
||||
"bpr",
|
||||
"fanchor",
|
||||
"vibrato",
|
||||
"v",
|
||||
"vmod",
|
||||
"hcutoff",
|
||||
"hp",
|
||||
"hresonance",
|
||||
"resonance",
|
||||
"delayfb",
|
||||
"dfb",
|
||||
"delayt",
|
||||
"dt",
|
||||
"lock",
|
||||
"det",
|
||||
"fadeTime",
|
||||
"fadeOutTime",
|
||||
"fadeInTime",
|
||||
"patt",
|
||||
"pdec",
|
||||
"psustain",
|
||||
"psus",
|
||||
"prel",
|
||||
"gate",
|
||||
"gat",
|
||||
"activeLabel",
|
||||
@@ -368,13 +291,26 @@
|
||||
"offset",
|
||||
"octaves",
|
||||
"mode",
|
||||
"rlp",
|
||||
"rdim",
|
||||
"rfade",
|
||||
"ir",
|
||||
"size",
|
||||
"sz",
|
||||
"rsize",
|
||||
"dist",
|
||||
"compressorKnee",
|
||||
"compressorRatio",
|
||||
"compressorAttack",
|
||||
"compressorRelease",
|
||||
"waveloss",
|
||||
"density",
|
||||
"expression",
|
||||
"sustainpedal",
|
||||
"tremolodepth",
|
||||
"tremdp",
|
||||
"tremolorate",
|
||||
"tremr",
|
||||
"fshift",
|
||||
"fshiftnote",
|
||||
"fshiftphase",
|
||||
@@ -400,15 +336,27 @@
|
||||
"binshift",
|
||||
"hbrick",
|
||||
"lbrick",
|
||||
"midichan",
|
||||
"control",
|
||||
"ccn",
|
||||
"ccv",
|
||||
"polyTouch",
|
||||
"midibend",
|
||||
"miditouch",
|
||||
"ctlNum",
|
||||
"frameRate",
|
||||
"frames",
|
||||
"hours",
|
||||
"midicmd",
|
||||
"minutes",
|
||||
"progNum",
|
||||
"seconds",
|
||||
"songPtr",
|
||||
"uid",
|
||||
"val",
|
||||
"cps",
|
||||
"legato",
|
||||
"dur",
|
||||
"zrand",
|
||||
"curve",
|
||||
"deltaSlide",
|
||||
@@ -420,40 +368,44 @@
|
||||
"zmod",
|
||||
"zcrush",
|
||||
"zdelay",
|
||||
"tremolo",
|
||||
"zzfx",
|
||||
"colour",
|
||||
"createParams",
|
||||
"ad",
|
||||
"ds",
|
||||
"ar",
|
||||
"midimap",
|
||||
"ctlNum",
|
||||
"polyTouch",
|
||||
"getControlName"
|
||||
"ar"
|
||||
],
|
||||
"/packages/core/euclid.mjs": [
|
||||
"bjork",
|
||||
"e",
|
||||
"euclidrot"
|
||||
],
|
||||
"/packages/core/zyklus.mjs": [],
|
||||
"/packages/core/signal.mjs": [
|
||||
"randrun",
|
||||
"steady",
|
||||
"signal",
|
||||
"isaw",
|
||||
"isaw2",
|
||||
"saw2",
|
||||
"sine2",
|
||||
"cosine2",
|
||||
"square2",
|
||||
"tri2",
|
||||
"time",
|
||||
"_brandBy",
|
||||
"_irand",
|
||||
"pickSqueeze",
|
||||
"pickmodSqueeze",
|
||||
"__chooseWith",
|
||||
"chooseIn",
|
||||
"chooseOut",
|
||||
"randcat",
|
||||
"wrandcat",
|
||||
"perlinWith",
|
||||
"berlinWith",
|
||||
"degradeByWith",
|
||||
"_keyDown"
|
||||
"degradeByWith"
|
||||
],
|
||||
"/packages/core/pick.mjs": [],
|
||||
"/packages/core/speak.mjs": [
|
||||
"speak"
|
||||
],
|
||||
"/packages/core/evaluate.mjs": [
|
||||
"strudelScope",
|
||||
"evalScope",
|
||||
"evaluate"
|
||||
],
|
||||
@@ -488,18 +440,13 @@
|
||||
"isTauri"
|
||||
],
|
||||
"/packages/desktopbridge/midibridge.mjs": [],
|
||||
"/packages/desktopbridge/oscbridge.mjs": [
|
||||
"oscTriggerTauri"
|
||||
],
|
||||
"/packages/desktopbridge/oscbridge.mjs": [],
|
||||
"/packages/desktopbridge/index.mjs": [],
|
||||
"/packages/draw/draw.mjs": [
|
||||
"getDrawContext",
|
||||
"cleanupDraw",
|
||||
"Framer",
|
||||
"Drawer",
|
||||
"getComputedPropertyValue",
|
||||
"getTheme",
|
||||
"setTheme"
|
||||
"Drawer"
|
||||
],
|
||||
"/packages/draw/animate.mjs": [
|
||||
"x",
|
||||
@@ -520,19 +467,16 @@
|
||||
"convertHexToNumber"
|
||||
],
|
||||
"/packages/draw/pianoroll.mjs": [
|
||||
"__pianoroll",
|
||||
"getDrawOptions",
|
||||
"getPunchcardPainter",
|
||||
"drawPianoroll"
|
||||
],
|
||||
"/packages/draw/spiral.mjs": [],
|
||||
"/packages/draw/pitchwheel.mjs": [],
|
||||
"/packages/draw/index.mjs": [],
|
||||
"/packages/midi/midi.mjs": [
|
||||
"WebMidi",
|
||||
"enableWebMidi",
|
||||
"midicontrolMap",
|
||||
"midisoundMap"
|
||||
"midin"
|
||||
],
|
||||
"/packages/midi/index.mjs": [],
|
||||
"/packages/mini/krill-parser.js": [],
|
||||
@@ -552,11 +496,12 @@
|
||||
"/packages/repl/prebake.mjs": [
|
||||
"prebake"
|
||||
],
|
||||
"/packages/repl/repl-component.mjs": [],
|
||||
"/packages/repl/repl-component.mjs": [
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/repl/index.mjs": [],
|
||||
"/packages/soundfonts/gm.mjs": [],
|
||||
"/packages/soundfonts/fontloader.mjs": [
|
||||
"setSoundfontUrl",
|
||||
"getFontBufferSource",
|
||||
"getFontPitch",
|
||||
"registerSoundfonts"
|
||||
@@ -577,7 +522,6 @@
|
||||
"vowelFormant"
|
||||
],
|
||||
"/packages/superdough/logger.mjs": [
|
||||
"errorLogger",
|
||||
"logger",
|
||||
"setLogger"
|
||||
],
|
||||
@@ -590,19 +534,10 @@
|
||||
"valueToMidi",
|
||||
"nanFallback",
|
||||
"_mod",
|
||||
"getSoundIndex",
|
||||
"cycleToSeconds",
|
||||
"secondsToCycle"
|
||||
],
|
||||
"/packages/superdough/noise.mjs": [
|
||||
"getNoiseBuffer",
|
||||
"getNoiseOscillator",
|
||||
"getNoiseMix"
|
||||
"getSoundIndex"
|
||||
],
|
||||
"/packages/superdough/helpers.mjs": [
|
||||
"noises",
|
||||
"gainNode",
|
||||
"getWorklet",
|
||||
"getParamADSR",
|
||||
"getCompressor",
|
||||
"getADSRValues",
|
||||
@@ -615,8 +550,6 @@
|
||||
],
|
||||
"/packages/superdough/sampler.mjs": [
|
||||
"getCachedBuffer",
|
||||
"getSampleInfo",
|
||||
"getSampleBuffer",
|
||||
"getSampleBufferSource",
|
||||
"loadBuffer",
|
||||
"reverseBuffer",
|
||||
@@ -626,32 +559,18 @@
|
||||
"onTriggerSample"
|
||||
],
|
||||
"/packages/superdough/superdough.mjs": [
|
||||
"DEFAULT_MAX_POLYPHONY",
|
||||
"setMaxPolyphony",
|
||||
"setMultiChannelOrbits",
|
||||
"soundMap",
|
||||
"registerSound",
|
||||
"applyGainCurve",
|
||||
"setGainCurve",
|
||||
"getSound",
|
||||
"getAudioDevices",
|
||||
"setDefault",
|
||||
"resetDefaults",
|
||||
"setDefaultValue",
|
||||
"getDefaultValue",
|
||||
"setDefaultValues",
|
||||
"resetDefaultValues",
|
||||
"setVersionDefaults",
|
||||
"resetLoadedSounds",
|
||||
"setDefaultAudioContext",
|
||||
"getAudioContext",
|
||||
"getAudioContextCurrentTime",
|
||||
"getWorklet",
|
||||
"initAudio",
|
||||
"initAudioOnFirstClick",
|
||||
"initializeAudioOutput",
|
||||
"connectToDestination",
|
||||
"panic",
|
||||
"getLfo",
|
||||
"analysers",
|
||||
"analysersData",
|
||||
"getAnalyserById",
|
||||
@@ -660,13 +579,17 @@
|
||||
"superdough",
|
||||
"superdoughTrigger"
|
||||
],
|
||||
"/packages/superdough/noise.mjs": [
|
||||
"getNoiseOscillator",
|
||||
"getNoiseMix"
|
||||
],
|
||||
"/packages/superdough/synth.mjs": [
|
||||
"registerSynthSounds",
|
||||
"waveformN",
|
||||
"getOscillator"
|
||||
],
|
||||
"/packages/superdough/zzfx_fork.mjs": [
|
||||
"buildSamples"
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/superdough/zzfx.mjs": [
|
||||
"getZZFX",
|
||||
@@ -717,7 +640,6 @@
|
||||
],
|
||||
"/packages/transpiler/transpiler.mjs": [
|
||||
"registerWidgetType",
|
||||
"registerLanguage",
|
||||
"transpiler",
|
||||
"getWidgetID"
|
||||
],
|
||||
@@ -732,7 +654,6 @@
|
||||
"drawTimeScope",
|
||||
"drawFrequencyScope"
|
||||
],
|
||||
"/packages/webaudio/spectrum.mjs": [],
|
||||
"/packages/webaudio/index.mjs": [],
|
||||
"/packages/xen/xen.mjs": [
|
||||
"edo",
|
||||
|
||||
@@ -79,13 +79,11 @@ const updateCodeWindow = (context, patternData, reset = false) => {
|
||||
context.handleUpdate(patternData, reset);
|
||||
};
|
||||
|
||||
const autoResetPatternOnChange = !isUdels();
|
||||
|
||||
function UserPatterns({ context }) {
|
||||
const activePattern = useActivePattern();
|
||||
const viewingPatternStore = useViewingPatternData();
|
||||
const viewingPatternData = parseJSON(viewingPatternStore);
|
||||
const { userPatterns, patternFilter } = useSettings();
|
||||
const { userPatterns, patternFilter, patternAutoStart } = useSettings();
|
||||
const viewingPatternID = viewingPatternData?.id;
|
||||
return (
|
||||
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">
|
||||
@@ -135,13 +133,13 @@ function UserPatterns({ context }) {
|
||||
<div className="overflow-auto h-full bg-background p-2 rounded-md">
|
||||
{/* {patternFilter === patternFilterName.user && ( */}
|
||||
<PatternButtons
|
||||
onClick={(id) =>
|
||||
updateCodeWindow(
|
||||
context,
|
||||
{ ...userPatterns[id], collection: userPattern.collection },
|
||||
autoResetPatternOnChange,
|
||||
)
|
||||
}
|
||||
onClick={(id) => {
|
||||
updateCodeWindow(context, { ...userPatterns[id], collection: userPattern.collection }, patternAutoStart);
|
||||
|
||||
if (context.started && activePattern === id) {
|
||||
context.handleEvaluate();
|
||||
}
|
||||
}}
|
||||
patterns={userPatterns}
|
||||
started={context.started}
|
||||
activePattern={activePattern}
|
||||
@@ -188,17 +186,14 @@ function FeaturedPatterns({ context }) {
|
||||
const examplePatterns = useExamplePatterns();
|
||||
const collections = examplePatterns.collections;
|
||||
const patterns = collections.get(patternFilterName.featured);
|
||||
const { patternAutoStart } = useSettings();
|
||||
return (
|
||||
<PatternPageWithPagination
|
||||
patterns={patterns}
|
||||
context={context}
|
||||
initialPage={featuredPageNum}
|
||||
patternOnClick={(id) => {
|
||||
updateCodeWindow(
|
||||
context,
|
||||
{ ...patterns[id], collection: patternFilterName.featured },
|
||||
autoResetPatternOnChange,
|
||||
);
|
||||
updateCodeWindow(context, { ...patterns[id], collection: patternFilterName.featured }, patternAutoStart);
|
||||
}}
|
||||
paginationOnChange={async (pageNum) => {
|
||||
await loadAndSetFeaturedPatterns(pageNum - 1);
|
||||
@@ -213,13 +208,14 @@ function LatestPatterns({ context }) {
|
||||
const examplePatterns = useExamplePatterns();
|
||||
const collections = examplePatterns.collections;
|
||||
const patterns = collections.get(patternFilterName.public);
|
||||
const { patternAutoStart } = useSettings();
|
||||
return (
|
||||
<PatternPageWithPagination
|
||||
patterns={patterns}
|
||||
context={context}
|
||||
initialPage={latestPageNum}
|
||||
patternOnClick={(id) => {
|
||||
updateCodeWindow(context, { ...patterns[id], collection: patternFilterName.public }, autoResetPatternOnChange);
|
||||
updateCodeWindow(context, { ...patterns[id], collection: patternFilterName.public }, patternAutoStart);
|
||||
}}
|
||||
paginationOnChange={async (pageNum) => {
|
||||
await loadAndSetPublicPatterns(pageNum - 1);
|
||||
|
||||
@@ -112,6 +112,7 @@ export function SettingsTab({ started }) {
|
||||
multiChannelOrbits,
|
||||
isTabIndentationEnabled,
|
||||
isMultiCursorEnabled,
|
||||
patternAutoStart,
|
||||
} = useSettings();
|
||||
const shouldAlwaysSync = isUdels();
|
||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||
@@ -304,6 +305,11 @@ export function SettingsTab({ started }) {
|
||||
onChange={(cbEvent) => settingsMap.setKey('isCSSAnimationDisabled', cbEvent.target.checked)}
|
||||
value={isCSSAnimationDisabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Auto-start pattern on pattern change"
|
||||
onChange={(cbEvent) => settingsMap.setKey('patternAutoStart', cbEvent.target.checked)}
|
||||
value={patternAutoStart}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
|
||||
<FormItem label="Reset Settings">
|
||||
|
||||
@@ -62,11 +62,9 @@ export function SoundsTab() {
|
||||
|
||||
// stop current sound on mouseup
|
||||
useEvent('mouseup', () => {
|
||||
const t = trigRef.current;
|
||||
const ref = trigRef.current;
|
||||
trigRef.current = undefined;
|
||||
t?.then((ref) => {
|
||||
ref?.stop(getAudioContext().currentTime + 0.01);
|
||||
});
|
||||
ref?.stop?.(getAudioContext().currentTime + 0.01);
|
||||
});
|
||||
return (
|
||||
<div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground">
|
||||
@@ -124,12 +122,19 @@ export function SoundsTab() {
|
||||
duration: 0.5,
|
||||
};
|
||||
soundPreviewIdx++;
|
||||
const time = ctx.currentTime + 0.05;
|
||||
const onended = () => trigRef.current?.node?.disconnect();
|
||||
trigRef.current = Promise.resolve(onTrigger(time, params, onended));
|
||||
trigRef.current.then((ref) => {
|
||||
connectToDestination(ref?.node);
|
||||
});
|
||||
try {
|
||||
// Pre-load the sample by calling onTrigger with a future time
|
||||
// This triggers the loading but schedules playback for later
|
||||
const time = ctx.currentTime + 0.5; // Give 500ms for loading
|
||||
const ref = await onTrigger(time, params, onended);
|
||||
trigRef.current = ref;
|
||||
if (ref?.node) {
|
||||
connectToDestination(ref.node);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to trigger sound:', err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
|
||||
@@ -92,11 +92,7 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
|
||||
|
||||
async function blobToDataUrl(blob) {
|
||||
return new Promise((resolve) => {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
resolve(URL.createObjectURL(blob));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,11 @@ export function useSettings() {
|
||||
isPanelOpen: parseBoolean(state.isPanelOpen),
|
||||
userPatterns: userPatterns,
|
||||
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
|
||||
patternAutoStart: isUdels()
|
||||
? false
|
||||
: state.patternAutoStart === undefined
|
||||
? true
|
||||
: parseBoolean(state.patternAutoStart),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user