Cvičení č. 12: Koleda, koleda, Štěpáne

Připravíme a zahrajeme tón, stupnici, vánoční koledu a dál už to bude na vašich Múzách.

Počítačový tón

V teorii Fourierových řad se ukazuje, že reálný periodický signál \(s(t)\) s periodou \(T\) lze na intervalu \(\left<-T/2,T/2\right>\) vyjádřit nekonečnou řadou

\[s(t)=a_0+\sum_{n=1}^\infty a_n \cos(2\pi nft+\phi_n),\]

kde \(t\) značí čas, \(n\) index členu řady, \(f=2\pi/T\) základní frekvenci a \(a_n\) jsou amplitudy jednotlivých členů řady, \(nf\) jejich frekvence a \(\phi_n\) fáze. Člen se základní frekvencí (\(n=1\)) nazveme prvním harmonickým tónem, další členy řady vyššími harmonickými (alikvotními) tóny.

Náš tón tak můžeme zapsat ve tvaru \(s(t)=a\cos(2\pi ft)\); na fázi nezáleží, slyšíme frekvence. Za \(f\) dosadíme např. frekvenci komorního A, \(f=440\) Hz. Pro počítač potřebujeme signál diskretizovat – vzorkovací frekvenci volíme podle zvyklostí audio kompaktních disků, tedy \(f_s=44100\) Hz. Ze světa CD vezmeme i další podněty: hodnotu každého vzorku bude vyjadřovat 16bitový znaménkový integer o rozsahu \(-2^{15}\)\(2^{15}-1\), to celé v jednom mono nebo dvou stereo kanálech, takže při 74minutovém záznamu bychom dostali 2 B × 44100 Hz × 74 × 60 s × 2 / (1024 × 1024) = 700+ MB dat, jako na CD. Máme tedy pro časovou síť: \(t_n=t_{min}+(t_{max}-t_{min}) n/N\), kde \(n=0,\dots,N-1\) a \(N=\lfloor f_s(t_{max}-t_{min})\rfloor\), a pro signál: \(y_n=\lfloor(2^{15}-1)\cos(2\pi ft_n)\rfloor\).

Takovéto \(y_n\) uložené v Pythonu jako NumPy pole umějí (samozřejmě i pro více tónů) přehrát moduly simpleaudio a sounddevice, první sice místy poněkud arytmicky, ale s menšími pomlkami mezi tóny, druhý v rytmu, ale s výrazněji oddělovanými tóny. Tyto nectnosti lze obejít, pokud se jednotlivé tóny v poli sekvenčně kumulují a na závěr jednorázově uloží do WAV souboru, což nabízí scipy.io.wavfile.write. WAV soubor lze přehrát skoro čímkoliv (např. i Google Chromem) a zkonvertovat do MP3 také.

Pythonský skript, který pípne tón o frekvenci freq po dobu tmax a uloží záznam do souboru fileWav:

# Python: testTone.py
# přehraje a uloží do wav-souboru tón předepsané frekvence

import numpy as np   # pro pole
import scipy         # pro uložení wav-souboru
import simpleaudio   # pro přehrávání zvuku z numpy-pole (při playSA=True, viz funkce playArray)
import sounddevice   # pro přehrávání zvuku z numpy-pole (při playSA=False,       viz playArray)

playNow=True         # hrát hned?
saveWav=True         # uložit wav?
dispFreq=True        # vypsat frekvenci?
freq=440             # frekvence tónu [a1: 440 Hz]
fs=44100             # vzorkovací frekvence/sampling frequency [44100 Hz]
dt=3.0               # délka tónu/tone length [sec]
if playNow:
  playSA=True                       # True pro simpleaudio, False pro sounddevice
if saveWav:
  fileWav='tone.wav'                # jméno wav-souboru
  wav=np.empty((0),dtype=np.int16)  # numpy-pole pro uložení do wav-souboru

# Přehraje zvukové pole a/nebo uloží zvukové pole do wav-souboru
def playArray(y,channels=1):
  global wav
  if playNow:
    if playSA:        # simpleaudio přehrává poněkud nerytmicky, s menšími pomlkami mezi tóny
      obj=simpleaudio.play_buffer(audio_data=y,num_channels=channels,bytes_per_sample=2,sample_rate=fs)
      obj.wait_done()
    else:             # sounddevice přehrává spíše rytmicky, s většími pomlkami mezi tóny
      sounddevice.play(data=y,samplerate=fs)
      sounddevice.wait()
  if saveWav:
    scipy.io.wavfile.write(filename=fileWav,rate=fs,data=y)

