Skip to content

Sound.java: Pure Sinusoidal and FSK Modulated Sound Generator

Giacomo G. edited this page Mar 7, 2025 · 1 revision

This page documents the Sound class found in the src.com.sstv package. The class was originally developed to generate pure sinusoidal tones. Later, as I kept on further documenting and researching, it was extended to support FSK (Frequency-Shift Keying) modulation, which is useful when a varying frequency is needed for signaling.

Overview

The Sound class provides methods to generate audio samples for:

  • Pure sinusoidal tones: A single frequency sine wave.
  • FSK modulated signals: A tone where the frequency linearly interpolates between a start and an end value.
  • Scan lines: Generating a series of frequencies from an array to create a scanning sound effect.
  • Buffer playback: Playing a pre-generated audio buffer.

Key Fields

  • SAMPLE_RATE: The sample rate (44100 Hz) used for audio processing.
  • frequency: The base frequency for pure tones.
  • startFreq & endFreq: Frequencies used for the start and end of the FSK modulated tone.
  • duration: Duration of the tone in milliseconds.
  • line: An instance of SourceDataLine for audio output.

Constructors

There are two constructors in the class:

  1. Pure Sinusoidal Tone Constructor

    public Sound(double frequency, int duration)
    • Initializes a sound object with a fixed frequency and duration.
    • Originally, this was the only available option.
  2. FSK Modulation Constructor

    public Sound(double startFreq, double endFreq, int duration)
    • Initializes a sound object for FSK modulation.
    • The frequency field is initially set to startFreq to avoid null references.
    • This extension was added when it became clear that a simple sinusoidal wave was not enough for all applications.

Methods

renderToBuffer

public static void renderToBuffer(ByteArrayOutputStream buffer, double freq, int durationMs)
  • Generates a sine wave tone and writes the audio samples into a ByteArrayOutputStream.
  • Calculates the number of samples based on the provided duration.

playTone

public void playTone() throws LineUnavailableException
  • Generates and plays a pure sine wave tone.
  • Converts the generated sine values to 16-bit PCM audio.
  • Utilizes Java's audio system to play the sound.

playFSK

public void playFSK() throws LineUnavailableException
  • Plays an FSK modulated signal.
  • Linearly interpolates the frequency from startFreq to endFreq over the duration.
  • Applies an amplitude envelope at the beginning and end (first and last 5% of samples) to avoid abrupt transitions.

playScanLine

public void playScanLine(double[] frequencies, int scanDurationMs) throws LineUnavailableException
  • Plays a scan-line sound where the frequency is chosen from an array of frequencies.
  • Each “pixel” or frequency segment is assigned a portion of the total duration.

playBuffer

public static void playBuffer(byte[] buffer) throws LineUnavailableException
  • Plays an audio buffer directly using the defined audio format.

Mathematical Formulas

Pure Sinusoidal Wave Generation

For a given frequency $f$ and sample index $i$, the phase angle is calculated as:

$$ \text{angle}_i = \frac{2\pi \cdot f \cdot i}{\text{sample rate}} $$

The resulting sample value is then computed as:

$$ \text{sample}_i = \sin(\text{angle}_i) \times A $$

where:

  • $A$ is the amplitude (in the code, this is Short.MAX_VALUE).

FSK Modulation

For FSK modulation, the frequency is linearly interpolated between a start frequency ( f_{\text{start}} ) and an end frequency ( f_{\text{end}} ). For a total of ( N ) samples, the frequency at sample ( i ) is given by:

$$ f_i = f_{\text{start}} + \left( \frac{f_{\text{end}} - f_{\text{start}}}{N} \right) \cdot i $$

The sample value at index ( i ) is then calculated as:

$$ \text{sample}_i = \sin\left(\frac{2\pi \cdot f_i \cdot i}{\text{sample rate}}\right) \times A \times E(i) $$

where ( E(i) ) is an amplitude envelope function designed to ramp the amplitude at the start and end of the tone. In this implementation, the envelope is applied as follows:

$$ E(i) = \begin{cases} \frac{i}{0.05N} & \text{for } i < 0.05N, \\ 1.0 & \text{for } 0.05N \le i \le 0.95N, \\ \frac{N-i}{0.05N} & \text{for } i > 0.95N. \end{cases} $$

This ensures a smooth ramp-up and ramp-down, minimizing clicks at the boundaries.

Development History

  • Initial Implementation:
    The class was first created to generate and play pure sinusoidal waves. This version was sufficient for applications that only required a single tone with a fixed frequency.

  • Extension to FSK Modulation:
    Later, it was discovered that FSK modulation was needed for certain signal processing tasks. The class was extended with additional fields (startFreq and endFreq) and a dedicated method (playFSK()) to support frequency interpolation. This allows the generation of tones where the frequency changes over time, which is essential for FSK-based applications.

Conclusion

The Sound class is a versatile tool for audio generation in Java, providing both simple sinusoidal tone generation and more complex FSK modulation. The inclusion of mathematical formulas in the documentation ensures that the underlying signal processing is transparent and can be adjusted if needed.