News:

NetSurf: the first new browser for OPENSTEP in decades!

Main Menu

What Needs to be done for a NeXT Emulator

Started by andreas_g, Sep 08, 2025, 04:47 PM

Previous topic - Next topic

mikeboss

IMHO it's always better to capture the unmolested data. filters can easily be applied later if desired.

andreas_g

This makes sense for me too. Volume can be adjusted easily during playback to the recorded data itself. Filters can also be applied and the current low-pass filter does not match real hardware anyway.

But one issue remains: The Soundbox accepts 44,1 and 22,05 kHz audio streams. Depending on setup it performs upsampling of 22,05 kHz data using sample repetition or zero-filling. Probably this step should be taken before saving the sound?

Rhetorica

Yes, just export 44.1 kHz. If someone truly needs to recover the 22050 Hz audio they can always resample it in an editor.
WARNING: preposterous time in Real Time Clock -- CHECK AND RESET THE DATE!

andreas_g

Thank you for the response. So I'll change recording to save the data before applying volume and filter. This change will be available in the next version of Previous.

Are there any audio experts in here? While trying to find out how to improve the low-pass filter I realized that in fact I need a de-emphasis filter for CD-standard emphasised sound. In real hardware the filter (analog implementation) is included in the Philips SAA7323 DAC inside the Soundbox. It is enabled via the DEC pin of the chip.

Does anyone have an idea how to implement de-emphasis for CD standard emphasised audio at 44,1 kHz?


ZombiePhysicist

I'm not expert.

I just asked some AI and it suggested what follows which may be garbage:


But maybe
  • [color=rgba(0, 0, 0, 0.96)]JUCE (C++)
    [/color]
    • [color=rgba(0, 0, 0, 0.96)]Cross‑platform, integrates with Core Audio on macOS. Use dsp::IIR::Coefficients anddsp::IIR::Filter (or dsp::ProcessorChain) for SOS processing; ready for plugin or standalone app.[/color]
[color=rgba(0, 0, 0, 0.96)]Ready-to-use coefficient approach (44.1 kHz)[/color]
  • [color=rgba(0, 0, 0, 0.96)]Use two first-order sections (cascade) converted via bilinear transform for τ = 50 µs and 15 µs.Implement as two 1st-order IIRs or combine into one biquad (SOS recommended).[/color]
[color=rgba(0, 0, 0, 0.96)]One combined biquad (Direct Form I, a0 = 1) for 44.1 kHz (use these in JUCE/vDSP/AudioUnit IIR APIs as b0,b1,b2 ; a0,a1,a2):[/color]
[color=rgba(0, 0, 0, 0.96)]b0 = 0.730944081 b1 = -1.432852847 b2 = 0.701908766 a0 = 1.000000000 a1 = -1.421865094 a2 = 0.711709612[/color]
[color=rgba(0, 0, 0, 0.96)]Implementation notes[/color]
  • [color=rgba(0, 0, 0, 0.96)]Use SOS/biquad routines (JUCE dsp::IIR, vDSP_biquadm, AudioUnit DSP code) to ensure numericstability.[/color]
  • [color=rgba(0, 0, 0, 0.96)]Normalize or verify 1 kHz reference gain if needed.[/color]
  • [color=rgba(0, 0, 0, 0.96)]Test with sweep and reference emphasis test tones.[/color]

Below is a minimal JUCE 7+ example demonstrating how to apply the de‑emphasis biquad in a standalone or plugin processor using JUCE's dsp::IIR::Filter. It loads the provided biquad coefficients into dsp::IIR::Coefficients and processes audio in processBlock.

Paste into your AudioProcessor's header/cpp or into a minimal JUCE Console/Audio plugin project.
Key points:
  • Sample rate must be 44100 Hz (coefficients precomputed for 44.1 kHz).
  • The coefficients are Direct Form I (a0 = 1) as b0,b1,b2 ; a0,a1,a2.


AudioProcessor.h (excerpt)

#pragma once

#include <JuceHeader.h>

class DeEmphasisProcessor  : public juce::AudioProcessor
{
public:
    DeEmphasisProcessor();
    ~DeEmphasisProcessor() override;

    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    void releaseResources() override;
    void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;

    // usual AudioProcessor boilerplate...
private:
    juce::dsp::IIR::Filter<float> deEmphasisFilter;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DeEmphasisProcessor)
};

AudioProcessor.h (excerpt)
#pragma once
#include <JuceHeader.h>

class DeEmphasisProcessor  : public juce::AudioProcessor
{
public:
    DeEmphasisProcessor();
    ~DeEmphasisProcessor() override;

    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    void releaseResources() override;
    void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;

    // usual AudioProcessor boilerplate...
private:
    juce::dsp::IIR::Filter<float> deEmphasisFilter;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DeEmphasisProcessor)
};

AudioProcessor.cpp (excerpt)
#include "AudioProcessor.h"

DeEmphasisProcessor::DeEmphasisProcessor()
{
    // constructor...
}

DeEmphasisProcessor::~DeEmphasisProcessor() = default;

void DeEmphasisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    juce::dsp::ProcessSpec spec;
    spec.sampleRate = sampleRate;
    spec.maximumBlockSize = static_cast<uint32> (samplesPerBlock);
    spec.numChannels = static_cast<uint32> (getTotalNumOutputChannels());

    deEmphasisFilter.reset();
    deEmphasisFilter.prepare(spec);

    // Ensure sample rate is 44.1 kHz (coeffs provided for 44100)
    if (std::abs(sampleRate - 44100.0) > 1.0)
    {
        // You can recompute coefficients for other rates; here we assert for clarity.
        jassertfalse;
    }

    // Coefficients (Direct Form I, a0 = 1)
    const double b0 = 0.730944081;
    const double b1 = -1.432852847;
    const double b2 = 0.701908766;
    const double a0 = 1.0;
    const double a1 = -1.421865094;
    const double a2 = 0.711709612;

    // JUCE expects coefficients as (b0,b1,b2,a0,a1,a2) for IIR::Coefficients::makeDirectFormI
    auto coeffs = juce::dsp::IIR::Coefficients<float>::makeDirectFormI(
        (float)b0, (float)b1, (float)b2, (float)a0, (float)a1, (float)a2);

    *deEmphasisFilter.state = *coeffs; // load coefficients into filter state
}

void DeEmphasisProcessor::releaseResources()
{
    deEmphasisFilter.reset();
}

void DeEmphasisProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
{
    juce::dsp::AudioBlock<float> block (buffer);
    juce::dsp::ProcessContextReplacing<float> context (block);

    // Process in-place (same coefficients used for all channels)
    deEmphasisFilter.process(context);
}





[color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]Notes and suggestions[/color][/color]
  • [color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]For different sample rates, recompute coefficients via bilinear transform or design an IIR from theanalog poles/zeros for the new sample rate.[/color][/color]
  • [color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]For better numeric stability use an SOS cascade of first‑order sections (two 1st‑order filters) implement as two dsp::IIR::Filter instances in series.[/color][/color]
  • [color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]In plugins, ensure processing is real‑time safe and avoid heavy allocations inprepareToPlay/processBlock.[/color][/color]
[color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]If you want, I can generate:[/color][/color]
  • [color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]the two 1st‑order section coefficients and an example chaining two dsp::IIR::Filter instances, or[/color][/color]
  • [color=rgba(0, 0, 0, 0.96)][color=var(--ds-text-primary)]code that recomputes coefficients at runtime for arbitrary sample rates. Which would you prefer?[/color][/color]





andreas_g

Thank you, but I generally do not use AI. It produces only garbage anyway. Previous is free from AI generated code.

Back to my question:
Does anyone know how to implement de-emphasis for CD standard emphasised audio at 44,1 kHz?

Protocol 7

You can find some info about CDDA de-emphasis here. Maybe it can lead in the right direction. SoX has a de-emphasis filter for example.

andreas_g

@Protocol 7
Thank you very much for the link. I had a look at cdda2wav and it seems to confirm my findings. I'll do some testing during the weekend.