annotate tests/test_core.py @ 405:f56b4d8adfb8

Use numpy instead of audioop everywhere
author Amine Sehili <amine.sehili@gmail.com>
date Mon, 17 Jun 2024 19:45:51 +0200
parents 996948ada980
children 0e938065a2db
rev   line source
amine@403 1 import math
amine@192 2 import os
amine@88 3 from random import random
amine@192 4 from tempfile import TemporaryDirectory
amine@403 5 from unittest.mock import Mock, patch
amine@403 6
amine@405 7 import numpy as np
amine@400 8 import pytest
amine@403 9
amine@403 10 from auditok import AudioParameterError, AudioRegion, load, split
amine@323 11 from auditok.core import (
amine@323 12 _duration_to_nb_windows,
amine@323 13 _make_audio_region,
amine@323 14 _read_chunks_online,
amine@323 15 _read_offline,
amine@323 16 )
amine@315 17 from auditok.io import get_audio_source
amine@405 18 from auditok.signal import to_array
amine@403 19 from auditok.util import AudioReader
amine@86 20
amine@86 21
amine@299 22 def _make_random_length_regions(
amine@299 23 byte_seq, sampling_rate, sample_width, channels
amine@299 24 ):
amine@88 25 regions = []
amine@88 26 for b in byte_seq:
amine@88 27 duration = round(random() * 10, 6)
amine@95 28 data = b * int(duration * sampling_rate) * sample_width * channels
amine@244 29 region = AudioRegion(data, sampling_rate, sample_width, channels)
amine@88 30 regions.append(region)
amine@88 31 return regions
amine@88 32
amine@88 33
amine@400 34 @pytest.mark.parametrize(
amine@400 35 "skip, max_read, channels",
amine@400 36 [
amine@405 37 (0, -1, 1), # no_skip_read_all
amine@405 38 (0, -1, 2), # no_skip_read_all_stereo
amine@405 39 (2, -1, 1), # skip_2_read_all
amine@405 40 (2, None, 1), # skip_2_read_all_None
amine@405 41 (2, 3, 1), # skip_2_read_3
amine@405 42 (2, 3.5, 2), # skip_2_read_3_5_stereo
amine@405 43 (2.4, 3.5, 2), # skip_2_4_read_3_5_stereo
amine@400 44 ],
amine@400 45 ids=[
amine@400 46 "no_skip_read_all",
amine@400 47 "no_skip_read_all_stereo",
amine@400 48 "skip_2_read_all",
amine@400 49 "skip_2_read_all_None",
amine@400 50 "skip_2_read_3",
amine@400 51 "skip_2_read_3_5_stereo",
amine@400 52 "skip_2_4_read_3_5_stereo",
amine@400 53 ],
amine@400 54 )
amine@400 55 def test_load(skip, max_read, channels):
amine@400 56 sampling_rate = 10
amine@400 57 sample_width = 2
amine@400 58 filename = "tests/data/test_split_10HZ_{}.raw"
amine@400 59 filename = filename.format("mono" if channels == 1 else "stereo")
amine@400 60 region = load(
amine@400 61 filename,
amine@400 62 skip=skip,
amine@400 63 max_read=max_read,
amine@400 64 sr=sampling_rate,
amine@400 65 sw=sample_width,
amine@400 66 ch=channels,
amine@371 67 )
amine@400 68 with open(filename, "rb") as fp:
amine@400 69 fp.read(round(skip * sampling_rate * sample_width * channels))
amine@400 70 if max_read is None or max_read < 0:
amine@400 71 to_read = -1
amine@400 72 else:
amine@400 73 to_read = round(max_read * sampling_rate * sample_width * channels)
amine@400 74 expected = fp.read(to_read)
amine@400 75 assert bytes(region) == expected
amine@400 76
amine@400 77
amine@400 78 @pytest.mark.parametrize(
amine@400 79 "duration, analysis_window, round_fn, expected, kwargs",
amine@400 80 [
amine@405 81 (0, 1, None, 0, None), # zero_duration
amine@405 82 (0.3, 0.1, round, 3, None), # multiple
amine@405 83 (0.35, 0.1, math.ceil, 4, None), # not_multiple_ceil
amine@405 84 (0.35, 0.1, math.floor, 3, None), # not_multiple_floor
amine@405 85 (0.05, 0.1, round, 0, None), # small_duration
amine@405 86 (0.05, 0.1, math.ceil, 1, None), # small_duration_ceil
amine@405 87 (0.3, 0.1, math.floor, 3, {"epsilon": 1e-6}), # with_round_error
amine@405 88 (-0.5, 0.1, math.ceil, ValueError, None), # negative_duration
amine@405 89 (0.5, -0.1, math.ceil, ValueError, None), # negative_analysis_window
amine@400 90 ],
amine@400 91 ids=[
amine@400 92 "zero_duration",
amine@400 93 "multiple",
amine@400 94 "not_multiple_ceil",
amine@400 95 "not_multiple_floor",
amine@400 96 "small_duration",
amine@400 97 "small_duration_ceil",
amine@400 98 "with_round_error",
amine@400 99 "negative_duration",
amine@400 100 "negative_analysis_window",
amine@400 101 ],
amine@400 102 )
amine@400 103 def test_duration_to_nb_windows(
amine@400 104 duration, analysis_window, round_fn, expected, kwargs
amine@400 105 ):
amine@400 106 if expected == ValueError:
amine@400 107 with pytest.raises(ValueError):
amine@400 108 _duration_to_nb_windows(duration, analysis_window, round_fn)
amine@400 109 else:
amine@400 110 if kwargs is None:
amine@400 111 kwargs = {}
amine@400 112 result = _duration_to_nb_windows(
amine@400 113 duration, analysis_window, round_fn, **kwargs
amine@371 114 )
amine@400 115 assert result == expected
amine@371 116
amine@400 117
amine@400 118 @pytest.mark.parametrize(
amine@400 119 "channels, skip, max_read",
amine@400 120 [
amine@405 121 (1, 0, None), # mono_skip_0_max_read_None
amine@405 122 (1, 3, None), # mono_skip_3_max_read_None
amine@405 123 (1, 2, -1), # mono_skip_2_max_read_negative
amine@405 124 (1, 2, 3), # mono_skip_2_max_read_3
amine@405 125 (2, 0, None), # stereo_skip_0_max_read_None
amine@405 126 (2, 3, None), # stereo_skip_3_max_read_None
amine@405 127 (2, 2, -1), # stereo_skip_2_max_read_negative
amine@405 128 (2, 2, 3), # stereo_skip_2_max_read_3
amine@400 129 ],
amine@400 130 ids=[
amine@400 131 "mono_skip_0_max_read_None",
amine@400 132 "mono_skip_3_max_read_None",
amine@400 133 "mono_skip_2_max_read_negative",
amine@400 134 "mono_skip_2_max_read_3",
amine@400 135 "stereo_skip_0_max_read_None",
amine@400 136 "stereo_skip_3_max_read_None",
amine@400 137 "stereo_skip_2_max_read_negative",
amine@400 138 "stereo_skip_2_max_read_3",
amine@400 139 ],
amine@400 140 )
amine@400 141 def test_read_offline(channels, skip, max_read):
amine@400 142 sampling_rate = 10
amine@400 143 sample_width = 2
amine@400 144 mono_or_stereo = "mono" if channels == 1 else "stereo"
amine@400 145 filename = "tests/data/test_split_10HZ_{}.raw".format(mono_or_stereo)
amine@400 146 with open(filename, "rb") as fp:
amine@400 147 data = fp.read()
amine@400 148 onset = round(skip * sampling_rate * sample_width * channels)
amine@400 149 if max_read in (-1, None):
amine@400 150 offset = len(data) + 1
amine@400 151 else:
amine@400 152 offset = onset + round(
amine@400 153 max_read * sampling_rate * sample_width * channels
amine@400 154 )
amine@400 155 expected_data = data[onset:offset]
amine@400 156 read_data, *audio_params = _read_offline(
amine@400 157 filename,
amine@400 158 skip=skip,
amine@400 159 max_read=max_read,
amine@400 160 sr=sampling_rate,
amine@400 161 sw=sample_width,
amine@400 162 ch=channels,
amine@215 163 )
amine@400 164 assert read_data == expected_data
amine@400 165 assert tuple(audio_params) == (sampling_rate, sample_width, channels)
amine@215 166
amine@323 167
amine@400 168 @pytest.mark.parametrize(
amine@405 169 (
amine@405 170 "min_dur, max_dur, max_silence, drop_trailing_silence, "
amine@405 171 + "strict_min_dur, kwargs, expected"
amine@405 172 ),
amine@400 173 [
amine@405 174 (
amine@405 175 0.2,
amine@405 176 5,
amine@405 177 0.2,
amine@405 178 False,
amine@405 179 False,
amine@405 180 {"eth": 50},
amine@405 181 [(2, 16), (17, 31), (34, 76)],
amine@405 182 ), # simple
amine@400 183 (
amine@400 184 0.3,
amine@400 185 2,
amine@400 186 0.2,
amine@400 187 False,
amine@400 188 False,
amine@400 189 {"eth": 50},
amine@400 190 [(2, 16), (17, 31), (34, 54), (54, 74), (74, 76)],
amine@405 191 ), # short_max_dur
amine@405 192 (3, 5, 0.2, False, False, {"eth": 50}, [(34, 76)]), # long_min_dur
amine@405 193 (0.2, 80, 10, False, False, {"eth": 50}, [(2, 76)]), # long_max_silence
amine@400 194 (
amine@400 195 0.2,
amine@400 196 5,
amine@400 197 0.0,
amine@400 198 False,
amine@400 199 False,
amine@400 200 {"eth": 50},
amine@400 201 [(2, 14), (17, 24), (26, 29), (34, 76)],
amine@405 202 ), # zero_max_silence
amine@400 203 (
amine@299 204 0.2,
amine@299 205 5,
amine@299 206 0.2,
amine@299 207 False,
amine@299 208 False,
amine@207 209 {"energy_threshold": 40},
amine@207 210 [(0, 50), (50, 76)],
amine@405 211 ), # low_energy_threshold
amine@405 212 (
amine@405 213 0.2,
amine@405 214 5,
amine@405 215 0.2,
amine@405 216 False,
amine@405 217 False,
amine@405 218 {"energy_threshold": 60},
amine@405 219 [],
amine@405 220 ), # high_energy_threshold
amine@405 221 (
amine@405 222 0.2,
amine@405 223 10,
amine@405 224 0.5,
amine@405 225 True,
amine@405 226 False,
amine@405 227 {"eth": 50},
amine@405 228 [(2, 76)],
amine@405 229 ), # trim_leading_and_trailing_silence
amine@405 230 (
amine@405 231 0.2,
amine@405 232 5,
amine@405 233 0.2,
amine@405 234 True,
amine@405 235 False,
amine@405 236 {"eth": 50},
amine@405 237 [(2, 14), (17, 29), (34, 76)],
amine@405 238 ), # drop_trailing_silence
amine@405 239 (
amine@405 240 1.5,
amine@405 241 5,
amine@405 242 0.2,
amine@405 243 True,
amine@405 244 False,
amine@405 245 {"eth": 50},
amine@405 246 [(34, 76)],
amine@405 247 ), # drop_trailing_silence_2
amine@400 248 (
amine@207 249 0.3,
amine@207 250 2,
amine@207 251 0.2,
amine@207 252 False,
amine@207 253 True,
amine@207 254 {"eth": 50},
amine@207 255 [(2, 16), (17, 31), (34, 54), (54, 74)],
amine@405 256 ), # strict_min_dur
amine@400 257 ],
amine@400 258 ids=[
amine@400 259 "simple",
amine@400 260 "short_max_dur",
amine@400 261 "long_min_dur",
amine@400 262 "long_max_silence",
amine@400 263 "zero_max_silence",
amine@400 264 "low_energy_threshold",
amine@400 265 "high_energy_threshold",
amine@400 266 "trim_leading_and_trailing_silence",
amine@400 267 "drop_trailing_silence",
amine@400 268 "drop_trailing_silence_2",
amine@400 269 "strict_min_dur",
amine@400 270 ],
amine@400 271 )
amine@400 272 def test_split_params(
amine@400 273 min_dur,
amine@400 274 max_dur,
amine@400 275 max_silence,
amine@400 276 drop_trailing_silence,
amine@400 277 strict_min_dur,
amine@400 278 kwargs,
amine@400 279 expected,
amine@400 280 ):
amine@400 281 with open("tests/data/test_split_10HZ_mono.raw", "rb") as fp:
amine@400 282 data = fp.read()
amine@400 283
amine@400 284 regions = split(
amine@400 285 data,
amine@207 286 min_dur,
amine@207 287 max_dur,
amine@207 288 max_silence,
amine@207 289 drop_trailing_silence,
amine@207 290 strict_min_dur,
amine@400 291 analysis_window=0.1,
amine@400 292 sr=10,
amine@400 293 sw=2,
amine@400 294 ch=1,
amine@400 295 **kwargs
amine@400 296 )
amine@207 297
amine@400 298 region = AudioRegion(data, 10, 2, 1)
amine@400 299 regions_ar = region.split(
amine@400 300 min_dur,
amine@400 301 max_dur,
amine@400 302 max_silence,
amine@400 303 drop_trailing_silence,
amine@400 304 strict_min_dur,
amine@400 305 analysis_window=0.1,
amine@400 306 **kwargs
amine@400 307 )
amine@255 308
amine@400 309 regions = list(regions)
amine@400 310 regions_ar = list(regions_ar)
amine@400 311 err_msg = "Wrong number of regions after split, expected: "
amine@400 312 err_msg += "{}, found: {}".format(len(expected), len(regions))
amine@400 313 assert len(regions) == len(expected), err_msg
amine@400 314 err_msg = "Wrong number of regions after AudioRegion.split, expected: "
amine@400 315 err_msg += "{}, found: {}".format(len(expected), len(regions_ar))
amine@400 316 assert len(regions_ar) == len(expected), err_msg
amine@255 317
amine@400 318 sample_width = 2
amine@405 319 for reg, reg_ar, exp in zip(regions, regions_ar, expected, strict=True):
amine@400 320 onset, offset = exp
amine@400 321 exp_data = data[onset * sample_width : offset * sample_width]
amine@400 322 assert bytes(reg) == exp_data
amine@400 323 assert reg == reg_ar
amine@207 324
amine@299 325
amine@400 326 @pytest.mark.parametrize(
amine@400 327 "channels, kwargs, expected",
amine@400 328 [
amine@405 329 (2, {}, [(2, 32), (34, 76)]), # stereo_all_default
amine@405 330 (1, {"max_read": 5}, [(2, 16), (17, 31), (34, 50)]), # mono_max_read
amine@405 331 (
amine@405 332 1,
amine@405 333 {"mr": 5},
amine@405 334 [(2, 16), (17, 31), (34, 50)],
amine@405 335 ), # mono_max_read_short_name
amine@405 336 (
amine@405 337 1,
amine@405 338 {"eth": 50, "use_channel": 0},
amine@405 339 [(2, 16), (17, 31), (34, 76)],
amine@405 340 ), # mono_use_channel_1
amine@405 341 (1, {"eth": 50, "uc": 1}, [(2, 16), (17, 31), (34, 76)]), # mono_uc_1
amine@405 342 (
amine@405 343 1,
amine@405 344 {"eth": 50, "use_channel": None},
amine@405 345 [(2, 16), (17, 31), (34, 76)],
amine@405 346 ), # mono_use_channel_None
amine@405 347 (
amine@405 348 2,
amine@405 349 {"eth": 50, "use_channel": 0},
amine@405 350 [(2, 16), (17, 31), (34, 76)],
amine@405 351 ), # stereo_use_channel_1
amine@405 352 (
amine@405 353 2,
amine@405 354 {"eth": 50},
amine@405 355 [(2, 32), (34, 76)],
amine@405 356 ), # stereo_use_channel_no_use_channel_given
amine@405 357 (
amine@405 358 2,
amine@405 359 {"eth": 50, "use_channel": -2},
amine@405 360 [(2, 16), (17, 31), (34, 76)],
amine@405 361 ), # stereo_use_channel_minus_2
amine@405 362 (2, {"eth": 50, "uc": 1}, [(10, 32), (36, 76)]), # stereo_uc_2
amine@405 363 (2, {"eth": 50, "uc": -1}, [(10, 32), (36, 76)]), # stereo_uc_minus_1
amine@405 364 (
amine@405 365 1,
amine@405 366 {"eth": 50, "uc": "mix"},
amine@405 367 [(2, 16), (17, 31), (34, 76)],
amine@405 368 ), # mono_uc_mix
amine@405 369 (
amine@405 370 2,
amine@405 371 {"energy_threshold": 53.5, "use_channel": "mix"},
amine@405 372 [(54, 76)],
amine@405 373 ), # stereo_use_channel_mix
amine@405 374 (2, {"eth": 52, "uc": "mix"}, [(17, 26), (54, 76)]), # stereo_uc_mix
amine@405 375 (
amine@405 376 2,
amine@405 377 {"uc": "mix"},
amine@405 378 [(10, 16), (17, 31), (36, 76)],
amine@405 379 ), # stereo_uc_mix_default_eth
amine@400 380 ],
amine@400 381 ids=[
amine@400 382 "stereo_all_default",
amine@400 383 "mono_max_read",
amine@400 384 "mono_max_read_short_name",
amine@400 385 "mono_use_channel_1",
amine@400 386 "mono_uc_1",
amine@400 387 "mono_use_channel_None",
amine@400 388 "stereo_use_channel_1",
amine@400 389 "stereo_use_channel_no_use_channel_given",
amine@400 390 "stereo_use_channel_minus_2",
amine@400 391 "stereo_uc_2",
amine@400 392 "stereo_uc_minus_1",
amine@400 393 "mono_uc_mix",
amine@400 394 "stereo_use_channel_mix",
amine@400 395 "stereo_uc_mix",
amine@400 396 "stereo_uc_mix_default_eth",
amine@400 397 ],
amine@400 398 )
amine@400 399 def test_split_kwargs(channels, kwargs, expected):
amine@400 400
amine@400 401 mono_or_stereo = "mono" if channels == 1 else "stereo"
amine@400 402 filename = "tests/data/test_split_10HZ_{}.raw".format(mono_or_stereo)
amine@400 403 with open(filename, "rb") as fp:
amine@400 404 data = fp.read()
amine@400 405
amine@400 406 regions = split(
amine@400 407 data,
amine@400 408 min_dur=0.2,
amine@400 409 max_dur=5,
amine@400 410 max_silence=0.2,
amine@400 411 drop_trailing_silence=False,
amine@400 412 strict_min_dur=False,
amine@400 413 analysis_window=0.1,
amine@400 414 sr=10,
amine@400 415 sw=2,
amine@400 416 ch=channels,
amine@400 417 **kwargs
amine@211 418 )
amine@211 419
amine@400 420 region = AudioRegion(data, 10, 2, channels)
amine@400 421 max_read = kwargs.get("max_read", kwargs.get("mr"))
amine@400 422 if max_read is not None:
amine@400 423 region = region.sec[:max_read]
amine@400 424 kwargs.pop("max_read", None)
amine@400 425 kwargs.pop("mr", None)
amine@211 426
amine@400 427 regions_ar = region.split(
amine@400 428 min_dur=0.2,
amine@400 429 max_dur=5,
amine@400 430 max_silence=0.2,
amine@400 431 drop_trailing_silence=False,
amine@400 432 strict_min_dur=False,
amine@400 433 analysis_window=0.1,
amine@400 434 **kwargs
amine@400 435 )
amine@255 436
amine@400 437 regions = list(regions)
amine@400 438 regions_ar = list(regions_ar)
amine@400 439 err_msg = "Wrong number of regions after split, expected: "
amine@400 440 err_msg += "{}, found: {}".format(len(expected), len(regions))
amine@400 441 assert len(regions) == len(expected), err_msg
amine@400 442 err_msg = "Wrong number of regions after AudioRegion.split, expected: "
amine@400 443 err_msg += "{}, found: {}".format(len(expected), len(regions_ar))
amine@400 444 assert len(regions_ar) == len(expected), err_msg
amine@306 445
amine@400 446 sample_width = 2
amine@400 447 sample_size_bytes = sample_width * channels
amine@405 448 for reg, reg_ar, exp in zip(regions, regions_ar, expected, strict=True):
amine@400 449 onset, offset = exp
amine@400 450 exp_data = data[onset * sample_size_bytes : offset * sample_size_bytes]
amine@400 451 assert len(bytes(reg)) == len(exp_data)
amine@400 452 assert reg == reg_ar
amine@255 453
amine@255 454
amine@400 455 @pytest.mark.parametrize(
amine@400 456 "min_dur, max_dur, max_silence, channels, kwargs, expected",
amine@400 457 [
amine@405 458 (
amine@405 459 0.2,
amine@405 460 5,
amine@405 461 0.2,
amine@405 462 1,
amine@405 463 {"aw": 0.2},
amine@405 464 [(2, 30), (34, 76)],
amine@405 465 ), # mono_aw_0_2_max_silence_0_2
amine@405 466 (
amine@405 467 0.2,
amine@405 468 5,
amine@405 469 0.3,
amine@405 470 1,
amine@405 471 {"aw": 0.2},
amine@405 472 [(2, 30), (34, 76)],
amine@405 473 ), # mono_aw_0_2_max_silence_0_3
amine@405 474 (
amine@405 475 0.2,
amine@405 476 5,
amine@405 477 0.4,
amine@405 478 1,
amine@405 479 {"aw": 0.2},
amine@405 480 [(2, 32), (34, 76)],
amine@405 481 ), # mono_aw_0_2_max_silence_0_4
amine@405 482 (
amine@405 483 0.2,
amine@405 484 5,
amine@405 485 0,
amine@405 486 1,
amine@405 487 {"aw": 0.2},
amine@405 488 [(2, 14), (16, 24), (26, 28), (34, 76)],
amine@405 489 ), # mono_aw_0_2_max_silence_0
amine@405 490 (0.2, 5, 0.2, 1, {"aw": 0.2}, [(2, 30), (34, 76)]), # mono_aw_0_2
amine@405 491 (
amine@405 492 0.3,
amine@405 493 5,
amine@405 494 0,
amine@405 495 1,
amine@405 496 {"aw": 0.3},
amine@405 497 [(3, 12), (15, 24), (36, 76)],
amine@405 498 ), # mono_aw_0_3_max_silence_0
amine@405 499 (
amine@405 500 0.3,
amine@405 501 5,
amine@405 502 0.3,
amine@405 503 1,
amine@405 504 {"aw": 0.3},
amine@405 505 [(3, 27), (36, 76)],
amine@405 506 ), # mono_aw_0_3_max_silence_0_3
amine@405 507 (
amine@405 508 0.3,
amine@405 509 5,
amine@405 510 0.5,
amine@405 511 1,
amine@405 512 {"aw": 0.3},
amine@405 513 [(3, 27), (36, 76)],
amine@405 514 ), # mono_aw_0_3_max_silence_0_5
amine@405 515 (
amine@405 516 0.3,
amine@405 517 5,
amine@405 518 0.6,
amine@405 519 1,
amine@405 520 {"aw": 0.3},
amine@405 521 [(3, 30), (36, 76)],
amine@405 522 ), # mono_aw_0_3_max_silence_0_6
amine@405 523 (
amine@405 524 0.2,
amine@405 525 5,
amine@405 526 0,
amine@405 527 1,
amine@405 528 {"aw": 0.4},
amine@405 529 [(4, 12), (16, 24), (36, 76)],
amine@405 530 ), # mono_aw_0_4_max_silence_0
amine@405 531 (
amine@405 532 0.2,
amine@405 533 5,
amine@405 534 0.3,
amine@405 535 1,
amine@405 536 {"aw": 0.4},
amine@405 537 [(4, 12), (16, 24), (36, 76)],
amine@405 538 ), # mono_aw_0_4_max_silence_0_3
amine@405 539 (
amine@405 540 0.2,
amine@405 541 5,
amine@405 542 0.4,
amine@405 543 1,
amine@405 544 {"aw": 0.4},
amine@405 545 [(4, 28), (36, 76)],
amine@405 546 ), # mono_aw_0_4_max_silence_0_4
amine@405 547 (
amine@405 548 0.2,
amine@405 549 5,
amine@405 550 0.2,
amine@405 551 2,
amine@405 552 {"analysis_window": 0.2},
amine@405 553 [(2, 32), (34, 76)],
amine@405 554 ), # stereo_uc_None_analysis_window_0_2
amine@400 555 (
amine@316 556 0.2,
amine@316 557 5,
amine@316 558 0.2,
amine@316 559 2,
amine@316 560 {"uc": None, "analysis_window": 0.2},
amine@316 561 [(2, 32), (34, 76)],
amine@405 562 ), # stereo_uc_any_analysis_window_0_2
amine@400 563 (
amine@316 564 0.2,
amine@316 565 5,
amine@316 566 0.2,
amine@316 567 2,
amine@316 568 {"use_channel": None, "analysis_window": 0.3},
amine@316 569 [(3, 30), (36, 76)],
amine@405 570 ), # stereo_use_channel_None_aw_0_3_max_silence_0_2
amine@400 571 (
amine@316 572 0.2,
amine@316 573 5,
amine@316 574 0.3,
amine@316 575 2,
amine@316 576 {"use_channel": "any", "analysis_window": 0.3},
amine@316 577 [(3, 33), (36, 76)],
amine@405 578 ), # stereo_use_channel_any_aw_0_3_max_silence_0_3
amine@400 579 (
amine@316 580 0.2,
amine@316 581 5,
amine@316 582 0.2,
amine@316 583 2,
amine@316 584 {"use_channel": None, "analysis_window": 0.4},
amine@316 585 [(4, 28), (36, 76)],
amine@405 586 ), # stereo_use_channel_None_aw_0_4_max_silence_0_2
amine@400 587 (
amine@316 588 0.2,
amine@316 589 5,
amine@316 590 0.4,
amine@316 591 2,
amine@316 592 {"use_channel": "any", "analysis_window": 0.4},
amine@316 593 [(4, 32), (36, 76)],
amine@405 594 ), # stereo_use_channel_any_aw_0_3_max_silence_0_4
amine@400 595 (
amine@241 596 0.2,
amine@241 597 5,
amine@241 598 0.2,
amine@241 599 2,
amine@241 600 {"uc": 0, "analysis_window": 0.2},
amine@241 601 [(2, 30), (34, 76)],
amine@405 602 ), # stereo_uc_0_analysis_window_0_2
amine@400 603 (
amine@220 604 0.2,
amine@220 605 5,
amine@220 606 0.2,
amine@220 607 2,
amine@220 608 {"uc": 1, "analysis_window": 0.2},
amine@231 609 [(10, 32), (36, 76)],
amine@405 610 ), # stereo_uc_1_analysis_window_0_2
amine@400 611 (
amine@233 612 0.2,
amine@233 613 5,
amine@233 614 0,
amine@233 615 2,
amine@233 616 {"uc": "mix", "analysis_window": 0.1},
amine@233 617 [(10, 14), (17, 24), (26, 29), (36, 76)],
amine@405 618 ), # stereo_uc_mix_aw_0_1_max_silence_0
amine@400 619 (
amine@233 620 0.2,
amine@233 621 5,
amine@233 622 0.1,
amine@233 623 2,
amine@233 624 {"uc": "mix", "analysis_window": 0.1},
amine@233 625 [(10, 15), (17, 25), (26, 30), (36, 76)],
amine@405 626 ), # stereo_uc_mix_aw_0_1_max_silence_0_1
amine@400 627 (
amine@233 628 0.2,
amine@233 629 5,
amine@233 630 0.2,
amine@233 631 2,
amine@233 632 {"uc": "mix", "analysis_window": 0.1},
amine@233 633 [(10, 16), (17, 31), (36, 76)],
amine@405 634 ), # stereo_uc_mix_aw_0_1_max_silence_0_2
amine@400 635 (
amine@233 636 0.2,
amine@233 637 5,
amine@233 638 0.3,
amine@233 639 2,
amine@233 640 {"uc": "mix", "analysis_window": 0.1},
amine@233 641 [(10, 32), (36, 76)],
amine@405 642 ), # stereo_uc_mix_aw_0_1_max_silence_0_3
amine@400 643 (
amine@233 644 0.3,
amine@233 645 5,
amine@233 646 0,
amine@233 647 2,
amine@316 648 {"uc": "avg", "analysis_window": 0.2},
amine@233 649 [(10, 14), (16, 24), (36, 76)],
amine@405 650 ), # stereo_uc_avg_aw_0_2_max_silence_0_min_dur_0_3
amine@400 651 (
amine@233 652 0.41,
amine@233 653 5,
amine@233 654 0,
amine@233 655 2,
amine@316 656 {"uc": "average", "analysis_window": 0.2},
amine@233 657 [(16, 24), (36, 76)],
amine@405 658 ), # stereo_uc_average_aw_0_2_max_silence_0_min_dur_0_41
amine@400 659 (
amine@233 660 0.2,
amine@233 661 5,
amine@233 662 0.1,
amine@233 663 2,
amine@233 664 {"uc": "mix", "analysis_window": 0.2},
amine@233 665 [(10, 14), (16, 24), (26, 28), (36, 76)],
amine@405 666 ), # stereo_uc_mix_aw_0_2_max_silence_0_1
amine@400 667 (
amine@233 668 0.2,
amine@233 669 5,
amine@233 670 0.2,
amine@233 671 2,
amine@233 672 {"uc": "mix", "analysis_window": 0.2},
amine@233 673 [(10, 30), (36, 76)],
amine@405 674 ), # stereo_uc_mix_aw_0_2_max_silence_0_2
amine@400 675 (
amine@233 676 0.2,
amine@233 677 5,
amine@233 678 0.4,
amine@233 679 2,
amine@233 680 {"uc": "mix", "analysis_window": 0.2},
amine@233 681 [(10, 32), (36, 76)],
amine@405 682 ), # stereo_uc_mix_aw_0_2_max_silence_0_4
amine@400 683 (
amine@233 684 0.2,
amine@233 685 5,
amine@233 686 0.5,
amine@233 687 2,
amine@233 688 {"uc": "mix", "analysis_window": 0.2},
amine@233 689 [(10, 32), (36, 76)],
amine@405 690 ), # stereo_uc_mix_aw_0_2_max_silence_0_5
amine@400 691 (
amine@233 692 0.2,
amine@233 693 5,
amine@233 694 0.6,
amine@233 695 2,
amine@233 696 {"uc": "mix", "analysis_window": 0.2},
amine@233 697 [(10, 34), (36, 76)],
amine@405 698 ), # stereo_uc_mix_aw_0_2_max_silence_0_6
amine@400 699 (
amine@233 700 0.2,
amine@233 701 5,
amine@233 702 0,
amine@233 703 2,
amine@233 704 {"uc": "mix", "analysis_window": 0.3},
amine@233 705 [(9, 24), (27, 30), (36, 76)],
amine@405 706 ), # stereo_uc_mix_aw_0_3_max_silence_0
amine@400 707 (
amine@233 708 0.4,
amine@233 709 5,
amine@233 710 0,
amine@233 711 2,
amine@233 712 {"uc": "mix", "analysis_window": 0.3},
amine@233 713 [(9, 24), (36, 76)],
amine@405 714 ), # stereo_uc_mix_aw_0_3_max_silence_0_min_dur_0_3
amine@400 715 (
amine@233 716 0.2,
amine@233 717 5,
amine@233 718 0.6,
amine@233 719 2,
amine@233 720 {"uc": "mix", "analysis_window": 0.3},
amine@233 721 [(9, 57), (57, 76)],
amine@405 722 ), # stereo_uc_mix_aw_0_3_max_silence_0_6
amine@400 723 (
amine@233 724 0.2,
amine@233 725 5.1,
amine@233 726 0.6,
amine@233 727 2,
amine@233 728 {"uc": "mix", "analysis_window": 0.3},
amine@233 729 [(9, 60), (60, 76)],
amine@405 730 ), # stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_1
amine@400 731 (
amine@233 732 0.2,
amine@233 733 5.2,
amine@233 734 0.6,
amine@233 735 2,
amine@233 736 {"uc": "mix", "analysis_window": 0.3},
amine@233 737 [(9, 60), (60, 76)],
amine@405 738 ), # stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_2
amine@400 739 (
amine@233 740 0.2,
amine@233 741 5.3,
amine@233 742 0.6,
amine@233 743 2,
amine@233 744 {"uc": "mix", "analysis_window": 0.3},
amine@233 745 [(9, 60), (60, 76)],
amine@405 746 ), # stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_3
amine@400 747 (
amine@233 748 0.2,
amine@233 749 5.4,
amine@233 750 0.6,
amine@233 751 2,
amine@233 752 {"uc": "mix", "analysis_window": 0.3},
amine@233 753 [(9, 63), (63, 76)],
amine@405 754 ), # stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_4
amine@400 755 (
amine@233 756 0.2,
amine@233 757 5,
amine@233 758 0,
amine@233 759 2,
amine@233 760 {"uc": "mix", "analysis_window": 0.4},
amine@233 761 [(16, 24), (36, 76)],
amine@405 762 ), # stereo_uc_mix_aw_0_4_max_silence_0
amine@400 763 (
amine@233 764 0.2,
amine@233 765 5,
amine@233 766 0.3,
amine@233 767 2,
amine@233 768 {"uc": "mix", "analysis_window": 0.4},
amine@233 769 [(16, 24), (36, 76)],
amine@405 770 ), # stereo_uc_mix_aw_0_4_max_silence_0_3
amine@400 771 (
amine@233 772 0.2,
amine@233 773 5,
amine@233 774 0.4,
amine@233 775 2,
amine@233 776 {"uc": "mix", "analysis_window": 0.4},
amine@233 777 [(16, 28), (36, 76)],
amine@405 778 ), # stereo_uc_mix_aw_0_4_max_silence_0_4
amine@400 779 ],
amine@400 780 ids=[
amine@400 781 "mono_aw_0_2_max_silence_0_2",
amine@400 782 "mono_aw_0_2_max_silence_0_3",
amine@400 783 "mono_aw_0_2_max_silence_0_4",
amine@400 784 "mono_aw_0_2_max_silence_0",
amine@400 785 "mono_aw_0_2",
amine@400 786 "mono_aw_0_3_max_silence_0",
amine@400 787 "mono_aw_0_3_max_silence_0_3",
amine@400 788 "mono_aw_0_3_max_silence_0_5",
amine@400 789 "mono_aw_0_3_max_silence_0_6",
amine@400 790 "mono_aw_0_4_max_silence_0",
amine@400 791 "mono_aw_0_4_max_silence_0_3",
amine@400 792 "mono_aw_0_4_max_silence_0_4",
amine@400 793 "stereo_uc_None_analysis_window_0_2",
amine@400 794 "stereo_uc_any_analysis_window_0_2",
amine@400 795 "stereo_use_channel_None_aw_0_3_max_silence_0_2",
amine@400 796 "stereo_use_channel_any_aw_0_3_max_silence_0_3",
amine@400 797 "stereo_use_channel_None_aw_0_4_max_silence_0_2",
amine@400 798 "stereo_use_channel_any_aw_0_3_max_silence_0_4",
amine@400 799 "stereo_uc_0_analysis_window_0_2",
amine@400 800 "stereo_uc_1_analysis_window_0_2",
amine@400 801 "stereo_uc_mix_aw_0_1_max_silence_0",
amine@400 802 "stereo_uc_mix_aw_0_1_max_silence_0_1",
amine@400 803 "stereo_uc_mix_aw_0_1_max_silence_0_2",
amine@400 804 "stereo_uc_mix_aw_0_1_max_silence_0_3",
amine@400 805 "stereo_uc_avg_aw_0_2_max_silence_0_min_dur_0_3",
amine@400 806 "stereo_uc_average_aw_0_2_max_silence_0_min_dur_0_41",
amine@400 807 "stereo_uc_mix_aw_0_2_max_silence_0_1",
amine@400 808 "stereo_uc_mix_aw_0_2_max_silence_0_2",
amine@400 809 "stereo_uc_mix_aw_0_2_max_silence_0_4",
amine@400 810 "stereo_uc_mix_aw_0_2_max_silence_0_5",
amine@400 811 "stereo_uc_mix_aw_0_2_max_silence_0_6",
amine@400 812 "stereo_uc_mix_aw_0_3_max_silence_0",
amine@400 813 "stereo_uc_mix_aw_0_3_max_silence_0_min_dur_0_3",
amine@400 814 "stereo_uc_mix_aw_0_3_max_silence_0_6",
amine@400 815 "stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_1",
amine@400 816 "stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_2",
amine@400 817 "stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_3",
amine@400 818 "stereo_uc_mix_aw_0_3_max_silence_0_6_max_dur_5_4",
amine@400 819 "stereo_uc_mix_aw_0_4_max_silence_0",
amine@400 820 "stereo_uc_mix_aw_0_4_max_silence_0_3",
amine@400 821 "stereo_uc_mix_aw_0_4_max_silence_0_4",
amine@400 822 ],
amine@400 823 )
amine@400 824 def test_split_analysis_window(
amine@400 825 min_dur, max_dur, max_silence, channels, kwargs, expected
amine@400 826 ):
amine@400 827
amine@400 828 mono_or_stereo = "mono" if channels == 1 else "stereo"
amine@400 829 filename = "tests/data/test_split_10HZ_{}.raw".format(mono_or_stereo)
amine@400 830 with open(filename, "rb") as fp:
amine@400 831 data = fp.read()
amine@400 832
amine@400 833 regions = split(
amine@400 834 data,
amine@400 835 min_dur=min_dur,
amine@400 836 max_dur=max_dur,
amine@400 837 max_silence=max_silence,
amine@400 838 drop_trailing_silence=False,
amine@400 839 strict_min_dur=False,
amine@400 840 sr=10,
amine@400 841 sw=2,
amine@400 842 ch=channels,
amine@400 843 eth=49.99,
amine@400 844 **kwargs
amine@220 845 )
amine@220 846
amine@400 847 region = AudioRegion(data, 10, 2, channels)
amine@400 848 regions_ar = region.split(
amine@400 849 min_dur=min_dur,
amine@400 850 max_dur=max_dur,
amine@400 851 max_silence=max_silence,
amine@400 852 drop_trailing_silence=False,
amine@400 853 strict_min_dur=False,
amine@400 854 eth=49.99,
amine@400 855 **kwargs
amine@400 856 )
amine@220 857
amine@400 858 regions = list(regions)
amine@400 859 regions_ar = list(regions_ar)
amine@400 860 err_msg = "Wrong number of regions after split, expected: "
amine@400 861 err_msg += "{}, found: {}".format(len(expected), len(regions))
amine@400 862 assert len(regions) == len(expected), err_msg
amine@400 863 err_msg = "Wrong number of regions after AudioRegion.split, expected: "
amine@400 864 err_msg += "{}, found: {}".format(len(expected), len(regions_ar))
amine@400 865 assert len(regions_ar) == len(expected), err_msg
amine@255 866
amine@400 867 sample_width = 2
amine@400 868 sample_size_bytes = sample_width * channels
amine@405 869 for reg, reg_ar, exp in zip(regions, regions_ar, expected, strict=True):
amine@400 870 onset, offset = exp
amine@400 871 exp_data = data[onset * sample_size_bytes : offset * sample_size_bytes]
amine@400 872 assert bytes(reg) == exp_data
amine@400 873 assert reg == reg_ar
amine@255 874
amine@255 875
amine@400 876 def test_split_custom_validator():
amine@400 877 filename = "tests/data/test_split_10HZ_mono.raw"
amine@400 878 with open(filename, "rb") as fp:
amine@400 879 data = fp.read()
amine@299 880
amine@400 881 regions = split(
amine@400 882 data,
amine@400 883 min_dur=0.2,
amine@400 884 max_dur=5,
amine@400 885 max_silence=0.2,
amine@400 886 drop_trailing_silence=False,
amine@400 887 strict_min_dur=False,
amine@400 888 sr=10,
amine@400 889 sw=2,
amine@400 890 ch=1,
amine@400 891 analysis_window=0.1,
amine@405 892 validator=lambda x: to_array(x, sample_width=2, channels=1)[0] >= 320,
amine@400 893 )
amine@299 894
amine@400 895 region = AudioRegion(data, 10, 2, 1)
amine@400 896 regions_ar = region.split(
amine@400 897 min_dur=0.2,
amine@400 898 max_dur=5,
amine@400 899 max_silence=0.2,
amine@400 900 drop_trailing_silence=False,
amine@400 901 strict_min_dur=False,
amine@400 902 analysis_window=0.1,
amine@405 903 validator=lambda x: to_array(x, sample_width=2, channels=1)[0] >= 320,
amine@400 904 )
amine@299 905
amine@400 906 expected = [(2, 16), (17, 31), (34, 76)]
amine@400 907 regions = list(regions)
amine@400 908 regions_ar = list(regions_ar)
amine@400 909 err_msg = "Wrong number of regions after split, expected: "
amine@400 910 err_msg += "{}, found: {}".format(len(expected), len(regions))
amine@400 911 assert len(regions) == len(expected), err_msg
amine@400 912 err_msg = "Wrong number of regions after AudioRegion.split, expected: "
amine@400 913 err_msg += "{}, found: {}".format(len(expected), len(regions_ar))
amine@400 914 assert len(regions_ar) == len(expected), err_msg
amine@299 915
amine@400 916 sample_size_bytes = 2
amine@405 917 for reg, reg_ar, exp in zip(regions, regions_ar, expected, strict=True):
amine@400 918 onset, offset = exp
amine@400 919 exp_data = data[onset * sample_size_bytes : offset * sample_size_bytes]
amine@400 920 assert bytes(reg) == exp_data
amine@400 921 assert reg == reg_ar
amine@299 922
amine@220 923
amine@400 924 @pytest.mark.parametrize(
amine@400 925 "input, kwargs",
amine@400 926 [
amine@400 927 (
amine@212 928 "tests/data/test_split_10HZ_stereo.raw",
amine@212 929 {"audio_format": "raw", "sr": 10, "sw": 2, "ch": 2},
amine@405 930 ), # filename_audio_format
amine@400 931 (
amine@212 932 "tests/data/test_split_10HZ_stereo.raw",
amine@212 933 {"fmt": "raw", "sr": 10, "sw": 2, "ch": 2},
amine@405 934 ), # filename_audio_format_short_name
amine@405 935 (
amine@405 936 "tests/data/test_split_10HZ_stereo.raw",
amine@405 937 {"sr": 10, "sw": 2, "ch": 2},
amine@405 938 ), # filename_no_audio_format
amine@400 939 (
amine@212 940 "tests/data/test_split_10HZ_stereo.raw",
amine@212 941 {"sampling_rate": 10, "sample_width": 2, "channels": 2},
amine@405 942 ), # filename_no_long_audio_params
amine@400 943 (
amine@212 944 open("tests/data/test_split_10HZ_stereo.raw", "rb").read(),
amine@212 945 {"sr": 10, "sw": 2, "ch": 2},
amine@405 946 ), # bytes_
amine@400 947 (
amine@403 948 AudioReader(
amine@212 949 "tests/data/test_split_10HZ_stereo.raw",
amine@212 950 sr=10,
amine@212 951 sw=2,
amine@212 952 ch=2,
amine@212 953 block_dur=0.1,
amine@212 954 ),
amine@212 955 {},
amine@405 956 ), # audio_reader
amine@400 957 (
amine@212 958 AudioRegion(
amine@299 959 open("tests/data/test_split_10HZ_stereo.raw", "rb").read(),
amine@299 960 10,
amine@299 961 2,
amine@299 962 2,
amine@212 963 ),
amine@212 964 {},
amine@405 965 ), # audio_region
amine@400 966 (
amine@212 967 get_audio_source(
amine@212 968 "tests/data/test_split_10HZ_stereo.raw", sr=10, sw=2, ch=2
amine@212 969 ),
amine@212 970 {},
amine@405 971 ), # audio_source
amine@400 972 ],
amine@400 973 ids=[
amine@400 974 "filename_audio_format",
amine@400 975 "filename_audio_format_short_name",
amine@400 976 "filename_no_audio_format",
amine@400 977 "filename_no_long_audio_params",
amine@400 978 "bytes_",
amine@400 979 "audio_reader",
amine@400 980 "audio_region",
amine@400 981 "audio_source",
amine@400 982 ],
amine@400 983 )
amine@400 984 def test_split_input_type(input, kwargs):
amine@400 985
amine@400 986 with open("tests/data/test_split_10HZ_stereo.raw", "rb") as fp:
amine@400 987 data = fp.read()
amine@400 988
amine@400 989 regions = split(
amine@400 990 input,
amine@400 991 min_dur=0.2,
amine@400 992 max_dur=5,
amine@400 993 max_silence=0.2,
amine@400 994 drop_trailing_silence=False,
amine@400 995 strict_min_dur=False,
amine@400 996 analysis_window=0.1,
amine@400 997 **kwargs
amine@212 998 )
amine@400 999 regions = list(regions)
amine@400 1000 expected = [(2, 32), (34, 76)]
amine@400 1001 sample_width = 2
amine@400 1002 err_msg = "Wrong number of regions after split, expected: "
amine@400 1003 err_msg += "{}, found: {}".format(expected, regions)
amine@400 1004 assert len(regions) == len(expected), err_msg
amine@405 1005 for reg, exp in zip(regions, expected, strict=True):
amine@400 1006 onset, offset = exp
amine@400 1007 exp_data = data[onset * sample_width * 2 : offset * sample_width * 2]
amine@400 1008 assert bytes(reg) == exp_data
amine@212 1009
amine@212 1010
amine@400 1011 @pytest.mark.parametrize(
amine@400 1012 "min_dur, max_dur, analysis_window",
amine@400 1013 [
amine@400 1014 (0.5, 0.4, 0.1),
amine@400 1015 (0.44, 0.49, 0.1),
amine@400 1016 ],
amine@400 1017 ids=[
amine@400 1018 "min_dur_greater_than_max_dur",
amine@400 1019 "durations_OK_but_wrong_number_of_analysis_windows",
amine@400 1020 ],
amine@400 1021 )
amine@400 1022 def test_split_wrong_min_max_dur(min_dur, max_dur, analysis_window):
amine@400 1023
amine@400 1024 with pytest.raises(ValueError) as val_err:
amine@400 1025 split(
amine@400 1026 b"0" * 16,
amine@400 1027 min_dur=min_dur,
amine@400 1028 max_dur=max_dur,
amine@400 1029 max_silence=0.2,
amine@400 1030 sr=16000,
amine@400 1031 sw=1,
amine@400 1032 ch=1,
amine@400 1033 analysis_window=analysis_window,
amine@400 1034 )
amine@400 1035
amine@400 1036 err_msg = "'min_dur' ({0} sec.) results in {1} analysis "
amine@400 1037 err_msg += "window(s) ({1} == ceil({0} / {2})) which is "
amine@400 1038 err_msg += "higher than the number of analysis window(s) for "
amine@400 1039 err_msg += "'max_dur' ({3} == floor({4} / {2}))"
amine@400 1040
amine@400 1041 err_msg = err_msg.format(
amine@400 1042 min_dur,
amine@400 1043 math.ceil(min_dur / analysis_window),
amine@400 1044 analysis_window,
amine@400 1045 math.floor(max_dur / analysis_window),
amine@400 1046 max_dur,
amine@400 1047 )
amine@400 1048 assert err_msg == str(val_err.value)
amine@400 1049
amine@400 1050
amine@400 1051 @pytest.mark.parametrize(
amine@400 1052 "max_silence, max_dur, analysis_window",
amine@400 1053 [
amine@405 1054 (0.5, 0.5, 0.1), # max_silence_equals_max_dur
amine@405 1055 (0.5, 0.4, 0.1), # max_silence_greater_than_max_dur
amine@405 1056 (0.44, 0.49, 0.1), # durations_OK_but_wrong_number_of_analysis_windows
amine@400 1057 ],
amine@400 1058 ids=[
amine@400 1059 "max_silence_equals_max_dur",
amine@400 1060 "max_silence_greater_than_max_dur",
amine@400 1061 "durations_OK_but_wrong_number_of_analysis_windows",
amine@400 1062 ],
amine@400 1063 )
amine@400 1064 def test_split_wrong_max_silence_max_dur(max_silence, max_dur, analysis_window):
amine@400 1065
amine@400 1066 with pytest.raises(ValueError) as val_err:
amine@400 1067 split(
amine@400 1068 b"0" * 16,
amine@400 1069 min_dur=0.2,
amine@400 1070 max_dur=max_dur,
amine@400 1071 max_silence=max_silence,
amine@400 1072 sr=16000,
amine@400 1073 sw=1,
amine@400 1074 ch=1,
amine@400 1075 analysis_window=analysis_window,
amine@400 1076 )
amine@400 1077
amine@400 1078 err_msg = "'max_silence' ({0} sec.) results in {1} analysis "
amine@400 1079 err_msg += "window(s) ({1} == floor({0} / {2})) which is "
amine@400 1080 err_msg += "higher or equal to the number of analysis window(s) for "
amine@400 1081 err_msg += "'max_dur' ({3} == floor({4} / {2}))"
amine@400 1082
amine@400 1083 err_msg = err_msg.format(
amine@400 1084 max_silence,
amine@400 1085 math.floor(max_silence / analysis_window),
amine@400 1086 analysis_window,
amine@400 1087 math.floor(max_dur / analysis_window),
amine@400 1088 max_dur,
amine@400 1089 )
amine@400 1090 assert err_msg == str(val_err.value)
amine@400 1091
amine@400 1092
amine@400 1093 @pytest.mark.parametrize(
amine@400 1094 "wrong_param",
amine@400 1095 [
amine@405 1096 {"min_dur": -1}, # negative_min_dur
amine@405 1097 {"min_dur": 0}, # zero_min_dur
amine@405 1098 {"max_dur": -1}, # negative_max_dur
amine@405 1099 {"max_dur": 0}, # zero_max_dur
amine@405 1100 {"max_silence": -1}, # negative_max_silence
amine@405 1101 {"analysis_window": 0}, # zero_analysis_window
amine@405 1102 {"analysis_window": -1}, # negative_analysis_window
amine@400 1103 ],
amine@400 1104 ids=[
amine@400 1105 "negative_min_dur",
amine@400 1106 "zero_min_dur",
amine@400 1107 "negative_max_dur",
amine@400 1108 "zero_max_dur",
amine@400 1109 "negative_max_silence",
amine@400 1110 "zero_analysis_window",
amine@400 1111 "negative_analysis_window",
amine@400 1112 ],
amine@400 1113 )
amine@400 1114 def test_split_negative_temporal_params(wrong_param):
amine@400 1115
amine@400 1116 params = {
amine@400 1117 "min_dur": 0.2,
amine@400 1118 "max_dur": 0.5,
amine@400 1119 "max_silence": 0.1,
amine@400 1120 "analysis_window": 0.1,
amine@400 1121 }
amine@400 1122 params.update(wrong_param)
amine@400 1123 with pytest.raises(ValueError) as val_err:
amine@400 1124 split(None, **params)
amine@400 1125
amine@400 1126 name = set(wrong_param).pop()
amine@400 1127 value = wrong_param[name]
amine@400 1128 err_msg = "'{}' ({}) must be >{} 0".format(
amine@400 1129 name, value, "=" if name == "max_silence" else ""
amine@400 1130 )
amine@400 1131 assert err_msg == str(val_err.value)
amine@400 1132
amine@400 1133
amine@400 1134 def test_split_too_small_analysis_window():
amine@400 1135 with pytest.raises(ValueError) as val_err:
amine@400 1136 split(b"", sr=10, sw=1, ch=1, analysis_window=0.09)
amine@403 1137 err_msg = "Too small 'analysis_window' (0.09) for sampling rate (10)."
amine@403 1138 err_msg += " Analysis window should at least be 1/10 to cover one "
amine@403 1139 err_msg += "data sample"
amine@400 1140 assert err_msg == str(val_err.value)
amine@400 1141
amine@400 1142
amine@400 1143 def test_split_and_plot():
amine@400 1144
amine@400 1145 with open("tests/data/test_split_10HZ_mono.raw", "rb") as fp:
amine@400 1146 data = fp.read()
amine@400 1147
amine@400 1148 region = AudioRegion(data, 10, 2, 1)
amine@405 1149 with patch("auditok.core.plot") as patch_fn:
amine@400 1150 regions = region.split_and_plot(
amine@212 1151 min_dur=0.2,
amine@212 1152 max_dur=5,
amine@212 1153 max_silence=0.2,
amine@212 1154 drop_trailing_silence=False,
amine@212 1155 strict_min_dur=False,
amine@212 1156 analysis_window=0.1,
amine@400 1157 sr=10,
amine@400 1158 sw=2,
amine@400 1159 ch=1,
amine@400 1160 eth=50,
amine@212 1161 )
amine@400 1162 assert patch_fn.called
amine@400 1163 expected = [(2, 16), (17, 31), (34, 76)]
amine@400 1164 sample_width = 2
amine@400 1165 expected_regions = []
amine@400 1166 for onset, offset in expected:
amine@400 1167 onset *= sample_width
amine@400 1168 offset *= sample_width
amine@400 1169 expected_regions.append(AudioRegion(data[onset:offset], 10, 2, 1))
amine@400 1170 assert regions == expected_regions
amine@211 1171
amine@223 1172
amine@400 1173 def test_split_exception():
amine@400 1174 with open("tests/data/test_split_10HZ_mono.raw", "rb") as fp:
amine@400 1175 data = fp.read()
amine@400 1176 region = AudioRegion(data, 10, 2, 1)
amine@223 1177
amine@400 1178 with pytest.raises(RuntimeWarning):
amine@400 1179 # max_read is not accepted when calling AudioRegion.split
amine@400 1180 region.split(max_read=2)
amine@223 1181
amine@223 1182
amine@400 1183 @pytest.mark.parametrize(
amine@405 1184 (
amine@405 1185 "data, start, sampling_rate, sample_width, channels, expected_end, "
amine@405 1186 + "expected_duration_s, expected_duration_ms"
amine@405 1187 ),
amine@400 1188 [
amine@405 1189 (b"\0" * 8000, 0, 8000, 1, 1, 1, 1, 1000), # simple
amine@405 1190 (
amine@405 1191 b"\0" * 7992,
amine@405 1192 0,
amine@405 1193 8000,
amine@405 1194 1,
amine@405 1195 1,
amine@405 1196 0.999,
amine@405 1197 0.999,
amine@405 1198 999,
amine@405 1199 ), # one_ms_less_than_1_sec
amine@405 1200 (
amine@405 1201 b"\0" * 7994,
amine@405 1202 0,
amine@405 1203 8000,
amine@405 1204 1,
amine@405 1205 1,
amine@405 1206 0.99925,
amine@405 1207 0.99925,
amine@405 1208 999,
amine@405 1209 ), # tree_quarter_ms_less_than_1_sec
amine@405 1210 (
amine@405 1211 b"\0" * 7996,
amine@405 1212 0,
amine@405 1213 8000,
amine@405 1214 1,
amine@405 1215 1,
amine@405 1216 0.9995,
amine@405 1217 0.9995,
amine@405 1218 1000,
amine@405 1219 ), # half_ms_less_than_1_sec
amine@405 1220 (
amine@405 1221 b"\0" * 7998,
amine@405 1222 0,
amine@405 1223 8000,
amine@405 1224 1,
amine@405 1225 1,
amine@405 1226 0.99975,
amine@405 1227 0.99975,
amine@405 1228 1000,
amine@405 1229 ), # quarter_ms_less_than_1_sec
amine@405 1230 (b"\0" * 8000 * 2, 0, 8000, 2, 1, 1, 1, 1000), # simple_sample_width_2
amine@405 1231 (b"\0" * 8000 * 2, 0, 8000, 1, 2, 1, 1, 1000), # simple_stereo
amine@405 1232 (b"\0" * 8000 * 5, 0, 8000, 1, 5, 1, 1, 1000), # simple_multichannel
amine@405 1233 (
amine@405 1234 b"\0" * 8000 * 2 * 5,
amine@405 1235 0,
amine@405 1236 8000,
amine@405 1237 2,
amine@405 1238 5,
amine@405 1239 1,
amine@405 1240 1,
amine@405 1241 1000,
amine@405 1242 ), # simple_sample_width_2_multichannel
amine@405 1243 (
amine@405 1244 b"\0" * 7992 * 2 * 5,
amine@405 1245 0,
amine@405 1246 8000,
amine@405 1247 2,
amine@405 1248 5,
amine@405 1249 0.999,
amine@405 1250 0.999,
amine@405 1251 999,
amine@405 1252 ), # one_ms_less_than_1s_sw_2_multichannel
amine@405 1253 (
amine@405 1254 b"\0" * 7994 * 2 * 5,
amine@405 1255 0,
amine@405 1256 8000,
amine@405 1257 2,
amine@405 1258 5,
amine@405 1259 0.99925,
amine@405 1260 0.99925,
amine@405 1261 999,
amine@405 1262 ), # tree_qrt_ms_lt_1_s_sw_2_multichannel
amine@405 1263 (
amine@405 1264 b"\0" * 7996 * 2 * 5,
amine@405 1265 0,
amine@405 1266 8000,
amine@405 1267 2,
amine@405 1268 5,
amine@405 1269 0.9995,
amine@405 1270 0.9995,
amine@405 1271 1000,
amine@405 1272 ), # half_ms_lt_1s_sw_2_multichannel
amine@405 1273 (
amine@405 1274 b"\0" * 7998 * 2 * 5,
amine@405 1275 0,
amine@405 1276 8000,
amine@405 1277 2,
amine@405 1278 5,
amine@405 1279 0.99975,
amine@405 1280 0.99975,
amine@405 1281 1000,
amine@405 1282 ), # quarter_ms_lt_1s_sw_2_multichannel
amine@405 1283 (
amine@405 1284 b"\0" * int(8000 * 1.33),
amine@405 1285 2.7,
amine@405 1286 8000,
amine@405 1287 1,
amine@405 1288 1,
amine@405 1289 4.03,
amine@405 1290 1.33,
amine@405 1291 1330,
amine@405 1292 ), # arbitrary_length_1
amine@405 1293 (
amine@405 1294 b"\0" * int(8000 * 0.476),
amine@405 1295 11.568,
amine@405 1296 8000,
amine@405 1297 1,
amine@405 1298 1,
amine@405 1299 12.044,
amine@405 1300 0.476,
amine@405 1301 476,
amine@405 1302 ), # arbitrary_length_2
amine@400 1303 (
amine@86 1304 b"\0" * int(8000 * 1.711) * 2 * 3,
amine@86 1305 9.415,
amine@86 1306 8000,
amine@86 1307 2,
amine@86 1308 3,
amine@86 1309 11.126,
amine@86 1310 1.711,
amine@86 1311 1711,
amine@405 1312 ), # arbitrary_length_sw_2_multichannel
amine@400 1313 (
amine@86 1314 b"\0" * int(3172 * 1.318),
amine@86 1315 17.236,
amine@86 1316 3172,
amine@86 1317 1,
amine@86 1318 1,
amine@86 1319 17.236 + int(3172 * 1.318) / 3172,
amine@86 1320 int(3172 * 1.318) / 3172,
amine@86 1321 1318,
amine@405 1322 ), # arbitrary_sampling_rate
amine@400 1323 (
amine@86 1324 b"\0" * int(11317 * 0.716) * 2 * 3,
amine@86 1325 18.811,
amine@86 1326 11317,
amine@86 1327 2,
amine@86 1328 3,
amine@86 1329 18.811 + int(11317 * 0.716) / 11317,
amine@86 1330 int(11317 * 0.716) / 11317,
amine@86 1331 716,
amine@405 1332 ), # arbitrary_sr_sw_2_multichannel
amine@400 1333 ],
amine@400 1334 ids=[
amine@400 1335 "simple",
amine@400 1336 "one_ms_less_than_1_sec",
amine@400 1337 "tree_quarter_ms_less_than_1_sec",
amine@400 1338 "half_ms_less_than_1_sec",
amine@400 1339 "quarter_ms_less_than_1_sec",
amine@400 1340 "simple_sample_width_2",
amine@400 1341 "simple_stereo",
amine@400 1342 "simple_multichannel",
amine@400 1343 "simple_sample_width_2_multichannel",
amine@400 1344 "one_ms_less_than_1s_sw_2_multichannel",
amine@400 1345 "tree_qrt_ms_lt_1_s_sw_2_multichannel",
amine@400 1346 "half_ms_lt_1s_sw_2_multichannel",
amine@400 1347 "quarter_ms_lt_1s_sw_2_multichannel",
amine@400 1348 "arbitrary_length_1",
amine@400 1349 "arbitrary_length_2",
amine@400 1350 "arbitrary_length_sw_2_multichannel",
amine@405 1351 "arbitrary_sampling_rate",
amine@400 1352 "arbitrary_sr_sw_2_multichannel",
amine@400 1353 ],
amine@400 1354 )
amine@400 1355 def test_creation(
amine@400 1356 data,
amine@400 1357 start,
amine@400 1358 sampling_rate,
amine@400 1359 sample_width,
amine@400 1360 channels,
amine@400 1361 expected_end,
amine@400 1362 expected_duration_s,
amine@400 1363 expected_duration_ms,
amine@400 1364 ):
amine@400 1365 meta = {"start": start, "end": expected_end}
amine@400 1366 region = AudioRegion(data, sampling_rate, sample_width, channels, meta)
amine@400 1367 assert region.sampling_rate == sampling_rate
amine@400 1368 assert region.sr == sampling_rate
amine@400 1369 assert region.sample_width == sample_width
amine@400 1370 assert region.sw == sample_width
amine@400 1371 assert region.channels == channels
amine@400 1372 assert region.ch == channels
amine@400 1373 assert region.meta.start == start
amine@400 1374 assert region.meta.end == expected_end
amine@400 1375 assert region.duration == expected_duration_s
amine@400 1376 assert len(region.ms) == expected_duration_ms
amine@400 1377 assert bytes(region) == data
amine@400 1378
amine@400 1379
amine@400 1380 def test_creation_invalid_data_exception():
amine@400 1381 with pytest.raises(AudioParameterError) as audio_param_err:
amine@400 1382 _ = AudioRegion(
amine@400 1383 data=b"ABCDEFGHI", sampling_rate=8, sample_width=2, channels=1
amine@400 1384 )
amine@400 1385 assert str(audio_param_err.value) == (
amine@400 1386 "The length of audio data must be an integer "
amine@400 1387 "multiple of `sample_width * channels`"
amine@86 1388 )
amine@88 1389
amine@97 1390
amine@400 1391 @pytest.mark.parametrize(
amine@400 1392 "skip, max_read, channels",
amine@400 1393 [
amine@405 1394 (0, -1, 1), # no_skip_read_all
amine@405 1395 (0, -1, 2), # no_skip_read_all_stereo
amine@405 1396 (2, -1, 1), # skip_2_read_all
amine@405 1397 (2, None, 1), # skip_2_read_all_None
amine@405 1398 (2, 3, 1), # skip_2_read_3
amine@405 1399 (2, 3.5, 2), # skip_2_read_3_5_stereo
amine@405 1400 (2.4, 3.5, 2), # skip_2_4_read_3_5_stereo
amine@400 1401 ],
amine@400 1402 ids=[
amine@400 1403 "no_skip_read_all",
amine@400 1404 "no_skip_read_all_stereo",
amine@400 1405 "skip_2_read_all",
amine@400 1406 "skip_2_read_all_None",
amine@400 1407 "skip_2_read_3",
amine@400 1408 "skip_2_read_3_5_stereo",
amine@400 1409 "skip_2_4_read_3_5_stereo",
amine@400 1410 ],
amine@400 1411 )
amine@400 1412 def test_load_AudioRegion(skip, max_read, channels):
amine@400 1413 sampling_rate = 10
amine@400 1414 sample_width = 2
amine@400 1415 filename = "tests/data/test_split_10HZ_{}.raw"
amine@400 1416 filename = filename.format("mono" if channels == 1 else "stereo")
amine@400 1417 region = AudioRegion.load(
amine@400 1418 filename,
amine@400 1419 skip=skip,
amine@400 1420 max_read=max_read,
amine@400 1421 sr=sampling_rate,
amine@400 1422 sw=sample_width,
amine@400 1423 ch=channels,
amine@308 1424 )
amine@400 1425 with open(filename, "rb") as fp:
amine@400 1426 fp.read(round(skip * sampling_rate * sample_width * channels))
amine@400 1427 if max_read is None or max_read < 0:
amine@400 1428 to_read = -1
amine@400 1429 else:
amine@400 1430 to_read = round(max_read * sampling_rate * sample_width * channels)
amine@400 1431 expected = fp.read(to_read)
amine@400 1432 assert bytes(region) == expected
amine@308 1433
amine@308 1434
amine@400 1435 def test_load_from_microphone():
amine@400 1436 with patch("auditok.io.PyAudioSource") as patch_pyaudio_source:
amine@400 1437 with patch("auditok.core.AudioReader.read") as patch_reader:
amine@400 1438 patch_reader.return_value = None
amine@400 1439 with patch(
amine@400 1440 "auditok.core.AudioRegion.__init__"
amine@400 1441 ) as patch_AudioRegion:
amine@400 1442 patch_AudioRegion.return_value = None
amine@400 1443 AudioRegion.load(None, skip=0, max_read=5, sr=16000, sw=2, ch=1)
amine@400 1444 assert patch_pyaudio_source.called
amine@400 1445 assert patch_reader.called
amine@400 1446 assert patch_AudioRegion.called
amine@307 1447
amine@308 1448
amine@400 1449 @pytest.mark.parametrize(
amine@400 1450 "max_read",
amine@400 1451 [
amine@405 1452 None, # None
amine@405 1453 -1, # negative
amine@400 1454 ],
amine@400 1455 ids=[
amine@405 1456 "None",
amine@400 1457 "negative",
amine@400 1458 ],
amine@400 1459 )
amine@400 1460 def test_load_from_microphone_without_max_read_exception(max_read):
amine@400 1461 with pytest.raises(ValueError) as val_err:
amine@400 1462 AudioRegion.load(None, max_read=max_read, sr=16000, sw=2, ch=1)
amine@400 1463 assert str(val_err.value) == (
amine@400 1464 "'max_read' should not be None when reading from microphone"
amine@400 1465 )
amine@400 1466
amine@400 1467
amine@400 1468 def test_load_from_microphone_with_nonzero_skip_exception():
amine@400 1469 with pytest.raises(ValueError) as val_err:
amine@400 1470 AudioRegion.load(None, skip=1, max_read=5, sr=16000, sw=2, ch=1)
amine@400 1471 assert str(val_err.value) == (
amine@400 1472 "'skip' should be 0 when reading from microphone"
amine@400 1473 )
amine@400 1474
amine@400 1475
amine@400 1476 @pytest.mark.parametrize(
amine@400 1477 "format, start, expected",
amine@400 1478 [
amine@405 1479 ("output.wav", 1.230, "output.wav"), # simple
amine@405 1480 ("output_{meta.start:g}.wav", 1.230, "output_1.23.wav"), # start
amine@405 1481 ("output_{meta.start}.wav", 1.233712, "output_1.233712.wav"), # start_2
amine@405 1482 (
amine@405 1483 "output_{meta.start:.2f}.wav",
amine@405 1484 1.2300001,
amine@405 1485 "output_1.23.wav",
amine@405 1486 ), # start_3
amine@405 1487 (
amine@405 1488 "output_{meta.start:.3f}.wav",
amine@405 1489 1.233712,
amine@405 1490 "output_1.234.wav",
amine@405 1491 ), # start_4
amine@405 1492 (
amine@405 1493 "output_{meta.start:.8f}.wav",
amine@405 1494 1.233712,
amine@405 1495 "output_1.23371200.wav",
amine@405 1496 ), # start_5
amine@400 1497 (
amine@244 1498 "output_{meta.start}_{meta.end}_{duration}.wav",
amine@192 1499 1.455,
amine@192 1500 "output_1.455_2.455_1.0.wav",
amine@405 1501 ), # start_end_duration
amine@400 1502 (
amine@244 1503 "output_{meta.start}_{meta.end}_{duration}.wav",
amine@192 1504 1.455321,
amine@192 1505 "output_1.455321_2.455321_1.0.wav",
amine@405 1506 ), # start_end_duration_2
amine@400 1507 ],
amine@400 1508 ids=[
amine@400 1509 "simple",
amine@400 1510 "start",
amine@400 1511 "start_2",
amine@400 1512 "start_3",
amine@400 1513 "start_4",
amine@400 1514 "start_5",
amine@400 1515 "start_end_duration",
amine@400 1516 "start_end_duration_2",
amine@400 1517 ],
amine@400 1518 )
amine@400 1519 def test_save(format, start, expected):
amine@400 1520 with TemporaryDirectory() as tmpdir:
amine@400 1521 region = AudioRegion(b"0" * 160, 160, 1, 1)
amine@400 1522 meta = {"start": start, "end": start + region.duration}
amine@400 1523 region.meta = meta
amine@400 1524 format = os.path.join(tmpdir, format)
amine@400 1525 filename = region.save(format)[len(tmpdir) + 1 :]
amine@400 1526 assert filename == expected
amine@192 1527
amine@193 1528
amine@400 1529 def test_save_file_exists_exception():
amine@400 1530 with TemporaryDirectory() as tmpdir:
amine@400 1531 filename = os.path.join(tmpdir, "output.wav")
amine@400 1532 open(filename, "w").close()
amine@400 1533 region = AudioRegion(b"0" * 160, 160, 1, 1)
amine@400 1534 with pytest.raises(FileExistsError):
amine@400 1535 region.save(filename, exists_ok=False)
amine@400 1536
amine@400 1537
amine@400 1538 @pytest.mark.parametrize(
amine@400 1539 "region, slice_, expected_data",
amine@400 1540 [
amine@400 1541 (
amine@244 1542 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@194 1543 slice(0, 500),
amine@405 1544 b"a" * 80, # first_half
amine@244 1545 ),
amine@400 1546 (
amine@244 1547 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1548 slice(500, None),
amine@405 1549 b"b" * 80, # second_half
amine@244 1550 ),
amine@400 1551 (
amine@244 1552 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1553 slice(-500, None),
amine@405 1554 b"b" * 80, # second_half_negative
amine@244 1555 ),
amine@400 1556 (
amine@244 1557 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1558 slice(200, 750),
amine@405 1559 b"a" * 48 + b"b" * 40, # middle
amine@244 1560 ),
amine@400 1561 (
amine@244 1562 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1563 slice(-800, -250),
amine@405 1564 b"a" * 48 + b"b" * 40, # middle_negative
amine@244 1565 ),
amine@400 1566 (
amine@244 1567 AudioRegion(b"a" * 160 + b"b" * 160, 160, 2, 1),
amine@244 1568 slice(200, 750),
amine@405 1569 b"a" * 96 + b"b" * 80, # middle_sw2
amine@244 1570 ),
amine@400 1571 (
amine@244 1572 AudioRegion(b"a" * 160 + b"b" * 160, 160, 1, 2),
amine@244 1573 slice(200, 750),
amine@405 1574 b"a" * 96 + b"b" * 80, # middle_ch2
amine@244 1575 ),
amine@400 1576 (
amine@244 1577 AudioRegion(b"a" * 320 + b"b" * 320, 160, 2, 2),
amine@244 1578 slice(200, 750),
amine@405 1579 b"a" * 192 + b"b" * 160, # middle_sw2_ch2
amine@244 1580 ),
amine@400 1581 (
amine@244 1582 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@244 1583 slice(1, None),
amine@405 1584 b"a" * (4000 - 8) + b"b" * 4000, # but_first_sample
amine@244 1585 ),
amine@400 1586 (
amine@244 1587 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@244 1588 slice(-999, None),
amine@405 1589 b"a" * (4000 - 8) + b"b" * 4000, # but_first_sample_negative
amine@244 1590 ),
amine@400 1591 (
amine@244 1592 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@244 1593 slice(0, 999),
amine@405 1594 b"a" * 4000 + b"b" * (4000 - 8), # but_last_sample
amine@244 1595 ),
amine@400 1596 (
amine@244 1597 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@244 1598 slice(0, -1),
amine@405 1599 b"a" * 4000 + b"b" * (4000 - 8), # but_last_sample_negative
amine@244 1600 ),
amine@405 1601 (
amine@405 1602 AudioRegion(b"a" * 160, 160, 1, 1),
amine@405 1603 slice(-5000, None),
amine@405 1604 b"a" * 160, # big_negative_start
amine@405 1605 ),
amine@405 1606 (
amine@405 1607 AudioRegion(b"a" * 160, 160, 1, 1),
amine@405 1608 slice(None, -1500),
amine@405 1609 b"", # big_negative_stop
amine@405 1610 ),
amine@405 1611 (
amine@405 1612 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@405 1613 slice(0, 0),
amine@405 1614 b"", # empty
amine@405 1615 ),
amine@405 1616 (
amine@405 1617 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@405 1618 slice(200, 100),
amine@405 1619 b"", # empty_start_stop_reversed
amine@405 1620 ),
amine@405 1621 (
amine@405 1622 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@405 1623 slice(2000, 3000),
amine@405 1624 b"", # empty_big_positive_start
amine@405 1625 ),
amine@405 1626 (
amine@405 1627 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@405 1628 slice(-100, -200),
amine@405 1629 b"", # empty_negative_reversed
amine@405 1630 ),
amine@405 1631 (
amine@405 1632 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@405 1633 slice(0, -2000),
amine@405 1634 b"", # empty_big_negative_stop
amine@405 1635 ),
amine@400 1636 (
amine@244 1637 AudioRegion(b"a" * 124 + b"b" * 376, 1234, 1, 1),
amine@244 1638 slice(100, 200),
amine@405 1639 b"a" + b"b" * 123, # arbitrary_sampling_rate
amine@244 1640 ),
amine@400 1641 ],
amine@400 1642 ids=[
amine@400 1643 "first_half",
amine@400 1644 "second_half",
amine@400 1645 "second_half_negative",
amine@400 1646 "middle",
amine@400 1647 "middle_negative",
amine@400 1648 "middle_sw2",
amine@400 1649 "middle_ch2",
amine@400 1650 "middle_sw2_ch2",
amine@400 1651 "but_first_sample",
amine@400 1652 "but_first_sample_negative",
amine@400 1653 "but_last_sample",
amine@400 1654 "but_last_sample_negative",
amine@400 1655 "big_negative_start",
amine@400 1656 "big_negative_stop",
amine@400 1657 "empty",
amine@400 1658 "empty_start_stop_reversed",
amine@400 1659 "empty_big_positive_start",
amine@400 1660 "empty_negative_reversed",
amine@400 1661 "empty_big_negative_stop",
amine@400 1662 "arbitrary_sampling_rate",
amine@400 1663 ],
amine@400 1664 )
amine@400 1665 def test_region_temporal_slicing(region, slice_, expected_data):
amine@400 1666 sub_region = region.millis[slice_]
amine@400 1667 assert bytes(sub_region) == expected_data
amine@400 1668 start_sec = slice_.start / 1000 if slice_.start is not None else None
amine@400 1669 stop_sec = slice_.stop / 1000 if slice_.stop is not None else None
amine@400 1670 sub_region = region.sec[start_sec:stop_sec]
amine@400 1671 assert bytes(sub_region) == expected_data
amine@244 1672
amine@400 1673
amine@400 1674 @pytest.mark.parametrize(
amine@400 1675 "region, slice_, time_shift, expected_data",
amine@400 1676 [
amine@400 1677 (
amine@244 1678 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1679 slice(0, 80),
amine@194 1680 0,
amine@405 1681 b"a" * 80, # first_half
amine@194 1682 ),
amine@400 1683 (
amine@244 1684 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1685 slice(80, None),
amine@194 1686 0.5,
amine@405 1687 b"b" * 80, # second_half
amine@194 1688 ),
amine@400 1689 (
amine@244 1690 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1691 slice(-80, None),
amine@194 1692 0.5,
amine@405 1693 b"b" * 80, # second_half_negative
amine@194 1694 ),
amine@400 1695 (
amine@244 1696 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1697 slice(160 // 5, 160 // 4 * 3),
amine@194 1698 0.2,
amine@405 1699 b"a" * 48 + b"b" * 40, # middle
amine@194 1700 ),
amine@400 1701 (
amine@244 1702 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1703 slice(-160 // 5 * 4, -160 // 4),
amine@194 1704 0.2,
amine@405 1705 b"a" * 48 + b"b" * 40, # middle_negative
amine@194 1706 ),
amine@400 1707 (
amine@244 1708 AudioRegion(b"a" * 160 + b"b" * 160, 160, 2, 1),
amine@244 1709 slice(160 // 5, 160 // 4 * 3),
amine@194 1710 0.2,
amine@405 1711 b"a" * 96 + b"b" * 80, # middle_sw2
amine@194 1712 ),
amine@400 1713 (
amine@244 1714 AudioRegion(b"a" * 160 + b"b" * 160, 160, 1, 2),
amine@244 1715 slice(160 // 5, 160 // 4 * 3),
amine@194 1716 0.2,
amine@405 1717 b"a" * 96 + b"b" * 80, # middle_ch2
amine@194 1718 ),
amine@400 1719 (
amine@244 1720 AudioRegion(b"a" * 320 + b"b" * 320, 160, 2, 2),
amine@244 1721 slice(160 // 5, 160 // 4 * 3),
amine@194 1722 0.2,
amine@405 1723 b"a" * 192 + b"b" * 160, # middle_sw2_ch2
amine@194 1724 ),
amine@400 1725 (
amine@244 1726 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@194 1727 slice(1, None),
amine@244 1728 1 / 8000,
amine@405 1729 b"a" * (4000 - 1) + b"b" * 4000, # but_first_sample
amine@194 1730 ),
amine@400 1731 (
amine@244 1732 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@244 1733 slice(-7999, None),
amine@244 1734 1 / 8000,
amine@405 1735 b"a" * (4000 - 1) + b"b" * 4000, # but_first_sample_negative
amine@194 1736 ),
amine@400 1737 (
amine@244 1738 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@244 1739 slice(0, 7999),
amine@194 1740 0,
amine@405 1741 b"a" * 4000 + b"b" * (4000 - 1), # but_last_sample
amine@194 1742 ),
amine@400 1743 (
amine@244 1744 AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
amine@194 1745 slice(0, -1),
amine@194 1746 0,
amine@405 1747 b"a" * 4000 + b"b" * (4000 - 1), # but_last_sample_negative
amine@194 1748 ),
amine@405 1749 (
amine@405 1750 AudioRegion(b"a" * 160, 160, 1, 1),
amine@405 1751 slice(-1600, None),
amine@405 1752 0,
amine@405 1753 b"a" * 160, # big_negative_start
amine@405 1754 ),
amine@405 1755 (
amine@405 1756 AudioRegion(b"a" * 160, 160, 1, 1),
amine@405 1757 slice(None, -1600),
amine@405 1758 0,
amine@405 1759 b"", # big_negative_stop
amine@405 1760 ),
amine@405 1761 (
amine@405 1762 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@405 1763 slice(0, 0),
amine@405 1764 0,
amine@405 1765 b"", # empty
amine@405 1766 ),
amine@400 1767 (
amine@244 1768 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1769 slice(80, 40),
amine@244 1770 0.5,
amine@405 1771 b"", # empty_start_stop_reversed
amine@194 1772 ),
amine@400 1773 (
amine@244 1774 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1775 slice(1600, 3000),
amine@244 1776 10,
amine@405 1777 b"", # empty_big_positive_start
amine@194 1778 ),
amine@400 1779 (
amine@244 1780 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@244 1781 slice(-16, -32),
amine@194 1782 0.9,
amine@405 1783 b"", # empty_negative_reversed
amine@194 1784 ),
amine@400 1785 (
amine@244 1786 AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
amine@194 1787 slice(0, -2000),
amine@194 1788 0,
amine@405 1789 b"", # empty_big_negative_stop
amine@194 1790 ),
amine@400 1791 (
amine@244 1792 AudioRegion(b"a" * 124 + b"b" * 376, 1235, 1, 1),
amine@231 1793 slice(100, 200),
amine@231 1794 100 / 1235,
amine@405 1795 b"a" * 24 + b"b" * 76, # arbitrary_sampling_rate
amine@231 1796 ),
amine@400 1797 (
amine@244 1798 AudioRegion(b"a" * 124 + b"b" * 376, 1235, 2, 2),
amine@231 1799 slice(25, 50),
amine@231 1800 25 / 1235,
amine@405 1801 b"a" * 24 + b"b" * 76, # arbitrary_sampling_rate_middle_sw2_ch2
amine@231 1802 ),
amine@400 1803 ],
amine@400 1804 ids=[
amine@400 1805 "first_half",
amine@400 1806 "second_half",
amine@400 1807 "second_half_negative",
amine@400 1808 "middle",
amine@400 1809 "middle_negative",
amine@400 1810 "middle_sw2",
amine@400 1811 "middle_ch2",
amine@400 1812 "middle_sw2_ch2",
amine@400 1813 "but_first_sample",
amine@400 1814 "but_first_sample_negative",
amine@400 1815 "but_last_sample",
amine@400 1816 "but_last_sample_negative",
amine@400 1817 "big_negative_start",
amine@400 1818 "big_negative_stop",
amine@400 1819 "empty",
amine@400 1820 "empty_start_stop_reversed",
amine@400 1821 "empty_big_positive_start",
amine@400 1822 "empty_negative_reversed",
amine@400 1823 "empty_big_negative_stop",
amine@400 1824 "arbitrary_sampling_rate",
amine@400 1825 "arbitrary_sampling_rate_middle_sw2_ch2",
amine@400 1826 ],
amine@400 1827 )
amine@400 1828 def test_region_sample_slicing(region, slice_, time_shift, expected_data):
amine@400 1829 sub_region = region[slice_]
amine@400 1830 assert bytes(sub_region) == expected_data
amine@400 1831
amine@400 1832
amine@400 1833 @pytest.mark.parametrize(
amine@400 1834 "sampling_rate, sample_width, channels",
amine@400 1835 [
amine@405 1836 (8000, 1, 1), # simple
amine@405 1837 (8000, 2, 2), # stereo_sw_2
amine@405 1838 (5413, 2, 3), # arbitrary_sr_multichannel
amine@400 1839 ],
amine@400 1840 ids=[
amine@400 1841 "simple",
amine@400 1842 "stereo_sw_2",
amine@400 1843 "arbitrary_sr_multichannel",
amine@400 1844 ],
amine@400 1845 )
amine@400 1846 def test_concatenation(sampling_rate, sample_width, channels):
amine@400 1847
amine@400 1848 region_1, region_2 = _make_random_length_regions(
amine@400 1849 [b"a", b"b"], sampling_rate, sample_width, channels
amine@231 1850 )
amine@400 1851 expected_duration = region_1.duration + region_2.duration
amine@400 1852 expected_data = bytes(region_1) + bytes(region_2)
amine@400 1853 concat_region = region_1 + region_2
amine@400 1854 assert concat_region.duration == pytest.approx(expected_duration, abs=1e-6)
amine@400 1855 assert bytes(concat_region) == expected_data
amine@231 1856
amine@400 1857
amine@400 1858 @pytest.mark.parametrize(
amine@400 1859 "sampling_rate, sample_width, channels",
amine@400 1860 [
amine@405 1861 (8000, 1, 1), # simple
amine@405 1862 (8000, 2, 2), # stereo_sw_2
amine@405 1863 (5413, 2, 3), # arbitrary_sr_multichannel
amine@400 1864 ],
amine@400 1865 ids=[
amine@400 1866 "simple",
amine@400 1867 "stereo_sw_2",
amine@400 1868 "arbitrary_sr_multichannel",
amine@400 1869 ],
amine@400 1870 )
amine@400 1871 def test_concatenation_many(sampling_rate, sample_width, channels):
amine@400 1872
amine@400 1873 regions = _make_random_length_regions(
amine@400 1874 [b"a", b"b", b"c"], sampling_rate, sample_width, channels
amine@88 1875 )
amine@400 1876 expected_duration = sum(r.duration for r in regions)
amine@400 1877 expected_data = b"".join(bytes(r) for r in regions)
amine@400 1878 concat_region = sum(regions)
amine@88 1879
amine@400 1880 assert concat_region.duration == pytest.approx(expected_duration, abs=1e-6)
amine@400 1881 assert bytes(concat_region) == expected_data
amine@88 1882
amine@400 1883
amine@400 1884 def test_concatenation_different_sampling_rate_error():
amine@400 1885 region_1 = AudioRegion(b"a" * 100, 8000, 1, 1)
amine@400 1886 region_2 = AudioRegion(b"b" * 100, 3000, 1, 1)
amine@400 1887
amine@400 1888 with pytest.raises(ValueError) as val_err:
amine@400 1889 region_1 + region_2
amine@400 1890 assert str(val_err.value) == (
amine@400 1891 "Can only concatenate AudioRegions of the same "
amine@405 1892 "sampling rate (8000 != 3000)" # different_sampling_rate
amine@88 1893 )
amine@88 1894
amine@88 1895
amine@400 1896 def test_concatenation_different_sample_width_error():
amine@400 1897 region_1 = AudioRegion(b"a" * 100, 8000, 2, 1)
amine@400 1898 region_2 = AudioRegion(b"b" * 100, 8000, 4, 1)
amine@88 1899
amine@400 1900 with pytest.raises(ValueError) as val_err:
amine@400 1901 region_1 + region_2
amine@400 1902 assert str(val_err.value) == (
amine@405 1903 "Can only concatenate AudioRegions of the same sample width (2 != 4)"
amine@400 1904 )
amine@88 1905
amine@88 1906
amine@400 1907 def test_concatenation_different_number_of_channels_error():
amine@400 1908 region_1 = AudioRegion(b"a" * 100, 8000, 1, 1)
amine@400 1909 region_2 = AudioRegion(b"b" * 100, 8000, 1, 2)
amine@88 1910
amine@400 1911 with pytest.raises(ValueError) as val_err:
amine@400 1912 region_1 + region_2
amine@400 1913 assert str(val_err.value) == (
amine@400 1914 "Can only concatenate AudioRegions of the same "
amine@405 1915 "number of channels (1 != 2)" # different_number_of_channels
amine@400 1916 )
amine@88 1917
amine@88 1918
amine@400 1919 @pytest.mark.parametrize(
amine@400 1920 "duration, expected_duration, expected_len, expected_len_ms",
amine@400 1921 [
amine@405 1922 (0.01, 0.03, 240, 30), # simple
amine@405 1923 (0.00575, 0.01725, 138, 17), # rounded_len_floor
amine@405 1924 (0.00625, 0.01875, 150, 19), # rounded_len_ceil
amine@400 1925 ],
amine@400 1926 ids=[
amine@400 1927 "simple",
amine@400 1928 "rounded_len_floor",
amine@400 1929 "rounded_len_ceil",
amine@400 1930 ],
amine@400 1931 )
amine@400 1932 def test_multiplication(
amine@400 1933 duration, expected_duration, expected_len, expected_len_ms
amine@400 1934 ):
amine@400 1935 sw = 2
amine@400 1936 data = b"0" * int(duration * 8000 * sw)
amine@400 1937 region = AudioRegion(data, 8000, sw, 1)
amine@400 1938 m_region = 1 * region * 3
amine@400 1939 assert bytes(m_region) == data * 3
amine@400 1940 assert m_region.sr == 8000
amine@400 1941 assert m_region.sw == 2
amine@400 1942 assert m_region.ch == 1
amine@400 1943 assert m_region.duration == expected_duration
amine@400 1944 assert len(m_region) == expected_len
amine@400 1945 assert m_region.len == expected_len
amine@400 1946 assert m_region.s.len == expected_duration
amine@400 1947 assert len(m_region.ms) == expected_len_ms
amine@400 1948 assert m_region.ms.len == expected_len_ms
amine@88 1949
amine@196 1950
amine@400 1951 @pytest.mark.parametrize(
amine@400 1952 "factor, _type",
amine@400 1953 [
amine@405 1954 ("x", str), # string
amine@405 1955 (1.4, float), # float
amine@400 1956 ],
amine@400 1957 ids=[
amine@405 1958 "string",
amine@405 1959 "float",
amine@400 1960 ],
amine@400 1961 )
amine@400 1962 def test_multiplication_non_int(factor, _type):
amine@400 1963 with pytest.raises(TypeError) as type_err:
amine@400 1964 AudioRegion(b"0" * 80, 8000, 1, 1) * factor
amine@405 1965 err_msg = "Can't multiply AudioRegion by a non-int of type '{}'"
amine@405 1966 assert err_msg.format(_type) == str(type_err.value)
amine@197 1967
amine@254 1968
amine@400 1969 @pytest.mark.parametrize(
amine@400 1970 "data",
amine@400 1971 [
amine@405 1972 [b"a" * 80, b"b" * 80], # simple
amine@405 1973 [b"a" * 31, b"b" * 31, b"c" * 30], # extra_samples_1
amine@405 1974 [b"a" * 31, b"b" * 30, b"c" * 30], # extra_samples_2
amine@405 1975 [b"a" * 11, b"b" * 11, b"c" * 10, b"c" * 10], # extra_samples_3
amine@400 1976 ],
amine@400 1977 ids=[
amine@400 1978 "simple",
amine@400 1979 "extra_samples_1",
amine@400 1980 "extra_samples_2",
amine@400 1981 "extra_samples_3",
amine@400 1982 ],
amine@400 1983 )
amine@400 1984 def test_truediv(data):
amine@254 1985
amine@400 1986 region = AudioRegion(b"".join(data), 80, 1, 1)
amine@252 1987
amine@400 1988 sub_regions = region / len(data)
amine@405 1989 for data_i, region in zip(data, sub_regions, strict=True):
amine@400 1990 assert len(data_i) == len(bytes(region))
amine@254 1991
amine@254 1992
amine@400 1993 @pytest.mark.parametrize(
amine@405 1994 "data, sample_width, channels, expected",
amine@400 1995 [
amine@405 1996 (b"a" * 10, 1, 1, [97] * 10), # mono_sw_1
amine@405 1997 (b"a" * 10, 2, 1, [24929] * 5), # mono_sw_2
amine@405 1998 (b"a" * 8, 4, 1, [1633771873] * 2), # mono_sw_4
amine@405 1999 (b"ab" * 5, 1, 2, [[97] * 5, [98] * 5]), # stereo_sw_1
amine@400 2000 ],
amine@400 2001 ids=[
amine@400 2002 "mono_sw_1",
amine@400 2003 "mono_sw_2",
amine@400 2004 "mono_sw_4",
amine@400 2005 "stereo_sw_1",
amine@400 2006 ],
amine@400 2007 )
amine@405 2008 def test_samples(data, sample_width, channels, expected):
amine@337 2009
amine@400 2010 region = AudioRegion(data, 10, sample_width, channels)
amine@405 2011 expected = np.array(expected)
amine@405 2012 assert (region.samples == expected).all()
amine@405 2013 assert (region.numpy() == expected).all()
amine@405 2014 assert (np.array(region) == expected).all()