|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 |
|
3 | | -##@file wav2mozzi.py |
4 | | -# @ingroup util |
5 | | -# A script for converting .WAV sound files to wavetables for Mozzi. |
6 | | -# |
7 | | -# Usage: |
8 | | -# >>>wav2mozzi.py infile [-t tablename] [-o outfile] [--output-bits {8,16}] [--no-symmetric-output] |
9 | | -# |
| 3 | +""" |
| 4 | +A script for converting .WAV files to wavetables for Mozzi. |
| 5 | +
|
| 6 | +Reads bitness, sample format, and sample rate from the WAV header automatically. |
| 7 | +Supports 8-bit unsigned, 16-bit signed, 24-bit signed, and 32-bit signed PCM WAV files, |
| 8 | +as well as 32-bit IEEE float WAV files (samples in -1.0..1.0 range). |
| 9 | +
|
| 10 | +All sample data is converted to signed 8-bit or 16-bit values. |
| 11 | +Output values are centered around 0 and can be negated without overflow. |
| 12 | +If audio file is stereo, only the first channel is used. |
| 13 | +
|
| 14 | +Requires Python 3.9+, no dependencies. |
| 15 | +
|
| 16 | +NOTE: Using Audacity to prepare sound files: |
| 17 | + |
| 18 | +For generated waveforms like sine or sawtooth, set the project |
| 19 | +rate to the size of the wavetable you wish to create, which must |
| 20 | +be a power of two (eg. 8192), and set the selection format |
| 21 | +(beneath the editing window) to samples. Then you can generate |
| 22 | +and save 1 second of a waveform and it will fit your table |
| 23 | +length. |
| 24 | +
|
| 25 | +For a recorded audio sample, set the project rate to the |
| 26 | +MOZZI_AUDIO_RATE (16384 in the current version). |
| 27 | +Samples can be any length, as long as they fit in your Arduino. |
| 28 | + |
| 29 | +Save the file by "Export" -> "Export as WAV". |
| 30 | +To keep all the details, choose "32-bit float" encoding. |
| 31 | +Other supported encodings are 8,16,24,32-bit PCM. |
| 32 | + |
| 33 | +Now use the file you just exported, as the "infile" to convert. |
| 34 | +
|
| 35 | +
|
| 36 | +Author: Paul Melnikov, 2026-04 |
| 37 | +""" |
| 38 | + |
| 39 | +# Usage: |
| 40 | +# >>>wav2mozzi.py infile [-t tablename] [-o outfile] [-b {8,16}] [--no-symmetric-output] |
10 | 41 | # Arguments: |
11 | | -# * infile The .WAV file to convert. |
| 42 | +# * infile The .WAV file to convert. |
12 | 43 | # * -t tablename (Optional) The name to give the table. Default: uppercase input filename. |
13 | | -# * -o outfile (Optional) The output .h file. Default: derived from input filename. |
14 | | -# * -b, --output-bits |
| 44 | +# * -o outfile (Optional) The output .h file. Default: derived from input filename. |
| 45 | +# * -b, --output-bits |
15 | 46 | # (Optional) Output sample size in bits. Allowed: 8 or 16. Default: 8. |
16 | | -# * --symmetric-output, --no-symmetric-output |
| 47 | +# * --symmetric-output, --no-symmetric-output |
17 | 48 | # (Optional) Generate symmetric signed output range. Default: enabled. |
18 | | -# |
19 | | -# Reads bitness, sample format, and sample rate from the WAV header automatically. |
20 | | -# Supports 8-bit unsigned, 16-bit signed, 24-bit signed, and 32-bit signed PCM WAV files, |
21 | | -# as well as 32-bit IEEE float WAV files (samples in -1.0..1.0 range). |
22 | | -# |
23 | | -# All sample data is converted to signed 8-bit (-128..127). |
24 | | -# If audio is stereo, only the first channel is used. |
25 | | -# |
26 | | -# Requires Python 3.9+, no dependencies. |
27 | | -# |
| 49 | + |
28 | 50 |
|
29 | 51 | import sys, os, textwrap, struct, random, argparse, re |
30 | 52 |
|
31 | 53 | def read_wav(infile): |
32 | 54 | """Read a WAV file, supporting both PCM and IEEE float formats. |
33 | | - Returns (nchannels, sample_size, samplerate, nframes, raw_bytes, is_float).""" |
| 55 | + Returns (nchannels, sample_size, samplerate, nframes, raw_bytes, is_float). |
| 56 | + |
| 57 | + Technically, python has built-in "wave" library for this, |
| 58 | + but it doesn't support IEEE floats, so decode headers manually. |
| 59 | + """ |
34 | 60 | with open(infile, 'rb') as f: |
35 | 61 | # Parse RIFF header |
36 | 62 | riff = f.read(4) |
@@ -201,14 +227,16 @@ def wav2mozzi(infile, outfile, tablename, output_bytes=1, symmetric_output=True) |
201 | 227 |
|
202 | 228 |
|
203 | 229 | if __name__ == "__main__": |
204 | | - parser = argparse.ArgumentParser(description='Convert a .WAV file to a Mozzi wavetable header.') |
| 230 | + parser = argparse.ArgumentParser( |
| 231 | + description=__doc__, |
| 232 | + formatter_class=argparse.RawDescriptionHelpFormatter) |
205 | 233 | parser.add_argument('infile', help='Input .WAV file') |
206 | 234 | parser.add_argument('-t', '--tablename', help='Table name for the generated header (default: uppercase input filename)') |
207 | 235 | parser.add_argument('-o', '--outfile', help='Output .h file (default: derived from input filename)') |
208 | 236 | parser.add_argument('-b', '--output-bits', type=int, choices=(8, 16), default=8, |
209 | 237 | help='Output sample size in bits (default: 8)') |
210 | 238 | parser.add_argument('-s', '--symmetric-output', action=argparse.BooleanOptionalAction, default=True, |
211 | | - help='Generate a symmetric signed output range (default: enabled)') |
| 239 | + help='Generate symmetric signed output range (default: enabled)') |
212 | 240 | args = parser.parse_args() |
213 | 241 |
|
214 | 242 | infile = os.path.expanduser(args.infile) |
|
0 commit comments