ZX Spectrum: Experimenteren met 1-bit geluid

De belangrijkste prestaties van deze week draaien om multichannel-geluid via de 1-bit speaker—in feite het spelen van akkoorden. Daarnaast heb ik de eenvoudige 1-bit PCM-weergave uit het PC Speaker-artikel opnieuw gemaakt en kort geëxperimenteerd met de geavanceerdere PWM-techniek. Beiden hebben uiteindelijk ook bijgedragen aan het werk met de weergave van akkoorden.

Dit zal deze week geen uitgebreide handleiding zijn; ik leg op deze blog zowel mijn mislukkingen als mijn successen vast, en deze week heeft meer dan zijn eerlijke deel aan mislukkingen gekend.

Pulse Code Modulation (PCM) en Pulse Width Modulation (PWM)

Pulse code modulation is een vrij eenvoudig concept: een golfvorm wordt gesampled als een reeks waarden tussen 0 en een maximale waarde, en die waarden worden naar de hardware gestuurd om te worden omgezet in speakervoltages. Hoe groter de maximale waarde, hoe fijner de controle over de amplitude; hoe sneller de samples worden verzonden, hoe fijner de controle over de frequentie. (Een fundamentele regel van signaalverwerking is dat een golfvorm met een frequentie die hoger is dan de helft van de sample-rate niet nauwkeurig kan worden gesampled.) PCM-golven worden over het algemeen beschreven door het aantal bits dat wordt gebruikt om elk sample uit te drukken en vervolgens de sample-rate—een hoogwaardig sample kan 16-bit 44kHz zijn, terwijl de meeste gebruiksscenario's kunnen worden bediend door een 8-bit sample bij sample-rates vanaf 8 kHz.

De Spectrum kan toegang krijgen tot zijn geheugen met ongeveer 1MHz, waardoor we een respectabele weergave van 16kHz kunnen halen, zelfs onder directe, cycle-counted CPU-controle. De speaker is echter alleen 'aan' of 'uit'. Dat betekent dat dit 1-bit PCM is, waarvan we kunnen verwachten dat het behoorlijk slecht klinkt; we krijgen het soort vervorming dat optreedt wanneer je een speaker overstuurt met te veel versterking, maar dan constant en zelfs bij lage volumes.

Verschillende modellen van de Spectrum hadden licht verschillende CPU-snelheden; ik gebruik de klok van de 48K, die draaide op een vlakke 3,5 MHz voor mijn cycle counting. Als we dit delen door 16.000 samples per seconde, blijkt dat we 219 cycli tussen de schrijfacties moeten wachten. Dat is een comfortabele hoeveelheid tijd; we kunnen een 1-bit PCM-opname met 8 samples per byte pakken en deze gemakkelijk consumeren. Samples verbruiken ongeveer 2KB per seconde, wat krap is op een 48K-systeem, maar niet rampzalig. Het belangrijkste probleem, zoals we zullen zien, is de vervorming van het geluid. De implementatie van deze techniek vormt geen bijzondere uitdagingen.

Pulse width modulation is iets lastiger, maar belooft een veel hogere audiokwaliteit. Op het niveau van het elektrische signaal verzendt PWM-data één 1-bit puls per sample, en de lengte van de puls geeft de bedoelde sterkte van het audiosignaal op dat samplepunt aan. (Vergelijk dit met PCM, dat effectief een multi-bit digitale waarde over de lijn stuurt om dit te bereiken. PWM is analoger dan PCM, ondanks dat het agressiever 1-bit is.) Op fysiek niveau is dit een gevolg van het feit dat elektrische signalen in nanoseconden kunnen veranderen van 1 naar 0 en terug, terwijl de fysieke speaker die aan het apparaat is bevestigd, tientallen microseconden nodig heeft om daadwerkelijk de reis tussen zijn "in"- en "out"-toestanden af te leggen. Door het signaal op verschillende punten in die reis uit te schakelen, varieert de totale sterkte van de speaker op een veel preciezer controleerbare manier dan 1-bit PCM biedt.

