"""
Generate simple waveforms
"""
import numpy as np
import random
import itertools
from typing import Mapping, Callable, Sequence, Optional
from collections import defaultdict
DFLT_SR = 44100
DFLT_CHK_SIZE_FRM = 21 * 2048
DFLT_MAX_AMPLITUDE = 30000
DFLT_PATTERN_LEN = 100
DFLT_PATTERN = [DFLT_MAX_AMPLITUDE] * 10 + [-DFLT_MAX_AMPLITUDE] * 10
[docs]def chk_from_pattern(chk_size_frm=DFLT_CHK_SIZE_FRM, pattern=None):
"""
Returns a chk with length chk_size_frm that repeats pattern if given, or creates a random pattern of length 100
>>> np.random.seed(1)
>>> assert all(chk_from_pattern(5) == [3003, -17828, -24808, 2511, 20057])
>>> chk = chk_from_pattern(6, [1,2,3])
>>> assert all(chk == [1, 2, 3, 1, 2, 3])
"""
if pattern is None:
pattern = random_samples(DFLT_PATTERN_LEN, DFLT_MAX_AMPLITUDE)
return np.tile(pattern, reps=int(np.ceil(chk_size_frm / float(len(pattern)))))[
:chk_size_frm
].astype(np.int16)
[docs]def random_samples(
chk_size_frm=DFLT_CHK_SIZE_FRM, max_amplitude=DFLT_MAX_AMPLITUDE, **kwargs
):
"""
Returns a random sample of integers of length chk_size_frm in the range [-max_amplitude, max_amplitude]
>>> np.random.seed(1)
>>> assert all(random_samples(5) == [3003, -17828, -24808, 2511, 20057])
"""
return np.random.randint(-max_amplitude, max_amplitude, chk_size_frm).astype(
np.int16
)
[docs]def pure_tone(
chk_size_frm=DFLT_CHK_SIZE_FRM,
freq=440,
sr=DFLT_SR,
max_amplitude=DFLT_MAX_AMPLITUDE,
):
"""
Generates a pure tone using given arguments
>>> np.random.seed(1)
>>> assert all(pure_tone(5) == [0, 1902, 3797, 5677, 7534])
"""
pattern_length = int(sr / freq)
pattern = max_amplitude * np.sin(np.linspace(0, 2 * np.pi, pattern_length))
return chk_from_pattern(chk_size_frm, pattern)
[docs]def triangular_tone(
chk_size_frm=DFLT_CHK_SIZE_FRM,
freq=440,
sr=DFLT_SR,
max_amplitude=DFLT_MAX_AMPLITUDE,
):
"""
Generates a triangular tone using given arguments
>>> np.random.seed(1)
>>> assert all(triangular_tone(5) == [-30000, -29900, -29800, -29700, -29600])
"""
pattern_length = int(sr / freq)
pattern = np.arange(-max_amplitude, max_amplitude, pattern_length)
return chk_from_pattern(chk_size_frm, pattern)
[docs]def square_tone(
chk_size_frm=DFLT_CHK_SIZE_FRM,
freq=440,
sr=DFLT_SR,
max_amplitude=DFLT_MAX_AMPLITUDE,
):
"""
Generates a square tone using given arguments
>>> np.random.seed(1)
>>> assert all(square_tone(5) == [30000, 30000, 30000, 30000, 30000])
"""
pattern_length = int(sr / freq)
half_pattern_length = int(pattern_length / 2) # oh well for the plus minus 1
pattern = [max_amplitude] * half_pattern_length + [
-max_amplitude
] * half_pattern_length
return chk_from_pattern(chk_size_frm, pattern)
tag_to_wf_gen_func = {
'random': random_samples,
'pure_tone': pure_tone,
'triangular_tone': triangular_tone,
'square_tone': square_tone,
}
[docs]def tag_wf_gen(
tag_wfgen_map: Optional[Mapping[object, Callable[[], Sequence]]] = None,
tag_sequence=None,
):
"""Generate (tag, wf) pairs.
:param tag_wfgen_map: A {tag: wfgen, ...} map where wfgen is a callable taking no arguments and returning a sequence
:param tag_sequence: A sequence of tags (that should all be keys of tag_wfgen_map)
:return:
"""
if tag_wfgen_map is None:
tag_wfgen_map = tag_to_wf_gen_func
if tag_sequence is None:
indefinite_random_choice_from_tags = map(
random.choice, itertools.repeat(list(tag_wfgen_map))
)
tag_sequence = indefinite_random_choice_from_tags
for tag in tag_sequence:
yield tag, tag_wfgen_map[tag]()