next up previous contents
Next: Filbehandling Up: C-programmering for musikere Previous: Algoritmisk komposisjon med CSound

Funksjoner, tabeller, datastrukturer

Funksjoner

Funksjoner er programbiter som utfører spesielle oppgaver. Det finnes mange innebygde funksjoner i C - vi har allerede sett printf, abs og rand. Men vi kan også definere våre egne funksjoner.

Eksempel 3: Note til frekvens

I eksemplene over har vi oppgitt tonehøyder i Hz. Hvis man heller vil bruke oktav (heltall) og pitchklasse (heltall fra 0 til 11), kan man endre CSound-orkesterfilen slik at den bruker tonehøyder i dette formatet. Men vi kan også lage oss en funksjon i C som foretar konverteringen. Dette krever at vi bruker en matematisk funksjon (powf) som ligger i et eget matematikkbibliotek.

#include <stdio.h>
#include <math.h>

float notefrekvens(int oktav, int pitchklasse)
{
  float frekvens;

  frekvens = 440.*powf(1.059463, (oktav-6)*12+pitchklasse-9);
  return frekvens;
}

void main(void)
{
  printf("%f\n", notefrekvens(6,9));
}

Vi har deklarert en funksjon som heter notefrekvens, som skal returnere et flyttall. Funksjonen tar to parametre, begge av typen int (heltall). Variabelen frekvens er lokal i funksjonen, dvs. at den ikke kan refereres andre steder enn inne i funksjonsdeklarasjonen.

Fordi vi bruker matematikkbiblioteket, må vi "lenke inn" dette når vi kompilerer. Dette gjøres ved å ta med "-lm" i kompileringskommandoen, f.eks. slik:

  cc notefrek.c -lm -o notefrek

Oppgave

Lag en funksjon som tar antall timer, minutter, sekunder og tiendedels sekunder som parametre, og returnerer den totale tiden som dette tilsvarer i sekunder. F.eks. skal 2 timer, 14 minutter, 23 sekunder og 3 tiendedeler gi 8063.3 sekunder.

Datastrukturer

I resten av dette kapittelet skal vi arbeide endel med "tradisjonell" noterepresentasjon. Vi kan betrakte en note som et objekt med endel egenskaper, f.eks. starttid, varighet, oktav, pitchklasse, styrke og instrument. Det gir renslige programmer om vi definerer en egen note-datatype der disse egenskapene inngår. Dette er faktisk et enkelt eksempel på en slags objektorientert programmering.

Eksempel 4: Et bibliotek for notebehandling

Her er definisjonen av note-datatypen, og en forbedret versjon av notefrekvens-funksjonen som benytter denne datatypen. I tillegg finnes en funksjon som skriver en CSound-notelinje. Vi skal bruke disse deklarasjonene i senere eksempler. Legg spesielt merke til definisjonen av datatypen notetype ved hjelp av en C-konstruksjon som heter struct. Nede i main kan vi så deklarere en variabel min_note av denne typen. Bemerk også hvordan vi refererer til elementene inne i noten med en punktum-notasjon. Vi leser note.oktav som "note sin oktav".

#include <stdio.h>
#include <math.h>

typedef struct {
  float starttid, varighet, styrke;
  int oktav, pitchklasse, instrument;
} notetype;

float notefrekvens(notetype note)
{
  float frekvens;

  frekvens = 440.*powf(1.059463, (note.oktav-6)*12+note.pitchklasse-9);
  return frekvens;
}

void skrivnote(notetype note)
{
  printf("i%d  %.3f  %.3f  %.2f %.2f\n",
    note.instrument,
    note.starttid,
    note.varighet,
    notefrekvens(note),
    note.styrke);
}

void main(void)
{
  notetype min_note;

  min_note.instrument = 1;
  min_note.starttid = 0.0;
  min_note.varighet = 1.0;
  min_note.oktav = 4;
  min_note.pitchklasse = 9;
  min_note.styrke = 80;
  skrivnote(min_note);
}

Disse definisjonene og rutinene (bortsett fra main) kan vi legge i en egen include-fil som vi kan kalle notelib.h, for senere bruk.

Oppgaver

1

Endre note-objektet slik at det takler mikrotonalitet (hvis du først skjønner hva jeg mener med spørsmålet, er dette fryktelig lett).

2

(Litt krevende)

Bruk notebiblioteket til å lage et program som spiller tilfeldige tolvtone-treklanger innenfor en fast oktav.

Tabeller

