Dr.Godfried-Willem RAES
Kursus Experimentele Muziek: Boekdeel 1: Algoritmisch Komponeren
Hogeschool Gent - Departement Muziek en Drama
1120:
REAL-TIME: INTERAKTIEVE PROGRAMMAS
Tot nu toe hebben we de komputer eigenlijk alleen gebruikt als rekenmachine en als bron van informatie voor de aangesloten MIDI- of andere instrumenten. Het MIDI-systeem kan evenwel ook omgekeerd worden gebruikt en wel zo dat datgene wat op een MIDI-instrument wordt gespeeld, door de komputer kan worden ontvangen.
Daartoe moet de komputer dus de data afkomstig van zijn MIDI-IN poort (de kabel moet uiteraard aangesloten worden...) lezen.
We gaan -eigenlijk in eerste plaats om historische redenen- eerst in op enkele programmavoorbeelden uitgewerkt voor de Atari ST. Doordat het midi-interface op deze machine immers als eerste een in tegrerend bestanddeel uitmaakte van het ontwerp, werd het meteen ook in de Basic implementaties ervoor geintegreerd. Pas in het volgende hoofdstukje gaan we nader in op interaktieve midi-software voor de IBM-achtige PC's.
Op het eerste gezicht lijkt het lezen van binnenstromende midi-informatie wellicht erg eenvoudig. We zouden het bvb. kunnen proberen met volgende subroutine:
DO
'eerste poging om bytes in te lezen op Atari
BYTE = INP(3)
'volgende zin gaat na of het gelezen byte een status is dan wel een noot of attack:
IF BYTE > 127 THEN STATUS = BYTE
'als het een status byte was tel dan vanaf nu met de teller-variabele I:
IF BYTE > 127 THEN I = 1
'als vorige byte status was, dan komt er nu een noot-getal
IF I = 1 AND BYTE < 128 THEN NOOT = BYTE
'als het een noot was tel 1 bij de teller I:
IF I = 1 AND BYTE < 128 THEN I = I + 1
'als I=2 dan moet het byte het aanslagbyte zijn:
IF I = 2 AND BYTE < 128 THEN VEL=BYTE
'in dit geval resetten we de I-teller naar nul:
IF I = 2 AND BYTE < 128 THEN I=0
'hier komt een verwijzingeroutine waarin we iets doen met de gegevens STATUS,NOOT en VEL
GOSUB VERWERK_BYTES
LOOP
Hoewel dit programma wel degelijk funktioneert zal het toch niet bruikbaar blijken te zijn. Het zal immers alsmaar door bytes vanaf de MIDI-poort inlezen, ook wanneer er helemaal geen beschikbaar zijn. Wat we dus eigenlijk moeten doen, is alleen dan bytes inlezen wanneer er nieuwe bytes op de poort worden aangeboden ! Willen we ook rekening kunnen houden met de metrische struktuur van het ingespeelde, dan zullen we bovendien ook nog een timing- routine moeten inbouwen, zo dat elk nieuw ingelezen byte 'gehecht' wordt aan een variabele waarin de tijdspositie wordt opgeslagen.
Om de komputer toe te laten slechts dan een nieuwe byte in te lezen wanneer er ook werkelijk een op een ingangspoort wordt ingeboden, gebruikt de Atari 1040 ST een logische funktie, nml. INP(-3) . Het cijfer 3 slaat hier op het adres dat de midi-poort in het besturingssysteem van deze machine toegewezen kreeg, terwijl het minteken de funktie tot logische funktie maakt. INP(-3) is dan ook waar (niet 0) of vals (0) al naargelang er al dan niet een byte ter inlezing op poort nummer 3 beschikbaar is.
Volgende routine demonstreert dit overduidelijk :
WHILE INP(-3)
PRINT INP(3)
WEND
Probeer je dit uit, dan zal je echter merken dat de cijfers die, mits je op het midi-klavier enkele toetsen indrukt, op het scherm verschijnen, negatieve getallen zijn! Dit is ook weer een eigenschap van het Atari besturingssysteem, waardoor eenvoudig in de software kan worden nagegaan of een byte uitgaand dan wel inkomend is. Om het naar de ons stilaan bekende midi-codes om te zetten volstaat het, bij het ingelezen getal 256 op te tellen. In een basic-instruktie wordt dit dus :
MIDIBYTE = 256 + INP(3)
Een programma dat alle door een Midi-klavier uitgezonden bytes in een array opslaat, kan er dan ook uitzien alsvolgt :
DIM N(500)
DO
WHILE INP(-3)
N(I) = 256 + INP(3)
IF I < 501 THEN I = I + 1 ELSE I = 0
WEND
LOOP
Dit programma heeft echter nog steeds vele gebreken. Vooreerst houdt het geen rekening met de ritmische struktuur van hetgeen werd ingespeeld, en voorts is het niet in staat de ontvangen gegevens onder te verdelen in status-informatie, controle-codes, notenwaarden enzomeer.
Het probleem van de tijdsstruktuur kan worden opgelost door de inleesroutine van een teller te voorzien, waarmee de tijdsafstand tussen de ontvangen bytes kan worden opgeslagen. Hiertoe zouden we vorig programma kunnen aanpassen, door het array tweedimensioneel te maken. In het volgende programmavoorbeeld werd dit gedaan :
Atari 1040ST programma op archiefdisk onder de naam LES12P1.BAS
DEFINT A,Z
DIM N(1,500)
De X dimensie in N(x,y) wordt gebruikt voor opslag van midi-bytes wanneer X=0. Detijdswaarden staan erin wanneer X=1
CLEARW2 : FULLW2
I=0 : T=0
MIDI_LEES:
WHILE INP(-3)
IF I<501 THEN N(1,I)=T ELSE GOTO UITVOER
T=0
IF I<500 THEN I=I+1 ELSE GOTO UITVOER
N(0,I)=256+INP(3)
WEND
T=T+1
IF T > 100000 THEN END
GOTO MIDI_LEES
UITVOER:
FOR I=0 TO 500
OUT 3,N(0,I)
IF N(1,I)<10 THEN N(1,I)=0
FOR J=0 TO N(1,I)*20 : NEXT J
NEXT I
I=0
GOTO MIDI_LEES:
Dit programma neemt al het op het klavier gespeelde op en vanzodra het geheugen (bepaald door de afmetingen van het array) volgelopen is, speelt het al het opgenomene opnieuw af. Het blijft dit ononderbroken doen , zolang we maar op het klavier blijven spelen. Spelen we een tijdje niks, dan bereikt de tijdteller de waarde 10000 en valt het programma stil. De programmaregel waar staat IF N(1,I)<10 THEN N(1,I)=0 is een filter waarmee akkoorden waarvoor niet alle toetsen precies gelijk werden ingedrukt, toch netjes en precies tesamen worden weergegeven.
We kwamen hiermee dus tegemoet aan het eerste bezwaar tegen onze eerdere versie van het programma. Het tweede bezwaar, nml. dat het programma nog niet in staat is de betekenis van de diverse midi-codes te achterhalen is ook relatief eenvoudig op te lossen. Wanneer we over een voldoende snelle komputer beschikken dan klasseren we de gegevens onmiddelijk wanneer ze door de komputer worden ontvangen. Immers , wanneer onze komputer traag zou zijn, lopen we het risico dat hij ingekomen bytes mist terwijl hij nog met de verwerking van de vorige doende is. Bij trage komputers kunnen we dan ook beter verzaken aan de optie om alles in 'real-time' te laten verlopen, en lezen we al het gewenste in in een array, waarop we onze klassifikatieroutines pas achteraf loslaten. De zwaarste hypotheek die echter op deze methode rust, is dat we daarvoor behoorlijk wat geheugen nodig hebben, iets wat nu uitgerekend op kleine en trage machines ook al niet in overvloed voorhanden is. We zullen verder echter veronderstellen dat onze machine, en de taal die we erop gebruiken, snel genoeg zijn.
Atari 1040ST programma op archiefdisk onder de naam LES12P2.BAS
INITIALIZE:
DEFINT A,Z
CLEARW2 : FULLW2
DIM N(3,100) : ' geheugen voor 100 noten vrijmaken
I=0 : T=0
*GOSUB NOOTNAAM:
De volgende routine leest en wist alle nog in de input-buffer aanwezige bytes .:
WHILE INP(-3)
B=INP(3)
WEND
B=0
De volgende routine zoekt in de data afkomstig van het keyboard het eerstvoorkomende byte voor note-on or off op kanaal 0. Pas als dat gevonden is gaat het programma verder. Je zal dus bij voorkeur eerst reset op het keyboard moeten uitvoeren ofwel even een of andere registerknop indrukken. Hierdoor immers wordt de eerstvolgende ingedrukte toets zeker uitgestuurd als een sekwens die begint met 144.
SYNC:
WHILE INP(-3)
B=INP(3) +256
IF B=144 THEN GOTO START:
WEND
GOTO SYNC:
START:
N(0,I)=T : N(3,I)=144
WHILE I <100
MIDILEES:
C=0
NOOTLEES:
WHILE INP(-3)
B=INP(3) + 256
IF B<128 THEN N(1,I)=B ELSE Z=1
C=1
IF C THEN GOTO VELOLEES:
WEND
IF Z=1 GOTO UITVOER:
D=0
VELOLEES:
WHILE INP(-3)
B=INP(3) + 256
IF B<128 THEN N(2,I)=B ELSE Z=1
D=1
IF D THEN GOTO VOLG:
WEND
IF Z=1 GOTO UITVOER:
VOLG:
D=0 : C=0 : E=0 : T=0
IF I<100 THEN I=I+1 ELSE GOTO UITVOER:
'de tijdsteller zit in de volgende lus verborgen
WHILE NOT INP(-3)
T=T+1
WEND
N(0,I)=T
WHILE INP(-3)
B=INP(3)+256
E=1
IF B=144 THEN N(3,I)=B
IF B=144 THEN GOTO NOOTLEES:
IF B<128 THEN N(1,I)=B
IF B<128 THEN GOTO VELOLEES:
IF E THEN GOTO VOLG:
WEND
WEND
UITVOER:
I=0
Z$=" S T O R I N G ! - Begin opnieuw en reset keyboard ... "
A$="Noot nummer =" : B$=" na " : C$=" tellen. De aanslag was = "
IF Z=1 THEN PRINT Z$
IF Z=1 THEN SOUND 1,0,0,0,50
IF Z=1 THEN END
PRINT " Gespeelde melodie : "
WHILE I<101
PRINT A$;N(1,I);B$;N(0,I);C$;N(2,I)
I=I+1
SOUND 1,0,0,0,20
WEND
Ter afwisseling gaat de uitvoer van het programma hier nu eens niet naar een midi-instrument, maar naar het scherm! Wanneer we deze uitvoerroutine daartoe geschikt maken middels grafische instrukties, kunnen we het op deze wijze zelfs de partituur laten schrijven van wat op het toetsenbord werd gespeeld. De grafische instrukties die daarvoor op elke moderne komputer ter beschikking staan, zijn evenwel geenszins gestandaardiseerd en dermate machineafhankelijk, dat we ze slechts in het kader van onze hoofdstukken over komputermuzieknotatie (5000 e.v.) binnen bestaande toepassingssoftware zullen behandelen.
Een simpele routine , geschreven in QB4.0, waarmee midinootgetallen kunnen omgezet worden naar meer gebruikelijke nootbenamingen van de vorm C#6, D_2, Fb0 enzomeer volgt hier voor de geinteresseerden:
NOOTNAAM:
'Het volgende array bevat in volgorde van de overeenkomstige 'midi-getallen, de namen van de noten.
DIM NM$(128)
FOR I = 0 TO 127
IF I MOD 12 = 0 OR I MOD 12 = 1 THEN NM$(I) = "C"
IF I MOD 12 = 2 THEN NM$(I) = "D"
IF I MOD 12 = 3 OR I MOD 12 = 4 THEN NM$(I) = "E"
IF I MOD 12 = 5 OR I MOD 12 = 6 THEN NM$(I) = "F"
IF I MOD 12 = 7 OR I MOD 12 = 8 THEN NM$(I) = "G"
IF I MOD 12 = 9 THEN NM$(I) = "A"
IF I MOD 12 = 10 OR I MOD 12 = 11 THEN NM$(I) = "B"
NEXT I
FOR I = 0 TO 127
TWEE:
IF I MOD 12 = 3 OR I MOD 12 = 10 THEN
NM$(I) = NM$(I) + "b"
ELSE
IF I MOD 12 = 1 OR I MOD 12 = 6 OR I MOD 12 = 8 THEN
NM$(I) = NM$(I) + "#"
ELSE
NM$(I) = NM$(I) + "_"
END IF
END IF
NEXT I
FOR I = 0 TO 127
NM$(I) = NM$(I) + CHR$(48 + (I \ 12))
NEXT I
RETURN
Wil je dit gebruiken in het vorige programma dan moet je het statement met * gemarkeerd in de initializatie-routine intypen en verder de uitvoerroutine alsvolgt aanpassen :
WHILE I<101
PRINT NM$(N(1,I));B$;N(0,I);C$;N(2,I)
I=I+1
SOUND 1,0,0,0,20
WEND
Naar inhoudtafel kursus: <Index-kursus>
Naar homepage dr.Godfried-Willem RAES