Skip to content

Commit

Permalink
implement Waveform.js
Browse files Browse the repository at this point in the history
  • Loading branch information
ish- authored and volyx committed Jul 18, 2024
1 parent 68e2cb0 commit f4f4f83
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 1 deletion.
1 change: 1 addition & 0 deletions content/episode/64.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ people:
- volyx
- andrei-kogun
audio: 64-javaswag-andreii-kogun-2.mp3
waveform: 64-javaswag-andreii-kogun-2.mp3.avg220.bin
size: 153467776
guid: 64-javaswag-andreii-kogun-2.mp3
image: images/logo.png
Expand Down
1 change: 1 addition & 0 deletions exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ people:
- volyx
- {{ .Guest}}
audio: {{ .Audio}}
waveform: {{ .Waveform}}
size: {{ .Size }}
guid: {{ .Guid }}
image: images/logo.png
Expand Down
23 changes: 22 additions & 1 deletion layout/_default/episode.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

<head>
{{ partial "meta.html" . }}
<script src="/Waveform/Waveform.js"></script>
<link rel="stylesheet" type="text/css" href="/Waveform/Waveform.css">
</head>

<body>
Expand All @@ -16,9 +18,28 @@
<span>{{ .Date.Format "2 Jan 2006" }}</span>
<h2>{{ .Title }}</h2>
</div>
<figure class="Waveform"></figure>
<div class="scc">
<audio src="{{ .Site.Params.yandexS3 }}{{.Params.Audio}}" controls="controls" preload="auto">
<audio id="WaveformAudio" src="{{ .Site.Params.yandexS3 }}{{.Params.Audio}}" controls="controls" preload="auto">
</audio>
<script>
const isMobile = window.matchMedia('(max-width: 780px)').matches;
if (isMobile)
document.body.classList.add('--mobile');

// add "{{ .Site.Params.yandexS3 }}" like it's in #WaveformAudio
// and to /episodes/*.md:waveform relative to s3
const waveformPath = "/waveforms/{{ $.Params.Waveform}}";

const waveform = new Waveform({
$el: document.querySelector('.Waveform'),
$audio: document.querySelector('#WaveformAudio'),
waveformPath,
height: 70,
barsCount: isMobile ? 110 : 220,
isMobile,
});
</script>
<!-- <div>
<label for="pbrate">Speed:</label>
<input type="range" id="pbrate" min=.5 max=2 value=1 step=.1>
Expand Down
49 changes: 49 additions & 0 deletions static/Waveform/Waveform.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.Waveform {
position: relative;
display: flex;
flex-direction: row;
gap: 1px;
align-items: end;

margin: 0;
width: var(--wf-w);

opacity: .8;
transition: opacity .2s;
}

.Waveform:hover { opacity: 1 }

.Waveform__col {
display: inline-block;
width: 2px;
background: rgba(0,0,0, .2);
padding: 0;
/* ff6a00 ff3400 */
}

.Waveform__col.--past {
background: red;
background: linear-gradient(0deg, rgba(255,0,0,1) 0%, rgba(255,155,0,1) 30px);
}

.Episode__Container audio {
--ml: 30px;
width: calc(var(--wf-w) + var(--ml));
/* margin-top: -10px;*/
opacity: .8;
border-radius: var(--ml);
height: var(--ml);
margin-left: calc(-1 * var(--ml) / 2);
transition: opacity .2s;
}

