Apps SDK(플러그인 개발)
SynapseRack 안에서 동작하는 나만의 도구를, HTML과 JavaScript로 만들 수 있는 구조입니다. 앱은 SynapseRack의 창으로 열리며, 거기에서 노드의 생성·연결·파라미터 조작이나 Layer의 조작, 영상의 출력(MediaOut / Spout / Syphon) 등을 수행할 수 있습니다.
빌드 절차는 없습니다. 폴더에 index.html 을 두고, 저장하면 알아서 다시 읽어들입니다.
그리고 앱의 템플릿에는 AI 에이전트용 API 레퍼런스가 그대로 동봉됩니다.
“폴더째로 AI에게 넘겨서 상담한다”는 것을 전제로 한 구성으로 되어 있습니다.
앱이란 무엇인가
하나의 앱=하나의 폴더입니다. 안에 다음 두 가지가 있으면 그것은 앱입니다.
| 파일 | 역할 |
|---|---|
synapse-app.json | 매니페스트. id / name / version / entry / apiVersion / capabilities / multiInstance |
index.html(= entry) | 입구. 여기에서 읽어들인 JS가 window.synapse 를 통해 SynapseRack을 조작합니다 |
entry 를 고쳐 쓰면 입구 파일의 이름은 바꿀 수 있습니다(기본은 index.html).
앱을 두는 위치
설치된 앱을 두는 위치는, Source Directory와 같은 뿌리 아래에 있는 Apps 폴더입니다.
<Source Directoryの親パス>/SainaWorks/SynapseRack/Apps/
기본적으로 Windows는 C:/SainaWorks/SynapseRack/Apps, macOS는 ~/SainaWorks/SynapseRack/Apps 가 됩니다
(상위 경로를 바꾸는 방법은 VJ 소재를 불러올 경로(Source Directory) 지정하기 와 같은 Settings.json 입니다).
이 폴더의 “바로 아래”만 봅니다.
Apps바로 아래의 폴더 중,synapse-app.json을 가진 것이 라이브러리에 실립니다. 시작할 때와 AppHub를 열었을 때 훑어보고, 나아가 폴더의 추가·삭제·이름 변경은 감시되고 있으므로, 앱 폴더를 던져 넣으면 그 자리에서 목록에 나옵니다(Ableton의 .ablx를 던져 넣는 감각입니다).
배포된 앱을 넣을 때도, 이 폴더에 폴더째로 복사하기만 하면 됩니다.
시작하는 법
Apps 메뉴
메뉴바의 Apps 에 다음 내용이 나열됩니다.
| 항목 | 동작 |
|---|---|
| App Hub… | AppHub 창을 엽니다 |
| (앱 이름의 목록) | 클릭으로 실행. 이미 동작 중인 앱은 앞에 ● 가 붙고, 클릭하면 창이 앞으로 나옵니다 |
| Open Apps Folder | 위의 Apps 폴더를 OS의 파일 탐색기로 엽니다 |
AppHub
앱의 관리는 전부 이 창에서 수행합니다. 상단의 툴바는 3개입니다.
| 버튼 | 동작 |
|---|---|
| New | 이름 입력란이 열립니다. Create 로 Apps 폴더 안에 새 앱을 생성. Elsewhere… 를 선택하면 둘 위치를 직접 지정할 수 있습니다 |
| Add | 기존 폴더를 앱으로 등록합니다(Apps 폴더 밖이어도 OK. dev 태그가 붙고, 다음 실행 이후에도 남습니다) |
| Folder | Apps 폴더를 OS의 파일 탐색기로 엽니다 |
이름 입력란 아래에는 “어디에 만들어지는지”가 항상 표시됩니다.
생성된 폴더 이름은 입력한 이름으로부터 만들어지며(사용할 수 없는 문자는 - 로 치환), 같은 이름이 있는 경우에는 날짜와 시각이 붙습니다.
각 앱의 행은, 맨 왼쪽의 상태 점(초록=실행 중/노랑=시작 중·리로드 중/빨강=실패/회색=정지)과 이름과 Open 버튼입니다.
⋯ 를 누르면 2차 조작이 열립니다.
| 항목 | 동작 |
|---|---|
| Stop / Reload | 정지/다시 읽기 |
| Console | 그 앱 전용의 콘솔 창을 엽니다 |
| Folder | 그 앱의 폴더를 OS의 파일 탐색기로 엽니다 |
| Info | 매니페스트의 내용(id / name / version / apiVersion 과 호스트 쪽의 버전 / capabilities)과, 실행 중이라면 소유 리소스의 내역·진단 정보 |
| Reveal | 그 앱이 내부에 가지고 있는 숨겨진 노드 그래프로 들어갑니다(빠져나오는 것은 노드 에디터의 Back) |
| Unlink | Add로 등록한 것에만 나옵니다. 폴더는 지워지지 않습니다. 목록에서 빼기만 합니다 |
| Hot Reload | 파일 변경으로 자동 리로드할지 여부(기본 ON. 인스턴스별 설정이며, Stop 하면 기본으로 돌아갑니다) |
새 앱의 내용물과, AI에게 상담하며 진행하는 방법
New로 만든 폴더에는 synapse-app.json 에 더해 다음 10개 파일이 복사됩니다.
| 파일 | 내용 |
|---|---|
index.html / main.js / style.css | 동작하는 템플릿. 웹 윈도에 색을 그려서 MediaOut으로 내보내고, Hue 컨트롤을 하나 자라게 하는 데까지 구현되어 있습니다 |
synapse-sdk.js | 편의 레이어(window.SynapseSDK). 쓰지 않아도 앱은 작성할 수 있습니다 |
synapse.d.ts | 전체 API의 타입 정의 |
SYNAPSE_API.md | API 레퍼런스 전문 |
NODE_CATALOG.md | 전체 노드의 id·포트·설정 가능 멤버의 목록 |
SHADER_EFFECTS.md | 셰이더 이펙트(Preview 기능)의 해설 |
SAMPLES.md | 공식 샘플 앱 모음으로의 안내 |
DESIGN.md | ”SynapseRack 안에 있는 것처럼 보이는” UI의 작법 |
이 동봉물 일체가, 그대로 AI 에이전트로의 입력입니다.
SYNAPSE_API.md는 서두에 “이 파일만 읽은 AI 어시스턴트가, 한 번에 동작하는index.html을 쓸 수 있도록 쓰여 있다”고 명기된, AI용 문서입니다. AppHub의 Folder 로 폴더를 열고, 그 폴더를 Claude Code / ChatGPT 등의 에이전트에게 넘겨서 “이 폴더의SYNAPSE_API.md와NODE_CATALOG.md를 읽고, ~하는 앱으로 만들어 줘”라고 부탁하는 것이 표준적인 진행 방식이 됩니다.
공식 샘플 앱(A/B 덱 믹서, 배경 루퍼, 컨트롤 서피스, MIDI 갤러리 등)은
SynapseRack-Apps-SDK 의 samples/ 에 있습니다.
폴더째로 복사하면 동작하는 형태이므로, 패턴을 흉내 내고 싶을 때는 이쪽을 읽히는 것이 빠릅니다.
API의 개관
모든 호출은 window.synapse.<그룹>.<메서드>() 이며, Promise를 반환합니다.
synapse-sdk.js 를 읽어들이면, 같은 것을 핸들 오브젝트를 통해 짧게 쓸 수 있습니다.
1. 연결해서 “준비 완료”를 알린다
const app = await SynapseSDK.connect(); // window.synapse を待って ping する
// ...ここでアプリが使うものを全部作る...
await app.ready(); // 作り終えたら1回だけ呼ぶ
app.ready() 는 필수입니다. 리로드 시, 지난번에 만든 리소스는 “아직 청구되지 않은” 상태로 살려 두고,
새로운 boot() 가 같은 id 로 다시 만들거나, ready() 가 오거나, 타임아웃될 때까지 기다렸다가 정리됩니다.
이것이 “편집해서 저장 → 다시 만들어지지만, 사용자가 연결한 배선은 끊기지 않는다”를 성립시키고 있습니다.
2. 노드를 만든다·값을 쓴다·연결한다
NODE_CATALOG.md 의 표에 실려 있는 id 를 그대로 type 에 넘깁니다.
// 作る(返り値の id がそのノードのmoduleId)
const lfo = await synapse.modules.create({ type: 'LFO' });
const blend = await synapse.modules.create({ type: 'BlendRT' });
// 設定可能メンバに値を書く(パスはカタログの settable 列の名前。ドット区切り不可)
await synapse.modules.set(blend.id, 'mixInput', 0.5);
// つなぐ(位置引数。ポート名はカタログの in/out の id。省略時は 'Out' / 'In')
await synapse.modules.connect(lfo.id, 'Value', blend.id, 'Mix');
같은 카탈로그는 실행 시에도 조회할 수 있습니다: const types = await synapse.modules.types();
modules.create는 “같은 id로 다시 호출하면 같은 것”이 되지는 않습니다. 두 번 호출하면 노드가 두 개 만들어집니다. 멱등하게 하고 싶다면 반환된moduleId를 직접 기억해 두거나, 후술하는render/web/output/layers쪽(id를 넘기면 get-or-create)을 사용해 주세요.
3. Layer를 다룬다/Layer의 그림을 텍스처로 꺼낸다
const selected = await synapse.layers.selected();
await synapse.layers.setOpacity(selected.id, 0.5);
// そのLayerの出力を textureId として取り出す(id を渡すのでリロードしても同じもの)
const deckA = await synapse.layers.createTextureSource(selected.id, { id: 'deck-a' });
textureId 는 영상의 공통 통화입니다. Layer의 출력도, 웹 윈도의 렌더링 결과도, 믹서의 결과도 같은 문자열로 표현되며,
믹서의 입력에도 output.publish 에도, 앱 내 미리보기에도 그대로 넘길 수 있습니다.
4. 자신의 그림을 SynapseRack의 영상 소스로 내보낸다
const canvas = await app.webWindow('canvas', {
title: 'Canvas',
html: '<canvas id="c"></canvas><script>/* requestAnimationFrame で描く */</script>',
width: 960, height: 540
});
await app.publishMedia('main-out', { source: canvas, name: 'My Output' });
publishMedia(= output.publish)는 MediaOut 노드를 세웁니다.
안정적인 id 를 넘겨 두면, 앱을 리로드해도, 프로젝트를 다시 열어도 같은 노드가 재사용되고,
사용자가 거기에서 연결한 배선도 다시 이어집니다.
5. 연속적인 변화는 호스트 쪽에 맡긴다
const mixer = await app.mixer('mix', { type: 'crossfade', inputs: [deckA, canvas], value: 0.5 });
const fader = await app.control('fade', { label: 'Fade', min: 0, max: 1, value: 0.5, midi: true });
fader.onChange((v) => mixer.value.set(v));
await mixer.value.lfo({ rate: '1', shape: 'sine', min: 0, max: 1 }); // 1小節で1周
await mixer.value.follow({ source: 'audio.bass', min: 0, max: 1, smooth: 0.5 });
rate 는 마디 단위의 음악적 분할('1/4' '1/2' '1' '2' '4'. { hz: n } 으로 프리런),
shape 는 sine / triangle / saw / square, follow 의 source 는
audio.level / audio.bass / audio.mid / audio.high 입니다.
controls.register(SDK에서는 app.control)에 midi: true 를 붙이고, 대응하는 DOM 요소를
data-synapse-control="<id>" 나 anchor 로 연결하면, SynapseRack의 MIDI 학습 모드에서,
당신의 페이지 위의 UI가 그대로 학습 대상이 됩니다. 전용 “MIDI 대기” 버튼을 직접 만들 필요는 없습니다.
노드의 id와 포트 이름을 조사한다
modules.create / modules.connect 는 정확한 id와 포트 id를 요구합니다. 조사하는 방법은 4가지입니다.
| 수단 | 쓰임새 |
|---|---|
앱 폴더의 NODE_CATALOG.md | 오프라인에서 완결. AI에게 읽히는 것을 전제로 한 목록(id / 타이틀 / 입력 / 출력 / settable) |
await synapse.modules.types() | 실행 시에 같은 내용을 취득. 손에 있는 버전의 실물과 반드시 일치합니다 |
노드 레퍼런스 (/ko/docs/nodes/) | 사람이 읽는 용도. 포트 표와 해설 |
https://synapserack.com/mcp / https://synapserack.com/llms-full.txt | AI 에이전트용. MCP 서버로 연결하거나, 전문을 한꺼번에 읽히기 |
MCP 서버에는 search_docs / get_doc / list_nodes / get_node 가 있어서,
문서와 노드 레퍼런스를 그대로 조회할 수 있습니다.
실행·리로드·디버그
핫 리로드
.html / .js / .css / .json 을 저장하기만 하면, 실행 중인 앱이 약 1초 만에 자동 리로드됩니다(node_modules 와 .git 는 대상 외). VS Code든 AI 코딩 도구든, 저장하는 방식을 신경 쓸 필요는 없습니다.
멈추고 싶을 때는 AppHub의 Hot Reload 를 OFF로 합니다(그 인스턴스가 살아 있는 동안만 유효).
콘솔
앱의 console.log / 에러, 브리지의 로그, 핫 리로드의 알림은 전부 하나의 콘솔에 모입니다. 보는 곳은 2군데입니다.
- 앱 창 하단의 “Console” 버튼 — 그 자리에서 열고 닫을 수 있습니다
- AppHub의
⋯→ Console — 독립된 창(앱을 정지해도 “App stopped.” 표시가 되어 남습니다)
어느 쪽에도 Copy 가 있어서, 표시 중인 로그를 그대로 클립보드에 넣습니다(AI에게 붙여 넣어 넘기기 위한 동선입니다). Clear 는 표시를 지울 뿐이고, 내부의 버퍼는 남습니다. 앱 창 쪽에는 추가로 Debug 토글이 있어서, ON으로 하면 브리지의 통신 자체도 로그에 흐릅니다.
에러는 구조화되어 있습니다. 실패한 호출은 Error 를 던지고, err.synapse 에
{ code, message, method, hint } 가 들어갑니다(code 는 unknown_method / invalid_params / not_found / internal).
원인을 물을 때는 이 내용물째로 넘기는 것이 가장 빠릅니다.
MCP 서버(개발 도구)
AppHub의 맨 아래의 Developer 를 열면, MCP Server 의 토글과 포트 입력란이 있습니다.
ON으로 하면 http://127.0.0.1:8765/(기본 포트)에서 MCP 서버가 서고,
AI 코딩 어시스턴트가 동작하고 있는 SynapseRack에 대해 직접 다음 조작을 할 수 있게 됩니다.
| 도구 | 내용 |
|---|---|
list_apps | 실행 중인 앱 인스턴스 목록 |
invoke | 실행 중인 앱에 대해, 문서에 있는 임의의 메서드를 호출 |
read_console | 그 앱의 콘솔을 읽는다(severity로 좁힐 수 있습니다) |
reload_app | 리로드 |
graph_state / graph_node_types / graph_create_node / graph_delete_node / graph_connect / graph_disconnect | 사용자에게 보이는 노드 그래프를 읽는다·편집한다 |
“편집 → reload_app → read_console 로 에러 확인 → invoke 로 결과를 검증”이라는 루프를 돌릴 수 있으므로,
AI에게 쓰게 한 코드를 육안으로 계속 확인할 필요가 없어집니다.
보안상, 기본은 OFF입니다. 매번 시작할 때마다 OFF로 돌아갑니다. 루프백(127.0.0.1)에만 바인드되지만, 인증은 없습니다. 포트 번호만은 다음번을 위해 저장되지만, 유효/무효는 저장하지 않습니다(여는 것은 매번의 명시적인 선택이어야 한다는 설계입니다). 다 쓰고 나면 OFF로 하고, 포트 포워드 등으로 밖으로 내보내지 말아 주세요.
저장되는 방식
앱은 프로젝트(.synapse)와 함께 저장됩니다. 프로젝트를 다시 열면,
SynapseRack이 폴더에서 앱을 자동으로 다시 시작합니다(Max for Live의 디바이스가 세트와 함께 저장되는 것과 같은 감각입니다).
저장에 얽힌 구조는 3가지입니다.
| 구조 | 무엇이 남는가 |
|---|---|
app.setState(obj) / app.onRestore(fn) | 앱의 UI 상태. 프로젝트와 함께 저장되고, app.ready() 직후에 돌아옵니다 |
output.publish 등의 안정적인 id | 출력 노드의 동일성. 사용자가 거기에서 연결한 배선째로 복원됩니다 |
synapse.storage(get/set/delete/list) | 프로젝트에 의존하지 않는 앱 단위의 저장 영역. 프리셋이나 캐시, 마지막으로 사용한 설정용 |
제약·주의점(구현으로부터)
매 프레임 JS에서 값을 쓰지 말아 주세요.
requestAnimationFrame으로setValue를 돌리는 것은, 이 구조가 피하려고 하는 왕복 비용 그 자체입니다. 연속 변조는 호스트 쪽의 바인딩(.value.lfo()/.value.follow()/.value.bindMidi())에 맡기고, JS에서는 슬라이더나 버튼의 조작 같은 이산적인 이벤트만 보내 주세요.
App 의 저레이어 노드는, 노드 추가 UI에 기본적으로 나오지 않습니다.*
AppMediaPlayer/AppTextureSource/AppOffscreenLayer/AppStackMixer/AppTextRender/AppFxChain의 6종은, 우클릭의 노드 추가 메뉴와 사이드 패널에서 숨겨져 있습니다(비개발자에게는 목록이 번잡해질 뿐이기 때문에). 표시하고 싶은 경우에는 글로벌 설정 → Library → App SDK Modules → Show App SDK modules 를 ON으로 해 주세요. 이 설정은 프로젝트에는 저장되지 않습니다. 앱 쪽의modules.create/render.*/modules.types()는, 이 설정에 일절 영향받지 않습니다 (설정이 OFF여도 앱에서는 만들 수 있습니다).
창끼리의 직접적인 주고받기는 할 수 없습니다. 메인 창과
web.createWindow로 연 자식 창은, 각각 독립된 브리지를 가집니다. 가져온 에셋의 id나 등록한 컨트롤은 창을 넘어 참조할 수 없습니다. 공유하고 싶은 값은synapse.storage를 경유해 주세요(textureId와 키가 붙은 리소스는 인스턴스 전체에서 공통이므로, 그대로 넘길 수 있습니다).
controls.register만은 엄밀한 get-or-create가 아닙니다. 같은id로 다시 등록하면, 이전 컨트롤의 메타데이터를 덮어씁니다(boot()의 재실행에서는 문제가 되지 않습니다).
믹서는
crossfade(입력 2개)뿐입니다.render.createSession은 호환을 위한 빈 구현이므로, 새 코드에서는 호출하지 말아 주세요.
오프스크린 플레이어는 기본적으로 음소거입니다. 소리를 내는 것은 명시적인 옵트인이고, 애초에 음성을 다룰 수 있는 것은 Unity 표준의 동영상 재생 백엔드뿐입니다.
SDK는 필수가 아닙니다.
synapse-sdk.js는 정형문을 줄일 뿐인 얇은 층이고, 할 수 있는 것은 전부window.synapse에서 직접 호출할 수 있습니다.
복수 실행(multiInstance)
기본적으로 앱은 하나만 동작합니다(두 번 실행해도 창이 앞으로 나올 뿐).
synapse-app.json 에 "multiInstance": true 를 더하면, 같은 앱을 몇 개든 나란히 동작시킬 수 있게 되고,
AppHub의 행이 “New Slot”+슬롯별 행으로 바뀝니다.
슬롯별로 독립되는 것은, 내부 그래프·공개한 출력(표시 이름에 (2) 가 붙습니다)·MIDI의 스코프·
창·app.setState / app.onRestore 입니다. synapse.storage 만은 전체 슬롯에서 공유됩니다.
자신이 몇 번째인지는 app.ping() 의 slot 으로 알 수 있습니다(Layer마다 하나 실행하는 식의 사용법을 위한 것입니다).
관련
- 노드 레퍼런스 —
modules.create에 넘기는 id와 포트 이름의 목록 - MIDI 매핑 — 앱의 컨트롤도 이 학습 모드의 대상이 됩니다
- VJ 소재를 불러올 경로(Source Directory) 지정하기 —
Apps폴더의 위치를 정하고 있는 설정












