Skip to content

Commit cf41db1

Browse files
committed
output_bit_depth manual test
1 parent 3728298 commit cf41db1

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# Record a longer I2S audio capture to a WAV file on an SD card, using the
6+
# `output_bit_depth` argument to have the driver downconvert each 32-bit slot
7+
# to signed 16-bit PCM. Compared to `i2sin_record_sdcard.py`, this avoids the
8+
# Python-side shift loop and writes directly from the recording buffer.
9+
#
10+
# Produces /sd/talk.wav. Set left_justified=True for SPH0645LM4H mics.
11+
12+
import array
13+
import struct
14+
import time
15+
16+
import board
17+
import sdcardio
18+
import storage
19+
import audioi2sin
20+
21+
# ---- Recording config ------------------------------------------------------
22+
SAMPLE_RATE = 16000
23+
RECORD_SECONDS = 10
24+
CHUNK_SAMPLES = 1024 # samples captured per record() call
25+
OUTPUT_PATH = "/sd/talk.wav"
26+
27+
# ---- Mount SD --------------------------------------------------------------
28+
spi = board.SPI()
29+
sdcard = sdcardio.SDCard(spi, cs=board.D10, baudrate=24_000_000)
30+
vfs = storage.VfsFat(sdcard)
31+
storage.mount(vfs, "/sd")
32+
33+
# ---- Mic -------------------------------------------------------------------
34+
# 24-bit MEMS mics ride in 32-bit slots. Ask the driver to downconvert each
35+
# slot to a signed 16-bit PCM sample, so `record()` writes straight into a
36+
# WAV-ready 'h' buffer.
37+
mic = audioi2sin.I2SIn(
38+
bit_clock=board.D5,
39+
word_select=board.D6,
40+
data=board.D9,
41+
sample_rate=SAMPLE_RATE,
42+
bit_depth=32,
43+
output_bit_depth=16,
44+
mono=True,
45+
left_justified=False, # True for SPH0645LM4H
46+
)
47+
48+
actual_rate = mic.sample_rate
49+
print("Recording at", actual_rate, "Hz for", RECORD_SECONDS, "s ->", OUTPUT_PATH)
50+
51+
pcm16 = array.array("h", [0] * CHUNK_SAMPLES)
52+
53+
54+
def write_wav_header(f, sample_rate, num_samples, bits_per_sample=16, channels=1):
55+
byte_rate = sample_rate * channels * bits_per_sample // 8
56+
block_align = channels * bits_per_sample // 8
57+
data_size = num_samples * block_align
58+
f.write(b"RIFF")
59+
f.write(struct.pack("<I", 36 + data_size))
60+
f.write(b"WAVE")
61+
f.write(b"fmt ")
62+
f.write(
63+
struct.pack(
64+
"<IHHIIHH",
65+
16, # fmt chunk size
66+
1, # PCM
67+
channels,
68+
sample_rate,
69+
byte_rate,
70+
block_align,
71+
bits_per_sample,
72+
)
73+
)
74+
f.write(b"data")
75+
f.write(struct.pack("<I", data_size))
76+
77+
78+
total_samples = actual_rate * RECORD_SECONDS
79+
written = 0
80+
81+
# Open with a write+read header placeholder; we'll seek back at the end to
82+
# patch in the real sample count.
83+
with open(OUTPUT_PATH, "wb") as f:
84+
write_wav_header(f, actual_rate, 0) # placeholder; rewritten below
85+
start = time.monotonic()
86+
87+
while written < total_samples:
88+
n = mic.record(pcm16, CHUNK_SAMPLES)
89+
# Driver already downconverted to int16; write only the valid portion.
90+
f.write(memoryview(pcm16)[:n])
91+
written += n
92+
93+
elapsed = time.monotonic() - start
94+
# Rewrite header now that we know the true sample count.
95+
f.seek(0)
96+
write_wav_header(f, actual_rate, written)
97+
98+
storage.umount("/sd")
99+
100+
print("Done. Wrote", written, "samples in", round(elapsed, 2), "s")

0 commit comments

Comments
 (0)