1.5.8 • Published 4 months ago

@sophialabs/spectro v1.5.8

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

Build Status License Version

Introdução

Spectro é uma biblioteca escrita em TypeScript que gera espectrogramas a partir de dados de áudio (Float32Array). Ela utiliza a Transformada Rápida de Fourier (FFT) com diferentes funções janela e suporta mapeamento de cores (colormaps) com um conjunto de colormaps inspirados no Matplotlib.

Recursos

  • Geração de espectrogramas a partir de um array de áudio (single channel)
  • Configuração flexível de parâmetros:
    • Taxa de amostragem, faixa de frequência (fMin e fMax)
    • Tamanho do FFT e função janela (ex.: Blackman-Harris 7)
    • Seleção de escala (Linear ou Mel)
    • Escolha de colormap
    • Altura final do canvas e quantidade de ticks no eixo de frequência
    • Geração de .png do espectrograma de alta resolução
    • Ocultar o eixo de frequência Hz
    • Passar ganho inicial e faixa de ganho Db.
    • Passagens de filtro Passa alta, Passa Baixo, Passa Banda, Rejeita banda
    • Detecção de Frequencia "Pitch Tracking"
    • Extração de Harmônicos
  • Colormaps exportados e tipados (ex.: hot, jet, etc.)

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

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.

Exemplo de index.html

