Skip to content

Instantly share code, notes, and snippets.

@wchargin
Created May 11, 2023 21:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wchargin/3d461634cad2a264e6a17fde6a8c8bda to your computer and use it in GitHub Desktop.
Save wchargin/3d461634cad2a264e6a17fde6a8c8bda to your computer and use it in GitHub Desktop.
record sound when the mic
#!/bin/bash
# Extended from a work of Jürgen Hötzel: https://stackoverflow.com/a/2613888
noise_threshold=15 # out of 99
base_dir="${HOME}/recordings"
raw_dir="${base_dir}/tmp"
chunk_dir="${base_dir}/chunks"
seg_dir="${base_dir}/seg"
out_dir="${base_dir}/out"
sox_raw_options=( -t raw -r 48k -e signed -b 16 -c 1 )
# Chunk size. This affects buffering, but also how much silence you get around
# the start and end of a segment (since the final segment is a whole number of
# chunks*) and also segmentation (since segment boundaries occur when one chunk
# is quiet). With a sample rate of 48 kHz and a bitrate of 16 bits/sample, the
# buffer grows at 96 kB/second, so a 1MiB buffer makes for ~10.9-second chunks.
#
# * except maybe the last one if this program is terminated early
split_size=1048576 # 1MiB
mkdir -p "${raw_dir}" "${chunk_dir}" "${seg_dir}" "${out_dir}"
rawfifo="${raw_dir}/in.raw"
test -e "${rawfifo}" || mkfifo "${rawfifo}"
# Start recording and splitting in background.
rec "${sox_raw_options[@]}" - >"${rawfifo}" 2>/dev/null &
printf >&2 'pid[rec]: %s\n' "$!"
split -a8 -b "${split_size}" - <"${rawfifo}" "${chunk_dir}/chunk-" &
printf >&2 'pid[split]: %s\n' "$!"
read_max_level() {
sox "${sox_raw_options[@]}" "$1" -n stats -s 99 2>&1 |
awk '/^Max level/ { print int($3) }'
}
poll() {
status=1
any_split=-false
if [ "$1" = all ]; then
any_split=-true
fi
find "${chunk_dir}" -type f \( -size "${split_size}c" -o "${any_split}" \) -print0 |
while IFS= read -r -d '' raw; do
status=0
max_level="$(read_max_level "${raw}")"
printf >&2 '%s: max_level=%s: ' "$(basename ${raw})" "${max_level}"
if [ "${max_level}" -ge "${noise_threshold}" ]; then
printf >&2 'appending to segment\n'
mv "${raw}" "${seg_dir}"
fi
if [ "${max_level}" -lt "${noise_threshold}" ] || [ "$1" = all ]; then
[ -f "${raw}" ] && rm "${raw}"
if ! find "${seg_dir}" -type f | grep -q .; then
printf >&2 'nothing to do\n'
else
segname="$(date +%FT%T)"
printf >&2 'finalizing segment %s\n' "${segname}"
find "${seg_dir}" -type f -print0 | sort -z | xargs -0 cat |
sox "${sox_raw_options[@]}" - "${out_dir}/recording-${segname}.flac"
rm "${seg_dir}"/*
fi
fi
done
return "${status}"
}
in_cleanup=0
cleanup() {
if [ "${in_cleanup}" = 1 ]; then
printf >&2 'cleanup: interrupted: goodbye\n'
exit 1
fi
in_cleanup=1
printf >&2 'cleanup: polling\n'
poll all
exit 0
}
trap cleanup INT
while true; do
poll
sleep 1
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment