Časté chyby ve skriptech

Pro posun na další strany použijte kurzorové šipky

Ⓒ Tomáš Bořil
borilt@gmail.com, 12. 4. 2015

Typy chyb

Chyby rozdělujeme na

  • Syntaktické: chyba v zápise, např. chybějící závorka apod.
  • Sémantické: program dělá něco jiného, než jsme zamýšleli.

„Programátor si málokdy vystačí s naučeným postupem. Neustále naráží na problémy a výzvy, nejvíce času tráví nad hledáním řešení nových situací.

Avšak skutečným programátorem se stane teprve tehdy, když ovládne techniku hledání a opravování chyb.“

Syntaktické chyby

Syntaktické chyby jsou příjemnější, protože

  • se snáze odhalují,
  • Praat nás na ně sám upozorní, takže o chybě víme,
  • ukáže nám, na kterém řádku chyba je a co se mu nelíbí.

Sémantické chyby

Sémantické chyby jsou velice nepříjemné, protože

  • si jich často na první pohled nevšimneme,
  • špatně se hledají (chyba může být důsledkem spousty navazujících operací),
  • mohou se projevit jen někdy,
  • často jsme v klidu, protože nám „něco vyšlo“ a nedojde nám, že je to špatně.

Jako příklad vzpomeňme havárii Mars Climate Orbiteru v roce 1999, kdy na misi stojící přes 300 milionů dolarů spolupracovalo několik týmů. Zatímco program v sondě očekával instrukce v metrických mírách, řídící středisko odesílalo hodnoty v anglických mírách. Satelit byl ztracen po téměř deseti měsících bezchybného letu k Marsu.

Chybná data

Samostatnou kapitolou jsou chybná data.

  • Sebelepší skript je zbytečný, pokud zpracovává špatná data.
  • Data mohou být nevhodně změřená, získaná z chybně postaveného experimentu apod., nebo jen unaveným uživatelem nesprávně do skriptu vložená.

Některé základní chyby v datech ale můžeme automatickými postupy odhalit a je dobré do skriptu umístit alespoň základní testy chránící uživatele před jeho vlastním pochybením. Například předpokládáme, že skript má zpracovat 2. vrstvu TextGridu obsahující hranice a labely segmentů. Je moudré pomocí jednoduché podmínky ověřit, že tento předpoklad je splněn a vrstva neobsahuje např. slova. V případě nesplnění je nutné uživatele na problém upozornit a skript předčasně ukončit.

Pokud náš skript vyžaduje vzorkovací frekvenci zvukového signálu fs = 32 000 Hz, opět je vhodné nespoléhat na to, že uživatel pozorně přečetl instrukce k použití, a raději provést automatickou kontrolu.

Samozřejmě je dobré zvážit, pro koho je program určen a jak závažná je jeho činnost, a nestrávit s ošetřením všech možných pochybení obsluhy více času, než kolik samotný skript v souhrnu ušetří.

Jak minimalizovat riziko chyb?

Rozložte velký problém na menší díly. Tvořte po částech. Začněte tím nejjednodušším. Používejte dočasné pomocné výpisy. Ověřte si, že daná věc funguje. Pokud ano, přidávejte další kroky. Pokud jste spokojeni, pomocné výpisy zakomentujte, aby se k nim dalo kdykoliv vrátit.

Např. úkolem je vypočítat průměrnou hodnotu F2 uprostřed vokálů [a] a [a:].

1) Načtení dat – proběho v pořádku?

2) Zjistit počet intervalů. Kolik jich je? Dává to číslo smysl?

3) For cyklus pro průchod intervalů – pro kontrolu je všechny vypisovat. Je to správně? Nebo je to třeba zcela jiná transkripce nebo se jedná o vrstvu slov?

4) Podmínka if – vypisovat nalezené labely. Jsou to skutečně jen [a] a [a:]?

5) Vypočítat trvání. Vypsat – jsou to rozumné hodnoty? Vypočítat hranice prostřední třetiny.

6) Zjistit hodnotu formantu – potřebujeme objekt Formant, necháme ho před cyklem vyrobit. Vypsat zjištěné formanty – jsou rozumné, nebo je extrakce nastavena špatně?

7) Přičteme hodnoty do sumy a zvyšujeme počítadlo – vypsat počítadlo, funguje?

8) Po cyklu přidat výpočet průměru. Když vše jde, tak na závěr provést „úklid“.

Problém 1

Chceme udělat první skript, který vypíše text Zkouška.

print line Zkouška

V čem je problém?

  1. Text není v uvozovkách.
  2. Text není v kulatých závorkách.
  3. printline se píše bez mezery.