<!DOCTYPE html>
<html lang="pt">
<head>
  <meta charset="UTF-8">
  <title>Teste da Lib Spectro - Parâmetros Completos</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 - Parâmetros Completos</h1>
  <div id="controls">
    <!-- Seleção do arquivo de áudio -->
    <div class="param-group">
      <label for="audioFile">Carregar arquivo de áudio:</label>
      <input type="file" id="audioFile" accept="audio/*">
    </div>

    <!-- Parâmetros básicos para o espectrograma -->
    <div class="param-group">
      <label for="scale">Escala (Linear ou Mel):</label>
      <select id="scale">
        <option value="Linear">Linear</option>
        <option value="Mel" selected>Mel</option>
      </select>
    </div>
    <div class="param-group">
      <label for="f_min">Frequência Mínima (Hz):</label>
      <select id="f_min">
        <option value="1" selected>1</option>
        <option value="10">10</option>
        <option value="20">20</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
    </div>
    <div class="param-group">
      <label for="f_max">Frequência Máxima (Hz):</label>
      <select id="f_max">
        <option value="1000">1 KHz</option>
        <option value="2000">2 KHz</option>
        <option value="5000">5 KHz</option>
        <option value="10000">10 KHz</option>
        <option value="20000" selected>20 KHz</option>
      </select>
    </div>
    <div class="param-group">
      <label for="customFmax">Frequência Máxima Personalizada (Hz):</label>
      <input type="number" id="customFmax" min="1" placeholder="Ex: 15000">
      <small>(Se preenchido, sobrescreve o select acima)</small>
    </div>
    <div class="param-group">
      <label for="fftSize">Tamanho do Buffer (FFT):</label>
      <select id="fftSize">
        <option value="2048" selected>2048</option>
        <option value="4096">4096</option>
        <option value="8192">8192</option>
      </select>
    </div>
    <div class="param-group">
      <label for="window">Função Janela:</label>
      <select id="window">
        <option value="None">None</option>
        <option value="Cosine">Cosine</option>
        <option value="Hanning">Hanning</option>
        <option value="BH7" selected>Blackman Harris 7</option>
      </select>
    </div>
    <div class="param-group">
      <label for="colormap">Colormap:</label>
      <select id="colormap">
        <option value="hot" selected>hot</option>
        <option value="jet">jet</option>
        <option value="viridis">viridis</option>
        <option value="Greens">Greens</option>
        <option value="turbo">turbo</option>
        <option value="terrain">terrain</option>
        <option value="RdPu">RdPu</option>
        <option value="binary">binary</option>
      </select>
    </div>
    <div class="param-group">
      <label for="canvasHeight">Altura do Espectrograma (px):</label>
      <input type="number" id="canvasHeight" value="400" min="100" step="50">
    </div>
    <div class="param-group">
      <label for="targetWidth">Largura Final Desejada (px):</label>
      <input type="number" id="targetWidth" value="0" min="0">
      <small>(0 utiliza window.innerWidth)</small>
    </div>
    <div class="param-group">
      <label for="nTicks">Quantidade de Ticks no eixo (0 para cálculo automático):</label>
      <input type="number" id="nTicks" value="0" min="0">
    </div>
    <div class="param-group">
      <label for="showFrequencyAxis">
        <input type="checkbox" id="showFrequencyAxis"> Exibir Eixo de Frequência
      </label>
    </div>

    <!-- Parâmetros de ganho e normalização -->
    <div class="param-group">
      <label for="gainDb">Ganho (gainDb):</label>
      <input type="number" id="gainDb" value="0" step="0.1">
      <small>(Valor em dB. Use positivo para aumentar, negativo para reduzir)</small>
    </div>
    <div class="param-group">
      <label for="rangeDb">Intervalo (rangeDb):</label>
      <input type="number" id="rangeDb" value="0" step="0.1">
      <small>(Intervalo em dB para normalização; 0 mantém a escala original)</small>
    </div>

    <!-- Parâmetros de filtragem -->
    <div class="param-group">
      <label for="filterType">Tipo de Filtro:</label>
      <select id="filterType">
        <option value="none" selected>none</option>
        <option value="lowpass">lowpass</option>
        <option value="highpass">highpass</option>
        <option value="bandpass">bandpass</option>
        <option value="notch">notch</option>
      </select>
    </div>
    <div class="param-group">
      <label for="filterCutoffs">Frequências de Corte do Filtro:</label>
      <input type="text" id="filterCutoffs" placeholder="Ex: 300,3000">
      <small>(Valores separados por vírgula. Para lowpass/highpass, informe um valor)</small>
    </div>

    <!-- Parâmetros para funcionalidades extras -->
    <div class="param-group">
      <label for="enablePitchDetection">
        <input type="checkbox" id="enablePitchDetection"> Habilitar Detecção de Pitch
      </label>
      <small>(Se ativado, a frequência fundamental será calculada via autocorrelação)</small>
    </div>
    <div class="param-group">
      <label for="enableHarmonicsExtraction">
        <input type="checkbox" id="enableHarmonicsExtraction"> Habilitar Extração de Harmônicos
      </label>
      <small>(Se ativado, os harmônicos serão extraídos com base na frequência fundamental)</small>
    </div>

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

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

  <!-- Importa a lib compilada (index.js já reexporta os colormaps) -->
  <script type="module">
    import { SpectrogramGenerator, partial } from './dist/index.es.js'; // ou index.cjs.js

    // Expor as funções colormap globalmente para que a lib as encontre
    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 fileInput = document.getElementById('audioFile');
      if (!fileInput.files || fileInput.files.length === 0) {
        console.error('Nenhum arquivo selecionado.');
        return;
      }
      const file = fileInput.files[0];
      const arrayBuffer = await file.arrayBuffer();
      const audioCtx = new AudioContext();
      const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
      const audioData = audioBuffer.getChannelData(0);

      // Captura dos parâmetros do formulário
      const scale = document.getElementById('scale').value;
      const fMin = parseFloat(document.getElementById('f_min').value);
      const fMaxSelect = parseFloat(document.getElementById('f_max').value);
      const customFmax = document.getElementById('customFmax').value.trim();
      const fMax = customFmax !== '' ? parseFloat(customFmax) : fMaxSelect;
      const fftSize = parseInt(document.getElementById('fftSize').value);
      const windowType = document.getElementById('window').value;
      const colormapName = document.getElementById('colormap').value;
      const canvasHeight = parseInt(document.getElementById('canvasHeight').value);
      const targetWidth = parseInt(document.getElementById('targetWidth').value);
      const nTicks = parseInt(document.getElementById('nTicks').value);
      const showFrequencyAxis = document.getElementById('showFrequencyAxis').checked;
      const gainDb = parseFloat(document.getElementById('gainDb').value);
      const rangeDb = parseFloat(document.getElementById('rangeDb').value);
      const filterType = document.getElementById('filterType').value;
      const filterCutoffsInput = document.getElementById('filterCutoffs').value;
      // Converte valores de corte para um array de números (se houver)
      const filterCutoffs = filterCutoffsInput.trim() === ''
        ? []
        : filterCutoffsInput.split(',').map(val => parseFloat(val.trim()));
      const enablePitchDetection = document.getElementById('enablePitchDetection').checked;
      const enableHarmonicsExtraction = document.getElementById('enableHarmonicsExtraction').checked;

      // Cria o objeto de parâmetros, com valores padrão zerados/falsos
      const params = {
        sampleRate: audioBuffer.sampleRate,
        scaleType: scale,
        fMin: fMin,
        fMax: fMax,
        fftSize: fftSize,
        windowType: windowType,
        colormapName: colormapName,
        canvasHeight: canvasHeight,
        nTicks: nTicks,
        gainDb: gainDb,
        rangeDb: rangeDb,
        targetWidth: targetWidth,
        showFrequencyAxis: showFrequencyAxis,
        filterType: filterType,
        filterCutoffs: filterCutoffs,
        enablePitchDetection: enablePitchDetection,
        enableHarmonicsExtraction: enableHarmonicsExtraction,
      };

      // Cria a instância do gerador e gera o espectrograma
      const generator = new SpectrogramGenerator(params);
      const spectroCanvas = generator.generateSpectrogram(audioData);
      const container = document.getElementById('spectroContainer');
      container.innerHTML = '';
      container.appendChild(spectroCanvas);

      // Exporta imagem em alta resolução e cria link para download
      const pngDataUrl = generator.exportHighResPNG(audioData, 3);
      console.log('PNG gerado:', pngDataUrl);
      const link = document.createElement('a');
      link.href = pngDataUrl;
      link.download = 'spectrogram.png';
      link.textContent = 'Baixar PNG de alta resolução';
      document.body.appendChild(link);

      // Se a detecção de pitch estiver habilitada, chama o método detectPitch
      const resultsDiv = document.getElementById('results');
      resultsDiv.innerHTML = '';
      if (enablePitchDetection) {
        const fundamentalFreq = generator.detectPitch(audioData);
        const p1 = document.createElement('p');
        p1.textContent = `Frequência Fundamental detectada: ${fundamentalFreq.toFixed(2)} Hz`;
        resultsDiv.appendChild(p1);
      } else {
        const p1 = document.createElement('p');
        p1.textContent = 'Detecção de Pitch desabilitada.';
        resultsDiv.appendChild(p1);
      }

      // Se a extração de harmônicos estiver habilitada, chama o método extractHarmonics
      if (enableHarmonicsExtraction) {
        const { fundamental, harmonics } = generator.extractHarmonics(audioData);
        const p2 = document.createElement('p');
        p2.textContent = `Fundamental: ${fundamental.toFixed(2)} Hz, Harmônicos: [${harmonics.map(h => h.toFixed(2)).join(', ')}]`;
        resultsDiv.appendChild(p2);
      } else {
        const p2 = document.createElement('p');
        p2.textContent = 'Extração de Harmônicos desabilitada.';
        resultsDiv.appendChild(p2);
      }
    });
  </script>
</body>
</html>

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.

1.5.8

4 months ago

1.4.8

4 months ago

1.3.8

4 months ago

1.3.7

4 months ago

1.2.7

4 months ago

1.0.7

4 months ago

1.0.6

4 months ago

1.0.5

4 months ago

1.0.4

4 months ago

1.0.3

4 months ago

1.0.2

4 months ago