# Konvertuje frekvenci a délku tónu na zvukové pole
def evalArray(freq,dt):
  tarr=np.linspace(0,dt,round(dt*fs),endpoint=False)  # časové vzorkování
  y=np.cos(2*np.pi*freq*tarr)*32767
  # y=np.cos(2*np.pi*freq*tarr)+np.cos(2*np.pi*2*freq*tarr); y=y/max(abs(y))*32767  # varianta pro vícezvuk
  nfade=200             # počet vzorků pro lineární nástup a útlum tónu
  y[:nfade]*=np.linspace(0,1,nfade)
  y[-nfade:]*=np.linspace(1,0,nfade)
  return y.astype(np.int16)                         # 16bitové int hodnoty

# Zpracuje tón o dané frekvenci a dané délce
def playFreq(freq,dt):
  if dispFreq: print(f'{freq:.1f}')
  playArray(evalArray(freq,dt))
  # playArray(np.column_stack((evalArray(freq,dt),evalArray(2*freq,dt))),channels=2)  # varianta pro dvojzvuk

playFreq(freq,dt)

Přehrávací moduly i WAV soubor snesou dva (stereo) kanály. Dvojzvuky lze vytvořit a poslat do funkce playArray jako dva vektory seřazené do matice, např.: playArray(np.column_stack((evalArray(freq,dt), evalArray(2*freq,dt))), channels=2). Stejné dva tóny můžeme sloučit už ve funkci evalArray sekvencí y=np.cos(2*np.pi*freq*tarr) + np.cos(2*np.pi*2*freq*tarr); y=y/max(abs(y))*32767, a takto bychom dokázali vytvářet i vícezvuky. V dalším zůstaneme u dvoukanálového dvojhlasu. Ukázky: tón 440 Hz, dvoukanálový dvojzvuk (oktáva) 440/880 Hz, jednokanálový dvojzvuk 440/880 Hz vytvořený sčítáním v evalArray.

Sekvenční navazování tónů bývá někdy provázeno nežádoucím lupáním. Tomu lze čelit tlumením hlasitosti nástupu a závěru každého tónu. Dosahujeme toho přenásobením prvních, resp. posledních nfade prvků zvukového pole funkcí np.linspace(0,1,nfade), resp. np.linspace(1,0,nfade). Počet tlumených prvků nfade stačí někdy 200, jindy 500 nebo 1000. (Při vzorkovací frekvenci 44.1 kHz ovlivňujeme jen zlomek sekundy.)

Stupnice

Téma, jak ladit, tedy jak nastavovat frekvence tónů 7tónové stupnice C dur (C-D-E-F-G-A-H), resp. 12tónové chromatické stupnice (včetně černých kláves klavíru), jsme zpracovali na odkazu Hudební ladění.

_images/piano-550.png

12tónová chromatická řada na klaviatuře. Kresleno v gnuplotu (skript a data).

Shrneme: ideálem by bylo ladit tak, aby poměry frekvencí blízkých tónů byly rovny jednoduchým zlomkům, jako jsou 2:1, 3:2, 4:3, 5:4 a 6:5. Toho lze dosáhnout, ovšem kromě těchto poměrů se (doslova) dostanou do hry i poměry méně libozvučné, jejichž součástí v přirozeném ladění (just intonation), resp. pythagorejském ladění (Pythagorean tuning) jsou tzv. syntonické koma 81:80, resp. pythagorejské koma \(3^{12}:2^{19}\). Tito dva provinilci tak diskvalifikují některé intervaly (dvojice tónů). Co hůře, ztěžují až znemožňují transpozici (modulaci) hudby z C dur do sousedních tónin, kde se tyto rozladěné intervaly mohou ocitnout na častěji užívaných pozicích. V přirozeném ladění C dur to je např. vlčí kvinta D-A a vlčí tercie D-F. Zatímco většina kvint v přirozeném ladění má poměr frekvencí čistých 3:2, na vlčí kvintu zbylo 40:27=(3:2)/(81:80). Ukázky: čistá kvinta C-G, vlčí kvinta D-A. Necvičené ucho možná problém s osamocenou vlčí kvintou nepostřehne, ostatně záleží také na reproduktoru, ale samotné syntonické koma uslyšíme všichni. Skript, ke kterému se níže dopracujeme, umí zahrát současně dva tóny o různých frekvencích. Zkusíme-li si např. frekvence 440 Hz a 440×81/80 Hz, projeví se výrazné interference – zázněje. Jejich obálka má frekvenci rozdílu zdrojových frekvencí, \(440(81/80-1)=5.5\) Hz, tedy při 4sekundovém poslechu jich uslyšíme 22 (ukázka: syntonické koma na 440 Hz). Na frekvenci 220 Hz má obálka záznějů frekvenci poloviční a za 4 sekundy jich uslyšíme 11 (ukázka: syntonické koma na 220 Hz). V běžně užívané logaritmické míře pro vzdálenost dvou tónů vyjadřované v centech – pro oktávu s podílem frekvencí 2:1 je logaritmický rozdíl frekvencí roven 1200 centů, obecně \(I_c=1200\log_2(I_f)\) – je syntonické koma \(1200\log_2(81/80)\) = 21,5 centu a pythagorejské koma 23,5 centu. Přítomnost takovýchto disonancí je v hudebním ladění na velkou obtíž.

