npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sophialabs/spectro

v1.6.9

Published

lib para geração de espectrogramas

Readme

Build Status License Version

Introdução

Spectro é uma biblioteca TypeScript para gerar espectrogramas a partir de dados de áudio (Float32Array).
Ela usa FFT com várias funções janela, possui escala Linear e Mel, colormaps inspirados no Matplotlib, filtros FIR (passa-baixa, passa-alta, passa-banda, rejeita-banda), eixo de frequência e eixo de tempo opcionais, hop size configurável, exportação PNG em alta resolução, além de detecção de pitch e extração de harmônicos.

Recursos

  • Geração de espectrograma a partir de single channel Float32Array
  • Parâmetros flexíveis:
    • Taxa de amostragem, faixa de frequência (fMin, fMax)
    • Tamanho de FFT, hop size configurável e funções janela (None, Cosine, Hanning, BH7)
    • Escala Linear ou Mel (remapeamento real na exibição)
    • Colormaps customizáveis
    • Eixo de frequência e eixo de tempo opcionais (com ticks dinâmicos)
    • Altura/largura finais do canvas (com margens dinâmicas quando eixos estão habilitados)
    • Ganho (gainDb) e normalização por faixa (rangeDb) ancorada no pico global
  • Exportação PNG em alta resolução (upscale configurável)
  • Filtros FIR de pré-processamento: lowpass, highpass, bandpass, notch
  • Pitch Tracking (autocorrelação) e extração de harmônicos
  • Colormaps exportados e tipados (ex.: hot, jet, viridis, …)

Pré-requisitos

Antes de começar, certifique-se de ter as seguintes ferramentas instaladas:

  • Node.js (recomendado versão LTS)
  • npm (geralmente vem com o Node.js)

Instalação

Siga as etapas abaixo para configurar o projeto em sua máquina local:

  1. Clone o repositório:
    git clone https://github.com/IMNascimento/Spectro.git
  2. Navegue até o diretório do projeto:
    cd Spectro
  3. Instale as dependências:
    npm install

Configuração do TypeScript:

O arquivo tsconfig.json já está configurado para gerar módulos ES6 e arquivos de declaração (d.ts):

{
    "compilerOptions": {
        "target": "ES5",
        "module": "ES6",
        "declaration": true,
        "outDir": "./dist",
        "strict": true,
        "esModuleInterop": true,
        "lib": ["dom", "es2015"]
    },
    "include": ["src/**/*"]
}

Compilação

Para compilar o código TypeScript e gerar os arquivos JavaScript na pasta dist, execute:

    npm run build

API (Visão Geral)

SpectrogramParams

| Parâmetro | Tipo | Padrão | Descrição | |---|---|---:|---| | sampleRate | number | 44100 | Taxa de amostragem (Hz). | | scaleType | 'Linear' \| 'Mel' | 'Linear' | Escala vertical. Em Mel, a exibição remapeia a altura do espectrograma para a escala perceptual. | | fMin | number | 1 | Frequência mínima (Hz). | | fMax | number | 30000 | Frequência máxima (Hz). | | fftSize | number | 2048 | Tamanho da FFT (potência de 2). | | hopSize | number | fftSize/2 | Passo entre frames em amostras. | | windowType | 'None' \| 'Cosine' \| 'Hanning' \| 'BH7' | 'BH7' | Função janela. | | colormapName | string | 'hot' | Nome do colormap (precisa existir no window). | | canvasHeight | number | 500 | Altura do espectrograma (área útil). | | targetWidth | number | 0 | Largura final desejada; 0 usa window.innerWidth. | | nTicks | number | 0 | Nº de ticks de frequência (0 = cálculo dinâmico). | | gainDb | number | 0 | Ganho em dB aplicado ao espectro. | | rangeDb | number | 80 | Faixa em dB p/ normalização ancorada no pico global. Se 0, usa fallback seguro 80. | | showFrequencyAxis | boolean | false | Exibe eixo de frequência (adiciona margens laterais). | | showTimeAxis | boolean | false | Exibe eixo de tempo (adiciona margem inferior). | | timeTickMinPx | number | 60 | Espaçamento mínimo entre ticks do eixo de tempo (px). | | filterType | 'none' \| 'lowpass' \| 'highpass' \| 'bandpass' \| 'notch' | 'none' | Tipo de filtro FIR aplicado antes da FFT. | | filterCutoffs | number[] | [] | Para lowpass/highpass: [cutoff]. Para bandpass/notch: [lowCut, highCut]. | | enablePitchDetection | boolean | false | Habilita detecção de frequência fundamental (autocorrelação). | | enableHarmonicsExtraction | boolean | false | Habilita extração de harmônicos (múltiplos inteiros da fundamental). |

