From 1e0a2e04d01963bfe259e11f7cd349af57fcb29a Mon Sep 17 00:00:00 2001 From: y0014984 Date: Wed, 15 Jan 2025 11:27:47 +0100 Subject: [PATCH] added simple sound output --- src/assets/games/snake.prg | Bin 1541 -> 1560 bytes src/logic/Computer.ts | 8 ++++ src/logic/Memory.ts | 14 ++++++- src/logic/Sound.ts | 82 +++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/logic/Sound.ts diff --git a/src/assets/games/snake.prg b/src/assets/games/snake.prg index e0f5eaf6643a88da7dd98c36b743b9f9deab0b6b..51ce1b54ad94074dc6a220b2fe72c49d13fe9368 100644 GIT binary patch delta 321 zcmZqWnZdJxi;+E7$VXUE;s0bo#zlUAh1Q-CID1;)1KYbZ0zMA~72XT3Wazyn03^-` zC~OyAdr9CV!v!{lwIV>tbC(3xDlnZrC2)Zm$O9U(lB-vRY2}VyRi>4Vy=qJelix9x zu*?-zke{5!q|9hCxr-^D@$ckUOx}#!CYv!kt1T2>soZ;7zz0Z+Kxr-@tzaSqv_~TM zqCoE@0iSJ?=QHcbeiMG@$h`8Q&o|+74$LbbfoLHAF_899u$ugW*_~~+$jYZavnQLf z7^nc5&wOCSa}etVhyb%*Lb;PWSz@IGMcrxH delta 330 zcmbQi)5^1fi;+D_$VXUE;mu@0#zoezgw~!BID1;)1KYbZ0zUTyS2FZo695ut1QeDC zue~I2lHmfI!YmP>%(+VfYZaKzo)Wmg4CDb#n9Rsj!qO$Gz%#j$Ntsb`@?55P##fX7 zGI=vDp6to&tkx&IQn~lEfDe!sfzn(+T0v0=Xs$%=MSB) diff --git a/src/logic/Computer.ts b/src/logic/Computer.ts index 71f6f14..68fb88b 100644 --- a/src/logic/Computer.ts +++ b/src/logic/Computer.ts @@ -1,5 +1,6 @@ import Processor from './Processor'; import Graphics from './Graphics'; +import Sound from './Sound'; import Memory from './Memory'; import { Storage, File, Directory, Program } from './Storage'; import biosUrl from '../assets/roms/bios.prg?url'; @@ -32,6 +33,7 @@ export class Computer { status: Status = Status.OFF; cpu: Processor; gfx: Graphics; + snd: Sound; mem: Memory; stor: Storage; domUpdateInstructionsInterval: number = 2_500; // adjust this for fps @@ -55,6 +57,10 @@ export class Computer { if (!this.gfx) return; this.gfx.checkMemWrite(index); }, + index => { + if (!this.snd) return; + this.snd.checkMemWrite(index); + }, index => { if (!this.stor) return; this.stor.checkMemWrite(index); @@ -63,6 +69,8 @@ export class Computer { this.gfx = new Graphics(monitorWidth, monitorHeight, this.mem); + this.snd = new Sound(this.mem); + this.cpu = new Processor(this.mem); this.stor = new Storage(this.mem); diff --git a/src/logic/Memory.ts b/src/logic/Memory.ts index 63b3efc..23f4ad7 100644 --- a/src/logic/Memory.ts +++ b/src/logic/Memory.ts @@ -1,10 +1,17 @@ export default class Memory { int: number[]; private onChangeGfx: (index: number) => void; + private onChangeSnd: (index: number) => void; private onChangeStor: (index: number) => void; - constructor(size: number = 65536, callbackGfx: (index: number) => void, callbackStor: (index: number) => void) { + constructor( + size: number = 65536, + callbackGfx: (index: number) => void, + callbackSnd: (index: number) => void, + callbackStor: (index: number) => void + ) { this.onChangeGfx = callbackGfx; + this.onChangeSnd = callbackSnd; this.onChangeStor = callbackStor; this.int = []; @@ -26,6 +33,7 @@ export default class Memory { this.int[index] = value; this.onChangeGfx(index); + this.onChangeSnd(index); this.onChangeStor(index); } @@ -38,6 +46,7 @@ export default class Memory { this.int[index] = isNaN(value) ? 0 : value; this.onChangeGfx(index); + this.onChangeSnd(index); this.onChangeStor(index); } @@ -57,6 +66,7 @@ export default class Memory { } this.onChangeGfx(index); + this.onChangeSnd(index); this.onChangeStor(index); } @@ -65,6 +75,7 @@ export default class Memory { if (this.int[index] > 255) this.int[index] = this.int[index] % 256; this.onChangeGfx(index); + this.onChangeSnd(index); this.onChangeStor(index); } @@ -73,6 +84,7 @@ export default class Memory { if (this.int[index] < 0) this.int[index] = 256 + this.int[index]; // this.int is negative this.onChangeGfx(index); + this.onChangeSnd(index); this.onChangeStor(index); } diff --git a/src/logic/Sound.ts b/src/logic/Sound.ts new file mode 100644 index 0000000..62bec71 --- /dev/null +++ b/src/logic/Sound.ts @@ -0,0 +1,82 @@ +import Memory from './Memory'; + +const registerWaveformDuration = 0x0224; +const registerStartStopFrequency = 0x0225; // Word + +export default class Sound { + private mem: Memory; + private duration: number = 0; + private frequency: number = 0; + private waveform: OscillatorType = 'sine'; + + constructor(mem: Memory) { + this.mem = mem; + + this.init(); + } + + init() { + this.updateWaveformDuration(); + + this.updateStartStopFrequency(0); + } + + start() { + const ctx = new AudioContext(); + const osc = ctx.createOscillator(); + osc.type = this.waveform; + osc.frequency.value = this.frequency; + osc.connect(ctx.destination); + osc.start(); + + const classSound = this; + // Beep for 500 milliseconds + setTimeout(function () { + osc.stop(); + classSound.mem.setInt(registerStartStopFrequency + 1, classSound.mem.int[registerStartStopFrequency + 1] & 0b01111111); + }, this.duration); // Duration in ms + } + + checkMemWrite(index: number) { + if (index === registerWaveformDuration) this.updateWaveformDuration(); + if (index === registerStartStopFrequency || index === registerStartStopFrequency + 1) this.updateStartStopFrequency(index); + } + + updateWaveformDuration() { + // registerWaveformDuration + // [2 Bits Waveform | 6 Bits Duration *10 in ms] + let waveform: OscillatorType; + switch ((this.mem.int[registerWaveformDuration] & 0b11000000) >> 6) { + case 0: + waveform = 'sine'; + break; + case 1: + waveform = 'square'; + break; + case 2: + waveform = 'sawtooth'; + break; + case 3: + waveform = 'triangle'; + break; + + default: + waveform = 'sine'; + break; + } + + this.waveform = waveform; + + this.duration = (this.mem.int[registerWaveformDuration] & 0b00111111) * 10; + } + + updateStartStopFrequency(index: number) { + // registerStartStopFrequency + // [1 Bit Start/Stop | 7 Bits Frequency] + this.frequency = ((this.mem.int[registerStartStopFrequency + 1] & 0b01111111) << 8) | this.mem.int[registerStartStopFrequency]; + + if ((this.mem.int[registerStartStopFrequency + 1] & 0b10000000) >> 7 === 1) { + this.start(); + } + } +}