summaryrefslogtreecommitdiff
path: root/tools/mq_editor/index.html
blob: 1dd36a6d1074972dbae651c60d4fef66ef2f02c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>MQ Spectral Editor</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="page-title">
    <h2>MQ Spectral Editor</h2>
    <span id="fileLabel"></span>
  </div>

  <div class="toolbar">
    <input type="file" id="wavFile" accept=".wav">
    <button id="chooseFileBtn">&#x1F4C2; Open WAV</button>
    <button id="testWavBtn">⚗ Test WAV</button>
    <button id="extractBtn" disabled>Extract Partials</button>
    <button id="autoSpreadAllBtn" disabled>Auto Spread All</button>
    <button id="playBtn" disabled>▶ Play</button>
    <button id="stopBtn" disabled>■ Stop</button>
    <button id="newPartialBtn" disabled>+ Partial</button>
    <button id="clearAllBtn" disabled>✕ Clear All</button>
    <button id="exploreBtn" disabled>⊕ Explore</button>
    <button id="contourBtn" disabled>≋ Contour</button>
    <button id="undoBtn" disabled>↩ Undo</button>
    <button id="redoBtn" disabled>↪ Redo</button>
    <span class="toolbar-wrap">
      <button id="paramsBtn">⚙ Params</button>
      <div id="paramsPanel">
        <div class="param-group">
          <span class="group-label">STFT</span>
          <label title="STFT hop size in samples. Smaller = finer time resolution, more frames, slower.">Hop</label>
          <input type="number" id="hopSize" value="256" min="64" max="1024" step="64" style="width:60px;">
        </div>
        <div class="param-group">
          <span class="group-label">Peak Detect</span>
          <label title="Minimum spectral peak amplitude in dB. Peaks below this are ignored.">Threshold (dB)</label>
          <input type="number" id="threshold" value="-20" step="any">
          <label title="Minimum prominence in dB: how much a peak must stand above its surrounding valley. Suppresses weak shoulders.">Prominence (dB)</label>
          <input type="number" id="prominence" value="1.0" step="0.1" min="0">
          <label title="Weight spectrum by frequency before peak detection: f × Power(f). Boosts high-frequency peaks relative to low-frequency ones.">
            <input type="checkbox" id="freqWeight"> f·Power
          </label>
        </div>
        <div class="param-group">
          <span class="group-label">Tracking</span>
          <label title="Frames a candidate must persist consecutively before being promoted to a tracked partial. Higher = fewer spurious short bursts.">Birth</label>
          <input type="number" id="birthPersistence" value="3" min="1" max="10" step="1" style="width:50px;">
          <label title="Frames a partial can go unmatched before it is terminated. Higher = bridges short gaps; lower = cuts off quickly.">Death</label>
          <input type="number" id="deathAge" value="5" min="1" max="20" step="1" style="width:50px;">
          <label title="Weight of phase prediction error in the peak-matching cost function. Higher = stricter phase coherence required to continue a partial.">Phase Wt</label>
          <input type="number" id="phaseErrorWeight" value="2.0" min="0" max="10" step="0.5" style="width:55px;">
          <label title="Minimum number of frames a tracked partial must span. Shorter partials are discarded after tracking.">Min Len</label>
          <input type="number" id="minLength" value="10" min="1" max="50" step="1" style="width:50px;">
        </div>
      </div>
    </span>
  </div>

  <div class="main-area">
    <div class="canvas-col">
      <div class="canvas-wrap">
        <canvas id="canvas" width="1400" height="600"></canvas>
        <canvas id="cursorCanvas" width="1400" height="600"></canvas>
        <canvas id="playheadCanvas" width="1400" height="600"></canvas>

        <!-- Mini spectrum viewer (bottom-right overlay) -->
        <div id="spectrumViewer">
          <canvas id="spectrumCanvas" width="400" height="100"></canvas>
        </div>
        <!-- Keep slider (bottom-left overlay) -->
        <div id="keepOverlay">
          <span title="Keep only the strongest N% of extracted partials, ranked by peak amplitude.">Keep</span>
          <input type="range" id="keepPct" min="1" max="100" value="100">
          <span id="keepPctLabel">100%</span>
        </div>
      </div>

      <!-- Amplitude bezier editor (shown when partial selected) -->
      <div id="ampEditPanel">
        <div class="amp-edit-header">
          <span>Amplitude</span>
          <span id="ampEditTitle"></span>
          <span class="amp-edit-hint">drag control points · Esc to deselect</span>
        </div>
        <canvas id="ampEditCanvas" width="1400" height="120"></canvas>
      </div>
    </div>

    <div class="right-panel">
      <!-- Partial properties (visible when a partial is selected) -->
      <div id="partialProps" style="display:none;">
        <div class="panel-title">
          <span id="propTitle">Partial #—</span>
          <span id="propSwatch"></span>
        </div>
        <div class="prop-row">
          <span class="prop-label">Peak</span>
          <span id="propPeak">—</span>
        </div>
        <div class="prop-row">
          <span class="prop-label">Time</span>
          <span id="propTime">—</span>
        </div>
        <div class="curve-tabs">
          <button class="tab-btn active" data-tab="Freq">Freq</button>
          <button class="tab-btn" data-tab="Amp">Amp</button>
          <button class="tab-btn" data-tab="Synth">Synth</button>
        </div>
        <div class="tab-pane" id="tabFreq">
          <div class="curve-grid" id="freqCurveGrid"></div>
        </div>
        <div class="tab-pane" id="tabAmp" style="display:none;">
          <div class="curve-grid" id="ampCurveGrid"></div>
        </div>
        <div class="tab-pane" id="tabSynth" style="display:none;">
          <div class="synth-grid" id="synthGrid"></div>
        </div>
        <div class="partial-actions">
          <button id="mutePartialBtn">Mute</button>
          <button id="deletePartialBtn">Delete</button>
        </div>
      </div>

      <div id="noSelMsg">Click a partial to select</div>

      <!-- Synthesis options (always at bottom) -->
      <div class="synth-section">
        <div class="panel-title">Synthesis</div>
        <label><input type="checkbox" id="integratePhase" checked> Integrate phase</label>
        <label><input type="checkbox" id="disableJitter"> Disable jitter</label>
        <label><input type="checkbox" id="disableSpread"> Disable spread</label>
        <label title="Test mode: force resonator synthesis for all partials (ignores per-partial mode setting)"><input type="checkbox" id="forceResonator"> Resonator (all)</label>
        <div id="globalResParams" style="display:none;">
          <label title="Global pole radius r in (0,1). Applied to all partials in resonator mode.">
            r (pole)
            <input type="range" id="globalR" min="0.75" max="0.9999" step="0.0001" value="0.995">
            <span id="globalRVal" class="slider-val">0.9950</span>
          </label>
          <label title="Global gain compensation applied to all partials in resonator mode.">
            gain
            <input type="range" id="globalGain" min="0.0" max="4.0" step="0.01" value="1.0">
            <span id="globalGainVal" class="slider-val">1.00</span>
          </label>
          <label title="Override per-partial r/gain with global values during playback"><input type="checkbox" id="forceRGain"> force r/gain</label>
        </div>
        <div style="margin-top:6px;">
          <label title="LP filter coefficient k1 in (0,1]. 1.0 = bypass.">
            LP k1
            <input type="range" id="lpK1" min="0.001" max="1.0" step="0.001" value="1.0">
            <span id="lpK1Val" class="slider-val">bypass</span>
          </label>
          <label title="HP filter coefficient k2 in (0,1]. 1.0 = bypass.">
            HP k2
            <input type="range" id="hpK2" min="0.001" max="1.0" step="0.001" value="1.0">
            <span id="hpK2Val" class="slider-val">bypass</span>
          </label>
        </div>
      </div>
    </div>
  </div>

  <div id="tooltip"></div>

  <div id="status">Load a WAV file to begin...</div>

  <script src="utils.js"></script>
  <script src="fft.js"></script>
  <script src="mq_extract.js"></script>
  <script src="mq_synth.js"></script>
  <script src="viewer.js"></script>
  <script src="editor.js"></script>
  <script src="app.js"></script>
</body>
</html>