Funksjonsmusikk
Info til lærer
Tanken med dette opplegget er å representere funksjoner med lyd, og å la elevene utforske egenskaper til ulike funksjoner. Elevene skal se på kjennetegn ved ulike typer funksjoner, og får mulighet til å leke seg med lydbildene de gir.
Utformingen av opplegget legger ikke opp til at elevene skal bygge kode, men endre på parametere i ferdig kode for å generere ulike funksjoner. Dette kan læreren selvsagt endre etter nivået til klassen.
Relevante kompetansemål i 1P
Mål for opplæringen er at eleven skal kunne
- bruke digitale verktøy i utforsking og problemløsing knyttet til egenskaper ved funksjoner, og diskutere løsningene
Relevante kompetansemål i 1T
Mål for opplæringen er at eleven skal kunne
- utforske og beskrive egenskapene ved polynomfunksjoner, rasjonale funksjoner, eksponentialfunksjoner og potensfunksjoner
En funksjon er en sammenheng mellom to forskjellige mengder, slik at hvert element i den ene mengden (definisjonsmengden) har nøyaktig ett tilhørende element i den andre mengden (verdimengden).
Et eksempel på en funksjon er , som er slik at for hvert tall vi "sender inn" i funksjonen, vil vi få ut et tall som er nøyaktig én mer enn .
Det er også mange ulike måter man kan visualisere funksjoner, og de vanligste er ved som funksjonsuttrykk og grafisk. Til høyre er det en grafisk framstilling av .
I dette programmet skal vi se på en tredje måte å utforske funksjoner, nemlig som lyd. Vi vil lage et program som kan ta inn en funksjon og gjøre høye tall om til lyse toner, og lave tall til mørke toner.
</div>Aller først importerer vi bibliotekene vi trenger for å kjøre de ulike delene av programmet:
import numpy as np # Diverse matematikkting vi trenger til programmet
import matplotlib.pyplot as plt # Brukes til plotting
from scipy.io.mp3file import write # Brukes til å lage lydfiler
from IPython.display import Audio # Brukes til å spille av lydfiler
Oppgave 1
Hør på lydklippene under.
<br>a) Hvordan tenker du grafene til funksjonene ser ut? Lag en skisse til hver lydfil.
<br>b) Prøv å bestemme hva slags funksjon hver av de to lydklippene viser - lineær, polynom, eksponentiell, rasjonal, eller kanskje noe annet?
</div>Info til lærer
Dette passer også ypperlig å ta felles. Klassen hører en lydfil felles og alle jobber i 1 min med å diskutere og tegne grafen før det tas i felleskap.
[1]:
from IPython.display import Audio
Audio('funksjon1.mp3') # Hvordan ser denne funksjonen ut?
[1]:
[2]:
Audio('funksjon2.mp3') # Hvordan ser denne funksjonen ut?
[2]:
[3]:
Audio('funksjon3.mp3') # Hvordan ser denne funksjonen ut?
[3]:
[4]:
Audio('funksjon4.mp3') # Hvordan ser denne funksjonen ut?
[4]:
Nå skal vi bygge opp et program som kan lage slike lydfiler.
Vi lager en funksjon
For å i det hele tatt ha noe å høre på, må vi først lage en funksjon. Kodebiten under lager funksjonen og plotter den på intervallet .
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return x**2 - 2*x - 3
x = np.linspace(-5, 5, 1000)
y = f(x)
plt.plot(x, y, 'r')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.show()
Oppgave 2
Hvordan tenker du funksjonen vil høres ut når vi spiller den av?
</div>Info til lærer 1P
For å gjøre programmet mindre komplekst kan du gjerne vurdere å slette fram til overskriften "Fra funksjon til musikk".
</div>Frekvens og toner
<img src="frekvens_b-lengde.png" style="float: right;" width="500"/>I dette programmet vil vi la høye -verdier tilsvare lyse toner, og lave tilsvare mørke. Når vi spiller av figuren over vil vi derfor starte på en lys tone som blir mørkere og mørkere helt til den når bunnpunktet, og deretter lysere og lysere helt til den er ferdig. Men for at programmet skal få dette til, må vi først endre litt på -verdiene.
<div class="alert alert-block alert-success">Toner er lydbølger og måles antall svingninger per sekund. De har enheten , som også kalles "hertz" (Hz). Antall hertz forteller oss frekvensen til en tone, og en høy frekvens vil gi en høy/lys tone, mens en med lavere frekvens er dypere.
</div>På denne siden kan du høre hvordan ulike frekvenser høres ut, og se hvilke toner de tilsvarer.
I funksjonen vi lagde over er den laveste -verdien vi får -4, og den høyeste er 32. Men vi kan ikke ha et negativt antall svingninger per sekund, og på lenken over vil du også se at en frekvens på 32 Hz ikke er hørbart for oss mennesker. Derfor er vi nødt til å skalere verdiene våre for at det skal gi mening. Det vil si at vi må endre alle verdiene slik at de har samme forhold til hverandre og ligger i et hørbart intervall.
Skalering
For å gjennomføre skaleringen, må vi først bestemme oss for hva som skal være den lyseste og den mørkeste tonen i funksjonsmelodien vår. Vi velger her å la den mørkeste tonen være A3 på 220 Hz, og den lyseste A7 på 3520 Hz, som gir oss en melodi som går over fire oktaver.
Vi kan bruke denne funksjonen til å skalere:
Her bruker vi øvre og nedre grense for våre -verdier, og øvre og nedre grense for verdiene vi vil at de skal ha i stedet.
- er en -verdi vi får ut av funksjonen . Dette tallet kan være hvilket som helst av -ene våre.
- og er nedre og øvre grensene for -verdiene vi får fra funksjonen . For vår funksjon er det -4 og 32.
- er den mørkeste tonen vi vil få ut, og er den lyseste. Vi har valgt 220 og 3520.
- er den skalerte verdien til .
Oppgave 3
Hva blir omtrent frekvensen i nullpunktene til funksjonen når vi bruker samme intervall som over, og bestemmer at alle tonene skal være mellom 220 og 3520 Hz?
</div> <div class="alert alert-block alert-danger">Løsning (slett dette)
Tonen er omtrent en D5.
</div>Vi definerer funksjonen skaler(y)
, som tar inn en -verdi og returnerer den skalerte verdien. Funksjonene max()
og min()
er innebygde funksjoner som finner den største verdien i ei liste/en array.
def skaler(y):
y_max = max(y)
y_min = min(y)
t_max = 3520
t_min = 220
y_skalert = (y - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y_skalert
Hvis plotter de skalerte -verdiene ser vi at grafen har samme form, men at verdiene er mellom ca. 250 og 1750 (i virkeligheten 220 og 1760), slik vi ønsker. Pass på å kjøre kodeboksen over før den under.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 1000)
y = f(x)
y = skaler(y)
plt.plot(x, y, 'r')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.show()
Fra funksjon til musikk
Siden lyd er bølger, må vi til slutt også lage en funksjon som gjør frekvensene vi har regnet ut om til en bølgebevegelse. Denne funksjonen kan se slik ut:
def lag_bolge(frekvens):
samplerate = 44100
amplitude = 4096
bolge = amplitude * np.sin(2 * np.pi * np.cumsum(frekvens/samplerate))
return bolge
samplerate
, eller "samplingsrate", bestemmer hvor mange datapunkter vi skal ha per sekund. Samplingsraten må være minst det dobbelte av den høyeste frekvensen i lydklippet for å få en riktig gjengiving av lyden. Siden mennesker kan høre lyder opp til ca. 20'000 Hz, bør samplingsraten være minst 40'000 Hz, og det er vanlig å bruke 44'100 Hz. I vårt eksempel får vi bare lyder opp til 3520 Hz, så her hadde det holdt med en samlplingsrate på minst 7040 Hz - men hvis du reduserer raten til 7040 og øker t_max
, vil du få uventede og hakkete melodier ut, så det er like greit å valge en høy samplerate
.
Når vi definerer variabelen bolge
lager vi selve bølgebevegelsen. Vi bruker en sinus-funksjon til å lage bølga, og ved å hente inn frekvensen til de skalerte -verdiene får vi en bølge som gjør alle frekvensene i input-funksjonen hørbare.
Nå skal vi sette alt dette sammen:
import numpy as np
import matplotlib.pyplot as plt
from scipy.io.mp3file import write
from IPython.display import Audio
samplerate = 44100
# Definerer funksjonen
def f(x):
return x**2 - 2*x - 3
# Skalerer funksjonen til hørbare frekvenser
def skaler(y):
y_max = max(y)
y_min = min(y)
t_max = 3520
t_min = 220
y_skalert = (y - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y_skalert
# Lager bølge av frekvensene
def lag_bolge(frekvens):
samplerate = 44100
amplitude = 4096
bolge = amplitude * np.sin(2 * np.pi * np.cumsum(frekvens/samplerate))
return bolge
start = -5 # Startverdi for x
slutt = 5 # Sluttverdi for x
tid = 5 # Antall sekunder melodien skal vare
x = np.linspace(start, slutt, int(samplerate * tid))
y = f(x)
# Tegner grafen til funksjonen
plt.plot(x, y, 'r')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.show()
# Bruker skaleringsfunksjonen på y-verdiene og bygger melodien
y = skaler(y)
melodi = lag_bolge(y)
write('funksjonsmelodi.mp3', samplerate, melodi.astype(np.int16))
Audio('funksjonsmelodi.mp3')
Oppgave 4
a) Hva skjer når du endrer amplituden? Hvordan påvirker det lyden?
<br>b) Få programmet til å spille melodien til på intervallet .
<br>c) Få programmet til å spille melodien til på intervallet . Hva forventer du å høre? Hva hører du? Snakk med sidemannen din om hvorfor dere tror melodien blir som den blir.
<br>d) Prøv å endre på funksjonen f(x)
eller variablene start
, slutt
, t_min
og t_max
for å teste ut andre funksjoner, -intervaller og tonehøyder.
Tips
Numpy-biblioteket vi importerte i starten inneholder mange matematiske funksjoner og konstanter som kan være greie å kjenne til. Her er en oversikt over noen vanlige, i tilfelle du har lyst til å lage funksjoner med dem:
Matematisk | Programmering med Numpy | Beskrivelse |
---|---|---|
np.pi | Den matematiske konstanten | |
np.exp(1) | Den matematiske konstanten | |
np.exp(x) | opphøyd i | |
np.sqrt(x) | Kvadratroten til | |
np.log(x) | Den naturlige logaritmen til | |
np.sin(x) | Sinus til | |
np.cos(x) | Cosinus til |
Nullpunkter
<img src="x2-2_original.png" style="float: right;" width="400"/>Til høyre ser du grafen til funksjonen , og som vi ser har den to nullpunkter.
Det er lett å se nullpunktene til en funksjon, men ikke like åpenbart hvordan man skal høre dem - men det skal vi prøve på nå.
<div class="alert alert-block alert-info">Oppgave 5
Diskuter med makkeren din - hva skjer med funksjonsverdien i nullpunktene til funksjonen?
Hva er funksjonsverdien rett til venstre og høyre for nullpunktene?
</div>I nullpunktene til skifter mellom å være positiv og negativ. Siden vi ikke kan ha "negativ lyd", må vi finne en alternativ måte å vise at lyden "skifter fortegn".
Absoluttverdi
Absoluttverdien av et tall er tallet med positivt fortegn. For eksempel er absoluttverdien til lik , og absoluttverdien til er også .
Vi skriver absoluttverdien til et tall som . Altså kan vi skrive og
I Python bruker vi funksjonen abs()
for å regne absoluttverdien til tall:
[18]:
a = int(input("Skriv et tall: "))
print("Absoluttverdien til", a, "er", abs(a))
Skriv et tall: -5 Absoluttverdien til -5 er 5
Til høyre ser du grafen til absoluttverdien til , altså .
<div class="alert alert-block alert-info">Oppgave 6
Hva er likt mellom grafen over og den til høyre? Hva er ulikt?
Hvordan tenker du funksjonen høres ut?
</div> <div class="alert alert-block alert-info">Oppgave 7
Endre koden under slik at du får et program som kan spille av absoluttverdien til funksjoner ved å bytte ut spørsmålstegnene i linje 14, y = ???
.
Du finner et forslag til kode lenger ned.
</div>import numpy as np
from scipy.io.mp3file import write
from IPython.display import Audio
samplerate = 44100
def f(x):
return x**2 - 2
def skaler(y):
y_max, y_min = max(y), min(y)
t_max, t_min = 3520, 220
y_skalert = (y - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y_skalert
def lag_bolge(frekvens):
samplerate = 44100
amplitude = 4096
bolge = amplitude * np.sin(2 * np.pi * np.cumsum(frekvens/samplerate))
return bolge
start = -5 # Startverdi for x
slutt = 5 # Sluttverdi for x
tid = 5 # Antall sekunder melodien skal vare
x = np.linspace(start, slutt, int(samplerate * tid))
# ----------
# HER MÅ DU ENDRE PROGRAMMET:
y = ??? # Hva må stå her for at vi skal få absoluttverdien til funksjonen?
y = skaler(y)
melodi = lag_bolge(y)
# ----------
write('funksjonsmelodi.mp3', samplerate, melodi.astype(np.int16))
Audio('funksjonsmelodi.mp3')
Oppgave 8
Bruk programmet til å bestemme hvor mange nullpunkt disse funksjonene har i på intervallet :
<br>a)
<br>b)
<br>c)
<br>d)
</div>import numpy as np
from scipy.io.mp3file import write
from IPython.display import Audio
samplerate = 44100
def f(x):
return x**2 - 2
def skaler(y):
y_max, y_min = max(y), min(y)
t_max, t_min = 3520, 220
y_skalert = (y - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y_skalert
def lag_bolge(frekvens):
samplerate = 44100
amplitude = 4096
bolge = amplitude * np.sin(2 * np.pi * np.cumsum(frekvens/samplerate))
return bolge
start = -1 # Startverdi for x
slutt = 3 # Sluttverdi for x
tid = 5 # Antall sekunder melodien skal vare
x = np.linspace(start, slutt, int(samplerate * tid))
y = abs(f(x))
y = skaler(y)
melodi = lag_bolge(y)
write('funksjonsmelodi.mp3', samplerate, melodi.astype(np.int16))
Audio('funksjonsmelodi.mp3')
Oppgave 9
Diskuter med makkeren din: Kan dere tenke dere noen situasjoner der det er problematisk å bruke dette programmet til å avgjøre om en funksjon har et eller flere nullpunkter i et intervall?
</div>Info til lærer:
Et nærliggende svar er selvsagt at det alltid vil by på vansker å bruke lyd framfor bilde/regning til å bestemme nullpunkter, men absoluttverdimetoden kan by på flere problemer i noen tilfeller, nemlig når funksjonen:
- ikke har et nullpunkt
- har nullpunkte(er) som også er ekstremalpunkt
- har nullpunkt(er) i start- og/eller sluttverdien til intervallet
Da vil det være vanskelig å avgjøre om den laveste verdien er et nullpunkt eller bare en minimumsverdi.
</div>Lyd i stereo - en funksjon i hvert øre
Når du spiller av funksjonsmelodiene med headset/ørepropper hører du samme melodi i hvert øre. Vi kaller dette monofoni, som er at det kun går ut ett signal til alle høytalerne. Monofoni forkortes ofte til "mono".
Lyd kan også spilles av i "stereo" (stereofoni), som har to eller flere utgående signaler. Lyd med mer enn to utgående signaler kan også kalles "surrond".
Lydinnspilling i stereo ble populært på 70-tallet, og mange musikere eksperimenterte med den nye opptaksformen, ved å for eksempel ha ulike instrumenter i hver kanal. Prøv å høre på Here Comes The Sun av The Beatles med ørepropper mens du bytter på hvilken du har i øret - det høres ganske ulikt ut.
Nå skal vi utvide programmet vårt til å returnere stereolyd, slik at vi kan få en funksjon i hvert øre. Det er faktisk bare tre ting vi må endre på/tilføye for å få til dette:
- Først må vi lage en ekstra funksjon, som vi kan kalle
g(x)
:
def g(x):
return x**2 - 2
- Så må vi lage en ny skaleringsfunksjon. I funksjonen
skaler(y)
brukte vimax(y)
ogmin(y)
for å finne den største og minste -verdien til . Når vi har to funksjoner, må vi passe på å skalere dem likt. Derfor lager vi funksjonenskaler_2(y1, y2)
, som henter inn -ene fra og og finner felles maksimum og minimum:
def skaler_2(y1, y2):
y_max = np.max([y1, y2]) # Finner den største verdien fra begge y-ene
y_min = np.min([y1, y2]) # Finner den minste verdien fra begge y-ene
t_max = 3520
t_min = 220
y1_frekvens = (y1 - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
y2_frekvens = (y2 - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y1_frekvens, y2_frekvens
- Etter å ha regnet ut og skalert -verdiene står vi igjen med to lister av tall. Det siste vi må gjøre for å få lyden i stereo, er å gjøre disse to listene til hver sin kolonne i lydsignalet som skal ut. Det kan vi gjøre med
numpy
-funksjonencolumn_stack()
:
melodi = np.column_stack([mel_f, mel_g])
Til slutt settes dette sammen med programmet vi hadde fra før:
import numpy as np
import matplotlib.pyplot as plt
from scipy.io.mp3file import write
from IPython.display import Audio
samplerate = 44100
# Definerer de to matematiske funksjonene:
def f(x):
return -x
def g(x):
return x**2-2
# Lager en ny skaleringsfunksjon som finner felles maksimum og minimum fra to funksjoner
def skaler_2(y1, y2):
y_max = np.max([y1, y2])
y_min = np.min([y1, y2])
t_max = 3520
t_min = 220
y1_frekvens = (y1 - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
y2_frekvens = (y2 - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y1_frekvens, y2_frekvens
# Bølgelagingsfunksjonen er den samme som før
def lag_bolge(frekvens):
samplerate = 44100
amplitude = 4096
bolge = amplitude * np.sin(2 * np.pi * np.cumsum(frekvens/samplerate))
return bolge
start = -2
slutt = 3
tid = 10
x = np.linspace(start, slutt, int(samplerate * tid))
y_f = f(x)
y_g = g(x)
# Her plottes de to funksjonene i samme koordinatsystem
plt.plot(x, y_f, 'r')
plt.plot(x, y_g, 'b')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.show()
# Bruker skaleringsfunksjonen på y-verdiene fra f og g, og bygger melodien
y_f, y_g = skaler_2(y_f, y_g)
mel_f = lag_bolge(y_f)
mel_g = lag_bolge(y_g)
melodi = np.column_stack([mel_f, mel_g])
write('funksjon_stereo.mp3', samplerate, melodi.astype(np.int16))
Audio('funksjon_stereo.mp3')
Oppgave 4
Bruk kodeboksen under for å undersøke hvor mange skjæringspunkt disse funksjene har på intervallet :
<br>a)
<br>b)
<br>c)
<br>d) Velg dine egne funksjoner og og prøv å høre etter skjæringspunkter
</div>import numpy as np
from scipy.io.mp3file import write
from IPython.display import Audio
samplerate = 44100
def f(x):
return -x+1
def g(x):
return np.exp(x)
def skaler_2(y1, y2):
y_max = np.max([y1, y2])
y_min = np.min([y1, y2])
t_max, t_min = 3520, 220
y1_frekvens = (y1 - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
y2_frekvens = (y2 - y_min)/(y_max - y_min) * (t_max - t_min) + t_min
return y1_frekvens, y2_frekvens
def lag_bolge(frekvens):
samplerate = 44100
amplitude = 4096
bolge = amplitude * np.sin(2 * np.pi * np.cumsum(frekvens/samplerate))
return bolge
start = -2
slutt = 2
tid = 10
x = np.linspace(start, slutt, int(samplerate * tid))
y_f = f(x)
y_g = g(x)
y_f, y_g = skaler_2(y_f, y_g)
mel_f = lag_bolge(y_f)
mel_g = lag_bolge(y_g)
melodi = np.column_stack([mel_f, mel_g])
write('funksjon_stereo.mp3', samplerate, melodi.astype(np.int16))
Audio('funksjon_stereo.mp3')
Info til lærer:
Her går det også mulig å diskutere hvordan man kan bruke dette programmet til å bestemme hvor mange nullpunkt en funksjon har i et gitt intervall (nemlig ved å la den ene funksjonen være ).
</div>