norm
def norm(
s
):Text in the form Su-śrotā writes it: NFC, without accents, digits, daṇḍas or avagraha, and with word-final म् as anusvāra.
A text-to-speech model sometimes mumbles, skips a word or garbles a line, and nobody can listen to thousands of renders. This module uses a speech recogniser to check them instead: it transcribes each render, measures how far the transcript is from the text, re-renders the bad ones, and finds where each word sits in the audio.
The recogniser is Su-śrotā, IndicConformer fine-tuned on śāstric Sanskrit. Its CTC output layer covers all 22 IndicConformer languages, and Sanskrit is the 256 columns starting at 4096, with the blank token at 5632. The checkpoint is configured for AI4Bharat’s fork of NeMo (a multilingual tokenizer and a multisoftmax decoder), so Ear rewrites the configuration for the standard NeMo: the tokenizer type becomes agg, the settings only the fork understands are removed, and the weights are loaded without requiring every key to match. Only the encoder and the CTC output layer are used. Install it with pip install 'vachaka[check]'.
Su-śrotā does not write exactly what the text says. It leaves out daṇḍas, digits and the avagraha (ऽ), and writes a word-final म् as an anusvāra (ं). norm(s) brings any text to that form, so a transcript can be compared with its source fairly. cer(ref, hyp) is the character error rate: the edit distance (lev, the number of single-character insertions, deletions and substitutions) between the two normalised texts, divided by the length of the reference. Spaces are ignored, because Sanskrit texts differ in where they split words joined by sandhi.
Text in the form Su-śrotā writes it: NFC, without accents, digits, daṇḍas or avagraha, and with word-final म् as anusvāra.
The character error rate of hyp against ref, both normalised and with spaces ignored.
Ear() loads Su-śrotā on the CPU. hear(audio, sr) transcribes audio and returns the normalised text. post(audio, sr) returns the model’s raw output for each 40 ms frame, the log-probability of the blank and of each of the 256 Sanskrit tokens, together with the frame length in milliseconds; words below uses it to align.
Each render uses a random seed, and a different seed gives a slightly different performance. best_of(render, ear, text) calls render(seed=...) up to tries (3) times with different seeds, measures each result’s character error rate against text, and keeps the best, stopping early once one is at or below max_cer (0.08). The rate is stored on the result as Recitation.cer.
ear.heard(rec) transcribes a render one piece at a time, using its spans, and joins the results. Transcribing the whole render at once fails on long runs of nearly identical lines, such as a tarpaṇa or the eight directions of a digvandana: the recogniser loses its place, and good renders scored between 0.47 and 0.76.
Transcribe rec one piece of its spans at a time and join the transcripts.
class _E(Ear):
def __init__(self): self.got = []
def hear(self, audio, sr): self.got.append(len(audio)); return 'क'
e, r = _E(), AttrDict(audio=np.zeros(16000*3, np.float32), sr=16000, spans=[(0, 1000), (1500, 3000)])
test_eq(e.heard(r), 'क क'); test_eq(e.got, [16000, 24000])
test_eq(_E().heard(AttrDict(audio=np.zeros(800, np.float32), sr=16000)), 'क')Call render(seed=...) up to tries times and keep the render whose transcript is closest to text.
ear.words(rec, words) finds the start and end of each word, given a render and, for each of its pieces, the list of words as displayed. It force-aligns them, matching the known word sequence against the audio with torchaudio’s CTC aligner, inside each piece’s own span, so a badly rendered line cannot drag the timings of the others. Each word is tokenised from its cleaned form. A word with no Sanskrit tokens, such as a lone daṇḍa, gets an empty span at the end of the word before it. The result has one list per piece, of [word, start_ms, end_ms].
The [word, start_ms, end_ms] of each displayed word, for each piece of rec.
These cells need the Su-śrotā checkpoint (520 MB) and the Vāgdhenu weights, so they do not run in the tests.