Skip to content

grunnverk/audio-tools

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

42 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@grunnverk/audio-tools

Professional Audio Recording & Transcription Toolkit for Node.js

License npm version TypeScript

Voice-driven development workflows made simple

Features β€’ Installation β€’ Quick Start β€’ Examples β€’ API Reference β€’ Documentation


🎯 Overview

@grunnverk/audio-tools is a comprehensive TypeScript library for recording, transcribing, and managing audio in Node.js applications. Built for developers who want to integrate voice-driven workflows, voice notes, audio documentation, or AI-powered transcription into their projects.

Key Highlights

  • πŸŽ™οΈ High-Quality Recording - Capture audio from any input device with configurable settings
  • 🎬 Visual Countdown Timers - Professional recording countdowns with ANSI colors and beeps
  • πŸ”Š Device Management - List, select, and configure audio input devices
  • πŸ€– AI Transcription - Powered by OpenAI's Whisper API for accurate transcription
  • πŸ“¦ Archive Management - Timestamped archiving of audio files and transcripts
  • πŸ”§ Fully Typed - Complete TypeScript definitions for excellent IDE support
  • πŸͺ΅ Flexible Logging - Optional Winston logger integration
  • πŸš€ Zero Config - Sensible defaults, works out of the box

✨ Features

Audio Recording

  • βœ… Cross-platform support (macOS, Linux, Windows)
  • βœ… Device selection and configuration
  • βœ… Configurable sample rates and formats (WAV, MP3, FLAC)
  • βœ… Duration limits and manual stop controls
  • βœ… Custom output paths
  • βœ… Real-time recording status

Visual Countdown Timers

  • βœ… ANSI color-coded terminal display
  • βœ… In-place updating (no screen clutter)
  • βœ… Audio beeps at configurable intervals
  • βœ… Warning colors when time is low
  • βœ… Callback support for custom behaviors
  • βœ… Graceful cleanup and process handling

Transcription & Archiving

  • βœ… OpenAI Whisper API integration
  • βœ… Automatic file archiving with timestamps
  • βœ… Transcript preservation in Markdown format
  • βœ… Batch processing support
  • βœ… Error recovery and retry logic

πŸ“¦ Installation

npm install @grunnverk/audio-tools

Peer Dependencies

The library has optional peer dependencies for enhanced functionality:

# For logging (recommended)
npm install winston

# For shared utilities (optional)
npm install @grunnverk/shared

System Requirements

  • Node.js: 18.x or later
  • Operating System: macOS, Linux, or Windows
  • Audio Input: Microphone or audio input device

πŸš€ Quick Start

Basic Recording

import { recordAudio } from '@grunnverk/audio-tools';

// Record up to 60 seconds of audio
const result = await recordAudio({
  duration: 60,
  countdownDelay: 3,
});

console.log('Audio recorded:', result.filePath);
console.log('Duration:', result.duration, 'seconds');
console.log('File size:', result.fileSize, 'bytes');

Record and Transcribe

import { recordAudio, transcribeAudio, archiveAudio } from '@grunnverk/audio-tools';

// Record audio
const recording = await recordAudio({ duration: 120 });

// Transcribe with OpenAI Whisper
const transcript = await transcribeAudio(recording.filePath);

// Archive both audio and transcript with timestamps
const archive = await archiveAudio(
  recording.filePath,
  transcript,
  'output/recordings'
);

console.log('Transcript:', transcript);
console.log('Archived to:', archive.audioPath);

Interactive Device Selection

import { selectDeviceInteractive, recordAudio } from '@grunnverk/audio-tools';

// Let user select audio device interactively
const device = await selectDeviceInteractive();

// Record with selected device
const result = await recordAudio({ duration: 60 });

Countdown Timer

import { CountdownTimer } from '@grunnverk/audio-tools';

