@@ -23,6 +23,89 @@ import { InflictorFactory } from "./audio/plugins/Inflictor";
2323import { FilterFactory } from "./audio/plugins/Filter" ;
2424import { ChorusFactory } from "./audio/plugins/Chorus" ;
2525
26+ class PeakMeter implements IComponent {
27+ app : Appl ;
28+ container : HTMLDivElement ;
29+ canvas : HTMLCanvasElement ;
30+ analyserNode : AnalyserNode ;
31+ dataArray : Uint8Array ;
32+ intervalId : number | null = null ;
33+
34+ constructor ( app : Appl ) {
35+ this . app = app ;
36+
37+ this . container = document . createElement ( "div" ) ;
38+ this . container . classList . add ( "flex" , "items-center" , "w-48" , "bg-neutral-900" , "rounded" ) ;
39+
40+ this . canvas = document . createElement ( "canvas" ) ;
41+ this . canvas . width = 192 ;
42+ this . canvas . height = 32 ;
43+ this . canvas . style . display = "block" ;
44+ this . container . appendChild ( this . canvas ) ;
45+ }
46+
47+ onUpdate = ( ) => {
48+ this . analyserNode . getByteTimeDomainData ( this . dataArray as Uint8Array < ArrayBuffer > ) ; // WTF
49+
50+ let max = 0 ;
51+ for ( let i = 0 ; i < this . dataArray . length ; i ++ ) {
52+ max = Math . max ( Math . abs ( this . dataArray [ i ] - 128 ) , max ) ;
53+ }
54+
55+ const level = Math . log10 ( 1 + max ) / Math . log10 ( 128 ) * 100 ;
56+
57+ const width = this . canvas . width ;
58+ const height = this . canvas . height ;
59+ const fillWidth = ( level / 100 ) * width ;
60+
61+ const context = this . canvas . getContext ( "2d" ) ;
62+ context . clearRect ( 0 , 0 , width , height ) ;
63+
64+ const greenEnd = 0.7 * width ;
65+ if ( fillWidth > 0 ) {
66+ context . fillStyle = "green" ;
67+ context . fillRect ( 0 , 0 , Math . min ( fillWidth , greenEnd ) , height ) ;
68+ }
69+
70+ const yellowEnd = 0.8 * width ;
71+ if ( fillWidth > greenEnd ) {
72+ context . fillStyle = "yellow" ;
73+ context . fillRect ( greenEnd , 0 , Math . min ( fillWidth , yellowEnd ) - greenEnd , height ) ;
74+ }
75+
76+ if ( fillWidth > yellowEnd ) {
77+ context . fillStyle = "red" ;
78+ context . fillRect ( yellowEnd , 0 , fillWidth - yellowEnd , height ) ;
79+ }
80+ }
81+
82+ setDevice ( ) {
83+ this . stopPolling ( ) ;
84+
85+ this . analyserNode = new AnalyserNode ( this . app . device . context , {
86+ fftSize : 1024 ,
87+ } ) ;
88+
89+ this . app . device . masterGainNode . connect ( this . analyserNode ) ;
90+
91+ const bufferLength = this . analyserNode . frequencyBinCount ;
92+ this . dataArray = new Uint8Array ( bufferLength ) ;
93+
94+ this . intervalId = window . setInterval ( this . onUpdate , 25 ) ;
95+ }
96+
97+ stopPolling ( ) {
98+ if ( this . intervalId !== null ) {
99+ clearInterval ( this . intervalId ) ;
100+ this . intervalId = null ;
101+ }
102+ }
103+
104+ getDomNode ( ) : Node {
105+ return this . container ;
106+ }
107+ }
108+
26109class BpmInput implements IComponent {
27110
28111 app : Appl ;
@@ -65,6 +148,7 @@ export class Appl extends CommandHost implements IComponent {
65148 fullscreen : FullScreen ;
66149 menuBar : MenuBar ;
67150 bpmInput : BpmInput ;
151+ peakMeter : PeakMeter ;
68152 toolbar : HTMLElement ;
69153 frame : GridFrameContainer ;
70154 mainTabs : TabFrameContainer ;
@@ -137,8 +221,9 @@ export class Appl extends CommandHost implements IComponent {
137221 ] ) ;
138222
139223 this . bpmInput = new BpmInput ( this ) ;
224+ this . peakMeter = new PeakMeter ( this ) ;
140225
141- const toolbarContainer = HFlex ( [ toolbar , this . bpmInput . getDomNode ( ) ] , "gap-1" ) ;
226+ const toolbarContainer = HFlex ( [ toolbar , this . bpmInput . getDomNode ( ) , this . peakMeter . getDomNode ( ) ] , "gap-1" ) ;
142227
143228 this . mainTabs = new TabFrameContainer ( false ) ;
144229
@@ -226,9 +311,11 @@ export class Appl extends CommandHost implements IComponent {
226311 async setAudioDevice ( outputDeviceId : string , inputDeviceId : string , latencySec : number ) {
227312 await this . device . create ( outputDeviceId , inputDeviceId , latencySec ) ;
228313
229- this . player = new Player ( this . instrumentFactories , this . device . context ) ;
314+ this . player = new Player ( this . instrumentFactories , this . device ) ;
230315 this . playerSongAdapter . attachPlayer ( this . player ) ;
231316
317+ this . peakMeter . setDevice ( ) ;
318+
232319 await this . writeSetting ( "OutputDevice" , outputDeviceId ) ;
233320 await this . writeSetting ( "InputDevice" , inputDeviceId ) ;
234321 await this . writeSetting ( "Latency" , latencySec ) ;
0 commit comments