Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Wave Laravel Package

boyhagemann/wave

A Laravel package for generating and working with audio waveforms. Extract waveform data from audio files via a simple API, making it easy to add audio previews, players, and visual waveform displays to PHP applications.

Deep Wiki
Context7

Getting Started

boyhagemann/wave is a small, dependency-light reader for RIFF/WAVE files. It parses the container and exposes the format header plus the raw PCM payload — it does not play, resample, or encode audio, so treat it as a decoding primitive you compose with yourself.

Install it, then read the source: the entire package is a handful of classes, and the chunk-reading logic is the thing you actually need to understand.

composer require boyhagemann/wave

Start with the smallest possible probe against a known-good file:

use Boyhagemann\Wave\Wave;

$wave = Wave::open(storage_path('app/audio/voice.wav'));

$wave->getSampleRate();     // 44100
$wave->getChannels();       // 2
$wave->getBitsPerSample();  // 16
$wave->getDuration();       // seconds, derived from data size

The first real use case in a Laravel app is metadata extraction on upload — you rarely need the samples themselves to justify the dependency. Validate the mime type, open the file, persist sample_rate, channels, bits_per_sample, and duration to your model, and you have enough to make downstream decisions (which transcription tier to bill, whether to re-encode, whether a browser can play it natively).

A second, equally common first use case is waveform/peak generation. You read the samples once, reduce them to N buckets of min/max amplitude, store that JSON, and never touch the file again at render time.

Implementation Patterns

Metadata-then-samples. Open the file, read the header, and bail early if the format is unacceptable. Only call the method that materialises sample data once you've confirmed the header. This keeps the expensive part out of the common path:

$wave = Wave::open($path);

if ($wave->getBitsPerSample() !== 16 || $wave->getChannels() > 2) {
    throw new UnsupportedAudioException('Expected 16-bit mono/stereo PCM');
}

$samples = $wave->getSamples(); // only now do we pay for the read

Normalise to floats at the boundary. Raw integer samples differ in signedness and width between 8/16/24/32-bit. Convert once, at ingestion, into -1.0 … 1.0, and let the rest of your application speak one dialect:

$scale = 1 / (2 ** ($bits - 1));

$normalised = array_map(fn ($s) => $s * $scale, $samples);

De-interleave early. Stereo PCM is stored frame-interleaved (L R L R …). Split it into channels immediately after reading; every subsequent operation (downmix, per-channel peak detection, channel-specific silence trimming) is simpler on planar data.

Downmix in one pass when you need mono: average the channels per frame. Remember to subtract the DC offset first if the recording has one — averaging a biased signal halves the bias rather than removing it.

Peak extraction for UI. Do not ship raw samples to the frontend. Walk the sample array in windows and keep min/max per bucket; that reproduces the classic SoundCloud-style waveform at a few hundred bytes per file:

$bucketSize = (int) ceil(count($samples) / $width);
$peaks = [];

for ($i = 0; $i < count($samples); $i += $bucketSize) {
    $window = array_slice($samples, $i, $bucketSize);
    $peaks[] = [min($window), max($window)];
}

Queue the heavy work. Store the uploaded file on a disk, dispatch a job with the path, and do all reading inside the worker. Never parse audio inside the HTTP request — a 60-second 44.1 kHz stereo file is ~10 MB of PHP array memory once decoded, and you want that in a bounded process, not a web worker.

Path resolution under Laravel. The reader wants a filesystem path, so use Storage::path($relative) for local disks. For S3-backed disks, copy to a temp file first (Storage::readStream() into tmpfile() or a local scratch disk) and clean it up in a finally block.

Compose, don't extend. Keep a thin AudioInspector service that wraps the package and returns a value object (SampleRate, Duration, ChannelCount, Peaks). Your domain code depends on that object; when the underlying package changes or you outgrow it, you rewrite one class.

Silence and speech detection. Frame the samples (e.g. 20 ms windows), compute RMS per frame, and threshold against a noise floor estimated from the quietest decile. This is a cheap and surprisingly effective gate for trimming leading/trailing silence before transcription.

Gotchas and Tips

WAVE_FORMAT_EXTENSIBLE (0xFFFE). Files exported by many DAWs declare a format tag of 0xFFFE with the real format buried in a sub-format GUID inside the fmt chunk. A naive formatTag === 1 check will reject perfectly valid PCM files. Handle 0xFFFE by reading the sub-format's first two bytes, or you will field "why doesn't my file upload work" reports indefinitely.

Chunks are word-aligned. RIFF requires odd-sized chunks to be padded with a single zero byte. If you hand-roll any chunk-walking (or extend the parser), forget that padding and every subsequent chunk offset is wrong — usually manifesting as garbage field values or an infinite loop rather than an exception.

Trust, but verify, the data size. Streamed or truncated WAVs frequently carry 0xFFFFFFFF or a 0 in the data chunk size. Clamp the read to actual bytes available on disk, otherwise you'll attempt a multi-gigabyte fread and get a memory exhaustion rather than a clean error.

24-bit is the classic pain point. PHP has no native 24-bit integer type, and unpack('V') on a 3-byte sample requires manual byte assembly. Read three bytes, shift, and sign-extend manually:

$v = $b0 | ($b1 << 8) | ($b2 << 16);
if ($v & 0x800000) { $v -= 0x1000000; }

Also confirm how the package represents 24-bit data before assuming; this is the most likely place for a library-specific quirk.

Signedness of 8-bit samples. 8-bit PCM is unsigned (0–255, silence at 128) while 16- and 32-bit are signed two's-complement. Normalising all widths with one formula produces a DC-offset waveform for 8-bit files and no audible bug until someone plots it.

Float formats are IEEE-754 little-endian. Format tag 3 is 32-bit float -1.0 … 1.0 — already normalised, do not rescale it. Confusing float for int PCM yields samples that look plausible and sound like noise.

Byte order. The overwhelming majority of WAVs are little-endian (RIFF). A RIFX container is big-endian and will silently decode as nonsense. Reject RIFX explicitly rather than producing corrupt output.

Duration is derived, not declared. Compute it as dataSize / (sampleRate * channels * bitsPerSample / 8). It's approximate for VBR and wrong for truncated files; don't treat it as authoritative if frame-accurate sync matters.

Memory scales badly. A 5-minute 44.1 kHz stereo 16-bit file is ~53 MB of decoded PHP data, and PHP arrays are far larger than the raw bytes. Above a few MB, prefer chunked reading: fseek past the header once, then loop fread in fixed frames, process, and release. Cache the parsed fmt header so you don't reparse per chunk.

Validate mime loosely. finfo reports audio/x-wav, audio/wave, or audio/wav depending on libmagic version. Laravel's mimes:wav rule maps extensions, not magic bytes, so pair it with a header sniff (first four bytes are RIFF, bytes 8–11 are WAVE) for anything user-supplied.

LIST/INFO chunks are your free metadata. Many recorders embed artist, comment, and creation date chunks. If you need richer metadata than the fmt chunk provides, walking LIST sub-chunks is the extension point — and it's where hand-rolled parsers most often break on the word-alignment rule above.

Extension points. Keep the reader behind your own interface and treat chunk parsing as pluggable: fmt → format object, data → sample source, LIST → metadata object, unknown → skip. That way adding cue or smpl support later doesn't disturb call sites.

Debugging checklist for "wrong numbers": (1) is the file actually PCM or is it compressed (ADPCM, MP3-in-WAV)? (2) does the fmt chunk say 0xFFFE? (3) is the data chunk size sane against the file size on disk? (4) are you reading from a stream that has already been consumed? That ladder resolves almost every issue with this class of library.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource