Skip to content

wavpack

Lossless archives of Intan integer data streams using the WavPack CLI.

Install the external wavpack and wvunpack programs separately. Archives are standard WavPack files, with no Python-specific container or sidecar.

compress_dat(input_path, n_channels, sample_rate, output_path=None, *, dtype='int16', executable=None, verify=True)

Compress an Intan .dat data stream into a lossless .wv archive.

Parameters:

Name Type Description Default
input_path str or Path

Headerless, little-endian integer data, interleaved by sample: channel 1 at sample 1, channel 2 at sample 1, ..., channel 1 at sample 2. Pass the original binary file directly, without scaling or conversion.

required
n_channels int

Number of values per sample frame actually saved in the file (1 to 4096). Use 1 for time.dat and bit-packed digitalin.dat/digitalout.dat, not the number of active digital lines.

required
sample_rate int

Sampling rate of this stream in Hz, for example 20000 or 30000.

required
output_path str or Path

New .wv file. Defaults to input_path with its suffix replaced by .wv. The parent directory must exist. Existing files are never overwritten.

None
dtype (int16, uint16, int32, uint32)

Original little-endian sample type. Default is "int16" for amplifier data. Use "int32" for time.dat and "uint16" for digital, analog, auxiliary, and supply streams. Unsigned values are reinterpreted as signed integers of the same width for encoding; their bits are never changed. There is no scaling, offset, or sample reordering.

"int16"
executable str or Path

Path to wavpack (wavpack.exe on Windows). By default, search PATH.

None
verify bool

Re-read and decode the archive after writing to verify recovery using WavPack's MD5 check. Default is True. The original data's MD5 is stored even if this extra verification pass is disabled.

True

Returns:

Type Description
Path

Absolute path to the completed .wv archive.

Raises:

Type Description
ValueError

Invalid recording parameters, empty input, incomplete sample frames, or an output filename without the .wv extension.

FileNotFoundError

Input, output directory, or WavPack executable is missing.

FileExistsError

The destination already exists, including the input itself.

RuntimeError

Compression or verification fails, or the source changes during work.

Notes

The source is never deleted. WavPack streams the recording using bounded memory; no entire-recording array is loaded into Python. A failed operation removes its temporary output. The archive stores channel count, sample rate, bit depth, and MD5. Keep info.rhd, session metadata, and each stream's dtype: WavPack stores unsigned streams as signed bit patterns; interpretation still uses the original Intan metadata. Compress each .dat stream separately. Structured spike.dat and .rhd/.rhs containers are not fixed-width streams supported by this function. Lossy and hybrid modes are not exposed.

