Skip to content

Commit

Permalink
Factor out editarea
Browse files Browse the repository at this point in the history
  • Loading branch information
dy committed May 30, 2024
1 parent cede403 commit a2a497e
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 139 deletions.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@
Record
</button> -->
</div>
<div class="w-status" :if="!segments.length" :text="loading || 'Open'">
<div class="w-status" :if="!segments.length" :text="loading || ''">
</div>
<div contenteditable inputmode="none" :ref="editarea"
class="w-editable wavefont" :class="{'w-playing':playing}"
class="w-editarea wavefont" :class="{'w-playing':playing}"
:fx="/* remove blank textnodes */[...editarea.childNodes].forEach(node => node.nodeValue && !node.nodeValue.trim() && node.remove());"
:ondblclick.prevent
:oninput="e => (handleCaret(e), updateTimecodes())"
Expand Down
14 changes: 7 additions & 7 deletions main.css
Original file line number Diff line number Diff line change
Expand Up @@ -79,42 +79,42 @@ body {
/* cursor: drop; */
}

.w-editable,
.w-editarea,
.w-loader {
outline: none;
width: 100%;
color: var(--primary);
}

.w-editable {
.w-editarea {
/* background-size: 1px calc(var(--wavefont-size) * 1.4);
background-position: 0% 4.2rem;
background-image: repeating-linear-gradient(0deg, var(--secondary) -0.5px, rgb(255 255 255 / 0%) 0.5px, rgb(255 255 255 / 0%)); */
position: relative;
}

.w-editable p::selection {
.w-editarea p::selection {
background-color: var(--secondary);
}

/* played samples dimmer */
.w-editable.w-playing:before,
.w-editable.w-playing:after {
.w-editarea.w-playing:before,
.w-editarea.w-playing:after {
content: '';
position: absolute;
background: rgba(255, 255, 255, .75);
pointer-events: none;
z-index: 1;
}

.w-editable:before {
.w-editarea:before {
bottom: 0;
left: -1px;
right: -1px;
top: calc(var(--carety) + var(--wavefont-lh));
}

.w-editable:after {
.w-editarea:after {
top: var(--carety);
right: -1px;
left: var(--caretx);
Expand Down
26 changes: 13 additions & 13 deletions src/audio-utils.js → src/audio-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function fetchAudio(src) {
}

// convert audio buffers to wav array buffer
export async function encodeAudio (...audioBuffers) {
export async function encodeAudio(...audioBuffers) {
console.time('wav encode')

// extracted parts of node-wav for seamless integration with audio buffers float32
Expand All @@ -33,7 +33,7 @@ export async function encodeAudio (...audioBuffers) {
const u8 = (x) => v.setUint8(pos++, x);
const u16 = (x) => (v.setUint16(pos, x, true), pos += 2)
const u32 = (x) => (v.setUint32(pos, x, true), pos += 4)
const string = (s) => { for (var i = 0; i < s.length; ++i) u8(s.charCodeAt(i));}
const string = (s) => { for (var i = 0; i < s.length; ++i) u8(s.charCodeAt(i)); }
string("RIFF");
u32(buffer.byteLength - 8);
string("WAVE");
Expand All @@ -53,8 +53,8 @@ export async function encodeAudio (...audioBuffers) {
let output = new Float32Array(buffer, pos);
for (let audioBuffer of audioBuffers) {
let channels = audioBuffer.numberOfChannels,
channelData = Array(channels),
length = audioBuffer.length
channelData = Array(channels),
length = audioBuffer.length
for (let ch = 0; ch < channels; ++ch) channelData[ch] = audioBuffer.getChannelData(ch)
for (let i = 0; i < length; ++i)
for (let ch = 0; ch < channels; ++ch) output[pos++] = channelData[ch][i];
Expand All @@ -65,7 +65,7 @@ export async function encodeAudio (...audioBuffers) {
}

// convert audio buffer to waveform string
export function drawAudio (audioBuffer) {
export function drawAudio(audioBuffer) {
if (!audioBuffer) return '';

// if waveform is rendered already - return cached
Expand Down Expand Up @@ -94,7 +94,7 @@ export function drawAudio (audioBuffer) {

// rms method
// drawback: waveform is smaller than needed
for (;i < nextBlock; i++) {
for (; i < nextBlock; i++) {
let x = i >= channelData.length ? 0 : channelData[i]
sum += x
ssum += x ** 2
Expand All @@ -103,7 +103,7 @@ export function drawAudio (audioBuffer) {
}
const avg = sum / BLOCK_SIZE
const rms = Math.sqrt(ssum / BLOCK_SIZE)
let v = Math.min(100, Math.ceil(rms * RANGE * VISUAL_AMP / (max-min))) || 0
let v = Math.min(100, Math.ceil(rms * RANGE * VISUAL_AMP / (max - min))) || 0

str += String.fromCharCode(0x0100 + v)
let shift = Math.abs(Math.round(avg * RANGE / 2))
Expand All @@ -128,7 +128,7 @@ export function drawAudio (audioBuffer) {
return str
}

export function sliceAudio (buffer, start=0, end=buffer.length) {
export function sliceAudio(buffer, start = 0, end = buffer.length) {
let newBuffer = new AudioBuffer({
length: end - start,
numberOfChannels: buffer.numberOfChannels,
Expand Down Expand Up @@ -166,7 +166,7 @@ export function joinAudio(a, b) {
return newBuffer
}

export function deleteAudio(buffer, start=0, end=buffer.length) {
export function deleteAudio(buffer, start = 0, end = buffer.length) {
let newBuffer = new AudioBuffer({
length: buffer.length - Math.abs(end - start),
numberOfChannels: buffer.numberOfChannels,
Expand All @@ -183,7 +183,7 @@ export function deleteAudio(buffer, start=0, end=buffer.length) {
return newBuffer
}

export function insertAudio (a, offset, b) {
export function insertAudio(a, offset, b) {
if (offset >= a.length) return joinAudio(a, b)
if (!offset) return joinAudio(b, a)

Expand All @@ -210,14 +210,14 @@ export function insertAudio (a, offset, b) {
return buffer
}

export function cloneAudio (a) {
let b = new AudioBuffer({sampleRate: a.sampleRate, numberOfChannels: a.numberOfChannels, length: a.length})
export function cloneAudio(a) {
let b = new AudioBuffer({ sampleRate: a.sampleRate, numberOfChannels: a.numberOfChannels, length: a.length })
for (let ch = 0; ch < a.numberOfChannels; ch++) b.getChannelData(ch).set(a.getChannelData(ch))
return b
}

export const fileToArrayBuffer = (file) => {
return new Promise((y,n) => {
return new Promise((y, n) => {
const reader = new FileReader();
reader.addEventListener('loadend', (event) => {
y(event.target.result);
Expand Down
102 changes: 102 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

export const selection = {
// get normalized selection
get() {
let s = window.getSelection()

// return unknown selection
if (!s.anchorNode || !s.anchorNode.parentNode.closest('.w-editarea')) return

// collect start/end offsets
let start = absOffset(s.anchorNode, s.anchorOffset), end = absOffset(s.focusNode, s.focusOffset)

// swap selection direction
let startNode = s.anchorNode.parentNode.closest('.w-segment'), startNodeOffset = s.anchorOffset,
endNode = s.focusNode.parentNode.closest('.w-segment'), endNodeOffset = s.focusOffset;
if (start > end) {
[end, endNode, endNodeOffset, start, startNode, startNodeOffset] =
[start, startNode, startNodeOffset, end, endNode, endNodeOffset]
}

return {
start,
startNode,
startNodeOffset,
end,
endNode,
endNodeOffset,
collapsed: s.isCollapsed,
range: s.getRangeAt(0)
}
},

/**
* Set normalized selection
* @param {number | Array} start – absolute offset (excluding modifier chars) or relative offset [node, offset]
* @param {number | Array} end – absolute offset (excluding modifier chars) or relative offset [node, offset]
* @returns {start, , end}
*/
set(start, end) {
let s = window.getSelection()

if (Array.isArray(start)) start = absOffset(...start)
if (Array.isArray(end)) end = absOffset(...end)

// start/end must be within limits
start = Math.max(0, start)
if (end == null) end = start

// find start/end nodes
let editarea = document.querySelector('.w-editarea')
let [startNode, startNodeOffset] = relOffset(editarea, start)
let [endNode, endNodeOffset] = relOffset(editarea, end)

let currentRange = s.getRangeAt(0)
if (
!(currentRange.startContainer === startNode.firstChild && currentRange.startOffset === startNodeOffset) &&
!(currentRange.endContainer === endNode.firstChild && currentRange.endOffset === endNodeOffset)
) {
// NOTE: Safari doesn't support reusing range
s.removeAllRanges()
let range = new Range()
range.setStart(startNode.firstChild, startNodeOffset)
range.setEnd(endNode.firstChild, endNodeOffset)
s.addRange(range)
}

return {
start, startNode, end, endNode,
startNodeOffset, endNodeOffset,
collapsed: s.isCollapsed,
range: s.getRangeAt(0)
}
}
}

// calculate absolute offset from relative pair
function absOffset(node, relOffset) {
let prevNode = node.parentNode.closest('.w-segment')
let offset = cleanText(prevNode.textContent.slice(0, relOffset)).length
while (prevNode = prevNode.previousSibling) offset += cleanText(prevNode.textContent).length
return offset
}

// calculate node and relative offset from absolute offset
function relOffset(editarea, offset) {
let node = editarea.firstChild, len
// discount previous nodes
while (offset > (len = cleanText(node.textContent).length)) {
offset -= len, node = node.nextSibling
}
// convert current node to relative offset
let skip = 0
for (let content = node.textContent, i = 0; i < offset; i++) {
while (content[i + skip] >= '\u0300') skip++
}
return [node, offset + skip]
}

// return clean from modifiers text
export function cleanText(str) {
return str.replace(/\u0300|\u0301/g, '')
}
Loading

0 comments on commit a2a497e

Please sign in to comment.