Op de IBM PC is de 1-bit speaker gekoppeld aan een hardwaretimer, en hoogwaardige audio kan worden gegenereerd door die timer 7-bit PCM-data te voeden. De Spectrum is niet zo behulpzaam, en we moeten het beheren met cycle counting. Het is me niet gelukt om een PWM-systeem op de Spectrum werkend te krijgen naar tevredenheid; ik heb echter wel enkele aanknopingspunten en ben ervan overtuigd dat de techniek in het algemeen solide is.

Exacte cyclusvertragingen op de Z80

Voordat we in detail in de code duiken, moeten we vaststellen wat er nodig is voor nauwkeurige timingvertragingen. De "T-State"-telling van de Z80 is veel groter en glibberiger dan de CPU-cycli op de 6502. Terwijl ik op de 6502 triviaal instructies kon samenstellen om elk aantal cycli te wachten, is mijn begeleiding voor de Z80 noodzakelijkerwijs iets afhankelijker van de context. Voor dit werk heb ik me voornamelijk vertrouwd op vuistregels, waarbij ik vertragingssequenties op een meer organische manier heb samengesteld.

  • NOP zelf is 4 cycli, net als vrijwel elke 8-bit operatie van één byte (waar er veel van zijn). Zodra de resterende vertraging kort is en een veelvoud van 4 cycli, zijn we in principe klaar.
  • INC HL is 6 cycli, en dat zijn 16-bit instructies met een soortgelijke structuur ook. Deze kunnen een verschil van 2 cycli wegwerken, of gepaard worden om 12 cycli te wachten met twee bytes code in plaats van 3.
  • LD A,n is 7 cycli, samen met de andere 8-bit immediate-instructies. Zolang de totale wachttijd lang genoeg is, stellen deze ons in staat om tot op de cyclus nauwkeurig te zijn.
  • JP-instructies zijn 10 cycli, zelfs als ze conditioneel zijn, wat ze veel voorkeurbaarder maakt dan JR of DJNZ voor code die beslissingen moet nemen in een nauwkeurig getimede loop. Die hebben echter nog steeds hun plek, omdat...
  • LD B,n; LBL: DJNZ LBL is de kortste loop die je kunt schrijven en verbruikt $13n+2$ cycli. Voor lange wachttijden zal deze loop het grootste deel vormen, en de bovenstaande instructies zullen eventuele ongemakken wegwerken.

Dit is niet precies systematisch, maar het is voldoende voor onze doeleinden.

Een PCM-afspeelroutine

Het algemene plan is om 8 samples in elke byte te pakken, waarbij het hoge bit als eerste wordt afgespeeld. Bij een 16-bit sample willen we 219 cycli tussen elke output. Onze input plaatst een pointer naar de inputdata in HL en het aantal bytes in het sample in DE. (We nemen aan dat het totale aantal samples een veelvoud van 8 is. Onze encoder moet het einde simpelweg opvullen, en dat is het probleem van de encoder, niet van ons.) Normaal gesproken gebruiken we BC voor onze tellers, maar we hebben B specifiek nodig voor onze interne tijdvertragingstellers, dus DE moet dat overnemen.

We beginnen de routine, vreemd genoeg, grotendeels aan het einde. Wanneer we de hoofd-afspeelloop betreden, bevinden we ons in het midden van een 219-cyclus-sequentie, en om te weten wat onze timingbeperkingen zijn, moeten we het einde van de loop schrijven om daarbij aan te sluiten. Daarnaast blijkt dat onze algehele functiep prologue en epilogue zo kort zijn dat we ze direct kunnen weggooien. Alles wat ze hoeven te doen is interrupts uitschakelen en weer inschakelen.