Métodos principais

  • generateSpectrogram(audioData: Float32Array): HTMLCanvasElement
    Gera e retorna um <canvas> com o espectrograma.
    Obs.:

    • Se showFrequencyAxis for true, são aplicadas margens laterais (60px esquerda, 10px direita).
    • Se showTimeAxis for true, é aplicada margem inferior (~22px) para os rótulos.
  • exportHighResPNG(audioData: Float32Array, upscale = 2): string
    Renderiza o espectrograma e retorna um DataURL PNG em alta resolução (upscale = fator de ampliação).

  • detectPitch(audioData: Float32Array): number
    Retorna a frequência fundamental (Hz) usando autocorrelação simples (útil para pitch tracking básico).

  • extractHarmonics(audioData: Float32Array): { fundamental: number; harmonics: number[] }
    Calcula a fundamental e retorna os harmônicos (múltiplos inteiros), respeitando fMax.


Colormaps (como disponibilizar)

A lib espera funções de colormap no escopo global (window). Use o helper partial para expor:

import { partial } from '@sophialabs/spectro';

(window as any).partial = partial;
(window as any).hot = partial('hot');
(window as any).jet = partial('jet');
(window as any).viridis = partial('viridis');
(window as any).Greens = partial('Greens');
(window as any).turbo = partial('turbo');
(window as any).terrain = partial('terrain');
(window as any).RdPu = partial('RdPu');
(window as any).binary = partial('binary');

Exemplos de Uso

Em Angular

  1. Instale sua lib via npm.
npm i @sophialabs/spectro
  1. Importe a classe em um componente Angular:
// app.component.ts
import { Component } from '@angular/core';
import { SpectrogramGenerator, SpectrogramParams, partial } from '@sophialabs/spectro';

