Skip to content

Commit

Permalink
added Low Pass Filter for Sound output
Browse files Browse the repository at this point in the history
  • Loading branch information
raydac committed Dec 8, 2024
1 parent 0525c81 commit 39adcd7
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 168 deletions.
4 changes: 4 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
__2.3.5 (SNAPSHOT)__

- added Low Pass Filter for sound output

__2.3.4 (07-dec-2024)__

- the minimum required JDK version has been raised to 21
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.igormaznitsa.zxpoly.components.video.VideoController;
import com.igormaznitsa.zxpoly.components.video.VirtualKeyboardDecoration;
import com.igormaznitsa.zxpoly.components.video.timings.TimingProfile;
import com.igormaznitsa.zxpoly.utils.AppOptions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -125,7 +126,13 @@ public Motherboard(
this.memoryTimings = timingProfile.makeUlaFrame();

this.boardMode = boardMode;
this.beeper = new Beeper(timingProfile, useAcbSoundScheme, enableCovoxFb, useTurboSound,

final float lowPassFilter = AppOptions.getInstance().isLpfActive() ? AppOptions.getInstance()
.getLpfValue() / 100.0f : -1.0f;
LOGGER.info("Low Pass Sound Filter is " + (lowPassFilter < 0 ? "OFF" : lowPassFilter));

this.beeper =
new Beeper(timingProfile, lowPassFilter, useAcbSoundScheme, enableCovoxFb, useTurboSound,
tryConsumeLessSystemResources);
if (rom.isTrdosPresented()) {
LOGGER.info("TR-DOS presented in ROM, creating BetaDiskInterface");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public final class Beeper {

private static final IWavWriter NULL_WAV = new IWavWriter() {
@Override
public void updateState(boolean tstatesInt, boolean wallclockInt, int spentTstates,
public void updateState(boolean tiStatesInt, boolean wallClockInt, int spentTiStates,
int levelLeft, int levelRight) {

}
Expand All @@ -65,7 +65,7 @@ public void dispose() {

private static final IBeeper NULL_BEEPER = new IBeeper() {
@Override
public void updateState(boolean tstatesInt, boolean wallclockInt, int spentTstates,
public void updateState(boolean tiStatesInt, boolean wallClockInt, int spentTiStates,
int levelLeft, int levelRight) {
}

Expand All @@ -89,9 +89,7 @@ public void start() {
};

private final AtomicReference<IBeeper> activeInternalBeeper = new AtomicReference<>(NULL_BEEPER);
private final SoundChannelValueFilter[] soundChannelFilters =
IntStream.range(0, 8).mapToObj(i -> new SoundChannelValueFilter())
.toArray(SoundChannelValueFilter[]::new);
private final SoundChannelLowPassFilter[] soundChannelLowPassFilters;
private final int[] channels = new int[8];
private final MixerFunction mixerLeft;
private final MixerFunction mixerRight;
Expand All @@ -100,10 +98,14 @@ public void start() {

public Beeper(
final TimingProfile timingProfile,
final float lowPassFilterValue,
final boolean useAcbSoundScheme,
final boolean covoxPresented,
final boolean turboSoundPresented,
final boolean tryConsumeLessSystemResources) {
this.soundChannelLowPassFilters =
IntStream.range(0, 8).mapToObj(i -> new SoundChannelLowPassFilter(i, lowPassFilterValue))
.toArray(SoundChannelLowPassFilter[]::new);
this.tryConsumeLessSystemResources = tryConsumeLessSystemResources;
this.timingProfile = timingProfile;
if (useAcbSoundScheme) {
Expand Down Expand Up @@ -195,25 +197,25 @@ public boolean isNullBeeper() {
return this.activeInternalBeeper.get() == NULL_BEEPER;
}

public void updateState(final boolean tstatesInt, final boolean wallclockInt,
final int spentTstates) {
public void updateState(final boolean tiStatesInt, final boolean wallClockInt,
final int spentTiStates) {
final int leftChannel =
this.mixerLeft.mix(this.channels, this.soundChannelFilters, spentTstates);
this.mixerLeft.mix(this.channels, this.soundChannelLowPassFilters, spentTiStates);
final int rightChannel =
this.mixerRight.mix(this.channels, this.soundChannelFilters, spentTstates);
this.mixerRight.mix(this.channels, this.soundChannelLowPassFilters, spentTiStates);

this.activeInternalBeeper.get()
.updateState(tstatesInt,
wallclockInt,
spentTstates,
.updateState(tiStatesInt,
wallClockInt,
spentTiStates,
leftChannel,
rightChannel
);

this.activeWavWriter.get()
.updateState(tstatesInt,
wallclockInt,
spentTstates,
.updateState(tiStatesInt,
wallClockInt,
spentTiStates,
leftChannel,
rightChannel
);
Expand All @@ -226,7 +228,7 @@ public void reset() {

public void clearChannels() {
Arrays.fill(this.channels, 0);
for (final SoundChannelValueFilter f : this.soundChannelFilters) {
for (final SoundChannelLowPassFilter f : this.soundChannelLowPassFilters) {
f.reset();
}
}
Expand All @@ -246,11 +248,11 @@ public AudioFormat getAudioFormat() {

@FunctionalInterface
private interface MixerFunction {
int mix(int[] values, SoundChannelValueFilter[] filters, int spentTstates);
int mix(int[] values, SoundChannelLowPassFilter[] filters, int spentTiStates);
}

private interface IWavWriter {
void updateState(boolean tstatesInt, boolean wallclockInt, int spentTstates, int levelLeft,
void updateState(boolean tiStatesInt, boolean wallClockInt, int spentTiStates, int levelLeft,
int levelRight);

void dispose();
Expand All @@ -262,7 +264,7 @@ private interface IBeeper {

void start();

void updateState(boolean tstatesInt, boolean wallclockInt, int spentTstates, int levelLeft,
void updateState(boolean tiStatesInt, boolean wallClockInt, int spentTiStates, int levelLeft,
int levelRight);

void dispose();
Expand Down Expand Up @@ -310,13 +312,13 @@ private WavWriterImpl(final TimingProfile timingProfile, final File wavFile)

@Override
public void updateState(
final boolean tstatesInt,
final boolean wallclockInt,
final int spentTstates,
final boolean tiStatesInt,
final boolean wallClockInt,
final int spentTiStates,
final int levelLeft,
final int levelRight
) {
final double frameOffset = spentTstates * this.framesPerTick;
final double frameOffset = spentTiStates * this.framesPerTick;

long currentFrame = (long) this.frameCounter;
this.frameCounter += frameOffset;
Expand Down Expand Up @@ -399,18 +401,18 @@ public void start() {

@Override
public void updateState(
boolean tstatesIntReached,
boolean wallclockInt,
int spentTstates,
boolean tiStatesInt,
boolean wallClockInt,
int spentTiStates,
final int levelLeft,
final int levelRight
) {
if (this.working) {
if (wallclockInt) {
if (wallClockInt) {
this.soundDataQueue.offer(sndBuffer.nextBuffer(levelLeft, levelRight));
sndBuffer.resetPosition();
} else {
sndBuffer.setValue(spentTstates, levelLeft, levelRight);
sndBuffer.setValue(spentTiStates, levelLeft, levelRight);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,86 +17,94 @@ private MixerUtilsABC() {
super();
}

public static int mixLeft_TS_CVX(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_COVOX].update(spentTstates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTstates, values[CHANNEL_TS_B]);
public static int mixLeft_TS_CVX(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_COVOX].update(spentTiStates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTiStates, values[CHANNEL_TS_B]);

final int left = filters[CHANNEL_TS_A].update(spentTstates, values[CHANNEL_TS_A])
+ filters[CHANNEL_AY_A].update(spentTstates, values[CHANNEL_AY_A]);
final int left = filters[CHANNEL_TS_A].update(spentTiStates, values[CHANNEL_TS_A])
+ filters[CHANNEL_AY_A].update(spentTiStates, values[CHANNEL_AY_A]);

return scaleLeft6(left, middle);
}

public static int mixRight_TS_CVX(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_COVOX].update(spentTstates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTstates, values[CHANNEL_TS_B]);
public static int mixRight_TS_CVX(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_COVOX].update(spentTiStates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTiStates, values[CHANNEL_TS_B]);

final int right = filters[CHANNEL_AY_C].update(spentTstates, values[CHANNEL_AY_C])
+ filters[CHANNEL_TS_C].update(spentTstates, values[CHANNEL_TS_C]);
final int right = filters[CHANNEL_AY_C].update(spentTiStates, values[CHANNEL_AY_C])
+ filters[CHANNEL_TS_C].update(spentTiStates, values[CHANNEL_TS_C]);

return scaleRight6(right, middle);
}

public static int mixLeft_CVX(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_COVOX].update(spentTstates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B]);
public static int mixLeft_CVX(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_COVOX].update(spentTiStates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B]);

final int left = filters[CHANNEL_AY_A].update(spentTstates, values[CHANNEL_AY_A]);
final int left = filters[CHANNEL_AY_A].update(spentTiStates, values[CHANNEL_AY_A]);

return scaleLeft4(left, middle);
}

public static int mixRight_CVX(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_COVOX].update(spentTstates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B]);
public static int mixRight_CVX(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_COVOX].update(spentTiStates, values[CHANNEL_COVOX])
+ filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B]);

final int right = filters[CHANNEL_AY_C].update(spentTstates, values[CHANNEL_AY_C]);
final int right = filters[CHANNEL_AY_C].update(spentTiStates, values[CHANNEL_AY_C]);

return scaleRight4(right, middle);
}

public static int mixLeft_TS(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTstates, values[CHANNEL_TS_B]);
public static int mixLeft_TS(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTiStates, values[CHANNEL_TS_B]);

final int left = filters[CHANNEL_TS_A].update(spentTstates, values[CHANNEL_TS_A])
+ filters[CHANNEL_AY_A].update(spentTstates, values[CHANNEL_AY_A]);
final int left = filters[CHANNEL_TS_A].update(spentTiStates, values[CHANNEL_TS_A])
+ filters[CHANNEL_AY_A].update(spentTiStates, values[CHANNEL_AY_A]);

return scaleLeft5(left, middle);
}

public static int mixRight_TS(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTstates, values[CHANNEL_TS_B]);
public static int mixRight_TS(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B])
+ filters[CHANNEL_TS_B].update(spentTiStates, values[CHANNEL_TS_B]);

final int right = filters[CHANNEL_AY_C].update(spentTstates, values[CHANNEL_AY_C])
+ filters[CHANNEL_TS_C].update(spentTstates, values[CHANNEL_TS_C]);
final int right = filters[CHANNEL_AY_C].update(spentTiStates, values[CHANNEL_AY_C])
+ filters[CHANNEL_TS_C].update(spentTiStates, values[CHANNEL_TS_C]);

return scaleRight5(right, middle);
}

public static int mixLeft(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B]);
public static int mixLeft(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B]);

final int left = filters[CHANNEL_AY_A].update(spentTstates, values[CHANNEL_AY_A]);
final int left = filters[CHANNEL_AY_A].update(spentTiStates, values[CHANNEL_AY_A]);

return scaleLeft3(left, middle);
}

public static int mixRight(final int[] values, final SoundChannelValueFilter[] filters, final int spentTstates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTstates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTstates, values[CHANNEL_AY_B]);
public static int mixRight(final int[] values, final SoundChannelLowPassFilter[] filters,
final int spentTiStates) {
final int middle = filters[CHANNEL_BEEPER].update(spentTiStates, values[CHANNEL_BEEPER])
+ filters[CHANNEL_AY_B].update(spentTiStates, values[CHANNEL_AY_B]);

final int right = filters[CHANNEL_AY_C].update(spentTstates, values[CHANNEL_AY_C]);
final int right = filters[CHANNEL_AY_C].update(spentTiStates, values[CHANNEL_AY_C]);

return scaleRight3(right, middle);
}
Expand Down
Loading

0 comments on commit 39adcd7

Please sign in to comment.