A quem possa interessar, esse é um Metrônomo/Afinador/Editor de Tablatura com Exercícios Proposto

    Autor Mensagem
    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



    Mi



    Sol

    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.

      Enviar sua resposta para este assunto
              Tablatura   
      Responder tópico na versão original
       

      Tópicos relacionados a A quem possa interessar, esse é um Metrônomo/Afinador/Editor de Tablatura com Exercícios Proposto