Všeobecně přijímaným řešením, byť příležitostně kritizovaným (např. ve filmu Werckmeister Harmonies, 32.-36. minuta), jsou temperovaná ladění, jmenovitě rovnoměrně temperované ladění (equal temperament, lat. temperare = mírnit). Oktáva se rozdělí na 12 stejných temperovaných půltónů o podílu frekvencí \(\sqrt[12]2\) neboli 100 centů. S výjimkou oktávy se takto všechny intervaly mírně rozladí. V následujících tabulkách shrnujeme frekvenční poměry přirozeného, pythagorejského a rovnoměrně temperovaného ladění:

Frekvenční poměry různých ladění sedmitónové stupnice

prima

sekunda

tercie

kvarta

kvinta

sexta

septima

oktáva

Přirozené ladění

C-C 1:1

C-D 9:8

C-E 5:4

C-F 4:3

C-G 3:2

C-A 5:3

C-H 15:8

C-C 2:1

C-D 9:8

D-E 10:9

E-F 16:15

F-G 9:8

G-A 10:9

A-H 9:8

H-C 16:15

Pythagorejské ladění

C-C 1:1

C-D 9:8

C-E 81:64

C-F 4:3

C-G 3:2

C-A 27:16

C-H 243:128

C-C 2:1

C-D 9:8

D-E 9:8

E-F 256:243

F-G 9:8

G-A 9:8

A-H 9:8

H-C 256:243

Rovnoměrně temperované ladění

C-C 1:1

C-D 1,122

C-E 1,260

C-F 1,335

C-G 1,498

C-A 1,682

C-H 1,888

C-C 2,000

C-D 2P

D-E 2P

E-F 1P

F-G 2P

G-A 2P

A-H 2P

H-C 1P

Podstatným přínosem je, že rovnoměrně temperované ladění dává všem intervalům téhož typu (tj. všem sekundám, všem terciím ad.) stejné poměry frekvencí ve všech tóninách, a transpozice skladby do jiné tóniny už není pro pevně laděné nástroje problém. Také se klesne z hodnot komat 21 až 23 centů na odchylky od přírodních intervalů o hodnotě 16 centů u temperovaných sext a 14 centů u tercií. Kvinty a kvarty (označované za čisté intervaly, v přirozeném ladění s ideálními poměry 3:2 a 4:3) jsou rozladěny jen o 2 centy.

Propracovali jsme se ke všemu, co potřebujeme pro přehrávání 7tónových durových stupnic v přirozeném, pythagorejském a rovnoměrně temperovaném ladění (s fixní frekvencí 440 Hz komorního A):

# Python: testScale.py
# přehraje a uloží do wav-souboru stupnici C dur v různých laděních
# ladění: temperované (ftemp), přirozené (fjust), pythagorejské (fpyth)

import numpy as np   # pro pole
import scipy         # pro uložení wav-souboru
import simpleaudio   # pro přehrávání zvuku z numpy-pole (při playSA=True, viz funkce playArray)
import sounddevice   # pro přehrávání zvuku z numpy-pole (při playSA=False,       viz playArray)

playNow=True         # hrát hned?
saveWav=True         # uložit wav?
dispFreq=True        # vypisovat frekvence?
fa1=440              # frekvence komorního a (a1)/concert pitch [440 Hz]
semitone=2**(1/12)   # temperovaný půltón/equal-tempered semitone
fs=44100             # vzorkovací frekvence/sampling frequency [44100 Hz]
tbeat=0.50           # délka doby/beat length (základní časová jednotka) [sec]
if playNow:
  playSA=True                                         # True pro simpleaudio, False pro sounddevice
if saveWav:
  fileWav='scale.wav'                                 # jméno wav-souboru
  wav=np.empty((0),dtype=np.int16)                    # numpy-pole pro uložení do wav-souboru
# ladicí faktory sedmitónové stupnice c1-d1-e1-f1-g1-a1-h1(-c2)
fjust=np.array([1,9/8,5/4,4/3,3/2,5/3,15/8,2])        # přirozené ladění/just intonation
fpyth=np.array([1,9/8,81/64,4/3,3/2,27/16,243/128,2]) # pythagorejské ladění/pythagorean tuning
ftemp=semitone**np.array([0,2,4,5,7,9,11,12])         # rovnoměrně temperované ladění/equal temperament