Zkuste skript do Praatu zkopírovat a všechny tři možnosti vyzkoušet. Nebojte se, takovýmto experimentováním počítač neshoří, ani nesmažete všechna data z disku :-)

Typická sémantická chyba: skript proběhl, chybová hláška nevyskočila, přesto program nedělá to, co my chceme.

Problém 2

Mezeru jsme tedy smazali, výstup ale stále není v pořádku.

printline Zkouška

V čem je problém?

  1. Toto také není správný zápis.
  2. V Praat Info zůstal výpis z minulého skriptu.
  3. Praat na nás zanevřel.

Napsalo se Zkouška? Co je v textu navíc oproti předchozímu problému?

Příkaz print z předchozího problému vypsal text a neudělal nový řádek (k tomu totiž slouží právě příkaz printline). Nový skript připojil svůj (správný) výstup k předchozímu, proto vypadá výsledný text zmateně. Bude dobré na začátku skriptu vložit příkaz pro smazání okna.

Problém 3

Přidali jsme příkaz na mazání okna Praat Info.

clear info
printline Zkouška

Na kterém řádku je dle chybové hlášky problém?

  1. Na prvním.
  2. Na druhém.
  3. Nelze určit, mluví to anglicky.

Chybová hláška při syntaktické chybě udává číslo řádku.

Script line 1, tedy řádek č. 1. Dokonce nám to i ukazuje text problematického řádku: clear info. Naučte se chybové hlášky číst, nezavírejte je bez přečtení.

U krátkého skriptu řádek najdeme snadno. U dlouhých programů můžeme použít menu Search, Go to line... Ctrl-L k přechodu na konkrétní řádek.

Ne vždy se bohužel Praat s číslem řádku trefí. Může se stát, že chybný zápis na jednom řádku způsobí špatné pochopení např. následujícího. Proto hledejme chybu raději v okolí uvedeného čísla.

Problém 4

clear info bylo špatně, zkusíme smazat mezeru a začít velkým písmenem (protože např. příkaz Remove se určitě s velkým písmenem píše.)

Clearinfo
printline Zkouška

Co s tím?

  1. Zkusíme malé písmeno.
  2. Vzdáme to.
  3. Problém je na 2. řádku.

Chybová hláška se změnila, ale pořád ukazuje na řádek č. 1.

Pokud si některé příkazy nepamatujeme, vyplatí si je na jedno místo přehledně vypsat. A nebo zkusit hledat na internetu "how to clear praat info".

Praat nemá zrovna systematicky vytvořený jazyk a to, že se příkaz Remove píše s velkým bohužel neznamená, že se bude s velkým psát i clearinfo.

Po úpravě již skript funguje správně.

Problém 5

Chceme vypsat labely 2 až 8 ve 2. vrstvě.

Proč to vypsalo jen poslední label?

  1. Chyba je v kódování znaků.
  2. Příkazy Get... přemazávají Praat Info.
  3. Je nutný for cyklus.

Toto jsme již řešili v první lekci Informace o zvukovém souboru, kde byla situace rozfázovaná po jednotlivých řádcích.

Příkazy Get... skutečně nejdříve vymažou okno Praat Info. Abychom si výsledek uchovali, je potřeba výstup příkazu uložit do proměnné (např. lab$).

Ten pak lze vypsat bez přemazávání předchozích výstupů (např. printline 'lab$').

Problém 6a

Chceme zjistit počet krátkých vokálů ve 2. vrstvě.

clearinfo
pocetInt = Get number of intervals: 2

