그동안 쌓여 있던 미커밋 파일들을 정리. 소스·자산만 올리고, 개인 데이터와 런타임 상태는 gitignore로 차단. 추가된 자산: - web-ui/audiomass, web-ui/wavacity — 자체 호스팅 오디오 에디터 - web-ui/drums — 드럼 샘플 - wol-gate — WOL 프록시 컨테이너(Dockerfile + server.js) - glm-1m-fix — GLM 1M 컨텍스트 설치 스크립트(linux/windows) - RENODE_FIXES_SUMMARY*.md, CCTV정보_서울특별시.csv, .claude/plans 수정: - edge_tts_synth.py: rate 인자 추가(말하기 속도 조절) - .smallclaw/config.json: google(gemini) provider 등록 — 키는 vault 참조 - assets/SmallClaw*.png 삭제분 반영 gitignore (중요): - .smallclaw/users/*/workspace/ 전체 차단. 기존 패턴이 memory/uploads와 일부 확장자만 막아서 detective/(고소장·통화녹취·의사소견서·가족관계증명서), generated-media/, music/, pptx/ 등 앱 생성 개인 데이터 403MB가 노출돼 있었음 - .smallclaw/active-sessions.json 차단 — 살아있는 bearer 토큰과 admin 역할이 평문으로 들어있어 커밋 시 게이트웨이 관리자 자격증명이 그대로 공개됨 - google-usage.json, config.json.bak-*, workspace/memory·weather-maps· task_result_*, voice/ 등 런타임 상태도 함께 차단 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
501 lines
11 KiB
JavaScript
501 lines
11 KiB
JavaScript
(function ( w, d, PKAE ) {
|
|
'use strict';
|
|
|
|
var _pid = 0;
|
|
var _aid = 0;
|
|
|
|
function FXAutomation ( app, filter_modal, val_cb, preview_cb ) {
|
|
var q = this;
|
|
|
|
q.modal = filter_modal;
|
|
q.app = app;
|
|
q.wv = app.engine.wavesurfer;
|
|
var mt = app.multitrack;
|
|
var mt_buffer = mt && mt.IsOn && mt.IsOn () && mt.GetFxBuffer ?
|
|
mt.GetFxBuffer () :
|
|
null;
|
|
if (mt_buffer) q.wv = {
|
|
backend:{buffer:mt_buffer},
|
|
regions:{list:[]},
|
|
getDuration:function () { return mt_buffer.duration; }
|
|
};
|
|
q.points = {};
|
|
q.act = null;
|
|
q.act_point = null;
|
|
q.in_auto = false;
|
|
q.rbuff = null;
|
|
q.waveDarken = filter_modal.waveDarken || 0;
|
|
|
|
q.btn_auto = _make_btn_auto ( q );
|
|
|
|
q.GetValue = function () {
|
|
var data = [];
|
|
|
|
var inputs = q.modal.el_body.getElementsByTagName('input');
|
|
var plen = q.points.length;
|
|
|
|
for (var i = 0; i < inputs.length; ++i)
|
|
{
|
|
var curr = inputs[i];
|
|
if (q.points[curr.id])
|
|
{
|
|
var arr = [];
|
|
var p = q.points[curr.id];
|
|
for (var j = 0; j < p.length; ++j)
|
|
{
|
|
var tmp = {
|
|
time: p[j].time,
|
|
val: p[j].val
|
|
};
|
|
arr.push(tmp);
|
|
val_cb && val_cb (tmp, curr);
|
|
}
|
|
data.push (arr);
|
|
}
|
|
else
|
|
{
|
|
var tmp = {
|
|
val: curr.value
|
|
};
|
|
|
|
data.push (tmp);
|
|
val_cb && val_cb (tmp, curr);
|
|
}
|
|
}
|
|
return (data);
|
|
};
|
|
q.DelAct = function ( min ) {
|
|
var p = q.act && q.points[q.act.id], i = p && p.indexOf (q.act_point);
|
|
if (!p || i < 0 || p.length <= min) return ;
|
|
p.splice (i, 1);
|
|
q.act_point = p[Math.min (i, p.length - 1)];
|
|
q.Render ();
|
|
return 1;
|
|
};
|
|
|
|
q.cw = 500;
|
|
q.ch = 200;
|
|
var els = _make_canvas ( q, q.cw, q.ch );
|
|
q.canvas = els[0];
|
|
q.ctx = els[1];
|
|
|
|
|
|
var _fillstyle = '#d9d955';
|
|
q.Render = function () {
|
|
var ctx = q.ctx;
|
|
var cw = q.cw;
|
|
var ch = q.ch;
|
|
|
|
if (q.rbuff) {
|
|
q.app.engine.GetWave (q.rbuff, 500, 200, null, null, q.canvas, q.ctx);
|
|
if (q.waveDarken) {
|
|
ctx.fillStyle = 'rgba(0,0,0,' + q.waveDarken + ')';
|
|
ctx.fillRect (0, 0, cw, ch);
|
|
}
|
|
}
|
|
|
|
// ctx.clearRect (0, 0, q.cw, q.ch);
|
|
ctx.fillStyle = _fillstyle;
|
|
ctx.strokeStyle = '#FF0000';
|
|
|
|
if (!q.act) return ;
|
|
|
|
ctx.beginPath ();
|
|
ctx.moveTo ( 0, ch / 2 );
|
|
var last_y = ch / 2;
|
|
|
|
for (var o = 0; o < q.points[q.act.id].length; ++o)
|
|
{
|
|
var curr = q.points[q.act.id][ o ];
|
|
|
|
var center_x = curr.ax;
|
|
var center_y = curr.ay;
|
|
|
|
ctx.lineTo ( center_x, center_y );
|
|
last_y = center_y;
|
|
}
|
|
|
|
ctx.lineTo ( cw, last_y );
|
|
ctx.stroke ();
|
|
|
|
var radius = 6;
|
|
for (var o = 0; o < q.points[q.act.id].length; ++o)
|
|
{
|
|
var curr = q.points[q.act.id][ o ];
|
|
|
|
var center_x = curr.ax;
|
|
var center_y = curr.ay;
|
|
|
|
ctx.beginPath ();
|
|
ctx.arc (center_x, center_y, radius, 0, 2 * Math.PI, false);
|
|
|
|
if (curr === q.act_point) {
|
|
ctx.shadowBlur = 24;
|
|
|
|
if (curr._on)
|
|
ctx.fillStyle = '#fff';
|
|
else
|
|
ctx.fillStyle = '#686868';
|
|
|
|
ctx.stroke ();
|
|
ctx.fill ();
|
|
|
|
ctx.shadowBlur = 0;
|
|
ctx.fillStyle = _fillstyle;
|
|
}
|
|
else if (curr._hov) {
|
|
|
|
if (curr._on)
|
|
ctx.fillStyle = 'blue';
|
|
else
|
|
ctx.fillStyle = 'darkblue';
|
|
|
|
ctx.stroke ();
|
|
ctx.fill ();
|
|
|
|
ctx.fillStyle = _fillstyle;
|
|
}
|
|
else if (curr._on) {
|
|
ctx.fill ();
|
|
}
|
|
else {
|
|
ctx.fillStyle = '#555';
|
|
ctx.fill ();
|
|
ctx.fillStyle = _fillstyle;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
_make_controls ( q );
|
|
|
|
// -------
|
|
function _make_controls ( q ) {
|
|
var click_time = 0, seek_t = 0, no_seek = 0;
|
|
q.canvas.addEventListener ('click', function ( e ) {
|
|
if (!q.act) return;
|
|
var h = q.app.engine.FXPreviewHost;
|
|
if (seek_t) { clearTimeout (seek_t); seek_t = 0; }
|
|
if (e.timeStamp - click_time < 260)
|
|
{
|
|
var bounds = q.canvas.getBoundingClientRect ();
|
|
var cw = q.cw;
|
|
var ch = q.ch;
|
|
var posx = e.clientX - bounds.left;
|
|
var posy = e.clientY - bounds.top;
|
|
|
|
var rel_x = posx / cw;
|
|
var rel_y = posy / ch;
|
|
|
|
if (!q.points[q.act.id]) q.points[q.act.id] = [];
|
|
|
|
var duration;
|
|
var region = q.wv.regions.list[0];
|
|
if (region) {
|
|
duration = region.end - region.start;
|
|
} else {
|
|
duration = q.wv.getDuration();
|
|
}
|
|
|
|
q.points[q.act.id].push ({
|
|
// el:q.act.el,
|
|
id: ++_pid,
|
|
x: rel_x,
|
|
y: rel_y,
|
|
ax: rel_x * cw,
|
|
ay: rel_y * ch,
|
|
time: duration * rel_x,
|
|
val : ((1 - rel_y) * (q.act.max - q.act.min)) + q.act.min,
|
|
_on: true,
|
|
_hov: false,
|
|
});
|
|
|
|
q.points[q.act.id].sort( _compare );
|
|
q.act_point = q.points[q.act.id][q.points[q.act.id].length - 1];
|
|
|
|
//_process ( q, q.wv.backend.buffer );
|
|
|
|
q.Render ();
|
|
// ----
|
|
}
|
|
else if (!no_seek && preview_cb && h && (h.previewing || h.MTPreviewing))
|
|
{
|
|
var bounds = q.canvas.getBoundingClientRect ();
|
|
var sx = Math.max (0, Math.min (1, (e.clientX - bounds.left) / bounds.width));
|
|
seek_t = setTimeout (function () { seek_t = 0; preview_cb (sx); }, 260);
|
|
}
|
|
|
|
no_seek = 0;
|
|
click_time = e.timeStamp;
|
|
}, false);
|
|
|
|
var is_dragging = false;
|
|
var skip = 3;
|
|
q.canvas.addEventListener ('mousemove', function ( e ) {
|
|
if (!is_dragging || !q.act_point) return ;
|
|
|
|
var ex = 0;
|
|
var ey = 0;
|
|
|
|
if (e.touches) {
|
|
if (e.touches.length > 1) { return ; }
|
|
|
|
ex = e.touches[0].clientX;
|
|
ey = e.touches[0].clientY;
|
|
} else {
|
|
ex = e.clientX;
|
|
ey = e.clientY;
|
|
}
|
|
|
|
var bounds = q.canvas.getBoundingClientRect ();
|
|
var cw = q.cw;
|
|
var ch = q.ch;
|
|
|
|
var posx = ex - bounds.left;
|
|
var posy = ey - bounds.top;
|
|
|
|
var rel_x = posx / cw;
|
|
var rel_y = posy / ch;
|
|
|
|
q.act_point.ax = posx;
|
|
q.act_point.ay = posy;
|
|
|
|
q.act_point.x = rel_x;
|
|
q.act_point.y = rel_y;
|
|
|
|
var duration;
|
|
var region = q.wv.regions.list[0];
|
|
if (region) {
|
|
duration = region.end - region.start;
|
|
} else {
|
|
duration = q.wv.getDuration();
|
|
}
|
|
|
|
q.act_point.time = duration * rel_x;
|
|
q.act_point.val = ((1 - rel_y) * (q.act.max - q.act.min)) + q.act.min;
|
|
no_seek = 1;
|
|
|
|
q.Render ();
|
|
|
|
if (--skip === 0) {
|
|
skip = 4;
|
|
_process ( q, q.wv.backend.buffer );
|
|
}
|
|
});
|
|
|
|
q.canvas.addEventListener ('mousedown', function ( e ) {
|
|
is_dragging = false;
|
|
if (!q.act) return ;
|
|
|
|
var bounds = q.canvas.getBoundingClientRect ();
|
|
var cw = q.cw;
|
|
var ch = q.ch;
|
|
|
|
var posx = e.clientX - bounds.left;
|
|
var posy = e.clientY - bounds.top;
|
|
|
|
var dist_x = e.is_touch ? 20 : 10;
|
|
var dist_y = e.is_touch ? 20 : 9;
|
|
|
|
if (!q.points[q.act.id]) q.points[q.act.id] = [];
|
|
|
|
for (var o = 0; o < q.points[q.act.id].length; ++o)
|
|
{
|
|
var curr = q.points[q.act.id][ o ];
|
|
if ( Math.abs (curr.ax - posx) < dist_x && Math.abs (curr.ay - posy) < dist_y)
|
|
{
|
|
is_dragging = true;
|
|
no_seek = 1;
|
|
q.act_point = curr;
|
|
q.Render ();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!is_dragging)
|
|
{
|
|
q.act_point = null;
|
|
q.Render ();
|
|
}
|
|
});
|
|
|
|
q.canvas.addEventListener ('mouseup', function ( e ) {
|
|
is_dragging = false;
|
|
});
|
|
|
|
|
|
var act_el = null;
|
|
q.modal.el_body.addEventListener ('mouseover', function(e) {
|
|
if (!q.in_auto) return ;
|
|
if (e.target.tagName === 'INPUT') {
|
|
e.target.classList.add ('pk_aut');
|
|
}
|
|
});
|
|
q.modal.el_body.addEventListener ('mouseout', function(e) {
|
|
if (!q.in_auto) return ;
|
|
if (e.target.tagName === 'INPUT') {
|
|
e.target.classList.remove ('pk_aut');
|
|
}
|
|
});
|
|
q.modal.el_body.addEventListener ('click', function(e) {
|
|
if (!q.in_auto) return ;
|
|
if (e.target.classList.contains ('pk_aut'))
|
|
{
|
|
if (act_el) {
|
|
act_el.classList.remove ('pk_aut_act');
|
|
act_el = null;
|
|
}
|
|
|
|
e.target.classList.add ('pk_aut_act');
|
|
act_el = e.target;
|
|
|
|
if (!e.target.id) e.target.id = 'pk' + (++_aid);
|
|
|
|
q.act = {
|
|
id: e.target.id,
|
|
el: e.target,
|
|
type:e.target.range,
|
|
min:e.target.min/1,
|
|
max:e.target.max/1,
|
|
step:e.target.step/1
|
|
};
|
|
|
|
if (!q.points[q.act.id]) q.points[q.act.id] = [];
|
|
|
|
q.Render ();
|
|
}
|
|
|
|
// console.log( 'click ', e.target );
|
|
});
|
|
};
|
|
|
|
|
|
function _make_btn_auto ( q ) {
|
|
var btn_automate = d.createElement ('a');
|
|
btn_automate.className = 'pk_modal_a_bottom';
|
|
btn_automate.innerHTML = 'AUTOMATE';
|
|
|
|
var in_auto = false;
|
|
btn_automate.onclick = function () {
|
|
q.in_auto = !q.in_auto;
|
|
|
|
if (q.in_auto) {
|
|
btn_automate.classList.add ('pk_act');
|
|
} else {
|
|
btn_automate.classList.remove ('pk_act');
|
|
}
|
|
};
|
|
|
|
q.modal.el_body.appendChild( btn_automate );
|
|
|
|
return (btn_automate);
|
|
};
|
|
|
|
function _make_canvas ( q ) {
|
|
var cc = document.createElement ('canvas');
|
|
cc.width = 500; cc.height = 200;
|
|
cc.style.background = '#000';
|
|
var ctx = cc.getContext('2d');
|
|
|
|
q.modal.el_body.appendChild( cc );
|
|
|
|
var buff = q.wv.backend.buffer;
|
|
if (!buff) return ([cc, ctx]);
|
|
|
|
var img = new Image();
|
|
img.onload = function () {
|
|
ctx.drawImage (img, 0, 0);
|
|
if (q.waveDarken) {
|
|
ctx.fillStyle = 'rgba(0,0,0,' + q.waveDarken + ')';
|
|
ctx.fillRect (0, 0, q.cw, q.ch);
|
|
}
|
|
if (q.act) q.Render ();
|
|
};
|
|
|
|
var offset; var length;
|
|
var region = q.wv.regions.list[0];
|
|
if (region) {
|
|
offset = (region.start * buff.sampleRate) >> 0;
|
|
length = (region.end * buff.sampleRate) >> 0;
|
|
}
|
|
|
|
_process ( q, buff );
|
|
|
|
img.src = q.app.engine.GetWave (buff, 500, 200, offset, length);
|
|
|
|
return ([cc, ctx]);
|
|
};
|
|
|
|
function _compare ( a, b ) {
|
|
if (a.x > b.x) return 1;
|
|
return -1;
|
|
};
|
|
|
|
function _process ( q, buffer ) {
|
|
if (!buffer) return ;
|
|
|
|
var getOfflineAudioContext = function (channels, sampleRate, duration) {
|
|
return new (window.OfflineAudioContext ||
|
|
window.webkitOfflineAudioContext)(channels, duration, sampleRate);
|
|
};
|
|
|
|
var region = q.wv.regions.list[0];
|
|
var offs = 0;
|
|
var durr = buffer.duration;
|
|
if (region) {
|
|
offs = region.start;
|
|
durr = region.end - region.start;
|
|
}
|
|
|
|
var rate = buffer.sampleRate;
|
|
var from = Math.min (buffer.length, Math.max (0, (offs * rate) >> 0));
|
|
var to = Math.min (buffer.length, ((offs + durr) * rate) >> 0);
|
|
var len = Math.max (1, to - from);
|
|
durr = len / rate;
|
|
|
|
var audio_ctx = getOfflineAudioContext (
|
|
1, // orig_buffer.numberOfChannels,
|
|
8000,
|
|
Math.max (1, (durr * 8000) >> 0)
|
|
);
|
|
|
|
var newbuffer = audio_ctx.createBuffer (1, len, rate);
|
|
newbuffer.getChannelData ( 0 ).set (
|
|
buffer.getChannelData ( 0 ).subarray ( from, to )
|
|
);
|
|
|
|
var source = audio_ctx.createBufferSource ();
|
|
source.buffer = newbuffer;
|
|
|
|
//var fx = q.app.engine.GetFX ('Gain', q.GetValue ());
|
|
//console.log ( fx.filter ( audio_ctx, audio_ctx.destination, source ) );
|
|
|
|
source.connect (audio_ctx.destination);
|
|
source.start (0); //, offs, durr);
|
|
|
|
var offline_callback = function( rendered_buffer ) {
|
|
q.rbuff = rendered_buffer;
|
|
|
|
// var img = new Image();
|
|
// img.src = q.app.engine.GetWave (rendered_buffer, 500, 200);
|
|
|
|
q.Render ();
|
|
|
|
};
|
|
var offline_renderer = audio_ctx.startRendering();
|
|
if (offline_renderer)
|
|
offline_renderer.then( offline_callback ).catch(function() {});
|
|
else
|
|
audio_ctx.oncomplete = function ( e ) {
|
|
offline_callback ( e.renderedBuffer );
|
|
};
|
|
|
|
// ---------
|
|
};
|
|
};
|
|
|
|
PKAudioEditor._deps.FxAUT = FXAutomation;
|
|
|
|
})( window, document, PKAudioEditor );
|