Source code for thebeat.linguistic

# Copyright (C) 2022-2023  Jelle van der Werff
#
# This file is part of thebeat.
#
# thebeat is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# thebeat is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with thebeat.  If not, see <https://www.gnu.org/licenses/>.

from __future__ import annotations
import numpy as np
import thebeat.core
from typing import Optional


[docs]def generate_moraic_sequence(n_feet: int, foot_ioi: float, rng: Optional[np.random.Generator] = None) -> thebeat.core.Sequence: """ This function generates a Sequence object with inter-onset intervals (IOIs) mimicing the rhythmic structure of mora-timed languages. The feet contain clusters of either one or two IOIs with the same total duration. The total duration is specified in ``foot_ioi``. The phrases all have the same duration, but are made up of different combinations of IOIs. Parameters ---------- n_feet The number of feet in the sequence. foot_ioi The duration in milliseconds of the foot. rng A :class:`numpy.random.Generator` object, if none is supplied will default to :func:`numpy.random.default_rng()` Examples -------- >>> rng = np.random.default_rng(seed=123) >>> seq = generate_moraic_sequence(n_feet=3, foot_ioi=500, rng=rng) >>> print(seq.iois) [500. 208.33333333 291.66666667 500. ] Notes ----- Both the methodology and the code are based on/taken from :cite:t:`ravignaniMeasuringRhythmicComplexity2017`. """ if rng is None: rng = np.random.default_rng() start_of_pattern = 2 # built around clusters with either one or two iois with same total duration (3 * syllable_ioi) ioi_types = (np.arange(start=1, stop=8) / 4) iois = np.array([], dtype=np.float32) i = 0 while i < n_feet: if rng.choice([1, 2], 1) == 1 or i == (n_feet - 1): iois = np.append(iois, 3) else: ioi_one = rng.choice(ioi_types, 1) ioi_two = 3 - ioi_one iois = np.concatenate([iois, ioi_one, ioi_two]) i += 1 ioi_sums = np.cumsum(iois) pattern_shifted = ioi_sums[:-1] + start_of_pattern pattern = np.concatenate([[start_of_pattern], pattern_shifted]) # Get iois from pattern iois[:-1] = np.diff(pattern) # Calculate with proper tempo iois = iois * (foot_ioi / 3) return thebeat.core.Sequence(iois)
[docs]def generate_stress_timed_sequence(n_events_per_phrase: int, syllable_ioi: int = 500, n_phrases: int = 1, rng: Optional[np.random.Generator] = None) -> thebeat.core.Sequence: """ This function generates a Sequence object with inter-onset intervals (IOIs) mimicing the rhythmic structure of stress-timed languages. In one sequence (cf. 'sentence'), there are ``n_phrases`` of ``n_events_per_phrase``. The phrases all have the same duration, but are made up of different combinations of IOIs. Parameters ---------- n_events_per_phrase The number of events in a single 'phrase'. syllable_ioi The duration of the reference IOI in milliseconds. n_phrases The number of phrases in the sequence. rng A :class:`numpy.random.Generator` object. if none is supplied will default to :func:`numpy.random.default_rng`. Examples -------- >>> rng = np.random.default_rng(seed=123) >>> seq = generate_stress_timed_sequence(n_events_per_phrase=4, n_phrases=3, rng=rng) >>> print(seq.iois) [ 62.5 687.5 562.5 687.5 62.5 875. 250. 812.5 250. 187.5 375. ] Notes ----- Both the methodology and the code are based on/taken from :cite:t:`ravignaniMeasuringRhythmicComplexity2017`. """ if rng is None: rng = np.random.default_rng() start_of_pattern = syllable_ioi ioi_types = (np.arange(start=1, stop=16) / 8) * syllable_ioi iois = np.array([]) c = 0 while c < n_phrases: iois_pattern = rng.choice(ioi_types, n_events_per_phrase - 1) # add a final ioi so that cluster duration = nIOI * ioiDur final_ioi = (n_events_per_phrase * syllable_ioi) - np.sum(iois_pattern) # if there is not enough room for a final ioi, repeat (q'n'd..) if final_ioi >= 0.25: iois_pattern = np.concatenate([iois_pattern, [final_ioi]]) iois = np.concatenate([iois, iois_pattern]) c += 1 else: continue ioi_sums = np.cumsum(iois) pattern_shifted = ioi_sums[:-1] + start_of_pattern pattern = np.concatenate([[start_of_pattern], pattern_shifted]) return thebeat.core.Sequence.from_onsets(pattern)