Skip to content

Commit 53921fa

Browse files
committed
Add new audio2mozzy.py
The script allows to create mozzi tables from raw files and it's supposed to replace all the "*2mozzy.py" scripts thanks to its high configurability (it also supports int16/int32 tables in case they'll be supported in the future)
1 parent 091c319 commit 53921fa

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

extras/python/audio2mozzi.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
"""
3+
For converting raw audio files to Mozzi table
4+
5+
To generate waveforms using Audacity:
6+
Set the project rate to the size of the wavetable you wish to create, which must
7+
be a power of two (eg. 8192), and set the selection format
8+
(beneath the editing window) to samples. Then you can generate
9+
and save 1 second of a waveform and it will fit your table
10+
length.
11+
12+
To convert samples using Audacity:
13+
For a recorded audio sample, set the project rate to the
14+
MOZZI_AUDIO_RATE (16384 in the current version).
15+
Samples can be any length, as long as they fit in your Arduino.
16+
Save by exporting with the format set to "Other uncompressed formats",
17+
"Header: RAW(headerless)" and choose the encoding you prefer (Signed 8/16/32-bit PCM or 32-bit Float).
18+
19+
Alternative to convert samples via CLI using sox (http://sox.sourceforge.net/):
20+
sox <inputfile> -b <8/16/32> -c 1 -e <floating-point/signed-integer> -r 16384 <outputfile>
21+
22+
Now use the file you just exported, as the "input_file" to convert and
23+
set the other parameters according to what you chose for conversion.
24+
"""
25+
26+
import array
27+
import math
28+
import random
29+
import sys
30+
import textwrap
31+
from argparse import ArgumentParser
32+
from pathlib import Path
33+
34+
35+
def float2mozzi(args):
36+
input_path = args.input_file.expanduser()
37+
output_path = (
38+
args.output_file.expanduser()
39+
if args.output_file is not None
40+
else input_path.with_suffix(".h")
41+
)
42+
43+
with input_path.open("rb") as fin, output_path.open("w") as fout:
44+
print(f"opened {input_path}")
45+
num_input_values = int(
46+
input_path.stat().st_size / (args.input_bits / 8)
47+
) # Adjust for number format (table at top of https://docs.python.org/3/library/array.html)
48+
49+
array_type = ""
50+
if args.input_bits == 8:
51+
array_type = "b"
52+
elif args.input_bits == 16:
53+
array_type = "h"
54+
elif args.input_bits == 32:
55+
array_type = "f" if args.input_encoding == "float" else "i"
56+
57+
valuesfromfile = array.array(array_type)
58+
try:
59+
valuesfromfile.fromfile(fin, num_input_values)
60+
except EOFError:
61+
pass
62+
values = valuesfromfile.tolist()
63+
64+
tablename = (
65+
args.table_name
66+
if args.table_name is not None
67+
else output_path.stem.replace("-", "_").upper()
68+
)
69+
70+
fout.write(f"#ifndef {tablename}_H_\n")
71+
fout.write(f"#define {tablename}_H_\n\n")
72+
fout.write("#include <Arduino.h>\n")
73+
fout.write('#include "mozzi_pgmspace.h"\n\n')
74+
fout.write(f"#define {tablename}_NUM_CELLS {len(values)}\n")
75+
fout.write(f"#define {tablename}_SAMPLERATE {args.sample_rate}\n\n")
76+
77+
table = f"CONSTTABLE_STORAGE(int{args.output_bits}_t) {tablename}_DATA [] = {{"
78+
max_output_value = 2 << (args.output_bits - 2) # Halved because signed
79+
for value in values:
80+
cnt_33 = 0
81+
out_value = (
82+
math.trunc((value * max_output_value) + 0.5)
83+
if args.input_encoding == "float"
84+
else value
85+
)
86+
# Mega2560 boards won't upload if there is 33, 33, 33 in the array, so dither the 3rd 33 if there is one
87+
if value == 33:
88+
cnt_33 += 1
89+
if cnt_33 == 3:
90+
out_value = random.choice((32, 34))
91+
cnt_33 = 0
92+
else:
93+
cnt_33 = 0
94+
table += f"{out_value}, "
95+
table += "};"
96+
table = textwrap.fill(table, 80)
97+
fout.write(table)
98+
fout.write("\n\n")
99+
fout.write(f"#endif /* {tablename}_H_ */\n")
100+
print(f"wrote {table} to {output_path}")
101+
102+
return 0
103+
104+
105+
if __name__ == "__main__":
106+
parser = ArgumentParser(
107+
description="Script for converting a raw audio file to a Mozzi table",
108+
)
109+
parser.add_argument(
110+
"-e",
111+
"--input-encoding",
112+
choices=("float", "int"),
113+
default="int",
114+
help="Input encoding",
115+
)
116+
parser.add_argument(
117+
"--input-bits",
118+
type=int,
119+
choices=(8, 16, 32),
120+
default=8,
121+
help="Number of bits for the INPUT encoding",
122+
)
123+
parser.add_argument(
124+
"--output-bits",
125+
type=int,
126+
choices=(8, 16, 32),
127+
default=8,
128+
help="Number of bits for each element of the OUTPUT table",
129+
)
130+
parser.add_argument("input_file", type=Path, help="Path to the input file")
131+
parser.add_argument(
132+
"-o",
133+
"--output-file",
134+
type=Path,
135+
help="Path to the output file. It will be input_file.h if not provided",
136+
)
137+
parser.add_argument(
138+
"-t",
139+
"--table-name",
140+
type=str,
141+
help="Name of the output table. If not provided, the name of the output will be used",
142+
)
143+
parser.add_argument(
144+
"-s",
145+
"--sample-rate",
146+
type=int,
147+
default=16384,
148+
help="Sample rate. Value of 16384 recommended",
149+
)
150+
151+
args_ = parser.parse_args()
152+
sys.exit(float2mozzi(args_))

0 commit comments

Comments
 (0)