diff --git a/app/src/main/java/xdsopl/robot36/Decoder.java b/app/src/main/java/xdsopl/robot36/Decoder.java index c7ed696..e6cb47b 100644 --- a/app/src/main/java/xdsopl/robot36/Decoder.java +++ b/app/src/main/java/xdsopl/robot36/Decoder.java @@ -55,23 +55,23 @@ public class Decoder { scanLineToleranceSamples = (int) Math.round(scanLineToleranceSeconds * sampleRate); rawMode = new RawDecoder(); syncPulse5msModes = new ArrayList<>(); - syncPulse5msModes.add(RGBModes.Wraase_SC2_180(sampleRate)); - syncPulse5msModes.add(RGBModes.Martin("1", 0.146432, sampleRate)); - syncPulse5msModes.add(RGBModes.Martin("2", 0.073216, sampleRate)); + syncPulse5msModes.add(RGBModes.Wraase_SC2_180(sampleRate, scopeWidth)); + syncPulse5msModes.add(RGBModes.Martin("1", 0.146432, sampleRate, scopeWidth)); + syncPulse5msModes.add(RGBModes.Martin("2", 0.073216, sampleRate, scopeWidth)); syncPulse9msModes = new ArrayList<>(); - syncPulse9msModes.add(new Robot_36_Color(sampleRate)); - syncPulse9msModes.add(new Robot_72_Color(sampleRate)); - syncPulse9msModes.add(RGBModes.Scottie("1", 0.138240, sampleRate)); - syncPulse9msModes.add(RGBModes.Scottie("2", 0.088064, sampleRate)); - syncPulse9msModes.add(RGBModes.Scottie("DX", 0.3456, sampleRate)); + syncPulse9msModes.add(new Robot_36_Color(sampleRate, scopeWidth)); + syncPulse9msModes.add(new Robot_72_Color(sampleRate, scopeWidth)); + syncPulse9msModes.add(RGBModes.Scottie("1", 0.138240, sampleRate, scopeWidth)); + syncPulse9msModes.add(RGBModes.Scottie("2", 0.088064, sampleRate, scopeWidth)); + syncPulse9msModes.add(RGBModes.Scottie("DX", 0.3456, sampleRate, scopeWidth)); syncPulse20msModes = new ArrayList<>(); - syncPulse20msModes.add(new PaulDon("50", 0.09152, sampleRate)); - syncPulse20msModes.add(new PaulDon("90", 0.17024, sampleRate)); - syncPulse20msModes.add(new PaulDon("120", 0.1216, sampleRate)); - syncPulse20msModes.add(new PaulDon("160", 0.195584, sampleRate)); - syncPulse20msModes.add(new PaulDon("180", 0.18304, sampleRate)); - syncPulse20msModes.add(new PaulDon("240", 0.24448, sampleRate)); - syncPulse20msModes.add(new PaulDon("290", 0.2288, sampleRate)); + syncPulse20msModes.add(new PaulDon("50", 0.09152, sampleRate, scopeWidth)); + syncPulse20msModes.add(new PaulDon("90", 0.17024, sampleRate, scopeWidth)); + syncPulse20msModes.add(new PaulDon("120", 0.1216, sampleRate, scopeWidth)); + syncPulse20msModes.add(new PaulDon("160", 0.195584, sampleRate, scopeWidth)); + syncPulse20msModes.add(new PaulDon("180", 0.18304, sampleRate, scopeWidth)); + syncPulse20msModes.add(new PaulDon("240", 0.24448, sampleRate, scopeWidth)); + syncPulse20msModes.add(new PaulDon("290", 0.2288, sampleRate, scopeWidth)); } private void adjustSyncPulses(int[] pulses, int shift) { diff --git a/app/src/main/java/xdsopl/robot36/ExponentialMovingAverage.java b/app/src/main/java/xdsopl/robot36/ExponentialMovingAverage.java new file mode 100644 index 0000000..b3f624c --- /dev/null +++ b/app/src/main/java/xdsopl/robot36/ExponentialMovingAverage.java @@ -0,0 +1,21 @@ +/* +Exponential Moving Average + +Copyright 2024 Ahmet Inan +*/ + +package xdsopl.robot36; + +public class ExponentialMovingAverage { + private final float alpha; + private float prev; + ExponentialMovingAverage(float alpha) { + this.alpha = alpha; + } + public float avg(float input) { + return prev = prev * (1 - alpha) + alpha * input; + } + public void reset() { + prev = 0; + } +} diff --git a/app/src/main/java/xdsopl/robot36/PaulDon.java b/app/src/main/java/xdsopl/robot36/PaulDon.java index 1afeedb..1baed6d 100644 --- a/app/src/main/java/xdsopl/robot36/PaulDon.java +++ b/app/src/main/java/xdsopl/robot36/PaulDon.java @@ -7,6 +7,7 @@ Copyright 2024 Ahmet Inan package xdsopl.robot36; public class PaulDon implements Mode { + private final ExponentialMovingAverage lowPassFilter; private final int scanLineSamples; private final int channelSamples; private final int beginSamples; @@ -17,7 +18,7 @@ public class PaulDon implements Mode { private final int endSamples; private final String name; - PaulDon(String name, double channelSeconds, int sampleRate) { + PaulDon(String name, double channelSeconds, int sampleRate, int bufferWidth) { this.name = "PD " + name; double syncPulseSeconds = 0.02; double syncPorchSeconds = 0.00208; @@ -35,6 +36,7 @@ public class PaulDon implements Mode { yOddBeginSamples = (int) Math.round(yOddBeginSeconds * sampleRate); double yOddEndSeconds = yOddBeginSeconds + channelSeconds; endSamples = (int) Math.round(yOddEndSeconds * sampleRate); + lowPassFilter = new ExponentialMovingAverage((float) (bufferWidth / (sampleRate * channelSeconds))); } @Override @@ -51,6 +53,12 @@ public class PaulDon implements Mode { public int decodeScanLine(int[] evenBuffer, int[] oddBuffer, float[] scanLineBuffer, int prevPulseIndex, int scanLineSamples) { if (prevPulseIndex + beginSamples < 0 || prevPulseIndex + endSamples > scanLineBuffer.length) return 0; + lowPassFilter.reset(); + for (int i = prevPulseIndex + beginSamples; i < prevPulseIndex + endSamples; ++i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); + lowPassFilter.reset(); + for (int i = prevPulseIndex + endSamples - 1; i >= scanLineSamples + beginSamples; --i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); for (int i = 0; i < evenBuffer.length; ++i) { int position = (i * channelSamples) / evenBuffer.length + prevPulseIndex; int yEvenPos = position + yEvenBeginSamples; diff --git a/app/src/main/java/xdsopl/robot36/RGBDecoder.java b/app/src/main/java/xdsopl/robot36/RGBDecoder.java index cf384b4..b7571f3 100644 --- a/app/src/main/java/xdsopl/robot36/RGBDecoder.java +++ b/app/src/main/java/xdsopl/robot36/RGBDecoder.java @@ -7,6 +7,7 @@ Copyright 2024 Ahmet Inan package xdsopl.robot36; public class RGBDecoder implements Mode { + private final ExponentialMovingAverage lowPassFilter; private final int scanLineSamples; private final int beginSamples; private final int redBeginSamples; @@ -18,7 +19,7 @@ public class RGBDecoder implements Mode { private final int endSamples; private final String name; - RGBDecoder(String name, double scanLineSeconds, double beginSeconds, double redBeginSeconds, double redEndSeconds, double greenBeginSeconds, double greenEndSeconds, double blueBeginSeconds, double blueEndSeconds, double endSeconds, int sampleRate) { + RGBDecoder(String name, double scanLineSeconds, double beginSeconds, double redBeginSeconds, double redEndSeconds, double greenBeginSeconds, double greenEndSeconds, double blueBeginSeconds, double blueEndSeconds, double endSeconds, int sampleRate, int bufferWidth) { this.name = name; scanLineSamples = (int) Math.round(scanLineSeconds * sampleRate); beginSamples = (int) Math.round(beginSeconds * sampleRate); @@ -29,6 +30,7 @@ public class RGBDecoder implements Mode { blueBeginSamples = (int) Math.round(blueBeginSeconds * sampleRate); blueSamples = (int) Math.round((blueEndSeconds - blueBeginSeconds) * sampleRate); endSamples = (int) Math.round(endSeconds * sampleRate); + lowPassFilter = new ExponentialMovingAverage((float) (bufferWidth / (sampleRate * (greenEndSeconds - greenBeginSeconds)))); } @Override @@ -45,6 +47,12 @@ public class RGBDecoder implements Mode { public int decodeScanLine(int[] evenBuffer, int[] oddBuffer, float[] scanLineBuffer, int prevPulseIndex, int scanLineSamples) { if (prevPulseIndex + beginSamples < 0 || prevPulseIndex + endSamples > scanLineBuffer.length) return 0; + lowPassFilter.reset(); + for (int i = prevPulseIndex + beginSamples; i < prevPulseIndex + endSamples; ++i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); + lowPassFilter.reset(); + for (int i = prevPulseIndex + endSamples - 1; i >= scanLineSamples + beginSamples; --i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); for (int i = 0; i < evenBuffer.length; ++i) { int redPos = redBeginSamples + (i * redSamples) / evenBuffer.length + prevPulseIndex; int greenPos = greenBeginSamples + (i * greenSamples) / evenBuffer.length + prevPulseIndex; diff --git a/app/src/main/java/xdsopl/robot36/RGBModes.java b/app/src/main/java/xdsopl/robot36/RGBModes.java index 4fe3380..7449da4 100644 --- a/app/src/main/java/xdsopl/robot36/RGBModes.java +++ b/app/src/main/java/xdsopl/robot36/RGBModes.java @@ -8,7 +8,7 @@ package xdsopl.robot36; public final class RGBModes { - public static RGBDecoder Martin(String name, double channelSeconds, int sampleRate) { + public static RGBDecoder Martin(String name, double channelSeconds, int sampleRate, int bufferWidth) { double syncPulseSeconds = 0.004862; double separatorSeconds = 0.000572; double scanLineSeconds = syncPulseSeconds + separatorSeconds + 3 * (channelSeconds + separatorSeconds); @@ -18,10 +18,10 @@ public final class RGBModes { double blueEndSeconds = blueBeginSeconds + channelSeconds; double redBeginSeconds = blueEndSeconds + separatorSeconds; double redEndSeconds = redBeginSeconds + channelSeconds; - return new RGBDecoder("Martin " + name, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate); + return new RGBDecoder("Martin " + name, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate, bufferWidth); } - public static RGBDecoder Scottie(String name, double channelSeconds, int sampleRate) { + public static RGBDecoder Scottie(String name, double channelSeconds, int sampleRate, int bufferWidth) { double syncPulseSeconds = 0.009; double separatorSeconds = 0.0015; double scanLineSeconds = syncPulseSeconds + 3 * (channelSeconds + separatorSeconds); @@ -31,10 +31,10 @@ public final class RGBModes { double greenBeginSeconds = greenEndSeconds - channelSeconds; double redBeginSeconds = syncPulseSeconds / 2 + separatorSeconds; double redEndSeconds = redBeginSeconds + channelSeconds; - return new RGBDecoder("Scottie " + name, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate); + return new RGBDecoder("Scottie " + name, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate, bufferWidth); } - public static RGBDecoder Wraase_SC2_180(int sampleRate) { + public static RGBDecoder Wraase_SC2_180(int sampleRate, int bufferWidth) { double syncPulseSeconds = 0.0055225; double syncPorchSeconds = 0.0005; double channelSeconds = 0.235; @@ -45,6 +45,6 @@ public final class RGBModes { double greenEndSeconds = greenBeginSeconds + channelSeconds; double blueBeginSeconds = greenEndSeconds; double blueEndSeconds = blueBeginSeconds + channelSeconds; - return new RGBDecoder("Wraase SC2-180", scanLineSeconds, redBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, blueEndSeconds, sampleRate); + return new RGBDecoder("Wraase SC2-180", scanLineSeconds, redBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, blueEndSeconds, sampleRate, bufferWidth); } } diff --git a/app/src/main/java/xdsopl/robot36/Robot_36_Color.java b/app/src/main/java/xdsopl/robot36/Robot_36_Color.java index 7e15d02..73b7b55 100644 --- a/app/src/main/java/xdsopl/robot36/Robot_36_Color.java +++ b/app/src/main/java/xdsopl/robot36/Robot_36_Color.java @@ -7,6 +7,7 @@ Copyright 2024 Ahmet Inan package xdsopl.robot36; public class Robot_36_Color implements Mode { + private final ExponentialMovingAverage lowPassFilter; private final int scanLineSamples; private final int luminanceSamples; private final int separatorSamples; @@ -17,7 +18,7 @@ public class Robot_36_Color implements Mode { private final int chrominanceBeginSamples; private final int endSamples; - Robot_36_Color(int sampleRate) { + Robot_36_Color(int sampleRate, int bufferWidth) { double syncPulseSeconds = 0.009; double syncPorchSeconds = 0.003; double luminanceSeconds = 0.088; @@ -39,6 +40,7 @@ public class Robot_36_Color implements Mode { chrominanceBeginSamples = (int) Math.round(chrominanceBeginSeconds * sampleRate); double chrominanceEndSeconds = chrominanceBeginSeconds + chrominanceSeconds; endSamples = (int) Math.round(chrominanceEndSeconds * sampleRate); + lowPassFilter = new ExponentialMovingAverage((float) (bufferWidth / (sampleRate * luminanceSeconds))); } @Override @@ -60,6 +62,12 @@ public class Robot_36_Color implements Mode { separator += scanLineBuffer[prevPulseIndex + separatorBeginSamples + i]; separator /= separatorSamples; boolean even = separator < 0.5f; + lowPassFilter.reset(); + for (int i = prevPulseIndex + beginSamples; i < prevPulseIndex + endSamples; ++i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); + lowPassFilter.reset(); + for (int i = prevPulseIndex + endSamples - 1; i >= scanLineSamples + beginSamples; --i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); for (int i = 0; i < evenBuffer.length; ++i) { int luminancePos = luminanceBeginSamples + (i * luminanceSamples) / evenBuffer.length + prevPulseIndex; int chrominancePos = chrominanceBeginSamples + (i * chrominanceSamples) / evenBuffer.length + prevPulseIndex; diff --git a/app/src/main/java/xdsopl/robot36/Robot_72_Color.java b/app/src/main/java/xdsopl/robot36/Robot_72_Color.java index 0572bbe..b53b621 100644 --- a/app/src/main/java/xdsopl/robot36/Robot_72_Color.java +++ b/app/src/main/java/xdsopl/robot36/Robot_72_Color.java @@ -7,6 +7,7 @@ Copyright 2024 Ahmet Inan package xdsopl.robot36; public class Robot_72_Color implements Mode { + private final ExponentialMovingAverage lowPassFilter; private final int scanLineSamples; private final int luminanceSamples; private final int chrominanceSamples; @@ -16,7 +17,7 @@ public class Robot_72_Color implements Mode { private final int uBeginSamples; private final int endSamples; - Robot_72_Color(int sampleRate) { + Robot_72_Color(int sampleRate, int bufferWidth) { double syncPulseSeconds = 0.009; double syncPorchSeconds = 0.003; double luminanceSeconds = 0.138; @@ -38,6 +39,7 @@ public class Robot_72_Color implements Mode { uBeginSamples = (int) Math.round(uBeginSeconds * sampleRate); double uEndSeconds = uBeginSeconds + chrominanceSeconds; endSamples = (int) Math.round(uEndSeconds * sampleRate); + lowPassFilter = new ExponentialMovingAverage((float) (bufferWidth / (sampleRate * luminanceSeconds))); } @Override @@ -54,6 +56,12 @@ public class Robot_72_Color implements Mode { public int decodeScanLine(int[] evenBuffer, int[] oddBuffer, float[] scanLineBuffer, int prevPulseIndex, int scanLineSamples) { if (prevPulseIndex + beginSamples < 0 || prevPulseIndex + endSamples > scanLineBuffer.length) return 0; + lowPassFilter.reset(); + for (int i = prevPulseIndex + beginSamples; i < prevPulseIndex + endSamples; ++i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); + lowPassFilter.reset(); + for (int i = prevPulseIndex + endSamples - 1; i >= scanLineSamples + beginSamples; --i) + scanLineBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]); for (int i = 0; i < evenBuffer.length; ++i) { int yPos = yBeginSamples + (i * luminanceSamples) / evenBuffer.length + prevPulseIndex; int uPos = uBeginSamples + (i * chrominanceSamples) / evenBuffer.length + prevPulseIndex;