const timer = new CountdownTimer({
  durationSeconds: 30,
  beepAt30Seconds: true,
  redAt30Seconds: true,
  onTick: (remaining) => {
    console.log(`${remaining} seconds remaining`);
  },
  onComplete: () => {
    console.log('Time\'s up!');
  }
});

await timer.start();

πŸ“– Documentation

Comprehensive documentation is available:

πŸ“š Examples

The examples/ directory contains comprehensive, runnable examples:

Example Description File
Basic Recording Simple audio recording with default settings basic-recording.ts
Record & Transcribe Complete workflow with transcription and archiving record-and-transcribe.ts
Countdown Demo Visual countdown timer demonstrations countdown-demo.ts
Device Selection List and select audio input devices device-selection.ts
Custom Output Specify custom paths and filenames custom-output-path.ts

Running Examples

cd examples
npm install
npm run basic          # Basic recording
npm run transcribe     # Record and transcribe (requires OPENAI_API_KEY)
npm run countdown      # Countdown timer demos
npm run devices        # Device selection
npm run custom-path    # Custom output paths

πŸ“– API Reference

Recording Functions

recordAudio(options?: RecordingOptions): Promise<RecordingResult>

Record audio from an input device.

Options:

interface RecordingOptions {
  device?: AudioDevice | string;     // Audio device (optional, uses default)
  duration?: number;                 // Max duration in seconds (optional)
  outputPath?: string;               // Output file path (optional, generates temp)
  countdownDelay?: number;          // Countdown delay (default: 3)
  sampleRate?: number;              // Sample rate in Hz (default: 44100)
  format?: 'wav' | 'mp3' | 'flac';  // Audio format (default: 'wav')
}

Returns:

interface RecordingResult {
  filePath: string;   // Path to recorded file
  duration: number;   // Recording duration in seconds
  fileSize: number;   // File size in bytes
}

Example:

const result = await recordAudio({
  duration: 60,
  sampleRate: 48000,
  format: 'wav',
  countdownDelay: 3
});

archiveAudio(audioPath: string, transcript: string, outputDir?: string)

Archive audio file with its transcription.

Parameters:

  • audioPath: Path to the original audio file
  • transcript: Transcribed text content
  • outputDir: Directory to save archived files (default: 'output')

Returns:

{
  audioPath: string;      // Path to archived audio file
  transcriptPath: string; // Path to archived transcript
}

Example:

const archive = await archiveAudio(
  'recording.wav',
  'This is the transcribed text...',
  'output/archives'
);

// Creates files like:
// - output/archives/250701-1430-review-audio.wav
// - output/archives/250701-1430-review-transcript.md

deleteAudio(audioPath: string): Promise<void>

Delete an audio file safely.

Example:

await deleteAudio('temp-recording.wav');

getAudioDuration(audioPath: string): Promise<number | null>

Get the duration of an audio file (currently returns null, planned feature).


Device Functions

listAudioDevices(): Promise<AudioDevice[]>

List all available audio input devices.

Returns:

interface AudioDevice {
  id: string;
  name: string;
  isDefault: boolean;
}

Example:

const devices = await listAudioDevices();
devices.forEach(device => {
  console.log(`${device.name} (${device.id})`);
  if (device.isDefault) {
    console.log('  βœ“ Default device');
  }
});

getDefaultDevice(): Promise<AudioDevice | null>

Get the system's default audio input device.

Example:

const device = await getDefaultDevice();
if (device) {
  console.log('Default device:', device.name);
}

findDevice(idOrName: string): Promise<AudioDevice | null>

Find a device by its ID or name.

Example:

const device = await findDevice('MacBook Pro Microphone');
if (device) {
  console.log('Found device:', device.id);
}

selectDeviceInteractive(): Promise<string>

Present an interactive menu to select an audio device.

Example:

const deviceId = await selectDeviceInteractive();
console.log('Selected device:', deviceId);

Transcription Functions

transcribeAudio(audioPath: string): Promise<string>