pcmout: di
.lp:    ?????????????                   ; Speel 8 samples uit een byte, eindigend met...
out     ($fe),a                 ; + 11 (219)  ...de laatste bit output
;; Pas byte-teller aan en loop terug
dec     de                      ; +  6 (  6)
ld      a,d                     ; +  4 ( 10)
or      e                       ; +  4 ( 14)
jp      nz,.lp                  ; + 10 ( 24)
;; We zijn klaar; schakel interrupts weer in en return
ei
ret

Normaal gesproken, wanneer we een byte bit voor bit consumeren, verschuiven we aan één van de kanten en raadplegen we de carry-bit om te beslissen wat we moeten doen. We kunnen hier wat slim zijn; we moeten het bit dat we consumeren kopiëren naar het $10-bit van de output-byte. Gezien dat feit is het effectiever om onze initiële byte drie bits naar rechts te roteren, en vervolgens direct met het $10-bit te werken. We moeten ervoor zorgen dat we de 8-bit RRC- en RLC-instructies gebruiken in plaats van de 9-bit RR- en RL-instructies.

We weten, uit het vorige deel, dat we de loop betreden bij cyclus 24. Het lezen van de byte, het voorbereiden ervan en het outputten van het eerste bit op het juiste moment zal er dus zo uitzien:

.lp:    ld      a,(hl)                  ; +  7 ( 31)  Laad een byte
inc     hl                      ; +  6 ( 37)  Verplaats pointer
rrca                            ; +  4 ( 41)  Roteer $80 bit naar $10
rrca                            ; +  4 ( 45)    met 8-bit rotaties
rrca                            ; +  4 ( 49)
ld      c,a                     ; +  4 ( 53)  Sla byte op in C
and     $10                     ; +  7 ( 60)  Isoleer geluidsbit
or      $01                     ; +  7 ( 67)  Blauwe rand, waarom ook niet
?????????????                   ; +141 (208)
out     ($fe),a                 ; + 11 (219)

Nu moeten we 141 cycli vertragen. Dit is waar ik op kwam:

inc     hl                      ; +  6 ( 73)
dec     hl                      ; +  6 ( 79)
ld      b,9                     ; +  7 ( 86)
jp      1F                      ; + 10 ( 96)
1       djnz    1B                      ; +112 (208)

Dat brengt ons precies waar we moeten zijn. Eén bit is klaar, er moeten er nog zeven volgen. Ik heb overwogen om hiervan een inner loop te maken, maar ik heb geen registers meer over en spilling naar het geheugen zou lelijk worden. Het is veel eenvoudiger om sjasm de code voor mij te laten kopiëren en plakken.

repeat  7
rlc     c                       ; +  8 (  8)  Volgende bit
ld      c,a                     ; +  4 ( 12)
and     $10                     ; +  7 ( 19)  Isoleer geluidsbit
or      $01                     ; +  7 ( 26)  Blauwe rand nog steeds
ld      b,1                     ; +  7 ( 33)  Vertraag 182 cycli...
dec     b                       ; +  4 ( 37)
ld      b,13                    ; +  7 ( 44)
1       djnz    1B                      ; +164 (208)
out     ($fe),a                 ; + 11 (219)  ... en output dit bit
endrepeat

Dat was alles wat we nodig hadden; daarbuiten hebben we alleen de laatste zes instructies uit ons initiële skelet. De functie werkt geweldig! De werkelijke geluidskwaliteit is echter vreselijk. Ik heb zelfs geprobeerd wat preprocessing te doen om wat van de ruis op te schonen in delen waar er niet echt een betekenisvol signaal is, en zelfs dat hielp niet veel. Toch werkt de functie prima voor wat het is, en het zal een handig instrument zijn om in onze achterzak te houden. Ik ben echter niet bijzonder geïnspireerd om de repeat-macro om te bouwen tot een proper loop.

Akkoorden spelen

