From 2d07b7b164bd8a5113f22e9996ee0ac9a81133ce Mon Sep 17 00:00:00 2001 From: tyfkda Date: Thu, 25 Apr 2024 13:32:23 +0900 Subject: [PATCH] Show noise and DMC channel --- src/app/other_wnd.ts | 170 +++++++++++++++++++++++++++---------------- src/res/dmc.png | Bin 0 -> 590 bytes src/res/noise.png | Bin 0 -> 5460 bytes 3 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 src/res/dmc.png create mode 100644 src/res/noise.png diff --git a/src/app/other_wnd.ts b/src/app/other_wnd.ts index 3966057..06b0977 100644 --- a/src/app/other_wnd.ts +++ b/src/app/other_wnd.ts @@ -2,7 +2,7 @@ import {WindowManager} from '../wnd/window_manager' import {Wnd} from '../wnd/wnd' import {AudioManager} from '../util/audio_manager' -import {WaveType} from '../nes/apu' +import {WaveType, INoiseChannel} from '../nes/apu' import {DomUtil} from '../util/dom_util' import {GlobalSetting} from './global_setting' import {Nes} from '../nes/nes' @@ -21,6 +21,8 @@ import aboutHtmlContent from '../res/about.html' import pluseImg from '../res/pulse.png' import triangleImg from '../res/triangle.png' +import noiseImg from '../res/noise.png' +import dmcImg from '../res/dmc.png' import sawtoothImg from '../res/sawtooth.png' export class FpsWnd extends Wnd { @@ -402,8 +404,8 @@ const kToneTable: number[] = [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12] // F F# G const kWaveTypeImages: string[] = [ pluseImg, triangleImg, - '', - '', + noiseImg, + dmcImg, sawtoothImg, ] @@ -422,6 +424,7 @@ export class AudioWnd extends Wnd { private nes: Nes private stream: AppEvent.Stream private subscription: Pubsub.Subscription + private waveTypes: Array private channelIndices: Array private dots: Array private keys: Array> @@ -430,12 +433,13 @@ export class AudioWnd extends Wnd { public constructor(wndMgr: WindowManager, nes: Nes, stream: AppEvent.Stream) { const waveTypes = nes.getChannelWaveTypes() const channelIndices = [...Array(waveTypes.length).keys()] - .filter(ch => waveTypes[ch] !== WaveType.DMC && waveTypes[ch] !== WaveType.NOISE) const channelCount = channelIndices.length - super(wndMgr, AudioWnd.W * AudioWnd.OCTAVE * 7, AudioWnd.H * channelCount, 'Audio') + // Assumed noise and DMC channels exist and they are half height. + super(wndMgr, AudioWnd.W * AudioWnd.OCTAVE * 7, AudioWnd.H * (channelCount - 1), 'Audio') this.nes = nes this.stream = stream + this.waveTypes = waveTypes this.channelIndices = channelIndices const {root, dots, keys} = this.createDom(channelCount, waveTypes) @@ -474,15 +478,66 @@ export class AudioWnd extends Wnd { const DOT_W = 12 for (let ich = 0; ich < this.channelIndices.length; ++ich) { const channel = this.nes.getSoundChannel(this.channelIndices[ich]) + const waveType = this.waveTypes[ich] + let x = 0 + let y = 0 + let vol = 0 + + switch (waveType) { + case WaveType.NOISE: + { + vol = channel.isEnabled() ? channel.getVolume() : 0 + const [period, _mode] = (channel as unknown as INoiseChannel).getNoisePeriod() + const APU_NOISE_HZ = 894887 + const freq = (APU_NOISE_HZ / 32) / (period + 1) // ??? + const toneIndex = Math.log(freq) * logScale - AudioWnd.kBaseTone + 0.5 + if ((toneIndex >= 0 && toneIndex <= AudioWnd.OCTAVE * ALL_NOTE) && vol > 0) { + const offset = kToneTable[(toneIndex | 0) % ALL_NOTE] | 0 + x = Math.round(((offset * 0.5) + (Math.floor(toneIndex / ALL_NOTE) | 0) * WHITE_NOTE + (toneIndex % 1)) * xScale) | 0 + y = AudioWnd.H * 0.25 + } else { + vol = 0 + } + } + break + case WaveType.DMC: + { + vol = channel.isEnabled() ? channel.getVolume() : 0 + x = (AudioWnd.W * AudioWnd.OCTAVE * 7) * 0.5; + y = AudioWnd.H * 0.25 + } + break + default: + { + vol = channel.isEnabled() ? channel.getVolume() : 0 + const freq = channel.getFrequency() + const toneIndex = Math.log(freq) * logScale - AudioWnd.kBaseTone + 0.5 + let keyIndex = -1 + if ((toneIndex >= 0 && toneIndex <= AudioWnd.OCTAVE * ALL_NOTE) && vol > 0) { + const offset = kToneTable[(toneIndex | 0) % ALL_NOTE] | 0 + x = Math.round(((offset * 0.5) + (Math.floor(toneIndex / ALL_NOTE) | 0) * WHITE_NOTE + (toneIndex % 1)) * xScale) | 0 + y = (offset & 1) === 0 ? yWhite : yBlack + keyIndex = Math.floor(toneIndex) | 0 + } + + const lastKeyIndex = this.lastKeyIndices[ich] + if (keyIndex !== lastKeyIndex) { + if (lastKeyIndex >= 0) { + const key = this.keys[ich][lastKeyIndex] + key.style.removeProperty('background-color') + } + if (keyIndex >= 0) { + const key = this.keys[ich][keyIndex] + key.style.setProperty('background-color', KEY_FLASH_COLOR) + } + this.lastKeyIndices[ich] = keyIndex + } + } + break + } + const dot = this.dots[ich] - const vol = channel.isEnabled() ? channel.getVolume() : 0 - const freq = channel.getFrequency() - const toneIndex = Math.log(freq) * logScale - AudioWnd.kBaseTone + 0.5 - let keyIndex = -1 - if ((toneIndex >= 0 && toneIndex <= AudioWnd.OCTAVE * ALL_NOTE) && vol > 0) { - const offset = kToneTable[(toneIndex | 0) % ALL_NOTE] | 0 - const x = Math.round(((offset * 0.5) + (Math.floor(toneIndex / ALL_NOTE) | 0) * WHITE_NOTE + (toneIndex % 1)) * xScale) | 0 - const y = (offset & 1) === 0 ? yWhite : yBlack + if (vol > 0) { const v = Math.pow(vol, 1 / 3) const w = Math.round(DOT_W * v) | 0 const r = (w * 0.5) | 0 @@ -494,26 +549,11 @@ export class AudioWnd extends Wnd { height: `${w}px`, borderRadius: `${w}px`, }) - - keyIndex = Math.floor(toneIndex) | 0 } else { DomUtil.setStyles(dot, { visibility: 'hidden', }) } - - const lastKeyIndex = this.lastKeyIndices[ich] - if (keyIndex !== lastKeyIndex) { - if (lastKeyIndex >= 0) { - const key = this.keys[ich][lastKeyIndex] - key.style.removeProperty('background-color') - } - if (keyIndex >= 0) { - const key = this.keys[ich][keyIndex] - key.style.setProperty('background-color', KEY_FLASH_COLOR) - } - this.lastKeyIndices[ich] = keyIndex - } } } @@ -536,54 +576,62 @@ export class AudioWnd extends Wnd { for (let ch = 0; ch < channelCount; ++ch) { const line = document.createElement('div') + const waveType = waveTypes[this.channelIndices[ch]] + const percussion = waveType === WaveType.NOISE || waveType === WaveType.DMC + const h = percussion ? H / 2 : H line.className = 'keyboard' DomUtil.setStyles(line, { - height: `${H - 1}px`, + height: `${h - 1}px`, }) - const whiteKeys = new Array() - const blackKeys = new Array() - const chKeys = new Array() - for (let octave = 0; octave < AudioWnd.OCTAVE; ++octave) { - for (let i = 0; i < kToneTable.length; ++i) { - const offset = kToneTable[i] | 0 - const note = octave * WHITE_NOTE + (offset >> 1) - let key: HTMLElement - if ((offset & 1) === 0) { - key = document.createElement('div') - key.className = 'white-key' - DomUtil.setStyles(key, { - left: `${note * W}px`, - width: `${W - 1}px`, - }) - whiteKeys.push(key) - } else { - key = document.createElement('div') - key.className = 'black-key' - DomUtil.setStyles(key, { - left: `${note * W + W / 2 + 1}px`, - width: `${W - 2}px`, - height: `${H / 2}px`, - }) - blackKeys.push(key) + let whiteKeys: Array + let blackKeys: Array + let chKeys: Array = [] + if (!percussion) { + whiteKeys = new Array() + blackKeys = new Array() + chKeys = new Array() + for (let octave = 0; octave < AudioWnd.OCTAVE; ++octave) { + for (let i = 0; i < kToneTable.length; ++i) { + const offset = kToneTable[i] | 0 + const note = octave * WHITE_NOTE + (offset >> 1) + let key: HTMLElement + if ((offset & 1) === 0) { + key = document.createElement('div') + key.className = 'white-key' + DomUtil.setStyles(key, { + left: `${note * W}px`, + width: `${W - 1}px`, + }) + whiteKeys.push(key) + } else { + key = document.createElement('div') + key.className = 'black-key' + DomUtil.setStyles(key, { + left: `${note * W + W / 2 + 1}px`, + width: `${W - 2}px`, + height: `${H / 2}px`, + }) + blackKeys.push(key) + } + chKeys.push(key) } - chKeys.push(key) } - } - for (const whiteKey of whiteKeys) - line.appendChild(whiteKey) - for (const blackKey of blackKeys) - line.appendChild(blackKey) + for (const whiteKey of whiteKeys) + line.appendChild(whiteKey) + for (const blackKey of blackKeys) + line.appendChild(blackKey) + } const icon = document.createElement('img') + icon.className = 'pixelated' DomUtil.setStyles(icon, { position: 'absolute', left: 0, top: 0, display: 'block', }) - const waveType = waveTypes[this.channelIndices[ch]] icon.src = kWaveTypeImages[waveType] line.appendChild(icon) diff --git a/src/res/dmc.png b/src/res/dmc.png new file mode 100644 index 0000000000000000000000000000000000000000..0f806613b5b7f980be1dd92a7032185ecd2760ac GIT binary patch literal 590 zcmV-U0EX>4Tx04R}tkv&MmKpe$i)0T=<9PFUtkfAzR6cusQDionYs1;guFu8t0lZGV4 z#ZhoAIQX$xb#QUk)xlK|1V2EW9h?+hq{ROvg%&X$9QWhhy~o`V^*KpO!gGAx!^hXV7|-%P_vh%z*& z=QwY1Rx35ux+i~OIIpcNbDic85?I6%B#2N@Lm3rVh|#K%Vj@NRaS#8Xb{T0ssI2 literal 0 HcmV?d00001 diff --git a/src/res/noise.png b/src/res/noise.png new file mode 100644 index 0000000000000000000000000000000000000000..869ccd11ff5f92a21bb2ceb202a149519a956a84 GIT binary patch literal 5460 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