|
1 | 1 | import asyncio |
| 2 | +import io |
| 3 | +import wave |
2 | 4 |
|
3 | 5 | import av |
4 | 6 | import numpy as np |
@@ -132,16 +134,14 @@ def from_bytes( |
132 | 134 | format: str = "s16", |
133 | 135 | channels: int = 1, |
134 | 136 | ) -> "PcmData": |
135 | | - """Create PcmData from raw PCM bytes (interleaved for multi-channel). |
136 | | -
|
137 | | - Args: |
138 | | - audio_bytes: Raw PCM data as bytes. |
139 | | - sample_rate: Sample rate in Hz. |
140 | | - format: Audio sample format, e.g. "s16" or "f32". |
141 | | - channels: Number of channels (1=mono, 2=stereo). |
142 | | -
|
143 | | - Returns: |
144 | | - PcmData object with numpy samples (mono: 1D, multi-channel: 2D [channels, samples]). |
| 137 | + """Build from raw PCM bytes (interleaved). |
| 138 | +
|
| 139 | + Example: |
| 140 | + >>> import numpy as np |
| 141 | + >>> b = np.array([1, -1, 2, -2], dtype=np.int16).tobytes() |
| 142 | + >>> pcm = PcmData.from_bytes(b, sample_rate=16000, format="s16", channels=2) |
| 143 | + >>> pcm.samples.shape[0] # channels-first |
| 144 | + 2 |
145 | 145 | """ |
146 | 146 | # Determine dtype and bytes per sample |
147 | 147 | dtype: Any |
@@ -203,10 +203,12 @@ def from_data( |
203 | 203 | format: str = "s16", |
204 | 204 | channels: int = 1, |
205 | 205 | ) -> "PcmData": |
206 | | - """Create PcmData from bytes or numpy arrays. |
| 206 | + """Build from bytes or numpy arrays. |
207 | 207 |
|
208 | | - - bytes-like: interpreted as interleaved PCM per channel. |
209 | | - - numpy arrays: accepts 1D [samples], 2D [channels, samples] or [samples, channels]. |
| 208 | + Example: |
| 209 | + >>> import numpy as np |
| 210 | + >>> PcmData.from_data(np.array([1, 2], np.int16), 16000, "s16", 1).channels |
| 211 | + 1 |
210 | 212 | """ |
211 | 213 | if isinstance(data, (bytes, bytearray, memoryview)): |
212 | 214 | return cls.from_bytes( |
@@ -264,18 +266,13 @@ def resample( |
264 | 266 | target_channels: Optional[int] = None, |
265 | 267 | resampler: Optional[Any] = None, |
266 | 268 | ) -> "PcmData": |
267 | | - """ |
268 | | - Resample PcmData to a different sample rate and/or channels using AV library. |
269 | | -
|
270 | | - Args: |
271 | | - target_sample_rate: Target sample rate in Hz |
272 | | - target_channels: Target number of channels (defaults to current) |
273 | | - resampler: Optional persistent AudioResampler instance to use. If None, |
274 | | - creates a new resampler (for one-off use). Pass a persistent |
275 | | - resampler to avoid discontinuities when resampling streaming chunks. |
| 269 | + """Resample to target sample rate/channels. |
276 | 270 |
|
277 | | - Returns: |
278 | | - New PcmData object with resampled audio |
| 271 | + Example: |
| 272 | + >>> import numpy as np |
| 273 | + >>> pcm = PcmData(np.arange(8, dtype=np.int16), 16000, "s16", 1) |
| 274 | + >>> pcm.resample(16000, target_channels=2).channels |
| 275 | + 2 |
279 | 276 | """ |
280 | 277 | if target_channels is None: |
281 | 278 | target_channels = self.channels |
@@ -410,11 +407,13 @@ def resample( |
410 | 407 | return self |
411 | 408 |
|
412 | 409 | def to_bytes(self) -> bytes: |
413 | | - """Return interleaved PCM bytes (s16 or f32 depending on format). |
| 410 | + """Return interleaved PCM bytes. |
414 | 411 |
|
415 | | - For multi-channel audio, this returns packed/interleaved bytes in the order |
416 | | - [L0, R0, L1, R1, ...]. The internal convention is (channels, samples). |
417 | | - If the stored ndarray is (samples, channels), we transpose it. |
| 412 | + Example: |
| 413 | + >>> import numpy as np |
| 414 | + >>> pcm = PcmData(np.array([[1, -1]], np.int16), 16000, "s16", 1) |
| 415 | + >>> len(pcm.to_bytes()) > 0 |
| 416 | + True |
418 | 417 | """ |
419 | 418 | arr = self.samples |
420 | 419 | if isinstance(arr, np.ndarray): |
@@ -449,15 +448,14 @@ def to_bytes(self) -> bytes: |
449 | 448 | return b"" |
450 | 449 |
|
451 | 450 | def to_wav_bytes(self) -> bytes: |
452 | | - """Return a complete WAV file (header + frames) as bytes. |
| 451 | + """Return WAV bytes (header + frames). |
453 | 452 |
|
454 | | - Notes: |
455 | | - - If the data format is not s16, it will be converted to s16. |
456 | | - - Channels and sample rate are taken from the PcmData instance. |
| 453 | + Example: |
| 454 | + >>> import numpy as np |
| 455 | + >>> pcm = PcmData(np.array([0, 0], np.int16), 16000, "s16", 1) |
| 456 | + >>> with open("out.wav", "wb") as f: # write to disk |
| 457 | + ... _ = f.write(pcm.to_wav_bytes()) |
457 | 458 | """ |
458 | | - import io |
459 | | - import wave |
460 | | - |
461 | 459 | # Ensure s16 frames |
462 | 460 | if self.format != "s16": |
463 | 461 | arr = self.samples |
@@ -492,12 +490,13 @@ def to_wav_bytes(self) -> bytes: |
492 | 490 | return buf.getvalue() |
493 | 491 |
|
494 | 492 | def to_float32(self) -> "PcmData": |
495 | | - """Return a new PcmData with samples converted to float32 in [-1.0, 1.0]. |
| 493 | + """Convert samples to float32 in [-1, 1]. |
496 | 494 |
|
497 | | - - If current format is "s16", scales int16 to float32 by 1/32768. |
498 | | - - If already "f32", ensures dtype is np.float32 and preserves values. |
499 | | - - Preserves sample_rate, pts, dts, time_base and channels. |
500 | | - - Preserves shape semantics (mono 1D, multi-channel 2D [channels, samples]). |
| 495 | + Example: |
| 496 | + >>> import numpy as np |
| 497 | + >>> pcm = PcmData(np.array([0, 1], np.int16), 16000, "s16", 1) |
| 498 | + >>> pcm.to_float32().samples.dtype == np.float32 |
| 499 | + True |
501 | 500 | """ |
502 | 501 | arr = self.samples |
503 | 502 |
|
@@ -541,15 +540,14 @@ def to_float32(self) -> "PcmData": |
541 | 540 | ) |
542 | 541 |
|
543 | 542 | def append(self, other: "PcmData") -> "PcmData": |
544 | | - """Append another PcmData to this one and return a new instance. |
545 | | -
|
546 | | - The input chunk is adjusted to match this instance's sample rate, |
547 | | - channel count, and sample format before concatenation. |
548 | | -
|
549 | | - Notes: |
550 | | - - Preserves shape semantics: mono as 1D, multi-channel as 2D [channels, samples]. |
551 | | - - Keeps metadata (sample_rate, format, channels, pts/dts/time_base) from self. |
552 | | - - Does not modify self; returns a new PcmData. |
| 543 | + """Append another chunk after adjusting it to match self. |
| 544 | +
|
| 545 | + Example: |
| 546 | + >>> import numpy as np |
| 547 | + >>> a = PcmData(np.array([1, 2], np.int16), 16000, "s16", 1) |
| 548 | + >>> b = PcmData(np.array([3, 4], np.int16), 16000, "s16", 1) |
| 549 | + >>> a.append(b).samples.tolist() |
| 550 | + [1, 2, 3, 4] |
553 | 551 | """ |
554 | 552 |
|
555 | 553 | # Early exits for empty cases |
@@ -717,14 +715,11 @@ def from_response( |
717 | 715 | channels: int = 1, |
718 | 716 | format: str = "s16", |
719 | 717 | ) -> Union["PcmData", Iterator["PcmData"], AsyncIterator["PcmData"]]: |
720 | | - """Create PcmData stream(s) from a provider response. |
721 | | -
|
722 | | - Supported inputs: |
723 | | - - bytes/bytearray/memoryview -> returns PcmData |
724 | | - - async iterator of bytes or objects with .data -> returns async iterator of PcmData |
725 | | - - iterator of bytes or objects with .data -> returns iterator of PcmData |
726 | | - - already PcmData -> returns PcmData |
727 | | - - single object with .data -> returns PcmData from its data |
| 718 | + """Normalize provider response to PcmData or iterators of it. |
| 719 | +
|
| 720 | + Example: |
| 721 | + >>> PcmData.from_response(b"\x00\x00", sample_rate=16000, format="s16").sample_rate |
| 722 | + 16000 |
728 | 723 | """ |
729 | 724 |
|
730 | 725 | # bytes-like returns a single PcmData |
|
0 commit comments