versão 2.0.2 - É possível gravar áudio do dispositivo
Deploy / Deploy (push) Successful in 1m3s
Details
Deploy / Deploy (push) Successful in 1m3s
Details
This commit is contained in:
parent
6903839643
commit
d9166f6967
|
|
@ -155,6 +155,36 @@ body.sidebar-hidden .sample-browser {
|
||||||
.editor-header { background-color: var(--bg-toolbar); padding: 4px 10px; font-size: .8rem; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-color); flex-shrink: 0; }
|
.editor-header { background-color: var(--bg-toolbar); padding: 4px 10px; font-size: .8rem; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-color); flex-shrink: 0; }
|
||||||
.editor-toolbar { background-color: var(--bg-toolbar); padding: 5px 10px; border-bottom: 2px solid var(--border-color); flex-shrink: 0; display: flex; align-items: center; gap: 15px; }
|
.editor-toolbar { background-color: var(--bg-toolbar); padding: 5px 10px; border-bottom: 2px solid var(--border-color); flex-shrink: 0; display: flex; align-items: center; gap: 15px; }
|
||||||
|
|
||||||
|
/* Estilo para o botão de gravação */
|
||||||
|
#record-btn.active.recording {
|
||||||
|
color: var(--accent-red);
|
||||||
|
animation: pulse-red 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animação de "pulsar" */
|
||||||
|
@keyframes pulse-red {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
text-shadow: 0 0 3px var(--accent-red);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
text-shadow: 0 0 10px var(--accent-red);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
text-shadow: 0 0 3px var(--accent-red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#live-waveform-canvas {
|
||||||
|
width: 100%; /* Faz ocupar a largura do painel .track-info */
|
||||||
|
height: 40px; /* Altura que definimos em JS */
|
||||||
|
background-color: var(--background-dark);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 8px; /* Um pequeno espaçamento */
|
||||||
|
border: 1px solid var(--border-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================== */
|
/* =============================================== */
|
||||||
/* EDITOR DE BASES (BEAT EDITOR / STEP SEQUENCER)
|
/* EDITOR DE BASES (BEAT EDITOR / STEP SEQUENCER)
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,15 @@ export async function loadAudioForClip(clip) {
|
||||||
return clip;
|
return clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addAudioClipToTimeline(samplePath, trackId = 1, startTime = 0) {
|
export function addAudioClipToTimeline(samplePath, trackId = 1, startTime = 0, clipName = null) {
|
||||||
const newClip = {
|
const newClip = {
|
||||||
id: Date.now() + Math.random(),
|
id: Date.now() + Math.random(),
|
||||||
trackId: trackId,
|
trackId: trackId,
|
||||||
sourcePath: samplePath,
|
sourcePath: samplePath,
|
||||||
name: samplePath.split('/').pop(),
|
|
||||||
|
// --- MODIFICAÇÃO AQUI ---
|
||||||
|
// Usa o nome fornecido, ou extrai do caminho se não for fornecido
|
||||||
|
name: clipName || samplePath.split('/').pop(),
|
||||||
|
|
||||||
startTimeInSeconds: startTime,
|
startTimeInSeconds: startTime,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { appState, resetProjectState } from "./state.js";
|
import { appState, resetProjectState } from "./state.js";
|
||||||
import { addTrackToState, removeLastTrackFromState } from "./pattern/pattern_state.js";
|
import { addTrackToState, removeLastTrackFromState } from "./pattern/pattern_state.js";
|
||||||
// --- CORREÇÃO AQUI ---
|
// --- CORREÇÃO AQUI ---
|
||||||
|
import { toggleRecording } from "./recording.js";
|
||||||
import { addAudioTrackLane, removeAudioClip } from "./audio/audio_state.js";
|
import { addAudioTrackLane, removeAudioClip } from "./audio/audio_state.js";
|
||||||
import { updateTransportLoop } from "./audio/audio_audio.js";
|
import { updateTransportLoop } from "./audio/audio_audio.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -56,6 +57,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
// --- NOVOS BOTÕES ---
|
// --- NOVOS BOTÕES ---
|
||||||
const resizeToolTrimBtn = document.getElementById("resize-tool-trim");
|
const resizeToolTrimBtn = document.getElementById("resize-tool-trim");
|
||||||
const resizeToolStretchBtn = document.getElementById("resize-tool-stretch");
|
const resizeToolStretchBtn = document.getElementById("resize-tool-stretch");
|
||||||
|
const recordBtn = document.getElementById("record-btn");
|
||||||
|
|
||||||
const mmpFileInput = document.getElementById("mmp-file-input");
|
const mmpFileInput = document.getElementById("mmp-file-input");
|
||||||
const sampleFileInput = document.getElementById("sample-file-input");
|
const sampleFileInput = document.getElementById("sample-file-input");
|
||||||
|
|
@ -69,6 +71,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
// --- LISTENERS ADICIONADOS (COM LÓGICA DE CONTROLLER) ---
|
// --- LISTENERS ADICIONADOS (COM LÓGICA DE CONTROLLER) ---
|
||||||
|
|
||||||
|
// --- NOVO LISTENER PARA O BOTÃO DE GRAVAR ---
|
||||||
|
if (recordBtn) {
|
||||||
|
recordBtn.addEventListener("click", () => {
|
||||||
|
// Garante que o contexto de áudio foi iniciado por um gesto do usuário
|
||||||
|
initializeAudioContext();
|
||||||
|
toggleRecording();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Listener para o botão "Excluir Clipe" no menu de contexto
|
// Listener para o botão "Excluir Clipe" no menu de contexto
|
||||||
const deleteClipBtn = document.getElementById('delete-clip');
|
const deleteClipBtn = document.getElementById('delete-clip');
|
||||||
if (deleteClipBtn) {
|
if (deleteClipBtn) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
// js/recording.js
|
||||||
|
import { appState } from './state.js';
|
||||||
|
import { addAudioTrackLane, addAudioClipToTimeline } from './audio/audio_state.js';
|
||||||
|
import { renderAudioEditor } from './audio/audio_ui.js';
|
||||||
|
|
||||||
|
let userMedia = null;
|
||||||
|
let recorder = null;
|
||||||
|
let isRecordingInitialized = false;
|
||||||
|
|
||||||
|
// --- NOVO: Variáveis para análise e desenho em tempo real ---
|
||||||
|
let analyser = null;
|
||||||
|
let liveWaveformCanvas = null;
|
||||||
|
let liveWaveformCtx = null;
|
||||||
|
let animationFrameId = null;
|
||||||
|
let currentRecordingTrackId = null;
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pede permissão e prepara o microfone (Tone.UserMedia) e o gravador (Tone.Recorder).
|
||||||
|
*/
|
||||||
|
async function _initializeRecorder() {
|
||||||
|
if (isRecordingInitialized) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
userMedia = new Tone.UserMedia();
|
||||||
|
await userMedia.open();
|
||||||
|
|
||||||
|
recorder = new Tone.Recorder();
|
||||||
|
|
||||||
|
// --- NOVO: Inicializa o Analisador ---
|
||||||
|
// 'waveform' nos dá os dados de amplitude ao longo do tempo. 1024 é um bom tamanho de buffer.
|
||||||
|
analyser = new Tone.Analyser('waveform', 1024);
|
||||||
|
|
||||||
|
// 3. Conecta o microfone a *ambos*: o gravador E o analisador
|
||||||
|
userMedia.connect(recorder);
|
||||||
|
userMedia.connect(analyser); // "Fan-out" para o analisador
|
||||||
|
|
||||||
|
isRecordingInitialized = true;
|
||||||
|
console.log("Sistema de gravação e análise inicializado.");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao inicializar a gravação (permissão negada?):", err);
|
||||||
|
alert("Erro ao acessar o microfone. Verifique as permissões do navegador.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- NOVO: Função para desenhar a onda ao vivo ---
|
||||||
|
function _drawLiveWaveform() {
|
||||||
|
// Continua o loop enquanto estivermos gravando
|
||||||
|
if (!appState.global.isRecording || !analyser || !liveWaveformCtx) { //
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pede o próximo quadro de animação
|
||||||
|
animationFrameId = requestAnimationFrame(_drawLiveWaveform);
|
||||||
|
|
||||||
|
// Pega os dados da forma de onda do analisador
|
||||||
|
const waveformData = analyser.getValue(); // Retorna um Float32Array
|
||||||
|
|
||||||
|
const canvas = liveWaveformCanvas;
|
||||||
|
const ctx = liveWaveformCtx;
|
||||||
|
const width = canvas.width;
|
||||||
|
const height = canvas.height;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
ctx.strokeStyle = 'var(--accent-red)'; // Cor vermelha para "gravando"
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
const sliceWidth = width * 1.0 / waveformData.length;
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < waveformData.length; i++) {
|
||||||
|
const v = waveformData[i]; // Valor entre -1.0 e 1.0
|
||||||
|
const y = (v * 0.5 + 0.5) * height; // Mapeia para 0..height
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
x += sliceWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.lineTo(width, height / 2); // Linha final até o meio
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicia a gravação de fato.
|
||||||
|
*/
|
||||||
|
async function _startRecording() {
|
||||||
|
if (appState.global.isAudioEditorPlaying) {
|
||||||
|
console.warn("A gravação foi iniciada, mas o playback do editor continua.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = await _initializeRecorder();
|
||||||
|
if (!success) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// --- MUDANÇA: Lógica movida para cá ---
|
||||||
|
// 1. Cria a pista de áudio *antes* de gravar
|
||||||
|
addAudioTrackLane(); //
|
||||||
|
|
||||||
|
// 2. Pega o ID da pista recém-criada
|
||||||
|
const newTrack = appState.audio.tracks[appState.audio.tracks.length - 1]; //
|
||||||
|
if (!newTrack) {
|
||||||
|
console.error("Falha ao criar a nova pista de áudio no estado.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentRecordingTrackId = newTrack.id;
|
||||||
|
|
||||||
|
// 3. Renderiza o editor para a nova pista aparecer
|
||||||
|
renderAudioEditor(); //
|
||||||
|
|
||||||
|
// 4. Encontra o painel de info da nova pista e injeta o canvas
|
||||||
|
const trackInfoPanel = document.querySelector(`.audio-track-lane[data-track-id="${currentRecordingTrackId}"] .track-info`); //
|
||||||
|
if (!trackInfoPanel) {
|
||||||
|
console.error("Não foi possível encontrar o painel da nova pista para o canvas.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
liveWaveformCanvas = document.createElement('canvas');
|
||||||
|
liveWaveformCanvas.id = 'live-waveform-canvas';
|
||||||
|
// Ajuste a largura e altura como preferir para caber no painel
|
||||||
|
liveWaveformCanvas.width = 100;
|
||||||
|
liveWaveformCanvas.height = 40;
|
||||||
|
trackInfoPanel.appendChild(liveWaveformCanvas);
|
||||||
|
liveWaveformCtx = liveWaveformCanvas.getContext('2d');
|
||||||
|
// --- Fim da lógica movida ---
|
||||||
|
|
||||||
|
// 5. Inicia a gravação (Tone.Recorder)
|
||||||
|
await recorder.start();
|
||||||
|
appState.global.isRecording = true; //
|
||||||
|
console.log("Gravação iniciada...");
|
||||||
|
_updateRecordButtonUI(true); //
|
||||||
|
|
||||||
|
// 6. Inicia o loop de desenho
|
||||||
|
_drawLiveWaveform();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao iniciar a gravação:", err);
|
||||||
|
appState.global.isRecording = false; //
|
||||||
|
_updateRecordButtonUI(false); //
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Para a gravação e processa o áudio resultante.
|
||||||
|
*/
|
||||||
|
async function _stopRecording() {
|
||||||
|
if (!recorder || !userMedia) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const recordingBlob = await recorder.stop();
|
||||||
|
|
||||||
|
// --- MUDANÇA: Limpa tudo ---
|
||||||
|
userMedia.close();
|
||||||
|
if(analyser) analyser.dispose(); // Limpa o analisador
|
||||||
|
analyser = null;
|
||||||
|
isRecordingInitialized = false;
|
||||||
|
|
||||||
|
appState.global.isRecording = false; //
|
||||||
|
|
||||||
|
// Para o loop de animação
|
||||||
|
if (animationFrameId) {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
animationFrameId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove o canvas temporário
|
||||||
|
if (liveWaveformCanvas) {
|
||||||
|
liveWaveformCanvas.remove();
|
||||||
|
liveWaveformCanvas = null;
|
||||||
|
liveWaveformCtx = null;
|
||||||
|
}
|
||||||
|
// --- Fim da limpeza ---
|
||||||
|
|
||||||
|
console.log("Gravação parada.");
|
||||||
|
_updateRecordButtonUI(false); //
|
||||||
|
|
||||||
|
_processRecording(recordingBlob);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao parar a gravação:", err);
|
||||||
|
appState.global.isRecording = false; //
|
||||||
|
_updateRecordButtonUI(false); //
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona o áudio gravado (Blob) ao editor.
|
||||||
|
*/
|
||||||
|
function _processRecording(blob) {
|
||||||
|
if (!blob || blob.size === 0) {
|
||||||
|
console.warn("Blob de gravação está vazio. Nada a adicionar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- MUDANÇA: Não criamos mais a pista aqui, apenas pegamos o ID ---
|
||||||
|
// const newTrackId = newTrack.id; // [removido]
|
||||||
|
const targetTrackId = currentRecordingTrackId;
|
||||||
|
if (!targetTrackId) {
|
||||||
|
console.error("ID da pista de gravação não encontrado.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentRecordingTrackId = null; // Limpa o ID
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
const clipName = `Rec_${new Date().toISOString().slice(11, 19).replace(/:/g, '-')}`;
|
||||||
|
|
||||||
|
// Adiciona o clipe à pista que já criamos
|
||||||
|
addAudioClipToTimeline(blobUrl, targetTrackId, 0, clipName); //
|
||||||
|
|
||||||
|
// addAudioClipToTimeline já chama o render, mas como o estado mudou
|
||||||
|
// (o clipe foi adicionado), renderizar de novo garante que o
|
||||||
|
// waveform *final* (do blob) seja desenhado corretamente.
|
||||||
|
renderAudioEditor(); //
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o visual do botão de gravação.
|
||||||
|
*/
|
||||||
|
function _updateRecordButtonUI(isRecording) {
|
||||||
|
const recordBtn = document.getElementById("record-btn"); //
|
||||||
|
if (recordBtn) {
|
||||||
|
recordBtn.classList.toggle("active", isRecording);
|
||||||
|
recordBtn.classList.toggle("recording", isRecording);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Função pública que será chamada pelo botão em main.js
|
||||||
|
*/
|
||||||
|
export function toggleRecording() {
|
||||||
|
if (appState.global.isRecording) { //
|
||||||
|
_stopRecording();
|
||||||
|
} else {
|
||||||
|
_startRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,9 @@ const globalState = {
|
||||||
// --- ADICIONADO PARA O MODO DE REDIMENSIONAMENTO ---
|
// --- ADICIONADO PARA O MODO DE REDIMENSIONAMENTO ---
|
||||||
resizeMode: 'trim', // Pode ser 'trim' (Modo 2) ou 'stretch' (Modo 1)
|
resizeMode: 'trim', // Pode ser 'trim' (Modo 2) ou 'stretch' (Modo 1)
|
||||||
selectedClipId: null,
|
selectedClipId: null,
|
||||||
|
|
||||||
|
// --- RECORDING ---
|
||||||
|
isRecording: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Combina todos os estados em um único objeto namespaced
|
// Combina todos os estados em um único objeto namespaced
|
||||||
|
|
@ -56,5 +59,8 @@ export function resetProjectState() {
|
||||||
loopEndTime: 8,
|
loopEndTime: 8,
|
||||||
resizeMode: 'trim', // Reseta para o modo 'trim'
|
resizeMode: 'trim', // Reseta para o modo 'trim'
|
||||||
selectedClipId: null,
|
selectedClipId: null,
|
||||||
|
|
||||||
|
// --- RECORDING ---
|
||||||
|
isRecording: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
424
creation.html
424
creation.html
|
|
@ -10,7 +10,65 @@
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="assets/css/style.css" />
|
<link rel="stylesheet" href="assets/css/style.css" />
|
||||||
</head>
|
|
||||||
|
<style>
|
||||||
|
/* Estilo para clipes de pattern */
|
||||||
|
.timeline-clip.pattern-clip {
|
||||||
|
background: linear-gradient(to bottom, #4a4f57, #3b3f45);
|
||||||
|
height: 70px; /* Mais alto para ver as notas */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
/* Container para as notas do pattern */
|
||||||
|
.pattern-clip-view {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
/* Linha de trilha dentro do clipe */
|
||||||
|
.pattern-clip-track-row {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
/* Cada "nota" (bloco branco) */
|
||||||
|
.pattern-step-note {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-left: 1px solid #333;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
/* Menu de contexto */
|
||||||
|
#timeline-context-menu .menu-divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--border-color);
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#timeline-context-menu div#delete-clip:hover {
|
||||||
|
background-color: var(--accent-red);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#timeline-context-menu div#paste-clip.disabled {
|
||||||
|
color: var(--text-dark);
|
||||||
|
cursor: default;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
#timeline-context-menu div#paste-clip.disabled:hover {
|
||||||
|
color: var(--text-dark);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<aside class="sample-browser">
|
<aside class="sample-browser">
|
||||||
<div class="browser-header">Navegador de Samples</div>
|
<div class="browser-header">Navegador de Samples</div>
|
||||||
|
|
@ -21,54 +79,142 @@
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<header class="global-toolbar">
|
<header class="global-toolbar">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<i class="fa-solid fa-file" id="new-project-btn" title="Novo Projeto"></i>
|
<i
|
||||||
<i class="fa-solid fa-folder-open" id="open-mmp-btn" title="Abrir Projeto do Servidor"></i>
|
class="fa-solid fa-file"
|
||||||
<i class="fa-solid fa-save" id="save-mmp-btn" title="Salvar Projeto (.mmp)"></i>
|
id="new-project-btn"
|
||||||
<i class="fa-solid fa-upload" id="upload-sample-btn" title="Carregar Sample do Computador"></i>
|
title="Novo Projeto"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-folder-open"
|
||||||
|
id="open-mmp-btn"
|
||||||
|
title="Abrir Projeto do Servidor"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-save"
|
||||||
|
id="save-mmp-btn"
|
||||||
|
title="Salvar Projeto (.mmp)"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-upload"
|
||||||
|
id="upload-sample-btn"
|
||||||
|
title="Carregar Sample do Computador"
|
||||||
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<i class="fa-solid fa-backward-step" id="rewind-btn" title="Voltar ao Início"></i>
|
<i
|
||||||
|
class="fa-solid fa-backward-step"
|
||||||
|
id="rewind-btn"
|
||||||
|
title="Voltar ao Início"
|
||||||
|
></i>
|
||||||
<i class="fa-solid fa-play" title="Play/Pause Global (Futuro)"></i>
|
<i class="fa-solid fa-play" title="Play/Pause Global (Futuro)"></i>
|
||||||
<i class="fa-solid fa-stop" title="Stop Global (Futuro)"></i>
|
<i class="fa-solid fa-stop" title="Stop Global (Futuro)"></i>
|
||||||
<i class="fa-solid fa-circle-dot" title="Gravar"></i>
|
<button id="record-btn" class="transport-btn" title="Gravar">
|
||||||
|
<i class="fa-solid fa-circle-dot"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="info-display-group">
|
<div class="info-display-group">
|
||||||
<div class="info-display">
|
<div class="info-display">
|
||||||
<div class="interactive-input-container">
|
<div class="interactive-input-container">
|
||||||
<button class="adjust-btn" data-target="bpm" data-step="-1">-</button>
|
<button class="adjust-btn" data-target="bpm" data-step="-1">
|
||||||
<input type="text" class="value-input" id="bpm-input" value="140" data-min="20" data-max="400"/>
|
-
|
||||||
<button class="adjust-btn" data-target="bpm" data-step="1">+</button>
|
</button>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="value-input"
|
||||||
|
id="bpm-input"
|
||||||
|
value="140"
|
||||||
|
data-min="20"
|
||||||
|
data-max="400"
|
||||||
|
/>
|
||||||
|
<button class="adjust-btn" data-target="bpm" data-step="1">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">ANDAMENTO/BPM</div>
|
<div class="label">ANDAMENTO/BPM</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-display">
|
<div class="info-display">
|
||||||
<div class="interactive-input-container">
|
<div class="interactive-input-container">
|
||||||
<button class="adjust-btn" data-target="bars" data-step="-1">-</button>
|
<button class="adjust-btn" data-target="bars" data-step="-1">
|
||||||
<input type="text" class="value-input" id="bars-input" value="1" data-min="1" data-max="64"/>
|
-
|
||||||
<button class="adjust-btn" data-target="bars" data-step="1">+</button>
|
</button>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="value-input"
|
||||||
|
id="bars-input"
|
||||||
|
value="1"
|
||||||
|
data-min="1"
|
||||||
|
data-max="64"
|
||||||
|
/>
|
||||||
|
<button class="adjust-btn" data-target="bars" data-step="1">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">COMPASSOS</div>
|
<div class="label">COMPASSOS</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-display">
|
<div class="info-display">
|
||||||
<div class="interactive-input-container">
|
<div class="interactive-input-container">
|
||||||
<div class="compasso-group">
|
<div class="compasso-group">
|
||||||
<button class="adjust-btn" data-target="compasso-a" data-step="-1">-</button>
|
<button
|
||||||
<input type="text" class="value-input compasso-input" id="compasso-a-input" value="4" data-min="1" data-max="16"/>
|
class="adjust-btn"
|
||||||
<button class="adjust-btn" data-target="compasso-a" data-step="1">+</button>
|
data-target="compasso-a"
|
||||||
|
data-step="-1"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="value-input compasso-input"
|
||||||
|
id="compasso-a-input"
|
||||||
|
value="4"
|
||||||
|
data-min="1"
|
||||||
|
data-max="16"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="adjust-btn"
|
||||||
|
data-target="compasso-a"
|
||||||
|
data-step="1"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="compasso-separator">/</span>
|
<span class="compasso-separator">/</span>
|
||||||
<div class="compasso-group">
|
<div class="compasso-group">
|
||||||
<button class="adjust-btn" data-target="compasso-b" data-step="-1">-</button>
|
<button
|
||||||
<input type="text" class="value-input compasso-input" id="compasso-b-input" value="4" data-min="1" data-max="16"/>
|
class="adjust-btn"
|
||||||
<button class="adjust-btn" data-target="compasso-b" data-step="1">+</button>
|
data-target="compasso-b"
|
||||||
|
data-step="-1"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="value-input compasso-input"
|
||||||
|
id="compasso-b-input"
|
||||||
|
value="4"
|
||||||
|
data-min="1"
|
||||||
|
data-max="16"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="adjust-btn"
|
||||||
|
data-target="compasso-b"
|
||||||
|
data-step="1"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">COMPASSO</div>
|
<div class="label">COMPASSO</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-display">
|
<div class="info-display">
|
||||||
<div id="timer-display" class="interactive-input-container" style="font-size: 0.7rem; color: var(--text-dark)">00:00:00</div>
|
<div
|
||||||
|
id="timer-display"
|
||||||
|
class="interactive-input-container"
|
||||||
|
style="font-size: 0.7rem; color: var(--text-dark)"
|
||||||
|
>
|
||||||
|
00:00:00
|
||||||
|
</div>
|
||||||
<div class="label">MIN:SEC:MSEC</div>
|
<div class="label">MIN:SEC:MSEC</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -78,12 +224,16 @@
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="control-group master-controls">
|
<div class="control-group master-controls">
|
||||||
<div class="knob-container">
|
<div class="knob-container">
|
||||||
<div class="knob" id="master-volume-knob"><div class="knob-indicator"></div></div>
|
<div class="knob" id="master-volume-knob">
|
||||||
<span>VOL MASTER</span>
|
<div class="knob-indicator"></div>
|
||||||
|
</div>
|
||||||
|
<span>VOL MASTER</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="knob-container">
|
<div class="knob-container">
|
||||||
<div class="knob" id="master-pan-knob"><div class="knob-indicator"></div></div>
|
<div class="knob" id="master-pan-knob">
|
||||||
<span>PAN MASTER</span>
|
<div class="knob-indicator"></div>
|
||||||
|
</div>
|
||||||
|
<span>PAN MASTER</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -93,7 +243,9 @@
|
||||||
<div class="editor-header">
|
<div class="editor-header">
|
||||||
Mostrar/esconder Editor de Bases
|
Mostrar/esconder Editor de Bases
|
||||||
<div class="window-controls">
|
<div class="window-controls">
|
||||||
<i class="fa-solid fa-minus"></i><i class="fa-regular fa-square"></i><i class="fa-solid fa-xmark"></i>
|
<i class="fa-solid fa-minus"></i
|
||||||
|
><i class="fa-regular fa-square"></i
|
||||||
|
><i class="fa-solid fa-xmark"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-toolbar">
|
<div class="editor-toolbar">
|
||||||
|
|
@ -103,17 +255,38 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="pattern-manager">
|
<div class="pattern-manager">
|
||||||
<h2 id="beat-bassline-title"></h2>
|
<h2 id="beat-bassline-title"></h2>
|
||||||
<select id="global-pattern-selector" class="pattern-selector" disabled>
|
<select
|
||||||
<option>Selecione uma faixa</option>
|
id="global-pattern-selector"
|
||||||
|
class="pattern-selector"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<option>Selecione uma faixa</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="add-pattern-btn" class="pattern-btn">+</button>
|
<button id="add-pattern-btn" class="pattern-btn">+</button>
|
||||||
<button id="remove-pattern-btn" class="pattern-btn">-</button>
|
<button id="remove-pattern-btn" class="pattern-btn">-</button>
|
||||||
</div>
|
|
||||||
|
<button
|
||||||
|
id="send-pattern-to-playlist-btn"
|
||||||
|
class="pattern-btn"
|
||||||
|
title="Enviar Pattern para a Playlist"
|
||||||
|
style="width: auto; padding: 0 8px; font-size: 0.9rem"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-arrow-right-to-bracket"></i> Enviar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="tool-icons">
|
<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-wave-square enabled"></i><i class="fa-solid fa-plus" id="add-bar-btn" title="Adicionar 1 Compasso"></i>
|
<i class="fa-solid fa-table-cells"></i
|
||||||
|
><i class="fa-solid fa-bars-staggered"></i
|
||||||
|
><i class="fa-solid fa-wave-square enabled"></i
|
||||||
|
><i
|
||||||
|
class="fa-solid fa-plus"
|
||||||
|
id="add-bar-btn"
|
||||||
|
title="Adicionar 1 Compasso"
|
||||||
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="zoom-controls">
|
<div class="zoom-controls">
|
||||||
<i class="fa-solid fa-minus" id="remove-instrument-btn"></i><i class="fa-solid fa-plus" id="add-instrument-btn"></i>
|
<i class="fa-solid fa-minus" id="remove-instrument-btn"></i
|
||||||
|
><i class="fa-solid fa-plus" id="add-instrument-btn"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="track-container"></div>
|
<div id="track-container"></div>
|
||||||
|
|
@ -123,79 +296,135 @@
|
||||||
<span>Editor de Amostras de Áudio</span>
|
<span>Editor de Amostras de Áudio</span>
|
||||||
|
|
||||||
<div class="playback-controls">
|
<div class="playback-controls">
|
||||||
<i class="fa-solid fa-search-minus" id="zoom-out-btn" title="Zoom Out"></i>
|
<i
|
||||||
<i class="fa-solid fa-search-plus" id="zoom-in-btn" title="Zoom In"></i>
|
class="fa-solid fa-search-minus"
|
||||||
<i class="fa-solid fa-scissors" id="slice-tool-btn" title="Ferramenta de Corte"></i>
|
id="zoom-out-btn"
|
||||||
|
title="Zoom Out"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-search-plus"
|
||||||
|
id="zoom-in-btn"
|
||||||
|
title="Zoom In"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-scissors"
|
||||||
|
id="slice-tool-btn"
|
||||||
|
title="Ferramenta de Corte"
|
||||||
|
></i>
|
||||||
|
|
||||||
<i class="fa-solid fa-arrows-left-right-to-line" id="resize-tool-trim" title="Modo de Redimensionamento (Aparar/Trimming)"></i>
|
<i
|
||||||
<i class="fa-solid fa-arrows-left-right" id="resize-tool-stretch" title="Modo de Redimensionamento (Esticar/Time Stretch)"></i>
|
class="fa-solid fa-arrows-left-right-to-line"
|
||||||
<i class="fa-solid fa-play" id="audio-editor-play-btn" title="Play/Pause"></i>
|
id="resize-tool-trim"
|
||||||
<i class="fa-solid fa-stop" id="audio-editor-stop-btn" title="Stop"></i>
|
title="Modo de Redimensionamento (Aparar/Trimming)"
|
||||||
<i class="fa-solid fa-repeat" id="audio-editor-loop-btn" title="Ativar/Desativar Loop"></i>
|
></i>
|
||||||
<i class="fa-solid fa-plus" id="add-audio-track-btn" title="Adicionar Pista de Áudio"></i>
|
<i
|
||||||
|
class="fa-solid fa-arrows-left-right"
|
||||||
|
id="resize-tool-stretch"
|
||||||
|
title="Modo de Redimensionamento (Esticar/Time Stretch)"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-play"
|
||||||
|
id="audio-editor-play-btn"
|
||||||
|
title="Play/Pause"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-stop"
|
||||||
|
id="audio-editor-stop-btn"
|
||||||
|
title="Stop"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-repeat"
|
||||||
|
id="audio-editor-loop-btn"
|
||||||
|
title="Ativar/Desativar Loop"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="fa-solid fa-plus"
|
||||||
|
id="add-audio-track-btn"
|
||||||
|
title="Adicionar Pista de Áudio"
|
||||||
|
></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="audio-track-container">
|
<div id="audio-track-container">
|
||||||
<div class="audio-track-lane">
|
<div class="audio-track-lane">
|
||||||
<div class="track-info">
|
<div class="track-info">
|
||||||
<div class="track-info-header">
|
<div class="track-info-header">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
<span class="track-name">Pista de Áudio 1</span>
|
<span class="track-name">Pista de Áudio 1</span>
|
||||||
<div class="track-mute"></div>
|
<div class="track-mute"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-controls">
|
<div class="track-controls">
|
||||||
<div class="knob-container">
|
<div class="knob-container">
|
||||||
<div class="knob" data-control="volume"><div class="knob-indicator"></div></div>
|
<div class="knob" data-control="volume">
|
||||||
<span>VOL</span>
|
<div class="knob-indicator"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="knob-container">
|
<span>VOL</span>
|
||||||
<div class="knob" data-control="pan"><div class="knob-indicator"></div></div>
|
</div>
|
||||||
<span>PAN</span>
|
<div class="knob-container">
|
||||||
</div>
|
<div class="knob" data-control="pan">
|
||||||
</div>
|
<div class="knob-indicator"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<span>PAN</span>
|
||||||
<div class="timeline-container">
|
</div>
|
||||||
<div class="spectrogram-view-grid" style="width: 4000px;"> <div class="timeline-clip" style="left: 100px; width: 400px;"></div>
|
</div>
|
||||||
<div class="playhead"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="audio-track-lane">
|
|
||||||
<div class="track-info">
|
|
||||||
<div class="track-info-header">
|
|
||||||
<i class="fa-solid fa-gear"></i>
|
|
||||||
<span class="track-name">Pista de Áudio 2</span>
|
|
||||||
<div class="track-mute"></div>
|
|
||||||
</div>
|
|
||||||
<div class="track-controls">
|
|
||||||
<div class="knob-container">
|
|
||||||
<div class="knob" data-control="volume"><div class="knob-indicator"></div></div>
|
|
||||||
<span>VOL</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="knob-container">
|
|
||||||
<div class="knob" data-control="pan"><div class="knob-indicator"></div></div>
|
<div class="timeline-container">
|
||||||
<span>PAN</span>
|
<div class="spectrogram-view-grid" style="width: 4000px">
|
||||||
|
<div class="timeline-clip" style="left: 100px; width: 400px"></div>
|
||||||
|
<div class="playhead"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="timeline-container">
|
|
||||||
<div id="loop-region" class="loop-region">
|
|
||||||
<div class="spectrogram-view-grid" style="width: 4000px;">
|
|
||||||
|
|
||||||
<div class="timeline-clip" style="left: 50px; width: 600px;">
|
<div class="audio-track-lane">
|
||||||
<div class="clip-name">jungle01.ogg</div>
|
<div class="track-info">
|
||||||
|
<div class="track-info-header">
|
||||||
|
<i class="fa-solid fa-gear"></i>
|
||||||
|
<span class="track-name">Pista de Áudio 2</span>
|
||||||
|
<div class="track-mute"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="track-controls">
|
||||||
|
<div class="knob-container">
|
||||||
|
<div class="knob" data-control="volume">
|
||||||
|
<div class="knob-indicator"></div>
|
||||||
|
</div>
|
||||||
|
<span>VOL</span>
|
||||||
|
</div>
|
||||||
|
<div class="knob-container">
|
||||||
|
<div class="knob" data-control="pan">
|
||||||
|
<div class="knob-indicator"></div>
|
||||||
|
</div>
|
||||||
|
<span>PAN</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-container">
|
||||||
|
<div id="loop-region" class="loop-region">
|
||||||
|
<div class="spectrogram-view-grid" style="width: 4000px">
|
||||||
|
<div class="timeline-clip" style="left: 50px; width: 600px">
|
||||||
|
<div class="clip-name">jungle01.ogg</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="playhead"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="playhead"></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<input type="file" id="mmp-file-input" accept=".mmp, .mmpz" style="display: none"/>
|
<input
|
||||||
<input type="file" id="sample-file-input" accept=".wav,.flac,.ogg,.mp3" style="display: none"/>
|
type="file"
|
||||||
|
id="mmp-file-input"
|
||||||
|
accept=".mmp, .mmpz"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="sample-file-input"
|
||||||
|
accept=".wav,.flac,.ogg,.mp3"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="modal-overlay" id="open-project-modal">
|
<div class="modal-overlay" id="open-project-modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
@ -214,14 +443,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="timeline-context-menu">
|
<div id="timeline-context-menu">
|
||||||
<div id="set-loop-start">Definir Início do Loop</div>
|
<div id="set-loop-start">Definir Início do Loop</div>
|
||||||
<div id="set-loop-end">Definir Fim do Loop</div>
|
<div id="set-loop-end">Definir Fim do Loop</div>
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
<div id="delete-clip" style="color: var(--accent-red);">Excluir Clipe</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div id="copy-clip">Copiar</div>
|
||||||
|
<div id="cut-clip">Recortar</div>
|
||||||
|
<div id="paste-clip">Colar</div>
|
||||||
|
<div class="menu-divider"></div>
|
||||||
|
<div id="delete-clip" style="color: var(--accent-red)">Excluir Clipe</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js"></script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue