mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-23 19:48:31 +00:00
Merge remote-tracking branch 'origin/main' into notes-and-numbers
This commit is contained in:
commit
af81626f43
@ -125,7 +125,7 @@ class Hap {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.stateful = stateful;
|
this.stateful = stateful;
|
||||||
if (stateful) {
|
if (stateful) {
|
||||||
assert(typeof this.value === "function", "Stateful values must be functions");
|
console.assert(typeof this.value === "function", "Stateful values must be functions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
withSpan(func) {
|
withSpan(func) {
|
||||||
@ -140,8 +140,10 @@ class Hap {
|
|||||||
}
|
}
|
||||||
resolveState(state) {
|
resolveState(state) {
|
||||||
if (this.stateful && this.hasOnset()) {
|
if (this.stateful && this.hasOnset()) {
|
||||||
const func = this.value[newState, newValue] = func(state);
|
console.log("stateful");
|
||||||
return [newState, this.withValue(() => newValue)];
|
const func = this.value;
|
||||||
|
const [newState, newValue] = func(state);
|
||||||
|
return [newState, new Hap(this.whole, this.part, newValue, this.context, false)];
|
||||||
}
|
}
|
||||||
return [state, this];
|
return [state, this];
|
||||||
}
|
}
|
||||||
@ -518,8 +520,17 @@ class Pattern {
|
|||||||
hush() {
|
hush() {
|
||||||
return silence;
|
return silence;
|
||||||
}
|
}
|
||||||
|
_duration(value) {
|
||||||
|
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||||
|
}
|
||||||
|
_legato(value) {
|
||||||
|
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||||
|
}
|
||||||
|
_velocity(velocity) {
|
||||||
|
return this._withContext((context) => ({...context, velocity: (context.velocity || 1) * velocity}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Pattern.prototype.patternified = ["apply", "fast", "slow", "early", "late"];
|
Pattern.prototype.patternified = ["apply", "fast", "slow", "early", "late", "duration", "legato", "velocity"];
|
||||||
Pattern.prototype.factories = {pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
Pattern.prototype.factories = {pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
||||||
const silence = new Pattern((_) => []);
|
const silence = new Pattern((_) => []);
|
||||||
function pure(value) {
|
function pure(value) {
|
||||||
|
|||||||
23
docs/dist/midi.js
vendored
23
docs/dist/midi.js
vendored
@ -24,12 +24,12 @@ Pattern.prototype.midi = function(output, channel = 1) {
|
|||||||
if (output?.constructor?.name === "Pattern") {
|
if (output?.constructor?.name === "Pattern") {
|
||||||
throw new Error(`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${WebMidi.outputs?.[0]?.name || "IAC Driver Bus 1"}')`);
|
throw new Error(`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${WebMidi.outputs?.[0]?.name || "IAC Driver Bus 1"}')`);
|
||||||
}
|
}
|
||||||
return this.fmap((value) => ({
|
return this._withEvent((event) => {
|
||||||
...value,
|
const onTrigger = (time, event2) => {
|
||||||
onTrigger: (time, event) => {
|
let note = event2.value;
|
||||||
value = value.value || value;
|
const velocity = event2.context?.velocity ?? 0.9;
|
||||||
if (!isNote(value)) {
|
if (!isNote(note)) {
|
||||||
throw new Error("not a note: " + value);
|
throw new Error("not a note: " + note);
|
||||||
}
|
}
|
||||||
if (!WebMidi.enabled) {
|
if (!WebMidi.enabled) {
|
||||||
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||||
@ -43,13 +43,14 @@ Pattern.prototype.midi = function(output, channel = 1) {
|
|||||||
}
|
}
|
||||||
const timingOffset = WebMidi.time - Tone.context.currentTime * 1e3;
|
const timingOffset = WebMidi.time - Tone.context.currentTime * 1e3;
|
||||||
time = time * 1e3 + timingOffset;
|
time = time * 1e3 + timingOffset;
|
||||||
device.playNote(value, channel, {
|
device.playNote(note, channel, {
|
||||||
time,
|
time,
|
||||||
duration: event.duration * 1e3 - 5,
|
duration: event2.duration * 1e3 - 5,
|
||||||
velocity: 0.9
|
velocity
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}));
|
return event.setContext({...event.context, onTrigger});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
export function useWebMidi(props) {
|
export function useWebMidi(props) {
|
||||||
const {ready, connected, disconnected} = props;
|
const {ready, connected, disconnected} = props;
|
||||||
|
|||||||
6
docs/dist/tonal.js
vendored
6
docs/dist/tonal.js
vendored
@ -25,11 +25,11 @@ function scaleTranspose(scale, offset, note) {
|
|||||||
while (Math.abs(i - noteIndex) < Math.abs(offset)) {
|
while (Math.abs(i - noteIndex) < Math.abs(offset)) {
|
||||||
i += direction;
|
i += direction;
|
||||||
const index = mod(i, notes.length);
|
const index = mod(i, notes.length);
|
||||||
if (direction < 0 && n === "C") {
|
if (direction < 0 && n[0] === "C") {
|
||||||
o += direction;
|
o += direction;
|
||||||
}
|
}
|
||||||
n = notes[index];
|
n = notes[index];
|
||||||
if (direction > 0 && n === "C") {
|
if (direction > 0 && n[0] === "C") {
|
||||||
o += direction;
|
o += direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ Pattern.prototype._transpose = function(intervalOrSemitones) {
|
|||||||
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
|
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
|
||||||
return event.withValue(() => event.value + semitones);
|
return event.withValue(() => event.value + semitones);
|
||||||
}
|
}
|
||||||
return event.withValue(() => Note.transpose(event.value, interval));
|
return event.withValue(() => Note.simplify(Note.transpose(event.value, interval)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype._scaleTranspose = function(offset) {
|
Pattern.prototype._scaleTranspose = function(offset) {
|
||||||
|
|||||||
48
docs/dist/tone.js
vendored
48
docs/dist/tone.js
vendored
@ -15,22 +15,40 @@ import {
|
|||||||
NoiseSynth,
|
NoiseSynth,
|
||||||
PluckSynth,
|
PluckSynth,
|
||||||
Sampler,
|
Sampler,
|
||||||
getDestination
|
getDestination,
|
||||||
|
Players
|
||||||
} from "../_snowpack/pkg/tone.js";
|
} from "../_snowpack/pkg/tone.js";
|
||||||
import {Piano} from "../_snowpack/pkg/@tonejs/piano.js";
|
import {Piano} from "../_snowpack/pkg/@tonejs/piano.js";
|
||||||
const Pattern = _Pattern;
|
const Pattern = _Pattern;
|
||||||
Pattern.prototype.tone = function(instrument) {
|
Pattern.prototype.tone = function(instrument) {
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
const onTrigger = (time, event2) => {
|
const onTrigger = (time, event2) => {
|
||||||
if (instrument.constructor.name === "PluckSynth") {
|
let note = event2.value;
|
||||||
instrument.triggerAttack(event2.value, time);
|
let velocity = event2.context?.velocity ?? 0.75;
|
||||||
} else if (instrument.constructor.name === "NoiseSynth") {
|
switch (instrument.constructor.name) {
|
||||||
instrument.triggerAttackRelease(event2.duration, time);
|
case "PluckSynth":
|
||||||
} else if (instrument.constructor.name === "Piano") {
|
instrument.triggerAttack(note, time);
|
||||||
instrument.keyDown({note: event2.value, time, velocity: 0.5});
|
break;
|
||||||
instrument.keyUp({note: event2.value, time: time + event2.duration});
|
case "NoiseSynth":
|
||||||
} else {
|
instrument.triggerAttackRelease(event2.duration, time);
|
||||||
instrument.triggerAttackRelease(event2.value, event2.duration, time);
|
break;
|
||||||
|
case "Piano":
|
||||||
|
instrument.keyDown({note, time, velocity: 0.5});
|
||||||
|
instrument.keyUp({note, time: time + event2.duration, velocity});
|
||||||
|
break;
|
||||||
|
case "Sampler":
|
||||||
|
instrument.triggerAttackRelease(note, event2.duration, time, velocity);
|
||||||
|
break;
|
||||||
|
case "Players":
|
||||||
|
if (!instrument.has(event2.value)) {
|
||||||
|
throw new Error(`name "${event2.value}" not defined for players`);
|
||||||
|
}
|
||||||
|
const player = instrument.player(event2.value);
|
||||||
|
player.start(time);
|
||||||
|
player.stop(time + event2.duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
instrument.triggerAttackRelease(note, event2.duration, time, velocity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return event.setContext({...event.context, instrument, onTrigger});
|
return event.setContext({...event.context, instrument, onTrigger});
|
||||||
@ -46,7 +64,15 @@ export const monosynth = (options) => new MonoSynth(options);
|
|||||||
export const noise = (options) => new NoiseSynth(options);
|
export const noise = (options) => new NoiseSynth(options);
|
||||||
export const pluck = (options) => new PluckSynth(options);
|
export const pluck = (options) => new PluckSynth(options);
|
||||||
export const polysynth = (options) => new PolySynth(options);
|
export const polysynth = (options) => new PolySynth(options);
|
||||||
export const sampler = (options) => new Sampler(options);
|
export const sampler = (options, baseUrl) => new Promise((resolve) => {
|
||||||
|
const s = new Sampler(options, () => resolve(s), baseUrl);
|
||||||
|
});
|
||||||
|
export const players = (options, baseUrl = "") => {
|
||||||
|
options = !baseUrl ? options : Object.fromEntries(Object.entries(options).map(([key, value]) => [key, baseUrl + value]));
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const s = new Players(options, () => resolve(s));
|
||||||
|
});
|
||||||
|
};
|
||||||
export const synth = (options) => new Synth(options);
|
export const synth = (options) => new Synth(options);
|
||||||
export const piano = async (options = {velocities: 1}) => {
|
export const piano = async (options = {velocities: 1}) => {
|
||||||
const p = new Piano(options);
|
const p = new Piano(options);
|
||||||
|
|||||||
55
docs/dist/tunes.js
vendored
55
docs/dist/tunes.js
vendored
@ -434,3 +434,58 @@ export const barryHarris = `piano()
|
|||||||
.slow(2)
|
.slow(2)
|
||||||
.tone(p.toDestination()))
|
.tone(p.toDestination()))
|
||||||
`;
|
`;
|
||||||
|
export const blippyRhodes = `Promise.all([
|
||||||
|
players({
|
||||||
|
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
||||||
|
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
||||||
|
hh: 'samples/tidal/hh/000_hh3closedhh.wav'
|
||||||
|
}, 'https://loophole-letters.vercel.app/'),
|
||||||
|
sampler({
|
||||||
|
E1: 'samples/rhodes/MK2Md2000.mp3',
|
||||||
|
E2: 'samples/rhodes/MK2Md2012.mp3',
|
||||||
|
E3: 'samples/rhodes/MK2Md2024.mp3',
|
||||||
|
E4: 'samples/rhodes/MK2Md2036.mp3',
|
||||||
|
E5: 'samples/rhodes/MK2Md2048.mp3',
|
||||||
|
E6: 'samples/rhodes/MK2Md2060.mp3',
|
||||||
|
E7: 'samples/rhodes/MK2Md2072.mp3'
|
||||||
|
}, 'https://loophole-letters.vercel.app/')
|
||||||
|
])
|
||||||
|
.then(([drums, rhodes])=>{
|
||||||
|
const delay = new FeedbackDelay(1/12, .4).chain(vol(0.3), out());
|
||||||
|
rhodes = rhodes.chain(vol(0.5).connect(delay), out());
|
||||||
|
const bass = synth(osc('sawtooth8')).chain(vol(.5),out());
|
||||||
|
const scales = ['C major', 'C mixolydian', 'F lydian', ['F minor',slowcat('Db major','Db mixolydian')]];
|
||||||
|
const t = x => x.scale(sequence(...scales).slow(4));
|
||||||
|
return stack(
|
||||||
|
"<bd sn> <hh hh*2 hh*3>".tone(drums.chain(out())),
|
||||||
|
"<g4 c5 a4 [ab4 <eb5 f5>]>".apply(t).struct("x*8").apply(scaleTranspose("0 [-5,-2] -7 [-9,-2]")).legato(.2).slow(2).tone(rhodes),
|
||||||
|
//"<C^7 C7 F^7 [Fm7 <Db^7 Db7>]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes),
|
||||||
|
"<c2 c3 f2 [[F2 C2] db2]>".legato("<1@3 [.3 1]>").slow(2).tone(bass),
|
||||||
|
).fast(3/2)
|
||||||
|
})`;
|
||||||
|
export const wavyKalimba = `sampler({
|
||||||
|
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
|
||||||
|
}).then((kalimba)=>{
|
||||||
|
const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out());
|
||||||
|
kalimba = kalimba.chain(vol(0.6).connect(delay),out());
|
||||||
|
const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4);
|
||||||
|
return stack(
|
||||||
|
"[0 2 4 6 9 2 0 -2]*3"
|
||||||
|
.add("<0 2>/4")
|
||||||
|
.scale(scales)
|
||||||
|
.struct("x*8")
|
||||||
|
.velocity("<.8 .3 .6>*8")
|
||||||
|
.slow(2)
|
||||||
|
.tone(kalimba),
|
||||||
|
"<c2 c2 f2 [[F2 C2] db2]>"
|
||||||
|
.scale(scales)
|
||||||
|
.scaleTranspose("[0 <2 4>]*2")
|
||||||
|
.struct("x*4")
|
||||||
|
.velocity("<.8 .5>*4")
|
||||||
|
.velocity(0.8)
|
||||||
|
.slow(2)
|
||||||
|
.tone(kalimba)
|
||||||
|
)
|
||||||
|
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
|
||||||
|
.fast(1)
|
||||||
|
})`;
|
||||||
|
|||||||
4
docs/dist/useRepl.js
vendored
4
docs/dist/useRepl.js
vendored
@ -52,14 +52,14 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
onEvent: useCallback((time, event) => {
|
onEvent: useCallback((time, event) => {
|
||||||
try {
|
try {
|
||||||
onEvent?.(event);
|
onEvent?.(event);
|
||||||
const {onTrigger} = event.context;
|
const {onTrigger, velocity} = event.context;
|
||||||
if (!onTrigger) {
|
if (!onTrigger) {
|
||||||
const note = event.value;
|
const note = event.value;
|
||||||
if (!isNote(note)) {
|
if (!isNote(note)) {
|
||||||
throw new Error("not a note: " + note);
|
throw new Error("not a note: " + note);
|
||||||
}
|
}
|
||||||
if (defaultSynth) {
|
if (defaultSynth) {
|
||||||
defaultSynth.triggerAttackRelease(note, event.duration, time);
|
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("no defaultSynth passed to useRepl.");
|
throw new Error("no defaultSynth passed to useRepl.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7531,16 +7531,19 @@ For sharp notes, the letter "s" is used instead of "#", because JavaScript does
|
|||||||
}), /*#__PURE__*/ _react1.mdx("h3", null, `late(cycles)`), /*#__PURE__*/ _react1.mdx("p", null, `Like early, but in the other direction:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `late(cycles)`), /*#__PURE__*/ _react1.mdx("p", null, `Like early, but in the other direction:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
tune: `cat(e5, b4.late(0.5))`,
|
tune: `cat(e5, b4.late(0.5))`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("p", null, `TODO: shouldn't it sound different?`), /*#__PURE__*/ _react1.mdx("h3", null, `rev()`), /*#__PURE__*/ _react1.mdx("p", null, `Will reverse the pattern:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `rev()`), /*#__PURE__*/ _react1.mdx("p", null, `Will reverse the pattern:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
tune: `cat(c3,d3,e3,f3).rev()`,
|
tune: `cat(c3,d3,e3,f3).rev()`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("h3", null, `every(n, func)`), /*#__PURE__*/ _react1.mdx("p", null, `Will apply the given function every n cycles:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `every(n, func)`), /*#__PURE__*/ _react1.mdx("p", null, `Will apply the given function every n cycles:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
tune: `cat(e5, pure(b4).every(4, late(0.5)))`,
|
tune: `cat(e5, pure(b4).every(4, late(0.5)))`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("p", null, `TODO: should be able to do b4.every => like already possible with fast slow etc..`), /*#__PURE__*/ _react1.mdx("p", null, `Note that late is called directly. This is a shortcut for:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("p", null, `Note that late is called directly. This is a shortcut for:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
tune: `cat(e5, pure(b4).every(4, x => x.late(0.5)))`,
|
tune: `cat(e5, pure(b4).every(4, x => x.late(0.5)))`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("p", null, `TODO: should the function really run the first cycle?`), /*#__PURE__*/ _react1.mdx("h3", null, `Functions not documented yet`), /*#__PURE__*/ _react1.mdx("ul", null, /*#__PURE__*/ _react1.mdx("li", {
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `add(n)`), /*#__PURE__*/ _react1.mdx("p", null, `Adds the given number to each item in the pattern:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
|
tune: `stack(0, 2, 4).add(slowcat(0, -2, -4, -5)).scale('C minor')`,
|
||||||
|
mdxType: "MiniRepl"
|
||||||
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `Functions not documented yet`), /*#__PURE__*/ _react1.mdx("ul", null, /*#__PURE__*/ _react1.mdx("li", {
|
||||||
parentName: "ul"
|
parentName: "ul"
|
||||||
}, `add`), /*#__PURE__*/ _react1.mdx("li", {
|
}, `add`), /*#__PURE__*/ _react1.mdx("li", {
|
||||||
parentName: "ul"
|
parentName: "ul"
|
||||||
@ -7681,7 +7684,7 @@ It would be great to get this to work without glitches though, because it is fun
|
|||||||
}), /*#__PURE__*/ _react1.mdx("h3", null, `voicings(range?)`), /*#__PURE__*/ _react1.mdx("p", null, `Turns chord symbols into voicings, using the smoothest voice leading possible:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `voicings(range?)`), /*#__PURE__*/ _react1.mdx("p", null, `Turns chord symbols into voicings, using the smoothest voice leading possible:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
tune: `stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")`,
|
tune: `stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("p", null, `TODO: use voicing collection as first param + patternify.`), /*#__PURE__*/ _react1.mdx("h3", null, `rootNotes(octave = 2)`), /*#__PURE__*/ _react1.mdx("p", null, `Turns chord symbols into root notes of chords in given octave.`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("h3", null, `rootNotes(octave = 2)`), /*#__PURE__*/ _react1.mdx("p", null, `Turns chord symbols into root notes of chords in given octave.`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
tune: `"<C^7 A7b13 Dm7 G7>".rootNotes(3)`,
|
tune: `"<C^7 A7b13 Dm7 G7>".rootNotes(3)`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("p", null, `Together with edit, struct and voicings, this can be used to create a basic backing track:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
}), /*#__PURE__*/ _react1.mdx("p", null, `Together with edit, struct and voicings, this can be used to create a basic backing track:`), /*#__PURE__*/ _react1.mdx(_miniReplDefault.default, {
|
||||||
@ -7690,8 +7693,7 @@ It would be great to get this to work without glitches though, because it is fun
|
|||||||
x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out()))
|
x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out()))
|
||||||
)`,
|
)`,
|
||||||
mdxType: "MiniRepl"
|
mdxType: "MiniRepl"
|
||||||
}), /*#__PURE__*/ _react1.mdx("p", null, `TODO: use range instead of octave.
|
}), /*#__PURE__*/ _react1.mdx("h2", null, `MIDI API`), /*#__PURE__*/ _react1.mdx("p", null, `Strudel also supports midi via `, /*#__PURE__*/ _react1.mdx("a", {
|
||||||
TODO: find out why composition does not work`), /*#__PURE__*/ _react1.mdx("h2", null, `MIDI API`), /*#__PURE__*/ _react1.mdx("p", null, `Strudel also supports midi via `, /*#__PURE__*/ _react1.mdx("a", {
|
|
||||||
parentName: "p",
|
parentName: "p",
|
||||||
"href": "https://npmjs.com/package/webmidi"
|
"href": "https://npmjs.com/package/webmidi"
|
||||||
}, `webmidi`), `.`), /*#__PURE__*/ _react1.mdx("h3", null, `midi(outputName?)`), /*#__PURE__*/ _react1.mdx("p", null, `Make sure to have a midi device connected or to use an IAC Driver.
|
}, `webmidi`), `.`), /*#__PURE__*/ _react1.mdx("h3", null, `midi(outputName?)`), /*#__PURE__*/ _react1.mdx("p", null, `Make sure to have a midi device connected or to use an IAC Driver.
|
||||||
@ -7944,10 +7946,7 @@ const defaultSynth = new _tone.PolySynth().chain(new _tone.Gain(0.5), _tone.Dest
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// "balanced" | "interactive" | "playback";
|
// "balanced" | "interactive" | "playback";
|
||||||
_tone.setContext(new _tone.Context({
|
// Tone.setContext(new Tone.Context({ latencyHint: 'playback', lookAhead: 1 }));
|
||||||
latencyHint: 'playback',
|
|
||||||
lookAhead: 1
|
|
||||||
}));
|
|
||||||
function MiniRepl({ tune , maxHeight =500 }) {
|
function MiniRepl({ tune , maxHeight =500 }) {
|
||||||
const [editor, setEditor] = _react.useState();
|
const [editor, setEditor] = _react.useState();
|
||||||
const { code , setCode , activateCode , activeCode , setPattern , error , cycle , dirty , log , togglePlay , hash } = _useReplDefault.default({
|
const { code , setCode , activateCode , activeCode , setPattern , error , cycle , dirty , log , togglePlay , hash } = _useReplDefault.default({
|
||||||
@ -41710,11 +41709,11 @@ function useRepl({ tune , defaultSynth , autolink =true , onEvent , onDraw }) {
|
|||||||
onEvent: _react.useCallback((time, event)=>{
|
onEvent: _react.useCallback((time, event)=>{
|
||||||
try {
|
try {
|
||||||
onEvent?.(event);
|
onEvent?.(event);
|
||||||
const { onTrigger } = event.context;
|
const { onTrigger , velocity } = event.context;
|
||||||
if (!onTrigger) {
|
if (!onTrigger) {
|
||||||
const note = event.value;
|
const note = event.value;
|
||||||
if (!_tone.isNote(note)) throw new Error('not a note: ' + note);
|
if (!_tone.isNote(note)) throw new Error('not a note: ' + note);
|
||||||
if (defaultSynth) defaultSynth.triggerAttackRelease(note, event.duration, time);
|
if (defaultSynth) defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
else throw new Error('no defaultSynth passed to useRepl.');
|
else throw new Error('no defaultSynth passed to useRepl.');
|
||||||
/* console.warn('no instrument chosen', event);
|
/* console.warn('no instrument chosen', event);
|
||||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */ } else onTrigger(time, event);
|
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */ } else onTrigger(time, event);
|
||||||
@ -42117,7 +42116,7 @@ class Hap {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.stateful = stateful;
|
this.stateful = stateful;
|
||||||
if (stateful) assert(typeof this.value === "function", "Stateful values must be functions");
|
if (stateful) console.assert(typeof this.value === "function", "Stateful values must be functions");
|
||||||
}
|
}
|
||||||
withSpan(func) {
|
withSpan(func) {
|
||||||
// Returns a new event with the function f applies to the event timespan.
|
// Returns a new event with the function f applies to the event timespan.
|
||||||
@ -42135,11 +42134,12 @@ class Hap {
|
|||||||
}
|
}
|
||||||
resolveState(state) {
|
resolveState(state) {
|
||||||
if (this.stateful && this.hasOnset()) {
|
if (this.stateful && this.hasOnset()) {
|
||||||
const func = this.value[newValue] = func(state);
|
console.log("stateful");
|
||||||
|
const func = this.value;
|
||||||
|
const [newState, newValue] = func(state);
|
||||||
return [
|
return [
|
||||||
newState,
|
newState,
|
||||||
this.withValue(()=>newValue
|
new Hap(this.whole, this.part, newValue, this.context, false)
|
||||||
)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
@ -42631,6 +42631,23 @@ class Pattern {
|
|||||||
hush() {
|
hush() {
|
||||||
return silence;
|
return silence;
|
||||||
}
|
}
|
||||||
|
// sets absolute duration of events
|
||||||
|
_duration(value) {
|
||||||
|
return this.withEventSpan((span)=>new TimeSpan(span.begin, span.begin.add(value))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// sets event relative duration of events
|
||||||
|
_legato(value) {
|
||||||
|
return this.withEventSpan((span)=>new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_velocity(velocity) {
|
||||||
|
return this._withContext((context)=>({
|
||||||
|
...context,
|
||||||
|
velocity: (context.velocity || 1) * velocity
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// methods of Pattern that get callable factories
|
// methods of Pattern that get callable factories
|
||||||
Pattern.prototype.patternified = [
|
Pattern.prototype.patternified = [
|
||||||
@ -42638,7 +42655,10 @@ Pattern.prototype.patternified = [
|
|||||||
'fast',
|
'fast',
|
||||||
'slow',
|
'slow',
|
||||||
'early',
|
'early',
|
||||||
'late'
|
'late',
|
||||||
|
'duration',
|
||||||
|
'legato',
|
||||||
|
'velocity'
|
||||||
];
|
];
|
||||||
// methods that create patterns, which are added to patternified Pattern methods
|
// methods that create patterns, which are added to patternified Pattern methods
|
||||||
Pattern.prototype.factories = {
|
Pattern.prototype.factories = {
|
||||||
@ -56533,6 +56553,8 @@ parcelHelpers.export(exports, "polysynth", ()=>polysynth
|
|||||||
);
|
);
|
||||||
parcelHelpers.export(exports, "sampler", ()=>sampler
|
parcelHelpers.export(exports, "sampler", ()=>sampler
|
||||||
);
|
);
|
||||||
|
parcelHelpers.export(exports, "players", ()=>players
|
||||||
|
);
|
||||||
parcelHelpers.export(exports, "synth", ()=>synth
|
parcelHelpers.export(exports, "synth", ()=>synth
|
||||||
);
|
);
|
||||||
parcelHelpers.export(exports, "piano", ()=>piano
|
parcelHelpers.export(exports, "piano", ()=>piano
|
||||||
@ -56568,19 +56590,45 @@ Pattern.prototype.tone = function(instrument) {
|
|||||||
// instrument.toDestination();
|
// instrument.toDestination();
|
||||||
return this._withEvent((event1)=>{
|
return this._withEvent((event1)=>{
|
||||||
const onTrigger = (time, event)=>{
|
const onTrigger = (time, event)=>{
|
||||||
if (instrument.constructor.name === 'PluckSynth') instrument.triggerAttack(event.value, time);
|
let note = event.value;
|
||||||
else if (instrument.constructor.name === 'NoiseSynth') instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
let velocity = event.context?.velocity ?? 0.75;
|
||||||
else if (instrument.constructor.name === 'Piano') {
|
switch(instrument.constructor.name){
|
||||||
instrument.keyDown({
|
case 'PluckSynth':
|
||||||
note: event.value,
|
// note = getPlayableNoteValue(event);
|
||||||
time,
|
// velocity?
|
||||||
velocity: 0.5
|
instrument.triggerAttack(note, time);
|
||||||
});
|
break;
|
||||||
instrument.keyUp({
|
case 'NoiseSynth':
|
||||||
note: event.value,
|
instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
||||||
time: time + event.duration
|
break;
|
||||||
});
|
case 'Piano':
|
||||||
} else instrument.triggerAttackRelease(event.value, event.duration, time);
|
// note = getPlayableNoteValue(event);
|
||||||
|
instrument.keyDown({
|
||||||
|
note,
|
||||||
|
time,
|
||||||
|
velocity: 0.5
|
||||||
|
});
|
||||||
|
instrument.keyUp({
|
||||||
|
note,
|
||||||
|
time: time + event.duration,
|
||||||
|
velocity
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'Sampler':
|
||||||
|
// note = getPlayableNoteValue(event);
|
||||||
|
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
|
break;
|
||||||
|
case 'Players':
|
||||||
|
if (!instrument.has(event.value)) throw new Error(`name "${event.value}" not defined for players`);
|
||||||
|
const player = instrument.player(event.value);
|
||||||
|
// velocity ?
|
||||||
|
player.start(time);
|
||||||
|
player.stop(time + event.duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// note = getPlayableNoteValue(event);
|
||||||
|
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return event1.setContext({
|
return event1.setContext({
|
||||||
...event1.context,
|
...event1.context,
|
||||||
@ -56612,8 +56660,22 @@ const pluck = (options)=>new _tone.PluckSynth(options)
|
|||||||
;
|
;
|
||||||
const polysynth = (options)=>new _tone.PolySynth(options)
|
const polysynth = (options)=>new _tone.PolySynth(options)
|
||||||
;
|
;
|
||||||
const sampler = (options)=>new _tone.Sampler(options)
|
const sampler = (options, baseUrl)=>new Promise((resolve)=>{
|
||||||
|
const s = new _tone.Sampler(options, ()=>resolve(s)
|
||||||
|
, baseUrl);
|
||||||
|
})
|
||||||
;
|
;
|
||||||
|
const players = (options, baseUrl = '')=>{
|
||||||
|
options = !baseUrl ? options : Object.fromEntries(Object.entries(options).map(([key, value])=>[
|
||||||
|
key,
|
||||||
|
baseUrl + value
|
||||||
|
]
|
||||||
|
));
|
||||||
|
return new Promise((resolve)=>{
|
||||||
|
const s = new _tone.Players(options, ()=>resolve(s)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
const synth = (options)=>new _tone.Synth(options)
|
const synth = (options)=>new _tone.Synth(options)
|
||||||
;
|
;
|
||||||
const piano = async (options = {
|
const piano = async (options = {
|
||||||
@ -59311,30 +59373,32 @@ const outputByName = (name)=>WebMidi.getOutputByName(name)
|
|||||||
;
|
;
|
||||||
Pattern.prototype.midi = function(output, channel = 1) {
|
Pattern.prototype.midi = function(output, channel = 1) {
|
||||||
if (output?.constructor?.name === 'Pattern') throw new Error(`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'}')`);
|
if (output?.constructor?.name === 'Pattern') throw new Error(`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'}')`);
|
||||||
return this.fmap((value)=>({
|
return this._withEvent((event1)=>{
|
||||||
...value,
|
const onTrigger = (time, event)=>{
|
||||||
onTrigger: (time, event)=>{
|
let note = event.value;
|
||||||
value = value.value || value;
|
const velocity = event.context?.velocity ?? 0.9;
|
||||||
if (!_tone.isNote(value)) throw new Error('not a note: ' + value);
|
if (!_tone.isNote(note)) throw new Error('not a note: ' + note);
|
||||||
if (!WebMidi.enabled) throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
if (!WebMidi.enabled) throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||||
if (!WebMidi.outputs.length) throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
if (!WebMidi.outputs.length) throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
||||||
const device = output ? outputByName(output) : WebMidi.outputs[0];
|
const device = output ? outputByName(output) : WebMidi.outputs[0];
|
||||||
if (!device) throw new Error(`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs.map((o)=>`'${o.name}'`
|
if (!device) throw new Error(`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs.map((o)=>`'${o.name}'`
|
||||||
).join(' | ')}`);
|
).join(' | ')}`);
|
||||||
// console.log('midi', value, output);
|
// console.log('midi', value, output);
|
||||||
const timingOffset = WebMidi.time - _tone.context.currentTime * 1000;
|
const timingOffset = WebMidi.time - _tone.context.currentTime * 1000;
|
||||||
time = time * 1000 + timingOffset;
|
time = time * 1000 + timingOffset;
|
||||||
// const inMs = '+' + (time - Tone.context.currentTime) * 1000;
|
// const inMs = '+' + (time - Tone.context.currentTime) * 1000;
|
||||||
// await enableWebMidi()
|
// await enableWebMidi()
|
||||||
device.playNote(value, channel, {
|
device.playNote(note, channel, {
|
||||||
time,
|
time,
|
||||||
duration: event.duration * 1000 - 5,
|
duration: event.duration * 1000 - 5,
|
||||||
// velocity: velocity ?? 0.5,
|
velocity
|
||||||
velocity: 0.9
|
});
|
||||||
});
|
};
|
||||||
}
|
return event1.setContext({
|
||||||
})
|
...event1.context,
|
||||||
);
|
onTrigger
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
function useWebMidi(props) {
|
function useWebMidi(props) {
|
||||||
const { ready , connected , disconnected } = props;
|
const { ready , connected , disconnected } = props;
|
||||||
@ -64067,9 +64131,9 @@ function scaleTranspose(scale, offset, note1) {
|
|||||||
while(Math.abs(i - noteIndex) < Math.abs(offset)){
|
while(Math.abs(i - noteIndex) < Math.abs(offset)){
|
||||||
i += direction;
|
i += direction;
|
||||||
const index = mod(i, notes.length);
|
const index = mod(i, notes.length);
|
||||||
if (direction < 0 && n === 'C') o += direction;
|
if (direction < 0 && n[0] === 'C') o += direction;
|
||||||
n = notes[index];
|
n = notes[index];
|
||||||
if (direction > 0 && n === 'C') o += direction;
|
if (direction > 0 && n[0] === 'C') o += direction;
|
||||||
}
|
}
|
||||||
return n + o;
|
return n + o;
|
||||||
}
|
}
|
||||||
@ -64081,7 +64145,9 @@ Pattern.prototype._transpose = function(intervalOrSemitones) {
|
|||||||
return event.withValue(()=>event.value + semitones
|
return event.withValue(()=>event.value + semitones
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return event.withValue(()=>_tonal.Note.transpose(event.value, interval)
|
// TODO: move simplify to player to preserve enharmonics
|
||||||
|
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
|
||||||
|
return event.withValue(()=>_tonal.Note.simplify(_tonal.Note.transpose(event.value, interval))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -110892,4 +110958,4 @@ exports.default = cx;
|
|||||||
|
|
||||||
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["3uVTb"], "3uVTb", "parcelRequire94c2")
|
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["3uVTb"], "3uVTb", "parcelRequire94c2")
|
||||||
|
|
||||||
//# sourceMappingURL=index.26c147ee.js.map
|
//# sourceMappingURL=index.23fc2d31.js.map
|
||||||
File diff suppressed because one or more lines are too long
@ -11,6 +11,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<script src="/tutorial/index.26c147ee.js" defer=""></script>
|
<script src="/tutorial/index.23fc2d31.js" defer=""></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default {
|
|||||||
},
|
},
|
||||||
packageOptions: {
|
packageOptions: {
|
||||||
/* ... */
|
/* ... */
|
||||||
knownEntrypoints: ['fraction.js', 'codemirror'],
|
knownEntrypoints: ['fraction.js', 'codemirror', 'shift-ast', 'ramda'],
|
||||||
},
|
},
|
||||||
devOptions: {
|
devOptions: {
|
||||||
tailwindConfig: './tailwind.config.js',
|
tailwindConfig: './tailwind.config.js',
|
||||||
|
|||||||
@ -32,12 +32,12 @@ Pattern.prototype.midi = function (output: string, channel = 1) {
|
|||||||
}')`
|
}')`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.fmap((value: any) => ({
|
return this._withEvent((event: any) => {
|
||||||
...value,
|
const onTrigger = (time: number, event: any) => {
|
||||||
onTrigger: (time: number, event: any) => {
|
let note = event.value;
|
||||||
value = value.value || value;
|
const velocity = event.context?.velocity ?? 0.9;
|
||||||
if (!isNote(value)) {
|
if (!isNote(note)) {
|
||||||
throw new Error('not a note: ' + value);
|
throw new Error('not a note: ' + note);
|
||||||
}
|
}
|
||||||
if (!WebMidi.enabled) {
|
if (!WebMidi.enabled) {
|
||||||
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||||
@ -58,14 +58,14 @@ Pattern.prototype.midi = function (output: string, channel = 1) {
|
|||||||
time = time * 1000 + timingOffset;
|
time = time * 1000 + timingOffset;
|
||||||
// const inMs = '+' + (time - Tone.context.currentTime) * 1000;
|
// const inMs = '+' + (time - Tone.context.currentTime) * 1000;
|
||||||
// await enableWebMidi()
|
// await enableWebMidi()
|
||||||
device.playNote(value, channel, {
|
device.playNote(note, channel, {
|
||||||
time,
|
time,
|
||||||
duration: event.duration * 1000 - 5,
|
duration: event.duration * 1000 - 5,
|
||||||
// velocity: velocity ?? 0.5,
|
velocity,
|
||||||
velocity: 0.9,
|
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
}));
|
return event.setContext({ ...event.context, onTrigger });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useWebMidi(props?: any) {
|
export function useWebMidi(props?: any) {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
PluckSynth,
|
PluckSynth,
|
||||||
Sampler,
|
Sampler,
|
||||||
getDestination,
|
getDestination,
|
||||||
|
Players,
|
||||||
} from 'tone';
|
} from 'tone';
|
||||||
import { Piano } from '@tonejs/piano';
|
import { Piano } from '@tonejs/piano';
|
||||||
import { getPlayableNoteValue } from '../../util.mjs';
|
import { getPlayableNoteValue } from '../../util.mjs';
|
||||||
@ -31,17 +32,37 @@ Pattern.prototype.tone = function (instrument) {
|
|||||||
// instrument.toDestination();
|
// instrument.toDestination();
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
const onTrigger = (time, event) => {
|
const onTrigger = (time, event) => {
|
||||||
const note = getPlayableNoteValue(event);
|
let note;
|
||||||
// TODO: test if all tonejs instruments can handle freqs
|
let velocity = event.context?.velocity ?? 0.75;
|
||||||
if (instrument.constructor.name === 'PluckSynth') {
|
switch (instrument.constructor.name) {
|
||||||
instrument.triggerAttack(note, time);
|
case 'PluckSynth':
|
||||||
} else if (instrument.constructor.name === 'NoiseSynth') {
|
note = getPlayableNoteValue(event);
|
||||||
instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
instrument.triggerAttack(note, time);
|
||||||
} else if (instrument.constructor.name === 'Piano') {
|
break;
|
||||||
instrument.keyDown({ note, time, velocity: 0.5 });
|
case 'NoiseSynth':
|
||||||
instrument.keyUp({ note, time: time + event.duration });
|
instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
||||||
} else {
|
break;
|
||||||
instrument.triggerAttackRelease(event.value, event.duration, time);
|
case 'Piano':
|
||||||
|
note = getPlayableNoteValue(event);
|
||||||
|
instrument.keyDown({ note, time, velocity: 0.5 });
|
||||||
|
instrument.keyUp({ note, time: time + event.duration, velocity });
|
||||||
|
break;
|
||||||
|
case 'Sampler':
|
||||||
|
note = getPlayableNoteValue(event);
|
||||||
|
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
|
break;
|
||||||
|
case 'Players':
|
||||||
|
if (!instrument.has(event.value)) {
|
||||||
|
throw new Error(`name "${event.value}" not defined for players`);
|
||||||
|
}
|
||||||
|
const player = instrument.player(event.value);
|
||||||
|
// velocity ?
|
||||||
|
player.start(time);
|
||||||
|
player.stop(time + event.duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
note = getPlayableNoteValue(event);
|
||||||
|
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return event.setContext({ ...event.context, instrument, onTrigger });
|
return event.setContext({ ...event.context, instrument, onTrigger });
|
||||||
@ -60,7 +81,18 @@ export const monosynth = (options) => new MonoSynth(options);
|
|||||||
export const noise = (options) => new NoiseSynth(options);
|
export const noise = (options) => new NoiseSynth(options);
|
||||||
export const pluck = (options) => new PluckSynth(options);
|
export const pluck = (options) => new PluckSynth(options);
|
||||||
export const polysynth = (options) => new PolySynth(options);
|
export const polysynth = (options) => new PolySynth(options);
|
||||||
export const sampler = (options) => new Sampler(options);
|
export const sampler = (options, baseUrl) =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const s = new Sampler(options, () => resolve(s), baseUrl);
|
||||||
|
});
|
||||||
|
export const players = (options, baseUrl = '') => {
|
||||||
|
options = !baseUrl
|
||||||
|
? options
|
||||||
|
: Object.fromEntries(Object.entries(options).map(([key, value]: any) => [key, baseUrl + value]));
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const s = new Players(options, () => resolve(s));
|
||||||
|
});
|
||||||
|
};
|
||||||
export const synth = (options) => new Synth(options);
|
export const synth = (options) => new Synth(options);
|
||||||
export const piano = async (options = { velocities: 1 }) => {
|
export const piano = async (options = { velocities: 1 }) => {
|
||||||
const p = new Piano(options);
|
const p = new Piano(options);
|
||||||
|
|||||||
@ -459,6 +459,63 @@ export const barryHarris = `piano()
|
|||||||
.tone(p.toDestination()))
|
.tone(p.toDestination()))
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const blippyRhodes = `Promise.all([
|
||||||
|
players({
|
||||||
|
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
||||||
|
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
||||||
|
hh: 'samples/tidal/hh/000_hh3closedhh.wav'
|
||||||
|
}, 'https://loophole-letters.vercel.app/'),
|
||||||
|
sampler({
|
||||||
|
E1: 'samples/rhodes/MK2Md2000.mp3',
|
||||||
|
E2: 'samples/rhodes/MK2Md2012.mp3',
|
||||||
|
E3: 'samples/rhodes/MK2Md2024.mp3',
|
||||||
|
E4: 'samples/rhodes/MK2Md2036.mp3',
|
||||||
|
E5: 'samples/rhodes/MK2Md2048.mp3',
|
||||||
|
E6: 'samples/rhodes/MK2Md2060.mp3',
|
||||||
|
E7: 'samples/rhodes/MK2Md2072.mp3'
|
||||||
|
}, 'https://loophole-letters.vercel.app/')
|
||||||
|
])
|
||||||
|
.then(([drums, rhodes])=>{
|
||||||
|
const delay = new FeedbackDelay(1/12, .4).chain(vol(0.3), out());
|
||||||
|
rhodes = rhodes.chain(vol(0.5).connect(delay), out());
|
||||||
|
const bass = synth(osc('sawtooth8')).chain(vol(.5),out());
|
||||||
|
const scales = ['C major', 'C mixolydian', 'F lydian', ['F minor',slowcat('Db major','Db mixolydian')]];
|
||||||
|
const t = x => x.scale(sequence(...scales).slow(4));
|
||||||
|
return stack(
|
||||||
|
"<bd sn> <hh hh*2 hh*3>".tone(drums.chain(out())),
|
||||||
|
"<g4 c5 a4 [ab4 <eb5 f5>]>".apply(t).struct("x*8").apply(scaleTranspose("0 [-5,-2] -7 [-9,-2]")).legato(.2).slow(2).tone(rhodes),
|
||||||
|
//"<C^7 C7 F^7 [Fm7 <Db^7 Db7>]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes),
|
||||||
|
"<c2 c3 f2 [[F2 C2] db2]>".legato("<1@3 [.3 1]>").slow(2).tone(bass),
|
||||||
|
).fast(3/2)
|
||||||
|
})`;
|
||||||
|
|
||||||
|
export const wavyKalimba = `sampler({
|
||||||
|
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
|
||||||
|
}).then((kalimba)=>{
|
||||||
|
const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out());
|
||||||
|
kalimba = kalimba.chain(vol(0.6).connect(delay),out());
|
||||||
|
const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4);
|
||||||
|
return stack(
|
||||||
|
"[0 2 4 6 9 2 0 -2]*3"
|
||||||
|
.add("<0 2>/4")
|
||||||
|
.scale(scales)
|
||||||
|
.struct("x*8")
|
||||||
|
.velocity("<.8 .3 .6>*8")
|
||||||
|
.slow(2)
|
||||||
|
.tone(kalimba),
|
||||||
|
"<c2 c2 f2 [[F2 C2] db2]>"
|
||||||
|
.scale(scales)
|
||||||
|
.scaleTranspose("[0 <2 4>]*2")
|
||||||
|
.struct("x*4")
|
||||||
|
.velocity("<.8 .5>*4")
|
||||||
|
.velocity(0.8)
|
||||||
|
.slow(2)
|
||||||
|
.tone(kalimba)
|
||||||
|
)
|
||||||
|
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
|
||||||
|
.fast(1)
|
||||||
|
})`;
|
||||||
|
|
||||||
export const jemblung = `() => {
|
export const jemblung = `() => {
|
||||||
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
|
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
|
||||||
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
|
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
|
||||||
@ -467,12 +524,12 @@ export const jemblung = `() => {
|
|||||||
stack(
|
stack(
|
||||||
"0 1 4 [3!2 5]".edit(
|
"0 1 4 [3!2 5]".edit(
|
||||||
// chords
|
// chords
|
||||||
x=>x.add("0,3").len("0.05!3 0.02"),
|
x=>x.add("0,3").duration("0.05!3 0.02"),
|
||||||
// bass
|
// bass
|
||||||
x=>x.add("-8").struct("x*8").len(0.1)
|
x=>x.add("-8").struct("x*8").duration(0.1)
|
||||||
),
|
),
|
||||||
// melody
|
// melody
|
||||||
"12 11*3 12 ~".len(0.005)
|
"12 11*3 12 ~".duration(0.005)
|
||||||
)
|
)
|
||||||
.add("<0 1>")
|
.add("<0 1>")
|
||||||
.tune("jemblung2")
|
.tune("jemblung2")
|
||||||
@ -480,10 +537,10 @@ export const jemblung = `() => {
|
|||||||
//.mul(12/5).round().xen("12edo")
|
//.mul(12/5).round().xen("12edo")
|
||||||
.tone(s),
|
.tone(s),
|
||||||
// kick
|
// kick
|
||||||
"[c2 ~]*2".len(0.05).tone(membrane().chain(out())),
|
"[c2 ~]*2".duration(0.05).tone(membrane().chain(out())),
|
||||||
// snare
|
// snare
|
||||||
"[~ c1]*2".early(0.001).tone(snare),
|
"[~ c1]*2".early(0.001).tone(snare),
|
||||||
// hihat
|
// hihat
|
||||||
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
|
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
|
||||||
).slow(3)
|
).slow(3)
|
||||||
}`;
|
}`;
|
||||||
@ -61,11 +61,11 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any)
|
|||||||
(time, event) => {
|
(time, event) => {
|
||||||
try {
|
try {
|
||||||
onEvent?.(event);
|
onEvent?.(event);
|
||||||
const { onTrigger } = event.context;
|
const { onTrigger, velocity } = event.context;
|
||||||
if (!onTrigger) {
|
if (!onTrigger) {
|
||||||
if (defaultSynth) {
|
if (defaultSynth) {
|
||||||
const note = getPlayableNoteValue(event);
|
const note = getPlayableNoteValue(event);
|
||||||
defaultSynth.triggerAttackRelease(note, event.duration, time);
|
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('no defaultSynth passed to useRepl.');
|
throw new Error('no defaultSynth passed to useRepl.');
|
||||||
}
|
}
|
||||||
|
|||||||
31
strudel.mjs
31
strudel.mjs
@ -749,30 +749,23 @@ class Pattern {
|
|||||||
return silence;
|
return silence;
|
||||||
}
|
}
|
||||||
|
|
||||||
_len(value) {
|
// sets absolute duration of events
|
||||||
// return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
_duration(value) {
|
||||||
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
_resolveTies() {
|
// sets event relative duration of events
|
||||||
return this._withEvents((events)=>{
|
_legato(value) {
|
||||||
return events.reduce((tied, event, i) => {
|
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||||
const value = event.value?.value || event.value;
|
}
|
||||||
if (value !== '_') {
|
|
||||||
return tied.concat([event]);
|
_velocity(velocity) {
|
||||||
}
|
return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
|
||||||
console.log('tie!', lastEvent);
|
}
|
||||||
tied[i - 1] = tied[i - 1].withSpan((span) => span.withEnd((_) => event.part.end));
|
|
||||||
// above only works if the tie is not across a cycle boundary... how to do that???
|
|
||||||
// TODO: handle case that there is a gap between tied[i-1].part.end and event.part.begin => tie would make no sense
|
|
||||||
return tied;
|
|
||||||
}, []);
|
|
||||||
})
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// methods of Pattern that get callable factories
|
// methods of Pattern that get callable factories
|
||||||
Pattern.prototype.patternified = ['apply', 'fast', 'slow', 'early', 'late', 'len'];
|
Pattern.prototype.patternified = ['apply', 'fast', 'slow', 'early', 'late', 'duration', 'legato', 'velocity'];
|
||||||
// methods that create patterns, which are added to patternified Pattern methods
|
// methods that create patterns, which are added to patternified Pattern methods
|
||||||
Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
||||||
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
|
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user