Transcribe an audio file using OpenAI's Whisper API.

Requirements:

  • OPENAI_API_KEY environment variable must be set
  • Audio file must be in a supported format (WAV, MP3, FLAC, etc.)

Example:

const transcript = await transcribeAudio('recording.wav');
console.log('Transcription:', transcript);

Countdown Timer Classes

CountdownTimer

A visual countdown timer with customizable behavior.

Constructor Options:

interface CountdownOptions {
  durationSeconds: number;          // Duration in seconds
  beepAt30Seconds?: boolean;        // Beep at 30s (default: true)
  redAt30Seconds?: boolean;         // Red color at 30s (default: true)
  onTick?: (remaining: number) => void;   // Called every second
  onComplete?: () => void;          // Called when complete
  clearOnComplete?: boolean;        // Clear display when done (default: false)
}

Methods:

  • start(): Promise<void> - Start the countdown
  • stop(): void - Stop the countdown
  • getRemainingSeconds(): number - Get remaining time
  • destroy(): void - Clean up resources
  • isTimerDestroyed(): boolean - Check if destroyed

Example:

const timer = new CountdownTimer({
  durationSeconds: 60,
  beepAt30Seconds: true,
  redAt30Seconds: true,
  onTick: (remaining) => {
    if (remaining % 10 === 0) {
      console.log(`${remaining} seconds left`);
    }
  },
  onComplete: () => {
    console.log('Recording complete!');
  }
});

await timer.start();

startCountdown(options: CountdownOptions): Promise<void>

Convenience function to create and start a countdown timer.

Example:

await startCountdown({
  durationSeconds: 30,
  onComplete: () => console.log('Done!')
});

createAudioRecordingCountdown(seconds: number): CountdownTimer

Create a countdown timer with sensible defaults for audio recording.

Example:

const timer = createAudioRecordingCountdown(120);
await timer.start();

Utility Functions

getTimestampedArchivedAudioFilename(extension?: string): string

Generate a timestamped filename for archived audio.

Example:

const filename = getTimestampedArchivedAudioFilename('.mp3');
// Returns: "250701-1430-review-audio.mp3"

getTimestampedArchivedTranscriptFilename(): string

Generate a timestamped filename for archived transcripts.

Example:

const filename = getTimestampedArchivedTranscriptFilename();
// Returns: "250701-1430-review-transcript.md"

Logging

setLogger(logger: Logger): void

Set a custom Winston logger instance.

Example:

import { setLogger } from '@grunnverk/audio-tools';
import { createLogger, format, transports } from 'winston';

const logger = createLogger({
  level: 'debug',
  format: format.combine(
    format.timestamp(),
    format.colorize(),
    format.simple()
  ),
  transports: [
    new transports.Console(),
    new transports.File({ filename: 'audio-tools.log' })
  ]
});

setLogger(logger);

getLogger(): Logger

Get the current logger instance.

Example:

import { getLogger } from '@grunnverk/audio-tools';

const logger = getLogger();
logger.info('Starting recording...');

🎬 Complete Usage Example

Here's a complete example showing a typical workflow:

import {
  recordAudio,
  transcribeAudio,
  archiveAudio,
  deleteAudio,
  CountdownTimer,
  setLogger
} from '@grunnverk/audio-tools';
import { createLogger, format, transports } from 'winston';
import { config } from 'dotenv';

// Load environment variables
config();

// Configure logging
const logger = createLogger({
  level: 'info',
  format: format.combine(
    format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    format.colorize(),
    format.printf(({ timestamp, level, message }) =>
      `${timestamp} [${level}]: ${message}`
    )
  ),
  transports: [
    new transports.Console(),
    new transports.File({ filename: 'audio-tools.log' })
  ]
});

setLogger(logger);