Multichannel muziek was blijkbaar redelijk gebruikelijk via de beeper, vroeger. Mijn eigen avonturen door de jaren heen suggereerden twee benaderingen om redelijk goede resultaten te behalen:

  1. Arpeggiation. Het was zeer gebruikelijk op zowel de C64 als de Amiga om een akkoord aan één outputkanaal te geven en simpelweg de frequentie per frame te veranderen om het akkoord-effect te produceren. Dit gaf ze een heel kenmerkend soort "buzz"-geluid. Dit zou triviaal te implementeren moeten zijn, gezien wat ik tot nu toe heb geschreven.
  2. Software Mixing. De gebruikelijke manier om meerdere geluidskanalen op een monauraal systeem te maken, is simpelweg alle binnenkomende signalen op te tellen en die waarde als output te geven. We zullen wat concessies moeten doen als we dat hier proberen, maar het basisprincipe zou solide moeten zijn.

Implementatie van Arpeggiation

Dit zou echt makkelijk moeten zijn. Ik heb al een routine die één toon gedurende een bepaalde tijd speelt. Ik kan die tijd gewoon heel kort maken en het in een outer loop plaatsen. Iets als dit:

ld      b,$18
1       push    bc
ld      bc,$0d0
ld      de,$0367
call    sound
ld      bc,$0d0
ld      de,$0441
call    sound
ld      bc,$0d0
ld      de,$051a
call    sound
pop     bc
djnz    1B

We hebben vorige keer geleerd dat we het geheugen tussen $4000 en $7FFF niet mogen aanraken als we consistente timing willen, dus ik heb de origin hier ingesteld op $8100 om er zeker van te zijn dat we niet worden vertraagd door de PUSH, POP en CALL-instructies.

De resultaten zijn over het algemeen plausibel. Het is een beetje krakerig, maar het is duidelijk een akkoord. Eén ding dat me opviel was dat als ik de intervallen te kort maakte, het akkoord ontstemd raakte. Ik ben er vrij zeker van dat wat er gebeurde, was dat het veranderen van noten niet alleen extra tijd kostte, maar ook de pulsteller resette, wat kon resulteren in waveforms die ernstig buiten de specificaties vielen op de overgangspunten. De zaken leken iets te verbeteren toen ik de initialisatie van HL en A verplaatste naar de top-level chord-functie in plaats van de oorspronkelijke sound-routine. Vervolgens gaf ik de drie frequenties in registers allemaal tegelijk door en lade ik ze in de argumenten van de immediate-instructies. De chord-functie zag er toen zo uit:

chord:  ld      (.f1),hl
ld      (.f2),de
ld      (.f3),bc
ld      a,($5c48)               ; BORDCR
and     $38
rrca
rrca
rrca
or      $08
di
ld      b,$18
ld      hl,0
1       push    bc
ld      bc,$0d0
.f1     equ     $+1
ld      de,$0000
call    sound
ld      bc,$0d0
.f2     equ     $+1
ld      de,$0000
call    sound
ld      bc,$0d0
.f3     equ     $+1
ld      de,$0000
call    sound
pop     bc
djnz    1B
ei
ret

Uiteindelijk heb ik ook een kleine macro samengesteld om akkoordprogressies gemakkelijker te specificeren, zodat ik het een kleine progressie kon geven.

macro   play    3
ld      hl,@1
ld      de,@2
ld      bc,@3
call    chord
endmacro

;; Main program
play    $0367,$0441,$051a       ; I
play    $0367,$048b,$05ba       ; IV
play    $0367,$0441,$051a       ; I
play    $0336,$03d2,$051a       ; V
play    $0367,$0441,$051a       ; I
ret

In tegenstelling tot de PCM-code hierboven, zou de geluidskwaliteit hier niet misplaatst zijn in een echte applicatie. Net als bij de PCM-code haal ik veel voordeel uit de macro-faciliteiten van Sjasm.

Software Mixing

Ik heb eigenlijk al eens een cycle-counted polyfone synthesizer gebouwd: het was een wave-table systeem voor de Dragon. De basisprincipes daarvan zijn nog steeds van toepassing, maar met een 1-bit output kunnen we het iets vereenvoudigen.

  1. Voer hetzelfde op frequentie-tellers gebaseerde soundsysteem uit als we al deden, maar houd drie tellers en frequentiestappen bij in plaats van slechts één.
  2. Nadat elke teller is bijgewerkt, tellen we de top-bits van elk een op en stellen we de speaker in op basis van of we ons in de bovenste of onderste helft van het mogelijke bereik bevinden.

