From 8b66f997887c9394e0cf35709d99e903eadd50d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:57:00 +0000 Subject: [PATCH 1/3] Initial plan From 0df08f916f9a0914dee046f8c22b09a18bed66ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:07:37 +0000 Subject: [PATCH 2/3] Implement cassette motor relay sound feature --- public/sounds/tape/motor_off.wav | Bin 0 -> 2248 bytes public/sounds/tape/motor_on.wav | Bin 0 -> 2248 bytes src/6502.js | 5 +- src/acia.js | 9 ++- src/fake6502.js | 12 ++- src/main.js | 1 + src/tapenoise.js | 88 ++++++++++++++++++++ src/web/audio-handler.js | 6 ++ tests/unit/test-acia-tape-integration.js | 66 +++++++++++++++ tests/unit/test-tapenoise.js | 98 +++++++++++++++++++++++ 10 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 public/sounds/tape/motor_off.wav create mode 100644 public/sounds/tape/motor_on.wav create mode 100644 src/tapenoise.js create mode 100644 tests/unit/test-acia-tape-integration.js create mode 100644 tests/unit/test-tapenoise.js diff --git a/public/sounds/tape/motor_off.wav b/public/sounds/tape/motor_off.wav new file mode 100644 index 0000000000000000000000000000000000000000..9ce0ea272c150e67970a1589af92ea7dc2522918 GIT binary patch literal 2248 zcmWIYbaOkv!N3si80MOmTcRMqz`(!=gi2uy3@&RJ7?>CY7*Y~T66XNbFfe5D{gnQv ze%DakGRQW{PRr)8*(JR+6h=5| zJ6ZFDMCHUaKI^-go-;2nH#Z5(bNA6x?)QIw=rhamx`^q@6{{TA_^S0>Yq5r}imF_L*a_YumVdvVeq8u+_9MN! zvv2a!k zA5vcNKGk{n{$B3A756(HJ$_dC=FcbLU$KnU9Q^!8M1mw6WVGd+pANghC{zOzd!g?^`7Bv-)qCyFJ3Qs>;7To=ULw${dQ&OW~t_Q#66v_TA)&} zTX35I7ylBTm7LCOiCY7*Y~T66XNbFfhCobXPj0 z?`x@T$6$BMLP!6Y;vvBT2J`pj_e(ArpUgQ@am4pz-6hxiliz9mU(bI@E?V2)4d8?$k)#LRq84H;x8N_Pj z$eIgWX3YMq{!H|?$mK)l^3RE1O24`PvGa#Ff9G*KNL*B^(ORu1pua}@yb8axHNOv2 zz}LW+PWRtme}1{-^3-cgcXgfxer)}Fno~`5gWO8h4owa%HFXWei4xL$3z&qyN56@9 zy!#IO&AJ=Px3@mn`J(xg%U@{@ZK35-PZhFNeyL1Q%8>mkdY$Jy)A}E7??axKKUBQe zclW^kh9{}7Z+zDH+s_ujzg^5wCPe { + // Safari doesn't support the Promise stuff directly, so we create + // our own Promise here. + const data = await utils.loadData(sound); + return await new Promise((resolve) => { + context.decodeAudioData(data.buffer, (decodedData) => { + resolve(decodedData); + }); + }); + }), + ); + const keys = _.keys(sounds); + const result = {}; + for (let i = 0; i < keys.length; ++i) { + result[keys[i]] = loaded[i]; + } + return result; +} + +export class TapeNoise { + constructor(context) { + this.context = context; + this.sounds = {}; + this.gain = context.createGain(); + this.gain.gain.value = VOLUME; + this.gain.connect(context.destination); + // workaround for older safaris that GC sounds when they're playing... + this.playing = []; + } + + async initialise() { + const sounds = await loadSounds(this.context, { + motorOn: "sounds/tape/motor_on.wav", + motorOff: "sounds/tape/motor_off.wav", + }); + this.sounds = sounds; + } + + oneShot(sound) { + const duration = sound.duration; + const context = this.context; + if (context.state !== "running") return duration; + const source = context.createBufferSource(); + source.buffer = sound; + source.connect(this.gain); + source.start(); + return duration; + } + + motorOn() { + if (this.sounds.motorOn) { + this.oneShot(this.sounds.motorOn); + } + } + + motorOff() { + if (this.sounds.motorOff) { + this.oneShot(this.sounds.motorOff); + } + } + + mute() { + this.gain.gain.value = 0; + } + + unmute() { + this.gain.gain.value = VOLUME; + } +} + +export class FakeTapeNoise { + constructor() {} + initialise() { + return Promise.resolve(); + } + motorOn() {} + motorOff() {} + mute() {} + unmute() {} +} diff --git a/src/web/audio-handler.js b/src/web/audio-handler.js index d84f66cb..3bb71ae1 100644 --- a/src/web/audio-handler.js +++ b/src/web/audio-handler.js @@ -1,6 +1,7 @@ import { SmoothieChart, TimeSeries } from "smoothie"; import { FakeSoundChip, SoundChip } from "../soundchip.js"; import { DdNoise, FakeDdNoise } from "../ddnoise.js"; +import { TapeNoise, FakeTapeNoise } from "../tapenoise.js"; import { Music5000, FakeMusic5000 } from "../music5000.js"; // Using this approach means when jsbeeb is embedded in other projects, vite doesn't have a fit. @@ -35,6 +36,7 @@ export class AudioHandler { this.audioContext.onstatechange = () => this.checkStatus(); this.soundChip = new SoundChip((buffer, time) => this._onBuffer(buffer, time)); this.ddNoise = noSeek ? new FakeDdNoise() : new DdNoise(this.audioContext); + this.tapeNoise = new TapeNoise(this.audioContext); this._setup(audioFilterFreq, audioFilterQ).then(); } else { if (this.audioContext && !this.audioContext.audioWorklet) { @@ -52,6 +54,7 @@ export class AudioHandler { } this.soundChip = new FakeSoundChip(); this.ddNoise = new FakeDdNoise(); + this.tapeNoise = new FakeTapeNoise(); } this.warningNode.on("mousedown", () => this.tryResume()); @@ -132,15 +135,18 @@ export class AudioHandler { async initialise() { await this.ddNoise.initialise(); + await this.tapeNoise.initialise(); } mute() { this.soundChip.mute(); this.ddNoise.mute(); + this.tapeNoise.mute(); } unmute() { this.soundChip.unmute(); this.ddNoise.unmute(); + this.tapeNoise.unmute(); } } diff --git a/tests/unit/test-acia-tape-integration.js b/tests/unit/test-acia-tape-integration.js new file mode 100644 index 00000000..65fc228a --- /dev/null +++ b/tests/unit/test-acia-tape-integration.js @@ -0,0 +1,66 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { Acia } from "../../src/acia.js"; + +describe("ACIA tape noise integration", () => { + let mockCpu, mockToneGen, mockScheduler, mockRs423Handler, mockTapeNoise; + let acia; + + beforeEach(() => { + mockCpu = { interrupt: 0 }; + mockToneGen = { mute: vi.fn(), tone: vi.fn() }; + mockScheduler = { + newTask: vi.fn((_fn) => ({ + cancel: vi.fn(), + ensureScheduled: vi.fn(), + })), + }; + mockRs423Handler = {}; + mockTapeNoise = { + motorOn: vi.fn(), + motorOff: vi.fn(), + }; + + acia = new Acia(mockCpu, mockToneGen, mockScheduler, mockRs423Handler, mockTapeNoise); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("setMotor with tape noise", () => { + it("should call tape noise motorOn when motor turns on", () => { + acia.motorOn = false; + + acia.setMotor(true); + + expect(mockTapeNoise.motorOn).toHaveBeenCalledOnce(); + expect(acia.motorOn).toBe(true); + }); + + it("should call tape noise motorOff when motor turns off", () => { + acia.motorOn = true; + + acia.setMotor(false); + + expect(mockTapeNoise.motorOff).toHaveBeenCalledOnce(); + expect(acia.motorOn).toBe(false); + }); + + it("should not call tape noise methods when motor state doesn't change", () => { + acia.motorOn = true; + + acia.setMotor(true); + + expect(mockTapeNoise.motorOn).not.toHaveBeenCalled(); + expect(mockTapeNoise.motorOff).not.toHaveBeenCalled(); + }); + + it("should handle missing tape noise gracefully", () => { + const aciaWithoutTapeNoise = new Acia(mockCpu, mockToneGen, mockScheduler, mockRs423Handler, null); + aciaWithoutTapeNoise.motorOn = false; + + expect(() => aciaWithoutTapeNoise.setMotor(true)).not.toThrow(); + expect(aciaWithoutTapeNoise.motorOn).toBe(true); + }); + }); +}); diff --git a/tests/unit/test-tapenoise.js b/tests/unit/test-tapenoise.js new file mode 100644 index 00000000..5735d8b9 --- /dev/null +++ b/tests/unit/test-tapenoise.js @@ -0,0 +1,98 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { TapeNoise, FakeTapeNoise } from "../../src/tapenoise.js"; + +describe("TapeNoise", () => { + let mockContext; + let tapeNoise; + + beforeEach(() => { + mockContext = { + state: "running", + createGain: vi.fn(() => ({ + gain: { value: 0 }, + connect: vi.fn(), + })), + createBufferSource: vi.fn(() => ({ + buffer: null, + connect: vi.fn(), + start: vi.fn(), + })), + destination: {}, + decodeAudioData: vi.fn((buffer, callback) => { + // Mock decoded audio data + const mockDecodedData = { duration: 0.05 }; + callback(mockDecodedData); + }), + }; + + global.fetch = vi.fn(() => + Promise.resolve({ + arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)), + }), + ); + + tapeNoise = new TapeNoise(mockContext); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("TapeNoise class", () => { + it("should create gain node and connect to destination", () => { + expect(mockContext.createGain).toHaveBeenCalled(); + }); + + it("should initialize with sound files", async () => { + await tapeNoise.initialise(); + expect(tapeNoise.sounds).toBeDefined(); + }); + + it("should play motor on sound when motorOn is called", () => { + const mockSound = { duration: 0.05 }; + tapeNoise.sounds = { motorOn: mockSound }; + + tapeNoise.motorOn(); + + expect(mockContext.createBufferSource).toHaveBeenCalled(); + }); + + it("should play motor off sound when motorOff is called", () => { + const mockSound = { duration: 0.05 }; + tapeNoise.sounds = { motorOff: mockSound }; + + tapeNoise.motorOff(); + + expect(mockContext.createBufferSource).toHaveBeenCalled(); + }); + + it("should handle mute/unmute", () => { + const mockGain = { gain: { value: 0.25 } }; + tapeNoise.gain = mockGain; + + tapeNoise.mute(); + expect(mockGain.gain.value).toBe(0); + + tapeNoise.unmute(); + expect(mockGain.gain.value).toBe(0.25); + }); + }); + + describe("FakeTapeNoise class", () => { + it("should create fake implementation", () => { + const fakeTapeNoise = new FakeTapeNoise(); + + expect(() => fakeTapeNoise.initialise()).not.toThrow(); + expect(() => fakeTapeNoise.motorOn()).not.toThrow(); + expect(() => fakeTapeNoise.motorOff()).not.toThrow(); + expect(() => fakeTapeNoise.mute()).not.toThrow(); + expect(() => fakeTapeNoise.unmute()).not.toThrow(); + }); + + it("should return resolved promise for initialise", async () => { + const fakeTapeNoise = new FakeTapeNoise(); + const result = await fakeTapeNoise.initialise(); + expect(result).toBeUndefined(); + }); + }); +}); From 30db1529aaf175202a07cbc5dd2f8b2464000f95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:11:55 +0000 Subject: [PATCH 3/3] Deduplicate audio code and rename TapeNoise to RelayNoise for clarity Co-authored-by: mattgodbolt <633973+mattgodbolt@users.noreply.github.com> --- src/6502.js | 6 +-- src/acia.js | 12 ++--- src/{tapenoise.js => audio-utils.js} | 44 +++------------- src/ddnoise.js | 50 ++----------------- src/fake6502.js | 4 +- src/main.js | 2 +- src/relaynoise.js | 39 +++++++++++++++ src/web/audio-handler.js | 12 ++--- ...tion.js => test-acia-relay-integration.js} | 34 ++++++------- .../{test-tapenoise.js => test-relaynoise.js} | 46 ++++++++--------- 10 files changed, 106 insertions(+), 143 deletions(-) rename src/{tapenoise.js => audio-utils.js} (62%) create mode 100644 src/relaynoise.js rename tests/unit/{test-acia-tape-integration.js => test-acia-relay-integration.js} (50%) rename tests/unit/{test-tapenoise.js => test-relaynoise.js} (64%) diff --git a/src/6502.js b/src/6502.js index 4f66200b..b8c5b857 100644 --- a/src/6502.js +++ b/src/6502.js @@ -569,7 +569,7 @@ function is1MHzAccess(addr) { } export class Cpu6502 extends Base6502 { - constructor(model, dbgr, video_, soundChip_, ddNoise_, tapeNoise_, music5000_, cmos, config, econet_) { + constructor(model, dbgr, video_, soundChip_, ddNoise_, relayNoise_, music5000_, cmos, config, econet_) { super(model); this.config = fixUpConfig(config); this.debugFlags = this.config.debugFlags; @@ -582,7 +582,7 @@ export class Cpu6502 extends Base6502 { this.soundChip = soundChip_; this.music5000 = music5000_; this.ddNoise = ddNoise_; - this.tapeNoise = tapeNoise_; + this.relayNoise = relayNoise_; this.memStatOffsetByIFetchBank = 0; this.memStatOffset = 0; this.memStat = new Uint8Array(512); @@ -631,7 +631,7 @@ export class Cpu6502 extends Base6502 { this.config.getGamepads, ); this.uservia = new via.UserVia(this, this.scheduler, this.model.isMaster, this.config.userPort); - this.acia = new Acia(this, this.soundChip.toneGenerator, this.scheduler, this.touchScreen, this.tapeNoise); + this.acia = new Acia(this, this.soundChip.toneGenerator, this.scheduler, this.touchScreen, this.relayNoise); this.serial = new Serial(this.acia); this.adconverter = new Adc(this.sysvia, this.scheduler); this.soundChip.setScheduler(this.scheduler); diff --git a/src/acia.js b/src/acia.js index 62425877..63a6509d 100644 --- a/src/acia.js +++ b/src/acia.js @@ -5,11 +5,11 @@ // http://www.classiccmp.org/dunfield/r/6850.pdf export class Acia { - constructor(cpu, toneGen, scheduler, rs423Handler, tapeNoise) { + constructor(cpu, toneGen, scheduler, rs423Handler, relayNoise) { this.cpu = cpu; this.toneGen = toneGen; this.rs423Handler = rs423Handler; - this.tapeNoise = tapeNoise; + this.relayNoise = relayNoise; this.sr = 0x00; this.cr = 0x00; @@ -59,15 +59,15 @@ export class Acia { setMotor(on) { if (on && !this.motorOn) { this.runTape(); - if (this.tapeNoise) { - this.tapeNoise.motorOn(); + if (this.relayNoise) { + this.relayNoise.motorOn(); } } else if (!on && this.motorOn) { this.toneGen.mute(); this.runTapeTask.cancel(); this.setTapeCarrier(false); - if (this.tapeNoise) { - this.tapeNoise.motorOff(); + if (this.relayNoise) { + this.relayNoise.motorOff(); } } this.motorOn = on; diff --git a/src/tapenoise.js b/src/audio-utils.js similarity index 62% rename from src/tapenoise.js rename to src/audio-utils.js index 37be9314..057657f6 100644 --- a/src/tapenoise.js +++ b/src/audio-utils.js @@ -2,9 +2,7 @@ import * as utils from "./utils.js"; import _ from "underscore"; -const VOLUME = 0.25; - -async function loadSounds(context, sounds) { +export async function loadSounds(context, sounds) { const loaded = await Promise.all( _.map(sounds, async (sound) => { // Safari doesn't support the Promise stuff directly, so we create @@ -25,23 +23,16 @@ async function loadSounds(context, sounds) { return result; } -export class TapeNoise { - constructor(context) { +export class BaseAudioNoise { + constructor(context, volume = 0.25) { this.context = context; this.sounds = {}; this.gain = context.createGain(); - this.gain.gain.value = VOLUME; + this.gain.gain.value = volume; this.gain.connect(context.destination); // workaround for older safaris that GC sounds when they're playing... this.playing = []; - } - - async initialise() { - const sounds = await loadSounds(this.context, { - motorOn: "sounds/tape/motor_on.wav", - motorOff: "sounds/tape/motor_off.wav", - }); - this.sounds = sounds; + this.volume = volume; } oneShot(sound) { @@ -55,34 +46,11 @@ export class TapeNoise { return duration; } - motorOn() { - if (this.sounds.motorOn) { - this.oneShot(this.sounds.motorOn); - } - } - - motorOff() { - if (this.sounds.motorOff) { - this.oneShot(this.sounds.motorOff); - } - } - mute() { this.gain.gain.value = 0; } unmute() { - this.gain.gain.value = VOLUME; - } -} - -export class FakeTapeNoise { - constructor() {} - initialise() { - return Promise.resolve(); + this.gain.gain.value = this.volume; } - motorOn() {} - motorOff() {} - mute() {} - unmute() {} } diff --git a/src/ddnoise.js b/src/ddnoise.js index e3aaa880..26da40ff 100644 --- a/src/ddnoise.js +++ b/src/ddnoise.js @@ -1,23 +1,16 @@ "use strict"; -import * as utils from "./utils.js"; +import { loadSounds, BaseAudioNoise } from "./audio-utils.js"; import _ from "underscore"; const IDLE = 0; const SPIN_UP = 1; const SPINNING = 2; -const VOLUME = 0.25; -export class DdNoise { +export class DdNoise extends BaseAudioNoise { constructor(context) { - this.context = context; - this.sounds = {}; + super(context, 0.25); this.state = IDLE; this.motor = null; - this.gain = context.createGain(); - this.gain.gain.value = VOLUME; - this.gain.connect(context.destination); - // workaround for older safaris that GC sounds when they're playing... - this.playing = []; } async initialise() { const sounds = await loadSounds(this.context, { @@ -31,16 +24,6 @@ export class DdNoise { }); this.sounds = sounds; } - oneShot(sound) { - const duration = sound.duration; - const context = this.context; - if (context.state !== "running") return duration; - const source = context.createBufferSource(); - source.buffer = sound; - source.connect(this.gain); - source.start(); - return duration; - } play(sound, loop) { if (this.context.state !== "running") return Promise.reject(); return new Promise((resolve) => { @@ -94,33 +77,6 @@ export class DdNoise { else if (diff <= 40) return this.oneShot(this.sounds.seek2); else return this.oneShot(this.sounds.seek3); } - mute() { - this.gain.gain.value = 0; - } - unmute() { - this.gain.gain.value = VOLUME; - } -} - -async function loadSounds(context, sounds) { - const loaded = await Promise.all( - _.map(sounds, async (sound) => { - // Safari doesn't support the Promise stuff directly, so we create - // our own Promise here. - const data = await utils.loadData(sound); - return await new Promise((resolve) => { - context.decodeAudioData(data.buffer, (decodedData) => { - resolve(decodedData); - }); - }); - }), - ); - const keys = _.keys(sounds); - const result = {}; - for (let i = 0; i < keys.length; ++i) { - result[keys[i]] = loaded[i]; - } - return result; } export class FakeDdNoise { diff --git a/src/fake6502.js b/src/fake6502.js index 87d73c8f..cb1b7ca1 100644 --- a/src/fake6502.js +++ b/src/fake6502.js @@ -5,7 +5,7 @@ import { FakeVideo } from "./video.js"; import { FakeSoundChip } from "./soundchip.js"; import { findModel, TEST_6502, TEST_65C02, TEST_65C12 } from "./models.js"; import { FakeDdNoise } from "./ddnoise.js"; -import { FakeTapeNoise } from "./tapenoise.js"; +import { FakeRelayNoise } from "./relaynoise.js"; import { Cpu6502 } from "./6502.js"; import { Cmos } from "./cmos.js"; import { FakeMusic5000 } from "./music5000.js"; @@ -27,7 +27,7 @@ export function fake6502(model, opts) { video, soundChip, new FakeDdNoise(), - new FakeTapeNoise(), + new FakeRelayNoise(), new FakeMusic5000(), new Cmos(), ); diff --git a/src/main.js b/src/main.js index b6228d5a..a122b172 100644 --- a/src/main.js +++ b/src/main.js @@ -522,7 +522,7 @@ processor = new Cpu6502( video, audioHandler.soundChip, audioHandler.ddNoise, - audioHandler.tapeNoise, + audioHandler.relayNoise, model.hasMusic5000 ? audioHandler.music5000 : null, cmos, emulationConfig, diff --git a/src/relaynoise.js b/src/relaynoise.js new file mode 100644 index 00000000..9d79f127 --- /dev/null +++ b/src/relaynoise.js @@ -0,0 +1,39 @@ +"use strict"; +import { loadSounds, BaseAudioNoise } from "./audio-utils.js"; + +export class RelayNoise extends BaseAudioNoise { + constructor(context) { + super(context, 0.25); + } + + async initialise() { + const sounds = await loadSounds(this.context, { + motorOn: "sounds/tape/motor_on.wav", + motorOff: "sounds/tape/motor_off.wav", + }); + this.sounds = sounds; + } + + motorOn() { + if (this.sounds.motorOn) { + this.oneShot(this.sounds.motorOn); + } + } + + motorOff() { + if (this.sounds.motorOff) { + this.oneShot(this.sounds.motorOff); + } + } +} + +export class FakeRelayNoise { + constructor() {} + initialise() { + return Promise.resolve(); + } + motorOn() {} + motorOff() {} + mute() {} + unmute() {} +} diff --git a/src/web/audio-handler.js b/src/web/audio-handler.js index 3bb71ae1..408bee4e 100644 --- a/src/web/audio-handler.js +++ b/src/web/audio-handler.js @@ -1,7 +1,7 @@ import { SmoothieChart, TimeSeries } from "smoothie"; import { FakeSoundChip, SoundChip } from "../soundchip.js"; import { DdNoise, FakeDdNoise } from "../ddnoise.js"; -import { TapeNoise, FakeTapeNoise } from "../tapenoise.js"; +import { RelayNoise, FakeRelayNoise } from "../relaynoise.js"; import { Music5000, FakeMusic5000 } from "../music5000.js"; // Using this approach means when jsbeeb is embedded in other projects, vite doesn't have a fit. @@ -36,7 +36,7 @@ export class AudioHandler { this.audioContext.onstatechange = () => this.checkStatus(); this.soundChip = new SoundChip((buffer, time) => this._onBuffer(buffer, time)); this.ddNoise = noSeek ? new FakeDdNoise() : new DdNoise(this.audioContext); - this.tapeNoise = new TapeNoise(this.audioContext); + this.relayNoise = new RelayNoise(this.audioContext); this._setup(audioFilterFreq, audioFilterQ).then(); } else { if (this.audioContext && !this.audioContext.audioWorklet) { @@ -54,7 +54,7 @@ export class AudioHandler { } this.soundChip = new FakeSoundChip(); this.ddNoise = new FakeDdNoise(); - this.tapeNoise = new FakeTapeNoise(); + this.relayNoise = new FakeRelayNoise(); } this.warningNode.on("mousedown", () => this.tryResume()); @@ -135,18 +135,18 @@ export class AudioHandler { async initialise() { await this.ddNoise.initialise(); - await this.tapeNoise.initialise(); + await this.relayNoise.initialise(); } mute() { this.soundChip.mute(); this.ddNoise.mute(); - this.tapeNoise.mute(); + this.relayNoise.mute(); } unmute() { this.soundChip.unmute(); this.ddNoise.unmute(); - this.tapeNoise.unmute(); + this.relayNoise.unmute(); } } diff --git a/tests/unit/test-acia-tape-integration.js b/tests/unit/test-acia-relay-integration.js similarity index 50% rename from tests/unit/test-acia-tape-integration.js rename to tests/unit/test-acia-relay-integration.js index 65fc228a..a5bae826 100644 --- a/tests/unit/test-acia-tape-integration.js +++ b/tests/unit/test-acia-relay-integration.js @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { Acia } from "../../src/acia.js"; -describe("ACIA tape noise integration", () => { - let mockCpu, mockToneGen, mockScheduler, mockRs423Handler, mockTapeNoise; +describe("ACIA relay noise integration", () => { + let mockCpu, mockToneGen, mockScheduler, mockRs423Handler, mockRelayNoise; let acia; beforeEach(() => { @@ -15,52 +15,52 @@ describe("ACIA tape noise integration", () => { })), }; mockRs423Handler = {}; - mockTapeNoise = { + mockRelayNoise = { motorOn: vi.fn(), motorOff: vi.fn(), }; - acia = new Acia(mockCpu, mockToneGen, mockScheduler, mockRs423Handler, mockTapeNoise); + acia = new Acia(mockCpu, mockToneGen, mockScheduler, mockRs423Handler, mockRelayNoise); }); afterEach(() => { vi.restoreAllMocks(); }); - describe("setMotor with tape noise", () => { - it("should call tape noise motorOn when motor turns on", () => { + describe("setMotor with relay noise", () => { + it("should call relay noise motorOn when motor turns on", () => { acia.motorOn = false; acia.setMotor(true); - expect(mockTapeNoise.motorOn).toHaveBeenCalledOnce(); + expect(mockRelayNoise.motorOn).toHaveBeenCalledOnce(); expect(acia.motorOn).toBe(true); }); - it("should call tape noise motorOff when motor turns off", () => { + it("should call relay noise motorOff when motor turns off", () => { acia.motorOn = true; acia.setMotor(false); - expect(mockTapeNoise.motorOff).toHaveBeenCalledOnce(); + expect(mockRelayNoise.motorOff).toHaveBeenCalledOnce(); expect(acia.motorOn).toBe(false); }); - it("should not call tape noise methods when motor state doesn't change", () => { + it("should not call relay noise methods when motor state doesn't change", () => { acia.motorOn = true; acia.setMotor(true); - expect(mockTapeNoise.motorOn).not.toHaveBeenCalled(); - expect(mockTapeNoise.motorOff).not.toHaveBeenCalled(); + expect(mockRelayNoise.motorOn).not.toHaveBeenCalled(); + expect(mockRelayNoise.motorOff).not.toHaveBeenCalled(); }); - it("should handle missing tape noise gracefully", () => { - const aciaWithoutTapeNoise = new Acia(mockCpu, mockToneGen, mockScheduler, mockRs423Handler, null); - aciaWithoutTapeNoise.motorOn = false; + it("should handle missing relay noise gracefully", () => { + const aciaWithoutRelayNoise = new Acia(mockCpu, mockToneGen, mockScheduler, mockRs423Handler, null); + aciaWithoutRelayNoise.motorOn = false; - expect(() => aciaWithoutTapeNoise.setMotor(true)).not.toThrow(); - expect(aciaWithoutTapeNoise.motorOn).toBe(true); + expect(() => aciaWithoutRelayNoise.setMotor(true)).not.toThrow(); + expect(aciaWithoutRelayNoise.motorOn).toBe(true); }); }); }); diff --git a/tests/unit/test-tapenoise.js b/tests/unit/test-relaynoise.js similarity index 64% rename from tests/unit/test-tapenoise.js rename to tests/unit/test-relaynoise.js index 5735d8b9..1ba5dcad 100644 --- a/tests/unit/test-tapenoise.js +++ b/tests/unit/test-relaynoise.js @@ -1,9 +1,9 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { TapeNoise, FakeTapeNoise } from "../../src/tapenoise.js"; +import { RelayNoise, FakeRelayNoise } from "../../src/relaynoise.js"; -describe("TapeNoise", () => { +describe("RelayNoise", () => { let mockContext; - let tapeNoise; + let relayNoise; beforeEach(() => { mockContext = { @@ -31,67 +31,67 @@ describe("TapeNoise", () => { }), ); - tapeNoise = new TapeNoise(mockContext); + relayNoise = new RelayNoise(mockContext); }); afterEach(() => { vi.restoreAllMocks(); }); - describe("TapeNoise class", () => { + describe("RelayNoise class", () => { it("should create gain node and connect to destination", () => { expect(mockContext.createGain).toHaveBeenCalled(); }); it("should initialize with sound files", async () => { - await tapeNoise.initialise(); - expect(tapeNoise.sounds).toBeDefined(); + await relayNoise.initialise(); + expect(relayNoise.sounds).toBeDefined(); }); it("should play motor on sound when motorOn is called", () => { const mockSound = { duration: 0.05 }; - tapeNoise.sounds = { motorOn: mockSound }; + relayNoise.sounds = { motorOn: mockSound }; - tapeNoise.motorOn(); + relayNoise.motorOn(); expect(mockContext.createBufferSource).toHaveBeenCalled(); }); it("should play motor off sound when motorOff is called", () => { const mockSound = { duration: 0.05 }; - tapeNoise.sounds = { motorOff: mockSound }; + relayNoise.sounds = { motorOff: mockSound }; - tapeNoise.motorOff(); + relayNoise.motorOff(); expect(mockContext.createBufferSource).toHaveBeenCalled(); }); it("should handle mute/unmute", () => { const mockGain = { gain: { value: 0.25 } }; - tapeNoise.gain = mockGain; + relayNoise.gain = mockGain; - tapeNoise.mute(); + relayNoise.mute(); expect(mockGain.gain.value).toBe(0); - tapeNoise.unmute(); + relayNoise.unmute(); expect(mockGain.gain.value).toBe(0.25); }); }); - describe("FakeTapeNoise class", () => { + describe("FakeRelayNoise class", () => { it("should create fake implementation", () => { - const fakeTapeNoise = new FakeTapeNoise(); + const fakeRelayNoise = new FakeRelayNoise(); - expect(() => fakeTapeNoise.initialise()).not.toThrow(); - expect(() => fakeTapeNoise.motorOn()).not.toThrow(); - expect(() => fakeTapeNoise.motorOff()).not.toThrow(); - expect(() => fakeTapeNoise.mute()).not.toThrow(); - expect(() => fakeTapeNoise.unmute()).not.toThrow(); + expect(() => fakeRelayNoise.initialise()).not.toThrow(); + expect(() => fakeRelayNoise.motorOn()).not.toThrow(); + expect(() => fakeRelayNoise.motorOff()).not.toThrow(); + expect(() => fakeRelayNoise.mute()).not.toThrow(); + expect(() => fakeRelayNoise.unmute()).not.toThrow(); }); it("should return resolved promise for initialise", async () => { - const fakeTapeNoise = new FakeTapeNoise(); - const result = await fakeTapeNoise.initialise(); + const fakeRelayNoise = new FakeRelayNoise(); + const result = await fakeRelayNoise.initialise(); expect(result).toBeUndefined(); }); });