Initial commit
This commit is contained in:
BIN
Home/LTI/asset/clap.mp3
Normal file
BIN
Home/LTI/asset/clap.mp3
Normal file
Binary file not shown.
BIN
Home/LTI/asset/sample.mp3
Normal file
BIN
Home/LTI/asset/sample.mp3
Normal file
Binary file not shown.
BIN
Home/LTI/asset/wavform-clap.png
Normal file
BIN
Home/LTI/asset/wavform-clap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
Home/LTI/asset/wavform-sample.png
Normal file
BIN
Home/LTI/asset/wavform-sample.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
201
Home/LTI/index.html
Normal file
201
Home/LTI/index.html
Normal file
@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>LTI System Analysis</title>
|
||||
<link rel="stylesheet" href="lib/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<script src="lib/plotlyjs.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="container">
|
||||
<h1 class="mt-3">LTI System Analysis</h1>
|
||||
<h3 class="code mt-5">
|
||||
<span class="coef">a<sub>0</sub></span>y[n] +
|
||||
<span class="coef">a<sub>1</sub></span>y[n-1] +
|
||||
<span class="coef">a<sub>2</sub></span>y[n-2] + ... +
|
||||
<span class="coef">a<sub>p</sub></span>y[n-<span class="deg">p</span>] =
|
||||
</h3>
|
||||
<h3 class="code text-right">
|
||||
<span class="coef">b<sub>0</sub></span>x[n] +
|
||||
<span class="coef">b<sub>1</sub></span>x[n-1] +
|
||||
<span class="coef">b<sub>2</sub></span>x[n-2] + ... +
|
||||
<span class="coef">b<sub>q</sub></span>x[n-<span class="deg">q</span>]
|
||||
</h3>
|
||||
<dl class="row mt-5">
|
||||
<dt class="col-sm-2">JSON data:</dt>
|
||||
<dd class="col-sm-10">
|
||||
<div class="custom-file">
|
||||
<input type="file" id="systemFile" class="custom-file-input" @change="systemFileHandler">
|
||||
<label class="custom-file-label" for="systemFile">{{ systemFile ? systemFile.name : 'Choose file' }}</label>
|
||||
<button type="button" class="btn btn-secondary" @click="systemFileHandler">Reload</button>
|
||||
</div>
|
||||
</dd>
|
||||
<dd class="col-sm-12 maxheight">
|
||||
<dl class="row mt-0 mb-0">
|
||||
<dt class="col-sm-2">Orders:</dt>
|
||||
<dd class="code col-sm-5">
|
||||
<label>p<sub> </sub> =</label>
|
||||
<input type="number" min="0" v-model.number="p" @change="system('a')">
|
||||
</dd>
|
||||
<dd class="code col-sm-5">
|
||||
<label>q<sub> </sub> =</label>
|
||||
<input type="number" min="0" v-model.number="q" @change="system('b')">
|
||||
</dd>
|
||||
<dt class="col-sm-2">Coefficients:</dt>
|
||||
<dd class="code col-sm-5">
|
||||
<p v-for="(c, i) in a">
|
||||
<label>a<sub>{{ i }}</sub> =</label>
|
||||
<input type="number" v-model.number="a[i]" @change="output">
|
||||
</p>
|
||||
</dd>
|
||||
<dd class="code col-sm-5">
|
||||
<p v-for="(c, i) in b">
|
||||
<label>b<sub>{{ i }}</sub> =</label>
|
||||
<input type="number" v-model.number="b[i]" @change="output">
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt class="col-sm-2">System:</dt>
|
||||
<dd class="code col-sm-10">
|
||||
<span v-for="(c, i) in a">
|
||||
<span v-if="!firstTerm(a, i) && c > 0">+</span>
|
||||
<span v-else-if="c < 0">-</span>
|
||||
<span v-if="c == 1 || c == -1">
|
||||
y[n{{ i ? '-' + i : '' }}]
|
||||
</span>
|
||||
<span v-else-if="c > 0">
|
||||
{{ c }}y[n{{ i ? '-' + i : '' }}]
|
||||
</span>
|
||||
<span v-else-if="c < 0">
|
||||
{{ Math.abs(c) }}y[n{{ i ? '-' + i : '' }}]
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="allZero(a)">0</span>
|
||||
=
|
||||
<span v-for="(c, i) in b">
|
||||
<span v-if="!firstTerm(b, i) && c > 0">+</span>
|
||||
<span v-else-if="c < 0">-</span>
|
||||
<span v-if="c == 1 || c == -1">
|
||||
x[n{{ i ? '-' + i : '' }}]
|
||||
</span>
|
||||
<span v-else-if="c > 0">
|
||||
{{ c }}x[n{{ i ? '-' + i : '' }}]
|
||||
</span>
|
||||
<span v-else-if="c < 0">
|
||||
{{ Math.abs(c) }}x[n{{ i ? '-' + i : '' }}]
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="allZero(b)">0</span>
|
||||
</dd>
|
||||
<hr class="divider">
|
||||
<dt class="col-sm-12 mb-4"><h5>Time Response Simulation:</h5></dt>
|
||||
<dt class="col-sm-2">Plot mode:</dt>
|
||||
<dd class="col-sm-10">
|
||||
<input id="stem" type="checkbox" v-model="stem" @change="output">
|
||||
<label for="stem">stem</label>
|
||||
</dd>
|
||||
<dt class="col-sm-2">Input preset:</dt>
|
||||
<dd class="code col-sm-3">
|
||||
<select v-model="preset" @change="presetHandler">
|
||||
<option value="d">δ[n]</option>
|
||||
<option value="u">u[n]</option>
|
||||
<option value="sin">Asin[Ωn+Φ]</option>
|
||||
<option value="cos">Acos[Ωn+Φ]</option>
|
||||
<option value="exp">exp[n]</option>
|
||||
<option value="log">log[n+1]</option>
|
||||
<option value="n">n</option>
|
||||
<option value="c">custom</option>
|
||||
</select>
|
||||
</dd>
|
||||
<dt class="col-sm-2">Input length: </dt>
|
||||
<dd class="code col-sm-5">
|
||||
<input type="number" min="1" v-model.number="length" @change="lengthHandler">
|
||||
</dd>
|
||||
<dt v-if="isTrig" class="col-sm-2">A:</dt>
|
||||
<dd v-if="isTrig" class="code col-sm-10">
|
||||
<input type="number" v-model.number="A" @change="trigHandler">
|
||||
</dd>
|
||||
<dt v-if="isTrig" class="col-sm-2">Ω:</dt>
|
||||
<dd v-if="isTrig" class="code col-sm-10">
|
||||
2π / <input type="number" min="1" v-model.number="T" @change="trigHandler">
|
||||
</dd>
|
||||
<dt v-if="isTrig" class="col-sm-2">Φ:</dt>
|
||||
<dd v-if="isTrig" class="code col-sm-10">
|
||||
Ω × <input type="number" v-model.number="tao" @change="trigHandler">
|
||||
</dd>
|
||||
<dt class="col-sm-2">Input scaler:</dt>
|
||||
<dd class="code col-sm-3">
|
||||
<input type="number" v-model.number="scaler" @change="output" :disabled="!readonly">
|
||||
</dd>
|
||||
<dt class="col-sm-3">Raise scaler to the power of n: </dt>
|
||||
<dd class="col-sm-4">
|
||||
<input type="checkbox" v-model="toPower" @change="output" :disabled="!readonly">
|
||||
</dd>
|
||||
<dt class="col-sm-2"></dt>
|
||||
<dd class="col-sm-3"></dd>
|
||||
<dt class="col-sm-3">Reverse: </dt>
|
||||
<dd class="col-sm-4">
|
||||
<input type="checkbox" v-model="reverse" @change="output">
|
||||
</dd>
|
||||
<dt class="col-sm-2"></dt>
|
||||
<dd class="code col-sm-10"></dd>
|
||||
<dt class="col-sm-2">Input:</dt>
|
||||
<dd class="code col-sm-10">
|
||||
<input style="width: 100%" v-model="x" @change="inputHandler" :readonly="readonly">
|
||||
</dd>
|
||||
<div id="input"></div>
|
||||
<hr class="divider">
|
||||
<dt class="col-sm-2">Output:</dt>
|
||||
<dd class="code col-sm-10">
|
||||
<p class="nowrap" v-text="y.join(',')"></p>
|
||||
</dd>
|
||||
<div id="output"></div>
|
||||
<hr class="divider">
|
||||
<dt class="col-sm-10 mb-3"><h5>Frequency Response Analysis:</h5></dt>
|
||||
<dd class="col-sm-2">
|
||||
<button class="btn btn-success float-right" @click="redrawFR">Replot</button>
|
||||
</dd>
|
||||
<div id="magnitude"></div>
|
||||
<div id="phase"></div>
|
||||
<hr class="divider">
|
||||
<dt class="col-sm-10 mb-3"><h5>Convolution Reverb Demo:</h5></dt>
|
||||
<dd class="col-sm-2"></dd>
|
||||
<dt class="col-sm-2">Clap response:</dt>
|
||||
<dd class="col-sm-10">
|
||||
<div>
|
||||
<iframe width="480" height="270" src="https://www.youtube.com/embed/W3hsOFazEz4?rel=0" frameborder="0"></iframe>
|
||||
</div>
|
||||
<div>
|
||||
<img src="asset/wavform-clap.png" alt="clap">
|
||||
</div>
|
||||
<audio controls>
|
||||
<source src="asset/clap.mp3" type="audio/mpeg">
|
||||
Your browser does not support the audio tag.
|
||||
</audio>
|
||||
</dd>
|
||||
<dt class="col-sm-2">Sample input:</dt>
|
||||
<dd class="col-sm-10">
|
||||
<div>
|
||||
<img src="asset/wavform-sample.png" alt="sample">
|
||||
</div>
|
||||
<audio controls>
|
||||
<source src="asset/sample.mp3" type="audio/mpeg">
|
||||
Your browser does not support the audio tag.
|
||||
</audio>
|
||||
</dd>
|
||||
<dt class="col-sm-2">Result:</dt>
|
||||
<dd class="col-sm-10">
|
||||
<div>
|
||||
<canvas id="waveform3" width="480" height="100"></canvas>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="reverb">Reverb</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<script src="lib/math.min.js"></script>
|
||||
<script src="lib/vue.min.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
7
Home/LTI/lib/bootstrap.min.css
vendored
Normal file
7
Home/LTI/lib/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
43
Home/LTI/lib/math.min.js
vendored
Normal file
43
Home/LTI/lib/math.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
24
Home/LTI/lib/plotlyjs.min.js
vendored
Normal file
24
Home/LTI/lib/plotlyjs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
Home/LTI/lib/vue.min.js
vendored
Normal file
6
Home/LTI/lib/vue.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
49
Home/LTI/main.css
Normal file
49
Home/LTI/main.css
Normal file
@ -0,0 +1,49 @@
|
||||
input {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.deg {
|
||||
color: rgb(192, 0, 0);
|
||||
}
|
||||
|
||||
.coef {
|
||||
color: rgb(0, 0, 192);
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: "Menlo", "Consolas", monospace;
|
||||
}
|
||||
|
||||
.code p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
margin-bottom: 1.8rem;
|
||||
}
|
||||
|
||||
.custom-file {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.custom-file button {
|
||||
position: relative;
|
||||
top: -38px;
|
||||
left: 305px;
|
||||
}
|
||||
|
||||
.maxheight {
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
audio:focus {
|
||||
outline: none;
|
||||
}
|
20
Home/LTI/package.json
Normal file
20
Home/LTI/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "LTI",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"watch": "npm run build -- --watch"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"babel-loader": "^8.0.6",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10"
|
||||
}
|
||||
}
|
706
Home/LTI/src/main.dev.js
Normal file
706
Home/LTI/src/main.dev.js
Normal 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()
|
||||
}
|
||||
}
|
||||
})
|
13
Home/LTI/webpack.config.js
Normal file
13
Home/LTI/webpack.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
mode: 'none',
|
||||
entry: './src/main.dev.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './'),
|
||||
filename: 'main.js'
|
||||
},
|
||||
module: {
|
||||
rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user