Fortsettelse av lydoppaven
Spørsmål eller kommentarer til oppgaveteksten sendes til haraldfl@ifi.uio.no eller jonaen@ifi.uio.no.
Denne uken skal vi jobbe med lyd i Python. Vi kommer til å snakke litt om lyd i gruppetimen, og vil se at lyd egentlig bare er bølger, og at vi enkelt kan representere slike bølger ved hjelp av en liste i Python (der hvert element representerer bølgehøyden på et gitt tidsrom).
For å gjøre denne oppgaven trenger du å ha numpy installert på pc-en din. Ta kontakt dersom du har problemer med å installere disse!
NB: Oppgaven bygger på oppgavene for uken (vi jobber og går gjennom disse på fredag) og det anbefales å gjøres disse før du gjør oppgaven. Oppgaven fortsetter på konseptene og koden som blir introdusert i undervisningen.
Prekode
Lag en fil lyd.py hvor du importerer numpy og numpy slik:
import numpy as np
Alle oppgavene i denne oppgaven (bortsett fra den valgfrie oppgaven) kan gjøres i filen lyd.py.
På gruppetimen har vi skrevet (eller vil skrive) en funksjon for å lage en liste som representerer en tone med en gitt frekvens i et gitt antall sekunder:
Legg denne funksjonen, som vi kaller lag_tone i lyd.py:
def lag_tone(antall_sekunder, antall_svingninger_i_sekundet):
lyd = []
for i in range(int(44100 * antall_sekunder)):
lyd.append(16000 * (1 + np.sin(antall_svingninger_i_sekundet * i/44100 * 2 * np.pi)))
return lyd
Du trenger også funksjonen skriv_lyd_til_fil:
import numpy as np
import wave
def skriv_lyd_til_fil(data, sample_rate, filnavn):
# tatt fra https://stackoverflow.com/a/64376061
audio = np.array([data, data]).T
audio = audio.astype("<h")
with wave.open(filnavn, "w") as f:
f.setnchannels(2)
f.setsampwidth(2)
f.setframerate(sample_rate)
f.writeframes(audio.tobytes())
Test at du får laget en tone og skrevet denne til fil og spilt den av (NB: det kan være lurt å ikke ha på for høyt volum første gang du prøver, i tilfelle lyden er høy/ubehagelig):
skriv_lyd_til_fil(lag_tone(3, 440), 44100, "filnavn.wav")
Hvis koden over fungerer og du får spilt av lyden, er du klar til å begynne på resten av oppgaven.
Oppgave 1: Lese noter fra fil
Vi ønsker nå lage et lite program som kan spille musikk som er skrevet ved hjelp av tekst i en fil. Sangen vår ser slik ut (se under). Hver linje inneholder navnet på en note og varigheten til noten (hvor lenge den skal spilles).
Kopier innholdet her og lagre innholdet i en fil sang.txt:
E 0.3
- 0.1
E 0.3
- 0.1
E 0.7
- 0.1
E 0.3
- 0.1
E 0.3
- 0.1
E 0.7
- 0.1
E 0.3
- 0.1
G 0.3
- 0.1
C 0.5
- 0.1
D 0.2
E 1.5
- 0.1
F 0.3
- 0.1
F 0.3
- 0.1
F 0.5
- 0.1
F 0.2
F 0.3
- 0.1
E 0.3
- 0.1
E 0.3
- 0.1
E 0.1
- 0.1
E 0.1
- 0.1
E 0.3
- 0.1
D 0.3
- 0.1
D 0.3
- 0.1
E 0.3
- 0.1
D 0.7
- 0.1
G 0.8
PS: Pass på at du ikke får noen ekstra tomme linjer til slutt når du lagrer filen.
Hver note tilsvarer en frekvens (antall svingninger i sekundet). For eksempel tilsvarer A frekvensen 440.
Vi kan bruke denne ordboken for å representere hvilke noter som tilsvarer hvilke frekvenser:
noter = {
"A": 440,
"G": 392,
"F": 349,
"E": 330,
"D": 294,
"B": 247,
"C": 261,
"-": 0
}
PS: Vi bruker "-" til å representere en stille tone (pause). Denne har frekvens 0, og vil ikke gi noen lyd.
Lag en funksjon les_sang_fra_fil som tar filnavn som parameter, leser sangen fra filen og returnerer en liste
der hvert element i listen er en ny liste bestående av to elementer: frekvens og varighet. Denne listen som returneres
vil representere sangen i filen på et format som vi kan bruke til å spille av sangen senere.
Test funksjonen les_sang_fra_fil på filen sang.txt. Print listen som returneres, og sjekk at den
starter slik:
[[330, 0.3], [0, 0.1], [330, 0.3], [0, 0.1], ...
Sjekk at du forstår dette formatet (hva denne nøstede listen betyr og representerer) før du går videre.
Oppgave 2:
Lag en funksjon lag_sang_fra_noter.
- Denne funksjonen skal i motsetning til
les_sang_fra_filikke ta et filnavn som parameter, men i stedet ta en liste av den typenles_sang_fra_filreturnerer. - Oppgaven til denne funksjonen er å lage en "sang", dvs. én enkelt liste som representerer lydbølgene til sangen, og som kan sendes rett inn til
skriv_lyd_til_filfor å lagre en komplett sang som senere kan spilles av. - Funksjonen må altså ta inn den nøstede listen med noter, gå gjennom hver note for å lage lydbølgene til den noten og slå alt sammen til én enkelt liste som kan lagres.
Test funksjonen med sangen du lagret i sang.txt, og spill av lyden som blir generert. Kjenner du igjen hvilken sang det er?
Oppgave 3:
Lag en funksjon fade_ut som tar en komplett lyd (en liste av typen som kan sendes inn til skriv_lyd_til_fil) og fader den ut (senker volumet gradvis).
Volum er rett og slett hvor høye verdier det er i lyden. Du kan derfor fade sangen ut ved å gange verdiene med gradvis lavere og lavere tall. Det er ulike måter å gjøre dette på. Prøv deg frem til du får noe du er fornøyd med.
Oppgave 4
Lag en funksjon forenkle_lyd som tar en lyd (en liste) og endrer hver verdi i lyden slik:
- Hvis verdien er under 16000: Sett verdien til å være 0
- Hvis verdien er over 16000: Sett verdien til å være 32000
Test funksjonen på lyden generert fra sangen i oppgave 2. Før du hører på resultatet, gjett om du tror du kommer til å kjenne igjen sangen eller ikke.
Oppgave 5 (valgfri konkurranse)
Skriv koden for denne oppgaven i en fil med navn konkurranse.py
Her finner du en fil med et lydsignal som inneholder en hemmelig kode. Koden er vanskelig/umulig å høre pga støy og andre problemer med lyden. Last ned filen og legg den i samme mappe som konkurranse.py ligger. Prekoden under viser deg hvordan du spiller av lyden.
Konkurransen går ut på å skrive kode som endrer lyden slik at du klarer å høre hva den hemmelige koden er. Jo renere og penere lyd du klarer å få, jo bedre. Det er altså to mål i konkurransen:
- Få tak i den hemmelige koden
- Lag en ny lyd som er så ren og pen som mulig (lite støy og andre artifakter). Vi vil høre på de ulike bidragene i neste gruppetime og sammen kåre en vinner.
Start med følgende prekode og følg instruksjonene i kommentarene:
import numpy as np
import matplotlib.pyplot as plt
import pickle
def skriv_lyd_til_fil(lydliste):
# dette er funksjonen du har fra før ...
def les_lyd_fra_fil():
lyd = pickle.load(open("kode.pickle", "rb"))
return lyd
hemmelig_kode = les_lyd_fra_fil()
# hemmelig_kode er nå en liste som inneholder lyden du skal jobbe med
# Start gjerne med å skrive lyden til fil og spille den av å få en fealing av hva du har å jobbe med:
skriv_lyd_til_fil(hemmelig_kode)
# Det kan være lurt å plotte lyden for å se etter mulige problemer og ting du kan gjøre for å forbedre signalet
plt.plot(hemmelig_kode)
plt.show()
# tips: Det kan være lurt å zoome inn på deler av plottet for å studere det nærmere
def fiks_lyd(lyd):
# Implementer ditt bidrag til konkurransen i denne funksjonen
# denne funksjonen kan kalle andre funksjoner (om du vil dele opp koden din),
# men den skal returnere en ny liste som er den korrigerte lyden
# det er denne lyden du vil bli vurdert på
pass
Et hint: Støy i lyd kommer ofte i form av enkeltverdier som er mye høyere eller lavere enn verdier i nærheten:
[1, 2, 3, 4, 1000, 5, 4, 3, 2, 1, 0, 1, 2, 3]
Her er typisk tallet 1000 støy, og lyden vil høres bedre ut om man hadde byttet ut 1000 med f. eks verdien før eller etter (eller gjennomsnittet/medianen av verdiene rundt).