LevyLL Membro Novato |
# há 12 minutos
Metrônomo
:root{ --wood-dark:#181310; --wood-mid:#241b16; --wood-line:#3a2c22; --brass:#c9975a; --brass-bright:#e8b877; --flamenco-red:#9a3b3b; --cream:#ece3d6; --cream-dim:#9c8f7f; --danger:#c25454; --tech-symbol:#d4a066; } *{box-sizing:border-box; -webkit-tap-highlight-color:transparent;} html,body{min-height:100%;} body{ margin:0; min-height:100vh; min-height:100dvh; background: radial-gradient(120% 90% at 50% -10%, #2a1f18 0%, var(--wood-dark) 55%); color:var(--cream); font-family:'Iowan Old Style','Georgia',serif; display:flex; align-items:flex-start; justify-content:center; padding:24px; overflow-x:hidden; overflow-y:auto; } .rig{width:100%; max-width:420px;} .eyebrow{text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; letter-spacing:.22em; text-transform:uppercase; font-size:11px; color:var(--brass); margin-bottom:6px;} h1{text-align:center; font-weight:400; font-size:22px; margin:0 0 18px; color:var(--cream); letter-spacing:.01em;}
.tabs{display:flex; gap:8px; background:var(--wood-mid); border:1px solid var(--wood-line); border-radius:12px; padding:4px; margin-bottom:24px;} .tab{flex:1; text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:12px; letter-spacing:.08em; text-transform:uppercase; padding:10px 8px; border-radius:9px; color:var(--cream-dim); cursor:pointer; user-select:none;} .tab.active{background:rgba(201,151,90,.16); color:var(--brass-bright);} .view{display:none;} .view.active{display:block;}
.rosette-wrap{position:relative; width:260px; height:260px; margin:0 auto 22px;} svg.rings{position:absolute; inset:0; width:100%; height:100%; transform:rotate(-90deg);} .ring-bg{fill:none; stroke:var(--wood-line); stroke-width:2;} .ring-fg{fill:none; stroke:var(--brass); stroke-width:2; stroke-linecap:round; transition:stroke-dashoffset .08s linear;}
.soundhole{position:absolute; inset:24px; border-radius:50%; background:radial-gradient(circle at 50% 42%, #120d0a 0%, #0a0706 70%); border:1px solid var(--wood-line); display:flex; align-items:center; justify-content:center; flex-direction:column; box-shadow:inset 0 0 30px rgba(0,0,0,.6);} .pick{position:absolute; top:50%; left:50%; width:10px; height:52px; margin-left:-5px; margin-top:-96px; transform-origin:50% 96px; transition:transform .01s linear; pointer-events:none;} .pick svg{width:100%; height:100%; display:block; overflow:visible;}
.bpm-display{position:relative; text-align:center;} .bpm-num{font-size:56px; line-height:1; color:var(--brass-bright); font-variant-numeric:tabular-nums; letter-spacing:.01em;} .bpm-label{font-family:'Helvetica Neue',Arial,sans-serif; font-size:10px; letter-spacing:.2em; text-transform:uppercase; color:var(--cream-dim); margin-top:2px;} .beat-label{font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; letter-spacing:.15em; color:var(--brass); margin-top:8px; min-height:14px;}
.bpm-row{display:flex; align-items:center; gap:10px; margin-bottom:10px;} .step-btn{flex:0 0 auto; width:46px; height:46px; border-radius:50%; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--brass-bright); font-size:20px; font-family:'Helvetica Neue',Arial,sans-serif; display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none;} .step-btn:active{background:#2f231b;}
.jump-row{display:flex; justify-content:center; gap:8px; margin-bottom:16px;} .jump-btn{flex:0 0 auto; min-width:52px; padding:8px 10px; border-radius:8px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream-dim); font-size:12px; font-family:'Helvetica Neue',Arial,sans-serif; text-align:center; cursor:pointer; user-select:none;} .jump-btn:active{background:#2f231b; color:var(--brass-bright);}
input[type=range]{-webkit-appearance:none; flex:1; height:2px; background:var(--wood-line); border-radius:2px; outline:none;} input[type=range]::-webkit-slider-thumb{-webkit-appearance:none; width:16px; height:16px; border-radius:50%; background:var(--brass-bright); border:2px solid var(--wood-dark); cursor:pointer; margin-top:-1px;} input[type=range]::-moz-range-thumb{width:16px; height:16px; border-radius:50%; background:var(--brass-bright); border:2px solid var(--wood-dark); cursor:pointer;}
.section-label{font-family:'Helvetica Neue',Arial,sans-serif; font-size:10px; letter-spacing:.2em; text-transform:uppercase; color:var(--cream-dim); margin:18px 0 8px;} .pills{display:flex; gap:8px; flex-wrap:wrap; justify-content:center;} .pill{font-family:'Helvetica Neue',Arial,sans-serif; font-size:12px; padding:9px 14px; border-radius:20px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream-dim); cursor:pointer; user-select:none; transition:background .15s, color .15s, border-color .15s;} .pill.active{background:rgba(201,151,90,.14); border-color:var(--brass); color:var(--brass-bright);}
.controls{display:flex; gap:12px; margin-top:24px;} .play-btn{flex:1; text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:14px; letter-spacing:.1em; text-transform:uppercase; padding:16px; border-radius:10px; border:1px solid var(--brass); background:linear-gradient(180deg, rgba(201,151,90,.22), rgba(201,151,90,.08)); color:var(--brass-bright); cursor:pointer; user-select:none;} .play-btn.playing{border-color:var(--flamenco-red); background:linear-gradient(180deg, rgba(154,59,59,.28), rgba(154,59,59,.1)); color:#e79a9a;} .tap-btn{flex:1; font-family:'Helvetica Neue',Arial,sans-serif; font-size:14px; letter-spacing:.1em; text-transform:uppercase; padding:16px; border-radius:10px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream); cursor:pointer; user-select:none;} .tap-btn:active, .play-btn:active{transform:scale(.98);}
.footnote{text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; color:var(--cream-dim); margin-top:20px; line-height:1.5;} .footnote b{color:var(--brass); font-weight:600;}
@media (max-width:380px){.rosette-wrap{width:220px; height:220px;} .bpm-num{font-size:46px;}}
.note-status{text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:13px; color:var(--cream-dim); margin-bottom:18px; min-height:18px;} .note-status.playing{color:var(--brass-bright);}
.octave-row{display:flex; align-items:center; justify-content:center; gap:18px; margin-bottom:6px;} .octave-btn{width:44px; height:44px; border-radius:50%; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--brass-bright); font-size:22px; font-family:'Helvetica Neue',Arial,sans-serif; display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none;} .octave-btn:active{background:#2f231b;} .octave-display{min-width:60px; text-align:center; font-size:22px; color:var(--brass-bright); font-family:'Iowan Old Style','Georgia',serif;}
.modifier-row{display:flex; gap:8px; margin-bottom:6px;} .modifier-btn{flex:1; text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:13px; padding:11px 6px; border-radius:8px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream-dim); cursor:pointer; user-select:none;} .modifier-btn.active{border-color:var(--brass); color:var(--brass-bright); background:rgba(201,151,90,.18);}
.natural-note-row{display:flex; gap:8px; margin-bottom:8px;} .natural-note-row:last-of-type{margin-bottom:6px;} .natural-note-btn{flex:1; text-align:center; font-family:'Iowan Old Style','Georgia',serif; font-size:16px; padding:16px 4px; border-radius:9px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream); cursor:pointer; user-select:none; transition:all .12s;} .natural-note-btn:active{transform:scale(.96);} .natural-note-btn.playing{border-color:var(--brass); color:var(--brass-bright); background:rgba(201,151,90,.2);}
.tab-help{font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; color:var(--cream-dim); line-height:1.6; margin-bottom:10px;} .tab-help code{background:var(--wood-mid); padding:1px 5px; border-radius:4px; color:var(--brass-bright); font-size:11px;} .tab-help .tech-list{margin-top:6px; font-size:10px;} .tab-help .tech-list b{color:var(--tech-symbol);} .tab-actions{display:flex; gap:6px; margin-bottom:8px; flex-wrap:wrap;} .tab-action-btn{font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; padding:8px 10px; border-radius:8px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream-dim); cursor:pointer; user-select:none; transition:all .15s;} .tab-action-btn:active{transform:scale(.97);} .tab-action-btn.danger{border-color:var(--danger); color:var(--danger);} .tab-action-btn.success{border-color:rgba(143,191,130,.6); color:#8fbf82;}
/* ===== CONTAINER LADO A LADO ===== */ .tab-top-row { display: flex; gap: 12px; margin-bottom: 14px; align-items: stretch; } .tab-top-row > * { flex: 1; min-width: 0; }
/* ===== MINI METRÔNOMO (sem botão tocar) ===== */ .mini-metronome { background: var(--wood-mid); border: 1px solid var(--wood-line); border-radius: 10px; padding: 4px 10px; display: flex; flex-direction: column; justify-content: center; align-items: center; } .mini-metronome .mini-bpm-num { font-size: 30px; font-weight: 400; color: var(--brass-bright); font-variant-numeric:tabular-nums; font-family: 'Helvetica Neue', Arial, sans-serif; line-height: 1.1; margin-bottom: 1px; } .mini-metronome .mini-buttons { display: flex; gap: 4px; flex-wrap: wrap; justify-content: center; } .mini-metronome .mini-buttons-row { display: flex; gap: 4px; justify-content: center; width: 100%; } .mini-metronome .mini-btn { font-family: 'Helvetica Neue', Arial, sans-serif; font-size: 9px; padding: 2px 8px; border-radius: 8px; border: 1px solid var(--wood-line); background: transparent; color: var(--cream-dim); cursor: pointer; user-select: none; transition: all .15s; text-align: center; flex: 1; } .mini-metronome .mini-btn:active { background: rgba(201,151,90,.2); color: var(--brass-bright); }
/* ===== EXERCÍCIOS ===== */ .exercises-container { background: var(--wood-mid); border: 1px solid var(--wood-line); border-radius: 10px; padding: 4px 10px; display: flex; flex-direction: column; justify-content: center; align-items: center; } .exercises-container .section-label { margin: 0 0 4px 0; font-size: 9px; letter-spacing: .15em; line-height: 1.2; text-align: center; width: 100%; } .exercises-row { display: flex; gap: 5px; flex-wrap: wrap; -webkit-overflow-scrolling: touch; justify-content: center; width: 100%; } .exercise-pill { flex: 1; min-width: 32px; max-width: 48px; font-family: 'Helvetica Neue', Arial, sans-serif; font-size: 11px; padding: 4px 4px; border-radius: 14px; border: 1px solid var(--wood-line); background: transparent; color: var(--cream-dim); cursor: pointer; user-select: none; white-space: nowrap; transition: all .15s; text-align: center; } .exercise-pill:active { transform: scale(.97); } .exercise-pill.loaded { border-color: var(--brass); color: var(--brass-bright); background: rgba(201,151,90,.14); } .exercise-empty { font-family: 'Helvetica Neue', Arial, sans-serif; font-size: 10px; color: var(--cream-dim); padding: 2px 0; }
/* ===== RESTO ===== */ textarea#tabInput{width:100%; min-height:80px; background:var(--wood-mid); border:1px solid var(--wood-line); border-radius:8px; color:var(--cream); font-family:'SF Mono','Courier New',monospace; font-size:13px; padding:10px; resize:vertical; margin-bottom:14px;} textarea#tabInput:focus{outline:1px solid var(--brass);}
.tab-render-wrap{position:relative; overflow:hidden; border-radius:10px; margin-bottom:12px;} .tab-render{background:var(--wood-mid); border:1px solid var(--wood-line); border-radius:10px; padding:12px 10px; overflow-x:auto; transition:transform 0.12s ease-out;} .tab-line{font-family:'SF Mono','Courier New',monospace; font-size:13px; line-height:1.9; white-space:pre; color:var(--cream-dim); letter-spacing:1px;} .tab-line .string-label{color:var(--brass);} .tab-line .fret-active{color:#000; background:rgba(201,151,90,.55); border-radius:3px; font-weight:bold;} .tab-line .tech{color:#000; background:rgba(212,160,102,.55); border-radius:3px; font-weight:bold; padding:0 1px;} .tab-line .col-current{background:rgba(201,151,90,.16);} .tab-line .tab-cell{display:inline-block;text-align:center;white-space:pre;} .tab-line .phrase-bar{font-weight:900;} .tab-empty{font-family:'Helvetica Neue',Arial,sans-serif; font-size:12px; color:var(--cream-dim); text-align:center; padding:8px 0;} .tab-controls{display:flex; gap:12px; margin-bottom:16px; flex-wrap:wrap;} .autoscroll-btn{flex:0 0 auto; text-align:center; line-height:1.35; font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; padding:8px 10px; border-radius:8px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream-dim); cursor:pointer; user-select:none;} .autoscroll-btn.active{border-color:var(--brass); color:var(--brass-bright); background:rgba(201,151,90,.14);} .tone-row{display:flex; gap:8px; margin-bottom:16px;} .tone-pill{flex:1; text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; padding:8px 6px; border-radius:8px; border:1px solid var(--wood-line); background:var(--wood-mid); color:var(--cream-dim); cursor:pointer; user-select:none;} .tone-pill.active{border-color:var(--brass); color:var(--brass-bright); background:rgba(201,151,90,.14);} .save-status{text-align:center; font-family:'Helvetica Neue',Arial,sans-serif; font-size:10px; letter-spacing:.08em; color:var(--cream-dim); opacity:0; transition:opacity .3s; margin-top:-6px; margin-bottom:10px;} .save-status.show{opacity:1;}
#editorTitle{width:100%; background:var(--wood-mid); border:1px solid var(--wood-line); border-radius:8px; color:var(--cream); font-family:'Helvetica Neue',Arial,sans-serif; font-size:16px; padding:10px 12px; margin-bottom:12px;} #editorTitle:focus{outline:1px solid var(--brass);} textarea#editorInput{width:100%; min-height:120px; background:var(--wood-mid); border:1px solid var(--wood-line); border-radius:8px; color:var(--cream); font-family:'SF Mono','Courier New',monospace; font-size:13px; padding:10px; resize:vertical; margin-bottom:14px; line-height:1.7;} textarea#editorInput:focus{outline:1px solid var(--brass);} .import-item{display:flex;align-items:flex-start;gap:8px;margin-bottom :10px;} .import-item .tab-action-btn{font-size:10px;padding:6px 8px;min-width:120px;text-align:center;} .import-item .editor-hint{margin:0;flex:1;} .editor-hint{font-family:'Helvetica Neue',Arial,sans-serif; font-size:11px; color:var(--cream-dim); margin:6px 2px 0; line-height:1.5;} .editor-hint b{color:var(--brass);} .editor-preview{background:var(--wood-mid); border:1px solid var(--wood-line); border-radius:10px; padding:14px 12px; margin-bottom:16px; overflow-x:auto;} .editor-preview .system{margin-bottom:16px;} .editor-preview .system:last-child{margin-bottom:0;} .editor-preview .tab-line{font-family:'SF Mono','Courier New',monospace; font-size:12px; line-height:1.8; white-space:pre; color:var(--cream-dim); letter-spacing:1px;} .editor-preview .tab-line .string-label{color:var(--brass);} .editor-preview .tab-line .fret-active{color:#000; background:rgba(201,151,90,.55); border-radius:3px; font-weight:bold;} .editor-preview .tab-line .tech{color:#000; background:rgba(212,160,102,.55); border-radius:3px; font-weight:bold; padding:0 1px;}
.editor-tablatura { color: #FFFFFF; } .editor-tablatura input, .editor-tablatura textarea { background-color: #241b16; color: #FFFFFF; border: 1px solid #3a2c22; font-family: 'Helvetica Neue', Arial, sans-serif; padding: 6px; margin: 4px 0; border-radius: 3px; } .editor-tablatura input::placeholder, .editor-tablatura textarea::placeholder { color: #F5EBE0; } .info-basica { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 6px; margin-bottom: 10px; width: 100%; box-sizing: border-box; } .info-basica input { width: 100%; min-width: 0; background-color: #241b16; color: #FFFFFF; border: 1px solid #3a2c22; padding: 8px 6px; border-radius: 3px; box-sizing: border-box; } .info-basica select, .select-basica { width: 100%; min-width: 0; background-color: #241b16; color: #FFFFFF; border: 1px solid #3a2c22; padding: 8px 6px; border-radius: 3px; box-sizing: border-box; font-family: 'Helvetica Neue', Arial, sans-serif; font-size: 14px; } .select-basica option{ background-color:#241b16; color:#FFFFFF; } #campo-observacao{ grid-column: 1 / -1; } .area-digitacao { width: 100%; background-color: #241b16; color: #FFFFFF; border: 1px solid #3a2c22; padding: 8px; border-radius: 3px; min-height: 180px; box-sizing: border-box; margin-bottom: 10px; } .info-estrutura { display: flex; gap: 2px; width: 100%; box-sizing: border-box; flex-wrap: wrap; } .info-estrutura input, .info-estrutura textarea { flex: 1; min-width: 120px; background-color: #795548; color: #FFFFFF; border: 1px solid #3E2723; padding: 8px 6px; border-radius: 3px; box-sizing: border-box; } .info-estrutura textarea { min-height: 60px; } .editor-tablatura input::placeholder, .editor-tablatura textarea::placeholder { color: #F5EBE0; }
/* ===== Modal de entrada ===== */ .credits-overlay{position:fixed; inset:0; z-index:1000; background:rgba(8,6,4,.78); display:flex; align-items:center; justify-content:center; padding:24px;} .credits-box{position:relative; max-width:360px; width:100%; background:linear-gradient(180deg, #241b16, #181310); border:1px solid var(--wood-line); border-radius:14px; padding:28px 22px 22px; box-shadow:0 20px 60px rgba(0,0,0,.5);} .credits-close{position:absolute; top:10px; right:10px; width:32px; height:32px; border-radius:50%; background:var(--wood-mid); border:1px solid var(--wood-line); color:var(--cream-dim); font-size:16px; display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none;} .credits-close:active{background:#2f231b;} .credits-text{font-family:'Helvetica Neue',Arial,sans-serif; font-size:13px; line-height:1.6; color:var(--cream-dim);} .credits-dontshow{display:flex; align-items:center; gap:8px; margin-top:18px; font-family:'Helvetica Neue',Arial,sans-serif; font-size:12px; color:var(--cream-dim); cursor:pointer; user-select:none;} .credits-dontshow input{width:16px; height:16px; accent-color:var(--brass);}
✕ A quem possa interessar, esse é um Metrônomo/Afinador/Editor de Tablatura com Exercícios Propostos para a fluidez da técnica Picado. Fica a seu critério e sua benevolência ajudar o idealizador com um PIX de qualquer valor para a compra de um violão novo, já que o atual está muito gasto e não compensa reparar/consertar. Chave PIX: levymeca@gmail.com, agradeço qualquer ajuda! Não exibir essa mensagem novamente
Estudo de Picado Metrônomo
Metrônomo Afinador Tablatura Editor
Picado 2 dedos (mão direita): os dedos indicador (i) e médio (m) se revezam nota a nota, nunca repetindo o mesmo dedo em sequência. Picado 3 dedos (mão direita): os dedos indicador (i), médio (m) e anelar se revezam nota a nota, nunca repetindo o mesmo dedo em sequência. Comece com 60–70 BPM e só aumente quando a passagem estiver fluída. O primeiro clique de cada grupo é acentuado — use-o como referência do dedo i. 92 BPM i · m
– +
−10 −5 +5 +10
Subdivisão (padrão de dedos) Semínima Colcheia Tercina Semicolcheia
Tocar
Toque numa nota pra ouvir
Som Nylon Aço Synth Puro
Oitava − 3 +
Alteração ♯ Sustenido ♭ Bemol
Nota Dó Ré Mi Fá Sol Lá Si
Escolha a oitava e, se quiser, sustenido ou bemol, depois toque na nota — ela toca em loop contínuo. Toque nela de novo (ou em outra) pra trocar/parar. Compare com a corda do violão até afinar de ouvido.
Formato corda.casa (corda 1 = Mi agudo, corda 6 = Mi grave), separado por espaço. Use - para silêncio. Para duas ou mais notas juntas, una-as com +: 6.0+5.2 → 6.0+5.2+4.2+3.1 assim, toca todas ao mesmo tempo. Para sinalizar o fim de um trecho ou frase utilize |, os limites das casas a direita ficarão em negrito. Ex.: 6.3|. Técnicas: h martelo / p puxada / / subida / \ descida / b dobradura / r soltar / ~ vibrato / x abafado Ex.: 5.2h4 5.4p2 3.0/5 3.5\2 4.3b5r3 1.0x
92 −1 +1 −5 +5 −10 +10
Exercícios propostos
????️ Limpar tudo Digite uma sequência acima para ver a tablatura Timbre Nylon Aço Synth Tocar passagem RolagemAutomática Salvo A velocidade da passagem segue o BPM e a subdivisão definidos na aba Metrônomo. Ajuste o tempo lá e volte aqui — a passagem acelera ou desacelera junto.
Toque para editar os campos #editorTitle::placeholder { color: #ffffff !important; opacity: 1; } Compasso 4/4 3/4 2/4 6/8 12/8 2/2 9/8 5/4 7/8 Tom C C# D D# E F F# G G# A A# B Cm C#m Dm D#m Em Fm F#m Gm G#m Am A#m Bm
Mesmo formato da Tablatura: corda.casa, + pra notas tocadas juntas, h p / \ b r ~ x pra técnicas.
???? Gerar tab vazia: cria uma tablatura com 150 posições, preencha primeiro a caixa de Afinação se não for a Padrão. ???? Gerar tab vazia (.txt) ????️ Limpar tudo
Pré-visualização Digite algo acima para ver a prévia Exportar ???? Exportar código (.txt) Exporta o código preenchido acima. ???? Exportar TAB (.txt) Exporta a tablatura com cabeçalho e TAB.
???? Exportar TAB (PDF) Gera um arquivo PDF (A4) com cabeçalho e TAB. ✂️ Exportar seleção Envia apenas o trecho selecionado no editor para a aba Tablatura. ???? Exportar tudo Envia todo o conteúdo do editor para a aba Tablatura.
Importar ???? Importar arquivo (.txt) Reconhece os arquivos .txt gerados pelo Editor, com ou sem cabeçalho, além de tablatura tradicional e código corda.casa.
(function(){ const creditsOverlay = document.getElementById('creditsOverlay'); const creditsClose = document.getElementById('creditsClose'); const creditsDontShow = document.getElementById('creditsDontShow'); if(creditsOverlay){ try{ if(localStorage.getItem('hideCreditsMessage') === 'true'){ creditsOverlay.style.display = 'none'; } }catch(e){} } if(creditsClose && creditsOverlay){ creditsClose.addEventListener('click', () => { creditsOverlay.style.display = 'none'; try{ if(creditsDontShow && creditsDontShow.checked){ localStorage.setItem('hideCreditsMessage', 'true'); } }catch(e){} }); }
// ===== REFERÊNCIAS ===== const bpmNum = document.getElementById('bpmNum'); const slider = document.getElementById('slider'); const minus = document.getElementById('minus'); const plus = document.getElementById('plus'); const playBtn = document.getElementById('playBtn'); const tapBtn = document.getElementById('tapBtn'); const pick = document.getElementById('pick'); const ringFg = document.getElementById('ringFg'); const beatLabel = document.getElementById('beatLabel'); const pills = document.querySelectorAll('.pill');
// Mini metrônomo const miniBpmNum = document.getElementById('miniBpmNum');
const R = 122; const CIRC = 2 * Math.PI * R; ringFg.style.strokeDasharray = CIRC; ringFg.style.strokeDashoffset = CIRC;
let bpm = 92, subdiv = 2, currentPattern = 'im', playing = false, audioCtx = null, nextNoteTime = 0.0, current16th = 0; let schedulerTimer = null, saveTimer = null; const lookahead = 25.0, scheduleAheadTime = 0.1; const fingerPatterns = {quarter:['i'], im:['i','m'], tercina:['i','m','i'], ima:['i','m','a'], sixteenth:['i','m','i','m'], ima4:['i','m','a','i']}; const patternToSubdiv = {quarter:1, im:2, tercina:3, ima:3, sixteenth:4, ima4:4};
// ===== FUNÇÕES BPM ===== function setBpm(v){ bpm = Math.max(30, Math.min(220, v)); bpmNum.textContent = bpm; slider.value = bpm; miniBpmNum.textContent = bpm; scheduleSave(); }
// ===== EVENTOS DO METRÔNOMO PRINCIPAL ===== slider.addEventListener('input', e => setBpm(parseInt(e.target.value, 10))); minus.addEventListener('click', () => setBpm(bpm - 1)); plus.addEventListener('click', () => setBpm(bpm + 1)); minus.addEventListener('touchstart', e => { e.preventDefault(); setBpm(bpm - 1); }, {passive:false}); plus.addEventListener('touchstart', e => { e.preventDefault(); setBpm(bpm + 1); }, {passive:false});
document.querySelectorAll('.jump-btn').forEach(btn => { const delta = parseInt(btn.dataset.jump, 10); btn.addEventListener('click', () => setBpm(bpm + delta)); btn.addEventListener('touchstart', e => { e.preventDefault(); setBpm(bpm + delta); }, {passive:false}); });
// ===== EVENTOS DO MINI METRÔNOMO ===== document.querySelectorAll('#miniMetronome .mini-btn[data-jump]').forEach(btn => { const delta = parseInt(btn.dataset.jump, 10); btn.addEventListener('click', () => setBpm(bpm + delta)); btn.addEventListener('touchstart', e => { e.preventDefault(); setBpm(bpm + delta); }, {passive:false}); });
// ===== SUBDIVISÃO / PADRÃO DE DEDOS ===== function setPattern(patternId) { currentPattern = patternId; subdiv = patternToSubdiv[patternId]; pills.forEach(p => p.classList.toggle('active', p.dataset.pattern === patternId)); beatLabel.textContent = fingerPatterns[patternId].join(' · '); scheduleSave(); } function setSubdiv(val){ // compatibilidade com chamadas antigas por número — usa o padrão padrão daquela subdivisão const fallback = Object.keys(patternToSubdiv).find(k => patternToSubdiv[k] === val) || 'im'; setPattern(fallback); }
pills.forEach(p => { p.addEventListener('click', () => setPattern(p.dataset.pattern)); });
// ===== ÁUDIO / PLAY ===== function ensureAudio(){ if(!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); if(audioCtx.state === 'suspended') audioCtx.resume(); }
function playClick(time, accent){ const osc = audioCtx.createOscillator(), gain = audioCtx.createGain(); osc.type = 'sine'; osc.frequency.value = accent ? 1500 : 1000; gain.gain.setValueAtTime(0.0001, time); gain.gain.exponentialRampToValueAtTime(accent ? 0.9 : 0.5, time + 0.002); gain.gain.exponentialRampToValueAtTime(0.0001, time + 0.05); osc.connect(gain); gain.connect(audioCtx.destination); osc.start(time); osc.stop(time + 0.06); }
let scheduledBeats = []; function scheduleNote(beatIndex, time){ const accent = (beatIndex % subdiv) === 0; playClick(time, accent); scheduledBeats.push({time, accent, beatIndex}); }
function nextNote(){ const sec = 60.0 / bpm / subdiv; nextNoteTime += sec; current16th = (current16th + 1) % subdiv; }
function scheduler(){ while(nextNoteTime < audioCtx.currentTime + scheduleAheadTime){ scheduleNote(current16th, nextNoteTime); nextNote(); } schedulerTimer = setTimeout(scheduler, lookahead); }
let animFrame; function animate(){ if(!playing) return; const now = audioCtx.currentTime; scheduledBeats = scheduledBeats.filter(b => b.time > now - 1); let last = null; for(const b of scheduledBeats){ if(b.time last.time)) last = b; } const beatPhase = (now / (60.0 / bpm)) % 1; pick.style.transform = `rotate(${Math.sin(beatPhase * Math.PI * 2) * 22}deg)`; ringFg.style.strokeDashoffset = CIRC * (1 - beatPhase); animFrame = requestAnimationFrame(animate); }
function start(){ ensureAudio(); playing = true; playBtn.textContent = 'Parar'; playBtn.classList.add('playing'); current16th = 0; nextNoteTime = audioCtx.currentTime + 0.05; scheduledBeats = []; scheduler(); animate(); }
function stop(){ playing = false; playBtn.textContent = 'Tocar'; playBtn.classList.remove('playing'); clearTimeout(schedulerTimer); cancelAnimationFrame(animFrame); pick.style.transform = 'rotate(0deg)'; ringFg.style.strokeDashoffset = CIRC; }
function togglePlay(){ if(playing) stop(); else start(); }
playBtn.addEventListener('click', togglePlay);
// ===== INICIALIZAÇÃO ===== beatLabel.textContent = fingerPatterns[currentPattern].join(' · '); setBpm(bpm);
// ===== TABS ===== document.querySelectorAll('.tab').forEach(t => { t.addEventListener('click', () => { document.querySelectorAll('.tab').forEach(x => x.classList.remove('active')); document.querySelectorAll('.view').forEach(v => v.classList.remove('active')); t.classList.add('active'); document.getElementById(t.dataset.view).classList.add('active'); if(t.dataset.view === 'metronomeView') { stopTuner(); stopTabPlayback(); } if(t.dataset.view !== 'metronomeView' && playing) stop(); if(t.dataset.view !== 'tunerView') stopTuner(); if(t.dataset.view !== 'tabView') stopTabPlayback(); }); });
// ===== AFINADOR (notas naturais + sustenido/bemol + oitava + timbre) ===== const noteStatus = document.getElementById('noteStatus'); const naturalBtns = document.querySelectorAll('.natural-note-btn'); const octaveDisplay = document.getElementById('octaveDisplay'); const octaveDownBtn = document.getElementById('octaveDown'), octaveUpBtn = document.getElementById('octaveUp'); const btnSustenido = document.getElementById('btnSustenido'), btnBemol = document.getElementById('btnBemol'); const refToneRow = document.getElementById('refToneRow'); const NOTE_SEMITONES = {C:0, D:2, E:4, F:5, G:7, A:9, B:11}, A4 = 440;
let refOctave = 3, refModifier = null, refTone = 'nylon'; let refCtx = null, refOsc1 = null, refOsc2 = null, refFilter = null, refGain = null, currentNoteKey = null;
function setOctave(v){ refOctave = Math.max(0, Math.min(7, v)); octaveDisplay.textContent = refOctave; } octaveDownBtn.addEventListener('click', () => setOctave(refOctave - 1)); octaveUpBtn.addEventListener('click', () => setOctave(refOctave + 1));
btnSustenido.addEventListener('click', () => { refModifier = refModifier === 'sustenido' ? null : 'sustenido'; btnSustenido.classList.toggle('active', refModifier === 'sustenido'); btnBemol.classList.remove('active'); }); btnBemol.addEventListener('click', () => { refModifier = refModifier === 'bemol' ? null : 'bemol'; btnBemol.classList.toggle('active', refModifier === 'bemol'); btnSustenido.classList.remove('active'); });
refToneRow.querySelectorAll('.tone-pill').forEach(p => { p.addEventListener('click', () => { refToneRow.querySelectorAll('.tone-pill').forEach(x => x.classList.remove('active')); p.classList.add('active'); refTone = p.dataset.tone; }); });
function noteToFreq(letter, modifier, octave){ let semitone = NOTE_SEMITONES[letter] + (modifier === 'sustenido' ? 1 : modifier === 'bemol' ? -1 : 0); let oct = octave; if(semitone < 0){ semitone += 12; oct -= 1; } if(semitone > 11){ semitone -= 12; oct += 1; } const midi = (oct + 1) * 12 + semitone; return { freq: A4 * Math.pow(2, (midi - 69) / 12), oct }; }
function ensureRefAudio(){ if(!refCtx) refCtx = new (window.AudioContext || window.webkitAudioContext)(); if(refCtx.state === 'suspended') refCtx.resume(); }
function stopReferenceTone(){ if(refGain){ const now = refCtx.currentTime; refGain.gain.cancelScheduledValues(now); refGain.gain.setValueAtTime(refGain.gain.value, now); refGain.gain.linearRampToValueAtTime(0.0001, now + 0.06); const o1 = refOsc1, o2 = refOsc2; setTimeout(() => { try{ o1.stop(); o2.stop(); }catch(e){} }, 90); } refOsc1 = null; refOsc2 = null; refFilter = null; refGain = null; currentNoteKey = null; naturalBtns.forEach(b => b.classList.remove('playing')); noteStatus.textContent = 'Toque numa nota pra ouvir'; noteStatus.classList.remove('playing'); }
function playReferenceTone(letter, btn){ ensureRefAudio(); const { freq, oct } = noteToFreq(letter, refModifier, refOctave); const now = refCtx.currentTime;
refOsc1 = refCtx.createOscillator(); refOsc2 = refCtx.createOscillator(); refFilter = refCtx.createBiquadFilter(); refFilter.type = 'lowpass';
if(refTone === 'nylon'){ refOsc1.type='triangle'; refOsc2.type='sine'; refOsc2.detune.value=4; refFilter.frequency.value=freq*5; } else if(refTone === 'steel'){ refOsc1.type='sawtooth'; refOsc2.type='triangle'; refOsc2.detune.value=6; refFilter.frequency.value=freq*7; } else if(refTone === 'synth'){ refOsc1.type='square'; refOsc2.type='sawtooth'; refOsc2.detune.value=3; refFilter.frequency.value=freq*4; } else { refOsc1.type='sine'; refOsc2.type='sine'; refOsc2.detune.value=0; refFilter.frequency.value=freq*20; }
refOsc1.frequency.value = freq; refOsc2.frequency.value = freq; refGain = refCtx.createGain(); refGain.gain.setValueAtTime(0.0001, now); refGain.gain.linearRampToValueAtTime(0.35, now + 0.05);
refOsc1.connect(refFilter); refOsc2.connect(refFilter); refFilter.connect(refGain); refGain.connect(refCtx.destination); refOsc1.start(now); refOsc2.start(now);
const symbol = refModifier === 'sustenido' ? '#' : refModifier === 'bemol' ? 'b' : ''; const displayName = letter + symbol + oct; currentNoteKey = displayName; naturalBtns.forEach(b => b.classList.remove('playing')); btn.classList.add('playing'); noteStatus.textContent = 'Tocando: ' + displayName + ' · ' + freq.toFixed(1) + ' Hz'; noteStatus.classList.add('playing'); }
naturalBtns.forEach(btn => { btn.addEventListener('click', () => { const letter = btn.dataset.letter; const symbol = refModifier === 'sustenido' ? '#' : refModifier === 'bemol' ? 'b' : ''; const { oct } = noteToFreq(letter, refModifier, refOctave); const key = letter + symbol + oct; if(currentNoteKey === key){ stopReferenceTone(); return; } stopReferenceTone(); playReferenceTone(letter, btn); }); });
function stopTuner(){ stopReferenceTone(); }
// ===== TABLATURA ===== const tabInput = document.getElementById('tabInput'), tabRender = document.getElementById('tabRender'); const tabPlayBtn = document.getElementById('tabPlayBtn'), toneRow = document.getElementById('toneRow'); const autoscrollBtn = document.getElementById('autoscrollBtn'); const btnClear = document.getElementById('btnClear'); const STRING_FREQS = {1:329.63, 2:246.94, 3:196.00, 4:146.83, 5:110.00, 6:82.41}; const DEFAULT_STRING_LABELS = ['e', 'B', 'G', 'D', 'A', 'E']; // topo -> base (afinação padrão)
// Lê a caixa "Afinação" (digitada grave->agudo, ex: E-A-D-G-B-E) e devolve // os rótulos na ordem de exibição da tab (agudo->grave, topo->base). function getCurrentStringLabels(){ const campo = document.getElementById('campo-afinacao'); const raw = campo ? campo.value.trim() : ''; if(!raw || raw.toLowerCase() === 'padrão') return DEFAULT_STRING_LABELS; const partes = raw.split('-').map(p => p.trim()).filter(Boolean); if(partes.length !== 6) return DEFAULT_STRING_LABELS; // digitação incompleta -> usa padrão até completar return partes.slice().reverse(); // grave->agudo digitado vira agudo->grave exibido } let tone = 'nylon', autoScroll = false, tabSteps = [], currentStepIdx = -1, tabPlaying = false;
toneRow.querySelectorAll('.tone-pill').forEach(p => p.addEventListener('click', () => { toneRow.querySelectorAll('.tone-pill').forEach(x => x.classList.remove('active')); p.classList.add('active'); tone = p.dataset.tone; scheduleSave(); }));
autoscrollBtn.addEventListener('click', () => { autoScroll = !autoScroll; autoscrollBtn.classList.toggle('active', autoScroll); });
btnClear.addEventListener('click', () => { if(confirm('Limpar a tablatura e apagar o que está salvo?')){ tabInput.value = ''; tabSteps = []; currentStepIdx = -1; renderTab(); localStorage.removeItem('picado-state'); showSaveStatus('Limpo!'); } });
const EXERCISES = [ { name:'1', tab:'- - - - - 1.4 1.5 1.7 1.5 1.4 2.7 2.5 2.4 3.6 2.4 2.5 2.7 2.5 2.4 3.6 - - - ' }, { name:'2', tab:'- - - - - 4.1h4 3.3 2.2 1.1 1.4 1.3 1.2 1.1 2.2 3.3 4.4 - 4.4p1 3.2 2.3 1.4 1.1 1.2 1.3 1.4 2.3 3.2 4.1 - - -' }, { name:'3', tab:'- - - - - 6.0 5.0 6.0 5.0 4.0 5.0 4.0 3.0 4.0 3.0 2.0 3.0 2.0 1.0 2.0 3.0 2.0 3.0 4.0 3.0 4.0 5.0 4.0 5.0 6.0 - - -' }, { name:'4', tab:'- - - - - 3.2 2.0 2.1 2.3 2.1 2.0 3.2 2.0 2.1 2.3 2.1 2.0 3.2 2.0 2.1 2.3 2.1 2.0 3.2 3.1 4.3 4.2 4.0 5.3 5.2 5.0 6.4 6.1 6.0 - - -' }, { name:'5', tab:'- - - - - 1.2 1.0 1.3 1.2 1.0 2.1 2.0 2.1 2.3 2.1 2.0 3.2 3.0 3.2 3.4 3.2 3.0 4.2 4.0 4.2 4.4 4.2 4.0 5.2 5.0 5.2 5.3 5.2 5.0 6.2 6.0 6.2 6.3 6.2 6.0 - - -' }, { name:'6', tab:'- - - - - 3.2 2.0 2.1 2.3 1.0 1.1 1.0 1.4 1.1 1.0 1.7 1.4 1.0 1.10 1.7 1.10 1.13 1.12 1.10 1.13 1.12 1.10 2.13 2.12 2.10 3.12 3.10 6.0 1.0 4.2 3.1 2,0 1.0 - - -' }, { name:'7', tab:'- - - - - 1.8 1.7 1.8 1.11 1.8 1.7 2.8 2.7 2.8 2.10 2.8 2.7 3.8 3.5 3.8 3.9 3.8 3.5 4.7 4.5 4.7 4.9 4.7 4.5 5.7 5.6 5.7 5.9 5.7 5.6 6.8 6.7 6.8 6.11 6.8 6.7 - 6.7+5.9+4.9+3.0+2.0+1.0 - - -' }, { name:'8', tab:'- - - - - 4.0+3.2+2.3 - 2.0 2.1 2.3 1.0 1.2 1.3 1.5 1.3 1.2 1.0 2.4 1.0 1.3 1.2 1.0 - 6.2+5.0+4.0+3.2+2.3+1.0 - 2.0 2.1 2.3 1.0 1.2 1.3 1.5 1.3 1.2 1.0 2.4 1.0 1.3 1.2 1.0 - 6.3+5.2+4.2+3.0+2.0+1.0 - - -' }, { name:'9', tab:'- - - - - 1.2 2.4 3.4 2.4 1.2 1.5 1.3 1.2 1.3 2.5 3.5 2.5 1.3 1.7 1.5 1.3 1.5 2.7 3.8 2.7 1.5 1.8 1.7 1.5 1.7 2.8 3.9 2.8 1.7 1.10 1.8 1.7 1.8 2.10 3.11 2.10 1.8 1.12 1.10 1.8 1.7 2.8 3.9 2.8 1.7 1.10 1.8 1.7 1.5 2.7 3.8 2.7 1.5 1.8 1.7 1.5 1.3 2.5 3.5 2.5 1.3 1.7 1.5 1.3 1.2 2.4 3.4 2.4 1.2 1.5 1.3 1.2 1.0 2.0 3.0 4.2 5.2 6.3\0 - - -' }, { name:'10', tab:'- - - - - 1.5 1.5 1.5 1.4 1.2 1.4 1.5 1.2 1.4 1.4 1.4 1.2 1.0 1.2 1.4 1.0 1.2 1.2 1.2 1.0 2.3 1.0 1.2 1.3 1.0 1.0 1.0 2.3 2.3 2.3 1.0 2.2 2.3 2.3 2.3 2.2 2.0 2.2 2.3 2.0 2.2 2.2 2.2 2.0 3.0 2.0 2.2 3.2 2.0 2.0 2.0 3.2 3.1 3.2 2.0 3.1 3.2 3.1 4.4 4.2 4.0 5.4 5.2 6.4 5.2 5.0 5.2 5.4 4.0 4.2 4.4 3.1 3.2 2.0 2.2 2.3 1.0 1.2 1.4 1.5 - - -' }, { name:'11', tab:'- - - - - 6.2 6.6 5.2 5.4 5.5 5.4 5.2 6.6 6.3 5.2 5.4 5.5 4.2 5.5 5.4 5.2 6.6 5.4 5.5 4.2 4.4 4.2 5.5 5.4 5.2 5.5 4.2 4.4 4.5 4.4 4.2 5.5 5.4 4.2 4.4 4.5 3.3 4.5 4.4 4.2 5.5 4.4 4.5 3.3 3.4 3.3 4.5 4.4 4.2 4.5 3.3 3.4 2.2 3.4 3.3 4.5 4.4 3.3 3.4 2.2 2.3 2.2 3.4 3.3 4.5 3.4 2.2 2.3 2.5 2.3 2.2 3.4 3.3 2.2 2.3 2.5 1.2 2.5 2.3 2.2 3.4 2.3 2.5 1.2 1.3 1.2 2.5 2.3 2.2 2.5 1.2 1.3 1.6 1.3 1.2 1.3 1.2 - - -' }, { name:'12', tab:'- - - - - 2.0+3.0+4.4+5.2 1.0 2.0 3.0 2.0 1.0 1.3 1.2 1.0 1.2 2.4 3.4 2.4 1.2 1.5 1.3 1.2 1.3 2.5 3.5 2.5 1.3 1.7 1.5 2.8 1.5 2.7 3.7 2.7 1.5 1.8 1.7 1.5 1.7 2.8 3.9 2.8 1.7 1.10 1.8 1.7 1.8 2.10 3.9 2.10 1.8 1.12 1.10 1.8 1.10 2.12 3.12 2.12 1.10 1.13 1.12 1.10 1.8 2.10 3.9 2.10 1.8 1.12 1.10 1.8 1.7 2.8 3.9 2.8 1.7 1.10 1.8 1.7 1.5 2.7 3.7 2.7 1.5 1.8 1.7 1.5 1.3 2.5 3.5 2.5 1.3 1.7 1.5 2.8 1.2 2.4 3.4 2.4 1.2 1.5 1.3 1.2 1.0 2.3 2.1 2.0 3.2 3.0 4.4 4.2 4.0 5.3 5.2 5.0 6.3 6.2 6.0 6.2 6.3 5.0 5.2 5.3 4.0 4.2 4.4 3.0 3.2 2.0 2.1 2.3 1.0 1.2 1.0 1.2 1.0 6.0 - 5.10+4.9+3.8+2.7 - - -' } ];
const exercisesRow = document.getElementById('exercisesRow'); function renderExercises(){ if(!EXERCISES.length){ exercisesRow.innerHTML = 'Nenhum exercício salvo ainda'; return; } exercisesRow.innerHTML = ''; EXERCISES.forEach(ex => { const btn = document.createElement('div'); btn.className = 'exercise-pill'; btn.textContent = ex.name; btn.addEventListener('click', () => { tabInput.value = ex.tab; tabSteps = parseTab(ex.tab); currentStepIdx = -1; renderTab(); scheduleSave(); document.querySelectorAll('.exercise-pill').forEach(p => p.classList.remove('loaded')); btn.classList.add('loaded'); }); exercisesRow.appendChild(btn); }); }
// ===== CONVERSOR DE TAB ASCII PADRÃO (importação de .txt) ===== function convertAsciiTabToAppFormat(text){ const rawLines = text.split(/\r?\n/);
function isStringLine(line){ return /^\s*[A-Ga-g](?:#|b)?\d?\s*\|/.test(line || ''); }
// Reconhece uma ou mais TABs visuais geradas pelo editor, com ou sem // linha numérica de colunas. Assim arquivos com vários sistemas também voltam completos. function convertVisualBlocks(lines){ const convertedBlocks = []; let i = 0; while(i < lines.length){ if(/^\s*[0-9\s]+$/.test((lines[i] || '').trim()) && (lines[i] || '').trim() && i + 1 < lines.length && isStringLine(lines[i + 1])){ i++; } if(i + 5 < lines.length && lines.slice(i, i + 6).every(isStringLine)){ const stringLines = lines.slice(i, i + 6); const stringData = stringLines.map(line => { const content = line.replace(/^\s*[A-Ga-g](?:#|b)?\d?\s*\|/, ''); const parts = content.split('|'); if(parts.length && parts[parts.length - 1] === '') parts.pop(); return parts.map(p => p.trim()); }); const maxCols = Math.max(...stringData.map(row => row.length)); const result = []; for(let col = 0; col < maxCols; col++){ const notes = []; for(let string = 0; string < 6; string++){ const rawVal = stringData[string] && stringData[string][col] ? stringData[string][col] : ''; const val = rawVal.replace(/^-+|-+$/g, '').trim(); if(val){ const noteMatch = val.match(/^(\d{1,2})([hpb~xr\/\\])(\d{1,2})?$/i); if(noteMatch){ let note = (string + 1) + '.' + noteMatch[1]; if(noteMatch[2]) note += noteMatch[2] + (noteMatch[3] || ''); notes.push(note); } else if(/^\d+$/.test(val)){ notes.push((string + 1) + '.' + val); } } } result.push(notes.length ? notes.join('+') : '-'); } convertedBlocks.push(result.join(' ')); i += 6; continue; } i++; } return convertedBlocks.length ? convertedBlocks.join('\n') : null; }
const visualConverted = convertVisualBlocks(rawLines); if(visualConverted) return visualConverted;
// Se não for visual, tenta o formato tradicional. function extractBody(line){ const m = line.match(/^\s*[A-Ga-g](?:#|b)?\d?\s*\|?(.*)$/); if(!m) return null; const body = m[1]; const cleanCount = (body.match(/[-\d]/g) || []).length; if(cleanCount < 4) return null; const nonTabChars = body.replace(/[\-\dhpxbr~\/\\|\s()]/gi, '').length; if(nonTabChars > body.length * 0.15) return null; return body; }
const candidates = rawLines.map(l => ({ raw: l, body: extractBody(l) }));
const blocks = []; let current = []; for(const c of candidates){ if(c.body !== null){ current.push(c.body); } else { if(current.length >= 2) blocks.push(current); current = []; } } if(current.length >= 2) blocks.push(current);
if(blocks.length === 0) return null;
const outputLines = []; for(const block of blocks){ for(let start = 0; start < block.length; start += 6){ const chunk = block.slice(start, start + 6); if(chunk.length < 2) continue; const noteRegex = /(\d{1,2})(?:([hp\/\\])([0-9]{1,2}))?/g; const events = []; chunk.forEach((lineBody, idx) => { const stringNum = idx + 1; let match; noteRegex.lastIndex = 0; while((match = noteRegex.exec(lineBody)) !== null){ events.push({ col: match.index, stringNum, fret: parseInt(match[1], 10), tech: match[2] || null, techTarget: match[3] ? parseInt(match[3], 10) : null }); } }); if(events.length === 0) continue; events.sort((a,b) => a.col - b.col); const steps = []; const tolerance = 1; for(const ev of events){ let step = steps.find(s => Math.abs(s.col - ev.col) a.col - b.col); const tokens = steps.map(step => step.notes.map(n => { let tok = n.stringNum + '.' + n.fret; if(n.tech) tok += n.tech + (n.techTarget || ''); return tok; }).join('+')); if(tokens.length) outputLines.push(tokens.join(' ')); } } return outputLines.length ? outputLines.join('\n') : null; }
// ===== PARSE TAB ===== // O caractere | no código marca uma divisão de trecho/frase. // Ex.: 1.6| deixa a barra imediatamente à direita daquela coluna // em negrito nas seis cordas, sem alterar a nota tocada. function parseTab(txt){ if (!txt || txt.trim() === '') return []; const tokens = txt.trim().split(/\s+/).filter(Boolean); const steps = [];
tokens.forEach(originalToken => { let tok = originalToken;
// Também aceita | separado por espaços e o aplica à coluna anterior. if(/^\|+$/.test(tok)){ if(steps.length && steps[steps.length - 1]) steps[steps.length - 1].phraseBreak = true; return; }
const phraseBreak = /\|+$/.test(tok); if(phraseBreak) tok = tok.replace(/\|+$/, ''); if(!tok) return;
if (/^[-_.]$/.test(tok)) { const silentStep = []; silentStep.isSilence = true; silentStep.phraseBreak = phraseBreak; steps.push(silentStep); return; }
const parts = tok.split('+'); const nts = parts.map(p => { const m = p.match(/^([1-6])[.:]([0-9]{1,2})([hpb/~rx\/\\])?([0-9]{1,2})?$/i); return m ? { string: +m[1], fret: +m[2], tech: m[3] ? m[3].toLowerCase() : null, tv: +m[4] || null } : null; }).filter(Boolean);
if(nts.length){ nts.phraseBreak = phraseBreak; steps.push(nts); } });
return steps; }
function getStepCellInfo(step){ const labels = []; let maxLen = 0; for(let s = 1; s x.string === s) : null; let plain = ''; if(n){ plain = String(n.fret); if(n.tech) plain += n.tech + (n.tv || ''); } labels.push({ note:n, plain }); maxLen = Math.max(maxLen, plain.length); } return { labels, width:Math.max(2, maxLen + 2) }; }
function noteHtml(note){ if(!note) return ''; let html = String(note.fret); if(note.tech) html += '' + note.
|