mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-17 19:14:45 -04:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5887d4b999 | |||
| 688a3d29fd | |||
| 800f2f466b | |||
| 655a9ff1a4 | |||
| 12f8902c5f | |||
| 9cdb1a53f9 | |||
| f5efa18e96 | |||
| fcebde5abd | |||
| 7fbd4527e5 | |||
| d79716c3f8 | |||
| 884811fd99 | |||
| 712a2ae018 | |||
| 4f460eeb32 | |||
| 913416a9d5 | |||
| fd61001b4d | |||
| ce4d2c17a7 | |||
| a42ebd7aaf | |||
| f38718004f | |||
| 1ddf968750 | |||
| 6211be5cc7 | |||
| 6855311d2d | |||
| d2b060a057 | |||
| 3f550cc6ce | |||
| 727fc2c049 | |||
| c0f006eef1 | |||
| adb13cc458 | |||
| 1ab3c16045 |
@@ -10,7 +10,7 @@ import Hap from './hap.mjs';
|
||||
import State from './state.mjs';
|
||||
import { unionWithObj } from './value.mjs';
|
||||
|
||||
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
|
||||
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod, objectify } from './util.mjs';
|
||||
import drawLine from './drawLine.mjs';
|
||||
|
||||
/** @class Class representing a pattern. */
|
||||
@@ -546,6 +546,10 @@ export class Pattern {
|
||||
return this._fromBipolar().range(min, max);
|
||||
}
|
||||
|
||||
union(other) {
|
||||
return this._opleft(other, (a) => (b) => Object.assign({}, objectify(a), objectify(b)));
|
||||
}
|
||||
|
||||
_bindWhole(choose_whole, func) {
|
||||
const pat_val = this;
|
||||
const query = function (state) {
|
||||
@@ -1222,9 +1226,8 @@ export function pure(value) {
|
||||
|
||||
export function isPattern(thing) {
|
||||
// thing?.constructor?.name !== 'Pattern' // <- this will fail when code is mangled
|
||||
const is = thing instanceof Pattern || thing?._Pattern;
|
||||
|
||||
if (thing?._Pattern && !thing instanceof Pattern) {
|
||||
const is = thing instanceof Pattern || thing._Pattern;
|
||||
if (!thing instanceof Pattern) {
|
||||
console.warn(
|
||||
`Found Pattern that fails "instanceof Pattern" check.
|
||||
This may happen if you are using multiple versions of @strudel.cycles/core.
|
||||
@@ -1567,8 +1570,3 @@ Pattern.prototype.define = (name, func, options = {}) => {
|
||||
// Pattern.prototype.define('early', (a, pat) => pat.early(a), { patternified: true, composable: true });
|
||||
Pattern.prototype.define('hush', (pat) => pat.hush(), { patternified: false, composable: true });
|
||||
Pattern.prototype.define('bypass', (pat) => pat.bypass(on), { patternified: true, composable: true });
|
||||
|
||||
export async function loadScript(url) {
|
||||
const code = await fetch(url).then((res) => res.text());
|
||||
return eval(code);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
|
||||
*/
|
||||
export const sine = sine2._fromBipolar();
|
||||
|
||||
|
||||
/**
|
||||
* A cosine signal between 0 and 1.
|
||||
*
|
||||
@@ -59,7 +58,6 @@ export const sine = sine2._fromBipolar();
|
||||
export const cosine = sine._early(Fraction(1).div(4));
|
||||
export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
|
||||
|
||||
/**
|
||||
* A square signal between 0 and 1.
|
||||
*
|
||||
@@ -112,7 +110,14 @@ const timeToRandsPrime = (seed, n) => {
|
||||
|
||||
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||
|
||||
/**
|
||||
* A continuous pattern of random numbers, between 0 and 1
|
||||
*/
|
||||
export const rand = signal(timeToRand);
|
||||
/**
|
||||
* A continuous pattern of random numbers, between -1 and 1
|
||||
*/
|
||||
export const rand2 = rand._toBipolar();
|
||||
|
||||
export const _brandBy = (p) => rand.fmap((x) => x < p);
|
||||
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
|
||||
@@ -121,19 +126,67 @@ export const brand = _brandBy(0.5);
|
||||
export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
|
||||
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
|
||||
|
||||
export const chooseWith = (pat, xs) => {
|
||||
export const __chooseWith = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
if (xs.length == 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat
|
||||
.range(0, xs.length)
|
||||
.fmap((i) => xs[Math.floor(i)])
|
||||
.outerJoin();
|
||||
return pat.range(0, xs.length).fmap((i) => xs[Math.floor(i)]);
|
||||
};
|
||||
/**
|
||||
* Choose from the list of values (or patterns of values) using the given
|
||||
* pattern of numbers, which should be in the range of 0..1
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const chooseWith = (pat, xs) => {
|
||||
return __chooseWith(pat, xs).outerJoin();
|
||||
};
|
||||
|
||||
/**
|
||||
* As with {chooseWith}, but the structure comes from the chosen values, rather
|
||||
* than the pattern you're using to choose with.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const chooseInWith = (pat, xs) => {
|
||||
return __chooseWith(pat, xs).innerJoin();
|
||||
};
|
||||
|
||||
/**
|
||||
* Chooses randomly from the given list of values.
|
||||
* @param {...any} xs
|
||||
* @returns {Pattern} - a continuous pattern.
|
||||
*/
|
||||
export const choose = (...xs) => chooseWith(rand, xs);
|
||||
|
||||
/**
|
||||
* Chooses from the given list of values (or patterns of values), according
|
||||
* to the pattern that the method is called on. The pattern should be in
|
||||
* the range 0 .. 1.
|
||||
* @param {...any} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
Pattern.prototype.choose = function (...xs) {
|
||||
return chooseWith(this, xs);
|
||||
};
|
||||
|
||||
/**
|
||||
* As with choose, but the pattern that this method is called on should be
|
||||
* in the range -1 .. 1
|
||||
* @param {...any} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
Pattern.prototype.choose2 = function (...xs) {
|
||||
return chooseWith(this._fromBipolar(), xs);
|
||||
};
|
||||
|
||||
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
|
||||
export const randcat = chooseCycles;
|
||||
|
||||
const _wchooseWith = function (pat, ...pairs) {
|
||||
const values = pairs.map((pair) => reify(pair[0]));
|
||||
const weights = [];
|
||||
|
||||
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { strict as assert } from 'assert';
|
||||
import { pure } from '../pattern.mjs';
|
||||
import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency } from '../util.mjs';
|
||||
import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency, dirtify } from '../util.mjs';
|
||||
|
||||
describe('isNote', () => {
|
||||
it('should recognize notes without accidentals', () => {
|
||||
@@ -118,3 +118,31 @@ describe('compose', () => {
|
||||
assert.equal(compose(addS('a'), addS('b'))('x'), 'xab');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dirtify', () => {
|
||||
it('should offset plain number', () => {
|
||||
assert.deepStrictEqual(dirtify(61), { n: 1 });
|
||||
assert.deepStrictEqual(dirtify(60), { n: 0 });
|
||||
});
|
||||
it('should parse note string as midi number', () => {
|
||||
assert.deepStrictEqual(dirtify('c4'), { n: 0 });
|
||||
assert.deepStrictEqual(dirtify('c5'), { n: 12 });
|
||||
});
|
||||
it('should use non note string as sound', () => {
|
||||
assert.deepStrictEqual(dirtify('bd'), { s: 'bd' });
|
||||
});
|
||||
it('should keep objects as is', () => {
|
||||
assert.deepStrictEqual(dirtify({ s: 'bd' }), { s: 'bd' });
|
||||
assert.deepStrictEqual(dirtify({ n: 7 }), { n: 7 });
|
||||
assert.deepStrictEqual(dirtify({ n: 7, room: 0.5 }), { n: 7, room: 0.5 });
|
||||
});
|
||||
it('should parse notes inside object', () => {
|
||||
assert.deepStrictEqual(dirtify({ n: 'c3', s: 'supersaw' }), { n: -12, s: 'supersaw' });
|
||||
});
|
||||
it('should dirtify .value', () => {
|
||||
assert.deepStrictEqual(dirtify({ value: 61 }), { n: 1 });
|
||||
assert.deepStrictEqual(dirtify({ value: 'c5' }), { n: 12 });
|
||||
assert.deepStrictEqual(dirtify({ value: 'bd' }), { s: 'bd' });
|
||||
assert.deepStrictEqual(dirtify({ value: 61, room: 0.5 }), { n: 1, room: 0.5 });
|
||||
});
|
||||
});
|
||||
|
||||
+40
-3
@@ -19,9 +19,6 @@ export const tokenizeNote = (note) => {
|
||||
|
||||
// turns the given note into its midi number representation
|
||||
export const toMidi = (note) => {
|
||||
if (typeof note === 'number') {
|
||||
return note;
|
||||
}
|
||||
const [pc, acc, oct] = tokenizeNote(note);
|
||||
if (!pc) {
|
||||
throw new Error('not a note: "' + note + '"');
|
||||
@@ -122,3 +119,43 @@ export function curry(func, overload) {
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
// this functions just makes sure non objects values are wrapped in { value }
|
||||
export function objectify(value) {
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value; // do nothing if its already an object
|
||||
}
|
||||
return { value };
|
||||
}
|
||||
|
||||
// maybe move this to osc?
|
||||
// turns value into object that is the right format superdirt osc
|
||||
export function dirtify(val) {
|
||||
const obj = objectify(val);
|
||||
|
||||
if (obj.n && typeof obj.n === 'string') {
|
||||
return { ...obj, ...dirtify(obj.n) };
|
||||
}
|
||||
const { value, ...rest } = obj;
|
||||
if (typeof value === 'undefined') {
|
||||
return rest;
|
||||
}
|
||||
const isNumber = typeof value === 'number';
|
||||
const isMidi = typeof value === 'string' && isNote(value);
|
||||
if (isNumber || isMidi) {
|
||||
const numberOffset = -60; // tidal 0 = midi 60
|
||||
const n = (isNumber ? value : toMidi(value)) + numberOffset;
|
||||
if (rest.n) {
|
||||
console.warn(`dirtify: n is already defined as "${rest.n}", overwriting with value "${n}"`);
|
||||
}
|
||||
return { ...rest, n };
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
// not a note => should be a sound
|
||||
return { ...rest, s: value };
|
||||
}
|
||||
console.warn(`dirtify: ignored value "${value}"`);
|
||||
// what lands here?
|
||||
// throw new Error('cannot objectify: ' + value);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import OSC from 'osc-js';
|
||||
import { Pattern } from '@strudel.cycles/core';
|
||||
import { Pattern, objectify } from '@strudel.cycles/core';
|
||||
|
||||
const comm = new OSC();
|
||||
comm.open();
|
||||
@@ -30,7 +30,7 @@ Pattern.prototype.osc = function () {
|
||||
if (startedAt < 0) {
|
||||
startedAt = Date.now() - currentTime * 1000;
|
||||
}
|
||||
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
||||
const controls = Object.assign({}, { cps, cycle, delta }, objectify(hap.value));
|
||||
const keyvals = Object.entries(controls).flat();
|
||||
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
||||
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||
@@ -41,3 +41,7 @@ Pattern.prototype.osc = function () {
|
||||
return hap.setContext({ ...hap.context, onTrigger });
|
||||
});
|
||||
};
|
||||
|
||||
Pattern.prototype.superdirt = function () {
|
||||
return this.withValue(dirtify).osc();
|
||||
};
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+19
-51
@@ -77,7 +77,8 @@ const materialPalenightTheme = EditorView.theme(
|
||||
backgroundColor: '#6199ff2f',
|
||||
},
|
||||
|
||||
'.cm-activeLine': { backgroundColor: cursor + '50' },
|
||||
// commented out because it looks bad in mini repl one liners
|
||||
//'.cm-activeLine': { backgroundColor: cursor + '50' },
|
||||
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
@@ -211,7 +212,7 @@ const highlightField = StateField.define({
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f)
|
||||
});
|
||||
function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount, theme }) {
|
||||
function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount }) {
|
||||
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(CodeMirror$1, {
|
||||
onViewChange: onViewChanged,
|
||||
style: {
|
||||
@@ -223,7 +224,7 @@ function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorD
|
||||
onChange,
|
||||
extensions: [
|
||||
javascript(),
|
||||
theme || materialPalenight,
|
||||
materialPalenight,
|
||||
highlightField,
|
||||
flashField
|
||||
]
|
||||
@@ -294,13 +295,8 @@ function useCycle(props) {
|
||||
await Tone.start();
|
||||
Tone.getTransport().start('+0.1');
|
||||
};
|
||||
const stop = (setZero = false) => {
|
||||
if (setZero) {
|
||||
Tone.getTransport().cancel();
|
||||
Tone.getTransport().stop();
|
||||
} else {
|
||||
Tone.getTransport().pause();
|
||||
}
|
||||
const stop = () => {
|
||||
Tone.getTransport().pause();
|
||||
setStarted(false);
|
||||
};
|
||||
const toggle = () => (started ? stop() : start());
|
||||
@@ -370,7 +366,7 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
onEvent: useCallback(
|
||||
(time, event, currentTime) => {
|
||||
try {
|
||||
onEvent?.(time, event, currentTime);
|
||||
onEvent?.(event);
|
||||
if (event.context.logs?.length) {
|
||||
event.context.logs.forEach(pushLog);
|
||||
}
|
||||
@@ -379,10 +375,8 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
if (defaultSynth) {
|
||||
const note = getPlayableNoteValue(event);
|
||||
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
} else if (!onEvent) {
|
||||
throw new Error(
|
||||
'no defaultSynth nor onEvent passed to useRepl + event has no onTrigger. nothing happens',
|
||||
);
|
||||
} else {
|
||||
throw new Error('no defaultSynth passed to useRepl.');
|
||||
}
|
||||
/* console.warn('no instrument chosen', event);
|
||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
||||
@@ -423,16 +417,16 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
});
|
||||
|
||||
const activateCode = useCallback(
|
||||
async (_code = code, evaluateOnly = false) => {
|
||||
async (_code = code) => {
|
||||
if (activeCode && !dirty) {
|
||||
setError(undefined);
|
||||
!evaluateOnly && cycle.start();
|
||||
cycle.start();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setPending(true);
|
||||
const parsed = await evaluate(_code);
|
||||
!evaluateOnly && cycle.start();
|
||||
cycle.start();
|
||||
broadcast({ type: 'start', from: id });
|
||||
setPattern(() => parsed.pattern);
|
||||
if (autolink) {
|
||||
@@ -463,19 +457,6 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
}
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
cycle.stop(true);
|
||||
setActiveCode(undefined);
|
||||
};
|
||||
|
||||
const evaluateOnly = () => {
|
||||
activateCode(code, true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => stop();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
hideHeader,
|
||||
hideConsole,
|
||||
@@ -489,10 +470,8 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
dirty,
|
||||
log,
|
||||
togglePlay,
|
||||
stop,
|
||||
setActiveCode,
|
||||
activateCode,
|
||||
evaluateOnly,
|
||||
activeCode,
|
||||
pushLog,
|
||||
hash,
|
||||
@@ -553,13 +532,13 @@ var tailwind = '';
|
||||
|
||||
var style = '';
|
||||
|
||||
const container = "_container_10e1g_1";
|
||||
const header = "_header_10e1g_5";
|
||||
const buttons = "_buttons_10e1g_9";
|
||||
const button = "_button_10e1g_9";
|
||||
const buttonDisabled = "_buttonDisabled_10e1g_17";
|
||||
const error = "_error_10e1g_21";
|
||||
const body = "_body_10e1g_25";
|
||||
const container = "_container_xpa19_1";
|
||||
const header = "_header_xpa19_5";
|
||||
const buttons = "_buttons_xpa19_9";
|
||||
const button = "_button_xpa19_9";
|
||||
const buttonDisabled = "_buttonDisabled_xpa19_17";
|
||||
const error = "_error_xpa19_21";
|
||||
const body = "_body_xpa19_25";
|
||||
var styles = {
|
||||
container: container,
|
||||
header: header,
|
||||
@@ -591,11 +570,6 @@ function Icon({ type }) {
|
||||
fillRule: "evenodd",
|
||||
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
|
||||
clipRule: "evenodd"
|
||||
}),
|
||||
stop: /* @__PURE__ */ React.createElement("path", {
|
||||
fillRule: "evenodd",
|
||||
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z",
|
||||
clipRule: "evenodd"
|
||||
})
|
||||
}[type]);
|
||||
}
|
||||
@@ -657,17 +631,11 @@ function MiniRepl({ tune, defaultSynth, hideOutsideView = false, theme, init, on
|
||||
onClick: () => activateCode()
|
||||
}, /* @__PURE__ */ React.createElement(Icon, {
|
||||
type: "refresh"
|
||||
})), /* @__PURE__ */ React.createElement("button", {
|
||||
className: cx(styles.button),
|
||||
onClick: () => stop(true)
|
||||
}, /* @__PURE__ */ React.createElement(Icon, {
|
||||
type: "stop"
|
||||
}))), error && /* @__PURE__ */ React.createElement("div", {
|
||||
className: styles.error
|
||||
}, error.message)), /* @__PURE__ */ React.createElement("div", {
|
||||
className: styles.body
|
||||
}, show && /* @__PURE__ */ React.createElement(CodeMirror, {
|
||||
theme,
|
||||
value: code,
|
||||
onChange: setCode,
|
||||
onViewChanged: setView
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cm-editor{background-color:transparent!important}._container_10e1g_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(68 76 87 / var(--tw-bg-opacity))}._header_10e1g_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_10e1g_9{display:flex}._button_10e1g_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_10e1g_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_10e1g_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_10e1g_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_10e1g_25{position:relative;overflow:auto}
|
||||
*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cm-editor{background-color:transparent!important}._container_xpa19_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(17 17 17 / var(--tw-bg-opacity))}._header_xpa19_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_xpa19_9{display:flex}._button_xpa19_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_xpa19_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_xpa19_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_xpa19_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_xpa19_25{position:relative;overflow:auto}
|
||||
|
||||
@@ -79,7 +79,7 @@ const highlightField = StateField.define({
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
export default function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount, theme }) {
|
||||
export default function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount }) {
|
||||
return (
|
||||
<>
|
||||
<_CodeMirror
|
||||
@@ -93,7 +93,7 @@ export default function CodeMirror({ value, onChange, onViewChanged, onCursor, o
|
||||
onChange={onChange}
|
||||
extensions={[
|
||||
javascript(),
|
||||
theme || materialPalenight,
|
||||
materialPalenight,
|
||||
highlightField,
|
||||
flashField,
|
||||
// theme, language, ...
|
||||
|
||||
@@ -26,13 +26,6 @@ export function Icon({ type }) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
stop: (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
}[type]
|
||||
}
|
||||
</svg>
|
||||
|
||||
@@ -63,14 +63,11 @@ export function MiniRepl({ tune, defaultSynth, hideOutsideView = false, theme, i
|
||||
<button className={cx(dirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
<button className={cx(styles.button)} onClick={() => stop(true)}>
|
||||
<Icon type="stop" />
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className={styles.error}>{error.message}</div>}
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
{show && <CodeMirror6 theme={theme} value={code} onChange={setCode} onViewChanged={setView} />}
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.container {
|
||||
@apply sc-rounded-md sc-overflow-hidden sc-bg-[#444C57];
|
||||
@apply sc-rounded-md sc-overflow-hidden sc-bg-[#111111];
|
||||
}
|
||||
|
||||
.header {
|
||||
|
||||
@@ -66,13 +66,8 @@ function useCycle(props) {
|
||||
await Tone.start();
|
||||
Tone.getTransport().start('+0.1');
|
||||
};
|
||||
const stop = (setZero = false) => {
|
||||
if (setZero) {
|
||||
Tone.getTransport().cancel();
|
||||
Tone.getTransport().stop();
|
||||
} else {
|
||||
Tone.getTransport().pause();
|
||||
}
|
||||
const stop = () => {
|
||||
Tone.getTransport().pause();
|
||||
setStarted(false);
|
||||
};
|
||||
const toggle = () => (started ? stop() : start());
|
||||
|
||||
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
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 { useCallback, useState, useMemo, useEffect } from 'react';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
|
||||
import useCycle from './useCycle.mjs';
|
||||
@@ -44,7 +44,7 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
onEvent: useCallback(
|
||||
(time, event, currentTime) => {
|
||||
try {
|
||||
onEvent?.(time, event, currentTime);
|
||||
onEvent?.(event);
|
||||
if (event.context.logs?.length) {
|
||||
event.context.logs.forEach(pushLog);
|
||||
}
|
||||
@@ -53,10 +53,8 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
if (defaultSynth) {
|
||||
const note = getPlayableNoteValue(event);
|
||||
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
} else if (!onEvent) {
|
||||
throw new Error(
|
||||
'no defaultSynth nor onEvent passed to useRepl + event has no onTrigger. nothing happens',
|
||||
);
|
||||
} else {
|
||||
throw new Error('no defaultSynth passed to useRepl.');
|
||||
}
|
||||
/* console.warn('no instrument chosen', event);
|
||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
||||
@@ -97,16 +95,16 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
});
|
||||
|
||||
const activateCode = useCallback(
|
||||
async (_code = code, evaluateOnly = false) => {
|
||||
async (_code = code) => {
|
||||
if (activeCode && !dirty) {
|
||||
setError(undefined);
|
||||
!evaluateOnly && cycle.start();
|
||||
cycle.start();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setPending(true);
|
||||
const parsed = await evaluate(_code);
|
||||
!evaluateOnly && cycle.start();
|
||||
cycle.start();
|
||||
broadcast({ type: 'start', from: id });
|
||||
setPattern(() => parsed.pattern);
|
||||
if (autolink) {
|
||||
@@ -139,19 +137,6 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
}
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
cycle.stop(true);
|
||||
setActiveCode(undefined);
|
||||
};
|
||||
|
||||
const evaluateOnly = () => {
|
||||
activateCode(code, true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => stop();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
hideHeader,
|
||||
hideConsole,
|
||||
@@ -165,10 +150,8 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
dirty,
|
||||
log,
|
||||
togglePlay,
|
||||
stop,
|
||||
setActiveCode,
|
||||
activateCode,
|
||||
evaluateOnly,
|
||||
activeCode,
|
||||
pushLog,
|
||||
hash,
|
||||
|
||||
@@ -67,7 +67,8 @@ export const materialPalenightTheme = EditorView.theme(
|
||||
backgroundColor: '#6199ff2f',
|
||||
},
|
||||
|
||||
'.cm-activeLine': { backgroundColor: cursor + '50' },
|
||||
// commented out because it looks bad in mini repl one liners
|
||||
//'.cm-activeLine': { backgroundColor: cursor + '50' },
|
||||
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
|
||||
@@ -32,7 +32,7 @@ Pattern.prototype.pianoroll = function ({
|
||||
playheadColor = 'white',
|
||||
minMidi = 10,
|
||||
maxMidi = 90,
|
||||
autorange = 1,
|
||||
autorange = 0,
|
||||
timeframe: timeframeProp,
|
||||
fold = 0,
|
||||
vertical = 0,
|
||||
|
||||
@@ -62,11 +62,7 @@ Pattern.prototype.tone = function (instrument) {
|
||||
} else if (instrument instanceof NoiseSynth) {
|
||||
instrument.triggerAttackRelease(hap.duration.valueOf(), time); // noise has no value
|
||||
} else if (instrument instanceof Piano) {
|
||||
// note = getPlayableNoteValue(hap); // piano does not understand frequencies
|
||||
note = hap.value;
|
||||
if (typeof note === 'number') {
|
||||
note = midi2note(note);
|
||||
}
|
||||
note = getPlayableNoteValue(hap);
|
||||
instrument.keyDown({ note, time, velocity });
|
||||
instrument.keyUp({ note, time: time + hap.duration.valueOf(), velocity });
|
||||
} else if (instrument instanceof Sampler) {
|
||||
|
||||
@@ -6,6 +6,6 @@ This package adds [webdirt](https://github.com/dktr0/WebDirt) support to strudel
|
||||
|
||||
Add default samples to repl:
|
||||
|
||||
1. move samples to `repl/public/samples` folder. the samples folder is expected to have subfolders, with samples in it. the subfolders will be the names of the samples.
|
||||
2. run `./makeSampleMap.sh ../../repl/public/samples/EmuSP12 > ../../repl/public/samples/EmuSP12.json`
|
||||
1. move samples to `repl/public` folder. the samples folder is expected to have subfolders, with samples in it. the subfolders will be the names of the samples.
|
||||
2. run `./makeSampleMap.sh ../../repl/public/EmuSP12 > ../../repl/public/EmuSP12.json`
|
||||
3. adapt `loadWebDirt` in App.jsx + MiniRepl.jsx to use the generated json file
|
||||
|
||||
+1
-2
@@ -24,5 +24,4 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
oldtunes.mjs
|
||||
|
||||
public/samples/EMU World
|
||||
public/samples/EMU World/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user