finalizado4 min read

Valvecraft

Jogo educativo para trompetistas. Treine dedilhado, leitura de partitura e senso de ritmo direto no navegador com áudio real de trompete.

React 19TypeScriptViteTailwind CSSVexFlowTone.js

O Projeto

Valvecraft é um jogo para trompetistas que querem melhorar dedilhado, leitura de partitura e senso de ritmo de forma interativa. Com amostras de áudio reais e feedback visual em tempo real, o estudo técnico vira uma experiência dinâmica.

Suporta Trompete em Si♭ (padrão) e Trompete em Dó.

Modos de Jogo

Modo Quiz

Você vê uma nota na pauta e precisa identificar e tocar o dedilhado correto nas válvulas.

VarianteDescrição
DesafioCronômetro de 60s. Acertos estendem o tempo, erros penalizam. Sistema de streaks com multiplicadores.
TreinoSem tempo, com dicas visuais de dedilhado. Ideal para iniciantes.

O sistema de streaks premia acertos consecutivos com multiplicadores de pontuação:

NívelAcertosMultiplicador
Ritmo0+1.0x
Combo5+1.15x
Foco10+1.3x
Blitz15+1.5x

Modo Ritmo

Toque músicas reais em partitura. Um playhead percorre as notas no tempo certo, você precisa pressionar as válvulas corretas no momento exato.

Feedback por nota:

  • Perfeito — dedilhado correto no tempo certo
  • Dedilhado errado — tempo certo, nota errada
  • Miss — janela de tempo perdida

Repertório disponível: escalas maiores, exercícios técnicos e melodias como Ode à Alegria, Amazing Grace, When the Saints, entre outras.

Desafios Técnicos

Renderização de Partituras com VexFlow

O maior desafio foi integrar o VexFlow para renderizar partituras musicais corretamente no React. VexFlow manipula o DOM diretamente via canvas ou SVG, o que conflita com o modelo de reconciliação do React.

1// hooks/useSheetMusic.ts
2export function useSheetMusic(containerRef: RefObject<HTMLDivElement>) {
3 useEffect(() => {
4 if (!containerRef.current) return;
5
6 // VexFlow requer limpeza manual antes de re-renderizar
7 containerRef.current.innerHTML = "";
8
9 const renderer = new Renderer(containerRef.current, Renderer.Backends.SVG);
10 renderer.resize(600, 150);
11
12 const context = renderer.getContext();
13 const stave = new Stave(10, 10, 580);
14 stave.addClef("treble").addTimeSignature("4/4");
15 stave.setContext(context).draw();
16
17 return () => {
18 if (containerRef.current) {
19 containerRef.current.innerHTML = "";
20 }
21 };
22 }, [note]);
23}

Engine de Áudio com Tone.js

Para o áudio, usei Tone.js com ToneAudioBuffer carregando amostras reais de trompete por nota. A detecção de timing no Modo Ritmo usa Tone.Transport para sincronizar o playhead com a janela de acerto de cada nota.

1// hooks/useTrumpetAudio.ts
2const SAMPLE_MAP: Record<string, string> = {
3 C4: "/audio/trumpet/C4.mp3",
4 D4: "/audio/trumpet/D4.mp3",
5 // ...
6};
7
8export function useTrumpetAudio() {
9 const samplerRef = useRef<Tone.Sampler | null>(null);
10
11 useEffect(() => {
12 samplerRef.current = new Tone.Sampler(SAMPLE_MAP).toDestination();
13 return () => samplerRef.current?.dispose();
14 }, []);
15
16 const playNote = useCallback((note: string) => {
17 samplerRef.current?.triggerAttackRelease(note, "8n");
18 }, []);
19
20 return { playNote };
21}

Mapeamento de Dedilhado

Cada nota do trompete tem um dedilhado específico (combinação de válvulas 1, 2 e 3). O fingeringMap mapeia nota para válvulas e também inclui dedilhados alternativos para notas com múltiplas opções.

1// data/fingeringMap.ts
2export const fingeringMap: Record<string, number[][]> = {
3 C4: [[0, 0, 0]], // Nota aberta
4 D4: [[1, 3, 0]], // Válvulas 1 e 3
5 E4: [[1, 2, 0]], // Válvulas 1 e 2
6 F4: [[1, 0, 0]], // Válvula 1
7 "F#4": [
8 [2, 3, 0],
9 [1, 2, 3],
10 ], // Alternativo: todas as válvulas
11 G4: [[0, 0, 0]], // Nota aberta (parcial diferente)
12 // ...
13};

Controles

AçãoTecla
Válvula 1Q
Válvula 2W
Válvula 3E
3º slide (afinação)Shift
Confirmar / Nota abertaEspaço

Os controles podem ser alterados no menu principal.

O que aprendi

  • VexFlow tem uma API imperativa que requer atenção ao ciclo de vida do React: precisei gerenciar limpeza do DOM manualmente nos useEffect
  • Tone.js e timing preciso: a janela de detecção de acerto no Modo Ritmo foi a parte mais difícil, sincronizar áudio com animação visual tem latências diferentes
  • Sistema de gamificação simples mas eficaz: o multiplier de streaks foi o que mais engajou nos testes, pequenos feedbacks visuais fazem diferença