# Přehraje zvukové pole a/nebo přidá zvukové pole k wav-poli
def playArray(y,channels=1):
  global wav
  if playNow:
    if playSA:        # simpleaudio přehrává poněkud nerytmicky, s menšími pomlkami mezi tóny
      obj=simpleaudio.play_buffer(audio_data=y,num_channels=channels,bytes_per_sample=2,sample_rate=fs)
      obj.wait_done()
    else:             # sounddevice přehrává spíše rytmicky, s většími pomlkami mezi tóny
      sounddevice.play(data=y,samplerate=fs)
      sounddevice.wait()
  if saveWav:
    wav=np.append(wav,y)  # kumulace tónů pro jednorázové uložení

# Konvertuje frekvenci a délku tónu na zvukové pole
def evalArray(freq,dt):
  tarr=np.linspace(0,dt,round(dt*fs),endpoint=False)  # časové vzorkování
  y=np.cos(2*np.pi*freq*tarr)*32767
  nfade=200             # počet vzorků pro lineární nástup a útlum tónu
  y[:nfade]*=np.linspace(0,1,nfade)
  y[-nfade:]*=np.linspace(1,0,nfade)
  return y.astype(np.int16)                         # 16bitové int hodnoty

# Zpracuje tón o dané frekvenci a dané délce
def playFreq(freq,dt):
  if dispFreq: print(f'{freq:.1f}',end=' ')
  playArray(evalArray(freq,dt))

# délka 7+1 tónů stupnice
dt=tbeat*np.ones(8); dt[-1]=tbeat*2

# rovnoměrně temperované ladění
fc1=fa1/ftemp[5]          # frekvence c1
freq=fc1*ftemp            # frekvence celé stupnice
for n in range(8): playFreq(freq[n],dt[n])
if dispFreq: print()

# přirozené ladění
fc1=fa1/fjust[5]
freq=fc1*fjust
for n in range(8): playFreq(freq[n],dt[n])
if dispFreq: print()

# pythagorejské ladění
fc1=fa1/fpyth[5]
freq=fc1*fpyth
for n in range(8): playFreq(freq[n],dt[n])
if dispFreq: print()

# uloží wav-soubor
if saveWav:
  if wav.size: scipy.io.wavfile.write(filename=fileWav,rate=fs,data=wav)

Jak je patrné z tabulek uvedených výše a snad i poslechem, mezi 5 páry sousedících tónů 7tónové stupnice jsou v každém z uvedených ladění větší intervaly (tzv. celé tóny) než mezi 2 zbylými páry (půltóny). Vložením 5 dalších tónů dovnitř celých tónů vznikne 12tónová (chromatická) stupnice. Postupů, jak tyto vložené tóny naladit, je více (viz Hudební ladění). My se zde u všech tří ladění (přirozeného, pythagorejského a temperovaného) omezíme na řešení dodané rovnoměrně temperovaným laděním, tedy ke frekvencím zvýšených tónů (Cis, Dis, …), resp. snížených tónů (Ces, Des, …) se dostaneme násobením, resp. dělením temperovaným půltónem, tedy \(\sqrt[12]2\). Frekvence tónů shora, resp. zdola sousedících oktáv získáme násobením, resp. dělením frekvencí stejnojmenných tónů dvěma.

Ukázky: Stupnice C dur, D dur a E dur v přirozeném, pythagorejském a rovnoměrně temperovaném ladění. Stupnice C dur paralelně v přirozeném vs. rovnoměrně temperovaném ladění s fixní frekvencí A1 (frekvence tónů v Hz: C1 264.0/261.6, D1 297.0/293.7, E1 330.0/329.6, F1 352.0/349.2, G1 396.0/392.0, A1 440.0/440.0, H1 495.0/493.9, C2 528.0/523.3).

Notový zápis

Máme naladěno, můžeme postoupit od stupnic k jednohlasým melodiím, a s možností dvoukanálového přehrávání a ukládání i k dvojhlasu (k melodii s doprovodem). Abychom měli kde brát, potřebujeme ještě zavést symbolický (ASCII) notový zápis. Zde je návrh: [délka]tón[posuvka][oktáva], kde tón je c-d-e-f-g-a-h a také - pro pomlku, volitelná posuvka je +- pro zvýšení, resp. snížení o půltón, oktáva je 12345 pro jednočárkovanou (default) a další oktávy směrem vzhůru a zyx pro oktávy směrem dolů (zvané malá, velká a kontra). Absolutní délku doby v sekundách stanovíme nezávisle, v zápisu noty je relativní délka: 1 (default) až 9 pro lineární prodloužení délky noty v dobách (řekněme: čtvrťová, půlová, půlová s tečkou a celá nota pro 1234) a zyx pro zkracování délky noty půlením (osmina, šestnáctina a dvaatřicetina).

