Dr.Godfried-Willem RAES

Kursus Experimentele Muziek: Boekdeel 1: Algoritmische Kompositie

Hogeschool Gent - Departement Muziek en Drama


Naar inhoudstafel

1160:

FILE I/O PROGRAMMERING

1.Sekwentiele bestanden

In alle programmas die we tot hiertoe beschouwden en schreven, waren alle gegevens waarmee het programma werkte ofwel opgenomen in het programma zelf (hetzij als gewone konstanten, hetzij als data-statements), ofwel afkomstig van externe invoer: het toetsenbord, de muis, een midi-klavier.

Iedere keer we zo'n programma laten lopen, begint alles van voorafaan. Speelden we bijvoorbeeld een reeks noten op het klavier en sloegen we die op als een array met midi-kodes, dan konden we die reeks weliswaar tot in den treure opnieuw laten spelen en herhalen, maar, schakelden we de machine uit, of werden we tot een reset gedwongen, dan waren we definitief al onze gegevens en kodes kwijt. Om aan dit grote bezwaar tegemoet te komen, voorziet elke komputertaal, en dus ook elke Basic versie, in een of andere vorm van meer permanente gegevensopslag. In eerste-generatie eenvoudige home-computers, en zelfs bij de allereerste echte IBM-PC's, was daarom voorzien in een 'cassette-I/O'-poort. Dit liet ons immers toe een reeks bytes - en dus eender welke data-reeks - permanent op magneetbandje op te slaan, en uiteraard ook achteraf opnieuw in te lezen. Een van de grootste nadelen verbonden aan magneetband als opslagmedium, afgezien nog van hun bijzonder lage snelheid, is echter dat gegevens alleen sekwentieel kunnen worden opgeslagen en ingelezen. Willen we immers gegevens die zich op het einde van de band bevinden, inlezen, dan zijn we verplicht het gehele bandje af te spelen tot de komputer aan de plek is gekomen waar de gezochte gegevens werden geregistreerd. Dit bezwaar treft ook de modernere DAT digitale backup tapes en alle andere tape-streamers.

Een op deze wijze opgebouwd bestand noemt men dus sekwentieel, en kan worden vergelijken met een één-dimensioneel array. Het zal duidelijk zijn dat het bij dergelijk bestand niet mogelijk is ook maar een enkel gegeven achteraf in te voegen of te wijzigen, zonder dat de gehele data-sekwens herschreven moet worden. Elk gegeven in een sekwentieel bestand kan een verschillende lengte hebben. Een string neemt een veranderlijk aantal bytes (1 per letter) in beslag dan een integer-getal (2 bytes). Aangezien alle bytes onmiddellijk achter elkaar worden weggeschreven, is het belangrijk bij sekwentiele bestanden een 'afscheidingsteken' (delimiter) af te spreken. In de meeste Basic versies geldt de komma als afscheidingsteken tussen de verschillende gegevens van een sekwentiele file. Een gegeven wordt dan voor de komputer de gehele reeks bytes die zich tussen twee kommas bevindt. Ook de ASCII code voor 'carriage-return' kan als delimiter worden gebruikt.

Ook wanneer de komputer met de veel snellere disk-drives, CD spelers en hard-disks is uitgerust, bestaat deze vorm van bestandsopslag.

Om disk of tape als opslagmedium voor data te gebruiken moeten we het besturingssysteem (OS of operating system) van de komputer terzake goed kennen. Immers, elke file (engels voor 'bestand') dient een naam te krijgen aangezien er meerdere op een disk kunnen voorkomen. Aan die naamgeving worden door het besturingssysteem beperkingen gesteld die niet door welkdanige Basic-versie of enige andere programmeertaal ook kunnen worden opgeheven. Zo geldt bijvoorbeeld zowel voor de Atari als voor alle IBM- achtigen dat de naam van een bestand moet bestaan uit minstens 1 tot hoogstens 8 letters of cijfers, eventueel gevolgd door een punt en een 'extentie' bestaande uit nog eens maximaal drie letters of cijfers. Geldige namen voor bestanden op deze systemen zijn dan ook bijvoorbeeld:

Sedert de introduktie van Windows95 in kombinatie met DOS7.0 als operating system op IBM-actigen (tegenwoordig veelal ‘Wintel’ machines genoemd) zijn heel beperkingen in de naamgeving van bestanden weggevallen. Net zoals vroeger reeds gebruikelijk was op Apple-machines, zijn nu ook lange bestandsnamen (en dus deskriptieve benamingen) mogelijk geworden. Deze lange bestandsnamen zijn echter NIET downwards kompatibel. Gebleven in in elk geval -gelukkig maar- de gewoonte om filenamen van extenties te voorzien. Daaraan kunnen we (en ook de PC zelf...) weten om welk soort bestand het gaat en met welk programma het kan worden geopend en bewerkt.