De variablene vi har sett på hittil kan bare lagre enkelttall og enkeltobjekter. Hvis vi vil arbeide med lister av objekter, må vi bruke tabeller. En tabell som kan lagre 1000 heltall deklareres slik:

  int tabell[1000];

Enkeltelementer i tabellen kan så refereres ved å indeksere. Slik legger vi tallet 13 i element nummer 576:

  tabell[576] = 13;

Indeksene løper fra null, slik at høyeste lovlige indeks her er 999. Hvis man går utenfor denne grensen, vil programmet kræsje.

Eksempel 5: Skalaer

De forsøkene vi har gjort med algoritmisk komposisjon har brukt en kromatisk skala, slik at heltallene som genereres angir et halvtonetrinn direkte. For å spille i en annen skala, kan vi bruke en tabell.

En c-dur-skala kan vi spesifisere slik:

  int skala[7] = { 0, 2, 4, 5, 7, 9, 11 };

Her er angitt de forskjellige halvtonetrinnene innenfor en oktav (pitchklasser) som utgjør skalaen.

Vi kan nå lage et program som spiller tilfeldige melodier i c-dur. Først finner vi et tilfeldig tall mellom 0 og 15 (dette dekker to oktaver). Deretter beregnes oktaven ved hjelp av heltallsdivisjon med 7. Noten innen oktaven bestemmes ved hjelp av modulo-operatoren '%', som gir resten ved heltallsdivisjon, slik at f.eks. 9%7=2. Dette siste tallet indekserer så en av de 7 pitchklassene i skalatabellen.

Dessuten blir alle C'er aksentuert med 10 dB ekstra styrke.

#include <stdio.h>
#include <math.h>
#include "notelib.h"

int skala[7] = { 0, 2, 4, 5, 7, 9, 11 };

void main(void)
{
  int i, tall;
  notetype cnote;

  for (i=0; i<50; i++) {
    tall=rand()*16/32768;
    cnote.instrument = 2;
    cnote.starttid = i/5.;
    cnote.varighet = 0.3;
    cnote.oktav = tall/7+6;
    cnote.pitchklasse = skala[tall%7];
    if (cnote.pitchklasse==0) cnote.styrke = 90; else cnote.styrke=80;
    skrivnote(cnote);
  }
}

Eksempel 6: Serielle operasjoner

For å behandle rekker av noter, kan vi lage en tabell som inneholder note-objekter. Eksemplet under lager en tilfeldig tolvtonerekke. Deretter spilles rekken baklengs, og til slutt med inverterte pitchklasser.

#include <stdio.h>
#include <math.h>
#include "notelib.h"

notetype rekke[8];

void main(void)
{
  int i, tall;

  /* Lag og spill rekken */
  for (i=0; i<8; i++) {
    tall=rand()*16/32768;
    rekke[i].instrument = 2;
    rekke[i].starttid = i/4.;
    rekke[i].varighet = 0.3;
    rekke[i].oktav = tall/12+6;
    rekke[i].pitchklasse = tall%12;
    rekke[i].styrke=80;
    skrivnote(rekke[i]);
  }

  /* Spill rekken baklengs */
  for (i=0; i<8; i++) {
    rekke[7-i].starttid = 2.5 + i/4.;
    skrivnote(rekke[7-i]);
  }

  /* Spill rekken invertert */
  for (i=0; i<8; i++) {
    rekke[i].starttid = 5 + i/4.;
    rekke[i].pitchklasse = 11-rekke[i].pitchklasse;
    skrivnote(rekke[i]);
  }
}

Oppgaver

1

Endre programmet i eksempel 5 til å spille pentatont.

2

Lag en funksjon som tar et noteobjekt og et intervall (heltall) som parametre, og returnerer en transponerert note. Det blir endel fikling fram og tilbake med oktaver, pitchklasser og halvtonetrinn - det hadde kanskje vært lurere å bruke noteobjekter som representerer pitch med bare et heltall fra 0 til 84?

3

(Krevende)

Lag et program hvor du legger inn en kort melodi som heltall i en tabell, f.eks. kan vi representere "Lisa gikk til skolen" med tallsekvensen 60, 62, 64, 65, 67, 67, 69, 69, 69, 69, 67 .... Vi sier at alle notene er like lange.

Spill av denne melodien mange ganger, men bytt om to av notene (tilfeldig valgt) hver gang, slik at melodien blir mer og mer (per)mutert. Å bytte om to tall i en tabell krever litt tenking.


next up previous contents
Next: Filbehandling Up: C-programmering for musikere Previous: Algoritmisk komposisjon med CSound

\yvind Hammer
Tue Oct 1 17:56:30 MDT 1996