Stupnice C dur v symbolickém notovém zápisu: 'c d e f g a h 4c2', stupnice D dur: 'd e f+ g a h c+2 4d2', chromatická stupnice: 'c c+ d d+ e f f+ g g+ a a+ h 4c2'.

Ovčáci, čtveráci: '2c 2e  2g 2-  2c 2e  2g 2-  e e d e  2f 2d  e e d e  2f 2d  2e 2d  4c'.

Slíbená koleda:
'c2 g c2 a d2 h-  c2 g c2 a d2 h-  c2 g a c2 g a  6f  ' +
'c2 g c2 a d2 h-  c2 g c2 a d2 h-  c2 g a c2 g a  6f  ' +
'2f a f a c2      2f a f g c       2f a f a c2    2f a f g c  c2 g a c2 g a  6f'

DÚ: WAV soubory

Blížíme se ke skriptu, který umožňuje přehrát a uložit ve WAV formátu jedno- a dvojhlasé skladby v symbolickém notovém zápisu. Dvojhlas je řešen jako paralelní jednohlasy ve dvou nezávislých kanálech, nebo jako sekvence dvojzvuků. Default ladění je rovnoměrně temperované, lze přepínat na přirozené nebo pythagorejské. Připraveny k volání jsou funkce pro přehrání a uložení sekvencí tónů (playSeq1 pro jednohlas, playSeq11 pro dvojhlas nezávislých hlasů, playSeq2 pro dvojhlas po dvojzvucích), samostatných tónů nebo dvojzvuků (playTone a playTone2) a tónů nebo dvojzvuků daných frekvencí (playFreq a playFreq2). Skript také obsahuje několik jedno- a dvojhlasých skladeb pro vyzkoušení.

# Python: musicbox.py
# přehrává a ukládá do wav-souboru sekvence tónů a dvojzvuků v různých laděních
# funkce: playSeq1(seq[,tuning])                   - jednohlas
#         playSeq11(seq1,seq2[,tuning1][,tuning2]) - dvojhlas nezávislých hlasů
#         playSeq2(seq1,seq2[,tuning1][,tuning2])  - dvojhlas po dvojzvucích
#         playTone(s[,tuning])                     - jediný tón
#         playTone2(s1,s2[,tuning1][,tuning2])     - dvojzvuk
#         playFreq(freq,dt)                        - jediný tón daný frekvencí
#         playFreq2(freq1,freq2,dt)                - dvojzvuk daný frekvencemi
# s:      symbolický zápis tónu: [délka]tón[posuvka][oktáva]
#     kde délka je nejvýše jeden znak z xyz123456789 (default 1)
#           tón je   právě jeden znak z -cdefgah  (- pro pomlku)
#       posuvka je nejvýše jeden znak z +-         (default nic)
#        oktáva je nejvýše jeden znak z xyz12345     (default 1)
# seq: př. '2c2 zd2 zc2  g 2h- za zg  2f' pro půlovou a dvě osminové v dvoučárkované oktávě
#                      a čtvrťovou, půlovou, dvě osminové a půlovou v jednočárkované oktávě
# tuning: ftemp (temperované ladění), fjust (přirozené ladění), fpyth (pythagorejské ladění)
# závislost: numpy pro pole
#            scipy pro uložení wav-souboru (při saveWav=True)
#            simpleaudio nebo sounddevice pro přehrávání zvuku z numpy-pole (při playNow=True)

import numpy as np   # pro pole
import scipy         # pro uložení wav-souboru
import simpleaudio   # pro přehrávání zvuku z numpy-pole (při playSA=True, viz funkce playArray)
import sounddevice   # pro přehrávání zvuku z numpy-pole (při playSA=False,       viz playArray)

playNow=True         # hrát hned?
saveWav=True         # uložit wav?
dispFreq=True        # vypisovat frekvence?
fa1=440              # frekvence komorního a (a1)/concert pitch [440 Hz]
semitone=2**(1/12)   # temperovaný půltón/equal-tempered semitone
fs=44100             # vzorkovací frekvence/sampling frequency [44100 Hz]
tbeat=0.20           # délka doby/beat length (základní časová jednotka) [sec]
if playNow:
  playSA=True                                         # True pro simpleaudio, False pro sounddevice
if saveWav:
  fileWav1='1voice.wav'                               # jméno wav-souboru
  fileWav2='2voices.wav'                              # jméno dvoukanálového wav-souboru
  wav=np.empty((0),dtype=np.int16)                    # pracovní numpy-pole pro wav data
  wav1=np.empty((0),dtype=np.int16)                   # numpy-pole pro 1kanálový wav-soubor
  wav2=np.empty((0,2),dtype=np.int16)                 # numpy-pole pro 2kanálový wav-soubor