Dit is veel gemakkelijker als er alleen oneven aantallen actieve kanalen zijn; ik denk dat ik voor een even aantal kanalen zou willen dat de middelste waarde de speaker-output ongewijzigd laat. Dat zou makkelijker te bewerkstelligen zijn op de Apple II (waar de speakercontrole een toggle is) dan op de Spectrum (waar we de speakerwaarde direct schrijven). Met precies drie stemmen, zoals hier, is het nog makkelijker, omdat we bij een mogelijk bereik van 0-3 gewoon naar het top-bit van de 2-bit som kunnen kijken.

De logica voor elk kanaal is zeer vergelijkbaar met wat ik schreef in de sound tour. Er zijn twee grote verschillen: ik moet de teller en de frequentiecode synchroniseren met het geheugen voor elke code (omdat we niet genoeg registers hebben voor alle drie tegelijk) en ik moet een 16-bit teller gebruiken in plaats van mijn vorige in feite 17-bit teller waarbij de carry-bit betrokken was. In de oude code flipte en hield ik de waarde die werd uitgegeven vast wanneer de carry-bit was gezet; nu moet ik het top-bit van de teller nemen en dit gebruiken als de waarde die ik aan het lopende totaal toevoeg. Ik verpak het in een macro, inclusief timings.

macro   count_channel   count,freq
ld      hl,(count)              ; +16
ld      de,(freq)               ; +20
add     hl,de                   ; +11
bit     7,h                     ; + 8
jp      m,1F                    ; +10
jp      2F                      ;     +10
1       inc     a                       ;           + 4
dec     de                      ;           + 6
2       ld      (count),hl              ; +16
endmacro

Er is hier wat branching, dus ik kan niet zomaar een rechte som maken, maar elk pad door deze code is exact 91 cycli.

De frequentiecodes zullen publieke variabelen zijn (we geven argumenten op die manier door), maar de kanaaltellers kunnen privé zijn.

freq1 # 2
freq2 # 2
freq3 # 2
chord:  ld      hl,0
ld      (.counter1),hl
ld      (.counter2),hl
ld      (.counter3),hl
di
.lp:    xor     a                       ; + 4  Maak de somwaarde leeg
count_channel .counter1,freq1   ; +91  Verwerk elke stem op zijn beurt
count_channel .counter2,freq2   ; +91
count_channel .counter3,freq3   ; +91
add     a                       ; + 4  Vermenigvuldig A met 8, waardoor het
add     a                       ; + 4  $02 bit naar de $10 positie gaat
add     a                       ; + 4
and     $10                     ; + 7  Isoleer dat bit
or      $0f                     ; + 7  Witte achtergrond
out     ($fe),a                 ; +11  Output gesommeerd bit
dec     bc                      ; + 6  Verlaag teller en ga verder
ld      a,b                     ; + 4
or      c                       ; + 4
jp      nz,.lp                  ; +10
ei
ret
.counter1 # 2
.counter2 # 2
.counter3 # 2

Bij 338 cycli per loop, en een "flip"-tellerwaarde van $8000 in plaats van $10000, moet ik mijn schalen opnieuw berekenen.

BCDEFGA
$061b$0678$0743$0826$08a2$09b1$0ae1

Het hoofdprogramma is deze keer iets uitgebreider, omdat we meer geheugenwerk hebben gedelegeerd aan de call.

macro   play    4
ld      hl,@2
ld      (freq1),hl
ld      hl,@3
ld      (freq2),hl
ld      hl,@4
ld      (freq3),hl
ld      bc,@1
call    chord
endmacro

