This commit is contained in:
2025-10-12 15:21:36 +02:00
parent a56b089bb2
commit 9c6997e6be
9 changed files with 155 additions and 45 deletions

5
.gitignore vendored
View File

@ -22,3 +22,8 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Claude artifacts
.claude/
.clinerules
CLAUDE.md

View File

@ -4,9 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Audio sample generator" /> <meta name="description" content="Poof: a sample generator" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<title>Vending Machine</title> <title>Poof</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -1,5 +1,5 @@
{ {
"name": "vendingmachine", "name": "poof",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",

View File

@ -1,4 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" fill="#000"/> <rect width="32" height="32" fill="#000"/>
<text x="16" y="21" font-family="monospace" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">VM</text> <circle cx="16" cy="16" r="8" fill="none" stroke="#fff" stroke-width="1.5"/>
<circle cx="16" cy="16" r="4" fill="none" stroke="#fff" stroke-width="1"/>
<path d="M16 4 L16 8 M28 16 L24 16 M16 28 L16 24 M4 16 L8 16" stroke="#fff" stroke-width="1.5" stroke-linecap="square"/>
<path d="M23 9 L20.5 11.5 M23 23 L20.5 20.5 M9 23 L11.5 20.5 M9 9 L11.5 11.5" stroke="#fff" stroke-width="1" stroke-linecap="square"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 236 B

After

Width:  |  Height:  |  Size: 529 B

View File

@ -908,7 +908,7 @@ export class Benjolin implements SynthEngine<BenjolinParams> {
} }
// Correlate cross-mod amounts // Correlate cross-mod amounts
const crossModDelta = (Math.random() - 0.5) * mutationAmount; const crossModDelta = (Math.random() - 0.5) * mutAmount;
mutated.crossMod1to2 = Math.max(0, Math.min(1, mutated.crossMod1to2 + crossModDelta)); mutated.crossMod1to2 = Math.max(0, Math.min(1, mutated.crossMod1to2 + crossModDelta));
mutated.crossMod2to1 = Math.max(0, Math.min(1, mutated.crossMod2to1 + crossModDelta * 0.7)); mutated.crossMod2to1 = Math.max(0, Math.min(1, mutated.crossMod2to1 + crossModDelta * 0.7));

View File

@ -1,4 +1,4 @@
import type { SynthEngine } from './SynthEngine'; import type { SynthEngine, PitchLock } from './SynthEngine';
interface InputParams { interface InputParams {
recorded: boolean; recorded: boolean;
@ -110,11 +110,11 @@ export class Input implements SynthEngine<InputParams> {
return [leftResampled, rightResampled]; return [leftResampled, rightResampled];
} }
randomParams(): InputParams { randomParams(_pitchLock?: PitchLock): InputParams {
return { recorded: this.leftChannel !== null && this.rightChannel !== null }; return { recorded: this.leftChannel !== null && this.rightChannel !== null };
} }
mutateParams(params: InputParams): InputParams { mutateParams(params: InputParams, _mutationAmount?: number, _pitchLock?: PitchLock): InputParams {
return params; return params;
} }
} }

View File

@ -1,4 +1,4 @@
import type { SynthEngine } from './SynthEngine'; import type { SynthEngine, PitchLock } from './SynthEngine';
interface SampleParams { interface SampleParams {
loaded: boolean; loaded: boolean;
@ -66,11 +66,11 @@ export class Sample implements SynthEngine<SampleParams> {
return [leftResampled, rightResampled]; return [leftResampled, rightResampled];
} }
randomParams(): SampleParams { randomParams(_pitchLock?: PitchLock): SampleParams {
return { loaded: this.leftChannel !== null && this.rightChannel !== null }; return { loaded: this.leftChannel !== null && this.rightChannel !== null };
} }
mutateParams(params: SampleParams): SampleParams { mutateParams(params: SampleParams, _mutationAmount?: number, _pitchLock?: PitchLock): SampleParams {
return params; return params;
} }
} }

View File