# ladicí faktory sedmitónové stupnice c1-d1-e1-f1-g1-a1-h1(-c2)
fjust=np.array([1,9/8,5/4,4/3,3/2,5/3,15/8,2])        # přirozené ladění/just intonation
fpyth=np.array([1,9/8,81/64,4/3,3/2,27/16,243/128,2]) # pythagorejské ladění/pythagorean tuning
ftemp=semitone**np.array([0,2,4,5,7,9,11,12])         # rovnoměrně temperované ladění/equal temperament
class cTone:                                          # datový typ pro tón
  def __init__(self,iton=0,iacc=0,ioct=1,ilen=1):     # pořadí v sedmitónové stupnici, posuvka, oktáva, délka
    self.iton=iton; self.iacc=iacc; self.ioct=ioct; self.ilen=ilen  # tone, accidental, octave, length

# Přehraje zvukové pole a/nebo přidá zvukové pole k wav-poli
def playArray(y,channels=1):
  global wav,wav2
  if playNow:
    if playSA:        # simpleaudio přehrává poněkud nerytmicky, s menšími pomlkami mezi tóny
      obj=simpleaudio.play_buffer(audio_data=y,num_channels=channels,bytes_per_sample=2,sample_rate=fs)
      obj.wait_done()
    else:             # sounddevice přehrává spíše rytmicky, s většími pomlkami mezi tóny
      sounddevice.play(data=y,samplerate=fs)
      sounddevice.wait()
  if saveWav:         # kumulace tónů pro jednorázové uložení
    if channels==1:   wav=np.append(wav,y)            # jednokanálové pole
    elif channels==2: wav2=np.append(wav2,y,axis=0)   # dvoukanálové pole

# Konvertuje frekvenci a délku tónu na zvukové pole
def evalArray(freq,dt):
  tarr=np.linspace(0,dt,round(dt*fs),endpoint=False)    # časové vzorkování
  y=np.cos(2*np.pi*freq*tarr)*32767
  nfade=200                 # počet vzorků pro lineární nástup a útlum tónu
  y[:nfade]*=np.linspace(0,1,nfade)
  y[-nfade:]*=np.linspace(1,0,nfade)
  return y.astype(np.int16)                           # 16bitové int hodnoty

# Zpracuje tón o dané frekvenci a dané délce
def playFreq(freq,dt):
  if dispFreq: print(f'{freq:.1f}',end=' ')
  playArray(evalArray(freq,dt))

# Zpracuje dvojzvuk o daných frekvencích a dané délce
def playFreq2(freq1,freq2,dt):
  if dispFreq: print(f'{freq1:.1f}/{freq2:.1f}',end=' ')
  y1=evalArray(freq1,dt)
  y2=evalArray(freq2,dt)
  playArray(np.column_stack((y1,y2)),channels=2)

# Konvertuje proměnnou třídy cTone na frekvenci a délku tónu v předepsaném ladění
def evalFreq(tone,tuning=ftemp):
  if tone.iton<0: freq=0
  else:
    fc1=fa1/tuning[5]                           # frekvence c1
    freq=fc1*tuning[tone.iton]                  # sedmitónové ladění
    if tone.iacc!=0: freq*=semitone**tone.iacc  # temperované půltóny
    if tone.ioct!=1: freq*=2**(tone.ioct-1)     # oktávové posuny
  dt=tbeat*tone.ilen                            # délka tónu
  return (freq,dt)

# Zpracuje údaje o tónu z proměnné třídy cTone
def playcTone(tone,tuning=ftemp):
  playFreq(*evalFreq(tone,tuning))

# Zpracuje údaje o dvojzvuku z proměnných třídy cTone
def playcTone2(tone1,tone2,tuning1=ftemp,tuning2=[]):
  if len(tuning2)==0: tuning2=tuning1
  (freq1,dt1)=evalFreq(tone1,tuning1)
  (freq2,dt2)=evalFreq(tone2,tuning2)
  playFreq2(freq1,freq2,max(dt1,dt2))

# Konvertuje řetězcový zápis tónu na proměnnou třídy cTone
def evalcTone(s):
  s=s.lower()
  ilen='xyz123456789'.find(s[0])          # délka tónu/tone length
  if ilen<0: ilen=1
  elif ilen<3: ilen=2**(ilen-3); s=s[1:]
  else: ilen=ilen-2; s=s[1:]
  iton='-cdefgah'.find(s[0])              # výška tónu/tone pitch
  if iton<0: return cTone(0,0,0,2)        # error: chybný znak
  elif iton==0: iton=-1; s=s[1:]          # pomlka
  else: iton-=1; s=s[1:]
  if len(s):
    iacc='-+'.find(s[0])                  # posuvka/accidental
    if iacc<0: iacc=0
    elif iacc==0: iacc=-1; s=s[1:]
    else: iacc=1; s=s[1:]
  else: iacc=0
  if len(s):
    ioct='xyz12345'.find(s[0])            # oktáva/octave
    if ioct<0: ioct=1
    else: ioct-=2
  else: ioct=1
  return cTone(iton,iacc,ioct,ilen)

