Skip to content

Commit dde07a8

Browse files
committed
add some fallback controls for mobile & hover hint for pc
1 parent 4bc0a17 commit dde07a8

1 file changed

Lines changed: 116 additions & 100 deletions

File tree

index.html

Lines changed: 116 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@
99
<script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/spectrogram.min.js"></script>
1010
<style>
1111
body { font-family: sans-serif; margin: 20px; }
12-
#controls { margin-top: 10px; margin-bottom: 10px; display: none; padding-left: 0px; }
12+
#controls { margin-top: 10px; margin-bottom: 10px; display: none; padding-left: 0px; }
1313
#zipInput { margin-right: 10px; }
14-
#waveform { width: 100%; height: 150px; margin-top: 10px; margin-bottom: 10px;}
15-
#spectrogram { width: 100%; height: 150px; margin-top: 10px; margin-bottom: 15px;}
16-
#track-info { margin-top: 10px; font-style: italic; color: #444; font-size: 0.9em;}
17-
#currentTime { margin-top: 5px; font-family:monospace; font-size: 2em;}
14+
#waveform, #spectrogram { width: 100%; height: 150px; margin-top: 10px; margin-bottom: 10px; }
15+
#track-info { margin-top: 10px; font-style: italic; color: #444; font-size: 0.9em; }
16+
#currentTime { margin-top: 5px; font-family: monospace; font-size: 2em; }
1817
.radio-group { margin-top: 10px; margin-bottom: 10px; }
19-
.load-control { margin-top: 5px; margin-bottom: 15px;}
18+
.load-control { margin-top: 5px; margin-bottom: 15px; }
19+
#mobile-controls { display: none; margin-top: 10px; gap: 10px; justify-content: start; }
20+
@media (max-width: 768px) { #mobile-controls { display: flex; } }
2021
</style>
2122
</head>
2223
<body>
2324
<h2>BlastViz</h2>
2425
<h4>Blastbeat Detection Visualizer</h4>
2526
<div id="explainer">
2627
<hr>
27-
<p><i><strong>Warning:</strong> This is a work in progress. This visualizer expects a ZIP file containing an audio
28-
file
29-
(MP3 or WAV), a JSON file with the identified blast-beat sections, and optionally a drum-only audio track.</i>
28+
<p><i><strong>Warning:</strong> This is a work in progress. The visualizer expects a ZIP file containing an audio
29+
file (MP3 or WAV), a JSON file with the identified blast-beat sections, and optionally a drum-only audio track.</i>
3030
</p>
3131
<p>Expected JSON structure:</p>
3232
<pre>
@@ -39,8 +39,7 @@ <h4>Blastbeat Detection Visualizer</h4>
3939
</pre>
4040
<p>You can download some examples from <a
4141
href="https://drive.google.com/drive/folders/1YFoxrsrBo8hl0cOYkdxCsfW_bf3CX7Al?usp=drive_link"
42-
target="_blank">this Google
43-
Drive folder</a></p>
42+
target="_blank">this Google Drive folder</a></p>
4443
</div>
4544
<div class="load-control">
4645
<input type="file" id="zipInput" accept=".zip">
@@ -56,8 +55,13 @@ <h4>Blastbeat Detection Visualizer</h4>
5655
</div>
5756
<div id="controls">
5857
<div id="currentTime">0:00:000</div>
58+
<div id="mobile-controls">
59+
<button id="playPauseBtn">Play/Pause</button>
60+
<button id="zoomInBtn">Zoom In</button>
61+
<button id="zoomOutBtn">Zoom Out</button>
62+
</div>
5963
</div>
60-
<div id="waveform"></div>
64+
<div id="waveform" title="Space bar: play/pause -- Mouse wheel: zoom in/out"></div>
6165
<div id="spectrogram"></div>
6266
<script>
6367
let wavesurfer = null
@@ -66,115 +70,127 @@ <h4>Blastbeat Detection Visualizer</h4>
6670
let zoomLevel = 0
6771

6872
function applyZoom() {
69-
if (wavesurfer) wavesurfer.zoom(zoomLevel)
73+
if (wavesurfer) wavesurfer.zoom(zoomLevel)
7074
}
7175

7276
function handleWheelZoom(e) {
73-
if (!wavesurfer) return
74-
e.preventDefault()
75-
zoomLevel += e.deltaY < 0 ? 10 : -10
76-
zoomLevel = Math.max(0, Math.min(zoomLevel, 200))
77-
applyZoom()
77+
if (!wavesurfer) return
78+
e.preventDefault()
79+
zoomLevel += e.deltaY < 0 ? 10 : -10
80+
zoomLevel = Math.max(0, Math.min(zoomLevel, 200))
81+
applyZoom()
7882
}
7983

8084
function initWaveSurfer(audioFile, intervals) {
81-
if (wavesurfer) {
82-
wavesurfer.destroy()
83-
document.getElementById('waveform').innerHTML = ""
84-
document.getElementById('spectrogram').innerHTML = ""
85-
document.getElementById('currentTime').textContent = "0:00:000"
86-
}
87-
const regions = WaveSurfer.Regions.create()
88-
const spectrogram = WaveSurfer.Spectrogram.create({
89-
container: '#spectrogram',
90-
labels: true,
91-
frequencyMax: 500,
92-
scale: "mel",
93-
rangeDB: 60,
94-
gainDB: 20,
95-
windowFunc: 'hann',
96-
colorMap: 'roseus',
97-
fftSamples: 256
98-
})
99-
wavesurfer = WaveSurfer.create({
100-
container: '#waveform',
101-
waveColor: '#000',
102-
progressColor: '#888',
103-
cursorColor: '#f00',
104-
height: 150,
105-
plugins: [regions, spectrogram]
106-
})
107-
wavesurfer.load(audioFile)
108-
wavesurfer.on('ready', () => {
109-
document.getElementById('explainer').style.display = 'none'
110-
audioReady = true
111-
document.getElementById('controls').style.display = 'block'
112-
intervals.forEach(({ start_time, end_time }) => {
113-
regions.addRegion({
114-
start: start_time,
115-
end: end_time,
116-
color: 'rgba(2,247,7,0.3)',
117-
drag: false,
118-
resize: false
119-
})
85+
if (wavesurfer) {
86+
wavesurfer.destroy()
87+
document.getElementById('waveform').innerHTML = ""
88+
document.getElementById('spectrogram').innerHTML = ""
89+
document.getElementById('currentTime').textContent = "0:00:000"
90+
}
91+
const regions = WaveSurfer.Regions.create()
92+
const spectrogram = WaveSurfer.Spectrogram.create({
93+
container: '#spectrogram',
94+
labels: true,
95+
frequencyMax: 500,
96+
scale: "mel",
97+
rangeDB: 60,
98+
gainDB: 20,
99+
windowFunc: 'hann',
100+
colorMap: 'roseus',
101+
fftSamples: 256
102+
})
103+
wavesurfer = WaveSurfer.create({
104+
container: '#waveform',
105+
waveColor: '#000',
106+
progressColor: '#888',
107+
cursorColor: '#f00',
108+
height: 150,
109+
plugins: [regions, spectrogram]
110+
})
111+
wavesurfer.load(audioFile)
112+
wavesurfer.on('ready', () => {
113+
document.getElementById('explainer').style.display = 'none'
114+
audioReady = true
115+
document.getElementById('controls').style.display = 'block'
116+
intervals.forEach(({ start_time, end_time }) => {
117+
regions.addRegion({
118+
start: start_time,
119+
end: end_time,
120+
color: 'rgba(2,247,7,0.3)',
121+
drag: false,
122+
resize: false
123+
})
124+
})
120125
})
121-
})
122-
wavesurfer.on('timeupdate', t => {
123-
const m = Math.floor(t / 60)
124-
const s = Math.floor(t % 60)
125-
const ms = Math.floor((t % 1) * 1000)
126-
document.getElementById('currentTime').textContent = `${m}:${s.toString().padStart(2, '0')}:${ms.toString().padStart(3, '0')}`
127-
})
128-
document.getElementById('waveform').addEventListener('wheel', handleWheelZoom, { passive: false })
129-
document.getElementById('spectrogram').addEventListener('wheel', handleWheelZoom, { passive: false })
126+
wavesurfer.on('timeupdate', t => {
127+
const m = Math.floor(t / 60)
128+
const s = Math.floor(t % 60)
129+
const ms = Math.floor((t % 1) * 1000)
130+
document.getElementById('currentTime').textContent = `${m}:${s.toString().padStart(2, '0')}:${ms.toString().padStart(3, '0')}`
131+
})
132+
document.getElementById('waveform').addEventListener('wheel', handleWheelZoom, { passive: false })
133+
document.getElementById('spectrogram').addEventListener('wheel', handleWheelZoom, { passive: false })
130134
}
131135

132136
async function loadZip(zipFile) {
133-
const zip = await JSZip.loadAsync(zipFile)
134-
const jsonFileName = Object.keys(zip.files).find(n => n.endsWith('.json'))
135-
if (!jsonFileName) return alert("No JSON found in zip")
136-
const jsonData = await zip.files[jsonFileName].async('string')
137-
const config = JSON.parse(jsonData)
138-
const intervals = config.blast_beats || []
139-
const trackType = document.querySelector('input[name="trackType"]:checked').value
140-
let audioFileName = null
141-
if (trackType === "drums") {
142-
audioFileName = Object.keys(zip.files).find(n => /_drums\.(mp3|wav)$/i.test(n))
143-
if (!audioFileName) return alert("Drums-only file (_drums) not found in zip")
137+
const zip = await JSZip.loadAsync(zipFile)
138+
const jsonFileName = Object.keys(zip.files).find(n => n.endsWith('.json'))
139+
if (!jsonFileName) return alert("No JSON found in zip")
140+
const jsonData = await zip.files[jsonFileName].async('string')
141+
const config = JSON.parse(jsonData)
142+
const intervals = config.blast_beats || []
143+
const trackType = document.querySelector('input[name="trackType"]:checked').value
144+
let audioFileName = null
145+
if (trackType === "drums") {
146+
audioFileName = Object.keys(zip.files).find(n => /_drums\.(mp3|wav)$/i.test(n))
147+
if (!audioFileName) return alert("Drums-only file (_drums) not found in zip")
144148
} else {
145-
audioFileName = Object.keys(zip.files).find(n => /\.(mp3|wav)$/i.test(n) && !/_drums\./i.test(n))
149+
audioFileName = Object.keys(zip.files).find(n => /\.(mp3|wav)$/i.test(n) && !/_drums\./i.test(n))
146150
}
147-
console.log(trackType)
148-
console.log("Selected audio file:", audioFileName)
149-
if (!audioFileName) return alert("No audio file found in zip")
150-
const audioBlob = await zip.files[audioFileName].async('blob')
151-
const audioURL = URL.createObjectURL(audioBlob)
152-
document.getElementById('audioPath').textContent = "Track: " + audioFileName
153-
if (config.snare_frequency !== undefined)
154-
document.getElementById('snareFreq').textContent = "| Snare drum freq: " + config.snare_frequency.toFixed(2) + " Hz"
155-
if (config.bass_drum_frequency !== undefined)
156-
document.getElementById('bassdrumFreq').textContent = "| Bass drum freq: " + config.bass_drum_frequency.toFixed(2) + " Hz"
157-
initWaveSurfer(audioURL, intervals)
151+
if (!audioFileName) return alert("No audio file found in zip")
152+
const audioBlob = await zip.files[audioFileName].async('blob')
153+
const audioURL = URL.createObjectURL(audioBlob)
154+
document.getElementById('audioPath').textContent = "Track: " + audioFileName
155+
if (config.snare_frequency !== undefined)
156+
document.getElementById('snareFreq').textContent = "| Snare drum freq: " + config.snare_frequency.toFixed(2) + " Hz"
157+
if (config.bass_drum_frequency !== undefined)
158+
document.getElementById('bassdrumFreq').textContent = "| Bass drum freq: " + config.bass_drum_frequency.toFixed(2) + " Hz"
159+
initWaveSurfer(audioURL, intervals)
158160
}
159161

160162
document.getElementById('zipInput').addEventListener('change', e => {
161-
const file = e.target.files[0]
162-
if (!file) return
163-
currentZipFile = file
164-
loadZip(file)
163+
const file = e.target.files[0]
164+
if (!file) return
165+
currentZipFile = file
166+
loadZip(file)
165167
})
166168

167169
document.querySelectorAll('input[name="trackType"]').forEach(r => {
168-
r.addEventListener('change', () => {
169-
if (currentZipFile) loadZip(currentZipFile)
170-
})
170+
r.addEventListener('change', () => {
171+
if (currentZipFile) loadZip(currentZipFile)
172+
})
171173
})
172174

173175
document.addEventListener('keydown', e => {
174-
if (e.code === 'Space' && audioReady) {
175-
e.preventDefault()
176-
if (wavesurfer) wavesurfer.playPause()
177-
}
176+
if (e.code === 'Space' && audioReady) {
177+
e.preventDefault()
178+
if (wavesurfer) wavesurfer.playPause()
179+
}
180+
})
181+
182+
document.getElementById('playPauseBtn').addEventListener('click', () => {
183+
if (audioReady && wavesurfer) wavesurfer.playPause()
184+
})
185+
186+
document.getElementById('zoomInBtn').addEventListener('click', () => {
187+
zoomLevel = Math.min(200, zoomLevel + 10)
188+
applyZoom()
189+
})
190+
191+
document.getElementById('zoomOutBtn').addEventListener('click', () => {
192+
zoomLevel = Math.max(0, zoomLevel - 10)
193+
applyZoom()
178194
})
179195
</script>
180196
</body>

0 commit comments

Comments
 (0)