De namen voor bestanden mogen echter niet die zijn die door het besturingssysteen ook voor 'devices' gebruikt worden. Niet toegelaten zijn dan ook:

Voorts zijn er nog enkele min of meer algemene regels met betrekking tot de toelaatbare of aanbevelenswaardige extenties. Immers bepaalde extenties hebben heel specifieke betekenissen voor bepaalde DOS-versies (disk- besturingssysteem of 'Disk Operating Systems') :

Op IBM-machines vermijdt je dan ook best volgende extenties, tenzij het gaat om bestanden van het ermee overeenkomende type:

Op AtariST zijn o.m. volgende filenaam-extenties gereserveerd:

Een laatste beperking, maar wel een die wanneer ze wordt overtreden soms desastreuse gevolgen kan hebben, is dat een filenaam niet reeds op het betreffende opslagmedium (disk) mag voorkomen. Wanneer dat immers wel het geval is, bestaat het gevaar dat de reeds bestaande file ongewild door de nieuwe wordt overschreven. De oude is in zo'n geval niet meer de redden... (ook niet met UNDELETE!)

Willen we binnen Basic kunnen nagaan wat er op een bepaald opslagmedium aan bestanden of files te vinden is dan hebben we de beschikking over het statement

FILES

dat ons een inhoudstafel van alle files op een disk op het scherm afdrukt. Er zijn, zoals steeds weer, tal van varianten zoals FILES A: , waarbij de te lezen disk-drive moet worden opgegeven, of FILES A:\BASIC\*.* waarbij ( meer voor hard-disks) ook het subdirectory opgegeven moet worden. In GFA-Basic werken volgende instrukties :

DIR "A:<filenaam>"

FILES "A:\*.DTA"

FILES "A:\*.*" TO "LST:"

Een file wissen kan veelal met een van volgende statements gebeuren :

KILL <filenaam>

ERASE <filenaam>

DELETE <filenaam>

Een file herbenoemen kan alsvolgt :

NAME <filenaam> AS <filenaam> GFA

RENAME <filenaam> AS <filenaam>

Binnen QBX V7.1, PowerBasic en Visual Basic kunnen alle dos funkties ook vanuit Basic aangeroepen worden via:

SHELL "doskommando"

bvb.: SHELL "DIR C:\bc7\kompos\prg\*.BAS > BASDIR.FIL"

Binnen de interpreter loop je wel gauw vast tegen de grens van het beschikbaar geheugen in het onderste 640kbyte segment, maar eens gekompileerd is het een heel goede metode.

Willen we binnen Basic een permanent bestand aanleggen voor gegevensopslag, dan beschikken we daartoe over volgende instrukties. Voor de variabele <filenaam> gelden alle hierboven beschreven beperkingen!

QB_V4.5, QBX_V7.1 -IBM:

OPEN <filenaam> FOR OUTPUT AS#X

X is een geheel getal

OPEN <filenaam> FOR INPUT AS#X

OPEN <filenaam> FOR APPEND AS#X

hiermee kan data aan een bestaande file worden toegevoegd.

(Wanneer de file niet reeds bestond wordt hij aangemaakt).

PRINT #1,<numeriek gegeven>;

PRINT #1,<stringvariabele of konstante>;

WRITE #1

CLOSE #1

GFA-Basic Atari ST:

OPEN "O",#1,<filenaam>

OUT#1,X ! X moet een byte zijn

PRINT #1,"Bijvoorbeeld deze zin"

PRINT #1, 986743 ; &H678 ; 90

CLOSE #1

het bestaan van de zo geopende file kan hier getest worden met volgend statement:

PRINT EXIST(<filenaam>)

Terug inlezen van weggeschreven gegevens kan alsvolgt :

OPEN "I",#1,<filenaam>

WHILE NOT EOF#1

A=INP(#1)

PRINT A

WEND

CLOSE #1

In verband hiermee verdient het aanbeveling goed de mogelijkheden van instrukties zoals PRINT.. , PRINT USING, WRITE, READ, INPUT #, LINE INPUT, INPUT$, STORE , RECALL , SEEK, RELSEEK enzomeer na te zien. De meeste van deze instrukties kunnen immers ook voor file I/O gebruikt worden. Er zijn ook equivalenten beschikbaar onder Win32Api, bruikbaar binnen PowerBasic en C++.

Ook het als bestand opslaan van een deel van het komputergeheugen is mogelijk met :

BSAVE <filenaam>,adres,lengte

Het adres moet het eerste adres zijn van het geheugengebied dat men wil wegschrijven, en lengte dient de lengte te zijn van de file uitgedrukt in aantal bytes. Uiteraard kunnen we het op die manier opgeslagen geheugengebied opnieuw in het geheugen plaatsen met:

BLOAD <filenaam>,adres

waarbij adres, indien het wordt opgegeven, een ander adres mag zijn dan dat waar de data zich eerst bevonden. Deze opslagmetode werkt niet meer op Windows 98 machines en recenter.

2.RANDOM-ACCESS FILES

Was er bij sekwentiele bestanden of files sprake van een lange lijst gegevens (bytes, data) in een vaste volgorde, dan krijgen we hier te maken met een werkelijk vrij toegankelijk gegevensbestand. Random-access files komen volledig overeen met wat men in komputerterminologie een database noemt. Men kan zich dit voorstellen als een kaartenbak waarbij voor elke rubriek voorgedrukte (programmeerbare, maar eens vastgelegd, niet meer zomaar te wijzigen) vakjes van vastgestelde lengte zijn voorzien. Elk vakje noemt men een veld (field), en de velden hebben een volgnummer (zoals eerste vakje, tweede vakje, 3e vakje ...). Een volledige 'steekkaart' met 'vakjes' (fields) noemt men een 'record' of een dataregel. Ook de records zijn genummerd.

Het is makkelijk in te zien dat gegevensopslag in een dergelijk vakjes- systeem weliswaar het zoeken kan vergemakkelijken, maar dat het anderzijds heel wat meer opslagruimte opeist! Immers wanneer in een bepaald veld minder bytes worden geplaatst dan erin kunnen, dan kunnen deze lege plaatsen niet voor iets anders worden gebruikt.

De in GFA in verband met random access files geldende instrukties en statements zijn:

OPEN "R",#1,<filenaam> opent een random access file

FIELD veldnummer,veldlengte AS <veldnaam> legt een veldenstruktuur aan ('drukt' de vakjes)

GET #1, record-nummer lees een record

PUT #1, record-nummer schrijft een record op een plaats in het bestand weg.

CLOSE #1

Op te merken valt, dat het inrichten van een random access file met velden binnen elk record optioneel is, en geenszins verplicht.

In QuickBasic QB4.5 en QBX verloopt een en ander nagenoeg identiek :

OPEN <filenaam> ACCESS READ WRITE AS #X LEN=Y

OPEN <filenaam> ACCESS READ AS #X

OPEN <filenaam> ACCESS WRITE AS #X LEN=Y

FIELD #X, n AS A$, m AS B$, p AS C$

GET #X,Record-Nummer

PUT #X,Record-Nummer

Het getal Y staat voor de gewenste lengte in bytes van elk record in de file. Indien LEN=Y wordt weggelaten dan stelt de machine deze parameter in als LEN= 128 bytes per record.

Opgemerkt dient tot slot nog te worden, dat random access files op eenvoudige en veelal disk-loze homecomputers niet voorhanden zijn. Wie het als een al te groot gemis aanvoelt, zal enig kunst- en vliegwerk moeten uitvoeren door bvb. een array als random-access file te structureren, en dan verder een beroep te doen (via VARPTR enzomeer) op BSAVE en BLOAD voor permanente opslag. De eerder beperkte geheugencapaciteit , waardoor het formaat van het array wordt begrensd, zal hier echter nogal vlug de beperkende faktor worden. Dit gemis heeft overigens ook zowat de doodsteek gegeven aan de gehele generatie diskloze home-komputers.

3.Toepassingen

Wie enigszins vertrouwd is met MIDI-apparatuur, zal wel reeds hebben opgemerkt dat vele FM-synthesizers evenals samplers zijn uitgerust met een aansluiting voor een cassetterecorder. Meestal betreft het hier een vijfpolige DIN stekkerbus, die echter geenszins verward mag worden met een MIDI aansluiting. Een vergissing door bvb. MIDI IN of UIT hierop aan te sluiten kan potentieel zelfs ernstige schade toebrengen aan de toestellen. Evenmin kan deze aansluiting worden gebruikt voor geluidsweergave via een versterker. Waar ze dan wel voor dient, is als hulpmiddel om 'patches' , configuraties, kortom allerhande instellingen van het toestel, als sekwentieel en digitaal databestand naar een gewone cassetterecorder weg te schrijven, of om een bepaalde instelling vanaf een data-cassette weer op te slaan. De wijze waarop die opslag op band gebeurt vertoont grote gelijkenissen met de manier waarop eenvoudige home-komputers programmas opslaan op band en ze die vervolgens terug kunnen inlezen. Sekwentiele bestanden op home-komputers verlopen net eender. Wat in werkelijkheid op de band wordt opgenomen zijn niet de diverse 1 en 0 logische signalen zoals de komputer die gebruikt, maar wel het toonhoogte-gekodeerde equivalent daarvan. Zo kan een 0-bit bvb. overeen komen met een kort toontje van 1200Hz , en een 1-bit met een toontje van 2000 Hz. Gestandaardiseerd zijn deze toonhoogtes en hun betekenis echter geenszins. Modems werken overigens net zo.

Midi toestellen die over deze mogelijkheid beschikken, zijn evenwel steeds ook in staat, hun gegevens via hun MIDI-OUT poort, naar een komputer te 'dumpen'. Daartoe moet aan het betreffende MIDI-toestel, middels een speciale system exclusive byte (&HF0) sekwens die vanuit de komputer dient te worden gegeven, het bevel tot dumpen worden gegeven. De komputer moet dan uiteraard zelf instaan voor het openen van een dump-file (een disk-bestand waarin alle ontvangen data kunnen worden weggeschreven) evenals voor het orienteren van de inkomende gegevens naar dit bestand.

Een oud maar klein programmavoorbeeld , geschreven in GFA-Basic, voor de TX81Z van Yamaha moge hier een en ander verduidelijken :

DEFLIST 0

DEFBYT "b"

'OPEN de midi-poort als een lees-bestand

OPEN "I",#2,"MID:"

'OPEN een DUMP-FILE op disk

ODF:

CLS

INPUT "Onder welke naam wil je de file hebben?",N$

IF LEN(N$)<1 OR LEN(N$)>8 THEN

PRINT "Deze naam is te lang !"

GOTO ODF:

ENDIF

N$ = N$ + ".DMP"

IF EXIST N$ THEN

PRINT "Deze file bestaat al ... "

GOTO ODF:

ENDIF

OPEN "O",#1,N$

'INITIALIZE MIDI-DUMP REQUEST - voor TX81 dumpverzoek

INI_MIDUMP:

OUT 3,&HF0

OUT 3,&H43

OUT 3,&H20

OUT 3,&H03

OUT 3,&HF7

dit is volgens het handboek de byte sekwens waarmee de TX81 wordt verzocht alle patches (voice parameters) voor de voice op kanaal 0 , te dumpen.

'READ BYTES AND WRITE TO FILE - lees de data in & save

LEES:

BYTE|=INP(#2)

IF BYTE|=&HF0 THEN : ' wacht op een sys-ex byte

OUT(#1,BYTE|)

GOTO BULKSTART:

ELSE

GOTO LEES:

ENDIF

BULKSTART:

WHILE (NOT EOF(#2)) OR (NOT BYTE|=&HF7)

BYTE|=INP(#2)

OUT(#1,BYTE|)

WEND

CLOSE #1

CLOSE #2

terugsturen van deze file naar de TX81Z-synthesiser kan dan met :

OPEN "I", #1,N$

OPEN "O", #2,"MID:"

WHILE NOT EOF#1

BYTE|=INP(#1)

OUT(#2,BYTE|)

WEND

CLOSE#1

CLOSE#2

De stringvariabele N$ dient uiteraard wel gedefinieerd te zijn op voorhand, en een geldige filenaam te bevatten.

Merk op dat we hier de midi-poort eveneens als een sekwentiele file hebben behandeld. Dit is toegelaten voor alle zgn. 'devices'. Merk verder op dat we aan het begin van het programma ervoor zorgden dat de variabele BYTE nooit iets anders dan een byte kan zijn.

Hoewel bovenstaande routines op zich reeds een zeker praktisch nut kunnen hebben voor opslag van allerlei instellingen, is het toch pas wanneer de we komputer inzetten om in deze opgeslagen data wijzigingen aan te brengen, dat deze hele procedure goed tot haar recht kan komen.

Voorbeelden van het gebruik van (binaire) random-access files voor de opslag van op algoritmische wijze tot stand gekomen muziekbestanden zijn terug te vinden in bijna alle verder behandelde programmas door mij geschreven. Deze muziekbestanden worden immers opgevat als binaire files en vormen een kwazi letterlijke neerslag (opslag) van de 2-dimensionele arrays waarmee de muziekstukken eerst in het geheugen van de komputer worden opgeslagen.


Filedate: 840327 - last update:2003-11-25

Terug naar inhoudstafel kursus: <Index kursus>

Naar homepage Dr.Godfried-Willem Raes