blob: 89d9ae1b7bc92a2546e24cfe79933cd74728b000 [file] [log] [blame]
Alex Tawsedaba3cf2023-09-29 15:55:38 +01001# SPDX-FileCopyrightText: Copyright 2021, 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
alexander3c798932021-03-26 21:42:19 +00002# SPDX-License-Identifier: Apache-2.0
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17Utility script to convert a set of audio clip in a given location into
18corresponding cpp files and a single hpp file referencing the vectors
19from the cpp files.
20"""
21import datetime
22import glob
23import math
Richard Burton17069622022-03-17 10:54:26 +000024from argparse import ArgumentParser
Alex Tawsedaba3cf2023-09-29 15:55:38 +010025from pathlib import Path
alexander3c798932021-03-26 21:42:19 +000026
27import numpy as np
alexander3c798932021-03-26 21:42:19 +000028from jinja2 import Environment, FileSystemLoader
Richard Burton17069622022-03-17 10:54:26 +000029
Alex Tawsedaba3cf2023-09-29 15:55:38 +010030from gen_utils import GenUtils, AudioSample
alexander3c798932021-03-26 21:42:19 +000031
Alex Tawsedaba3cf2023-09-29 15:55:38 +010032# pylint: disable=duplicate-code
alexander3c798932021-03-26 21:42:19 +000033parser = ArgumentParser()
Alex Tawsedaba3cf2023-09-29 15:55:38 +010034
35parser.add_argument(
36 "--audio_path",
37 type=str,
38 help="path to audio folder to convert."
39)
40
41parser.add_argument(
42 "--source_folder_path",
43 type=str,
44 help="path to source folder to be generated."
45)
46
47parser.add_argument(
48 "--header_folder_path",
49 type=str,
50 help="path to header folder to be generated."
51)
52
53parser.add_argument(
54 "--sampling_rate",
55 type=int,
56 help="target sampling rate.",
57 default=16000
58)
59
60parser.add_argument(
61 "--mono",
62 type=bool,
63 help="convert signal to mono.",
64 default=True
65)
66
67parser.add_argument(
68 "--offset",
69 type=float,
70 help="start reading after this time (in seconds).",
71 default=0
72)
73
74parser.add_argument(
75 "--duration",
76 type=float,
77 help="only load up to this much audio (in seconds).",
78 default=0
79)
80
81parser.add_argument(
82 "--res_type",
83 type=GenUtils.res_data_type,
84 help=f"Resample type: {GenUtils.res_type_list()}.",
85 default='kaiser_best'
86)
87
88parser.add_argument(
89 "--min_samples",
90 type=int,
91 help="Minimum sample number.",
92 default=16000
93)
94
95parser.add_argument(
96 "--license_template",
97 type=str,
98 help="Header template file",
99 default="header_template.txt"
100)
101
102parser.add_argument(
103 "-v",
104 "--verbosity",
105 action="store_true"
106)
107
108parsed_args = parser.parse_args()
alexander3c798932021-03-26 21:42:19 +0000109
Richard Burton17069622022-03-17 10:54:26 +0000110env = Environment(loader=FileSystemLoader(Path(__file__).parent / 'templates'),
alexander3c798932021-03-26 21:42:19 +0000111 trim_blocks=True,
112 lstrip_blocks=True)
113
114
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100115# pylint: enable=duplicate-code
116def write_hpp_file(
117 header_filepath,
118 header,
119 num_audios,
120 audio_array_namesizes
121):
122 """
123 Write audio hpp file
124
125 @param header_filepath: .hpp filepath
126 @param header: Rendered header
127 @param num_audios: Audio file index
128 @param audio_array_namesizes: Audio array name sizes
129 """
alexander3c798932021-03-26 21:42:19 +0000130 print(f"++ Generating {header_filepath}")
131
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100132 env \
133 .get_template('AudioClips.hpp.template') \
134 .stream(common_template_header=header,
135 clips_count=num_audios,
136 varname_size=audio_array_namesizes) \
alexander3c798932021-03-26 21:42:19 +0000137 .dump(str(header_filepath))
138
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100139
140def write_cc_file(
141 cc_filepath,
142 header,
143 num_audios,
144 audio_filenames,
145 audio_array_namesizes
146):
147 """
148 Write cc file
149
150 @param cc_filepath: .cc filepath
151 @param header: Rendered header
152 @param num_audios: Audio file index
153 @param audio_filenames: Audio filenames
154 @param audio_array_namesizes: Audio array name sizes
155 """
alexander3c798932021-03-26 21:42:19 +0000156 print(f"++ Generating {cc_filepath}")
157
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100158 env \
159 .get_template('AudioClips.cc.template') \
160 .stream(common_template_header=header,
161 clips_count=num_audios,
162 var_names=(name for name, _ in audio_array_namesizes),
163 clip_sizes=(size for _, size in audio_array_namesizes),
164 clip_names=audio_filenames) \
alexander3c798932021-03-26 21:42:19 +0000165 .dump(str(cc_filepath))
166
167
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100168def write_individual_audio_cc_file(
169 resampled_audio: AudioSample,
170 clip_filename,
171 cc_filename,
172 header_template_file,
173 array_name
174):
175 """
176 Writes the provided audio sample to a .cc file
177
178 @param resampled_audio: Audio sample to write
179 @param clip_filename: File name of the clip
180 @param cc_filename: File name of the .cc file
181 @param header_template_file: Header template
182 @param array_name: Name of the array to write
183 @return: Array length of the audio data written
184 """
Richard Burton17069622022-03-17 10:54:26 +0000185 print(f"++ Converting {clip_filename} to {Path(cc_filename).name}")
alexander3c798932021-03-26 21:42:19 +0000186
187 # Change from [-1, 1] fp32 range to int16 range.
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100188 clip_data = np.clip((resampled_audio.data * (1 << 15)),
Kshitij Sisodia659fcd92021-05-19 10:30:06 +0100189 np.iinfo(np.int16).min,
alexander3c798932021-03-26 21:42:19 +0000190 np.iinfo(np.int16).max).flatten().astype(np.int16)
191
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100192 hdr = GenUtils.gen_header(env, header_template_file, clip_filename)
alexander3c798932021-03-26 21:42:19 +0000193
194 hex_line_generator = (', '.join(map(hex, sub_arr))
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100195 for sub_arr in np.array_split(clip_data, math.ceil(len(clip_data) / 20)))
alexander3c798932021-03-26 21:42:19 +0000196
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100197 env \
198 .get_template('audio.cc.template') \
199 .stream(common_template_header=hdr,
200 size=len(clip_data),
201 var_name=array_name,
202 audio_data=hex_line_generator) \
alexander3c798932021-03-26 21:42:19 +0000203 .dump(str(cc_filename))
204
205 return len(clip_data)
206
207
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100208def create_audio_cc_file(args, filename, array_name, clip_dirpath):
209 """
210 Create an individual audio cpp file
211
212 @param args: User-specified args
213 @param filename: Audio filename
214 @param array_name: Name of the array in the audio .cc file
215 @param clip_dirpath: Audio file directory path
216 @return: Array length of the audio data written
217 """
218 cc_filename = (Path(args.source_folder_path) /
219 (Path(filename).stem.replace(" ", "_") + ".cc"))
220 audio_filepath = Path(clip_dirpath) / filename
221 audio_sample = GenUtils.read_audio_file(audio_filepath, args.offset, args.duration)
222 resampled_audio = GenUtils.resample_audio_clip(
223 audio_sample, args.sampling_rate, args.mono, args.res_type, args.min_samples
224 )
225 return write_individual_audio_cc_file(
226 resampled_audio, filename, cc_filename, args.license_template, array_name,
227 )
228
229
alexander3c798932021-03-26 21:42:19 +0000230def main(args):
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100231 """
232 Convert audio files to .cc + .hpp files
233 @param args: Parsed args
234 """
alexander3c798932021-03-26 21:42:19 +0000235 # Keep the count of the audio files converted
236 audioclip_idx = 0
237 audioclip_filenames = []
238 audioclip_array_names = []
239 header_filename = "InputFiles.hpp"
240 common_cc_filename = "InputFiles.cc"
Richard Burton17069622022-03-17 10:54:26 +0000241 header_filepath = Path(args.header_folder_path) / header_filename
242 common_cc_filepath = Path(args.source_folder_path) / common_cc_filename
alexander3c798932021-03-26 21:42:19 +0000243
Richard Burton17069622022-03-17 10:54:26 +0000244 if Path(args.audio_path).is_dir():
245 filepaths = sorted(glob.glob(str(Path(args.audio_path) / '**/*.wav'), recursive=True))
246 elif Path(args.audio_path).is_file():
alexander3c798932021-03-26 21:42:19 +0000247 filepaths = [args.audio_path]
248 else:
249 raise OSError("Directory or file does not exist.")
250
251 for filepath in filepaths:
Richard Burton17069622022-03-17 10:54:26 +0000252 filename = Path(filepath).name
253 clip_dirpath = Path(filepath).parent
alexander3c798932021-03-26 21:42:19 +0000254 try:
255 audioclip_filenames.append(filename)
256
257 # Save the cc file
alexander3c798932021-03-26 21:42:19 +0000258 array_name = "audio" + str(audioclip_idx)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100259 array_size = create_audio_cc_file(args, filename, array_name, clip_dirpath)
alexander3c798932021-03-26 21:42:19 +0000260
261 audioclip_array_names.append((array_name, array_size))
262 # Increment audio index
263 audioclip_idx = audioclip_idx + 1
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100264 except OSError:
alexander3c798932021-03-26 21:42:19 +0000265 if args.verbosity:
266 print(f"Failed to open {filename} as an audio.")
267
Kshitij Sisodia659fcd92021-05-19 10:30:06 +0100268 if len(audioclip_filenames) > 0:
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100269 header = env \
270 .get_template(args.license_template) \
271 .render(script_name=Path(__file__).name,
272 gen_time=datetime.datetime.now(),
273 year=datetime.datetime.now().year)
274
275 write_hpp_file(
276 header_filepath,
277 header,
278 audioclip_idx,
279 audioclip_array_names
280 )
281
282 write_cc_file(
283 common_cc_filepath,
284 header,
285 audioclip_idx,
286 audioclip_filenames,
287 audioclip_array_names
288 )
289
Kshitij Sisodia659fcd92021-05-19 10:30:06 +0100290 else:
291 raise FileNotFoundError("No valid audio clip files found.")
alexander3c798932021-03-26 21:42:19 +0000292
293
294if __name__ == '__main__':
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100295 main(parsed_args)