async function recordAndTranscribeVoiceNote() {
  let audioPath: string | null = null;

  try {
    console.log('πŸŽ™οΈ  Voice Note Recorder');
    console.log('======================\n');

    // Step 1: Countdown
    console.log('Get ready to speak...\n');
    const countdown = new CountdownTimer({
      durationSeconds: 3,
      beepAt30Seconds: false,
      clearOnComplete: true
    });
    await countdown.start();

    // Step 2: Record
    console.log('πŸ”΄ Recording... (Press ENTER to stop)\n');
    const recording = await recordAudio({
      duration: 300, // 5 minutes max
      sampleRate: 48000,
      format: 'wav'
    });

    audioPath = recording.filePath;
    logger.info(`Recorded ${recording.duration.toFixed(2)}s of audio`);

    // Step 3: Transcribe
    console.log('\nπŸ“ Transcribing...');
    const transcript = await transcribeAudio(audioPath);

    console.log('\nβœ… Transcript:\n');
    console.log('─'.repeat(60));
    console.log(transcript);
    console.log('─'.repeat(60));

    // Step 4: Archive
    console.log('\nπŸ’Ύ Archiving...');
    const archive = await archiveAudio(
      audioPath,
      transcript,
      'output/voice-notes'
    );

    logger.info(`Archived to: ${archive.audioPath}`);
    logger.info(`Transcript saved: ${archive.transcriptPath}`);

    // Step 5: Cleanup
    await deleteAudio(audioPath);
    logger.info('Temporary file deleted');

    console.log('\nβœ… Voice note saved successfully!');

  } catch (error) {
    logger.error('Failed to process voice note:', error);

    // Cleanup on error
    if (audioPath) {
      try {
        await deleteAudio(audioPath);
      } catch {
        // Ignore cleanup errors
      }
    }

    throw error;
  }
}

// Run the example
recordAndTranscribeVoiceNote().catch(error => {
  console.error('\n❌ Error:', error.message);
  process.exit(1);
});

πŸ”§ Configuration

Environment Variables

  • OPENAI_API_KEY - Required for transcription functionality
  • NO_COLOR - Disable ANSI colors in terminal output
  • TERM - Terminal type detection for ANSI support

Audio Preferences

The library uses @utilarium/unplayable which stores audio device preferences in:

~/.unplayable/audio-preferences.json

You can manually edit this file to set default devices.

πŸ—οΈ Architecture

Dependencies

Platform Support

Platform Status Backend
macOS βœ… Supported CoreAudio
Linux βœ… Supported ALSA/PulseAudio
Windows βœ… Supported WASAPI

πŸ§ͺ Testing

The library includes comprehensive test coverage:

# Run tests
npm test

# Run tests with coverage
npm run test

# Watch mode
npm run test -- --watch

πŸ› οΈ Development

Build from Source

# Clone the repository
git clone https://github.com/grunnverk/audio-tools.git
cd audio-tools

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Lint
npm run lint

Project Structure

audio-tools/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ countdown.ts      # Countdown timer utilities
β”‚   β”œβ”€β”€ devices.ts        # Audio device management
β”‚   β”œβ”€β”€ recording.ts      # Recording and archiving
β”‚   β”œβ”€β”€ transcription.ts  # Transcription wrapper
β”‚   β”œβ”€β”€ types.ts          # TypeScript definitions
β”‚   └── index.ts          # Main exports
β”œβ”€β”€ tests/                # Test files
β”œβ”€β”€ examples/             # Usage examples
β”œβ”€β”€ dist/                 # Compiled output
└── package.json

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes with tests
  4. Run tests: npm test
  5. Commit with clear messages
  6. Push and open a Pull Request

πŸ“ License

Apache-2.0 License - see LICENSE file for details.

πŸ”— Related Projects

πŸ’¬ Support

πŸ“Š Changelog

See RELEASE_NOTES.md for version history and changes.


Made with ❀️ by Tim O'Brien

⭐ Star this repo if you find it useful!

About

Audio recording tools for voice-driven development workflows.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors