Merge pull request #40 from marek-o/adding-hffax2

Adding HF Fax
This commit is contained in:
Ahmet Inan 2025-08-23 07:53:21 +02:00 committed by GitHub
commit c55ae1b2ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 254 additions and 63 deletions

View file

@ -0,0 +1,16 @@
/*
Base class for all modes
Copyright 2025 Marek Ossowski <marek0ossowski@gmail.com>
*/
package xdsopl.robot36;
import android.graphics.Bitmap;
public abstract class BaseMode implements Mode {
@Override
public Bitmap postProcessScopeImage(Bitmap bmp) {
return bmp;
}
}

View file

@ -38,6 +38,7 @@ public class Decoder {
private final int visCodeBitSamples; private final int visCodeBitSamples;
private final int visCodeSamples; private final int visCodeSamples;
private final Mode rawMode; private final Mode rawMode;
private final Mode hfFaxMode;
private final ArrayList<Mode> syncPulse5msModes; private final ArrayList<Mode> syncPulse5msModes;
private final ArrayList<Mode> syncPulse9msModes; private final ArrayList<Mode> syncPulse9msModes;
private final ArrayList<Mode> syncPulse20msModes; private final ArrayList<Mode> syncPulse20msModes;
@ -95,6 +96,7 @@ public class Decoder {
double scanLineToleranceSeconds = 0.001; double scanLineToleranceSeconds = 0.001;
scanLineToleranceSamples = (int) Math.round(scanLineToleranceSeconds * sampleRate); scanLineToleranceSamples = (int) Math.round(scanLineToleranceSeconds * sampleRate);
rawMode = new RawDecoder(rawName, sampleRate); rawMode = new RawDecoder(rawName, sampleRate);
hfFaxMode = new HFFax(sampleRate);
Mode robot36 = new Robot_36_Color(sampleRate); Mode robot36 = new Robot_36_Color(sampleRate);
currentMode = robot36; currentMode = robot36;
currentScanLineSamples = robot36.getScanLineSamples(); currentScanLineSamples = robot36.getScanLineSamples();
@ -157,7 +159,7 @@ public class Decoder {
private Mode findMode(ArrayList<Mode> modes, int code) { private Mode findMode(ArrayList<Mode> modes, int code) {
for (Mode mode : modes) for (Mode mode : modes)
if (mode.getCode() == code) if (mode.getVISCode() == code)
return mode; return mode;
return null; return null;
} }
@ -332,7 +334,7 @@ public class Decoder {
} }
if (lockMode && mode != currentMode) if (lockMode && mode != currentMode)
return false; return false;
mode.reset(); mode.resetState();
imageBuffer.width = mode.getWidth(); imageBuffer.width = mode.getWidth();
imageBuffer.height = mode.getHeight(); imageBuffer.height = mode.getHeight();
imageBuffer.line = 0; imageBuffer.line = 0;
@ -346,29 +348,29 @@ public class Decoder {
for (int i = 0; i < pulses.length; ++i) for (int i = 0; i < pulses.length; ++i)
pulses[i] = oldestSyncPulseIndex + i * currentScanLineSamples; pulses[i] = oldestSyncPulseIndex + i * currentScanLineSamples;
Arrays.fill(lines, currentScanLineSamples); Arrays.fill(lines, currentScanLineSamples);
shiftSamples(lastSyncPulseIndex + mode.getBegin()); shiftSamples(lastSyncPulseIndex + mode.getFirstPixelSampleIndex());
drawLines(0xff00ff00, 8); drawLines(0xff00ff00, 8);
drawLines(0xff000000, 10); drawLines(0xff000000, 10);
return true; return true;
} }
private boolean processSyncPulse(ArrayList<Mode> modes, float[] freqOffs, int[] pulses, int[] lines, int index) { private boolean processSyncPulse(ArrayList<Mode> modes, float[] freqOffs, int[] syncIndexes, int[] lineLengths, int latestSyncIndex) {
for (int i = 1; i < pulses.length; ++i) for (int i = 1; i < syncIndexes.length; ++i)
pulses[i - 1] = pulses[i]; syncIndexes[i - 1] = syncIndexes[i];
pulses[pulses.length - 1] = index; syncIndexes[syncIndexes.length - 1] = latestSyncIndex;
for (int i = 1; i < lines.length; ++i) for (int i = 1; i < lineLengths.length; ++i)
lines[i - 1] = lines[i]; lineLengths[i - 1] = lineLengths[i];
lines[lines.length - 1] = pulses[pulses.length - 1] - pulses[pulses.length - 2]; lineLengths[lineLengths.length - 1] = syncIndexes[syncIndexes.length - 1] - syncIndexes[syncIndexes.length - 2];
for (int i = 1; i < freqOffs.length; ++i) for (int i = 1; i < freqOffs.length; ++i)
freqOffs[i - 1] = freqOffs[i]; freqOffs[i - 1] = freqOffs[i];
freqOffs[pulses.length - 1] = demodulator.frequencyOffset; freqOffs[syncIndexes.length - 1] = demodulator.frequencyOffset;
if (lines[0] == 0) if (lineLengths[0] == 0)
return false; return false;
double mean = scanLineMean(lines); double mean = scanLineMean(lineLengths);
int scanLineSamples = (int) Math.round(mean); int scanLineSamples = (int) Math.round(mean);
if (scanLineSamples < scanLineMinSamples || scanLineSamples > scratchBuffer.length) if (scanLineSamples < scanLineMinSamples || scanLineSamples > scratchBuffer.length)
return false; return false;
if (scanLineStdDev(lines, mean) > scanLineToleranceSamples) if (scanLineStdDev(lineLengths, mean) > scanLineToleranceSamples)
return false; return false;
boolean pictureChanged = false; boolean pictureChanged = false;
if (lockMode || imageBuffer.line >= 0 && imageBuffer.line < imageBuffer.height) { if (lockMode || imageBuffer.line >= 0 && imageBuffer.line < imageBuffer.height) {
@ -379,7 +381,7 @@ public class Decoder {
currentMode = detectMode(modes, scanLineSamples); currentMode = detectMode(modes, scanLineSamples);
pictureChanged = currentMode != prevMode pictureChanged = currentMode != prevMode
|| Math.abs(currentScanLineSamples - scanLineSamples) > scanLineToleranceSamples || Math.abs(currentScanLineSamples - scanLineSamples) > scanLineToleranceSamples
|| Math.abs(lastSyncPulseIndex + scanLineSamples - pulses[pulses.length - 1]) > syncPulseToleranceSamples; || Math.abs(lastSyncPulseIndex + scanLineSamples - syncIndexes[syncIndexes.length - 1]) > syncPulseToleranceSamples;
} }
if (pictureChanged) { if (pictureChanged) {
drawLines(0xff000000, 10); drawLines(0xff000000, 10);
@ -387,23 +389,24 @@ public class Decoder {
drawLines(0xff000000, 10); drawLines(0xff000000, 10);
} }
float frequencyOffset = (float) frequencyOffsetMean(freqOffs); float frequencyOffset = (float) frequencyOffsetMean(freqOffs);
if (pulses[0] >= scanLineSamples && pictureChanged) { if (syncIndexes[0] >= scanLineSamples && pictureChanged) {
int endPulse = pulses[0]; int endPulse = syncIndexes[0];
int extrapolate = endPulse / scanLineSamples; int extrapolate = endPulse / scanLineSamples;
int firstPulse = endPulse - extrapolate * scanLineSamples; int firstPulse = endPulse - extrapolate * scanLineSamples;
for (int pulseIndex = firstPulse; pulseIndex < endPulse; pulseIndex += scanLineSamples) for (int pulseIndex = firstPulse; pulseIndex < endPulse; pulseIndex += scanLineSamples)
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulseIndex, scanLineSamples, frequencyOffset)); copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulseIndex, scanLineSamples, frequencyOffset));
} }
for (int i = pictureChanged ? 0 : lines.length - 1; i < lines.length; ++i) for (int i = pictureChanged ? 0 : lineLengths.length - 1; i < lineLengths.length; ++i)
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulses[i], lines[i], frequencyOffset)); copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, syncIndexes[i], lineLengths[i], frequencyOffset));
lastSyncPulseIndex = pulses[pulses.length - 1]; lastSyncPulseIndex = syncIndexes[syncIndexes.length - 1];
currentScanLineSamples = scanLineSamples; currentScanLineSamples = scanLineSamples;
lastFrequencyOffset = frequencyOffset; lastFrequencyOffset = frequencyOffset;
shiftSamples(lastSyncPulseIndex + currentMode.getBegin()); shiftSamples(lastSyncPulseIndex + currentMode.getFirstPixelSampleIndex());
return true; return true;
} }
public boolean process(float[] recordBuffer, int channelSelect) { public boolean process(float[] recordBuffer, int channelSelect) {
boolean newLinesPresent = false;
boolean syncPulseDetected = demodulator.process(recordBuffer, channelSelect); boolean syncPulseDetected = demodulator.process(recordBuffer, channelSelect);
int syncPulseIndex = currentSample + demodulator.syncPulseOffset; int syncPulseIndex = currentSample + demodulator.syncPulseOffset;
int channels = channelSelect > 0 ? 2 : 1; int channels = channelSelect > 0 ? 2 : 1;
@ -417,25 +420,28 @@ public class Decoder {
if (syncPulseDetected) { if (syncPulseDetected) {
switch (demodulator.syncPulseWidth) { switch (demodulator.syncPulseWidth) {
case FiveMilliSeconds: case FiveMilliSeconds:
return processSyncPulse(syncPulse5msModes, last5msFrequencyOffsets, last5msSyncPulses, last5msScanLines, syncPulseIndex); newLinesPresent = processSyncPulse(syncPulse5msModes, last5msFrequencyOffsets, last5msSyncPulses, last5msScanLines, syncPulseIndex);
break;
case NineMilliSeconds: case NineMilliSeconds:
leaderBreakIndex = syncPulseIndex; leaderBreakIndex = syncPulseIndex;
return processSyncPulse(syncPulse9msModes, last9msFrequencyOffsets, last9msSyncPulses, last9msScanLines, syncPulseIndex); newLinesPresent = processSyncPulse(syncPulse9msModes, last9msFrequencyOffsets, last9msSyncPulses, last9msScanLines, syncPulseIndex);
break;
case TwentyMilliSeconds: case TwentyMilliSeconds:
leaderBreakIndex = syncPulseIndex; leaderBreakIndex = syncPulseIndex;
return processSyncPulse(syncPulse20msModes, last20msFrequencyOffsets, last20msSyncPulses, last20msScanLines, syncPulseIndex); newLinesPresent = processSyncPulse(syncPulse20msModes, last20msFrequencyOffsets, last20msSyncPulses, last20msScanLines, syncPulseIndex);
break;
default: default:
return false; break;
} }
} } else if (handleHeader()) {
if (handleHeader()) newLinesPresent = true;
return true; } else if (currentSample > lastSyncPulseIndex + (currentScanLineSamples * 5) / 4) {
if (currentSample > lastSyncPulseIndex + (currentScanLineSamples * 5) / 4) {
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, lastSyncPulseIndex, currentScanLineSamples, lastFrequencyOffset)); copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, lastSyncPulseIndex, currentScanLineSamples, lastFrequencyOffset));
lastSyncPulseIndex += currentScanLineSamples; lastSyncPulseIndex += currentScanLineSamples;
return true; newLinesPresent = true;
} }
return false;
return newLinesPresent;
} }
public void setMode(String name) { public void setMode(String name) {
@ -450,6 +456,8 @@ public class Decoder {
mode = findMode(syncPulse9msModes, name); mode = findMode(syncPulse9msModes, name);
if (mode == null) if (mode == null)
mode = findMode(syncPulse20msModes, name); mode = findMode(syncPulse20msModes, name);
if (mode == null && hfFaxMode.getName().equals(name))
mode = hfFaxMode;
if (mode == currentMode) { if (mode == currentMode) {
lockMode = true; lockMode = true;
return; return;

View file

@ -13,6 +13,8 @@ public class Demodulator {
private final SchmittTrigger syncPulseTrigger; private final SchmittTrigger syncPulseTrigger;
private final Phasor baseBandOscillator; private final Phasor baseBandOscillator;
private final Delay syncPulseValueDelay; private final Delay syncPulseValueDelay;
private final double scanLineBandwidth;
private final double centerFrequency;
private final float syncPulseFrequencyValue; private final float syncPulseFrequencyValue;
private final float syncPulseFrequencyTolerance; private final float syncPulseFrequencyTolerance;
private final int syncPulse5msMinSamples; private final int syncPulse5msMinSamples;
@ -33,10 +35,12 @@ public class Demodulator {
public int syncPulseOffset; public int syncPulseOffset;
public float frequencyOffset; public float frequencyOffset;
public static final double syncPulseFrequency = 1200;
public static final double blackFrequency = 1500;
public static final double whiteFrequency = 2300;
Demodulator(int sampleRate) { Demodulator(int sampleRate) {
double blackFrequency = 1500; scanLineBandwidth = whiteFrequency - blackFrequency;
double whiteFrequency = 2300;
double scanLineBandwidth = whiteFrequency - blackFrequency;
frequencyModulation = new FrequencyModulation(scanLineBandwidth, sampleRate); frequencyModulation = new FrequencyModulation(scanLineBandwidth, sampleRate);
double syncPulse5msSeconds = 0.005; double syncPulse5msSeconds = 0.005;
double syncPulse9msSeconds = 0.009; double syncPulse9msSeconds = 0.009;
@ -63,20 +67,23 @@ public class Demodulator {
Kaiser kaiser = new Kaiser(); Kaiser kaiser = new Kaiser();
for (int i = 0; i < baseBandLowPass.length; ++i) for (int i = 0; i < baseBandLowPass.length; ++i)
baseBandLowPass.taps[i] = (float) (kaiser.window(2.0, i, baseBandLowPass.length) * Filter.lowPass(cutoffFrequency, sampleRate, i, baseBandLowPass.length)); baseBandLowPass.taps[i] = (float) (kaiser.window(2.0, i, baseBandLowPass.length) * Filter.lowPass(cutoffFrequency, sampleRate, i, baseBandLowPass.length));
double centerFrequency = (lowestFrequency + highestFrequency) / 2; centerFrequency = (lowestFrequency + highestFrequency) / 2;
baseBandOscillator = new Phasor(-centerFrequency, sampleRate); baseBandOscillator = new Phasor(-centerFrequency, sampleRate);
double syncPulseFrequency = 1200; syncPulseFrequencyValue = (float) normalizeFrequency(syncPulseFrequency);
syncPulseFrequencyValue = (float) ((syncPulseFrequency - centerFrequency) * 2 / scanLineBandwidth);
syncPulseFrequencyTolerance = (float) (50 * 2 / scanLineBandwidth); syncPulseFrequencyTolerance = (float) (50 * 2 / scanLineBandwidth);
double syncPorchFrequency = 1500; double syncPorchFrequency = 1500;
double syncHighFrequency = (syncPulseFrequency + syncPorchFrequency) / 2; double syncHighFrequency = (syncPulseFrequency + syncPorchFrequency) / 2;
double syncLowFrequency = (syncPulseFrequency + syncHighFrequency) / 2; double syncLowFrequency = (syncPulseFrequency + syncHighFrequency) / 2;
double syncLowValue = (syncLowFrequency - centerFrequency) * 2 / scanLineBandwidth; double syncLowValue = normalizeFrequency(syncLowFrequency);
double syncHighValue = (syncHighFrequency - centerFrequency) * 2 / scanLineBandwidth; double syncHighValue = normalizeFrequency(syncHighFrequency);
syncPulseTrigger = new SchmittTrigger((float) syncLowValue, (float) syncHighValue); syncPulseTrigger = new SchmittTrigger((float) syncLowValue, (float) syncHighValue);
baseBand = new Complex(); baseBand = new Complex();
} }
private double normalizeFrequency(double frequency) {
return (frequency - centerFrequency) * 2 / scanLineBandwidth;
}
public boolean process(float[] buffer, int channelSelect) { public boolean process(float[] buffer, int channelSelect) {
boolean syncPulseDetected = false; boolean syncPulseDetected = false;
int channels = channelSelect > 0 ? 2 : 1; int channels = channelSelect > 0 ? 2 : 1;

View file

@ -0,0 +1,137 @@
/*
HF Fax mode
Copyright 2025 Marek Ossowski <marek0ossowski@gmail.com>
*/
package xdsopl.robot36;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
/**
* HF Fax, IOC 576, 120 lines per minute
*/
public class HFFax extends BaseMode {
private final ExponentialMovingAverage lowPassFilter;
private final String name;
private final int sampleRate;
private final float[] cumulated;
private int horizontalShift = 0;
HFFax(int sampleRate) {
this.name = "HF Fax";
lowPassFilter = new ExponentialMovingAverage();
this.sampleRate = sampleRate;
cumulated = new float[getWidth()];
}
private float freqToLevel(float frequency, float offset) {
return 0.5f * (frequency - offset + 1.f);
}
@Override
public String getName() {
return name;
}
@Override
public int getVISCode() {
return -1;
}
@Override
public int getWidth() {
return 640;
}
@Override
public int getHeight() {
return 1200;
}
@Override
public int getFirstPixelSampleIndex() {
return 0;
}
@Override
public int getFirstSyncPulseIndex() {
return -1;
}
@Override
public int getScanLineSamples() {
return sampleRate / 2;
}
@Override
public void resetState() {
}
@Override
public Bitmap postProcessScopeImage(Bitmap bmp) {
int realWidth = 1808;
int realHorizontalShift = horizontalShift * realWidth / getWidth();
Bitmap bmpMutable = Bitmap.createBitmap(realWidth, bmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmpMutable);
if (horizontalShift > 0) {
canvas.drawBitmap(
bmp,
new Rect(0, 0, horizontalShift, bmp.getHeight()),
new Rect(realWidth - realHorizontalShift, 0, realWidth, bmp.getHeight()),
null);
}
canvas.drawBitmap(
bmp,
new Rect(horizontalShift, 0, getWidth(), bmp.getHeight()),
new Rect(0, 1, realWidth - realHorizontalShift, bmp.getHeight() + 1),
null);
return bmpMutable;
}
@Override
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
if (syncPulseIndex < 0 || syncPulseIndex + scanLineSamples > scanLineBuffer.length)
return false;
int horizontalPixels = getWidth();
lowPassFilter.cutoff(horizontalPixels, 2 * scanLineSamples, 2);
lowPassFilter.reset();
for (int i = 0; i < scanLineSamples; ++i)
scratchBuffer[i] = lowPassFilter.avg(scanLineBuffer[i]);
lowPassFilter.reset();
for (int i = scanLineSamples - 1; i >= 0; --i)
scratchBuffer[i] = freqToLevel(lowPassFilter.avg(scratchBuffer[i]), frequencyOffset);
for (int i = 0; i < horizontalPixels; ++i) {
int position = (i * scanLineSamples) / horizontalPixels;
int color = ColorConverter.GRAY(scratchBuffer[position]);
pixelBuffer.pixels[i] = color;
//accumulate recent values, forget old
float decay = 0.99f;
cumulated[i] = cumulated[i] * decay + Color.luminance(color) * (1 - decay);
}
//try to detect "sync": thick white margin
int bestIndex = 0;
float bestValue = 0;
for (int x = 0; x < getWidth(); ++x)
{
float val = cumulated[x];
if (val > bestValue)
{
bestIndex = x;
bestValue = val;
}
}
horizontalShift = bestIndex;
pixelBuffer.width = horizontalPixels;
pixelBuffer.height = 1;
return true;
}
}

View file

@ -618,6 +618,10 @@ public class MainActivity extends AppCompatActivity {
setMode(R.string.raw_mode); setMode(R.string.raw_mode);
return true; return true;
} }
if (id == R.id.action_force_hffax_mode) {
setMode(R.string.hf_fax);
return true;
}
if (id == R.id.action_force_robot36_color) { if (id == R.id.action_force_robot36_color) {
setMode(R.string.robot36_color); setMode(R.string.robot36_color);
return true; return true;
@ -828,7 +832,14 @@ public class MainActivity extends AppCompatActivity {
int height = scopeBuffer.height / 2; int height = scopeBuffer.height / 2;
int stride = scopeBuffer.width; int stride = scopeBuffer.width;
int offset = stride * scopeBuffer.line; int offset = stride * scopeBuffer.line;
storeBitmap(Bitmap.createBitmap(scopeBuffer.pixels, offset, stride, width, height, Bitmap.Config.ARGB_8888)); Bitmap bmp = Bitmap.createBitmap(scopeBuffer.pixels, offset, stride, width, height, Bitmap.Config.ARGB_8888);
if (decoder != null)
{
bmp = decoder.currentMode.postProcessScopeImage(bmp);
}
storeBitmap(bmp);
} }
private void createScope(Configuration config) { private void createScope(Configuration config) {

View file

@ -6,22 +6,30 @@ Copyright 2024 Ahmet Inan <xdsopl@gmail.com>
package xdsopl.robot36; package xdsopl.robot36;
import android.graphics.Bitmap;
public interface Mode { public interface Mode {
String getName(); String getName();
int getCode(); int getVISCode();
int getWidth(); int getWidth();
int getHeight(); int getHeight();
int getBegin(); int getFirstPixelSampleIndex();
int getFirstSyncPulseIndex(); int getFirstSyncPulseIndex();
int getScanLineSamples(); int getScanLineSamples();
void reset(); Bitmap postProcessScopeImage(Bitmap bmp);
void resetState();
/**
* @param frequencyOffset normalized correction of frequency (expected vs actual)
* @return true if scanline was decoded
*/
boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset); boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset);
} }

View file

@ -6,7 +6,7 @@ Copyright 2024 Ahmet Inan <xdsopl@gmail.com>
package xdsopl.robot36; package xdsopl.robot36;
public class PaulDon implements Mode { public class PaulDon extends BaseMode {
private final ExponentialMovingAverage lowPassFilter; private final ExponentialMovingAverage lowPassFilter;
private final int horizontalPixels; private final int horizontalPixels;
private final int verticalPixels; private final int verticalPixels;
@ -56,7 +56,7 @@ public class PaulDon implements Mode {
} }
@Override @Override
public int getCode() { public int getVISCode() {
return code; return code;
} }
@ -71,7 +71,7 @@ public class PaulDon implements Mode {
} }
@Override @Override
public int getBegin() { public int getFirstPixelSampleIndex() {
return beginSamples; return beginSamples;
} }
@ -86,7 +86,7 @@ public class PaulDon implements Mode {
} }
@Override @Override
public void reset() { public void resetState() {
} }
@Override @Override

View file

@ -6,7 +6,7 @@ Copyright 2024 Ahmet Inan <xdsopl@gmail.com>
package xdsopl.robot36; package xdsopl.robot36;
public class RGBDecoder implements Mode { public class RGBDecoder extends BaseMode {
private final ExponentialMovingAverage lowPassFilter; private final ExponentialMovingAverage lowPassFilter;
private final int horizontalPixels; private final int horizontalPixels;
private final int verticalPixels; private final int verticalPixels;
@ -51,7 +51,7 @@ public class RGBDecoder implements Mode {
} }
@Override @Override
public int getCode() { public int getVISCode() {
return code; return code;
} }
@ -66,7 +66,7 @@ public class RGBDecoder implements Mode {
} }
@Override @Override
public int getBegin() { public int getFirstPixelSampleIndex() {
return beginSamples; return beginSamples;
} }
@ -81,7 +81,7 @@ public class RGBDecoder implements Mode {
} }
@Override @Override
public void reset() { public void resetState() {
} }
@Override @Override

View file

@ -6,7 +6,7 @@ Copyright 2024 Ahmet Inan <xdsopl@gmail.com>
package xdsopl.robot36; package xdsopl.robot36;
public class RawDecoder implements Mode { public class RawDecoder extends BaseMode {
private final ExponentialMovingAverage lowPassFilter; private final ExponentialMovingAverage lowPassFilter;
private final int smallPictureMaxSamples; private final int smallPictureMaxSamples;
private final int mediumPictureMaxSamples; private final int mediumPictureMaxSamples;
@ -29,7 +29,7 @@ public class RawDecoder implements Mode {
} }
@Override @Override
public int getCode() { public int getVISCode() {
return -1; return -1;
} }
@ -44,7 +44,7 @@ public class RawDecoder implements Mode {
} }
@Override @Override
public int getBegin() { public int getFirstPixelSampleIndex() {
return 0; return 0;
} }
@ -59,7 +59,7 @@ public class RawDecoder implements Mode {
} }
@Override @Override
public void reset() { public void resetState() {
} }
@Override @Override

View file

@ -6,7 +6,7 @@ Copyright 2024 Ahmet Inan <xdsopl@gmail.com>
package xdsopl.robot36; package xdsopl.robot36;
public class Robot_36_Color implements Mode { public class Robot_36_Color extends BaseMode {
private final ExponentialMovingAverage lowPassFilter; private final ExponentialMovingAverage lowPassFilter;
private final int horizontalPixels; private final int horizontalPixels;
private final int verticalPixels; private final int verticalPixels;
@ -59,7 +59,7 @@ public class Robot_36_Color implements Mode {
} }
@Override @Override
public int getCode() { public int getVISCode() {
return 8; return 8;
} }
@ -74,7 +74,7 @@ public class Robot_36_Color implements Mode {
} }
@Override @Override
public int getBegin() { public int getFirstPixelSampleIndex() {
return beginSamples; return beginSamples;
} }
@ -89,7 +89,7 @@ public class Robot_36_Color implements Mode {
} }
@Override @Override
public void reset() { public void resetState() {
lastEven = false; lastEven = false;
} }

View file

@ -6,7 +6,7 @@ Copyright 2024 Ahmet Inan <xdsopl@gmail.com>
package xdsopl.robot36; package xdsopl.robot36;
public class Robot_72_Color implements Mode { public class Robot_72_Color extends BaseMode {
private final ExponentialMovingAverage lowPassFilter; private final ExponentialMovingAverage lowPassFilter;
private final int horizontalPixels; private final int horizontalPixels;
private final int verticalPixels; private final int verticalPixels;
@ -57,7 +57,7 @@ public class Robot_72_Color implements Mode {
} }
@Override @Override
public int getCode() { public int getVISCode() {
return 12; return 12;
} }
@ -72,7 +72,7 @@ public class Robot_72_Color implements Mode {
} }
@Override @Override
public int getBegin() { public int getFirstPixelSampleIndex() {
return beginSamples; return beginSamples;
} }
@ -87,7 +87,7 @@ public class Robot_72_Color implements Mode {
} }
@Override @Override
public void reset() { public void resetState() {
} }
@Override @Override

View file

@ -97,6 +97,9 @@
android:title="@string/wraase_sc2_180" /> android:title="@string/wraase_sc2_180" />
</menu> </menu>
</item> </item>
<item
android:id="@+id/action_force_hffax_mode"
android:title="@string/hf_fax" />
<item <item
android:id="@+id/action_force_raw_mode" android:id="@+id/action_force_raw_mode"
android:icon="@drawable/baseline_image_not_supported_24" android:icon="@drawable/baseline_image_not_supported_24"

View file

@ -21,6 +21,7 @@
<string name="scottie2" translatable="false">Scottie 2</string> <string name="scottie2" translatable="false">Scottie 2</string>
<string name="scottie_dx" translatable="false">Scottie DX</string> <string name="scottie_dx" translatable="false">Scottie DX</string>
<string name="wraase_sc2_180" translatable="false">Wraase SC2180</string> <string name="wraase_sc2_180" translatable="false">Wraase SC2180</string>
<string name="hf_fax" translatable="false">HF Fax</string>
<string name="rate_8000" translatable="false">8 kHz</string> <string name="rate_8000" translatable="false">8 kHz</string>
<string name="rate_16000" translatable="false">16 kHz</string> <string name="rate_16000" translatable="false">16 kHz</string>
<string name="rate_32000" translatable="false">32 kHz</string> <string name="rate_32000" translatable="false">32 kHz</string>