Vandaag heb ik wat hobby projectjes geschreven in QBasic afgewerkt en geupload. Wie dat leuk lijkt kan de code hier vinden: https://github.com/PeterSwinkels/QBasic-font-demos
Vragen en opmerkingen zijn welkom, zolang het over QBasic gaat en het leuk blijft. 
benleentje
Golden Member
Vroeger nooit toegekomen aan Qbasic maar als dat zo terug lees lijkt het toch wel sterk op de latere basic.
Ik zie dan wel weer peek en poke staan wat ik nogvan de C64 ken. Is dat wat we nu een pointer zouden noemen. Maar pointers begrijp ik nog steeds niet maar wat poke doet begrijp ik dan weer wel.
POKE Target, 0Als target dan bv 1000 is. Dan is deze regel toch schrijf naar het adress 1000 de waarde 0.
Waarom zijn ze daarvan afgestapt naar iets wat ik niet meer begrijp?
Ja, maar moderne basics verschillen op veel manieren van QBasic, zoals je zelf aangeeft kent QBasic geen echte pointers in de moderne zin.
Als je nog vragen of opmerkingen hebt over mijn programmatjes waar ik naar gelinkt heb dan hoor ik ze graag! 
SparkyGSX
Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
@benleentje: omdat pointers veel meer zijn dan "lees/schrijf op adres x".
Met pointers kun je bijvoorbeeld een verwijzing naar een groot stuk data (zoals een array, structure, array van structures, complete inhoud van een file, etc.) doorgeven, zonder er een kopie van te maken. Je kunt zelfs een verwijzing doorgeven naar iets waarvan je niet eens weet wat het voorstelt, als je daarbij maar doorgeeft hoe lang het is. Dat doe je bijvoorbeeld bij memcopy, en als je data gaat versturen over een netwerkverbinding of opslaan in een file. De ontvanger van die data moet begrijpen wat het is, maar alles wat daar tussenin zit niet.
benleentje
Golden Member
Ok dus pointer is een een door ontwikkeling en kan dus meer dan poke en peek.
Ik moet ook toegeven dat ik pointers ook bewust een beetje gemeden hebt omdat ik dat nog niet nodig heb gehad voor een arduino te programmeren.
Maar vroeger dacht ik dat Qbasis moeilijk was en ben er pas met de arduino aan C++ begonnen. En dan blijkt als je er eenmaal aan begint het best meevalt. Maar dat was ook pas voor het eerst dat ik ook echt een toepassing had waarin ik programmeren leuk vond. Ik bedoel software voor een pc of computer vind ik niet leuk is te droog voor mij maar als er eenmaal iets gaat bewegen en tot leven komt vind ik het wel leuk.
SparkyGSX
Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
Nou, niet helemaal, pointers zijn een zeer fundamenteel onderdeel van een CPU. Peek en poke zijn zeer beperkte implementaties van wat er met pointers mogelijk is. Er bestaan ook pointers naar code (function pointers), die o.a. worden gebruikt voor interrupt vectors en zo.
Voor "Arduino code" heb je ze nauwelijks nodig, maar als je low-level code schrijft, voor datacommunicatie, of DMA transfers en zo, gebruik je ze veel meer.
Arco
Special Member
Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com
QBasic zelf heeft eigenlijk geen echte ondersteuning voor verschillende lettertypes zoals bijvoorbeeld Visual Basic in Windows, een van de redenen dat ik deze programmaatjes gemaakt heb is om te kijken wat er mogelijk was om zelf een klein beetje variatie toe te voegen aan wat QBasic standaard aanbiedt.
Een van de programma's leest karakterinformatie direct uit het ROM geheugen uit zodat het programma meer invloed heeft op de karakters op het scherm worden getekend.
De ander maakt gebruik van een array met allemaal bitmapjes die met behulp van de grafische PUT opdracht op het scherm kunnen worden getekend.
Hieruit blijkt dat QBasic veel dingen die een modernere taal standaard heeft zelf niet direct aanbiedt maar dat er toch wel veel mogelijk is met een beetje vindingrijkheid. 
Ik heb geprobeerd om iets dat pointers emuleert te maken in QBasic:
DEFINT A-Z
DECLARE SUB ListPointers (Pointers() AS INTEGER)
DIM Buffer AS STRING * 5
DIM Pointers(0 TO 4) AS INTEGER
SCREEN 0
CLS
DEF SEG = VARSEG(Buffer)
FOR Index = LBOUND(Pointers) TO UBOUND(Pointers)
Pointers(Index) = VARPTR(Buffer) + Index
NEXT Index
Letter = 1
FOR Index = LBOUND(Pointers) TO UBOUND(Pointers)
POKE Pointers(Index), ASC("@") + Letter
Letter = Letter + 1
NEXT Index
PRINT "Oorspronkelijke inhoud:"
ListPointers Pointers()
PRINT
PRINT "(Een waarde wordt gewijzigd.)"
PRINT
DEF SEG = VARSEG(Buffer)
POKE Pointers(3), ASC("Z")
PRINT "Na wijzigen:"
ListPointers Pointers()
SUB ListPointers (Pointers() AS INTEGER)
FOR Index = LBOUND(Pointers) TO UBOUND(Pointers)
PRINT "Pointer:"; Index + 1; "= "; CHR$(34); CHR$(PEEK(Pointers(Index))); CHR$(34)
NEXT Index
END SUB
Dit is een eerste grove schets, constructieve feedback is welkom.
SparkyGSX
Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
Mijn grootste vraag is eigenlijk: waarom? Dit zou mogelijk nuttig zijn geweest, ongeveer 40 jaar geleden. Kun je niet beter een modernere taal leren waar je nog iets aan hebt? Python bijvoorbeeld, of C voor microcontrollers.
Ik heb alleen gewerkt met de basic die in de Acorn Atom zat..En daar ging ik ook al snel over naar assembler...
Dingen hoewel verouderd kunnen nog steeds leuk, interessant en leerzaam zijn. QBasic en soortgelijke oude talen kunnen mensen nog veel leren over hoe het allemaal begon en het kan leuk zijn om terug te kijken naar dingen van vroeger.
Assembler is een geweldige manier om stukjes low-level code aan je QBasic programma's toe te voegen. Wie heeft er wel eens met CALL ABSOLUTE gewerkt?
Nibbles is leuk! Wie herinnert er zich nog QBasic Money Mananager?
nonius
set SCE to AUX.
Mijn grootste vraag is eigenlijk: waarom?
Dat was ook mijn vraag, en reden dat ik niet eerder geantwoord had; het is verder weinig constructief en OP vraagt specifiek om feedback over de door hem geschreven software en dat het leuk blijft. Deze discussies ontaarden vaak in 'mijn programmeertaal is beter dan de jouwe en je bent een idioot/genie* (*doorhalen wat niet van toepassing is) omdat je een andere/dezelfde (*) programmeertaal gebruikt als ik.
Ik moet toegeven dat ook ik dingen doe en bouw die verre van rationeel zijn, simpelweg omdat het me voldoening geeft. En zo lijkt het me verdraaid leuk om nog eens een keer een relais-computer te bouwen, en een kleine electromechanische telefooncentrale..... Meer reden dan 'het verschaft me genoegen' is voor een hobby eigenlijk niet nodig, en ik vermoed dat QBasic op de één of andere manier ook voldoening aan OP geeft. Zelf heeft het me toch altijd een beetje verbaasd dat er mensen voor een andere programmeertaal dan C kiezen/kozen (met evt. nog assembly voor het lagere niveau). BASIC en Pascal waren toch eigenlijk altijd meer voor hobbyïsten (en de 'B' in 'BASIC' zegt het eigenlijk ook al gelijk.) Wat uiteraard niet wil zeggen dat je er geen serieuze programma's mee zou kunnen schrijven. Maar als je dan toch zoveel tijd en energie in het leren van iets steekt dan zou ik het zelf in wat nuttigers doen. En ook C heeft een rijke historie 
Zoals ik dacht dat het was waren peek en poke manieren om direct een geheugenplaats uit te lezen, of te schrijven. Eigenlijk op een heel laag niveau direct in de hardware ingrijpen dus, bijna op assembly-niveau bits of bytes veranderen op een geheugenadres. Was ook niet bepaald een elegante manier van programmeren, viel toch ook meer in de categorie van 'hack'. Maar soms kon het niet anders, bijv. voor de C64 poke 53281,0 en poke 53280,0 voor wie het zich nog herinnert
Pas in het C128-BASIC (v.7.0 meen ik) kwamen daar specifieke commando's voor en kon je dus ook in BASIC de schermkleurtjes veranderen.
Overigens, pointers heeft mij destijds (in Turbo-C, ca. 1989) aanvankelijk heel veel moeite gekost, tot op een gegeven moment tijdens het schrijven van programma's plots het kwartje viel en ik sindsdien intuïtief direct wist of ik iets met een gewone variabele of met een pointer moest uitvoeren. Was best wel een vreemde gewaarwording dat het zo plots ineens 'vanzelf' ging, terwijl ik daarvoor altijd diep moest nadenken, en het dan nog vaak fout deed. Sterker nog, begon zelfs de kracht van pointers te waarderen (terwijl ik het eerder alleen maar als 'lastige onhebbelijkheid' van C zag....)Maar het is 25 jaar geleden dat ik er nog iets mee gedaan heb en de kennis/vaardigheid is al lang weer verloren gegaan.
Heb vroeger wel met C64/C128-Basic en GW-Basic gespeeld voor ik 'echt' ging programmeren in (Turbo-)C. Tegen die tijd was ook QBasic in opkomst; als ik me goed herinner zat er een compiler bij QBasic en kon je direct een com/exe-file maken; GW-Basic was, bij mijn weten, altijd alleen maar een interpreter, en dus traag. (bij zojuist nader bestuderen zat er blijkbaar toch geen compiler in QBasic, blijkbaar ben ik dan in de war met een andere BASIC-variant).
RAAF12
Golden Member
In SALT kon dat ook, veel gebruikt destijds. Met een toetsaanslag ging die uitbellen naar het BBS en dan automagies post ophalen en brengen. Ik had geen zin om point te worden bij dat HCC BBS.
SparkyGSX
Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
Ik zal niemand vertellen wat hij of zij mag doen voor de hobby, maar zelf zou ik toch eerder tijd steken in iets wat juist meer mogelijk maakt voor andere projecten, en niet een taal die marginaal iets kan op hardware die zo oud is dat het elk moment de geest kan geven.
Hoe zou je 2 programma's met elkaar laten praten? En als ze op verschillende machines draaien? Met een seriële poort? Waar vind je die nog. En wat nou als ze verder uit elkaar staan?
Als je nou een ESP32 neemt, kun je in C of micropython programmeren, kunnen ze onderling communiceren via wifi of ESP-NOW, een scherm eraan kan ook als je dat leuk vindt, en er zijn nog talloze andere mogelijkheden.
Met QBasic (of in dit geval QuickBASIC) kun je WAVE geluiden afspelen met behulp van EMS en DMA:
==========================================================================
DEFINT A-Z
TYPE DTAStr
Reserved AS STRING * 20
Attribute AS INTEGER
FileTime AS INTEGER
FileDate AS INTEGER
FileSize AS LONG
FileName AS STRING * 13
END TYPE
TYPE RegTypeX
ax AS INTEGER
bx AS INTEGER
cx AS INTEGER
dx AS INTEGER
bp AS INTEGER
si AS INTEGER
di AS INTEGER
flags AS INTEGER
ds AS INTEGER
es AS INTEGER
END TYPE
TYPE SBSettingsStr
BaseAddress AS INTEGER
DMAAddress AS INTEGER
DMAChannel AS INTEGER
DMALength AS INTEGER
DMAPage AS INTEGER
END TYPE
TYPE WaveDataStr
DataID AS STRING * 4
DataSize AS LONG
END TYPE
TYPE WaveFactStr
Padding AS STRING * 2
FactID AS STRING * 4
FactSize AS LONG
FactDataSize AS LONG
END TYPE
TYPE WaveHeaderStr
RIFFID AS STRING * 4
RIFFSize AS LONG
WAVEID AS STRING * 4
FormatID AS STRING * 4
FormatSize AS LONG
Format AS INTEGER
Channels AS INTEGER
Frequency AS LONG
ByteRate AS LONG
Alignment AS INTEGER
BitsPerSample AS INTEGER
END TYPE
TYPE WaveParametersStr
EMSHandle AS INTEGER
Frequency AS LONG
Length AS LONG
END TYPE
TYPE WavePlayerStr
ChunkLength AS LONG
ChunkPageCount AS INTEGER
CurrentPage AS INTEGER
Paused AS INTEGER
Remainder AS LONG
END TYPE
CONST EMSPAGESIZE = &H4000&
DECLARE FUNCTION EMSAllocatePages (PageCount AS INTEGER)
DECLARE FUNCTION EMSError$ (ErrorCode AS INTEGER)
DECLARE FUNCTION EMSFreeHandles ()
DECLARE FUNCTION EMSFreePages ()
DECLARE FUNCTION EMSInstalled ()
DECLARE FUNCTION EMSPageFrameAddress ()
DECLARE FUNCTION FileExists (FileName AS STRING)
DECLARE FUNCTION LoadWave (WaveFile AS STRING, WaveParameters AS WaveParametersStr, EMSPagesReserved AS INTEGER)
DECLARE FUNCTION SBBaseAddress ()
DECLARE FUNCTION SBBytesLeft& (SB AS SBSettingsStr)
DECLARE FUNCTION SBDMAChannel ()
DECLARE FUNCTION SBInitialize (SB AS SBSettingsStr)
DECLARE FUNCTION SBInUse (BytesLeft AS LONG)
DECLARE SUB Delay (Interval AS SINGLE)
DECLARE SUB DisplayVolume (x AS INTEGER, y AS INTEGER, LeftSide AS INTEGER, RightSide AS INTEGER)
DECLARE SUB DMASetChannel (SB AS SBSettingsStr, Enabled AS INTEGER)
DECLARE SUB EMSCopyBaseToEMS (Length AS LONG, SourceSegment AS INTEGER, SourceOffset AS INTEGER, DestinationHandle AS INTEGER, DestinationOffset AS INTEGER, DestinationPage AS INTEGER)
DECLARE SUB EMSDeallocatePages (Handle AS INTEGER)
DECLARE SUB EMSMapPages (PhysicalStart AS INTEGER, LogicalStart AS INTEGER, PageCount AS INTEGER, Handle AS INTEGER)
DECLARE SUB GetShiftStatus (LeftShift AS INTEGER, RightShift AS INTEGER)
DECLARE SUB Initialize ()
DECLARE SUB InterruptX (intnum AS INTEGER, inreg AS RegTypeX, outreg AS RegTypeX)
DECLARE SUB Main (WaveFile AS STRING)
DECLARE SUB Quit (Message AS STRING, WaveParameters AS WaveParametersStr)
DECLARE SUB SBGetVolume (SB AS SBSettingsStr, LeftSide AS INTEGER, RightSide AS INTEGER)
DECLARE SUB SBPause (SB AS SBSettingsStr)
DECLARE SUB SBPlayWave (WaveParameters AS WaveParametersStr, WavePlayer AS WavePlayerStr, SB AS SBSettingsStr)
DECLARE SUB SBResume (SB AS SBSettingsStr)
DECLARE SUB SBSetVolume (SB AS SBSettingsStr, LeftSide AS INTEGER, RightSide AS INTEGER)
DECLARE SUB SBSpeakerOff (SB AS SBSettingsStr)
DECLARE SUB SBSpeakerOn (SB AS SBSettingsStr)
DECLARE SUB SBWriteDSP (SB AS SBSettingsStr, Byte AS INTEGER)
DIM SHARED EMSErrorCode AS INTEGER
Initialize
Main COMMAND$
SUB Delay (Interval AS SINGLE)
DIM StartTime AS SINGLE
StartTime = TIMER
DO
LOOP UNTIL TIMER >= StartTime + Interval OR TIMER <= Interval
END SUB
SUB DisplayVolume (x AS INTEGER, y AS INTEGER, LeftSide AS INTEGER, RightSide AS INTEGER)
LOCATE x, y: PRINT "Volume:"
LOCATE , y
PRINT " Left: ["; STRING$(LeftSide, "*"); SPACE$(&HF - LeftSide); "]"
LOCATE , y
PRINT " Right: ["; STRING$(RightSide, "*"); SPACE$(&HF - RightSide); "]"
END SUB
SUB DMASetChannel (SB AS SBSettingsStr, Enabled AS INTEGER)
OUT &HA, (ABS(Enabled) * &H4) OR SB.DMAChannel
OUT &HB, &H48 OR SB.DMAChannel
OUT &HC, &H0
END SUB
FUNCTION EMSAllocatePages (PageCount AS INTEGER)
DIM Registers AS RegTypeX
Registers.ax = &H4300
Registers.bx = PageCount
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
EMSAllocatePages = Registers.dx
END FUNCTION
SUB EMSCopyBaseToEMS (Length AS LONG, SourceSegment AS INTEGER, SourceOffset AS INTEGER, DestinationHandle AS INTEGER, DestinationOffset AS INTEGER, DestinationPage AS INTEGER)
DIM CopyInformation AS STRING
DIM Registers AS RegTypeX
CopyInformation = MKL$(Length) + CHR$(&H0) + MKI$(&H0) + MKI$(SourceOffset) + MKI$(SourceSegment) + CHR$(&H1) + MKI$(DestinationHandle) + MKI$(DestinationOffset) + MKI$(DestinationPage)
Registers.ax = &H5700
Registers.ds = VARSEG(CopyInformation)
Registers.si = SADD(CopyInformation)
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
END SUB
SUB EMSDeallocatePages (Handle AS INTEGER)
DIM Registers AS RegTypeX
Registers.ax = &H4500
Registers.dx = Handle
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
END SUB
FUNCTION EMSError$ (ErrorCode AS INTEGER)
DIM Message AS STRING
DIM Registers AS RegTypeX
SELECT CASE EMSErrorCode
CASE &H0: Message = "No error."
CASE &H80: Message = "Internal error."
CASE &H81: Message = "Hardware malfunction."
CASE &H82: Message = "Busy."
CASE &H83: Message = "Invalid handle."
CASE &H84: Message = "Undefined function requested by application."
CASE &H85: Message = "No more handles available."
CASE &H86: Message = "Error in save or restore of mapping context."
CASE &H87: Message = "Insufficient memory pages in system."
CASE &H88: Message = "Insufficient memory pages available."
CASE &H89: Message = "Zero pages requested."
CASE &H8A: Message = "Invalid logical page number encountered."
CASE &H8B: Message = "Invalid physical page number encountered."
CASE &H8C: Message = "Page-mapping hardware state save area is full."
CASE &H8D: Message = "Save of mapping context failed."
CASE &H8E: Message = "Restore of mapping context failed."
CASE &H8F: Message = "Undefined subfunction."
CASE &H90: Message = "Undefined attribute type."
CASE &H91: Message = "Feature not supported."
CASE &H92: Message = "Successful, but a portion of the source region has been overwritten."
CASE &H93: Message = "Length of source or destination region exceeds length of region allocated to either source or destination handle."
CASE &H94: Message = "Conventional and expanded memory regions overlap."
CASE &H95: Message = "Offset within logical page exceeds size of logical page."
CASE &H96: Message = "Region length exceeds 1 MB"
CASE &H97: Message = "Source and destination EMS regions have same handle and overlap."
CASE &H98: Message = "Memory source or destination type undefined."
CASE &H9A: Message = "Specified alternate map register or DMA register set not supported."
CASE &H9B: Message = "All alternate map register or DMA register sets currently allocated."
CASE &H9C: Message = "Alternate map register or DMA register sets not supported."
CASE &H9D: Message = "Undefined or unallocated alternate map register or DMA register set."
CASE &H9E: Message = "Dedicated DMA channels not supported."
CASE &H9F: Message = "Specified dedicated DMA channel not supported."
CASE &HA0: Message = "No such handle name."
CASE &HA1: Message = "A handle found had no name, or duplicate handle name."
CASE &HA2: Message = "Attempted to wrap around 1 MB conventional address space."
CASE &HA3: Message = "Source array corrupted."
CASE &HA4: Message = "Operating system denied access."
CASE ELSE: Message = "Undefined error: 0x" + HEX$(EMSErrorCode) + "."
END SELECT
EMSError$ = Message
END FUNCTION
FUNCTION EMSFreeHandles
DIM Registers AS RegTypeX
DIM TotalHandles AS INTEGER
DIM UsedHandles AS INTEGER
Registers.ax = &H4B00
InterruptX &H67, Registers, Registers
UsedHandles = Registers.bx
Registers.ax = &H5402
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
TotalHandles = Registers.bx
EMSFreeHandles = TotalHandles - UsedHandles
END FUNCTION
FUNCTION EMSFreePages
DIM Registers AS RegTypeX
Registers.ax = &H4200
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
EMSFreePages = Registers.bx
END FUNCTION
FUNCTION EMSInstalled
DIM EMM AS STRING
DIM Position AS INTEGER
DIM Registers AS RegTypeX
Registers.ax = &H3567
InterruptX &H21, Registers, Registers
DEF SEG = Registers.es
FOR Position = &HA TO &H11
EMM = EMM + CHR$(PEEK(Position))
NEXT Position
EMSInstalled = (EMM = "EMMXXXX0")
END FUNCTION
SUB EMSMapPages (PhysicalStart AS INTEGER, LogicalStart AS INTEGER, PageCount AS INTEGER, Handle AS INTEGER)
DIM MapInformation AS STRING
DIM Page AS INTEGER
DIM Registers AS RegTypeX
FOR Page = 0 TO PageCount - 1
MapInformation = MapInformation + MKI$(LogicalStart + Page) + MKI$(PhysicalStart + Page)
NEXT Page
Registers.ax = &H5000
Registers.cx = PageCount
Registers.dx = Handle
Registers.ds = VARSEG(MapInformation)
Registers.si = SADD(MapInformation)
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
END SUB
FUNCTION EMSPageFrameAddress
DIM Registers AS RegTypeX
Registers.ax = &H4100
InterruptX &H67, Registers, Registers
EMSErrorCode = Registers.ax \ &H100
EMSPageFrameAddress = Registers.bx
END FUNCTION
FUNCTION FileExists (FileName AS STRING)
DIM Attributes AS INTEGER
DIM DTA AS DTAStr
DIM Pattern AS STRING
DIM Registers AS RegTypeX
Registers.ax = &H1A00
Registers.dx = VARPTR(DTA)
Registers.ds = VARSEG(DTA)
InterruptX &H21, Registers, Registers
Attributes = 0
Pattern = FileName + CHR$(0)
Registers.ax = &H4E00
Registers.cx = Attributes
Registers.dx = SADD(Pattern)
Registers.ds = VARSEG(Pattern)
InterruptX &H21, Registers, Registers
FileExists = (INSTR(DTA.FileName, CHR$(0)) > 1)
END FUNCTION
SUB GetShiftStatus (LeftShift AS INTEGER, RightShift AS INTEGER)
DIM Status AS INTEGER
DEF SEG = &H40
Status = PEEK(&H17)
RightShift = ((Status AND &H1) = &H1)
LeftShift = ((Status AND &H2) = &H2)
END SUB
SUB Initialize
SCREEN 0
WIDTH 80, 25
PALETTE
COLOR 7, 0
CLS
EMSErrorCode = &H0
END SUB
FUNCTION LoadWave (WaveFile AS STRING, WaveParameters AS WaveParametersStr, EMSPagesReserved AS INTEGER)
DIM Buffer AS STRING
DIM DataRead AS LONG
DIM FileH AS INTEGER
DIM MaximumLength AS LONG
DIM Offset AS INTEGER
DIM Page AS INTEGER
DIM Success AS INTEGER
DIM WaveData AS WaveDataStr
DIM WaveFact AS WaveFactStr
DIM WaveHeader AS WaveHeaderStr
Success = -1
FileH = FREEFILE
OPEN WaveFile FOR BINARY LOCK READ WRITE AS FileH
GET #FileH, , WaveHeader
IF WaveHeader.FormatSize = 18 THEN GET #FileH, , WaveFact
GET #FileH, , WaveData
IF WaveHeader.RIFFID = "RIFF" AND WaveHeader.WAVEID = "WAVE" AND WaveHeader.FormatID = "fmt " AND WaveHeader.Format = 1 AND WaveHeader.Channels = 1 AND WaveHeader.Frequency <= 32000 AND WaveHeader.Alignment = 1 AND WaveData.DataID = "data" THEN
WaveParameters.Frequency = WaveHeader.Frequency
WaveParameters.Length = LOF(FileH) - LEN(WaveHeader)
MaximumLength = EMSPagesReserved * EMSPAGESIZE
IF WaveParameters.Length > MaximumLength THEN WaveParameters.Length = MaximumLength
Buffer = STRING$(&H7FFF, &H0)
DataRead = 0
DO
IF DataRead > WaveParameters.Length THEN Buffer = STRING$(WaveParameters.Length - DataRead, &H0)
GET #FileH, , Buffer
Page = DataRead \ EMSPAGESIZE
Offset = DataRead - Page * EMSPAGESIZE
EMSCopyBaseToEMS LEN(Buffer), VARSEG(Buffer), SADD(Buffer), WaveParameters.EMSHandle, Offset, Page
DataRead = DataRead + LEN(Buffer)
LOOP UNTIL DataRead >= WaveParameters.Length
Buffer = ""
ELSE
Success = 0
END IF
CLOSE FileH
LoadWave = Success
END FUNCTION
SUB Main (WaveFile AS STRING)
DIM BytesLeft AS LONG
DIM EMSPagesToReserve AS INTEGER
DIM KeyStroke AS STRING
DIM LeftShift AS INTEGER
DIM LeftSide AS INTEGER
DIM SB AS SBSettingsStr
DIM RightShift AS INTEGER
DIM RightSide AS INTEGER
DIM WaveParameters AS WaveParametersStr
DIM WavePlayer AS WavePlayerStr
WaveFile = LTRIM$(RTRIM$(WaveFile))
WavePlayer.Paused = 0
SB.BaseAddress = SBBaseAddress
SB.DMAChannel = SBDMAChannel
SBSetVolume SB, &HF, &HF
LOCATE 1, 1: PRINT "Wave Player v1.02 - by: Peter Swinkels ***2021***"
PRINT
IF WaveFile = "" THEN
Quit "Specify the wave file to be played as the command line argument.", WaveParameters
ELSE
IF FileExists(WaveFile) THEN
IF SBInitialize(SB) THEN
IF EMSInstalled THEN
EMSPagesToReserve = EMSFreePages
IF EMSPagesToReserve > 0 THEN
WaveParameters.EMSHandle = EMSAllocatePages(EMSPagesToReserve)
IF LoadWave(WaveFile, WaveParameters, EMSPagesToReserve) THEN
LOCATE 3, 1
PRINT " Playing: "; WaveFile
PRINT
PRINT " Base address: 0x"; HEX$(SB.BaseAddress)
PRINT " DMA channel: 0x"; HEX$(SB.DMAChannel)
SBGetVolume SB, LeftSide, RightSide
DisplayVolume 10, 2, LeftSide, RightSide
LOCATE 23, 1
PRINT " Escape = Quit P = Pause/Resume -/+ [+Left/Right Shift] = Volume "
SBSpeakerOn SB
WavePlayer.CurrentPage = 0
WavePlayer.Remainder = WaveParameters.Length
DO
IF WavePlayer.Remainder = &H0& THEN
EXIT DO
ELSEIF WavePlayer.Remainder > &HFFFF& THEN
WavePlayer.ChunkLength = &HFFFF&
ELSE
WavePlayer.ChunkLength = WavePlayer.Remainder
END IF
WavePlayer.ChunkPageCount = (WavePlayer.ChunkLength \ EMSPAGESIZE) + 1
SBPlayWave WaveParameters, WavePlayer, SB
DO
BytesLeft = SBBytesLeft&(SB)
LOCATE 8, 3: PRINT "Data remaining: "; LTRIM$(RTRIM$(STR$(WavePlayer.Remainder - (EMSPAGESIZE - BytesLeft)))); "/"; LTRIM$(RTRIM$(STR$(WaveParameters.Length))); " bytes."; SPACE$(5)
KeyStroke = INKEY$
SELECT CASE KeyStroke
CASE "p", "P"
WavePlayer.Paused = NOT WavePlayer.Paused
IF WavePlayer.Paused THEN SBPause SB ELSE SBResume SB
CASE "-", "+"
GetShiftStatus LeftShift, RightShift
IF NOT (LeftShift OR RightShift) THEN LeftShift = -1: RightShift = -1
SBGetVolume SB, LeftSide, RightSide
SELECT CASE KeyStroke
CASE "-"
IF LeftShift AND LeftSide > &H0 THEN LeftSide = LeftSide - &H1
IF RightShift AND RightSide > &H0 THEN RightSide = RightSide - &H1
CASE "+"
IF LeftShift AND LeftSide < &HF THEN LeftSide = LeftSide + &H1
IF RightShift AND RightSide < &HF THEN RightSide = RightSide + &H1
END SELECT
SBSetVolume SB, LeftSide, RightSide
DisplayVolume 10, 2, LeftSide, RightSide
CASE CHR$(27)
EXIT DO
END SELECT
LOOP WHILE SBInUse(BytesLeft)
WavePlayer.CurrentPage = WavePlayer.CurrentPage + WavePlayer.ChunkPageCount
WavePlayer.Remainder = WavePlayer.Remainder - WavePlayer.ChunkLength
LOOP UNTIL KeyStroke = CHR$(27)
SBSpeakerOff SB
Quit "", WaveParameters
ELSE
Quit WaveFile + " - unsupported WAVE file format.", WaveParameters
END IF
ELSE
Quit "Not enough free EMS pages.", WaveParameters
END IF
ELSE
Quit "No EMS driver detected.", WaveParameters
END IF
ELSE
Quit "Could not initialize the Sound Blaster.", WaveParameters
END IF
ELSE
Quit "Could not open the file: " + WaveFile + ".", WaveParameters
END IF
END IF
END SUB
SUB Quit (Message AS STRING, WaveParameters AS WaveParametersStr)
IF (NOT EMSErrorCode = &H0) OR (NOT Message = "") THEN
IF NOT Message = "" THEN PRINT Message
IF NOT EMSErrorCode = &H0 THEN
PRINT "EMS Error code: 0x"; HEX$(EMSErrorCode); ""
PRINT EMSError$(EMSErrorCode)
END IF
DO WHILE INKEY$ = ""
LOOP
END IF
EMSDeallocatePages WaveParameters.EMSHandle
SCREEN 0
WIDTH 80, 25
PALETTE
COLOR 7, 0
CLS
SYSTEM
END SUB
FUNCTION SBBaseAddress
DIM BaseAddress AS INTEGER
DIM Position1 AS INTEGER
DIM Position2 AS INTEGER
DIM Settings AS STRING
BaseAddress = &H220
Settings = LTRIM$(RTRIM$(UCASE$(ENVIRON$("BLASTER"))))
Position1 = INSTR(Settings, "A")
IF Position1 > 0 THEN
Position2 = INSTR(Position1, Settings, " ")
BaseAddress = VAL("&H" + MID$(Settings, Position1 + 1, (Position2 - Position1)))
END IF
SBBaseAddress = BaseAddress
END FUNCTION
FUNCTION SBBytesLeft& (SB AS SBSettingsStr)
SBBytesLeft& = INP(SB.DMALength) OR INP(SB.DMALength) * &H100&
END FUNCTION
FUNCTION SBDMAChannel
DIM DMAChannel AS INTEGER
DIM Position1 AS INTEGER
DIM Position2 AS INTEGER
DIM Settings AS STRING
DMAChannel = &H1
Settings = LTRIM$(RTRIM$(UCASE$(ENVIRON$("BLASTER"))))
Position1 = INSTR(Settings, "D")
IF Position1 > 0 THEN
Position2 = INSTR(Position1, Settings, " ")
DMAChannel = VAL("&H" + MID$(Settings, Position1 + 1, (Position2 - Position1)))
END IF
SBDMAChannel = DMAChannel
END FUNCTION
SUB SBGetVolume (SB AS SBSettingsStr, LeftSide AS INTEGER, RightSide AS INTEGER)
DIM Volume AS INTEGER
OUT SB.BaseAddress + &H4, &H22
Volume = INP(SB.BaseAddress + &H5)
LeftSide = Volume \ &H10
RightSide = Volume AND &HF
END SUB
FUNCTION SBInitialize (SB AS SBSettingsStr)
DIM Success AS INTEGER
Success = -1
OUT SB.BaseAddress + &H6, &H1
WAIT SB.BaseAddress + &H6, &H80
OUT SB.BaseAddress + &H6, &H0
Delay .03
Success = ((INP(SB.BaseAddress + &HE) AND &H80) = &H80) AND (INP(SB.BaseAddress + &HA) = &HAA)
IF Success THEN
SB.DMAAddress = SB.DMAChannel + &H1
SB.DMALength = SB.DMAChannel + &H2
SELECT CASE SB.DMAChannel
CASE 0
SB.DMAPage = &H87
CASE 1
SB.DMAPage = &H83
CASE 2
SB.DMAPage = &H81
CASE 3
SB.DMAPage = &H82
CASE ELSE
Success = 0
END SELECT
END IF
SBInitialize = Success
END FUNCTION
FUNCTION SBInUse (BytesLeft AS LONG)
SBInUse = NOT (BytesLeft = &H0 OR BytesLeft = &HFFFF&)
END FUNCTION
SUB SBPause (SB AS SBSettingsStr)
SBWriteDSP SB, &HD0
END SUB
SUB SBPlayWave (WaveParameters AS WaveParametersStr, WavePlayer AS WavePlayerStr, SB AS SBSettingsStr)
DIM EMSPageFrameFlatAddress AS LONG
EMSMapPages 0, WavePlayer.CurrentPage, WavePlayer.ChunkPageCount, WaveParameters.EMSHandle
DMASetChannel SB, -1
EMSPageFrameFlatAddress = (&H10000 + EMSPageFrameAddress) * &H10&
OUT SB.DMAAddress, EMSPageFrameFlatAddress AND &HFF&
OUT SB.DMAAddress, (EMSPageFrameFlatAddress AND &HFF00&) \ &H100&
OUT SB.DMAPage, (&H10000 + EMSPageFrameAddress) / &H1000&
OUT SB.DMALength, (WavePlayer.ChunkLength - &H1&) AND &HFF&
OUT SB.DMALength, ((WavePlayer.ChunkLength - &H1&) AND &HFF00&) \ &H100&
DMASetChannel SB, 0
SBWriteDSP SB, &H40
SBWriteDSP SB, ((&H100& - &HF4240) \ WaveParameters.Frequency)
SBWriteDSP SB, &H14
SBWriteDSP SB, ((WavePlayer.ChunkLength - &H1&) AND &HFF&)
SBWriteDSP SB, (((WavePlayer.ChunkLength - &H1&) AND &HFF00&) \ &H100&)
END SUB
SUB SBResume (SB AS SBSettingsStr)
SBWriteDSP SB, &HD4
END SUB
SUB SBSetVolume (SB AS SBSettingsStr, LeftSide, RightSide)
OUT SB.BaseAddress + &H4, &H22
OUT SB.BaseAddress + &H5, (LeftSide * &H10 OR RightSide)
END SUB
SUB SBSpeakerOff (SB AS SBSettingsStr)
SBWriteDSP SB, &HD3
END SUB
SUB SBSpeakerOn (SB AS SBSettingsStr)
SBWriteDSP SB, &HD1
END SUB
SUB SBWriteDSP (SB AS SBSettingsStr, Byte)
WAIT SB.BaseAddress + &HC, &H80, &H80
OUT SB.BaseAddress + &HC, Byte
END SUB
==========================================================================
En er is niet speciaal oude hardware voor nodig, dit programma werkt uitstekend in DOSBox of DOSBox-X! 