play    $2000,$0678,$0826,$09b1 ; I
play    $2000,$0678,$08a2,$0ae1 ; IV
play    $2000,$0678,$0826,$09b1 ; I
play    $2000,$061b,$0743,$09b1 ; V
play    $4000,$0678,$0826,$09b1 ; I
ret

De logica is op hoog niveau echter niet veel anders, en de algehele geluidsresultaten voor deze benadering zijn veel beter dan de gearpegieerde versie; het geluid voelt veel rijker en het klinkt niet eens echt als een 1-bit systeem. Ik was behoorlijk onder de indruk hiervan.

Op dit punt heb ik de limiet bereikt van de play-routines die daadwerkelijk werkten. Ik heb een collectie programma's geüpload naar hun eigen directory in mijn Github Repo; je kunt ze bouwen en runnen met de Makefile, of het audiobestand bekijken om de arpeggiatie- en channel-summing-technieken te horen naast een PCM-waveform. Vanaf hier zal ik benaderingen behandelen die ofwel niet werkten, of niet volledig in een echte implementatie terechtkwamen.

Andere benaderingen

Ik wilde nog een paar andere dingen experimenteren, vooral wat betreft het arpeggiatiesysteem, maar het was een hele klus om de code correct te krijgen, en het was niet altijd duidelijk of een slecht geluid kwam omdat de techniek slecht was, of omdat mijn implementatie buggy was. Na een tijdje realiseerde ik me iets belangrijks: ik hoef geen aangepaste sound-engines te schrijven om dit te testen. Ik kan gewoon de wave-bestanden die ik wil vooraf genereren en ze vervolgens door de PCM-speler halen.

Het belangrijkste dat ik wilde proberen, was het probleem onderzoeken waarbij het geluid ontstemd raakte als we de frequenties te snel wisselden. Mijn theorie was dat het er verkeerd uitzag omdat we geen volledige waveforms kregen. Als dat zo is, kunnen we dat oplossen door de waveform vooraf te genereren: we kunnen dan precies één golf van elke frequentie maken, één na de ander, in volgorde.

Ook dit klonk niet correct, maar bij het bekijken van de PCM-data kwam een zeer goede potentiële verklaring naar voren: lagere frequenties doen er langer over om af te spelen. Stel je voor dat je een simpel akkoord speelt van twee noten die een octaaf uit elkaar liggen. De lage noot heeft de helft van de frequentie van de hoge noot, wat betekent dat twee derde van de waveform wordt besteed aan de lagere noot, en dit lijkt de resulterende waveform uit balans te brengen.

Hetzelfde gebeurde bij het verdelen van waveforms per timeslice; dit produceerde zeer vergelijkbare vervormingen als mijn initiële tests. Hoe korter de timeslice, hoe minder daadwerkelijke pulsen de juiste lengte hebben.

Maar... wat als we de puls-golven helemaal niet behouden?

Het geluidssysteem van de Apple IIgs is zeer ongebruikelijk. Het beschikt over 15 stereo wavetable-kanalen, een volstrekt onredelijk aantal voor 1986. De manier waarop dit wordt bereikt, is dat het al zijn digitale geluidskanalen "arpeggieert"; als er drie kanalen actief zijn, speelt de geluidschip dan één sample van elk kanaal op zijn beurt, waardoor een specifiek kanaal slechts bij elke derde sample verschijnt. In plaats van het interleaven zoals we deden met onze pulsgolven, wat als we een pagina uit het boek van de IIgs zouden slaan en de waveforms sample voor sample zouden husselen in plaats van puls voor puls?

Het antwoord is: helemaal niet slecht. Het is merkbaar beter dan al mijn eerdere pogingen tot arpeggiatie, maar naar mijn gehoor toe is het niet zo goed als de sample-addition-benadering. Ik vermoed dat dit komt doordat de channel-addition-benadering probeert transities te minimaliseren, waardoor de algehele golf "schoner" aanvoelt. De sample-shuffling-benadering heeft alle dezelfde frequentiecomponenten in het signaal, maar het grotere aantal transities maakt dat het geheel iets "krakeriger" aanvoelt.

