π Sample Library
10K+ Royalty-Free Samples with AI-Powered Search
π― Library Strategy
FlowState includes a curated library of royalty-free samples. Users get instant access to quality sounds without legal concerns.
Starter Library: 10,000+ government/public domain sound effects plus curated free samples from trusted sources. Users can also upload their own samples.
π Sample Sources
| Source | Samples | License | Quality |
|---|---|---|---|
| Government Sound Library | 10,000+ | Public Domain | Variable |
| Freesound.org (curated) | 2,000+ | CC0 | Good |
| NASA Sound Library | 500+ | Public Domain | Unique |
| BBC Sound Effects | 16,000+ | Remix allowed | Excellent |
| Custom Recorded | 500+ | Original | Excellent |
ποΈ Sample Categories
| Category | Subcategories | Est. Count |
|---|---|---|
| Drums | Kicks, Snares, Hats, Claps, Percussion, Loops | 3,000 |
| Bass | 808s, Sub Bass, Synth Bass, Acoustic | 500 |
| Melodic | Keys, Strings, Pads, Leads, Plucks | 1,500 |
| Vocals | Chops, Ad-libs, FX, Chants | 800 |
| FX | Risers, Impacts, Transitions, Textures | 1,200 |
| Foley | Ambient, Nature, Urban, Mechanical | 3,000 |
π AI-Powered Search
Samples are indexed with semantic embeddings for natural language search.
Search Examples
| Query | Results |
|---|---|
| "dark trap snare" | Filtered snares with dark/distorted character |
| "something like Travis Scott" | Reverby 808s, ambient pads, auto-tune style |
| "punchy kick 90bpm" | Tempo-matched kicks with transient impact |
| "ethereal melody F minor" | Key-matched melodic samples |
Search Implementation
// sample-search.ts
interface Sample {
id: string;
name: string;
category: string;
tags: string[];
bpm: number | null;
key: string | null;
duration: number;
waveformPeaks: number[];
embedding: number[]; // 768-dim BGE embedding
url: string;
}
async function searchSamples(query: string, filters?: SearchFilters) {
// Generate embedding for query
const queryEmbedding = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: query
});
// Search Vectorize index
const results = await env.SAMPLES_INDEX.query(queryEmbedding.data[0], {
topK: 20,
filter: buildFilter(filters)
});
// Fetch full sample metadata
const samples = await Promise.all(
results.matches.map(m => env.DB.prepare(
'SELECT * FROM samples WHERE id = ?'
).bind(m.id).first())
);
return samples;
}
function buildFilter(filters?: SearchFilters): VectorizeFilter {
const conditions: any = {};
if (filters?.category) {
conditions.category = { $eq: filters.category };
}
if (filters?.bpmRange) {
conditions.bpm = {
$gte: filters.bpmRange[0],
$lte: filters.bpmRange[1]
};
}
if (filters?.key) {
conditions.key = { $eq: filters.key };
}
return conditions;
}
π Sample Metadata Schema
-- D1 Schema
CREATE TABLE samples (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
filename TEXT NOT NULL,
category TEXT NOT NULL,
subcategory TEXT,
tags TEXT, -- JSON array
-- Audio properties
duration_ms INTEGER NOT NULL,
bpm REAL,
key TEXT,
sample_rate INTEGER DEFAULT 44100,
bit_depth INTEGER DEFAULT 16,
channels INTEGER DEFAULT 2,
-- Analysis
loudness_lufs REAL,
peak_db REAL,
spectral_centroid REAL,
-- Display
waveform_peaks TEXT, -- JSON array for visualization
color TEXT, -- Category color hint
-- Metadata
source TEXT, -- e.g., "government", "freesound", "user"
license TEXT,
attribution TEXT,
-- Storage
r2_key TEXT NOT NULL,
file_size INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_samples_category ON samples(category);
CREATE INDEX idx_samples_bpm ON samples(bpm);
CREATE INDEX idx_samples_key ON samples(key);
π΅ Audio Analysis Pipeline
Samples are analyzed on upload to extract metadata automatically.
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Upload β β Analyze β β Embed β β Index β
β Sample βββββΆβ Audio βββββΆβ Tags βββββΆβ Store β
β (R2) β β (Worker) β β (AI) β β (D1+Vec) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
Analysis Functions
| Analysis | Method | Output |
|---|---|---|
| BPM Detection | Essentia.js / Web Audio | Float (e.g., 140.5) |
| Key Detection | Chroma analysis | String (e.g., "F#m") |
| Loudness | LUFS calculation | Float (e.g., -14.2) |
| Waveform Peaks | Downsampled max values | Array[100] |
| Category | AI classification | String (e.g., "drums/kick") |
| Embedding | BGE text encoder | Float[768] |
// analyze-sample.ts
async function analyzeSample(audioBuffer: ArrayBuffer): Promise<SampleAnalysis> {
const audioContext = new OfflineAudioContext(2, 44100 * 30, 44100);
const buffer = await audioContext.decodeAudioData(audioBuffer);
// Extract features
const channelData = buffer.getChannelData(0);
return {
duration_ms: Math.round(buffer.duration * 1000),
sample_rate: buffer.sampleRate,
channels: buffer.numberOfChannels,
waveform_peaks: computePeaks(channelData, 100),
loudness_lufs: calculateLUFS(channelData, buffer.sampleRate),
peak_db: calculatePeakDb(channelData),
bpm: await detectBPM(channelData, buffer.sampleRate),
key: await detectKey(channelData, buffer.sampleRate)
};
}
function computePeaks(data: Float32Array, numPeaks: number): number[] {
const peaks: number[] = [];
const samplesPerPeak = Math.floor(data.length / numPeaks);
for (let i = 0; i < numPeaks; i++) {
let max = 0;
const start = i * samplesPerPeak;
for (let j = start; j < start + samplesPerPeak && j < data.length; j++) {
max = Math.max(max, Math.abs(data[j]));
}
peaks.push(max);
}
return peaks;
}
π¦ Storage Architecture
| Storage | Content | Access Pattern |
|---|---|---|
| R2 (samples/) | Audio files (WAV/MP3) | CDN cached, range requests |
| R2 (waveforms/) | Pre-rendered waveform images | CDN cached |
| D1 | Sample metadata | Filtered queries |
| Vectorize | Semantic embeddings | Similarity search |
R2 Key Structure
samples/
library/ # Built-in samples
drums/
kicks/
808-deep-01.wav
808-punchy-01.wav
snares/
hats/
melodic/
fx/
user/ # User uploads
{user_id}/
{sample_id}.wav
temp/ # Processing queue
{job_id}.wav
ποΈ Sample Browser UI
The sample browser provides multiple ways to find sounds.
UI Components
- Search Bar: Natural language + filters
- Category Tree: Hierarchical navigation
- Tag Cloud: Quick tag filtering
- Waveform Preview: Visual + hover-to-play
- Drag-to-Timeline: Direct placement
- Favorites: Quick access list
- Recent: Recently used samples
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Space | Preview selected sample |
| Enter | Add to current track |
| β/β | Navigate list |
| Cmd/Ctrl + F | Focus search |
| F | Toggle favorite |
π€ User Uploads
Users can upload their own samples to their personal library.
| Tier | Storage Limit | Max File Size |
|---|---|---|
| Free | 100MB | 10MB |
| Pro | 5GB | 50MB |
| Enterprise | 50GB | 200MB |
Upload Flow
// upload.ts
async function uploadSample(file: File, userId: string) {
// Validate
if (!['audio/wav', 'audio/mp3', 'audio/mpeg'].includes(file.type)) {
throw new Error('Unsupported format');
}
// Check quota
const usage = await getUserStorageUsage(userId);
if (usage + file.size > getUserStorageLimit(userId)) {
throw new Error('Storage quota exceeded');
}
// Generate ID and key
const sampleId = crypto.randomUUID();
const r2Key = `samples/user/${userId}/${sampleId}.wav`;
// Upload to R2
await env.SAMPLES_BUCKET.put(r2Key, file.stream());
// Analyze async
await env.ANALYSIS_QUEUE.send({
sampleId,
r2Key,
userId
});
return { sampleId, status: 'processing' };
}
π° Cost Breakdown
| Component | Cost (10K users) |
|---|---|
| R2 Storage (library) | $1.50 (100GB @ $0.015/GB) |
| R2 Storage (user uploads) | $7.50 (500GB) |
| Vectorize (5M vectors) | $0 (free tier) |
| D1 Database | $0 (free tier) |
| Workers AI (embeddings) | $2.00 |
| Total | ~$11/mo |
R2 Advantage: Zero egress fees mean sample streaming is essentially free regardless of how many times samples are previewed.