Source code in neuro_py/raw/wavpack.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def compress_dat(
    input_path: str | Path,
    n_channels: int,
    sample_rate: int,
    output_path: str | Path | None = None,
    *,
    dtype: str = "int16",
    executable: str | Path | None = None,
    verify: bool = True,
) -> Path:
    """Compress an Intan .dat data stream into a lossless .wv archive.

    Parameters
    ----------
    input_path : str or pathlib.Path
        Headerless, little-endian integer data, interleaved by sample:
        channel 1 at sample 1, channel 2 at sample 1, ..., channel 1 at sample 2.
        Pass the original binary file directly, without scaling or conversion.
    n_channels : int
        Number of values per sample frame actually saved in the file (1 to
        4096). Use 1 for time.dat and bit-packed digitalin.dat/digitalout.dat,
        not the number of active digital lines.
    sample_rate : int
        Sampling rate of this stream in Hz, for example 20000 or 30000.
    output_path : str or pathlib.Path, optional
        New .wv file. Defaults to input_path with its suffix replaced by .wv.
        The parent directory must exist. Existing files are never overwritten.
    dtype : {"int16", "uint16", "int32", "uint32"}, optional
        Original little-endian sample type. Default is "int16" for amplifier
        data. Use "int32" for time.dat and "uint16" for digital, analog,
        auxiliary, and supply streams. Unsigned values are reinterpreted as
        signed integers of the same width for encoding; their bits are never
        changed. There is no scaling, offset, or sample reordering.
    executable : str or pathlib.Path, optional
        Path to wavpack (wavpack.exe on Windows). By default, search PATH.
    verify : bool, optional
        Re-read and decode the archive after writing to verify recovery using
        WavPack's MD5 check. Default is True. The original data's MD5 is stored
        even if this extra verification pass is disabled.

    Returns
    -------
    pathlib.Path
        Absolute path to the completed .wv archive.

    Raises
    ------
    ValueError
        Invalid recording parameters, empty input, incomplete sample frames,
        or an output filename without the .wv extension.
    FileNotFoundError
        Input, output directory, or WavPack executable is missing.
    FileExistsError
        The destination already exists, including the input itself.
    RuntimeError
        Compression or verification fails, or the source changes during work.

    Notes
    -----
    The source is never deleted. WavPack streams the recording using bounded
    memory; no entire-recording array is loaded into Python. A failed operation
    removes its temporary output. The archive stores channel count, sample rate,
    bit depth, and MD5. Keep info.rhd, session metadata, and each stream's dtype:
    WavPack stores unsigned streams as signed bit patterns; interpretation still
    uses the original Intan metadata. Compress each .dat stream separately.
    Structured spike.dat and .rhd/.rhs containers are not fixed-width streams
    supported by this function. Lossy and hybrid modes are not exposed.
    """
    n_channels = _positive_integer(n_channels, "n_channels", 4096)
    sample_rate = _positive_integer(sample_rate, "sample_rate", 1_000_000_000)
    bit_depths = {"int16": 16, "uint16": 16, "int32": 32, "uint32": 32}
    if dtype not in bit_depths:
        raise ValueError("dtype must be int16, uint16, int32, or uint32.")
    bits = bit_depths[dtype]
    bytes_per_frame = n_channels * (bits // 8)
    source = _source(input_path)
    output = _destination(source, output_path, ".wv")
    before = _signature(source)
    size = before[2]
    if size == 0 or size % bytes_per_frame:
        raise ValueError(
            "Input must contain complete, nonempty integer channel frames."
        )
    if size // bytes_per_frame > (1 << 40) - 257:
        raise ValueError("Recording exceeds WavPack's maximum sample count.")
    program = _executable("wavpack", executable)
    with tempfile.TemporaryDirectory(
        prefix=".wavpack-", dir=output.parent
    ) as directory:
        staged = Path(directory) / output.name
        arguments = [
            program,
            # Extended raw mode bypasses the CLI's conservative 256-channel
            # default while retaining the format's real channel count.
            f"--raw-pcm{'-ex' if n_channels > 256 else ''}="
            f"{sample_rate},{bits}s,{n_channels},le",
            "--channel-order=...",
            "--pair-unassigned-chans",
            "-m",
            "-q",
        ]
        if verify:
            arguments.append("-v")
        arguments.extend([str(source), "-o", str(staged)])
        _run(arguments)
        if _signature(source) != before:
            raise RuntimeError(
                "Source changed during compression; archive not published."
            )
        if not staged.is_file() or staged.stat().st_size == 0:
            raise RuntimeError("WavPack did not create a nonempty archive.")
        _publish(staged, output)
    return output

decompress_dat(input_path, output_path=None, *, executable=None, verify=True)

Restore an Intan .dat recording from a standard lossless WavPack archive.

Parameters:

Name Type Description Default
input_path str or Path

A lossless .wv archive of 16- or 32-bit integer samples, such as one created by compress_dat. Hybrid/lossy and other bit depths are rejected.

required
output_path str or Path

New .dat file. Defaults to input_path with its suffix replaced by .dat. Use a different filename if the original .dat is still present. The parent directory must exist. Existing files are never overwritten.

None
executable str or Path

Path to wvunpack (wvunpack.exe on Windows). By default, search PATH.

None
verify bool

Check restored bytes against the MD5 stored in the archive. Default is True, which requires an embedded MD5 (always stored by compress_dat). WavPack's block checks remain active when False.

True

Returns:

Type Description
Path

Absolute path to the restored, headerless .dat, with the original bytes. No dtype, channel count, or rate is needed for restoration.

Raises:

Type Description
ValueError

The output filename does not have the .dat extension.

FileNotFoundError

Input, output directory, or WvUnpack executable is missing.

FileExistsError

The destination already exists, including the archive itself.

RuntimeError

The archive is unsupported, corrupt, changed during work, lacks an MD5 required for verification, or cannot be completely decoded.

Notes

The archive is never deleted. Raw-output mode prevents insertion of a WAV header. Decoding streams through WvUnpack using bounded memory. Temporary output is cleaned on failure; completed output is never overwritten.

Source code in neuro_py/raw/wavpack.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def decompress_dat(
    input_path: str | Path,
    output_path: str | Path | None = None,
    *,
    executable: str | Path | None = None,
    verify: bool = True,
) -> Path:
    """Restore an Intan .dat recording from a standard lossless WavPack archive.

    Parameters
    ----------
    input_path : str or pathlib.Path
        A lossless .wv archive of 16- or 32-bit integer samples, such as one
        created by compress_dat. Hybrid/lossy and other bit depths are rejected.
    output_path : str or pathlib.Path, optional
        New .dat file. Defaults to input_path with its suffix replaced by .dat.
        Use a different filename if the original .dat is still present. The
        parent directory must exist. Existing files are never overwritten.
    executable : str or pathlib.Path, optional
        Path to wvunpack (wvunpack.exe on Windows). By default, search PATH.
    verify : bool, optional
        Check restored bytes against the MD5 stored in the archive. Default
        is True, which requires an embedded MD5 (always stored by compress_dat).
        WavPack's block checks remain active when False.

    Returns
    -------
    pathlib.Path
        Absolute path to the restored, headerless .dat, with the original bytes.
        No dtype, channel count, or rate is needed for restoration.

    Raises
    ------
    ValueError
        The output filename does not have the .dat extension.
    FileNotFoundError
        Input, output directory, or WvUnpack executable is missing.
    FileExistsError
        The destination already exists, including the archive itself.
    RuntimeError
        The archive is unsupported, corrupt, changed during work, lacks an MD5
        required for verification, or cannot be completely decoded.

    Notes
    -----
    The archive is never deleted. Raw-output mode prevents insertion of a WAV
    header. Decoding streams through WvUnpack using bounded memory. Temporary
    output is cleaned on failure; completed output is never overwritten.
    """
    source = _source(input_path)
    output = _destination(source, output_path, ".dat")
    program = _executable("wvunpack", executable)
    before = _signature(source)
    fields = _run([program, "-f", str(source)]).strip().split(";", maxsplit=9)
    try:
        bits, channels, samples = int(fields[1]), int(fields[3]), int(fields[5])
        mode = int(fields[8], 0)
        supported = (
            bits in (16, 32)
            and fields[2] == "int"
            and 1 <= channels <= 4096
            and samples > 0
            and bool(mode & 0x2)  # MODE_LOSSLESS, from WavPack's public API.
            and not mode & 0x4  # MODE_HYBRID: reject even with a correction file.
        )
    except (ValueError, IndexError) as exc:
        raise RuntimeError("Cannot read WavPack recording metadata.") from exc
    if not supported:
        raise RuntimeError(
            "Archive must contain lossless, non-hybrid 16/32-bit integers."
        )
    if verify and re.fullmatch(r"[0-9a-fA-F]{32}", fields[6]) is None:
        raise RuntimeError("Archive has no stored MD5; cannot verify exact recovery.")
    with tempfile.TemporaryDirectory(
        prefix=".wavpack-", dir=output.parent
    ) as directory:
        staged = Path(directory) / output.name
        arguments = [program, "-r", "-q"]
        if verify:
            arguments.append("-m")
        arguments.extend([str(source), "-o", str(staged)])
        _run(arguments)
        if _signature(source) != before:
            raise RuntimeError(
                "Archive changed during decompression; output not published."
            )
        expected_size = samples * channels * (bits // 8)
        if not staged.is_file() or staged.stat().st_size != expected_size:
            raise RuntimeError(
                "Restored recording size does not match archive metadata."
            )
        # Native -m can report a mismatch without failing when an external file
        # was encoded with different byte order, signedness, or channel order.
        # Comparing the raw output itself makes exact recovery unambiguous.
        if verify and _md5(staged) != fields[6].lower():
            raise RuntimeError(
                "Restored recording MD5 does not match the original bytes."
            )
        _publish(staged, output)
    return output