@Component({
  selector: 'app-root',
  template: `
    <input type="file" (change)="onFileChange($event)" accept="audio/*" />
    <div id="container"></div>
  `,
  styles: [`
    #container canvas {
      border: 1px solid #000;
      display: block;
      margin: 10px auto;
    }
  `]
})
export class AppComponent {
  onFileChange(event: Event) {
    const input = event.target as HTMLInputElement;
    if (input.files && input.files.length) {
      const file = input.files[0];
      const reader = new FileReader();
      reader.onload = async (e: any) => {
        const arrayBuffer = e.target.result;
        const audioCtx = new AudioContext();
        const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
        const audioData = audioBuffer.getChannelData(0);

        // Exponha os colormaps globalmente para que a lib os encontre:
        (window as any).partial = partial;
        (window as any).hot = partial('hot');
        (window as any).jet = partial('jet');
        (window as any).viridis = partial('viridis');
        (window as any).Greens = partial('Greens');
        (window as any).turbo = partial('turbo');
        (window as any).terrain = partial('terrain');
        (window as any).RdPu = partial('RdPu');
        (window as any).binary = partial('binary');

        // Defina os parâmetros completos com valores e comentários explicativos:
        const params: SpectrogramParams = {
          sampleRate: 44100,         // Taxa de amostragem em Hz.
          scaleType: 'Mel',          // Escala de frequência ('Mel' ou 'Linear').
          fMin: 1,                   // Frequência mínima (Hz).
          fMax: 20000,               // Frequência máxima (Hz).
          fftSize: 2048,             // Tamanho do buffer FFT (deve ser potência de 2).
          windowType: 'BH7',         // Função janela: 'None', 'Cosine', 'Hanning' ou 'BH7'.
          colormapName: 'hot',       // Nome do colormap para renderização.
          canvasHeight: 500,         // Altura do canvas final (px).
          nTicks: 20,                // Número de ticks para o eixo de frequência (0 para cálculo automático).
          gainDb: 20,                // Ganho em dB (0 para sem alteração).
          rangeDb: 80,               // Intervalo em dB para normalização (0 para manter escala original).
          targetWidth: 0,            // Largura final desejada (0 utiliza window.innerWidth).
          showFrequencyAxis: false,  // Define se o eixo de frequência será exibido.
          filterType: 'none',        // Tipo de filtro: 'none', 'lowpass', 'highpass', 'bandpass' ou 'notch'.
          filterCutoffs: [],         // Frequências de corte para o filtro (ex: [cutoff] para lowpass).
          enablePitchDetection: true,    // Se true, habilita a detecção de pitch (calcula a frequência fundamental).
          enableHarmonicsExtraction: true  // Se true, habilita a extração de harmônicos (baseada na fundamental).
        };

        // Cria a instância do gerador com os parâmetros definidos:
        const generator = new SpectrogramGenerator(params);

        // Gera o espectrograma e insere o canvas no DOM:
        const canvas = generator.generateSpectrogram(audioData);
        document.querySelector('#container')?.appendChild(canvas);

        // Se a flag de detecção de pitch estiver habilitada, chama o método detectPitch():
        if (params.enablePitchDetection) {
          const fundamentalFreq = generator.detectPitch(audioData);
          console.log('Frequência Fundamental detectada:', fundamentalFreq, 'Hz');
        } else {
          console.log('Detecção de Pitch desabilitada.');
        }

        // Se a flag de extração de harmônicos estiver habilitada, chama o método extractHarmonics():
        if (params.enableHarmonicsExtraction) {
          const { fundamental, harmonics } = generator.extractHarmonics(audioData);
          console.log('Frequência Fundamental:', fundamental, 'Hz');
          console.log('Harmônicos extraídos:', harmonics);
        } else {
          console.log('Extração de Harmônicos desabilitada.');
        }
      };
      reader.readAsArrayBuffer(file);
    }
  }
}
  1. Adicione os assets necessários: Certifique-se de que os arquivos compilados (por exemplo, os arquivos de sua lib e os colormaps) estejam disponíveis no build final do Angular. Você pode incluí-los via assets ou importar diretamente em seus módulos.

Em Outros Projetos TypeScript/JavaScript

Basta importar a lib normalmente, seja via npm ou via um caminho relativo. Por exemplo, em um projeto Node.js ou um script ES:

import { SpectrogramGenerator, SpectrogramParams, partial } from '@sophialabs/spectro';

// Exponha os colormaps globalmente, se necessário:
window.hot = partial('hot');
window.jet = partial('jet');
window.viridis = partial('viridis');
window.Greens = partial('Greens');
window.turbo = partial('turbo');
window.terrain = partial('terrain');
window.RdPu = partial('RdPu');
window.binary = partial('binary');