# Zpracuje řetězcový zápis tónu v předepsaném ladění
def playTone(s,tuning=ftemp):
  playcTone(evalcTone(s),tuning)

# Zpracuje řetězcový zápis dvojzvuku v předepsaných laděních
def playTone2(s1,s2,tuning1=ftemp,tuning2=[]):
  tone1=evalcTone(s1)
  tone2=evalcTone(s2)
  playcTone2(tone1,tone2,tuning1,tuning2)

# Zpracuje sekvenci tónů
def playSeq(seq,tuning=ftemp):
  global wav
  if saveWav:
    wav=np.empty((0),dtype=np.int16)    # numpy-pole pro data pro wav-soubor
  for s in seq.split():
    playTone(s,tuning)

# Zpracuje sekvenci tónů z řetězce a uloží jednohlas
def playSeq1(seq,tuning=ftemp):
  global wav1
  playSeq(seq,tuning)
  if dispFreq: print()
  if saveWav:                           # přidání sekvence k wav1
    wav1=np.append(wav1,wav)

# Zpracuje sekvence tónů ze dvou řetězců a uloží dvojhlas (dva nezávislé hlasy)
def playSeq11(seq1,seq2,tuning1=ftemp,tuning2=[]):
  global wav2,playNow
  if len(tuning2)==0: tuning2=tuning1
  playNow=False                         # nezávislé hlasy nelze přehrávat po dvojzvucích
  playSeq(seq1,tuning1)                 # první kanál do wav
  if dispFreq: print()
  if saveWav: wav2a=wav.copy()          # zálohování prvního kanálu
  playSeq(seq2,tuning2)                 # druhý kanál do wav
  if dispFreq: print()
  if saveWav: wav2b=wav.copy()          # zálohování druhého kanálu
  if saveWav:                           # sloučení kanálů a přidání sekvence k wav2
    wav2=np.append(wav2,np.column_stack((wav2a,wav2b)),axis=0)

# Zpracuje sekvenci dvojzvuků z řetězců a uloží dvojhlas (po dvojzvucích)
def playSeq2(seq1,seq2,tuning1=ftemp,tuning2=[]):
  list2=seq2.split()
  for n,s1 in enumerate(seq1.split()):
    s2=list2[n]
    playTone2(s1,s2,tuning1,tuning2)
  if dispFreq: print()

### Syntonické/pythagorejské koma na 440/220 Hz, přírodní/vlčí kvinta/tercie
# playFreq2(440,440*81/80,4)
# playFreq2(220,220*81/80,4)
# playFreq2(440,440*3**12/2**19,4)
# playFreq2(220,220*3**12/2**19,4)
# tbeat=0.50
# playTone2('8c','8g',fjust)
# playTone2('8d','8a',fjust)
# playTone2('8e','8g',fjust)
# playTone2('8d','8f',fjust)
# if dispFreq: print()

### Stupnice C dur, D dur a E dur v různých laděních
# tbeat=0.30
# playSeq1('c d e f g a h 2c2  -  d e f+ g a h c+2 2d2  -  e f+ g+ a h c+2 d+2 2e2  -',fjust)
# playSeq1('c d e f g a h 2c2  -  d e f+ g a h c+2 2d2  -  e f+ g+ a h c+2 d+2 2e2  -',fpyth)
# playSeq1('c d e f g a h 2c2  -  d e f+ g a h c+2 2d2  -  e f+ g+ a h c+2 d+2 2e2  -',ftemp)
### Stupnice C dur paralelně v přirozeném a rovnoměrně temperovaném ladění
# tbeat=2.00
# playSeq2('c d e f g a h 2c2','c d e f g a h 2c2',fjust,ftemp)

### Test ze záhlaví
# tbeat=0.50
# playSeq1('2c2 zd2 zc2  g 2h- za zg  2f')
### Ovčáci čtveráci
# tbeat=0.20
# playSeq1('2c 2e  2g 2-  2c 2e  2g 2-  e e d e  2f 2d  e e d e  2f 2d  2e 2d  4c')
### Koleda: Nesem vám noviny
# tbeat=0.20
# playSeq1('c2 g c2 a d2 h-  c2 g c2 a d2 h-  c2 g a c2 g a  6f  ' +
#          'c2 g c2 a d2 h-  c2 g c2 a d2 h-  c2 g a c2 g a  6f  ' +
#          '2f a f a c2      2f a f g c       2f a f a c2    2f a f g c  c2 g a c2 g a  6f')
### Koleda: Veselé vánoční hody
# tbeat=0.20
# playSeq1('d f+ a a  2a d2 h  2a 2g  4f+  d f+ a a  2a d2 h  2a 2g  4f+  ' +
#          'a a g f+  e f+ g e  a a g f+  e f+ g e  2f+ 2e  4d  ' +
#          'a a g f+  e f+ g e  a a g f+  e f+ g e  2f+ 2e  4d  ')

