MMPCreator
This commit is contained in:
1 parent
0dda2b1b0c
commit
8395783986
22 files changed
+2883
-460
No files matched your search
@@ -6,7 +6,7 @@
|
||||
background-color: #f4f4f4;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -40,4 +40,3 @@
|
||||
padding: 0.3rem 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-32
@@ -1,33 +1,32 @@
|
||||
.sidebar-center {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #f4f4f4;
|
||||
padding: 2rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.submenu a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
transition: background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.submenu a:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #f4f4f4;
|
||||
padding: 2rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.submenu a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
transition: background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.submenu a:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
@@ -0,0 +1,703 @@
|
||||
/* =============================================== */
|
||||
/* VÁRIAVEIS GLOBAIS (ROOT)
|
||||
/* =============================================== */
|
||||
:root {
|
||||
--bg-body: #2d3035;
|
||||
--bg-toolbar: #3b3f45;
|
||||
--bg-editor: #33373d;
|
||||
--border-color: #232529;
|
||||
--text-light: #c0c0c0;
|
||||
--text-dark: #888;
|
||||
--accent-green: #2ecc71;
|
||||
--accent-red: #d9534f;
|
||||
}
|
||||
|
||||
/* =============================================== */
|
||||
/* ESTILOS GLOBAIS E LAYOUT PRINCIPAL
|
||||
/* =============================================== */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
background-color: var(--bg-body);
|
||||
color: var(--text-light);
|
||||
padding-left: 300px;
|
||||
transition: padding-left .3s ease;
|
||||
}
|
||||
|
||||
body.sidebar-hidden {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
body.knob-dragging {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* =============================================== */
|
||||
/* BARRA LATERAL (SAMPLE BROWSER)
|
||||
/* =============================================== */
|
||||
.sample-browser {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 300px;
|
||||
height: 100vh;
|
||||
background-color: var(--bg-toolbar);
|
||||
border-right: 2px solid var(--border-color);
|
||||
z-index: 1500;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateX(0);
|
||||
transition: transform .3s ease;
|
||||
}
|
||||
|
||||
body.sidebar-hidden .sample-browser {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.browser-header {
|
||||
padding: 15px;
|
||||
background-color: #2a2c30;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.browser-content {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.browser-content ul {
|
||||
list-style: none;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.browser-content li {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.browser-content li:hover {
|
||||
background-color: var(--bg-editor);
|
||||
}
|
||||
|
||||
.browser-content li i {
|
||||
margin-right: 8px;
|
||||
width: 12px;
|
||||
color: var(--text-dark);
|
||||
transition: transform .2s;
|
||||
}
|
||||
|
||||
.browser-content li.directory > ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.browser-content li.directory.open > ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.browser-content li.directory.open > .fa-folder {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
#sidebar-toggle {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 305px;
|
||||
z-index: 1400;
|
||||
background-color: var(--bg-toolbar);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-light);
|
||||
width: 25px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
transition: left .3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body.sidebar-hidden #sidebar-toggle {
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
/* =============================================== */
|
||||
/* BARRA DE FERRAMENTAS GLOBAL
|
||||
/* =============================================== */
|
||||
.global-toolbar {
|
||||
padding: 8px 15px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
/* Removido width 100% para se adaptar ao padding do body */
|
||||
left: 300px;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
background-color: var(--bg-toolbar);
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
transition: left .3s ease;
|
||||
}
|
||||
|
||||
body.sidebar-hidden .global-toolbar {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================== */
|
||||
/* EDITOR DE BATIDAS (BEAT EDITOR)
|
||||
/* =============================================== */
|
||||
.beat-editor {
|
||||
background-color: var(--bg-body);
|
||||
border: 1px solid var(--border-color);
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, .3);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.window-controls i {
|
||||
margin-left: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
background-color: var(--bg-toolbar);
|
||||
padding: 5px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.editor-toolbar i {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.editor-toolbar i.enabled {
|
||||
background-color: var(--bg-body);
|
||||
box-shadow: inset 0 0 2px #000;
|
||||
}
|
||||
|
||||
.pattern-selector {
|
||||
background-color: var(--bg-body);
|
||||
padding: 5px 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
flex-grow: 1;
|
||||
font-size: .9rem;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* =============================================== */
|
||||
/* FAIXAS (TRACK LANES) E SEQUENCIADOR
|
||||
/* =============================================== */
|
||||
.track-lane {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
background-color: var(--bg-editor);
|
||||
border: 2px dashed transparent;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.track-lane.drag-over {
|
||||
border-color: var(--accent-green);
|
||||
}
|
||||
|
||||
.track-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 180px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.track-info .fa-gear {
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.track-mute {
|
||||
width: 25px;
|
||||
height: 12px;
|
||||
background-color: var(--accent-green);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: inset 0 0 2px #000;
|
||||
}
|
||||
|
||||
.track-name {
|
||||
color: var(--accent-red);
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.track-controls {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin: 0 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid var(--bg-toolbar);
|
||||
}
|
||||
|
||||
.knob-container {
|
||||
text-align: center;
|
||||
font-size: .7rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.knob {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: var(--bg-toolbar);
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: 2px;
|
||||
cursor: grab;
|
||||
box-shadow: inset 0 0 4px #222;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.knob:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.knob-indicator {
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
background-color: var(--text-light);
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
transform: translateX(-50%) rotate(0deg);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.step-sequencer {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
gap: 4px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-sequencer::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.step-sequencer::-webkit-scrollbar-track {
|
||||
background: var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.step-sequencer::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-toolbar);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.step-sequencer::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.step-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-marker {
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
left: 1px;
|
||||
font-size: .6rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.step {
|
||||
width: 28px;
|
||||
aspect-ratio: 1 / 1;
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #4a4a4a;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: background-color .1s, transform 0.1s;
|
||||
flex-shrink: 0; /* Impede que os steps encolham */
|
||||
}
|
||||
|
||||
.step-dark {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
.step:hover {
|
||||
background-color: #555;
|
||||
border-color: #888;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
background-color: var(--accent-green);
|
||||
border: 1px solid #fff;
|
||||
box-shadow: 0 0 8px var(--accent-green);
|
||||
}
|
||||
|
||||
.step.playing {
|
||||
transform: scale(1.1);
|
||||
box-shadow: inset 0 0 8px rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* =============================================== */
|
||||
/* CONTROLES E INPUTS
|
||||
/* =============================================== */
|
||||
.interactive-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.compasso-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.value-input {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: var(--accent-green);
|
||||
font-weight: 700;
|
||||
font-size: 1.4rem;
|
||||
font-family: Courier New, Courier, monospace;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.compasso-input {
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.compasso-separator {
|
||||
color: var(--accent-green);
|
||||
font-weight: 700;
|
||||
font-size: 1.4rem;
|
||||
font-family: Courier New, Courier, monospace;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.value-input::-webkit-outer-spin-button,
|
||||
.value-input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.value-input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.adjust-btn {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
color: var(--text-dark);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
transition: color .2s;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.adjust-btn:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.control-group i {
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
color: var(--text-light);
|
||||
transition: color .2s;
|
||||
}
|
||||
|
||||
.control-group i:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fa-play,
|
||||
.fa-pause {
|
||||
color: var(--accent-green) !important;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 25px;
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
.info-display-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.info-display {
|
||||
background-color: #1a1c1e;
|
||||
padding: 5px 8px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-display .label {
|
||||
color: var(--text-dark);
|
||||
font-size: .6rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#metronome-btn {
|
||||
background: 0 0;
|
||||
border: 1px solid var(--text-dark);
|
||||
color: var(--accent-green);
|
||||
font-family: inherit;
|
||||
font-weight: 700;
|
||||
font-size: .8rem;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
#metronome-btn:hover {
|
||||
border-color: var(--text-light);
|
||||
background-color: var(--bg-editor);
|
||||
}
|
||||
|
||||
#metronome-btn.active {
|
||||
background-color: var(--accent-green);
|
||||
color: var(--bg-body);
|
||||
border-color: var(--accent-green);
|
||||
}
|
||||
|
||||
/* =============================================== */
|
||||
/* MODAL (CAIXA DE DIÁLOGO)
|
||||
/* =============================================== */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0s 0.3s, opacity 0.3s;
|
||||
}
|
||||
|
||||
.modal-overlay.visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: visibility 0s, opacity 0.3s;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--bg-body);
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-dark);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--text-light);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.8rem;
|
||||
border-bottom: 1px solid var(--bg-toolbar);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
background-color: var(--bg-toolbar);
|
||||
color: var(--text-light);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-button:hover {
|
||||
background-color: #4a4f57;
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
#server-projects-list .project-item {
|
||||
background-color: var(--bg-editor);
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
#server-projects-list .project-item:hover {
|
||||
background-color: var(--bg-body);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================== */
|
||||
/* ESTILOS RESPONSIVOS
|
||||
/* =============================================== */
|
||||
|
||||
/* Para telas menores como laptops pequenos e tablets grandes */
|
||||
@media (max-width: 992px) {
|
||||
.main-content {
|
||||
padding: 1.5rem; /* Reduz o padding */
|
||||
}
|
||||
|
||||
.beat-editor {
|
||||
max-width: 100%; /* Permite que o editor use mais espaço */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Para tablets e celulares */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding-left: 0 !important; /* Remove o padding fixo, !important para garantir */
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* A sidebar agora se sobrepõe ao conteúdo e fica escondida por padrão */
|
||||
.sample-browser {
|
||||
transform: translateX(-100%);
|
||||
width: 280px; /* Pode diminuir um pouco a largura */
|
||||
}
|
||||
|
||||
/* Quando a sidebar for aberta, ela volta a posição original */
|
||||
body:not(.sidebar-hidden) .sample-browser {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#sidebar-toggle {
|
||||
left: 5px; /* Posição fixa do botão */
|
||||
}
|
||||
|
||||
/* A toolbar global agora ocupa 100% da largura */
|
||||
.global-toolbar {
|
||||
left: 0;
|
||||
padding-left: 45px; /* Espaço para o botão do menu */
|
||||
}
|
||||
|
||||
/* Quebra a linha dos itens da toolbar se não couberem */
|
||||
.editor-toolbar,
|
||||
.control-group {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Reorganiza a track para um layout vertical */
|
||||
.track-lane {
|
||||
flex-direction: column;
|
||||
align-items: stretch; /* Itens ocupam 100% da largura */
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.track-info,
|
||||
.track-controls {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.track-controls {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
justify-content: space-around; /* Distribui melhor os knobs */
|
||||
}
|
||||
|
||||
.step-sequencer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Ajusta o modal para telas pequenas */
|
||||
.modal-content {
|
||||
max-width: 90vw;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//TODO ver Tone.js
|
||||
|
||||
// js/audio.js
|
||||
import { appState } from "./state.js";
|
||||
import { highlightStep } from "./ui.js";
|
||||
import { getTotalSteps } from "./utils.js";
|
||||
|
||||
let audioContext;
|
||||
let mainGainNode;
|
||||
|
||||
export function getAudioContext() {
|
||||
return audioContext;
|
||||
}
|
||||
export function getMainGainNode() {
|
||||
return mainGainNode;
|
||||
}
|
||||
|
||||
export function initializeAudioContext() {
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
mainGainNode = audioContext.createGain();
|
||||
mainGainNode.connect(audioContext.destination);
|
||||
}
|
||||
if (audioContext.state === "suspended") {
|
||||
audioContext.resume();
|
||||
}
|
||||
}
|
||||
|
||||
export function playMetronomeSound(isDownbeat) {
|
||||
initializeAudioContext();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
const frequency = isDownbeat ? 1000 : 800;
|
||||
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
|
||||
oscillator.type = "sine";
|
||||
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(
|
||||
0.00001,
|
||||
audioContext.currentTime + 0.05
|
||||
);
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
oscillator.start(audioContext.currentTime);
|
||||
oscillator.stop(audioContext.currentTime + 0.05);
|
||||
}
|
||||
|
||||
export function playSample(filePath, trackId) {
|
||||
initializeAudioContext();
|
||||
if (!filePath) return;
|
||||
|
||||
const track = trackId ? appState.tracks.find((t) => t.id == trackId) : null;
|
||||
const audio = new Audio(filePath);
|
||||
const source = audioContext.createMediaElementSource(audio);
|
||||
|
||||
if (track && track.gainNode) {
|
||||
source.connect(track.gainNode);
|
||||
} else {
|
||||
source.connect(mainGainNode);
|
||||
}
|
||||
|
||||
audio.play();
|
||||
}
|
||||
|
||||
function tick() {
|
||||
const totalSteps = getTotalSteps();
|
||||
if (totalSteps === 0) {
|
||||
stopPlayback();
|
||||
return;
|
||||
}
|
||||
const lastStepIndex =
|
||||
appState.currentStep === 0 ? totalSteps - 1 : appState.currentStep - 1;
|
||||
highlightStep(lastStepIndex, false);
|
||||
|
||||
if (appState.metronomeEnabled) {
|
||||
const noteValue =
|
||||
parseInt(document.getElementById("compasso-b-input").value, 10) || 4;
|
||||
const stepsPerBeat = 16 / noteValue;
|
||||
if (appState.currentStep % stepsPerBeat === 0) {
|
||||
playMetronomeSound(appState.currentStep === 0);
|
||||
}
|
||||
}
|
||||
|
||||
appState.tracks.forEach((track) => {
|
||||
if (track.steps[appState.currentStep] && track.samplePath) {
|
||||
playSample(track.samplePath, track.id);
|
||||
}
|
||||
});
|
||||
|
||||
highlightStep(appState.currentStep, true);
|
||||
appState.currentStep = (appState.currentStep + 1) % totalSteps;
|
||||
}
|
||||
|
||||
export function startPlayback() {
|
||||
if (appState.isPlaying || appState.tracks.length === 0) return;
|
||||
initializeAudioContext();
|
||||
const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
|
||||
const stepInterval = (60 * 1000) / (bpm * 4);
|
||||
|
||||
if (appState.playbackIntervalId) clearInterval(appState.playbackIntervalId);
|
||||
|
||||
appState.isPlaying = true;
|
||||
document.getElementById("play-btn").classList.remove("fa-play");
|
||||
document.getElementById("play-btn").classList.add("fa-pause");
|
||||
|
||||
tick();
|
||||
appState.playbackIntervalId = setInterval(tick, stepInterval);
|
||||
}
|
||||
|
||||
export function stopPlayback() {
|
||||
clearInterval(appState.playbackIntervalId);
|
||||
appState.playbackIntervalId = null;
|
||||
appState.isPlaying = false;
|
||||
highlightStep(appState.currentStep - 1, false);
|
||||
appState.currentStep = 0;
|
||||
document.getElementById("play-btn").classList.remove("fa-pause");
|
||||
document.getElementById("play-btn").classList.add("fa-play");
|
||||
}
|
||||
|
||||
export function rewindPlayback() {
|
||||
appState.currentStep = 0;
|
||||
if (!appState.isPlaying) {
|
||||
document
|
||||
.querySelectorAll(".step.playing")
|
||||
.forEach((s) => s.classList.remove("playing"));
|
||||
}
|
||||
}
|
||||
|
||||
export function togglePlayback() {
|
||||
if (appState.isPlaying) {
|
||||
stopPlayback();
|
||||
} else {
|
||||
startPlayback();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// js/config.js
|
||||
|
||||
// Constantes usadas para o cálculo de posição de notas no arquivo .mmp
|
||||
export const TICKS_PER_BAR = 192;
|
||||
export const NOTE_LENGTH = 12;
|
||||
|
||||
// Constantes para os valores padrão dos knobs
|
||||
export const DEFAULT_VOLUME = 0.8;
|
||||
export const DEFAULT_PAN = 0.0;
|
||||
@@ -0,0 +1,243 @@
|
||||
// js/file.js
|
||||
import { appState } from "./state.js";
|
||||
import { getTotalSteps } from "./utils.js";
|
||||
import { renderApp } from "./ui.js";
|
||||
import { NOTE_LENGTH, TICKS_PER_BAR } from "./config.js";
|
||||
import {
|
||||
initializeAudioContext,
|
||||
getAudioContext,
|
||||
getMainGainNode,
|
||||
} from "./audio.js";
|
||||
|
||||
export async function handleFileLoad(file) {
|
||||
let xmlContent = "";
|
||||
try {
|
||||
if (file.name.toLowerCase().endsWith(".mmpz")) {
|
||||
const jszip = new JSZip();
|
||||
const zip = await jszip.loadAsync(file);
|
||||
const projectFile = Object.keys(zip.files).find((name) =>
|
||||
name.toLowerCase().endsWith(".mmp")
|
||||
);
|
||||
if (!projectFile)
|
||||
throw new Error(
|
||||
"Não foi possível encontrar um arquivo .mmp dentro do .mmpz"
|
||||
);
|
||||
xmlContent = await zip.files[projectFile].async("string");
|
||||
} else {
|
||||
xmlContent = await file.text();
|
||||
}
|
||||
parseMmpContent(xmlContent);
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar o projeto:", error);
|
||||
alert(`Erro ao carregar projeto: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseMmpContent(xmlString) {
|
||||
initializeAudioContext();
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
|
||||
|
||||
appState.originalXmlDoc = xmlDoc;
|
||||
const newTracks = [];
|
||||
|
||||
const head = xmlDoc.querySelector("head");
|
||||
if (head) {
|
||||
document.getElementById("bpm-input").value =
|
||||
head.getAttribute("bpm") || 140;
|
||||
document.getElementById("compasso-a-input").value =
|
||||
head.getAttribute("timesig_numerator") || 4;
|
||||
document.getElementById("compasso-b-input").value =
|
||||
head.getAttribute("timesig_denominator") || 4;
|
||||
}
|
||||
const sampleTrackElements = xmlDoc.querySelectorAll(
|
||||
'instrument[name="audiofileprocessor"]'
|
||||
);
|
||||
sampleTrackElements.forEach((instrumentNode) => {
|
||||
const afpNode = instrumentNode.querySelector("audiofileprocessor");
|
||||
const instrumentTrackNode = instrumentNode.parentElement;
|
||||
const trackNode = instrumentTrackNode.parentElement;
|
||||
if (!afpNode || !instrumentTrackNode || !trackNode) return;
|
||||
|
||||
const audioContext = getAudioContext();
|
||||
const mainGainNode = getMainGainNode();
|
||||
const totalSteps = parseInt(
|
||||
trackNode.querySelector("pattern")?.getAttribute("steps") ||
|
||||
getTotalSteps(),
|
||||
10
|
||||
);
|
||||
const newSteps = new Array(totalSteps).fill(false);
|
||||
const ticksPerStep = totalSteps > 0 ? TICKS_PER_BAR / totalSteps : 0;
|
||||
|
||||
trackNode.querySelectorAll("pattern note").forEach((noteNode) => {
|
||||
const pos = parseInt(noteNode.getAttribute("pos"), 10);
|
||||
const stepIndex = Math.round(pos / ticksPerStep);
|
||||
if (stepIndex < totalSteps) {
|
||||
newSteps[stepIndex] = true;
|
||||
}
|
||||
});
|
||||
const newTrack = {
|
||||
id: Date.now() + Math.random(),
|
||||
name:
|
||||
afpNode.getAttribute("src").split("/").pop() ||
|
||||
trackNode.getAttribute("name"),
|
||||
samplePath: `samples/${afpNode.getAttribute("src")}`,
|
||||
steps: newSteps,
|
||||
volume: parseFloat(instrumentTrackNode.getAttribute("vol")) / 100,
|
||||
pan: parseFloat(instrumentTrackNode.getAttribute("pan")) / 100,
|
||||
gainNode: audioContext.createGain(),
|
||||
pannerNode: audioContext.createStereoPanner(),
|
||||
};
|
||||
newTrack.gainNode.connect(newTrack.pannerNode);
|
||||
newTrack.pannerNode.connect(mainGainNode);
|
||||
newTrack.gainNode.gain.value = newTrack.volume;
|
||||
newTrack.pannerNode.pan.value = newTrack.pan;
|
||||
newTracks.push(newTrack);
|
||||
});
|
||||
appState.tracks = newTracks;
|
||||
renderApp();
|
||||
console.log("Projeto carregado com sucesso!", appState);
|
||||
}
|
||||
|
||||
export function generateMmpFile() {
|
||||
if (appState.originalXmlDoc) {
|
||||
modifyAndSaveExistingMmp();
|
||||
} else {
|
||||
generateNewMmp();
|
||||
}
|
||||
}
|
||||
|
||||
function generateNewMmp() {
|
||||
console.log("Gerando novo arquivo .mmp do zero...");
|
||||
const bpm = document.getElementById("bpm-input").value;
|
||||
const sig_num = document.getElementById("compasso-a-input").value;
|
||||
const sig_den = document.getElementById("compasso-b-input").value;
|
||||
const tracksXml = appState.tracks
|
||||
.map((track) => createTrackXml(track))
|
||||
.join("");
|
||||
|
||||
const mmpContent = `<?xml version="1.0"?>
|
||||
<!DOCTYPE lmms-project>
|
||||
<lmms-project version="1.0" type="song" creator="MMPCreator" creatorversion="1.0">
|
||||
<head mastervol="100" timesig_denominator="${sig_den}" bpm="${bpm}" timesig_numerator="${sig_num}" masterpitch="0"/>
|
||||
<song>
|
||||
<trackcontainer type="song">
|
||||
<track type="1" solo="0" muted="0" name="Beat/Bassline 0">
|
||||
<bbtrack>
|
||||
<trackcontainer type="bbtrackcontainer">
|
||||
${tracksXml}
|
||||
</trackcontainer>
|
||||
</bbtrack>
|
||||
<bbtco color="4286611584" len="192" usestyle="1" pos="0" muted="0" name="Pattern 1"/>
|
||||
</track>
|
||||
</trackcontainer>
|
||||
<timeline lp1pos="192" lp0pos="0" lpstate="0"/>
|
||||
<controllers/>
|
||||
<projectnotes width="686" y="10" minimized="0" visible="0" x="700" maximized="0" height="400"><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body>
|
||||
<p>Feito com MMPCreator no https://alice.ufsj.edu.br/MMPSearch/creator</p>
|
||||
</body></html>]]></projectnotes>
|
||||
</song>
|
||||
</lmms-project>`;
|
||||
downloadFile(mmpContent, "novo_projeto.mmp");
|
||||
}
|
||||
|
||||
function modifyAndSaveExistingMmp() {
|
||||
console.log("Modificando arquivo .mmp existente...");
|
||||
const xmlDoc = appState.originalXmlDoc.cloneNode(true);
|
||||
const head = xmlDoc.querySelector("head");
|
||||
if (head) {
|
||||
head.setAttribute("bpm", document.getElementById("bpm-input").value);
|
||||
head.setAttribute(
|
||||
"timesig_numerator",
|
||||
document.getElementById("compasso-a-input").value
|
||||
);
|
||||
head.setAttribute(
|
||||
"timesig_denominator",
|
||||
document.getElementById("compasso-b-input").value
|
||||
);
|
||||
}
|
||||
const bbTrackContainer = xmlDoc.querySelector("bbtrack > trackcontainer");
|
||||
if (bbTrackContainer) {
|
||||
const oldSampleTracks = bbTrackContainer.querySelectorAll(
|
||||
'instrument[name="audiofileprocessor"]'
|
||||
);
|
||||
oldSampleTracks.forEach((node) => node.closest("track").remove());
|
||||
const tracksXml = appState.tracks
|
||||
.map((track) => createTrackXml(track))
|
||||
.join("");
|
||||
const tempDoc = new DOMParser().parseFromString(
|
||||
`<root>${tracksXml}</root>`,
|
||||
"application/xml"
|
||||
);
|
||||
Array.from(tempDoc.documentElement.children).forEach((newTrackNode) => {
|
||||
bbTrackContainer.appendChild(newTrackNode);
|
||||
});
|
||||
}
|
||||
|
||||
const serializer = new XMLSerializer();
|
||||
const mmpContent = serializer.serializeToString(xmlDoc);
|
||||
downloadFile(mmpContent, "projeto_editado.mmp");
|
||||
}
|
||||
|
||||
function createTrackXml(track) {
|
||||
if (!track.samplePath) return "";
|
||||
const totalSteps = track.steps.length || getTotalSteps();
|
||||
const ticksPerStep = totalSteps > 0 ? TICKS_PER_BAR / totalSteps : 0;
|
||||
const lmmsVolume = Math.round(track.volume * 100);
|
||||
const lmmsPan = Math.round(track.pan * 100);
|
||||
const sampleSrc = track.samplePath.replace("samples/", "");
|
||||
const notesXml = track.steps
|
||||
.map((isActive, index) => {
|
||||
if (isActive) {
|
||||
const notePos = Math.round(index * ticksPerStep);
|
||||
return `<note vol="100" len="${NOTE_LENGTH}" pos="${notePos}" pan="0" key="57"/>`;
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.join("\n ");
|
||||
return `
|
||||
<track type="0" solo="0" muted="0" name="${track.name}">
|
||||
<instrumenttrack vol="${lmmsVolume}" pitch="0" fxch="0" pitchrange="1" basenote="57" usemasterpitch="1" pan="${lmmsPan}">
|
||||
<instrument name="audiofileprocessor">
|
||||
<audiofileprocessor eframe="1" stutter="0" looped="0" interp="1" reversed="0" lframe="0" src="${sampleSrc}" amp="100" sframe="0"/>
|
||||
</instrument>
|
||||
<fxchain enabled="0" numofeffects="0"/>
|
||||
</instrumenttrack>
|
||||
<pattern type="0" pos="0" muted="0" steps="${totalSteps}" name="${track.name}">
|
||||
${notesXml}
|
||||
</pattern>
|
||||
</track>`;
|
||||
}
|
||||
|
||||
function downloadFile(content, fileName) {
|
||||
const blob = new Blob([content], { type: "application/xml;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// (ALTERADO) Adiciona export na frente da função
|
||||
export async function loadProjectFromServer(fileName) {
|
||||
try {
|
||||
const response = await fetch(`mmp/${fileName}`);
|
||||
if (!response.ok)
|
||||
throw new Error(`Não foi possível carregar o arquivo ${fileName}`);
|
||||
const xmlContent = await response.text();
|
||||
parseMmpContent(xmlContent);
|
||||
// Retorna true em caso de sucesso
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar projeto do servidor:", error);
|
||||
alert(`Erro ao carregar projeto: ${error.message}`);
|
||||
// Retorna false em caso de falha
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// js/main.js
|
||||
import {
|
||||
appState,
|
||||
addTrackToState,
|
||||
removeLastTrackFromState,
|
||||
} from "./state.js";
|
||||
import {
|
||||
togglePlayback,
|
||||
stopPlayback,
|
||||
rewindPlayback,
|
||||
initializeAudioContext,
|
||||
} from "./audio.js";
|
||||
import { handleFileLoad, generateMmpFile } from "./file.js";
|
||||
import {
|
||||
renderApp,
|
||||
redrawSequencer,
|
||||
loadAndRenderSampleBrowser,
|
||||
showOpenProjectModal,
|
||||
closeOpenProjectModal,
|
||||
} from "./ui.js";
|
||||
import { adjustValue, enforceNumericInput } from "./utils.js";
|
||||
|
||||
// --- INICIALIZAÇÃO E EVENTOS FINAIS ---
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const newProjectBtn = document.getElementById("new-project-btn");
|
||||
const openMmpBtn = document.getElementById("open-mmp-btn");
|
||||
const saveMmpBtn = document.getElementById("save-mmp-btn");
|
||||
const addInstrumentBtn = document.getElementById("add-instrument-btn");
|
||||
const removeInstrumentBtn = document.getElementById("remove-instrument-btn");
|
||||
const playBtn = document.getElementById("play-btn");
|
||||
const stopBtn = document.getElementById("stop-btn");
|
||||
const rewindBtn = document.getElementById("rewind-btn");
|
||||
const metronomeBtn = document.getElementById("metronome-btn");
|
||||
const mmpFileInput = document.getElementById("mmp-file-input");
|
||||
const openProjectModal = document.getElementById("open-project-modal");
|
||||
const openModalCloseBtn = document.getElementById("open-modal-close-btn");
|
||||
const loadFromComputerBtn = document.getElementById("load-from-computer-btn");
|
||||
const sidebarToggle = document.getElementById("sidebar-toggle");
|
||||
|
||||
newProjectBtn.addEventListener("click", () => {
|
||||
if (
|
||||
appState.tracks.length > 0 &&
|
||||
!confirm("Você tem certeza? Alterações não salvas serão perdidas.")
|
||||
)
|
||||
return;
|
||||
Object.assign(appState, {
|
||||
tracks: [],
|
||||
isPlaying: false,
|
||||
playbackIntervalId: null,
|
||||
currentStep: 0,
|
||||
metronomeEnabled: false,
|
||||
originalXmlDoc: null,
|
||||
});
|
||||
renderApp();
|
||||
});
|
||||
|
||||
openMmpBtn.addEventListener("click", showOpenProjectModal);
|
||||
loadFromComputerBtn.addEventListener("click", () => mmpFileInput.click());
|
||||
mmpFileInput.addEventListener("change", (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
handleFileLoad(file);
|
||||
closeOpenProjectModal();
|
||||
}
|
||||
});
|
||||
saveMmpBtn.addEventListener("click", generateMmpFile);
|
||||
addInstrumentBtn.addEventListener("click", addTrackToState);
|
||||
removeInstrumentBtn.addEventListener("click", removeLastTrackFromState);
|
||||
playBtn.addEventListener("click", togglePlayback);
|
||||
stopBtn.addEventListener("click", stopPlayback);
|
||||
rewindBtn.addEventListener("click", rewindPlayback);
|
||||
metronomeBtn.addEventListener("click", () => {
|
||||
initializeAudioContext();
|
||||
appState.metronomeEnabled = !appState.metronomeEnabled;
|
||||
metronomeBtn.classList.toggle("active", appState.metronomeEnabled);
|
||||
});
|
||||
openModalCloseBtn.addEventListener("click", closeOpenProjectModal);
|
||||
openProjectModal.addEventListener("click", (e) => {
|
||||
if (e.target === openProjectModal) closeOpenProjectModal();
|
||||
});
|
||||
sidebarToggle.addEventListener("click", () => {
|
||||
document.body.classList.toggle("sidebar-hidden");
|
||||
const icon = sidebarToggle.querySelector("i");
|
||||
icon.className = document.body.classList.contains("sidebar-hidden")
|
||||
? "fa-solid fa-caret-right"
|
||||
: "fa-solid fa-caret-left";
|
||||
});
|
||||
|
||||
const inputs = document.querySelectorAll(".value-input");
|
||||
inputs.forEach((input) => {
|
||||
input.addEventListener("input", (event) => {
|
||||
enforceNumericInput(event);
|
||||
if (appState.isPlaying && event.target.id.startsWith("compasso-")) {
|
||||
stopPlayback();
|
||||
}
|
||||
if (event.target.id.startsWith("compasso-")) {
|
||||
redrawSequencer();
|
||||
}
|
||||
});
|
||||
input.addEventListener("wheel", (event) => {
|
||||
event.preventDefault();
|
||||
const step = event.deltaY < 0 ? 1 : -1;
|
||||
adjustValue(event.target, step);
|
||||
});
|
||||
});
|
||||
|
||||
const buttons = document.querySelectorAll(".adjust-btn");
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const targetId = button.dataset.target + "-input";
|
||||
const targetInput = document.getElementById(targetId);
|
||||
if (targetInput) {
|
||||
adjustValue(targetInput, step);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Inicia a aplicação
|
||||
loadAndRenderSampleBrowser();
|
||||
renderApp();
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// js/state.js
|
||||
import { DEFAULT_VOLUME, DEFAULT_PAN } from "./config.js";
|
||||
import {
|
||||
initializeAudioContext,
|
||||
getAudioContext,
|
||||
getMainGainNode,
|
||||
} from "./audio.js";
|
||||
import { renderApp } from "./ui.js";
|
||||
|
||||
// O "cérebro" da aplicação
|
||||
export let appState = {
|
||||
tracks: [],
|
||||
isPlaying: false,
|
||||
playbackIntervalId: null,
|
||||
currentStep: 0,
|
||||
metronomeEnabled: false,
|
||||
originalXmlDoc: null,
|
||||
};
|
||||
|
||||
export function addTrackToState() {
|
||||
initializeAudioContext();
|
||||
const audioContext = getAudioContext();
|
||||
const mainGainNode = getMainGainNode();
|
||||
|
||||
const newTrack = {
|
||||
id: Date.now(),
|
||||
name: "novo instrumento",
|
||||
samplePath: null,
|
||||
steps: [],
|
||||
volume: DEFAULT_VOLUME,
|
||||
pan: DEFAULT_PAN,
|
||||
gainNode: audioContext.createGain(),
|
||||
pannerNode: audioContext.createStereoPanner(),
|
||||
};
|
||||
newTrack.gainNode.connect(newTrack.pannerNode);
|
||||
newTrack.pannerNode.connect(mainGainNode);
|
||||
newTrack.gainNode.gain.value = newTrack.volume;
|
||||
newTrack.pannerNode.pan.value = newTrack.pan;
|
||||
|
||||
appState.tracks.push(newTrack);
|
||||
renderApp();
|
||||
}
|
||||
|
||||
export function removeLastTrackFromState() {
|
||||
appState.tracks.pop();
|
||||
renderApp();
|
||||
}
|
||||
|
||||
export function updateTrackSample(trackId, samplePath) {
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
if (track) {
|
||||
track.samplePath = samplePath;
|
||||
track.name = samplePath.split("/").pop();
|
||||
}
|
||||
renderApp();
|
||||
}
|
||||
|
||||
export function toggleStepState(trackId, stepIndex) {
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
if (track) {
|
||||
track.steps[stepIndex] = !track.steps[stepIndex];
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTrackVolume(trackId, volume) {
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
const audioContext = getAudioContext();
|
||||
if (track) {
|
||||
const clampedVolume = Math.max(0, Math.min(1.5, volume));
|
||||
track.volume = clampedVolume;
|
||||
if (track.gainNode) {
|
||||
track.gainNode.gain.setValueAtTime(
|
||||
clampedVolume,
|
||||
audioContext.currentTime
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTrackPan(trackId, pan) {
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
const audioContext = getAudioContext();
|
||||
if (track) {
|
||||
const clampedPan = Math.max(-1, Math.min(1, pan));
|
||||
track.pan = clampedPan;
|
||||
if (track.pannerNode) {
|
||||
track.pannerNode.pan.setValueAtTime(clampedPan, audioContext.currentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
// js/ui.js
|
||||
import {
|
||||
appState,
|
||||
toggleStepState,
|
||||
updateTrackSample,
|
||||
updateTrackVolume,
|
||||
updateTrackPan,
|
||||
} from "./state.js";
|
||||
import { playSample } from "./audio.js";
|
||||
import { getTotalSteps } from "./utils.js";
|
||||
import { loadProjectFromServer } from "./file.js"; // (CORREÇÃO) Importa a função que faltava
|
||||
|
||||
// RENDERIZAÇÃO PRINCIPAL
|
||||
export function renderApp() {
|
||||
const trackContainer = document.getElementById("track-container");
|
||||
trackContainer.innerHTML = "";
|
||||
appState.tracks.forEach((trackData) => {
|
||||
const trackLane = document.createElement("div");
|
||||
trackLane.className = "track-lane";
|
||||
trackLane.dataset.trackId = trackData.id;
|
||||
|
||||
trackLane.innerHTML = `
|
||||
<div class="track-info"><i class="fa-solid fa-gear"></i><div class="track-mute"></div><span class="track-name">${trackData.name}</span></div>
|
||||
<div class="track-controls">
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="volume" data-track-id="${trackData.id}">
|
||||
<div class="knob-indicator"></div>
|
||||
</div>
|
||||
<span>VOL</span>
|
||||
</div>
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="pan" data-track-id="${trackData.id}">
|
||||
<div class="knob-indicator"></div>
|
||||
</div>
|
||||
<span>PAN</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-sequencer"></div>
|
||||
`;
|
||||
|
||||
trackLane.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
trackLane.classList.add("drag-over");
|
||||
});
|
||||
trackLane.addEventListener("dragleave", () =>
|
||||
trackLane.classList.remove("drag-over")
|
||||
);
|
||||
trackLane.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
trackLane.classList.remove("drag-over");
|
||||
const filePath = e.dataTransfer.getData("text/plain");
|
||||
if (filePath) {
|
||||
updateTrackSample(trackData.id, filePath);
|
||||
}
|
||||
});
|
||||
|
||||
trackContainer.appendChild(trackLane);
|
||||
|
||||
const volumeKnob = trackLane.querySelector(".knob[data-control='volume']");
|
||||
addKnobInteraction(volumeKnob);
|
||||
updateKnobVisual(volumeKnob, "volume");
|
||||
|
||||
const panKnob = trackLane.querySelector(".knob[data-control='pan']");
|
||||
addKnobInteraction(panKnob);
|
||||
updateKnobVisual(panKnob, "pan");
|
||||
});
|
||||
redrawSequencer();
|
||||
}
|
||||
|
||||
export function redrawSequencer() {
|
||||
const beatsPerBar =
|
||||
parseInt(document.getElementById("compasso-a-input").value, 10) || 4;
|
||||
const totalSteps = getTotalSteps();
|
||||
document.querySelectorAll(".step-sequencer").forEach((container) => {
|
||||
const parentTrackElement = container.closest(".track-lane");
|
||||
const trackId = parentTrackElement.dataset.trackId;
|
||||
const trackData = appState.tracks.find((t) => t.id == trackId);
|
||||
if (trackData && trackData.steps.length !== totalSteps) {
|
||||
const newStepsState = new Array(totalSteps).fill(false);
|
||||
for (let i = 0; i < Math.min(trackData.steps.length, totalSteps); i++) {
|
||||
newStepsState[i] = trackData.steps[i];
|
||||
}
|
||||
trackData.steps = newStepsState;
|
||||
}
|
||||
container.innerHTML = "";
|
||||
for (let i = 0; i < totalSteps; i++) {
|
||||
const stepWrapper = document.createElement("div");
|
||||
stepWrapper.className = "step-wrapper";
|
||||
const stepElement = document.createElement("div");
|
||||
stepElement.className = "step";
|
||||
if (trackData && trackData.steps[i] === true) {
|
||||
stepElement.classList.add("active");
|
||||
}
|
||||
stepElement.addEventListener("click", () => {
|
||||
toggleStepState(trackData.id, i);
|
||||
stepElement.classList.toggle("active");
|
||||
if (trackData && trackData.samplePath) {
|
||||
playSample(trackData.samplePath, trackData.id);
|
||||
}
|
||||
});
|
||||
const groupIndex = Math.floor(i / beatsPerBar);
|
||||
if (groupIndex % 2 === 0) {
|
||||
stepElement.classList.add("step-dark");
|
||||
}
|
||||
if (i > 0 && i % beatsPerBar === 0) {
|
||||
const marker = document.createElement("div");
|
||||
marker.className = "step-marker";
|
||||
marker.textContent = i;
|
||||
stepWrapper.appendChild(marker);
|
||||
}
|
||||
stepWrapper.appendChild(stepElement);
|
||||
container.appendChild(stepWrapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// LÓGICA DE INTERAÇÃO DOS KNOBS
|
||||
function addKnobInteraction(knobElement) {
|
||||
const controlType = knobElement.dataset.control;
|
||||
knobElement.addEventListener("mousedown", (e) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const defaultValue = controlType === "volume" ? 0.8 : 0.0;
|
||||
if (controlType === "volume") {
|
||||
updateTrackVolume(trackId, defaultValue);
|
||||
} else {
|
||||
updateTrackPan(trackId, defaultValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
knobElement.addEventListener("mousedown", (e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
if (!track) return;
|
||||
const startY = e.clientY;
|
||||
const startValue = controlType === "volume" ? track.volume : track.pan;
|
||||
document.body.classList.add("knob-dragging");
|
||||
function onMouseMove(moveEvent) {
|
||||
const deltaY = startY - moveEvent.clientY;
|
||||
const sensitivity = controlType === "volume" ? 150 : 200;
|
||||
const newValue = startValue + deltaY / sensitivity;
|
||||
if (controlType === "volume") {
|
||||
updateTrackVolume(trackId, newValue);
|
||||
} else {
|
||||
updateTrackPan(trackId, newValue);
|
||||
}
|
||||
}
|
||||
function onMouseUp() {
|
||||
document.body.classList.remove("knob-dragging");
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
}
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
});
|
||||
knobElement.addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
if (!track) return;
|
||||
const step = 0.05;
|
||||
const direction = e.deltaY < 0 ? 1 : -1;
|
||||
if (controlType === "volume") {
|
||||
const newValue = track.volume + direction * step;
|
||||
updateTrackVolume(trackId, newValue);
|
||||
} else {
|
||||
const newValue = track.pan + direction * step;
|
||||
updateTrackPan(trackId, newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateKnobVisual(knobElement, controlType) {
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const track = appState.tracks.find((t) => t.id == trackId);
|
||||
if (!track) return;
|
||||
const indicator = knobElement.querySelector(".knob-indicator");
|
||||
if (!indicator) return;
|
||||
const minAngle = -135;
|
||||
const maxAngle = 135;
|
||||
let percentage = 0.5;
|
||||
let title = "";
|
||||
if (controlType === "volume") {
|
||||
const value = track.volume;
|
||||
const clampedValue = Math.max(0, Math.min(1.5, value));
|
||||
percentage = clampedValue / 1.5;
|
||||
title = `Volume: ${Math.round(clampedValue * 100)}%`;
|
||||
} else {
|
||||
const value = track.pan;
|
||||
const clampedValue = Math.max(-1, Math.min(1, value));
|
||||
percentage = (clampedValue + 1) / 2;
|
||||
const panDisplay = Math.round(clampedValue * 100);
|
||||
title = `Pan: ${
|
||||
panDisplay === 0
|
||||
? "Centro"
|
||||
: panDisplay < 0
|
||||
? `${-panDisplay} L`
|
||||
: `${panDisplay} R`
|
||||
}`;
|
||||
}
|
||||
const angle = minAngle + percentage * (maxAngle - minAngle);
|
||||
indicator.style.transform = `translateX(-50%) rotate(${angle}deg)`;
|
||||
knobElement.title = title;
|
||||
}
|
||||
|
||||
export function highlightStep(stepIndex, isActive) {
|
||||
if (stepIndex < 0) return;
|
||||
document.querySelectorAll(".track-lane").forEach((track) => {
|
||||
const stepWrapper = track.querySelector(
|
||||
`.step-sequencer .step-wrapper:nth-child(${stepIndex + 1})`
|
||||
);
|
||||
if (stepWrapper) {
|
||||
const stepElement = stepWrapper.querySelector(".step");
|
||||
if (stepElement) {
|
||||
stepElement.classList.toggle("playing", isActive);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// LÓGICA DA SIDEBAR E MODAL
|
||||
export async function loadAndRenderSampleBrowser() {
|
||||
const browserContent = document.getElementById("browser-content");
|
||||
try {
|
||||
const response = await fetch("metadata/samples-manifest.json");
|
||||
if (!response.ok) {
|
||||
throw new Error("Arquivo samples-manifest.json não encontrado.");
|
||||
}
|
||||
const fileTree = await response.json();
|
||||
renderFileTree(fileTree, browserContent, "src/samples");
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar samples:", error);
|
||||
browserContent.innerHTML = `<p style="color:var(--accent-red); padding: 10px;">${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderFileTree(tree, parentElement, currentPath) {
|
||||
parentElement.innerHTML = "";
|
||||
const ul = document.createElement("ul");
|
||||
const sortedKeys = Object.keys(tree).sort((a, b) => {
|
||||
const aIsFile = tree[a]._isFile;
|
||||
const bIsFile = tree[b]._isFile;
|
||||
if (aIsFile === bIsFile) return a.localeCompare(b);
|
||||
return aIsFile ? 1 : -1;
|
||||
});
|
||||
for (const key of sortedKeys) {
|
||||
const node = tree[key];
|
||||
const li = document.createElement("li");
|
||||
const newPath = `${currentPath}/${key}`;
|
||||
if (node._isFile) {
|
||||
li.innerHTML = `<i class="fa-solid fa-file-audio"></i> ${key}`;
|
||||
li.setAttribute("draggable", true);
|
||||
li.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
playSample(newPath, null);
|
||||
});
|
||||
li.addEventListener("dragstart", (e) => {
|
||||
e.dataTransfer.setData("text/plain", newPath);
|
||||
e.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
ul.appendChild(li);
|
||||
} else {
|
||||
li.className = "directory";
|
||||
li.innerHTML = `<i class="fa-solid fa-folder"></i> ${key}`;
|
||||
const nestedUl = document.createElement("ul");
|
||||
renderFileTree(node, nestedUl, newPath);
|
||||
li.appendChild(nestedUl);
|
||||
li.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
li.classList.toggle("open");
|
||||
});
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
parentElement.appendChild(ul);
|
||||
}
|
||||
|
||||
export async function showOpenProjectModal() {
|
||||
const openProjectModal = document.getElementById("open-project-modal");
|
||||
const serverProjectsList = document.getElementById("server-projects-list");
|
||||
serverProjectsList.innerHTML = "<p>Carregando...</p>";
|
||||
openProjectModal.classList.add("visible");
|
||||
try {
|
||||
const response = await fetch("metadata/mmp-manifest.json");
|
||||
if (!response.ok)
|
||||
throw new Error("Arquivo mmp-manifest.json não encontrado.");
|
||||
const projects = await response.json();
|
||||
|
||||
serverProjectsList.innerHTML = "";
|
||||
if (projects.length === 0) {
|
||||
serverProjectsList.innerHTML =
|
||||
'<p style="color:var(--text-dark);">Nenhum projeto encontrado no servidor.</p>';
|
||||
}
|
||||
|
||||
projects.forEach((projectName) => {
|
||||
const projectItem = document.createElement("div");
|
||||
projectItem.className = "project-item";
|
||||
projectItem.textContent = projectName;
|
||||
projectItem.addEventListener("click", async () => {
|
||||
const success = await loadProjectFromServer(projectName);
|
||||
if (success) {
|
||||
closeOpenProjectModal();
|
||||
}
|
||||
});
|
||||
serverProjectsList.appendChild(projectItem);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar lista de projetos:", error);
|
||||
serverProjectsList.innerHTML = `<p style="color:var(--accent-red);">${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function closeOpenProjectModal() {
|
||||
const openProjectModal = document.getElementById("open-project-modal");
|
||||
openProjectModal.classList.remove("visible");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// js/utils.js
|
||||
|
||||
export function getTotalSteps() {
|
||||
const compassoAInput = document.getElementById("compasso-a-input");
|
||||
const compassoBInput = document.getElementById("compasso-b-input");
|
||||
const beatsPerBar = parseInt(compassoAInput.value, 10) || 4;
|
||||
const noteValue = parseInt(compassoBInput.value, 10) || 4;
|
||||
const subdivisions = Math.round(16 / noteValue);
|
||||
return beatsPerBar * subdivisions;
|
||||
}
|
||||
|
||||
export function enforceNumericInput(event) {
|
||||
event.target.value = event.target.value.replace(/[^0-9]/g, "");
|
||||
}
|
||||
|
||||
export function adjustValue(inputElement, step) {
|
||||
let currentValue = parseInt(inputElement.value, 10) || 0;
|
||||
let min = parseInt(inputElement.dataset.min, 10);
|
||||
let max = parseInt(inputElement.dataset.max, 10);
|
||||
let newValue = currentValue + step;
|
||||
if (!isNaN(min) && newValue < min) newValue = min;
|
||||
if (!isNaN(max) && newValue > max) newValue = max;
|
||||
inputElement.value = newValue;
|
||||
|
||||
// Dispara um evento 'input' para que outros listeners (como o que redesenha o sequenciador) sejam acionados.
|
||||
inputElement.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
+63
-48
@@ -1,108 +1,123 @@
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var fileItemsMMP = document.querySelectorAll('#file-list-mmp .file-item');
|
||||
var fileItemsWAV = document.querySelectorAll('#file-list-wav .file-wav-item');
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var fileItemsMMP = document.querySelectorAll("#file-list-mmp .file-item");
|
||||
var fileItemsWAV = document.querySelectorAll("#file-list-wav .file-wav-item");
|
||||
|
||||
// Função para exibir apenas os arquivos WAV correspondentes aos instrumentos encontrados nos projetos MMP
|
||||
function showWAVFilesByInstrument(selectedInstrument) {
|
||||
fileItemsWAV.forEach(function(item) {
|
||||
var fileName = item.getAttribute('data-file').toLowerCase();
|
||||
fileItemsWAV.forEach(function (item) {
|
||||
var fileName = item.getAttribute("data-file").toLowerCase();
|
||||
if (fileName.includes(selectedInstrument)) {
|
||||
item.style.display = 'block';
|
||||
item.style.display = "block";
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
item.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Buscar por instrumento usando o campo de entrada para MMP
|
||||
var searchButton = document.getElementById('search-button');
|
||||
searchButton.addEventListener('click', function() {
|
||||
var searchInput = document.getElementById('instrument-search').value.trim().toLowerCase();
|
||||
if (searchInput === '') {
|
||||
var searchButton = document.getElementById("search-button");
|
||||
searchButton.addEventListener("click", function () {
|
||||
var searchInput = document
|
||||
.getElementById("instrument-search")
|
||||
.value.trim()
|
||||
.toLowerCase();
|
||||
if (searchInput === "") {
|
||||
resetSearch();
|
||||
return;
|
||||
}
|
||||
fileItemsMMP.forEach(function(item) {
|
||||
var instrumentsList = item.querySelectorAll('.instrument-list .instrument-name');
|
||||
fileItemsMMP.forEach(function (item) {
|
||||
var instrumentsList = item.querySelectorAll(
|
||||
".instrument-list .instrument-name"
|
||||
);
|
||||
var hasInstrument = false;
|
||||
instrumentsList.forEach(function(instrument) {
|
||||
var instrumentName = instrument.getAttribute('data-instrument').toLowerCase();
|
||||
instrumentsList.forEach(function (instrument) {
|
||||
var instrumentName = instrument
|
||||
.getAttribute("data-instrument")
|
||||
.toLowerCase();
|
||||
if (instrumentName.includes(searchInput)) {
|
||||
hasInstrument = true;
|
||||
}
|
||||
});
|
||||
if (hasInstrument) {
|
||||
item.style.display = 'block';
|
||||
item.style.display = "block";
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
item.style.display = "none";
|
||||
}
|
||||
});
|
||||
// Exibir a lista de arquivos MMP após a busca
|
||||
document.getElementById('file-list-mmp').style.display = 'block';
|
||||
document.getElementById("file-list-mmp").style.display = "block";
|
||||
});
|
||||
|
||||
// Buscar por nome de arquivo WAV usando o campo de entrada
|
||||
var searchWavButton = document.getElementById('search-wav-button');
|
||||
searchWavButton.addEventListener('click', function() {
|
||||
var searchInput = document.getElementById('file-wav-search').value.trim().toLowerCase();
|
||||
if (searchInput === '') {
|
||||
var searchWavButton = document.getElementById("search-wav-button");
|
||||
searchWavButton.addEventListener("click", function () {
|
||||
var searchInput = document
|
||||
.getElementById("file-wav-search")
|
||||
.value.trim()
|
||||
.toLowerCase();
|
||||
if (searchInput === "") {
|
||||
resetSearch();
|
||||
return;
|
||||
}
|
||||
fileItemsWAV.forEach(function(item) {
|
||||
var fileName = item.querySelector('span').textContent.toLowerCase();
|
||||
fileItemsWAV.forEach(function (item) {
|
||||
var fileName = item.querySelector("span").textContent.toLowerCase();
|
||||
if (fileName.includes(searchInput)) {
|
||||
item.style.display = 'block';
|
||||
item.style.display = "block";
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
item.style.display = "none";
|
||||
}
|
||||
});
|
||||
// Exibir a lista de arquivos WAV após a busca
|
||||
document.getElementById('file-list-wav').style.display = 'block';
|
||||
document.getElementById("file-list-wav").style.display = "block";
|
||||
});
|
||||
|
||||
// Buscar por instrumento ou nome de arquivo WAV usando o campo de entrada combinado
|
||||
var searchBothButton = document.getElementById('search-both-button');
|
||||
searchBothButton.addEventListener('click', function() {
|
||||
var searchInput = document.getElementById('instrument-wav-search').value.trim().toLowerCase();
|
||||
if (searchInput === '') {
|
||||
var searchBothButton = document.getElementById("search-both-button");
|
||||
searchBothButton.addEventListener("click", function () {
|
||||
var searchInput = document
|
||||
.getElementById("instrument-wav-search")
|
||||
.value.trim()
|
||||
.toLowerCase();
|
||||
if (searchInput === "") {
|
||||
resetSearch();
|
||||
return;
|
||||
}
|
||||
// Buscar em arquivos MMP por instrumento
|
||||
fileItemsMMP.forEach(function(item) {
|
||||
var instrumentsList = item.querySelectorAll('.instrument-list .instrument-name');
|
||||
fileItemsMMP.forEach(function (item) {
|
||||
var instrumentsList = item.querySelectorAll(
|
||||
".instrument-list .instrument-name"
|
||||
);
|
||||
var hasInstrument = false;
|
||||
instrumentsList.forEach(function(instrument) {
|
||||
var instrumentName = instrument.getAttribute('data-instrument').toLowerCase();
|
||||
instrumentsList.forEach(function (instrument) {
|
||||
var instrumentName = instrument
|
||||
.getAttribute("data-instrument")
|
||||
.toLowerCase();
|
||||
if (instrumentName.includes(searchInput)) {
|
||||
hasInstrument = true;
|
||||
// Mostrar arquivo WAV correspondente ao instrumento
|
||||
showWAVFilesByInstrument(item.getAttribute('data-file'));
|
||||
showWAVFilesByInstrument(item.getAttribute("data-file"));
|
||||
}
|
||||
});
|
||||
if (hasInstrument) {
|
||||
item.style.display = 'block';
|
||||
item.style.display = "block";
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
item.style.display = "none";
|
||||
}
|
||||
});
|
||||
// Exibir as listas de arquivos após a busca
|
||||
document.getElementById('file-list-mmp').style.display = 'block';
|
||||
document.getElementById('file-list-wav').style.display = 'block';
|
||||
document.getElementById("file-list-mmp").style.display = "block";
|
||||
document.getElementById("file-list-wav").style.display = "block";
|
||||
});
|
||||
|
||||
// Função para resetar a busca
|
||||
function resetSearch() {
|
||||
fileItemsMMP.forEach(function(item) {
|
||||
item.style.display = 'block';
|
||||
fileItemsMMP.forEach(function (item) {
|
||||
item.style.display = "block";
|
||||
});
|
||||
fileItemsWAV.forEach(function(item) {
|
||||
item.style.display = 'block';
|
||||
fileItemsWAV.forEach(function (item) {
|
||||
item.style.display = "block";
|
||||
});
|
||||
document.getElementById('file-list-mmp').style.display = 'none';
|
||||
document.getElementById('file-list-wav').style.display = 'none';
|
||||
document.getElementById("file-list-mmp").style.display = "none";
|
||||
document.getElementById("file-list-wav").style.display = "none";
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user