// Criação do objeto de parâmetros, com comentários sobre cada um:
const params: SpectrogramParams = {
  sampleRate: 44100,         // Taxa de amostragem em Hz
  scaleType: 'Mel',          // Tipo de escala ('Mel' ou 'Linear')
  fMin: 1,                   // Frequência mínima (Hz)
  fMax: 30000,               // Frequência máxima (Hz)
  fftSize: 2048,             // Tamanho do buffer FFT (deve ser potência de 2)
  windowType: 'BH7',         // Função janela: 'None', 'Cosine', 'Hanning' ou 'BH7'
  colormapName: 'hot',       // Nome do colormap usado para renderizar o espectrograma
  canvasHeight: 500,         // Altura do canvas final em pixels
  nTicks: 30,                // Número de ticks para o eixo de frequência (0 para cálculo automático)
  gainDb: 10,                // Ganho em dB aplicado aos dados (use 0 para manter sem alteração)
  rangeDb: 20,               // Intervalo em dB para normalização dos dados (0 para manter sem alteração)
  targetWidth: 0,            // Largura do canvas final (0 usa window.innerWidth)
  showFrequencyAxis: false,  // Se true, exibe o eixo de frequência no canvas
  filterType: 'none',        // Tipo de filtro: 'none', 'lowpass', 'highpass', 'bandpass' ou 'notch'
  filterCutoffs: [],         // Frequências de corte para o filtro (ex: [cutoff] para lowpass)
  enablePitchDetection: true,      // Habilita a detecção de pitch (retorna a frequência fundamental)
  enableHarmonicsExtraction: true    // Habilita a extração de harmônicos (calculados com base na fundamental)
};

// Suponha que audioData seja um Float32Array contendo os dados de áudio:
declare const audioData: Float32Array;

// Instancia a classe do gerador com os parâmetros definidos:
const generator = new SpectrogramGenerator(params);

// Gera o espectrograma e obtém o canvas resultante:
const canvas = generator.generateSpectrogram(audioData);
// Exemplo de uso: adicionar o canvas ao DOM:
document.body.appendChild(canvas);

// Se a detecção de pitch estiver habilitada, calcula a frequência fundamental:
if (params.enablePitchDetection) {
  const fundamentalFreq = generator.detectPitch(audioData);
  console.log('Frequência Fundamental detectada:', fundamentalFreq, 'Hz');
}

// Se a extração de harmônicos estiver habilitada, extrai os harmônicos:
if (params.enableHarmonicsExtraction) {
  const { fundamental, harmonics } = generator.extractHarmonics(audioData);
  console.log('Frequência Fundamental:', fundamental, 'Hz');
  console.log('Harmônicos extraídos:', harmonics);
}

// Opcional: Exporta uma imagem PNG de alta resolução do espectrograma (fator de ampliação = 3)
const pngDataUrl = generator.exportHighResPNG(audioData, 3);
console.log('PNG de alta resolução:', pngDataUrl);

Testando a Biblioteca com um Áudio Local

Para testar a lib em uma página web:

  1. Crie um arquivo index.html na raiz do projeto (ou utilize o exemplo fornecido abaixo).

  2. Utilize um servidor local para servir os arquivos (por exemplo, com http-server). Se ainda não tiver o http-server instalado globalmente, instale-o via npm:

npm install -g http-server
  1. Na raiz do projeto, execute:
http-server .
  1. Acesse a URL fornecida (por exemplo, http://127.0.0.1:8080/) no navegador.

Importante: não abra via file://. Sirva com um servidor local (ex.: http-server .).

Exemplo de index.html

<!DOCTYPE html>
<html lang="pt">
<head>
  <meta charset="UTF-8" />
  <title>Teste da Lib Spectro - Completo</title>
  <style>
    body { font-family: sans-serif; margin: 20px; }
    #controls { margin-bottom: 20px; }
    canvas { border: 1px solid #000; display: block; margin-top: 10px; max-width: 100%; }
    #spectroContainer { max-width: 100%; overflow-x: auto; }
    .param-group { margin-bottom: 10px; }
    label { display:block; margin-top: 5px; }
  </style>
</head>
<body>
  <h1>Teste da Lib Spectro - Completo</h1>

  <div id="controls">
    <div class="param-group">
      <label for="audioFile">Carregar arquivo de áudio:</label>
      <input type="file" id="audioFile" accept="audio/*">
    </div>

    <div class="param-group">
      <label>Escala:</label>
      <select id="scale">
        <option value="Linear">Linear</option>
        <option value="Mel" selected>Mel</option>
      </select>
    </div>

    <div class="param-group">
      <label>fMin / fMax (Hz):</label>
      <input type="number" id="f_min" value="1" min="1" />
      <input type="number" id="f_max" value="20000" min="10" />
    </div>

    <div class="param-group">
      <label>FFT / Hop:</label>
      <select id="fftSize">
        <option>2048</option>
        <option selected>4096</option>
        <option>8192</option>
      </select>
      <input type="number" id="hopSize" value="2048" min="1" />
      <small>(hop em amostras; deixe ~fft/2 para começo)</small>
    </div>

    <div class="param-group">
      <label>Janela / Colormap:</label>
      <select id="window">
        <option>None</option>
        <option>Cosine</option>
        <option>Hanning</option>
        <option selected>BH7</option>
      </select>
      <select id="colormap">
        <option selected>hot</option>
        <option>jet</option>
        <option>viridis</option>
        <option>Greens</option>
        <option>turbo</option>
        <option>terrain</option>
        <option>RdPu</option>
        <option>binary</option>
      </select>
    </div>

    <div class="param-group">
      <label>Dimensões:</label>
      <input type="number" id="canvasHeight" value="400" min="100" step="50" />
      <input type="number" id="targetWidth" value="0" min="0" />
      <small>(0 usa window.innerWidth)</small>
    </div>

    <div class="param-group">
      <label>Ticks / Eixos:</label>
      <input type="number" id="nTicks" value="0" min="0" />
      <label><input type="checkbox" id="showFrequencyAxis" checked> Eixo de Frequência</label>
      <label><input type="checkbox" id="showTimeAxis" checked> Eixo de Tempo</label>
    </div>

    <div class="param-group">
      <label>Ganho / Faixa (dB):</label>
      <input type="number" id="gainDb" value="0" step="0.1" />
      <input type="number" id="rangeDb" value="80" step="0.1" />
    </div>

    <div class="param-group">
      <label>Filtro:</label>
      <select id="filterType">
        <option>none</option>
        <option>lowpass</option>
        <option>highpass</option>
        <option>bandpass</option>
        <option>notch</option>
      </select>
      <input type="text" id="filterCutoffs" placeholder="Ex: 300,3000" />
      <small>Para low/high: [cutoff]. Para band/notch: [low,high].</small>
    </div>

    <div class="param-group">
      <label><input type="checkbox" id="enablePitchDetection" checked> Pitch Tracking</label>
      <label><input type="checkbox" id="enableHarmonicsExtraction" checked> Harmônicos</label>
    </div>

    <button id="generateBtn">Gerar Espectrograma</button>
  </div>

  <div id="spectroContainer"></div>
  <div id="results"></div>

  <script type="module">
    import { SpectrogramGenerator, partial } from './dist/index.es.js';

    // Colormaps no window
    window.partial = partial;
    window.hot = partial('hot');
    window.jet = partial('jet');
    window.viridis = partial('viridis');
    window.Greens = partial('Greens');
    window.turbo = partial('turbo');
    window.terrain = partial('terrain');
    window.RdPu = partial('RdPu');
    window.binary = partial('binary');

    document.getElementById('generateBtn').addEventListener('click', async () => {
      const f = document.getElementById('audioFile');
      if (!f.files?.length) return;

      const file = f.files[0];
      const arrayBuffer = await file.arrayBuffer();
      const audioCtx = new AudioContext();
      const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
      const audioData = audioBuffer.getChannelData(0);

      const params = {
        sampleRate: audioBuffer.sampleRate,
        scaleType: document.getElementById('scale').value,
        fMin: parseFloat(document.getElementById('f_min').value),
        fMax: parseFloat(document.getElementById('f_max').value),
        fftSize: parseInt(document.getElementById('fftSize').value),
        hopSize: parseInt(document.getElementById('hopSize').value),
        windowType: document.getElementById('window').value,
        colormapName: document.getElementById('colormap').value,
        canvasHeight: parseInt(document.getElementById('canvasHeight').value),
        targetWidth: parseInt(document.getElementById('targetWidth').value),
        nTicks: parseInt(document.getElementById('nTicks').value),
        showFrequencyAxis: document.getElementById('showFrequencyAxis').checked,
        showTimeAxis: document.getElementById('showTimeAxis').checked,
        gainDb: parseFloat(document.getElementById('gainDb').value),
        rangeDb: parseFloat(document.getElementById('rangeDb').value),

        filterType: document.getElementById('filterType').value,
        filterCutoffs: (document.getElementById('filterCutoffs').value || '')
          .split(',')
          .map(s => s.trim())
          .filter(Boolean)
          .map(Number),

        enablePitchDetection: document.getElementById('enablePitchDetection').checked,
        enableHarmonicsExtraction: document.getElementById('enableHarmonicsExtraction').checked
      };

      const gen = new SpectrogramGenerator(params);
      const canvas = gen.generateSpectrogram(audioData);
      const container = document.getElementById('spectroContainer');
      container.innerHTML = '';
      container.appendChild(canvas);

      const resultsDiv = document.getElementById('results');
      resultsDiv.innerHTML = '';

      const png = gen.exportHighResPNG(audioData, 3);
      const link = document.createElement('a');
      link.href = png;
      link.download = 'spectrogram.png';
      link.textContent = 'Baixar PNG em alta resolução';
      resultsDiv.appendChild(link);

      if (params.enablePitchDetection) {
        const f0 = gen.detectPitch(audioData);
        const p = document.createElement('p');
        p.textContent = `Pitch (F0): ${f0.toFixed(2)} Hz`;
        resultsDiv.appendChild(p);
      }

      if (params.enableHarmonicsExtraction) {
        const { fundamental, harmonics } = gen.extractHarmonics(audioData);
        const p = document.createElement('p');
        p.textContent = `Fundamental: ${fundamental.toFixed(2)} Hz | Harmônicos: [${harmonics.map(h => h.toFixed(2)).join(', ')}]`;
        resultsDiv.appendChild(p);
      }
    });
  </script>
</body>
</html>

Dicas / Solução de problemas

  • CORS em file://: Sempre sirva via http:// (ex.: http-server .).
  • Cache do navegador: se estiver testando alterações locais, anexe um query param ao import:
    ./dist/index.es.js?v=\${Date.now()}.
  • Função não encontrada após build: confirme que está importando do dist gerado e que o servidor está servindo o arquivo atualizado (sem cache).

Changelog (resumo)

1.6.9

  • Eixo de tempo opcional com espaçamento de ticks automático (showTimeAxis, timeTickMinPx).
  • Hop size configurável (hopSize).
  • Escala Mel real na exibição (remapeamento vertical correto).
  • Normalização em dB ancorada no pico global com rangeDb (fallback seguro para 80).
  • Export PNG em alta resolução (exportHighResPNG).
  • Filtros FIR: lowpass, highpass, bandpass, notch.
  • Pitch tracking e extração de harmônicos.

Contribuindo

Contribuições são bem-vindas! Por favor, siga as diretrizes em CONTRIBUTING.md para fazer um pull request.

Licença

Distribuído sob a licença MIT. Veja LICENSE para mais informações.

Autores

Igor Nascimento - Desenvolvedor Principal - IMNascimento

Agradecimentos

Gostaríamos de expressar nossa sincera gratidão à empresa SophiaLabs pelo apoio inestimável no desenvolvimento de códigos open source. Sua dedicação incansável em fortalecer nossa comunidade e impulsionar o universo open source é uma fonte de constante inspiração.

Agradecemos, também, a Deus, cuja graça e orientação têm sido fundamentais em cada passo desta jornada, possibilitando conquistas e o contínuo aprimoramento de nossos projetos.

Muito obrigado a todos que, de alguma forma, colaboram para tornar esse trabalho possível.