Pulse Wave Modulation (PWM)

Mijn vorige werk met PWM vertrouwde op de mogelijkheid om een hardwaretimer in te stellen om te bepalen wanneer de speaker aan- en uitgaat. Dit was nauwkeurig genoeg waardoor ik het configuratieregister van de timer kon behandelen als een poort die 7-bit PCM-data ontvangt. Hier heb ik dat geluk niet, maar dat zou desondanks niet zo erg moeten zijn.

Ervan uitgaande dat de speaker van de Spectrum ongeveer gelijkwaardig is aan die van de PC, duurt het 50 microseconden (of 175 cycli, of 43,75 NOP-instructies) om de volledige uitslag te bereiken. Het leveren van een puls van precieze breedte zou een kwestie moeten zijn van de instructies OUT ($FE),A, XOR $10, en vervolgens een reeks van 45 NOPs. We kunnen dan een andere OUT-instructie op het juiste punt in die stroom van no-ops plaatsen, en deze daarna weer omzetten in een paar NOPs. Het resultaat zou een stuk code moeten zijn dat altijd in constante tijd draait en ons een controle over de pulsbreedte geeft die net iets slechter is dan een microseconde. Vervolgens herhalen we dit proces één keer per sample.

Dit zou prima moeten zijn. Ik heb dit echter in mijn experimenten niet werkend gekregen. Ik ben met dit deel van het project weggegaan met enkele leerzame mislukkingen.

Een nadeel van PWM-gebaseerde systemen is dat elke puls die we verzenden de speakertoestand reset zodra deze klaar is. De pulsen zelf zullen een regelmatige reeks signalen produceren die waarneembaar zijn als de sample-rate laag genoeg is. In mijn testprogramma's was dit hoogfrequente gepiep het enige wat ik kon horen. Als ik heel goed luisterde, kon ik misschien een klein stukje van het originele sample eruit pikken.

Dat suggereert althans dat het niet—of niet volledig—een emulator-probleem is. Maar verder geeft het me niet veel om mee te werken.

Hulp van experts

Gelukkig voor mij zijn er veel uiterst deskundige Sinclair-experts die me in de juiste richting kunnen wijzen. Gesprekken op sociale media en in de forums van de Spectrum Computing-site hebben me veel stof tot nadenken gegeven:

  • Pulse width modulation werkt absoluut; er zijn demo's van.
  • De Fuse-emulator is goed genoeg om dit af te handelen—zo is hij zelfs té betrouwbaar, aangezien de resultaten van echte hardware blijkbaar bijna onhoorbaar zijn zonder een versterker.
  • L Break Into Program, wiens werk ik eerder heb gelinkt als onderdeel van mijn tour door het graphics-systeem, werkte met de legendarische Follin-broers aan verschillende systemen, waaronder de Spectrum. Met toestemming van hun nalatenschap heeft hij enkele van hun sound-drivers op GitHub gepubliceerd.

Ik heb hier nog niet veel mee gedaan—ik vind het prettig om deze systemen met een zo fris mogelijke blik te benaderen, zodat ik niet gewoon eerder werk herhaal—maar ik ben op een punt gekomen waarbij ik tegen een muur aanloop, en ik denk dat het tijd is om eerder werk in detail te bestuderen.

Terwijl ik deze week afrond, heb ik niet veel kans gehad om dat daadwerkelijk te doen. Ik heb tenminste geïdentificeerd waar Fuse zijn beeper-simulator bewaart en ik kan zien dat het een van de drie mogelijke lowpass-filters over de output implementeert. Dat is een plausibel implementatiemechanisme voor het simuleren van de fysica van de speaker, en daarom ben ik er vrij zeker van dat dit zou moeten werken. Ik zal echter niets definitiefs kunnen zeggen totdat ik de kans krijg om tijd te lenen van verschillende mensen met verschillende soorten hardware. In een puur software-lab kun je maar tot een bepaald punt komen.