mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-24 05:57:17 -04:00
Merge commit '85d3d7392ecfdc985fa8cc93e01846bec0f16dc4' into talk
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+119
-116
@@ -108,6 +108,7 @@ function App() {
|
||||
pattern,
|
||||
pushLog,
|
||||
pending,
|
||||
hideHeader,
|
||||
} = useRepl({
|
||||
tune: '// LOADING...',
|
||||
defaultSynth,
|
||||
@@ -167,128 +168,130 @@ function App() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<header
|
||||
id="header"
|
||||
className={cx(
|
||||
'flex-none w-full px-2 flex border-b border-gray-200 justify-between z-[10] bg-gray-100',
|
||||
isEmbedded ? 'h-8' : 'h-14',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img src={logo} className={cx('Tidal-logo', isEmbedded ? 'w-6 h-6' : 'w-10 h-10')} alt="logo" />
|
||||
<h1 className={isEmbedded ? 'text-l' : 'text-xl'}>Strudel {isEmbedded ? 'Mini ' : ''}REPL</h1>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
onClick={() => {
|
||||
getAudioContext().resume(); // fixes no sound in ios webkit
|
||||
togglePlay();
|
||||
}}
|
||||
className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
>
|
||||
{!pending ? (
|
||||
<span className={cx('flex items-center', isEmbedded ? 'w-16' : 'w-16')}>
|
||||
{cycle.started ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{cycle.started ? 'pause' : 'play'}
|
||||
</span>
|
||||
) : (
|
||||
<>loading...</>
|
||||
)}
|
||||
</button>
|
||||
{!isEmbedded && (
|
||||
{!hideHeader && (
|
||||
<header
|
||||
id="header"
|
||||
className={cx(
|
||||
'flex-none w-full px-2 flex border-b border-gray-200 justify-between z-[10] bg-gray-100',
|
||||
isEmbedded ? 'h-8' : 'h-14',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img src={logo} className={cx('Tidal-logo', isEmbedded ? 'w-6 h-6' : 'w-10 h-10')} alt="logo" />
|
||||
<h1 className={isEmbedded ? 'text-l' : 'text-xl'}>Strudel {isEmbedded ? 'Mini ' : ''}REPL</h1>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
className="hover:bg-gray-300 p-2"
|
||||
onClick={async () => {
|
||||
const _code = getRandomTune();
|
||||
console.log('tune', _code); // uncomment this to debug when random code fails
|
||||
setCode(_code);
|
||||
cleanupDraw();
|
||||
cleanupUi();
|
||||
resetLoadedSamples();
|
||||
prebake();
|
||||
const parsed = await evaluate(_code);
|
||||
setPattern(parsed.pattern);
|
||||
setActiveCode(_code);
|
||||
onClick={() => {
|
||||
getAudioContext().resume(); // fixes no sound in ios webkit
|
||||
togglePlay();
|
||||
}}
|
||||
className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
>
|
||||
🎲 random
|
||||
{!pending ? (
|
||||
<span className={cx('flex items-center', isEmbedded ? 'w-16' : 'w-16')}>
|
||||
{cycle.started ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{cycle.started ? 'pause' : 'play'}
|
||||
</span>
|
||||
) : (
|
||||
<>loading...</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{!isEmbedded && (
|
||||
<button className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}>
|
||||
<a href="./tutorial">📚 tutorial</a>
|
||||
</button>
|
||||
)}
|
||||
{!isEmbedded && (
|
||||
<button
|
||||
className={cx('cursor-pointer hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
onClick={async () => {
|
||||
const codeToShare = activeCode || code;
|
||||
if (lastShared === codeToShare) {
|
||||
// alert('Link already generated!');
|
||||
pushLog(`Link already generated!`);
|
||||
return;
|
||||
}
|
||||
// generate uuid in the browser
|
||||
const hash = nanoid(12);
|
||||
const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]);
|
||||
if (!error) {
|
||||
setLastShared(activeCode || code);
|
||||
const shareUrl = window.location.origin + '?' + hash;
|
||||
// copy shareUrl to clipboard
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
const message = `Link copied to clipboard: ${shareUrl}`;
|
||||
// alert(message);
|
||||
pushLog(message);
|
||||
} else {
|
||||
console.log('error', error);
|
||||
const message = `Error: ${error.message}`;
|
||||
// alert(message);
|
||||
pushLog(message);
|
||||
}
|
||||
}}
|
||||
>
|
||||
📣 share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''}
|
||||
</button>
|
||||
)}
|
||||
{isEmbedded && (
|
||||
<button className={cx('hover:bg-gray-300 px-2')}>
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
||||
🚀 open
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
{isEmbedded && (
|
||||
<button className={cx('hover:bg-gray-300 px-2')}>
|
||||
<a
|
||||
onClick={() => {
|
||||
window.location.href = initialUrl;
|
||||
window.location.reload();
|
||||
{!isEmbedded && (
|
||||
<button
|
||||
className="hover:bg-gray-300 p-2"
|
||||
onClick={async () => {
|
||||
const _code = getRandomTune();
|
||||
console.log('tune', _code); // uncomment this to debug when random code fails
|
||||
setCode(_code);
|
||||
cleanupDraw();
|
||||
cleanupUi();
|
||||
resetLoadedSamples();
|
||||
prebake();
|
||||
const parsed = await evaluate(_code);
|
||||
setPattern(parsed.pattern);
|
||||
setActiveCode(_code);
|
||||
}}
|
||||
title="Reset"
|
||||
>
|
||||
💔 reset
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
🎲 random
|
||||
</button>
|
||||
)}
|
||||
{!isEmbedded && (
|
||||
<button className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}>
|
||||
<a href="./tutorial">📚 tutorial</a>
|
||||
</button>
|
||||
)}
|
||||
{!isEmbedded && (
|
||||
<button
|
||||
className={cx('cursor-pointer hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
onClick={async () => {
|
||||
const codeToShare = activeCode || code;
|
||||
if (lastShared === codeToShare) {
|
||||
// alert('Link already generated!');
|
||||
pushLog(`Link already generated!`);
|
||||
return;
|
||||
}
|
||||
// generate uuid in the browser
|
||||
const hash = nanoid(12);
|
||||
const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]);
|
||||
if (!error) {
|
||||
setLastShared(activeCode || code);
|
||||
const shareUrl = window.location.origin + '?' + hash;
|
||||
// copy shareUrl to clipboard
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
const message = `Link copied to clipboard: ${shareUrl}`;
|
||||
// alert(message);
|
||||
pushLog(message);
|
||||
} else {
|
||||
console.log('error', error);
|
||||
const message = `Error: ${error.message}`;
|
||||
// alert(message);
|
||||
pushLog(message);
|
||||
}
|
||||
}}
|
||||
>
|
||||
📣 share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''}
|
||||
</button>
|
||||
)}
|
||||
{isEmbedded && (
|
||||
<button className={cx('hover:bg-gray-300 px-2')}>
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
||||
🚀 open
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
{isEmbedded && (
|
||||
<button className={cx('hover:bg-gray-300 px-2')}>
|
||||
<a
|
||||
onClick={() => {
|
||||
window.location.href = initialUrl;
|
||||
window.location.reload();
|
||||
}}
|
||||
title="Reset"
|
||||
>
|
||||
💔 reset
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
<section className="grow flex flex-col text-gray-100">
|
||||
<div className="grow relative flex overflow-auto" id="code">
|
||||
{/* onCursor={markParens} */}
|
||||
|
||||
+50
-1
@@ -51,7 +51,56 @@ Pattern.prototype.piano = function () {
|
||||
.s('piano')
|
||||
.fmap((value) => {
|
||||
// pan by pitch
|
||||
const pan = panwidth(Math.min(toMidi(value.note) / maxPan, 1), 0.5);
|
||||
const pan = panwidth(Math.min(toMidi(value.note || value.n) / maxPan, 1), 0.5);
|
||||
return { ...value, pan: (value.pan || 1) * pan };
|
||||
});
|
||||
};
|
||||
|
||||
samples({
|
||||
jazzbass: {
|
||||
_base: './samples/jazzbass/moog_',
|
||||
c2: 'c2.mp3',
|
||||
e2: 'e2.mp3',
|
||||
a2: 'a2.mp3',
|
||||
c3: 'c3.mp3',
|
||||
e3: 'e3.mp3',
|
||||
a3: 'a3.mp3',
|
||||
c4: 'c4.mp3',
|
||||
},
|
||||
stage73: {
|
||||
_base: './samples/stage73/',
|
||||
c2: ['quiet/c2.mp3', 'loud/c2.mp3'],
|
||||
e2: ['quiet/e2.mp3', 'loud/e2.mp3'],
|
||||
a2: ['quiet/a2.mp3', 'loud/a2.mp3'],
|
||||
c3: ['quiet/c3.mp3', 'loud/c3.mp3'],
|
||||
e3: ['quiet/e3.mp3', 'loud/e3.mp3'],
|
||||
a3: ['quiet/a3.mp3', 'loud/a3.mp3'],
|
||||
c4: ['quiet/c4.mp3', 'loud/c4.mp3'],
|
||||
e4: ['quiet/e4.mp3', 'loud/e4.mp3'],
|
||||
a4: ['quiet/a4.mp3', 'loud/a4.mp3'],
|
||||
c5: ['quiet/c5.mp3', 'loud/c5.mp3'],
|
||||
e5: ['quiet/e5.mp3', 'loud/e5.mp3'],
|
||||
a5: ['quiet/a5.mp3', 'loud/a5.mp3'],
|
||||
c6: ['quiet/c6.mp3', 'loud/c6.mp3'],
|
||||
},
|
||||
flute: {
|
||||
_base: './samples/flute/',
|
||||
g2: 'g2.mp3',
|
||||
c3: 'c3.mp3',
|
||||
f3: 'f3.mp3',
|
||||
c4: 'c4.mp3',
|
||||
f4: 'f4.mp3',
|
||||
c5: 'c5.mp3',
|
||||
},
|
||||
});
|
||||
|
||||
samples(
|
||||
{
|
||||
bd: 'bd.mp3',
|
||||
sd: ['sd.mp3', 'sd2.mp3'],
|
||||
hh: ['hh.mp3'],
|
||||
snap: ['snap.mp3'],
|
||||
oh: ['oh.mp3'],
|
||||
},
|
||||
'./samples/president/president_',
|
||||
);
|
||||
|
||||
@@ -1019,3 +1019,112 @@ x=>x.add(7).color('steelblue')
|
||||
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
|
||||
.out()
|
||||
.pianoroll({vertical:1})`;
|
||||
|
||||
export const root73 = `const root = 'E'
|
||||
stack(
|
||||
stack(
|
||||
s("<bd(5,12) bd(3,12)>"),
|
||||
s("hh!6").echo(2,1/12+.02,.2), // .begin(0.04),
|
||||
s("~!5 oh"), // hush(),
|
||||
s("~ <sd!7 [sd@3 sd@2 sd]>")
|
||||
).slow(1) //.hcutoff(2000)
|
||||
,
|
||||
"<0(3,9) [2 <-3 3>]>"
|
||||
.scale(root+'2 minor')
|
||||
.legato(1).note().fast(2).s('jazzbass'),
|
||||
"0 2 4 1".euclid(7,12)
|
||||
.off(1/16, x=>x.add(2).color('steelblue'))
|
||||
.off(1/12, x=>x.add(7).color('orange'))
|
||||
.degradeBy(0.3)
|
||||
.slow(4)
|
||||
.scale(root+'4 minor').note()
|
||||
.gain(.9)
|
||||
.s('stage73').n(rand.range(0,.7).round())
|
||||
.legato(perlin.range(.1,.5))
|
||||
.velocity(perlin.range(.5,.8))
|
||||
.echo(4, 1/6, .5), //.hcutoff(1000).hresonance(10),
|
||||
mini(root+'m7@5 <Ab7 Eb7>').slow(2).voicings().transpose(0)
|
||||
//.euclid(5,12,1)
|
||||
.euclidLegato(5,12,1)
|
||||
.note()
|
||||
.gain(0.5)
|
||||
.echo(1, 1/12, .5)
|
||||
.slow(1)
|
||||
.s('stage73')
|
||||
.legato(.1)
|
||||
.color('tomato')
|
||||
.cutoff("300@2 600".fast(2))
|
||||
//.resonance("10 20 25 10")
|
||||
.n(1)
|
||||
).slow(4/2).clip(1)
|
||||
.out()
|
||||
.pianoroll({autorange:0,vertical:1})
|
||||
// strudel disable-highlighting`;
|
||||
|
||||
export const flute73 = `
|
||||
const harmony = x=>x.scale(cat('E major','F# minor').slow(4)).note().clip(1)
|
||||
stack(
|
||||
// flute
|
||||
"<0 2 5 4>"
|
||||
.off(1/6, add(2))
|
||||
.off(1/12, add(5))
|
||||
.layer(harmony)
|
||||
.legato("<1 .1>/16")
|
||||
.echo("<1 3>/16",1/3,.5)
|
||||
.s('flute')
|
||||
.cutoff(perlin.range(500,2000))
|
||||
.color('darkseagreen')
|
||||
,
|
||||
// bass
|
||||
"<-7 -5 -9 -5>(3,9)"
|
||||
.legato("<2.5 1.5>/16")
|
||||
.layer(harmony)
|
||||
.gain("1 .8 1")
|
||||
.s('stage73')
|
||||
.color('brown'),
|
||||
// drums
|
||||
s("bd*3, [~@2 <hh!3 hh*3>]*2,~ snap".slow(2)).gain(.4)
|
||||
)
|
||||
//.hcutoff(800)
|
||||
.out()
|
||||
.pianoroll({vertical:1,autorange:0})
|
||||
`;
|
||||
|
||||
export const frequencyTest = `sequence(880, [440, 660], 440, 660)
|
||||
.div(slowcat(3,2).slow(2))
|
||||
.off(1/8,mul(2))
|
||||
.off(1/4,mul(3))
|
||||
.freq()
|
||||
.legato(2)
|
||||
.slow(2)
|
||||
.s('sawtooth').cutoff(1000)
|
||||
.out()
|
||||
`;
|
||||
|
||||
export const gameboyParty = `
|
||||
stack(
|
||||
freq(
|
||||
sequence(1,2).mul(55/2) // frequencies
|
||||
.mul(slowcat(1,2))
|
||||
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
|
||||
.fast(3)
|
||||
.velocity(.5)
|
||||
)
|
||||
.s(cat('sawtooth','square').fast(2))
|
||||
.attack(.01).decay(.02).sustain(.5).release(.1)
|
||||
.cutoff(2000) //.resonance(25)
|
||||
,
|
||||
freq(
|
||||
sequence(1,[3/2,4/3],5/3)
|
||||
.off(1/6, x=>x.mul(2).velocity(.5))
|
||||
.iter(3)
|
||||
.fast(1)
|
||||
.mul(220)
|
||||
)
|
||||
.gain(.5)
|
||||
.s(cat('sawtooth','square').slow(8))
|
||||
.legato(sine.slow(16).add(.5).div(2))
|
||||
.echo(2,1/12,.6)
|
||||
.attack(.01).decay(.02).sustain(.5).release(.1)
|
||||
)
|
||||
.out()`;
|
||||
|
||||
Reference in New Issue
Block a user