From 4201936df7a58eb084d7f13017d12cc48f5fb623 Mon Sep 17 00:00:00 2001 From: 0Xero7 Date: Mon, 25 Nov 2024 22:46:15 +0530 Subject: [PATCH 1/4] play move sound everytime a new move is received or a move is selected --- frontend/app.ts | 11 +++++++++-- frontend/audio.ts | 15 +++++++++++++++ static/static/public_sound_standard_Move.mp3 | Bin 0 -> 2547 bytes 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 frontend/audio.ts create mode 100644 static/static/public_sound_standard_Move.mp3 diff --git a/frontend/app.ts b/frontend/app.ts index 05b22d5..f84d5cb 100644 --- a/frontend/app.ts +++ b/frontend/app.ts @@ -1,4 +1,5 @@ import {BoardArea} from './board_area'; +import {AudioPlayer} from './audio'; import {GameSelection} from './game_selection'; import {MoveList} from './movelist'; import {MultiPvView} from './multipv_view'; @@ -28,6 +29,7 @@ export class App implements WebsocketObserver { private boardArea: BoardArea; private jsHash?: string; private gameIsLive: boolean = false; + private audioPlayer: AudioPlayer; constructor() { this.gameSelection = new GameSelection( @@ -42,6 +44,7 @@ export class App implements WebsocketObserver { this.multiPvView.addObserver(this); this.websocketFeed = new WebSocketFeed(); this.websocketFeed.addObserver(this); + this.audioPlayer = new AudioPlayer(); window.addEventListener('keydown', (event: KeyboardEvent) => { if (event.key === 'Escape') this.moveList.unselectVariation(); }); @@ -78,8 +81,11 @@ export class App implements WebsocketObserver { this.gameSelection.updateGames(games); } public onPositionReceived(position: WsPositionData[]): void { - this.moveList.updatePositions( - position.filter(p => p.gameId == this.curGameId)); + const filteredPositions = position.filter(p => p.gameId == this.curGameId); + this.moveList.updatePositions(filteredPositions); + if (filteredPositions.length > 0) { + this.audioPlayer.playMoveAudio(); + } } public onEvaluationReceived(evaluation: WsEvaluationData[]): void { let evals = evaluation.filter( @@ -119,6 +125,7 @@ export class App implements WebsocketObserver { this.multiPvView.setPosition(position); this.websocketFeed.setPosition(position.ply); this.boardArea.resetPvVisualization(); + this.audioPlayer.playMoveAudio(); } this.boardArea.updatePosition(position); } diff --git a/frontend/audio.ts b/frontend/audio.ts new file mode 100644 index 0000000..41d151c --- /dev/null +++ b/frontend/audio.ts @@ -0,0 +1,15 @@ +export class AudioPlayer { + private move : HTMLAudioElement; + + constructor() { + this.move = new Audio('static/public_sound_standard_Move.mp3'); + } + + public playMoveAudio(): void { + try { + this.move.play(); + } catch (e) { + // Need user interaction to play audio + } + } +} \ No newline at end of file diff --git a/static/static/public_sound_standard_Move.mp3 b/static/static/public_sound_standard_Move.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7ed0cf66581fd7c1e3dc8b08303db12d9ef55f1f GIT binary patch literal 2547 zcmeZtF=l1}f#ML)5F;Qh3B=*~`6;P+3I&;|$*Bsu3dI?TMFk2OnI$0+5fMOX4Ip;T zFQ_caOwTAmuoR383=DJ>g7ZuBQd08Mi}jN8bD=s!fI5Qn(@M${i&7aJ%bm5g70QhC z4FCV0a0DoW3L-M|(t#rUK+Fk5WemIsAcX)~2rvTyHY30}1bB)7pAg_b0s!p_@Nx8Y zHP$mUFksmUbC9Tl3QMaa1LFgbJHka}Vqg*@h5r9r;0Ocr0S3l@Kp!hGFxVepU|`8q z*Xg|VrbCdU@?7yPH#g1(12)Ek1~=B|vo^Q)ET31c`Mm!~#5V1YjR_St_VeD&f72$B z%EA8R`Mjw%-@ZyWC+f0x^&hAa{h+UGQCV#FKj^Rce~>+z&1m-ge{p`npRcARQy5ry zHh8!sZpq?nU}}&_U|>-bJ~$y+A*fAb;kKlp3CB1UoD!Ot(^8oa11!9&yV%`&=dUi;>5c!wRA;rX1@w3i!D2q`fg* zW;3x^yv;|BpPBudXWFF*JznP*1_u+;0^T2&KG9+$ARNSZ*(8a_kayKL5ULB5zcVDcAkV9}nBAo(0@}x~!peU7yf5_S5Hiyx#0m<#o7p zE`2WR8~f^gGrAtJR6o9!`u9p*?Y$HZug+SPt8?bfI0TFn_4NPDf|Iu-t2nE0AGpdZ zJdx*A%fp1z7AKi(#oQj<7SCjDI?RyIw$j3AQKMMKnT;j$4U7zR1zI@rCtN;uLSn*Y z)v3!=Tt%j&WF;(4PfiJR6%`LKvj}tetyDVEse8q;DE&)zISCV2&zi09ZQH~n0wp2| zh2~k^TQ9Gi?C3X3ou^NA*(u*io?g+>+q%@|&N_NVjf3H|?G;83xyK3zPVfc160#KX zm^OKFgM-mC7jxB(*4jr`x+L*^I#TMMvWJg*(Rw9eORZ1yT!L9lR!#IvTo9zcamGpM z4=G~4A}b~wJi^dAt6^?Z+Uavv=l&{x&C?f~wEL=(k^av^_NV^{HH&ceU6)r#>Ji`f z%^)T2NRPt(grH>{ix+Y*`IwkFa@nvkoLJ!HCUbn^3?{D~Pi9qS-phP_a;rcD71*VgZrgs0l$;lC9 zYOwVG?}j568iYLUonvP-C>~q(d}~Xeiu94*z|(BZ9DW;etvX{p-L{=MR^o59INCeO z^Ze|(mSvS^9)3R@x4GGG@9FVSL~`>u$s)&trl{MD$NOgD;_zVI^wS~Gh;E+qz{W6TvYZNn!B_K8EEQk z-fqR&(Uxp|H8%3#vX^dm180B#nfq&l-S+zbw=Dhk{|k^gGVzh@w_l7QGY=gQvOXlD zo#K6|<#1nM=A>Sq`P&-hyAB6UZwffpHeGLhu>i-Gm1*flb5G2kef)Rgt!Jxk^?&o0 z2k!s>|L^wy7yR~}{}Nh}=GoC+yDx!(NnvTH&g{)Cmvj`_yLGN}2eAg1cI{tq<))y9 zh}acASBuxB4~iz7z2rG-^){o0B5plP%b)80KfW#g|Nnpe%m4rX_3i)vZyjsI9tj*$ z_Lz6kiZT3G(4zLf4i7d~&ZmV_P6+4x{~d7TR|8+7x#^mXd=F<&e`eb<^NQDX?a-Ic z6+Cih&7C+cr#cN->emCYp5gxA`Tui)#;(aK``PY%f9dHdTo)J^tyz~^b8!?Oa|mjj z-uYN)wdVg{mo~3U$oUefGH;S~+5cbCVgFzLfBXOcpVHL-s}rR^ZDt6{Vp0=*@umL% zm#093LEvxY{{KAi3S||!0OX(73_cR?)z?S_CR$EOwQc570UAcR$>h3-GJXGlI~@7Y z;N=^v{zk<&k#$Yd#%56m%8aCgZcyd)o#EKqY+=oYsD>niM Date: Tue, 26 Nov 2024 16:45:14 +0530 Subject: [PATCH 2/4] only play move sound when position changes forward by 1 ply --- frontend/app.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/app.ts b/frontend/app.ts index f84d5cb..32fe593 100644 --- a/frontend/app.ts +++ b/frontend/app.ts @@ -83,9 +83,6 @@ export class App implements WebsocketObserver { public onPositionReceived(position: WsPositionData[]): void { const filteredPositions = position.filter(p => p.gameId == this.curGameId); this.moveList.updatePositions(filteredPositions); - if (filteredPositions.length > 0) { - this.audioPlayer.playMoveAudio(); - } } public onEvaluationReceived(evaluation: WsEvaluationData[]): void { let evals = evaluation.filter( @@ -117,6 +114,7 @@ export class App implements WebsocketObserver { public onMoveSelected( position: WsPositionData, pos_changed: boolean, isOngoling: boolean): void { + const currentPly = this.curPly ?? -1; this.curPly = position.ply; const nextMove = this.moveList.getMoveAtPly(position.ply + 1)?.moveUci; this.boardArea.changePosition( @@ -125,7 +123,11 @@ export class App implements WebsocketObserver { this.multiPvView.setPosition(position); this.websocketFeed.setPosition(position.ply); this.boardArea.resetPvVisualization(); - this.audioPlayer.playMoveAudio(); + + // Only play move sound when the next move is 1 ply ahead of the current position + if (position.ply === currentPly + 1) { + this.audioPlayer.playMoveAudio(); + } } this.boardArea.updatePosition(position); } From 13b9145c14c7d8cec89331c896a50c6776dbe7aa Mon Sep 17 00:00:00 2001 From: 0Xero7 Date: Tue, 26 Nov 2024 17:28:41 +0530 Subject: [PATCH 3/4] use AudioContext, make adding other types of sounds (for checks, captures, etc) easy --- frontend/app.ts | 4 ++-- frontend/audio.ts | 58 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/frontend/app.ts b/frontend/app.ts index 32fe593..bbcc36d 100644 --- a/frontend/app.ts +++ b/frontend/app.ts @@ -1,5 +1,5 @@ import {BoardArea} from './board_area'; -import {AudioPlayer} from './audio'; +import {AudioPlayer, AudioEventType} from './audio'; import {GameSelection} from './game_selection'; import {MoveList} from './movelist'; import {MultiPvView} from './multipv_view'; @@ -126,7 +126,7 @@ export class App implements WebsocketObserver { // Only play move sound when the next move is 1 ply ahead of the current position if (position.ply === currentPly + 1) { - this.audioPlayer.playMoveAudio(); + this.audioPlayer.playAudio(AudioEventType.MOVE); } } this.boardArea.updatePosition(position); diff --git a/frontend/audio.ts b/frontend/audio.ts index 41d151c..a66a632 100644 --- a/frontend/audio.ts +++ b/frontend/audio.ts @@ -1,13 +1,63 @@ +export enum AudioEventType { + MOVE, +} + +class DebouncedAudioPlayer { + private audioContext : AudioContext; + private audio: AudioBuffer; + private cooldown: number; + private lastPlayed: number; + + constructor(context: AudioContext, audio: AudioBuffer, cooldown: number = 0) { + this.audioContext = context; + this.audio = audio; + this.cooldown = cooldown; + this.lastPlayed = 0; + } + + public play() { + const now = Date.now(); + if (now - this.lastPlayed < this.cooldown) { + return; + } + this.lastPlayed = now; + + this.audioContext.resume(); + if (this.audioContext.state !== "running") { + return; + } + + const source = this.audioContext.createBufferSource(); + source.buffer = this.audio; + source.connect(this.audioContext.destination); + source.start(0); + } +} + export class AudioPlayer { - private move : HTMLAudioElement; + private sounds: Map; constructor() { - this.move = new Audio('static/public_sound_standard_Move.mp3'); + this.sounds = new Map(); + const context = new AudioContext(); + this.addSound(context, AudioEventType.MOVE, 'static/public_sound_standard_Move.mp3'); + } + + private async addSound(context: AudioContext, eventType: AudioEventType, path: string): Promise { + const moveSound = await this.loadSound(context, path); + this.sounds.set(eventType, new DebouncedAudioPlayer(context, moveSound, 50)); + } + + private async loadSound(context: AudioContext, url: string): Promise { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + const audioBuffer = await context.decodeAudioData(arrayBuffer); + return audioBuffer; } - public playMoveAudio(): void { + public playAudio(eventType: AudioEventType): void { try { - this.move.play(); + this.sounds.get(eventType)?.play(); } catch (e) { // Need user interaction to play audio } From c1a5e4aec34fddab1fe67693430a01fb0babb2ec Mon Sep 17 00:00:00 2001 From: 0Xero7 Date: Sun, 1 Dec 2024 15:05:47 +0530 Subject: [PATCH 4/4] change move sound to a CC0 sound, add attribution text --- frontend/audio.ts | 2 +- static/index.template.html | 1 + static/static/chess_move.mp3 | Bin 0 -> 8784 bytes static/static/public_sound_standard_Move.mp3 | Bin 2547 -> 0 bytes 4 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 static/static/chess_move.mp3 delete mode 100644 static/static/public_sound_standard_Move.mp3 diff --git a/frontend/audio.ts b/frontend/audio.ts index a66a632..ff19839 100644 --- a/frontend/audio.ts +++ b/frontend/audio.ts @@ -40,7 +40,7 @@ export class AudioPlayer { constructor() { this.sounds = new Map(); const context = new AudioContext(); - this.addSound(context, AudioEventType.MOVE, 'static/public_sound_standard_Move.mp3'); + this.addSound(context, AudioEventType.MOVE, 'static/chess_move.mp3'); } private async addSound(context: AudioContext, eventType: AudioEventType, path: string): Promise { diff --git a/static/index.template.html b/static/index.template.html index 1796d67..85dd4bb 100644 --- a/static/index.template.html +++ b/static/index.template.html @@ -71,6 +71,7 @@ Colin M.L. Burnett . Analysis by LCZero engine. + chess pieces.wav by simone_ds (CC0 licence). Source code on GitHub. diff --git a/static/static/chess_move.mp3 b/static/static/chess_move.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8870037424913a103ffe13e090c8893d0ea66b26 GIT binary patch literal 8784 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 MJ^%pxKgmh|1^NjgEC2ui literal 0 HcmV?d00001 diff --git a/static/static/public_sound_standard_Move.mp3 b/static/static/public_sound_standard_Move.mp3 deleted file mode 100644 index 7ed0cf66581fd7c1e3dc8b08303db12d9ef55f1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2547 zcmeZtF=l1}f#ML)5F;Qh3B=*~`6;P+3I&;|$*Bsu3dI?TMFk2OnI$0+5fMOX4Ip;T zFQ_caOwTAmuoR383=DJ>g7ZuBQd08Mi}jN8bD=s!fI5Qn(@M${i&7aJ%bm5g70QhC z4FCV0a0DoW3L-M|(t#rUK+Fk5WemIsAcX)~2rvTyHY30}1bB)7pAg_b0s!p_@Nx8Y zHP$mUFksmUbC9Tl3QMaa1LFgbJHka}Vqg*@h5r9r;0Ocr0S3l@Kp!hGFxVepU|`8q z*Xg|VrbCdU@?7yPH#g1(12)Ek1~=B|vo^Q)ET31c`Mm!~#5V1YjR_St_VeD&f72$B z%EA8R`Mjw%-@ZyWC+f0x^&hAa{h+UGQCV#FKj^Rce~>+z&1m-ge{p`npRcARQy5ry zHh8!sZpq?nU}}&_U|>-bJ~$y+A*fAb;kKlp3CB1UoD!Ot(^8oa11!9&yV%`&=dUi;>5c!wRA;rX1@w3i!D2q`fg* zW;3x^yv;|BpPBudXWFF*JznP*1_u+;0^T2&KG9+$ARNSZ*(8a_kayKL5ULB5zcVDcAkV9}nBAo(0@}x~!peU7yf5_S5Hiyx#0m<#o7p zE`2WR8~f^gGrAtJR6o9!`u9p*?Y$HZug+SPt8?bfI0TFn_4NPDf|Iu-t2nE0AGpdZ zJdx*A%fp1z7AKi(#oQj<7SCjDI?RyIw$j3AQKMMKnT;j$4U7zR1zI@rCtN;uLSn*Y z)v3!=Tt%j&WF;(4PfiJR6%`LKvj}tetyDVEse8q;DE&)zISCV2&zi09ZQH~n0wp2| zh2~k^TQ9Gi?C3X3ou^NA*(u*io?g+>+q%@|&N_NVjf3H|?G;83xyK3zPVfc160#KX zm^OKFgM-mC7jxB(*4jr`x+L*^I#TMMvWJg*(Rw9eORZ1yT!L9lR!#IvTo9zcamGpM z4=G~4A}b~wJi^dAt6^?Z+Uavv=l&{x&C?f~wEL=(k^av^_NV^{HH&ceU6)r#>Ji`f z%^)T2NRPt(grH>{ix+Y*`IwkFa@nvkoLJ!HCUbn^3?{D~Pi9qS-phP_a;rcD71*VgZrgs0l$;lC9 zYOwVG?}j568iYLUonvP-C>~q(d}~Xeiu94*z|(BZ9DW;etvX{p-L{=MR^o59INCeO z^Ze|(mSvS^9)3R@x4GGG@9FVSL~`>u$s)&trl{MD$NOgD;_zVI^wS~Gh;E+qz{W6TvYZNn!B_K8EEQk z-fqR&(Uxp|H8%3#vX^dm180B#nfq&l-S+zbw=Dhk{|k^gGVzh@w_l7QGY=gQvOXlD zo#K6|<#1nM=A>Sq`P&-hyAB6UZwffpHeGLhu>i-Gm1*flb5G2kef)Rgt!Jxk^?&o0 z2k!s>|L^wy7yR~}{}Nh}=GoC+yDx!(NnvTH&g{)Cmvj`_yLGN}2eAg1cI{tq<))y9 zh}acASBuxB4~iz7z2rG-^){o0B5plP%b)80KfW#g|Nnpe%m4rX_3i)vZyjsI9tj*$ z_Lz6kiZT3G(4zLf4i7d~&ZmV_P6+4x{~d7TR|8+7x#^mXd=F<&e`eb<^NQDX?a-Ic z6+Cih&7C+cr#cN->emCYp5gxA`Tui)#;(aK``PY%f9dHdTo)J^tyz~^b8!?Oa|mjj z-uYN)wdVg{mo~3U$oUefGH;S~+5cbCVgFzLfBXOcpVHL-s}rR^ZDt6{Vp0=*@umL% zm#093LEvxY{{KAi3S||!0OX(73_cR?)z?S_CR$EOwQc570UAcR$>h3-GJXGlI~@7Y z;N=^v{zk<&k#$Yd#%56m%8aCgZcyd)o#EKqY+=oYsD>niM