### Schumann: Chopin
tbeat=0.20
voice1 ='8-                        2e-2 -   e-2  3e-2        f2  3e-2     -  2e-2   2f2      2g-2   2f2   2e-2 2d-2 2e-2 2c2     '
voice2 ='a-y e-z a-z c e- a- c2 a- e- c a-z e-z  g-y e-z g-z h-z e- g- h- g- e- h-z g-z e-z  fy e-z fz az c e- f e- c az fz e-z  '
voice1+='4d-2          4-          2f2  -   f2  3f2        g2 3f2    -  2f2 2g2     2a-2   2g2    2f2  2e-2   yg2 2f2    2d-2    3c2         5-            2e-2 2c2  '
voice2+='h-y fz h-z d- f h- d-2 h- f d- h-z fz  a-y fz a-z c f a- c2 a- f c a-z fz  fy d-z fz a-z d- f gy h-y y-  e-z gz h-z e-  a-y e-z a-z c e- a- c2 a- e- c a-z e-z  '
voice1+='4h-          4a          2h-    2c2     8d-2                        2f2    2d-2  2c2 d-3 c3 h2 h-2 g2 e2 c2 e-2 d-2 c2 h-  '
voice2+='gy d-z ez az h-z d- e d- h-z gz ez d-z  g-y d-z ez g-z h-z e fy d-z fz h-z d- f  2ey cz  ez gz h-z c  e  g  e-  d-  c  h-z  '
voice1+='6a-              2-   2g    2a-    8a                       2g+   2a      6h-                    4e-2       2h      4c2         3-  '
voice2+='fy cz fz a-z c f a- f c a-z fz cz  f-y ay d-z gz az d- g d- az gz d-z ay  e-y h-y e-z a-z h-z d- a- g e- hz gz e-z  a-y e-z a-z c e- 2a-  '
playSeq11(voice1,voice2)

# Uloží wav-soubor
if saveWav:
  if wav1.size: scipy.io.wavfile.write(filename=fileWav1,rate=fs,data=wav1)  # jednokanálový soubor
  if wav2.size: scipy.io.wavfile.write(filename=fileWav2,rate=fs,data=wav2)  # dvoukanálový soubor

Berte skript jako vánoční dárek, a za domácí úkol mi jeho užitím vytvořte také nějaké vánoční dárky ve WAV formátu.

Zdrojové soubory .py: zip. WAV soubory: Ovčáci čtveráci, Nesem vám noviny, Veselé vánoční hody, Schumannův Chopin a konkurence

P. S. Ach, synku, synku. Petr Eben, Lidové písně a koledy, Panton 1991 (WAV)

# Ach, synku, synku (jednohlas a dvojhlas)
tbeat=0.33
voice1 ='2e 2e 2e     e f+ 4g+     a g+ 4f+      6e   '
voice2 ='6-           3c+ hz 2a+z  4hz c+ d+     c+ hz 2az 2g+z  '
voice1+='2g+ 2g+ 2g+  g+ a 4h      c+2 h 2a 2g+  '
voice2+='6e           3e d+ 2d     6c+  '
voice1+='2f+ 2f+ 2f+  f+ g+ 4a     h a 2g+ 2f+   '
voice2+='6c+          3e d 2c      6hz  '
voice1+='2e 2e  2e    e f+ 4g+     a g+ 4f+      6e'
voice2+='3c+ hz 2az   4g+z 2h+z    2c+ 2hz 2az   6g+z'
playSeq1(voice1)
playSeq11(voice1,voice2)
# Ach, synku, synku (po dvojzvucích)
tbeat=0.33
playSeq2('2e 2e 2e  e  f+ g+ g+ 2g+   a  g+ 2f+ f+ f+  e  e  2e  2e    2g+ 2g+ 2g+  g+ a h h  2h  c+2 h  2a  2g+  2f+ 2f+ 2f+  f+ g+ a a 2a  h  a  2g+ 2f+  2e  e  e  2e   e   f+  2g+  2g+   a  g+ 2f+ 2f+  6e',
         '2e 2e 2e  c+ c+ c+ hz 2a+z  hz hz 2hz c+ d+  c+ hz 2az 2g+z  2e  2e  2e   e  e e d+ 2d  c+  c+ 2c+ 2c+  2c+ 2c+ 2c+  e  e  e d 2c  hz hz 2hz 2hz  2c+ c+ hz 2az  g+z g+z 2g+z 2h+z  c+ c+ 2hz 2az  6g+z')
_images/ach-synku-synku.jpg