mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-24 13:59:07 -04:00
Merge commit '85d3d7392ecfdc985fa8cc93e01846bec0f16dc4' into talk
This commit is contained in:
+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