@ -102,7 +102,7 @@ export class WavetableEngine implements SynthEngine<WavetableParams> {
}); });
const results = await Promise.all(loadPromises); const results = await Promise.all(loadPromises);
const loaded = results.filter((wt): wt is Wavetable => wt !== null); const loaded = results.filter((wt) => wt !== null) as Wavetable[];
if (loaded.length > 0) { if (loaded.length > 0) {
this.wavetables = loaded; this.wavetables = loaded;

View File

@ -21,11 +21,23 @@
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}
> >
<h1 id="modal-title">Vending Machine</h1> <h1 id="modal-title">Poof: a sample generator</h1>
<p class="description"> <p class="description">
Oh, looks like you found a sound vending machine. This one seems slightly Do you need to generate audio samples for your projects? Poof, it's
broken and it seems that you can get sounds for free... Have fun! already done! These are not the best samples you'll ever hear, but they
have the right to exist, nonetheless, in the realm of all the random and
haphazardly generated digital sounds. Have fun, give computers some love!
</p> </p>
<ul>
<li class="description">
Generate audio samples using various audio synthesis generators. Random
parameters.
</li>
<li class="description">
Process each sound with with a growing collection of random effects.
</li>
<li class="description">Export your samples as WAV files.</li>
</ul>
<div class="modal-links"> <div class="modal-links">
<p> <p>
Created by <a Created by <a
@ -53,107 +65,197 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.8); background-color: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(4px);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 2000; z-index: 2000;
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
.modal-content { .modal-content {
background-color: #000; background-color: #000;
border: 2px solid #fff; border: 2px solid #fff;
padding: 1.5rem; padding: 1.25rem;
max-width: 500px; max-width: 500px;
width: 90%; width: calc(100% - 2rem);
color: #fff; color: #fff;
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow-y: auto;
animation: slideIn 0.3s ease-out;
box-shadow: 0 8px 32px rgba(255, 255, 255, 0.1);
} }
.modal-content h1 { .modal-content h1 {
margin: 0 0 0.75rem 0; margin: 0 0 0.5rem 0;
font-size: 1.5rem; font-size: 1.75rem;
font-weight: bold; font-weight: bold;
letter-spacing: 0.02em;
} }
.modal-content .description { .modal-content .description {
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
line-height: 1.5; line-height: 1.6;
color: #ccc; color: #e0e0e0;
font-size: 0.9rem; font-size: 0.875rem;
}
.modal-content ul {
margin: 0 0 1rem 0;
padding-left: 1.25rem;
}
.modal-content ul li {
margin-bottom: 0.5rem;
}
.modal-content ul li:last-child {
margin-bottom: 0;
} }
.modal-links { .modal-links {
margin: 1rem 0; margin: 1.25rem 0;
padding: 0.75rem 0; padding: 0.875rem 0;
border-top: 1px solid #333; border-top: 1px solid #444;
border-bottom: 1px solid #333; border-bottom: 1px solid #444;
} }
.modal-links p { .modal-links p {
margin: 0.4rem 0; margin: 0.375rem 0;
font-size: 0.85rem; font-size: 0.8125rem;
color: #ccc; color: #bbb;
line-height: 1.4; line-height: 1.5;
} }
.modal-links a { .modal-links a {
color: #646cff; color: #646cff;
text-decoration: none; text-decoration: none;
word-break: break-word; word-break: break-word;
transition: color 0.2s ease;
} }
.modal-links a:hover { .modal-links a:hover {
color: #8891ff;
text-decoration: underline; text-decoration: underline;
} }
.modal-close { .modal-close {
margin-top: 0.75rem; margin-top: 1rem;
width: 100%; width: 100%;
padding: 0.65rem; padding: 0.75rem;
font-size: 1rem; font-size: 1rem;
background-color: #fff; background-color: #fff;
color: #000; color: #000;
border: none; border: 2px solid #fff;
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
.modal-close:hover { .modal-close:hover {
background-color: #ddd; background-color: #000;
color: #fff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
} }
@media (min-width: 768px) { .modal-close:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.1);
}
@media (min-width: 480px) {
.modal-content { .modal-content {
padding: 2rem; padding: 1.75rem;
width: calc(100% - 3rem);
} }
.modal-content h1 { .modal-content h1 {
font-size: 2rem; font-size: 2rem;
margin: 0 0 0.75rem 0;
}
.modal-content .description {
font-size: 0.9375rem;
}
.modal-links p {
font-size: 0.875rem;
}
}
@media (min-width: 768px) {
.modal-content {
padding: 2.5rem;
}
.modal-content h1 {
font-size: 2.5rem;
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
} }
.modal-content .description { .modal-content .description {
margin: 0 0 1.5rem 0; margin: 0 0 1.25rem 0;
font-size: 1rem; font-size: 1rem;
line-height: 1.6; line-height: 1.7;
}
.modal-content ul {
margin: 0 0 1.25rem 0;
} }
.modal-links { .modal-links {
margin: 1.5rem 0; margin: 1.75rem 0;
padding: 1rem 0; padding: 1.125rem 0;
} }
.modal-links p { .modal-links p {
font-size: 0.9rem; font-size: 0.9375rem;
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.modal-close { .modal-close {
margin-top: 1rem; margin-top: 1.25rem;
padding: 0.75rem; padding: 0.875rem;
font-size: 1.1rem; font-size: 1.125rem;
}
}
@media (prefers-reduced-motion: reduce) {
.modal-overlay,
.modal-content {
animation: none;
}
.modal-close {
transition: none;
}
.modal-links a {
transition: none;
} }
} }
</style> </style>