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 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-morse-audio

v1.2.0

Published

React components and hooks for morse code audio with contest simulation

Readme

react-morse-audio

React components and hooks for morse code audio playback. Built on top of morse-audio.

Not using React? Use morse-audio directly for vanilla JS, Vue, Angular, or Node.js.

Features

  • Component API: Drop-in <MorseAudio> component with ref support
  • Hook API: useMorseAudio for full programmatic control
  • Contest Simulator: useContestAudio for real-time streaming with pileups
  • Radio Effects: QRN (static), QSB (fading), and realistic HF simulation
  • TypeScript: Full type definitions included

Installation

npm install react-morse-audio

Quick Start

Component (Simplest)

import { MorseAudio } from 'react-morse-audio';

function App() {
  return <MorseAudio text="CQ CQ CQ" wpm={20} autoPlay />;
}

Hook (Programmatic Control)

import { useMorseAudio } from 'react-morse-audio';

function MorsePlayer() {
  const { play, stop, status } = useMorseAudio({
    text: 'HELLO WORLD',
    wpm: 25,
  });

  return (
    <div>
      <button onClick={play} disabled={status === 'playing'}>Play</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

Contest Simulator (Real-time Streaming)

import { useContestAudio } from 'react-morse-audio';

function ContestSimulator() {
  const {
    start,
    stop,
    isRunning,
    playStation,
    playSidetone,
    setQRN,
  } = useContestAudio({
    qrn: { snr: 15 },
    bandwidth: 500,
  });

  const handlePileup = async () => {
    // Play stations through the "receiver" (with noise/effects)
    await playStation({
      text: 'W1ABC',
      wpm: 25,
      frequencyOffset: -100,
      signalStrength: -6,
    });
  };

  const handleSend = async () => {
    // Play your sidetone (clean, loud)
    await playSidetone({ text: 'W1?', wpm: 25 });
  };

  return (
    <div>
      <button onClick={isRunning ? stop : start}>
        {isRunning ? 'Stop' : 'Start'}
      </button>
      <button onClick={handlePileup} disabled={!isRunning}>
        Generate Pileup
      </button>
      <button onClick={handleSend} disabled={!isRunning}>
        Send
      </button>
    </div>
  );
}

API Reference

<MorseAudio> Component

import { MorseAudio, MorseAudioRef } from 'react-morse-audio';

<MorseAudio
  ref={morseRef}
  text="CQ CQ CQ"
  wpm={20}
  fwpm={15}                    // Farnsworth spacing (optional)
  frequency={700}              // Tone Hz (400-1200)
  preDelay={300}               // Silence before (ms)
  postDelay={100}              // Silence after (ms)
  radioEffects={{
    qrn: { snr: 15 },          // Static noise
    qsb: { depth: 0.5, rate: 0.2 },  // Fading
  }}
  autoPlay={true}
  onPlay={() => {}}
  onComplete={() => {}}
  onError={(err) => {}}
  onStatusChange={(status) => {}}
/>

Ref Methods

interface MorseAudioRef {
  play(): void;
  stop(): void;
  replay(): void;
  status: MorsePlaybackStatus;
  duration: number | null;
}

useMorseAudio Hook

const {
  play,      // Start playback
  stop,      // Stop playback
  replay,    // Restart from beginning
  status,    // 'idle' | 'loading' | 'ready' | 'playing' | 'completed' | 'error'
  duration,  // Audio duration in seconds
} = useMorseAudio({
  text: 'HELLO',
  wpm: 20,
  autoPlay: false,
  onComplete: () => console.log('Done!'),
});

useContestAudio Hook

Real-time streaming audio for contest simulation.

const {
  // Engine state
  status,           // 'stopped' | 'starting' | 'running' | 'error'
  isRunning,        // boolean
  activeStations,   // Currently playing station info
  isSending,        // True while sidetone plays

  // Lifecycle
  start,            // Start the audio engine
  stop,             // Stop everything

  // Receiver controls
  setQRN,           // setQRN({ snr: 15 }) or setQRN(null) to disable
  setBandwidth,     // setBandwidth(500)
  setCenterFrequency,
  setReceiverVolume,
  setSidetoneFrequency,
  setSidetoneVolume,

  // Playback
  playStation,      // Play a station through the receiver
  playSidetone,     // Play your own sending (clean, loud)
  stopStation,      // Stop a specific station
  stopSidetone,
  stopAllStations,
} = useContestAudio({
  qrn: { snr: 15 },
  bandwidth: 500,
  sidetoneVolume: 0.8,
  receiverVolume: 0.5,
  onStationComplete: (id) => console.log('Station done:', id),
  onSidetoneComplete: () => console.log('Sidetone done'),
});

Playing Stations

await playStation({
  id: 'w1abc',                  // Optional unique ID
  text: 'W1ABC',
  wpm: 25,
  fwpm: 20,                     // Optional Farnsworth
  frequencyOffset: -100,        // Hz from center (-500 to +500)
  signalStrength: -6,           // dB relative to S9 (-30 to +20)
  effects: {
    rayleigh: { bandwidth: 0.5, depth: 0.5 },  // HF fading
    flutter: { rate: 15, depth: 0.3 },         // Auroral
    chirp: { deviation: 20, timeConstant: 30 }, // Freq drift
    buzz: { frequency: 60, amplitude: 0.1 },   // AC hum
  },
  onComplete: () => {},
});

Playing Sidetone

await playSidetone({
  text: 'TU 73',
  wpm: 25,
  frequency: 700,    // Optional override
  volume: 0.9,       // Optional override
  onComplete: () => {},
});

Playback Status

type MorsePlaybackStatus =
  | 'idle'       // No audio loaded
  | 'loading'    // Generating audio
  | 'ready'      // Ready to play
  | 'playing'    // Currently playing
  | 'completed'  // Finished
  | 'error';     // Error occurred

Constants

Re-exported from morse-audio for convenience:

import {
  MIN_WPM, MAX_WPM, DEFAULT_WPM,
  MIN_FREQUENCY, MAX_FREQUENCY, DEFAULT_FREQUENCY,
  MIN_SNR, MAX_SNR, DEFAULT_SNR,
  MIN_FADE_DEPTH, MAX_FADE_DEPTH, DEFAULT_FADE_DEPTH,
  MIN_FADE_RATE, MAX_FADE_RATE, DEFAULT_FADE_RATE,
} from 'react-morse-audio';

Examples

Quick Play Buttons

function QuickPlay() {
  const [phrase, setPhrase] = useState('');

  return (
    <div>
      {['SOS', 'CQ', '73'].map(p => (
        <button key={p} onClick={() => setPhrase(p)}>{p}</button>
      ))}
      {phrase && (
        <MorseAudio
          text={phrase}
          wpm={20}
          autoPlay
          onComplete={() => setPhrase('')}
        />
      )}
    </div>
  );
}

With Radio Effects UI

function RadioDemo() {
  const [snr, setSnr] = useState(20);

  return (
    <div>
      <label>
        SNR: {snr} dB
        <input
          type="range"
          min={-6}
          max={40}
          value={snr}
          onChange={e => setSnr(+e.target.value)}
        />
      </label>
      <MorseAudio
        text="TEST"
        wpm={20}
        radioEffects={{ qrn: { snr } }}
      />
    </div>
  );
}

Farnsworth Learning Mode

<MorseAudio
  text="PARIS"
  wpm={25}    // Fast character speed
  fwpm={10}   // Slow spacing
/>

Browser Support

  • useMorseAudio / MorseAudio: All modern browsers
  • useContestAudio: Requires AudioWorklet (Chrome 66+, Firefox 76+, Safari 14.1+)

TypeScript

import type {
  MorseAudioProps,
  MorseAudioRef,
  MorsePlaybackStatus,
  UseMorseAudioOptions,
  UseContestAudioOptions,
  UseContestAudioReturn,
  PlayStationOptions,
  PlaySidetoneOptions,
  RadioEffectsOptions,
  StationEffectsOptions,
} from 'react-morse-audio';

Related

License

MIT