Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# smplr

## 0.17.0
## 0.17.x

- Migrate Piano sample sources to smpldsnds (fix a iOS loading problem)

#### New audio player (`src/smplr/`)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "smplr",
"version": "0.17.0",
"version": "0.17.1",
"homepage": "https://github.com/danigb/smplr#readme",
"description": "A Sampled collection of instruments",
"main": "dist/index.js",
Expand Down
2 changes: 2 additions & 0 deletions site/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Inter } from "next/font/google";
import Head from "next/head";
import { version } from "smplr/package.json";
import { DrumMachineExample } from "src/DrumMachineExample";
import { ElectricPianoExample } from "src/ElectricPianoExample";
import { MalletExample } from "src/MalletExample";
Expand All @@ -23,6 +24,7 @@ export default function Home() {
<main className={"max-w-4xl mx-auto my-20 p-4" + inter.className}>
<div className="flex items-end mb-16">
<h1 className="text-6xl font-bold">smplr</h1>
<span className="text-zinc-500 ml-3 text-lg">{version}</span>
</div>

<div className="flex flex-col gap-8">
Expand Down
5 changes: 3 additions & 2 deletions site/src/DrumMachineExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LoadWithStatus, useStatus } from "./useStatus";
let reverb: Reverb | undefined;

export function DrumMachineExample({ className }: { className?: string }) {
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [dmName, setDmName] = useState(getDrumMachineNames()[0]);
const [drums, setDrumMachine] = useState<DrumMachine | undefined>(undefined);
const [reverbMix, setReverbMix] = useState(0.0);
Expand All @@ -16,7 +16,7 @@ export function DrumMachineExample({ className }: { className?: string }) {
setStatus("loading");
const context = getAudioContext();
reverb ??= new Reverb(context);
const drums = new DrumMachine(context, { instrument });
const drums = new DrumMachine(context, { instrument, onLoadProgress });
drums.output.addEffect("reverb", reverb, reverbMix);

drums.load
Expand All @@ -36,6 +36,7 @@ export function DrumMachineExample({ className }: { className?: string }) {
<h1 className="text-3xl">DrumMachine</h1>
<LoadWithStatus
status={status}
progress={progress}
onClick={() => {
loadDrumMachine(dmName);
}}
Expand Down
10 changes: 7 additions & 3 deletions site/src/ElectricPianoExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import { ElectricPiano, getElectricPianoNames, Reverb } from "smplr";
import { getAudioContext } from "./audio-context";
import { ConnectMidi } from "./ConnectMidi";
import { PianoKeyboard } from "./PianoKeyboard";
import { LoadWithStatus, useStatus } from "./useStatus";
import { LoadWithStatus, SampleFormat, useStatus } from "./useStatus";

let reverb: Reverb | undefined;
let instrumentNames = getElectricPianoNames();

export function ElectricPianoExample({ className }: { className?: string }) {
const [piano, setPiano] = useState<ElectricPiano | undefined>(undefined);
const [instrumentName, setInstrumentName] = useState("CP80");
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [format, setFormat] = useState<SampleFormat>("ogg");
const [reverbMix, setReverbMix] = useState(0);
const [tremolo, setTremolo] = useState(0);
const [volume, setVolume] = useState(100);
Expand All @@ -23,7 +24,7 @@ export function ElectricPianoExample({ className }: { className?: string }) {
setStatus("loading");
const context = getAudioContext();
reverb ??= new Reverb(context);
const newPiano = new ElectricPiano(context, { instrument, volume });
const newPiano = new ElectricPiano(context, { instrument, volume, onLoadProgress, formats: [format] });
newPiano.output.addEffect("reverb", reverb, reverbMix);
setPiano(newPiano);
newPiano.load.then(() => {
Expand All @@ -37,7 +38,10 @@ export function ElectricPianoExample({ className }: { className?: string }) {
<h1 className="text-3xl">Electric Piano</h1>
<LoadWithStatus
status={status}
progress={progress}
onClick={() => loadPiano(instrumentName)}
format={format}
onFormatChange={setFormat}
/>
<ConnectMidi instrument={piano} />
</div>
Expand Down
4 changes: 3 additions & 1 deletion site/src/MalletExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function MalletExample({ className }: { className?: string }) {
const [instrumentName, setInstrumentName] = useState<string>(
instrumentNames[0]
);
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [reverbMix, setReverbMix] = useState(0);
const [volume, setVolume] = useState(100);

Expand All @@ -27,6 +27,7 @@ export function MalletExample({ className }: { className?: string }) {
const newPiano = new Mallet(context, {
instrument: instrumentName,
volume,
onLoadProgress,
});
newPiano.output.addEffect("reverb", reverb, reverbMix);
setInstrument(newPiano);
Expand All @@ -42,6 +43,7 @@ export function MalletExample({ className }: { className?: string }) {

<LoadWithStatus
status={status}
progress={progress}
onClick={() => loadMallet(instrumentName)}
/>
<ConnectMidi instrument={instrument} />
Expand Down
4 changes: 3 additions & 1 deletion site/src/MellotronExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function MellotronExample({ className }: { className?: string }) {
const [instrumentName, setInstrumentName] = useState<string>(
instrumentNames[0]
);
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [reverbMix, setReverbMix] = useState(0);
const [volume, setVolume] = useState(100);

Expand All @@ -29,6 +29,7 @@ export function MellotronExample({ className }: { className?: string }) {
const newInstrument = new Mellotron(context, {
instrument: instrumentName,
volume,
onLoadProgress,
});
newInstrument.output.addEffect("reverb", reverb, reverbMix);
setInstrument(newInstrument);
Expand All @@ -44,6 +45,7 @@ export function MellotronExample({ className }: { className?: string }) {

<LoadWithStatus
status={status}
progress={progress}
onClick={() => loadMellotron(instrumentName)}
/>
<ConnectMidi instrument={instrument} />
Expand Down
9 changes: 5 additions & 4 deletions site/src/PianoExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { CacheStorage, Reverb, SplendidGrandPiano } from "smplr";
import { ConnectMidi } from "./ConnectMidi";
import { PianoKeyboard } from "./PianoKeyboard";
import { getAudioContext } from "./audio-context";
import { LoadWithStatus, useStatus } from "./useStatus";
import { LoadWithStatus, SampleFormat, useStatus } from "./useStatus";

let reverb: Reverb | undefined;
let storage: CacheStorage | undefined;

export function PianoExample({ className }: { className?: string }) {
const [piano, setPiano] = useState<SplendidGrandPiano | undefined>(undefined);
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [format, setFormat] = useState<SampleFormat>("ogg");
const [reverbMix, setReverbMix] = useState(0.0);
const [volume, setVolume] = useState(100);

Expand All @@ -22,7 +23,7 @@ export function PianoExample({ className }: { className?: string }) {
const context = getAudioContext();
reverb ??= new Reverb(context);
storage ??= new CacheStorage();
const newPiano = new SplendidGrandPiano(context, { volume, storage });
const newPiano = new SplendidGrandPiano(context, { volume, storage, onLoadProgress, formats: [format] });
newPiano.output.addEffect("reverb", reverb, reverbMix);
setPiano(newPiano);
newPiano.load.then(() => {
Expand All @@ -34,7 +35,7 @@ export function PianoExample({ className }: { className?: string }) {
<div className={className}>
<div className="flex gap-2 items-end mb-2">
<h1 className="text-3xl">SplendidGrandPiano</h1>
<LoadWithStatus status={status} onClick={loadPiano} />
<LoadWithStatus status={status} progress={progress} onClick={loadPiano} format={format} onFormatChange={status === "init" ? setFormat : undefined} />
<ConnectMidi instrument={piano} />
</div>
<div></div>
Expand Down
5 changes: 3 additions & 2 deletions site/src/SamplerExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const samples = [
];

export function SamplerExample({ className }: { className?: string }) {
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [sampler, setSampler] = useState<Sampler | undefined>();
const [reverbMix, setReverbMix] = useState(0.0);
const [volume, setVolume] = useState(100);
Expand All @@ -39,7 +39,7 @@ export function SamplerExample({ className }: { className?: string }) {
},
{} as Record<string, string>
);
const sampler = new Sampler(context, { buffers });
const sampler = new Sampler(context, { buffers, onLoadProgress });
sampler.output.addEffect("reverb", reverb, reverbMix);

sampler.load
Expand All @@ -59,6 +59,7 @@ export function SamplerExample({ className }: { className?: string }) {
<h1 className="text-3xl">Sampler</h1>
<LoadWithStatus
status={status}
progress={progress}
onClick={() => {
loadDrumMachine();
}}
Expand Down
4 changes: 3 additions & 1 deletion site/src/SmolkenExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function SmolkenExample({ className }: { className?: string }) {
const [instrumentName, setInstrumentName] = useState<string>(
instrumentNames[0]
);
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [reverbMix, setReverbMix] = useState(0);
const [volume, setVolume] = useState(100);

Expand All @@ -29,6 +29,7 @@ export function SmolkenExample({ className }: { className?: string }) {
const newInstrument = new Smolken(context, {
instrument: instrumentName,
volume,
onLoadProgress,
});
newInstrument.output.addEffect("reverb", reverb, reverbMix);
setInstrument(newInstrument);
Expand All @@ -44,6 +45,7 @@ export function SmolkenExample({ className }: { className?: string }) {

<LoadWithStatus
status={status}
progress={progress}
onClick={() => loadSmolken(instrumentName)}
/>
<ConnectMidi instrument={instrument} />
Expand Down
3 changes: 2 additions & 1 deletion site/src/Soundfont2Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function Soundfont2Example({ className }: { className?: string }) {
);
const [samplerName, setSamplerName] = useState<string>(samplerNames[0]);
const [instrumentName, setInstrumentName] = useState<string>("");
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [reverbMix, setReverbMix] = useState(0);
const [volume, setVolume] = useState(100);
const [isCustomEnabled, setCustomEnabled] = useState(false);
Expand Down Expand Up @@ -76,6 +76,7 @@ export function Soundfont2Example({ className }: { className?: string }) {

<LoadWithStatus
status={status}
progress={progress}
onClick={() => {
if (isCustomEnabled) {
loadSampler(customUrl);
Expand Down
4 changes: 3 additions & 1 deletion site/src/SoundfontExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LoadWithStatus, useStatus } from "./useStatus";
let reverb: Reverb | undefined;

export function SoundfontExample({ className }: { className?: string }) {
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [loopStatus, setLoopStatus] = useState<"disabled" | "loop" | "no-loop">(
"disabled"
);
Expand All @@ -27,6 +27,7 @@ export function SoundfontExample({ className }: { className?: string }) {
kit,
instrument,
loadLoopData: true,
onLoadProgress,
});
soundfont.output.addEffect("reverb", reverb, 0.0);
soundfont.load
Expand Down Expand Up @@ -54,6 +55,7 @@ export function SoundfontExample({ className }: { className?: string }) {
<h1 className="text-3xl">Soundfont</h1>
<LoadWithStatus
status={status}
progress={progress}
onClick={() => {
loadSoundfont(libraryName, instrumentName);
}}
Expand Down
4 changes: 3 additions & 1 deletion site/src/VersilianExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function VersilianExample({ className }: { className?: string }) {
const [instrumentName, setInstrumentName] = useState<string>(
instrumentNames[0]
);
const [status, setStatus] = useStatus();
const { status, setStatus, progress, onLoadProgress } = useStatus();
const [reverbMix, setReverbMix] = useState(0);
const [volume, setVolume] = useState(100);

Expand All @@ -46,6 +46,7 @@ export function VersilianExample({ className }: { className?: string }) {
const newInstrument = new Versilian(context, {
instrument: instrumentName,
volume,
onLoadProgress,
});
newInstrument.output.addEffect("reverb", reverb, reverbMix);
setInstrument(newInstrument);
Expand All @@ -66,6 +67,7 @@ export function VersilianExample({ className }: { className?: string }) {

<LoadWithStatus
status={status}
progress={progress}
onClick={() => loadVersilian(instrumentName)}
/>
<ConnectMidi instrument={instrument} />
Expand Down
55 changes: 45 additions & 10 deletions site/src/useStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,64 @@
import { useState } from "react";
import { useCallback, useState } from "react";

export type Status = "init" | "loading" | "ready" | "error";

export function useStatus() {
return useState<Status>("init");
const [status, setStatus] = useState<Status>("init");
const [progress, setProgress] = useState("");

const onLoadProgress = useCallback(
(p: { loaded: number; total: number }) => {
setProgress(
p.total > 0 ? `${Math.round((100 * p.loaded) / p.total)}%` : ""
);
},
[]
);

return { status, setStatus, progress, onLoadProgress } as const;
}

export type SampleFormat = "ogg" | "m4a";

export function LoadWithStatus({
status,
onClick,
progress,
format,
onFormatChange,
}: {
status: string;
onClick: () => void;
progress?: string;
format?: SampleFormat;
onFormatChange?: (format: SampleFormat) => void;
}) {
return status === "init" ? (
<button
className="text-rose-700 bg-zinc-900 px-2 rounded"
onClick={onClick}
>
Load
</button>
<div className="flex gap-2 items-center">
<button
className="text-rose-700 bg-zinc-900 px-2 rounded"
onClick={onClick}
>
Load
</button>
{onFormatChange && (
<select
className="bg-zinc-900 text-zinc-400 text-sm px-1 rounded"
value={format}
onChange={(e) => onFormatChange(e.target.value as SampleFormat)}
>
<option value="ogg">ogg</option>
<option value="m4a">m4a</option>
</select>
)}
</div>
) : status === "ready" ? (
<div className="text-teal-600">Ready</div>
<div className="flex gap-2 items-center">
<div className="text-teal-600">Ready</div>
{format && <div className="text-zinc-500 text-sm">({format})</div>}
</div>
) : status === "loading" ? (
<div className="text-gray-500">Loading...</div>
<div className="text-gray-500">Loading{progress ? ` ${progress}` : "..."}</div>
) : (
<div className="text-red-700">Error loading</div>
);
Expand Down
Loading