.Episode__Container audio:hover {
opacity: 1;
}
body {
--wf-w: 660px;
}
body.--mobile {
--wf-w: 330px;
}
131 changes: 131 additions & 0 deletions static/Waveform/Waveform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
class Waveform {
constructor (opts/* { waveform, $el }*/) {
Object.assign(this, opts);

this.$el.style.height = this.height;

this.fetchAnalyzeData(this.waveformPath).then(data => {
console.log('Fetched: ', this.duration, this.waveform);
this.draw();
this.pollAudio();
this.listenPointer();
});
}

async fetchAnalyzeData (waveformPath) {
const buf = await fetch(waveformPath, { mode: 'no-cors'})
.then(resp => resp.arrayBuffer());
const analyzeData = new Int16Array(buf);
this.waveform = analyzeData;
this.duration = analyzeData[ analyzeData.length - 1 ];
return this.waveform;
}

listenPointer () {
this.pointerPress = false;
this.$el.addEventListener('pointerdown', this.handlePointer);
this.$el.addEventListener('pointerup', this.handlePointer);
this.$el.addEventListener('pointermove', this.handlePointer);
}

handlePointer = (e) => {
if (e.type === 'pointerdown')
this.pointerPress = true;

if (e.type === 'pointerup') {
this.pointerPress = false;
return;
}

if (this.pointerPress) {
const seek = (e.x - this.$el.offsetLeft) / this.$el.offsetWidth;
const time = seek * this.duration;
this.$audio.currentTime = time;
this.handlePollAudio(time);
}
}

pollAudio () {
this.seekInterval = -1;
this.$audio.addEventListener('play', e => {
this.seekInterval = setInterval(this.handlePollAudio, 300);
});
this.$audio.addEventListener('pause', e => clearInterval(this.seekInterval));
this.$audio.addEventListener('seeked', this.handlePollAudio);
}

handlePollAudio = (time) => {
const prog = (Number.isInteger(time) ? time : this.$audio.currentTime) / this.duration;

// console.time('Toggle "--past" class');
[...this.$el.children].forEach(($col, i) => {
const colProg = i / 220;
$col.classList.toggle('--past', colProg < prog);
});
// console.timeEnd('Toggle "--past" class');
}

draw () {
const SAMPLE_LEN = Math.round(this.waveform.length / this.barsCount);
function SampleInfo (avg) {
return {
// min: Infinity,
max: -Infinity,
avg,
};
}
const sampleArr = Object.assign([], SampleInfo(0));
for (let i = 0; i < this.barsCount; i++) {
const sample = this.waveform.slice(i * SAMPLE_LEN, (i+1) * SAMPLE_LEN);

const sampleInfo = SampleInfo(sample[0]);
for (let i = 0; i < SAMPLE_LEN; i++) {
const d = Math.abs(sample[i]);
// sampleInfo.min = Math.min(d, sampleInfo.min);
sampleInfo.max = Math.max(d, sampleInfo.max);
sampleInfo.avg = (sampleInfo.avg + d) / 2;
}

// sampleArr.min = Math.min(sampleInfo.min, sampleArr.min);
sampleArr.max = Math.max(sampleInfo.max, sampleArr.max);
sampleArr.avg = (sampleInfo.avg, sampleArr.avg) / 2;

sampleArr.push(sampleInfo);
}

const LIMIT_AMP = (sampleArr.max - sampleArr.avg) / 1.2;
// sampleArr[0].max = .5;

sampleArr.forEach((sampleInfo, i) => {
let val = (sampleInfo.max + sampleInfo.avg) / 2;
val /= LIMIT_AMP;
val = Math.min(val, 1);
// val = Math.pow(val, .5);

const $col = this.createCol(val);
// $col.addEventListener('mouseover', e => {
// this.$el.style.setProperty('--over-i', i);
// }, false);
this.$el.appendChild($col);
});

window.ANLZ = sampleArr;
}

createCol (amp) {
// console.log(amp)
const $col = document.createElement('div');
$col.classList.add('Waveform__col');
$col.style.height = `${ Math.pow(amp, .5) * this.height }px`;
return $col;
}
}

// const waveform = new Waveform({
// $el: document.querySelector('.Waveform'),
// waveformPath: CONF.waveformPath,
// height: 70,
// barsCount: 220,
// $audio: document.querySelector('#audio'),
// audioPath: CONF.audioPath,
// });

0 comments on commit f4f4f83

Please sign in to comment.