pocet = 0
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if (lab$ <> "" and index("ieaou", lab$)
        pocet = pocet + 1
    endif
endfor

printline Počet krátkých vokálů: 'pocet'.

Na kterém řádku je podle hlášky chyba?

  1. Na pátém.
  2. Na šestém.
  3. Na sedmém.

Viz Script line ...

Kromě Script line 7 je uvedena i ukázka řádku

if (lab$ <> "" and index("ieaou", lab$)

Problém 6b

Chceme zjistit počet krátkých vokálů ve 2. vrstvě.

clearinfo
pocetInt = Get number of intervals: 2

pocet = 0
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if (lab$ <> "" and index("ieaou", lab$)
        pocet = pocet + 1
    endif
endfor

printline Počet krátkých vokálů: 'pocet'.

A co je podle hlášky špatně?

  1. Chybí pravá závorka.
  2. Chybný znak pro „není rovno“.
  3. Nezná funkci index().

Expected znamená očekávána.

Řádek tedy měl být asi takto:

if (lab$ <> "") and index("ieaou", lab$)

Fungovalo by to stejně ale i takto, protože priorita (tzv. precedence) operátoru <> je vyšší než and, takže není nutné jednotlivé části podmínky závorkovat:

if (lab$ <> "" and index("ieaou", lab$))

Šlo by tedy i toto:

if lab$ <> "" and index("ieaou", lab$)

Dobrý trik: vždy si u výrazu spočítejte počet levých a pravých závorek, obojí se musí rovnat. Pokud ne, máte ve výrazu chybu a je nutno hledat, kde závorka chybí, nebo naopak přebývá.

Problém 7

clearinfo
printline čísla 1 až 5
for i from 1 to 5
    printline i
endfor

V čem je problém?

  1. i je index pro for cyklus, uvnitř je nutno psát cislo = i a printline cislo.
  2. i je imaginární jednotka (komplexní číslo)
  3. i je nutno uzavřít do apostrofů.

Druhý řádek vypisuje přesně stanovený text, takže první dvě možnosti nedávají smysl.

Pro výpis obsahu proměnné v příkazech print a printline je nutné uzavřít ji do apostrofů.

Správný zápis je

printline 'i'

Problém 8a

clearinfo
printline čísla 5 až 1 s krokem -1
for i from 1 to 5
    cislo = (6 - i)
    printline 'i'.'tab$'cislo'
endfor

Na které řádce je problém?

  1. Na třetí.
  2. Na čtvrté.
  3. Na páté.

Problém je s výpisem, vše ostatní dává smysl.

For cyklus a přepočet je určitě v pořádku, to zjistíme hned. A pokud bychom si nebyli jisti, přidáme do skriptu dočasně pomocný výpis printline 'cislo' a vidíme, že to opravdu funguje správně.

Problém 8b

clearinfo
printline čísla 5 až 1 s krokem -1
for i from 1 to 5
    cislo = (6 - i)
    printline 'i'.'tab$'cislo'
endfor

A v čem je problém?

  1. Chybí apostrof za tab$.
  2. Přebývá apostrof za cislo.
  3. Přebývá apostrof před tab$.

Pro výpis obsahu je nutné v printline proměnnou uzavřít do apostrofů, tab$ je také proměnná.

Pokud si ještě nejste úplně jisti, jednotlivé varianty odpovědí vyzkoušejte a hned bude jasno.

Správný zápis je

printline 'i'.'tab$''cislo'

Problém 9

clearinfo

pocet = Get number of intervals... 2
for int from 1 to pocet
    label = Get label of interval... 2 int
    printline 'label'
endfor

V čem je problém?

  1. Na 5. řádku má být label$.
  2. Na 6. řádku má být 'label$'.
  3. Předchozí dvě možnosti dohromady.

Textová proměnná má za názvem $. Všude.

Je tedy nutné přidat dolar ke všem výskytům proměnné label.

label$ = Get label of interval... 2 int
printline 'label$'

Problém 10

clearinfo
textID = selected("TextGrid")
zvukID = selected("Sound")

select textID
pocet = Get number of intervals... 2
for i from 1 to pocet
    t1 = Get start point... 2 i
    t2 = Get end point... 2 i
    label$ = Get label of interval... 2 i
endfor

select zvukID
for i from 1 to pocet
    rms = Get root-mean-square... t1 t2
    printline 'label$''tab$''rms:5'
endfor

Pro každý segment podle 2. vrstvy se měla vypočítat RMS amplituda. V čem je problém?

  1. label$, t1 a t2 obsahují jen poslední hodnotu z 1. cyklu.
  2. Nelze vypisovat současně proměnné z různých objektů.

Zkuste jako „robot“ procházet skriptem řádek po řádku a přijdete na to.

V prvním cyklu se projdou všechny intervaly a neustále se přemazávají proměnné t1, t2 a label novými hodnotami, o předchozí hodnoty tím přicházíme (proměnné nemají žádnou „historii“, pamatují si vždy jen aktuální hodnotu).

Ve druhém cyklu pořád dokola počítáme rms mezi těmi samými časy t1 a t2 a vypisujeme ten samý label$ (hodnoty uložené v posledním běhu prvního cyklu).

Problém 10

clearinfo
textID = selected("TextGrid")
zvukID = selected("Sound")

select textID
pocet = Get number of intervals... 2
for i from 1 to pocet
    t1 = Get start point... 2 i
    t2 = Get end point... 2 i
    label$ = Get label of interval... 2 i
endfor

select zvukID
for i from 1 to pocet
    rms = Get root-mean-square... t1 t2
    printline 'label$''tab$''rms:5'
endfor

Výzva: zkuste skript upravit tak, aby fungoval dle našeho zadání. Na následující straně je řešení, pokuste se to však vymyslet nejdříve sami.

Pro každý segment zvuku podle 2. vrstvy TextGridu vypočtěte RMS amplitudu.

Problém 10: řešení

clearinfo
textID = selected("TextGrid")
zvukID = selected("Sound")

select textID
pocet = Get number of intervals... 2
for i from 1 to pocet
    select textID
    t1 = Get start point... 2 i
    t2 = Get end point... 2 i
    label$ = Get label of interval... 2 i

    select zvukID
    rms = Get root-mean-square... t1 t2
    printline 'label$''tab$''rms:5'
endfor

RMS amplituda podle segmentů.

Odhalování chyb – vhodná taktika

  • Představit si, jak by se fragment kódu měl chovat.
  • Zjistit, jak se chová ve skutečnosti.
    • Porovnáním odhadnout pravděpodobné místo výskytu chyby.
    • Pokud program nic nevypisuje a není co porovnávat, přidat do něj dočasné pomocné výpisy zajímavých proměnných.
  • Představit si, jak by měl vypadat funkční kód.
    • Porovnat s realitou, nalezené rozdíly opravit.

Postupy typu „tak já to celé smažu a udělám znovu“ jsou škoda, protože se připravíme o radost z nalezení chyby, poučení pro příště, a tím zásobováním své „znalostní databáze“. Ta je nesmírně důležitá, chybami se člověk nejvíce naučí a čím více chyb jsme v životě opravili, tím většími odborníky jsme, protože pak s lehkostí dokážeme vyřešit leccos, máloco nás ještě překvapí a zároveň díky těmto zkušenostem také mnoha chybám předejdeme.

Podobně, jako se látku nejlépe naučíme tak, že ji někomu jinému vysvětlujeme, opravováním vlastních chyb učíme sami sebe, takové zkušenosti se nám lépe zaryjí do hlavy a na dlouho v ní zůstanou.

Problém 11

clearinfo
pocetInt = Get number of intervals: 2

pocitadlo = 0
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if lab$ = "a" or lab$ = "a:"
        pocitado = pocitadlo + 1
    endif
endfor
printline Počet a-ových vokálů: 'pocitadlo'.

Proč to vypsalo 0?

  1. Nikdy není lab$ zároveň [a] i [a:].
  2. Výsledky vnořených výpočtů nejsou mimo cyklus přístupné.
  3. Proměnná pocetInt = 2, takže jsou kontrolovány jen první 2 intervaly.
  4. Překlep v názvu proměnné při zvyšování počítadla.

Přidejte si do programu pomocný výpis pocitadlo při jeho zvýšení.

Pomocí pomocného výpisu pocitadlo zjistíme, že podmínka je skutečně 1x splněna (jinak by se nevypsal pomocný výpočet), nicméně vypisovaná hodnota je 0. Pak už je jasné, že problém musí být na jediném řádku, a sice při zvyšování pocitadlo.

Ano, je tam překlep. Ošklivá sémantická chyba! Žádná chybová hláška o špatném názvu proměnné. Do chybné proměnné pocitado přiřadíme číslo, což opravdu syntaktický chyba není. A při závěrečném výpisu pocitadlo za cyklem také chyba nenastane, protože počítadlo bylo inicializováno na 0, takže existuje.

pocitadlo = pocitadlo + 1

Problém 12

clearinfo
delka = 3
sirka = 4
obsah = delka.sirka
printline Obdélník má obsah 'obsah'.

V čem je problém?

  1. Takto se násobení nezapisuje.
  2. Proměnné nemohou mít fixní hodnotu.
  3. Praat slouží k akustické analýze, nikoliv k matematickým úlohám.

Hláška odkazuje na řádek s problémem.

Násobení se v Praatu zapisuje hvězdičkou *.

obsah = delka*sirka

Problém 13

clearinfo
for I from 1 to 5
    j = 2 * i
    printline 'j'
endfor

V čem je problém?

  1. Takto se násobení nezapisuje.
  2. Proměnná i neexistuje.
  3. Došlo k chybě přetečení rozsahu.

Tady je to jednoduché, hláška je jasná.

Proměnná i neexistuje, pravděpodobně se jedná o překlep, kdy autor ve for cyklu chtěl napsat také malé i.

Problém 14

clearinfo
for j from 1 to 5
    printline 'i'
endfor

V čem je problém?

  1. i nemá být v apostrofech.
  2. Praat je unaven.
  3. Proměnná i neexistuje.

Tady je to horší, hláška se neobjevuje.

Přestože proměnná i neexistuje, Praat nehlásí chybu. Tak se chová při výpisu. Pokud bychom s i prováděli výpočet ve vzorci (viz předchozí problém), chybová hláška by vyskočila.

Toto je poněkud nebezpečně chování Praatu a mnoho jiných jazyků by chybu v obdobné situaci nahlásilo.

Problém 15

# Výpis čísel 1 až 5
clearinfo
for i from 1 to 5  # cyklus
    printline 'i'
endfor

V čem je problém?

  1. Komentář # musí být na samostatném řádku.
  2. Komentáře mohou být jen na začátku skriptu.
  3. Všechny komentáře mohou být jen na samostatném řádku.

Už jsme se o tom kdysi bavili :-)

Komentář # musí být skutečně na samostatném řádku.

Můžeme však využít komentář za středníkem ;

Ten může být umístěn i na stejném řádku s příkazem.

# Výpis čísel 1 až 5
clearinfo
for i from 1 to 5  ; cyklus
    printline 'i'
endfor

Problém 16

clearinfo
zvukID = Read from file: "D:\AKU\milanek.wav"
cas = 0.7
formantID = To Formant (burg): 0, 5, 5500, 0.025, 50
F2 = Get value at time: 2, cas, "Hertz", "Linear"
printline Okamžité F2: 'F2'
Remove
select zvukID
Remove

V čem je problém?

  1. Zvuk se nenačetl.
  2. Objekt Formant se nevytvořil.
  3. Proměnné musejí začínat malým písmenem.

Chybová hláška ukazuje na daný řádek a přesně popisuje problém.

Proměnné musejí začínat malým písmenem.

f2 = Get value at time: 2, cas, "Hertz", "Linear"
printline Okamžité F2: 'f2'

Problém 17

clearinfo
zvukID = Read from file: "D:\AKU\milanek.wav"
cas = 0.7
formantID = To Formant (burg): 0, 5, 5500, 0.025, 50
form = Get value at time: 2, cas, "Hertz", "Linear"
printline Okamžité F2: 'form'
Remove
select zvukID
Remove

V čem je problém?

  1. Zvuk se nenačetl.
  2. Objekt Formant se nevytvořil.
  3. form je interní příkaz, nelze použít pro název proměnné.

Chybová hláška je matoucí, ale problém je s form.

Hláška ukazuje na řádek s printline, což není zcela šťastné, protože ve skutečnosti nastává problém již o řádek dříve, přiřazení do form.

form je název interního příkazu, tudíž nelze použít jako název proměnné.

Problém 18

clearinfo

pocet = Get number of intervals: 2
for int from 1 to pocet
    label$ = Get label of interval: 2, 1
    printline 'label$'
endfor

V čem je problém?

  1. Ve výpisu 'label$'.
  2. V zjištění velikosti pocet.
  3. V zjišťování hodnoty label$.

Zkuste pomocný výpis proměnné int. Mimochodem, používá se vůbec její hodnota někde uvnitř cyklu?

Zapomněli jsme v příkazu pro zjištění labelu fixní index (1), takže se pořád zjišťoval label 1. intervalu. Je potřeba ho nahradit naší proměnnou, která se v cyklu zvyšuje.

label$ = Get label of interval: 2, int

Problém 19

clearinfo
pocetInt = Get number of intervals: 2
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if lab$ = "a" or lab$ = "a:"
        pocitadlo = pocitadlo + 1
    endif
endfor
printline Počet a-ových vokálů: 'pocitadlo'.

V čem je problém?

  1. pocitadlo není před zvyšováním definováno.
  2. Hodnoty labelů mají být v apostrofech.
  3. V Get label nemá být i, ale pocitadlo.

Hláška napovídá, v čem je problém. Co znamená Unknown variable?

Je hezké, že uvnitř cyklu při splnění podmínky chceme zvýšit hodnotu počítadla.

Ale jaká je jeho hodnota před prvním provedením takového příkazu?

Ta proměnná neexistuje. Je tedy nutné ještě před začátkem for cyklu nastavit počítadlo na nulu, tím se proměnná vytvoří a definuje se její hodnota.

pocitadlo = 0

Při splnění podmínky pak není problém k této hodnotě přičíst jedničku a uložit ji opět do proměnné pocitadlo, čímž se starý obsah přepíše.

Problém 20

clearinfo
pocetInt = Get number of intervals: 2
for i from 1 to pocetInt
    pocitadlo = 0
    lab$ = Get label of interval: 2, i
    if lab$ = "a" or lab$ = "a:"
        pocitadlo = pocitadlo + 1
    endif
endfor
printline Počet a-ových vokálů: 'pocitadlo'.

V čem je problém?

  1. A-ové vokály zde nejsou.
  2. pocitadlo = 0 musí být před cyklem.
  3. V Get label nemá být i.

Pokud to v tom nevidíte, zahrajte si zase „na roboty“, jeďte skript řádek po řádku a pište si na papír hypotetické hodnoty proměnných.

Je skvělé, že před zvýšením počítadla ho nastavíme na nulu, takže žádnou chybovou hlášku nedostáváme.

Jediný problém je v tom, že nastavení počítadla na nulu provádíme při každém dílčím průchodu vnitřní části cyklu (tedy pro každý nový interval TextGridu).

Příkaz na nastavení počítadla na nulu musí být před for cyklem.

pocitadlo = 0
for i from 1 to pocetInt
    ...

Pak bude vše fungovat.

Problém 21

clearinfo
pocetInt = Get number of intervals: 2
pocitadlo = 0
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if lab$ = "a" or lab$ = "a:"
        pocitadlo = pocitadlo + 1
    endif
printline Počet a-ových vokálů: 'pocitadlo'.
endfor

Proč se to vypisuje tolikrát?

  1. printline je uvnitř cyklu.
  2. Zvyšování počítadla je uvnitř podmínky.
  3. Zvyšování počítadla je uvnitř cyklu.

Hra „na robota“ opět neuškodí.

Přestože není příkaz printline odsazen mezerami (což by bylo výrazně přehlednější), stále je uvnitř cyklu mezi řádkami for a endfor, takže se provádí při každém průchodu vnitřní části skriptu.

Pokud chceme vypsat jen jednu souhrnnou hodnotu na konci, přesuňme příkaz printline až za endfor.

    ...
endfor
printline Počet a-ových vokálů: 'pocitadlo'.

Problém 22

clearinfo
pocetInt = Get number of intervals: 2
pocitadlo = 0
for i from 1 to pocetInt
    lab$ = Get Label of Interval: 2, i
    if lab$ = "a" or lab$ = "a:"
        pocitadlo = pocitadlo + 1
    endif
endfor
printline Počet a-ových vokálů: 'pocitadlo'.

V čem je problém?

  1. V příkazu Get Label... jsou špatné velikosti písmen.
  2. TextGrid neobsahuje labely.
  3. Chyba je v počítadle.

Command not available může značit buď špatný příkaz nebo vybraný nesprávný objekt, pro který tento příkaz neexistuje.

Tato chyba nastává, pokud píšeme příkaz z paměti, nepoužijeme vložení z historie a něco v zápise spleteme.

Občas se ale také stane, že Praat změní název příkazu, a přestože se jedná o starý dlouhodobě fungující skript, najednou nová verze Praatu některým příkazům nerozumí.

V takovém případě zkuste příkaz najít a vložit do skriptu přes historii. Uvidíte, zda se zápis příkazu náhodou nezměnil.

V tomto případě se skutečně jedná o chybné velikosti písmen, správně to má být takto:

lab$ = Get label of interval: 2, i

Kdežto my jsme tam měli:

lab$ = Get Label of Interval: 2, i

Problém 23

clearinfo
cas = 0
while cas <= 1.5
    printline 'cas'
    cas = cas + 0.1
endwhile

Proč není poslední číslo 1.5?

  1. Protože hodnota nalevo v podmínce <= nesmí být rovná té napravo.
  2. Kvůli nepřesné numerice počítače.
  3. Pokud je podmínka splněna, cyklus končí.

Už jsme o tom také kdysi mluvili. A ono je to i vidět.

Uvnitř while cyklu se přičítají desetinná čísla, ale skončí evidentně o položku dříve, než jsme zamýšleli.

Pokud bychom printline nepoužili, ani bychom si ničeho nevšimli!

Pozor na porovnávání desetinných čísel, zaokrouhlování nás může nepříjemně překvapit – jako index v cyklech používejme celá čísla a desetinná z nich teprve odvozujme.

Interní binární reprezentace numerických čísel v počítači se může chovat zrádně. Co nám připadá jako nevinné desetinné číslo, může být v binární soustavě periodické číslo nekonečné délky, což počítač pochopitelně zaokrouhlí na nepřesnou hodnotu.

Výrazně lepší řešení

clearinfo
casMS = 0
while casMS <= 1500
    cas = casMS / 1000
    printline 'cas'
    casMS = casMS + 100
endwhile

Problém 24

clearinfo

fBark = 7.38

fHz = barkToHertz(fBark)

zhruba = fHz:2
printline 'zhruba'

Chceme výsledek převodu vypsat na dvě desetinná místa. Proč to nejde?

  1. Problém je v převodu.
  2. Zápis dělení má být /2 místo :2.
  3. Zápis :2 je nutné dát až do výpisu.

Jestli nevíte, zkuste to.

Zápis :2 pro omezení počtu desetinných míst skutečně patří jen do printline.

Mimochodem, Praat umí provádět spoustu podobných šikovných konverzí, hledejte 'Praat Formulas Mathematical functions' a naleznete převody mezi hertzy, barky, erby, mely, půltóny atd.

clearinfo

fBark = 7.38

fHz = barkToHertz(fBark)

printline 'fHz:2'

Problém 25

clearinfo
textID = selected("TextGrid")
zvukID = selected("Sound")

select textID
t1 = Get start point: 2, 4
t2 = Get start point: 2, 4

select zvukID
rms = Get root-mean-square: t1, t2
printline 'rms'

A v čem je problém nyní?

  1. Nebyly vybrány oba objekty.
  2. Chybné volání výpočtu rms.
  3. Uživatel nenačetl TextGrid.

To se stane snad každému.

Uživatel zapomněl označit oba objekty před spuštěním skriptu.

Oba objekty musejí být označeny.

Problém 26

# Výpis všech hlásek před pauzou.
pocetInt = Get number of intervals: 2
for i from 1 to pocetInt
    lab2$ = Get label of interval: 2, i+1
    # je následující label prázdný?
    if lab2$ = ""
        lab$ = Get label of interval: 2, i
        printline 'lab$'
    endif
endfor

V čem spočívá chyba?

  1. Výpočet i+1 je nutno provést samostatně.
  2. U posledního intervalu se ptáme „za roh.“
  3. Příkaz Get label... se zapisuje jinak.

Nemůžeme se ptát na indexy mimo rozsah.

Ptát se na interval i+1, když i je poslední dostupný interval, vede na chybu. Je proto potřeba v cyklu skončit o interval dříve.

# Výpis všech hlásek před pauzou.
pocetInt = Get number of intervals: 2
for i from 1 to pocetInt-1
    lab2$ = Get label of interval: 2, i+1
    # je následující label prázdný?
    if lab2$ = ""
        lab$ = Get label of interval: 2, i
        printline 'lab$'
    endif
endfor

Sofistikovanější modifikací by bylo v cyklu projet všechny intervaly, ale před zjišťováním lab2$ přidat podmínku, zda i není poslední, a v takovém případě mu nastavit napevno hodnotu "" místo skutečného zjišťování. Výhodou takového přístupu je, že by nám vypsal i hlásku, kdyby byla posledním intervalem TextGridu.

Problém 27

zvukID = Read from file: "D:\AKU\milanek.wav"
textID = Read from file: "D:\AKU\milanek.TextGrid"
pocet = Get number of intervals: 2
for int from 1 to pocet
    t1 = Get start point: 2, int
    t2 = Get end point: 2, int
    select zvukID
    rms = Get root-mean-square: t1, t2
    printline 'rms:5'
endfor

Vypsala se jen 1. hodnota a nastala chyba. V čem je problém?

  1. Ptáme se ve špatné Tier.
  2. Uvnitř for je nutné přepnout na textID.
  3. Get start point se ptá mimo rozsah.

Zahrajte si „na robota“ u bude jasno, je chyba v přepínání objektů.

Je nutné promyslet přepínání objektů.

Zatímco při prvním průchodu vnitřní části cyklu byl ještě označen TextGrid, uvnitř cyklu došlo k přepnutí na Sound (select zvukID). Proto při dalším průchodu cyklem příkaz Get start point selže, jelikož je dotaz zaměřen na TextGrid, který však není vybrán.

zvukID = Read from file: "D:\AKU\milanek.wav"
textID = Read from file: "D:\AKU\milanek.TextGrid"

pocet = Get number of intervals: 2
for int from 1 to pocet
    select textID
    t1 = Get start point: 2, int
    t2 = Get end point: 2, int

    select zvukID
    rms = Get root-mean-square: t1, t2
    printline 'rms:5'
endfor

Problém 28

clearinfo
textID = Read from file: "D:\AKU\milánek.TextGrid"
pocet = Get number of intervals: 2

for int from 1 to pocet
    label$ = Get label of interval: 2, int
    printline 'label$'
endfor

V čem spočívá chyba?

  1. Uživatel neoznačil objekt TextGrid.
  2. Soubor neexistuje.
  3. Zapomněli jsme na příkaz select.

Chybová hláška přesně popisuje problém.

Když chybová hláška říká, že nelze otevřít konkrétní soubor a udává přímo jeho název, podívejte se, zda opravdu existuje. V tomto případě je překlep v pojmenování, skutečný soubor má jméno bez diakritiky (milanek.TextGrid).

Problém 29

# Vypsání dlouhých vokálů ve 2. vrstvě
clearinfo
pocetInt = Get number of intervals: 2
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if (lab$ <> "") and (index("i:e:a:o:u:", lab$))
        printline 'lab$'
    endif
endfor

Proč se vypisují i krátké vokály?

  1. Protože jsou součástí výčtu.
  2. Protože při nesplnění podmínky je v lab$ předchozí hodnota.
  3. V podmínce nemá být (lab$ <> "").

Funkce index vrací, zda našla lab$ v daném řetězci.

Vyjmenovat v řetězci všechny hlásky, o které máme zájem, a použít funkci index() pro dotaz, zda je konkrétní label v řetězci nalezen či ne, je sice příjemné, ale nebezpečné, pokud to nedomyslíme do všech detailů.

Například krátký vokál "e" je skutečně v řetězci "i:e:a:o:u:" nalezen.

Obdobně, pokud chceme najít jen krátké vokály a učiníme výčet "ieaou", musíme počítat s tím, že pokud bude nějaký label obsahovat "ou", tak bude také brán jako součást našeho výčtu.

Co tedy s tím? Nejbezpečnější variantou je rozepsat vše do dílčích podmínek. I když je to možná pracnější a třeba se nám to ani pořádně nevejde na jednu obrazovku. Nemusíme se ale tolik bát, že bychom udělali logickou chybu.

# Vypsání dlouhých vokálů ve 2. vrstvě
clearinfo
pocetInt = Get number of intervals: 2
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if lab$ == "i:" or lab$ == "e:" or lab$ == "a:" or lab$ == "o:" or lab$ == "u:"
        printline 'lab$'
    endif
endfor

Problém 30

# Průměrné trvání hlásek.
clearinfo
pocetInt = Get number of intervals: 2
soucet = 0
pocet = 0
for i from 1 to pocetInt
    lab$ = Get label of interval: 2, i
    if lab$ <> ""
        t1 = Get start point: 2, i
        t2 = Get end point: 2, i
        soucet = soucet + (t2-t1)
        pocet = pocet + 1
    endif
endfor
prumer = soucet/pocet
printline Průměr: 'prumer:3' sec.

Co není ve skriptu zcela v pořádku?

  1. Dělení nulou, pokud se nenalezne žádný správný label.
  2. Mělo by se sčítat v milisekundách.
  3. Zapomněli jsme načíst zvuk.

Co se stane, pokud skutečně ani jeden label nesplní podmínku?

Pokud se nenalezne ani jeden neprázdný label, v proměnné pocet bude 0 a Praat při dělení uloží do proměnné prumer hodnotu --undefined--.

Neznalého uživatele může takový výsledek vyděsit a nemusí mu tím pádem hned dojít, v čem je potíž (že má špatný TextGrid). Navíc, pokud by skript obsahoval ještě nějaké další příkazy, bude v nich vesele pokračovat dále.

Vhodnější by bylo vytvořit vlastní chybobou hlášku, ve které „lidsky“ popíšeme skutečnou podstatu problému: „Máte prázdný TextGrid“ a skript předčasně zastavíme. To uživatel jistě pochopí mnohem lépe než hodnotu --undefined--.

Na konci kapitoly Podmínky jsme probírali příkaz exitScript pro předčasné ukončení skriptu. Jedním řešením je tedy provést před výpočtem průměru kontrolu podmínkou.

if pocet < 1
    exitScript: "Nenalezen žádný label s textem."
endif
prumer = soucet/pocet
printline Průměr: 'prumer:3' sec.

Jinou (úspornější) metodou je použití příkazu assert, u kterého naopak specifikujeme, jak má podmínka vypadat, když je vše v pořádku. Na stejném řádku za středníkem pak můžeme napsat zprávu pro uživatele a nechat vypsat kontrolní hodnoty nějakých proměnných (formát je stejný, jako u printline).

Výhodou je stručnost tohoto zápisu (žádné if, endif, exitscript) – vše je na jednom řádku. Z důvodu tohoto pohodlí je tedy možné předpokládat, že bude pro programátora příjemnější takovéto kontroly do skriptu přidat a bude tak více motivován ošetřovat rizikové možnosti.

Součástí této hlášky je automaticky i číslo řádku ve skriptu, kde ověření nastalo, takže uživatel může v případě potřeby autorovi skriptu dobře popsat, kde přesně dochází k problému.

Navíc assert zvládne správně zareagovat i v případě, že proměnná, která je součástí podmínky, nebyla třeba vůbec ani definována. V takovém případě také zastaví skript a informuje o problému, k tomu opět připojí nastavenou zprávu pro uživatele.

assert pocet > 0; Nenalezen žádný label s textem, pocet = 'pocet'.
prumer = soucet/pocet
printline Průměr: 'prumer:3' sec.

Konec