plugin v1.0.0

This commit is contained in:
JotaChina committed 2025-11-20 20:54:29 -03:00
1 parent 12c5b55bce
commit 44fce48bb5
12 files changed
+1316 -635

No files matched your search

+248
View File
@@ -285,6 +285,7 @@
<div class="tool-icons">
<i class="fa-solid fa-table-cells"></i
><i class="fa-solid fa-bars-staggered"></i>
<i class="fa-solid fa-music" id="open-piano-roll-btn" title="Abrir Piano Roll"></i>
<i
class="fa-solid fa-wave-square"
id="bounce-pattern-btn"
@@ -317,6 +318,39 @@
</div>
<div id="track-container"></div>
</div>
<div class="piano-roll-editor" id="piano-roll-editor" style="display: none;">
<div class="editor-header">
<span>Piano Roll - <span id="piano-roll-instrument-name">Instrumento 1</span></span>
<div class="window-controls">
<i class="fa-solid fa-xmark" id="close-piano-roll-btn"></i>
</div>
</div>
<div class="piano-roll-toolbar">
<div class="playback-controls">
<i class="fa-solid fa-pencil active" title="Draw Tool"></i>
<i class="fa-solid fa-eraser" title="Erase Tool"></i>
</div>
<div class="snap-controls">
<label>Snap:</label>
<select>
<option>1/4</option>
<option>1/8</option>
<option selected>1/16</option>
</select>
</div>
</div>
<div class="piano-roll-workspace">
<div class="piano-keys-container" id="piano-keys-container">
<canvas id="piano-keys-canvas"></canvas>
</div>
<div class="piano-grid-container" id="piano-grid-container">
<canvas id="piano-grid-canvas"></canvas>
</div>
</div>
</div>
<div class="audio-editor">
<div class="editor-header">
<span>Editor de Amostras de Áudio</span>
@@ -486,6 +520,220 @@
document.body.classList.add("embed-mode");
}
</script>
<script type="module">
// Importamos o estado global para ler/salvar notas reais
import { appState } from "./assets/js/creations/state.js"; // Ajuste o caminho se necessário
import { sendAction } from "./assets/js/creations/socket.js";
import { renderAll } from "./assets/js/creations/ui.js";
import * as Tone from "https://esm.sh/tone";
// Variáveis de Controle
let currentTrackId = null;
const CONSTANTS = {
NOTE_HEIGHT: 20,
KEY_WIDTH: 60,
BEAT_WIDTH: 40,
TOTAL_KEYS: 84, // 7 oitavas
START_NOTE: 24, // C1
TICKS_PER_PIXEL: 0 // Calculado dinamicamente
};
const pianoRollEditor = document.getElementById('piano-roll-editor');
const keysCanvas = document.getElementById('piano-keys-canvas');
const gridCanvas = document.getElementById('piano-grid-canvas');
const keysCtx = keysCanvas.getContext('2d');
const gridCtx = gridCanvas.getContext('2d');
const gridContainer = document.getElementById('piano-grid-container');
const keysContainer = document.getElementById('piano-keys-container');
// Sintetizador de preview
const previewSynth = new Tone.PolySynth(Tone.Synth).toDestination();
previewSynth.volume.value = -10;
// --- FUNÇÃO GLOBAL PARA ABRIR O EDITOR ---
window.openPianoRoll = function(trackId) {
const track = appState.pattern.tracks.find(t => t.id === trackId);
if (!track) return;
currentTrackId = trackId;
document.getElementById('piano-roll-instrument-name').textContent = track.name;
// Mostra o editor
pianoRollEditor.style.display = 'flex';
// Redimensiona e desenha
resizeCanvas();
// Centraliza o scroll vertical (C5)
const middleY = (CONSTANTS.TOTAL_KEYS / 2) * CONSTANTS.NOTE_HEIGHT;
gridContainer.scrollTop = middleY - 200;
};
// --- DESENHO E LÓGICA ---
function resizeCanvas() {
const totalHeight = CONSTANTS.TOTAL_KEYS * CONSTANTS.NOTE_HEIGHT;
// 64 compassos * 192 ticks / (ticks por beat) * largura... simplificando:
// Vamos fixar uma largura grande por enquanto
const totalWidth = 3000;
keysCanvas.width = CONSTANTS.KEY_WIDTH;
keysCanvas.height = totalHeight;
gridCanvas.width = totalWidth;
gridCanvas.height = totalHeight;
// Importante: Sincronizar a conversão de Pixel <-> Tick
// 1 Beat = 48 ticks (em 16th) ou 192 ticks por bar?
// No seu file.js: ticksPerStep = 12 (1/16).
// Então BEAT_WIDTH (40px) = 4 steps = 48 ticks.
CONSTANTS.TICKS_PER_PIXEL = 48 / CONSTANTS.BEAT_WIDTH;
drawKeys();
drawGrid();
drawNotes();
}
function drawKeys() {
keysCtx.clearRect(0, 0, keysCanvas.width, keysCanvas.height);
for (let i = 0; i < CONSTANTS.TOTAL_KEYS; i++) {
const midiNote = CONSTANTS.START_NOTE + (CONSTANTS.TOTAL_KEYS - 1 - i);
const y = i * CONSTANTS.NOTE_HEIGHT;
const isBlack = isBlackKey(midiNote);
keysCtx.fillStyle = isBlack ? '#333' : '#eee';
keysCtx.fillRect(0, y, CONSTANTS.KEY_WIDTH, CONSTANTS.NOTE_HEIGHT);
keysCtx.strokeStyle = '#555';
keysCtx.strokeRect(0, y, CONSTANTS.KEY_WIDTH, CONSTANTS.NOTE_HEIGHT);
if (midiNote % 12 === 0) {
keysCtx.fillStyle = isBlack ? '#fff' : '#333';
keysCtx.font = '10px Arial';
keysCtx.fillText('C' + (Math.floor(midiNote / 12) - 1), 35, y + 14);
}
}
}
function drawGrid() {
gridCtx.fillStyle = '#292929';
gridCtx.fillRect(0, 0, gridCanvas.width, gridCanvas.height);
// Linhas das notas
for (let i = 0; i < CONSTANTS.TOTAL_KEYS; i++) {
const midiNote = CONSTANTS.START_NOTE + (CONSTANTS.TOTAL_KEYS - 1 - i);
const y = i * CONSTANTS.NOTE_HEIGHT;
const isBlack = isBlackKey(midiNote);
gridCtx.fillStyle = isBlack ? '#222' : '#2a2a2a';
gridCtx.fillRect(0, y, gridCanvas.width, CONSTANTS.NOTE_HEIGHT);
gridCtx.strokeStyle = '#333';
gridCtx.beginPath(); gridCtx.moveTo(0, y); gridCtx.lineTo(gridCanvas.width, y); gridCtx.stroke();
}
// Linhas verticais (Tempo)
for (let x = 0; x < gridCanvas.width; x += CONSTANTS.BEAT_WIDTH) {
gridCtx.beginPath();
gridCtx.strokeStyle = (x % (CONSTANTS.BEAT_WIDTH * 4) === 0) ? '#666' : '#383838';
gridCtx.moveTo(x, 0); gridCtx.lineTo(x, gridCanvas.height); gridCtx.stroke();
}
}
function drawNotes() {
if (!currentTrackId) return;
const track = appState.pattern.tracks.find(t => t.id === currentTrackId);
if (!track) return;
const pattern = track.patterns[track.activePatternIndex];
const notes = pattern.notes || [];
gridCtx.fillStyle = '#ffbb00'; // Cor Laranja
gridCtx.strokeStyle = '#000';
notes.forEach(note => {
// Converter MIDI para Y (Invertido)
const keyIndex = (CONSTANTS.TOTAL_KEYS - 1) - (note.key - CONSTANTS.START_NOTE);
const y = keyIndex * CONSTANTS.NOTE_HEIGHT;
// Converter Ticks (pos) para X
// Se 48 ticks = BEAT_WIDTH (40px) -> pos / 1.2
const x = note.pos / CONSTANTS.TICKS_PER_PIXEL;
const width = note.len / CONSTANTS.TICKS_PER_PIXEL;
gridCtx.fillRect(x + 1, y + 1, width - 2, CONSTANTS.NOTE_HEIGHT - 2);
gridCtx.strokeRect(x + 1, y + 1, width - 2, CONSTANTS.NOTE_HEIGHT - 2);
});
}
// --- INTERAÇÃO: CRIAR NOTAS ---
gridCanvas.addEventListener('mousedown', (e) => {
if (!currentTrackId) return;
const rect = gridCanvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 1. Calcular Nota (Y)
const keyIndex = Math.floor(y / CONSTANTS.NOTE_HEIGHT);
const midiNote = CONSTANTS.START_NOTE + (CONSTANTS.TOTAL_KEYS - 1 - keyIndex);
// 2. Calcular Posição (X) e Snap
// Snap padrão 1/16 = 12 ticks
const snapTicks = 12;
const rawTicks = x * CONSTANTS.TICKS_PER_PIXEL;
const quantizedPos = Math.floor(rawTicks / snapTicks) * snapTicks;
// 3. Criar Objeto Nota
const newNote = {
pos: quantizedPos,
len: 48, // Duração padrão (1 beat/seminima) - ajuste conforme desejar
key: midiNote,
vol: 100,
pan: 0
};
// 4. Atualizar Estado e Redesenhar
const track = appState.pattern.tracks.find(t => t.id === currentTrackId);
if (track) {
const pattern = track.patterns[track.activePatternIndex];
// Se não existir array de notas, cria
if (!pattern.notes) pattern.notes = [];
pattern.notes.push(newNote);
// Toca som
const noteName = Tone.Frequency(midiNote, "midi").toNote();
previewSynth.triggerAttackRelease(noteName, "8n");
// Redesenha Piano Roll
drawNotes();
// IMPORTANTE: Atualizar a UI da lista de trilhas (para aparecer a miniatura)
// E enviar via Socket para colaboradores
renderAll();
sendAction({
type: "UPDATE_PATTERN_NOTES",
trackId: currentTrackId,
patternIndex: track.activePatternIndex,
notes: pattern.notes
});
}
});
// Scroll Sync
gridContainer.addEventListener('scroll', () => {
keysContainer.scrollTop = gridContainer.scrollTop;
});
// Fechar
document.getElementById('close-piano-roll-btn').addEventListener('click', () => {
pianoRollEditor.style.display = 'none';
currentTrackId = null;
});
function isBlackKey(note) {
const n = note % 12;
return (n === 1 || n === 3 || n === 6 || n === 8 || n === 10);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>