Initial commit

This commit is contained in:
2021-09-16 20:45:54 +08:00
commit b7defdf5d8
35 changed files with 2343 additions and 0 deletions

706
Home/LTI/src/main.dev.js Normal file
View File

@ -0,0 +1,706 @@
/* global
Audio,
AudioContext,
FileReader,
Plotly,
Vue,
cancelAnimationFrame,
fetch,
math,
requestAnimationFrame
*/
Vue.config.devtools = true
const figureIn = {
data: [
{
line: {
dash: 'solid',
color: 'rgb(0,113.985,188.955)',
width: 1
},
mode: 'lines+markers',
name: '',
type: 'scatter',
x: [],
y: [],
xaxis: 'x',
yaxis: 'y',
marker: {
line: {
width: 1,
color: []
},
size: 8,
symbol: 'circle',
color: []
},
visible: true,
showlegend: true
}
],
layout: {
title: '',
width: 840,
xaxis: {
side: 'bottom',
type: 'linear',
range: [],
ticks: 'inside',
anchor: 'y',
domain: [0.13, 0.905],
mirror: 'ticks',
nticks: 9,
ticklen: 6.51,
showgrid: false,
showline: true,
tickfont: {
size: 10,
color: 'rgb(38.25,38.25,38.25)',
family: 'Arial, sans-serif'
},
tickmode: 'auto',
zeroline: false,
autorange: true,
gridcolor: 'rgb(38.25,38.25,38.25)',
gridwidth: 1,
linecolor: 'rgb(38.25,38.25,38.25)',
linewidth: 1,
tickcolor: 'rgb(38.25,38.25,38.25)',
tickwidth: 1,
titlefont: {
size: 11,
color: 'rgb(38.25,38.25,38.25)',
family: 'Arial, sans-serif'
},
exponentformat: 'none'
},
yaxis: {
side: 'left',
type: 'linear',
range: [],
ticks: 'inside',
anchor: 'x',
domain: [0.11, 0.925],
mirror: 'ticks',
nticks: 12,
ticklen: 6.51,
showgrid: false,
showline: true,
tickfont: {
size: 10,
color: 'rgb(38.25,38.25,38.25)',
family: 'Arial, sans-serif'
},
tickmode: 'auto',
zeroline: true,
autorange: false,
gridcolor: 'rgb(38.25,38.25,38.25)',
gridwidth: 1,
linecolor: 'rgb(38.25,38.25,38.25)',
linewidth: 1,
tickcolor: 'rgb(38.25,38.25,38.25)',
tickwidth: 1,
titlefont: {
size: 11,
color: 'rgb(38.25,38.25,38.25)',
family: 'Arial, sans-serif'
},
exponentformat: 'none',
showticklabels: true
},
height: 480,
margin: {
b: 0,
l: 0,
r: 0,
t: 0,
pad: 0
},
autosize: false,
hovermode: 'closest',
titlefont: {
color: 'rgba(0,0,0,0)'
},
showlegend: false,
annotations: [
{
x: 0.5175,
y: 0.935,
font: {
size: 11,
color: 'rgb(0,0,0)',
family: 'Arial, sans-serif'
},
text: '',
xref: 'paper',
yref: 'paper',
align: 'center',
xanchor: 'center',
yanchor: 'bottom',
borderpad: 3,
showarrow: false,
textangle: 0,
bordercolor: 'rgba(0,0,0,0)',
borderwidth: 0.5
}
],
plot_bgcolor: 'rgba(0,0,0,0)',
paper_bgcolor: 'rgb(255,255,255)'
},
frames: []
}
const figureMag = {
data: [
{
line: {
dash: 'solid',
color: 'rgb(0,113.985,188.955)',
width: 1
},
mode: 'lines',
name: '',
type: 'scatter',
x: [],
y: [],
xaxis: 'x',
yaxis: 'y',
marker: {
line: {
width: 1,
color: []
},
size: 8,
symbol: 'circle',
color: []
},
visible: true,
showlegend: true
}
],
layout: {
title: '',
width: 840,
xaxis: {
title: 'Normalized Frequency (× π rad/sample)',
side: 'bottom',
type: 'linear',
range: [0, 2],
ticks: 'inside',
anchor: 'y',
domain: [0.13, 0.905],
mirror: 'ticks',
nticks: 9,
ticklen: 6.51,
showgrid: true,
showline: true,
tickfont: {
size: 10,
color: 'rgb(38.25,38.25,38.25)',
family: 'Arial, sans-serif'
},
tickmode: 'array',
tickvals: [0, 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2],
zeroline: false,
autorange: true,
gridcolor: 'rgba(38.25,38.25,38.25,0.25)',
gridwidth: 1,
linecolor: 'rgb(38.25,38.25,38.25)',
linewidth: 1,
tickcolor: 'rgb(38.25,38.25,38.25)',
tickwidth: 1,
titlefont: {
size: 18,
color: 'rgb(38.25,38.25,38.25)',
family: 'serif'
},
exponentformat: 'none'
},
yaxis: {
title: 'Magnitude (dB)',
side: 'left',
type: 'linear',
range: [],
ticks: 'inside',
anchor: 'x',
domain: [0.11, 0.925],
mirror: 'ticks',
nticks: 12,
ticklen: 6.51,
showgrid: true,
showline: true,
tickfont: {
size: 10,
color: 'rgb(38.25,38.25,38.25)',
family: 'Arial, sans-serif'
},
tickmode: 'auto',
zeroline: false,
autorange: true,
gridcolor: 'rgba(38.25,38.25,38.25,0.25)',
gridwidth: 1,
linecolor: 'rgb(38.25,38.25,38.25)',
linewidth: 1,
tickcolor: 'rgb(38.25,38.25,38.25)',
tickwidth: 1,
titlefont: {
size: 18,
color: 'rgb(38.25,38.25,38.25)',
family: 'serif'
},
exponentformat: 'none',
showticklabels: true
},
height: 480,
margin: {
b: 0,
l: 0,
r: 0,
t: 0,
pad: 0
},
autosize: false,
hovermode: 'closest',
titlefont: {
color: 'rgba(38.25,38.25,38.25,1)'
},
showlegend: false,
annotations: [
{
x: 0.5175,
y: 0.935,
font: {
size: 11,
color: 'rgb(0,0,0)',
family: 'Arial, sans-serif'
},
text: '',
xref: 'paper',
yref: 'paper',
align: 'center',
xanchor: 'center',
yanchor: 'bottom',
borderpad: 3,
showarrow: false,
textangle: 0,
bordercolor: 'rgba(0,0,0,0)',
borderwidth: 0.5
}
],
plot_bgcolor: 'rgba(0,0,0,0)',
paper_bgcolor: 'rgb(255,255,255)'
},
frames: []
}
const figureOut = JSON.parse(JSON.stringify(figureIn))
const figurePhase = JSON.parse(JSON.stringify(figureMag))
window.app = new Vue({
el: '#root',
data: {
figureIn: figureIn,
figureOut: figureOut,
figureMag: figureMag,
figurePhase: figurePhase,
p: 0,
q: 0,
a: [1],
b: [1],
w: [],
x: [1].concat(Array(19).fill(0)),
y: [1].concat(Array(19).fill(0)),
z: [],
preset: 'd',
length: 20,
isTrig: false,
A: 1,
T: 19,
tao: 0,
scaler: 1,
reverse: false,
toPower: false,
readonly: true,
stem: true,
systemFile: '',
fileReader: new FileReader()
},
methods: {
systemFileHandler () {
const file = document.querySelector('#systemFile').files[0]
if (file) {
this.systemFile = file
this.fileReader.readAsText(this.systemFile)
}
},
getDegreeKey (s) {
// convert 'a' -> 'p', 'b' -> 'q'
// 'p' - 'a' == 15
return String.fromCharCode(s.charCodeAt(0) + 15)
},
system (s) {
this.truncate()
const old = this[s].slice(0)
const len = this[this.getDegreeKey(s)] + 1
if (len > old.length) {
this[s] = Array(len).fill(1)
Array.prototype.splice.apply(this[s], [0, old.length].concat(old))
} else {
this[s] = old.slice(0, len)
}
this.output()
},
input (name, length) {
const omega = (2 * Math.PI) / this.T
const fn = {
d: [1].concat(Array(length - 1).fill(0)),
u: Array(length).fill(1),
sin: [...Array(length).keys()].map(
x => this.A * Math.sin(omega * (x + this.tao))
),
cos: [...Array(length).keys()].map(
x => this.A * Math.cos(omega * (x + this.tao))
),
exp: [...Array(length).keys()].map(x => Math.exp(x)),
log: [...Array(length).keys()].map(x => Math.log(x + 1)),
n: [...Array(length).keys()],
c: this.x
}
return fn[name]
},
output () {
const d = this.input('d', this.length)
const h = this.filter(this.b, this.a, d)
const x = this.input(this.preset, this.length)
this.x = x.map((x, n) => this.scale(x, n))
if (this.reverse) this.x = this.x.reverse()
this.y = this.conv(this.x, h).slice(0, this.length)
this.redrawIO(this.x, this.figureIn)
this.redrawIO(this.y, this.figureOut)
},
scale (x, n) {
const s = Math.pow(this.scaler, this.toPower ? n : 1)
return s * x
},
presetHandler () {
const s = this.preset
if (s === 'c') {
this.scaler = 1
this.toPower = false
this.readonly = false
} else {
this.length = 20
this.x = this.input(s, this.length)
this.readonly = true
if (s === 'sin' || s === 'cos') {
this.isTrig = true
} else {
this.isTrig = false
}
}
this.output()
},
lengthHandler () {
this.truncate()
if (this.length > this.x.length) {
const s = this.preset
if (s === 'c') {
const diff = this.length - this.x.length
this.x = this.x.concat(Array(diff).fill(0))
} else {
this.x = this.input(s, this.length)
}
} else {
this.x.length = this.length
}
this.output()
},
inputHandler () {
this.x = this.x.split(',')
this.length = this.x.length
this.output()
},
trigHandler () {
this.truncate()
this.output()
},
truncate () {
const keys = ['p', 'q', 'length', 'T', 'tao']
keys.forEach(key => {
this[key] = Math.trunc(this[key])
})
},
// view helper function
firstTerm (a, i) {
for (let j = 0; j < i; j += 1) {
if (a[j] !== 0) return false
}
return true
},
// view helper function
allZero (a) {
for (const i of a) {
if (i !== 0) return false
}
return true
},
filter (b, a, x) {
if (a[0] !== 0) {
const n = a.length - 1
const m = b.length - 1
const y = []
for (let i = 0; i < x.length; i += 1) {
let lhs = 0
let rhs = 0
for (let j = 1; j <= n && j <= i; j += 1) {
lhs = lhs + a[j] * y[i - j]
}
for (let j = 0; j <= m && j <= i; j += 1) {
rhs = rhs + b[j] * x[i - j]
}
y[i] = (rhs - lhs) / a[0]
}
return y
}
return x
},
conv (x, h) {
let disp = 0
const y = []
for (let k = 0; k < h.length; k += 1) {
y.push(x[0] * h[k])
}
disp = disp + 1
for (let j = 1; j < x.length; j += 1) {
for (let k = 0; k < h.length; k += 1) {
if (disp + k !== y.length) {
y[disp + k] = y[disp + k] + x[j] * h[k]
} else {
y.push(x[j] * h[k])
}
}
disp = disp + 1
}
return y
},
redrawIO (data, figure) {
const x = figure.data[0].x
const y = figure.data[0].y
const lineColor = figure.data[0].marker.line.color
const color = figure.data[0].marker.color
x.length = 0
y.length = 0
lineColor.length = 0
color.length = 0
for (let i = 0; i < this.length; i += 1) {
if (this.stem) {
figure.data[0].mode = 'lines+markers'
x.push(i, i, null)
y.push(0, data[i], null)
lineColor.push(
'rgba(0,0,0,0)',
'rgb(0,113.985,188.955)',
'rgba(0,0,0,0)'
)
color.push('rgba(0,0,0,0)', 'rgba(0,0,0,0)', 'rgba(0,0,0,0)')
} else {
figure.data[0].mode = 'lines'
x.push(i)
y.push(data[i])
}
}
Plotly.redraw(document.getElementById('input'))
Plotly.redraw(document.getElementById('output'))
},
freqz (ob, oa, oz) {
let b = ob.slice()
let a = oa.slice()
if (b.length > a.length) {
a = a.slice().concat(Array(b.length - a.length).fill(0))
} else if (b.length < a.length) {
b = b.slice().concat(Array(a.length - b.length).fill(0))
}
const H = []
for (let i = 0; i < oz.length; i += 1) {
const z = math.conj(oz[i])
let B = math.complex(0, 0)
let A = math.complex(0, 0)
for (let i = 0; i < b.length; i += 1) {
B = math.add(B, math.multiply(b[i], math.pow(z, i)))
A = math.add(A, math.multiply(a[i], math.pow(z, i)))
}
H.push(math.round(math.divide(B, A), 5))
}
return H
},
unwrap (op) {
const p = op.slice()
const dp = []
const dps = []
const dpcorr = []
const cumsum = []
const cutoff = Math.PI
for (let i = 0; i < p.length - 1; i += 1) {
dp[i] = p[i + 1] - p[i]
}
for (let i = 0; i < p.length - 1; i += 1) {
dps[i] =
dp[i] +
Math.PI -
Math.floor((dp[i] + Math.PI) / (2 * Math.PI)) * (2 * Math.PI) -
Math.PI
}
for (let i = 0; i < p.length - 1; i += 1) {
if (dps[i] === -Math.PI && dp[i] > 0) {
dps[i] = Math.PI
}
}
for (let i = 0; i < p.length - 1; i += 1) {
dpcorr[i] = dps[i] - dp[i]
}
for (let i = 0; i < p.length - 1; i += 1) {
if (Math.abs(dp[i]) < cutoff) {
dpcorr[i] = 0
}
}
cumsum[0] = dpcorr[0]
for (let i = 1; i < p.length - 1; i += 1) {
cumsum[i] = cumsum[i - 1] + dpcorr[i]
}
for (let i = 1; i < p.length; i += 1) {
p[i] += cumsum[i - 1]
}
return p
},
redrawFR () {
const mags1 = []
const mags2 = []
const phases1 = []
const phases2 = []
const H = this.freqz(this.b, this.a, this.z)
for (let i = 0; i < H.length; i += 1) {
const mag = Math.sqrt(Math.pow(H[i].re, 2) + Math.pow(H[i].im, 2))
const phase = atan2(H[i].im, H[i].re)
if (i < 512) {
mags1.push(20 * Math.log10(mag))
phases1.push(phase)
} else {
mags2.push(20 * Math.log10(mag))
phases2.push(phase)
}
}
const unwrap1 = this.unwrap(phases1).map(x => (x * 180) / Math.PI)
const unwrap2 = this.unwrap(phases2).map(x => (x * 180) / Math.PI)
mags1[511] = null
unwrap1[511] = null
mags2[0] = null
unwrap2[0] = null
figureMag.data[0].y = mags1.concat(mags2)
figurePhase.data[0].y = unwrap1.concat(unwrap2)
Plotly.redraw(document.getElementById('magnitude'))
Plotly.redraw(document.getElementById('phase'))
function atan2 (im, re) {
if (im === 0 && re === 0) {
return 0
}
return Math.atan2(im, re)
}
},
drawBuffer (width, height, context, buffer) {
context.clearRect(0, 0, width, height)
const data = buffer
const step = Math.ceil(data.length / width)
const amp = height / 2
for (let i = 0; i < width; i++) {
let min = 1.0
let max = -1.0
for (let j = 0; j < step; j++) {
const datum = data[i * step + j]
if (datum < min) min = datum
if (datum > max) max = datum
}
context.fillStyle = '#0072BD'
context.fillRect(i, (1 + min) * amp, 1, Math.max(1, (max - min) * amp))
}
},
reverb () {
const canvas = document.getElementById('waveform3')
const audioSource = new Audio('asset/sample.mp3')
const audioCtx = new AudioContext()
const audioInput = audioCtx.createMediaElementSource(audioSource)
const convolver = audioCtx.createConvolver()
const gain = audioCtx.createGain()
const analyser = audioCtx.createAnalyser()
const waveform = new Float32Array(analyser.frequencyBinCount)
let request
let chunks = new Float32Array(0)
gain.gain.value = 3.5
audioSource.onended = e => {
cancelAnimationFrame(request)
audioCtx.close()
this.drawBuffer(
canvas.width,
canvas.height,
canvas.getContext('2d'),
chunks
)
}
fetch('asset/clap.mp3')
.then(response => response.arrayBuffer())
.then(arrayBuffer => audioCtx.decodeAudioData(arrayBuffer))
.then(buffer => {
convolver.buffer = buffer
audioInput
.connect(convolver)
.connect(gain)
.connect(analyser)
.connect(audioCtx.destination)
audioSource.play()
updateWaveform()
})
function updateWaveform () {
request = requestAnimationFrame(updateWaveform)
analyser.getFloatTimeDomainData(waveform)
chunks = Float32Concat(chunks, waveform)
}
function Float32Concat (first, second) {
const firstLength = first.length
const result = new Float32Array(firstLength + second.length)
result.set(first)
result.set(second, firstLength)
return result
}
}
},
mounted () {
const input = document.getElementById('input')
const output = document.getElementById('output')
const magnitude = document.getElementById('magnitude')
const phase = document.getElementById('phase')
for (let i = 0; i <= 1023; i += 1) {
this.w.push((i * (2 * Math.PI)) / 1023)
this.z.push(math.exp(math.complex(0, this.w[i])))
}
figurePhase.layout.yaxis.title = 'Phase (degrees)'
figureMag.data[0].x = this.w.map(x => x / Math.PI)
figurePhase.data[0].x = this.w.map(x => x / Math.PI)
Plotly.plot(input, { data: figureIn.data, layout: figureIn.layout })
Plotly.plot(output, { data: figureOut.data, layout: figureOut.layout })
Plotly.plot(magnitude, { data: figureMag.data, layout: figureMag.layout })
Plotly.plot(phase, { data: figurePhase.data, layout: figurePhase.layout })
this.redrawIO(this.x, this.figureIn)
this.redrawIO(this.y, this.figureOut)
this.redrawFR()
this.fileReader.onload = e => {
const json = JSON.parse(this.fileReader.result)
this.a = json.a
this.b = json.b
this.p = this.a.length - 1
this.q = this.b.length - 1
this.output()
}
}
})