mirror of
https://github.com/xdsopl/robot36.git
synced 2025-12-06 07:12:07 +01:00
Compare commits
14 commits
4c74d7347f
...
bb7b66b358
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb7b66b358 | ||
|
|
46f376e101 | ||
|
|
f745a6bddc | ||
|
|
e29f5728e3 | ||
|
|
a54b3f7fad | ||
|
|
1ed9f9094c | ||
|
|
df16e3c318 | ||
|
|
4f647e14b4 | ||
|
|
4779d67180 | ||
|
|
8173f147d8 | ||
|
|
09d5f6dc5a | ||
|
|
7ad54873da | ||
|
|
1502f20af1 | ||
|
|
5aa3be6cc9 |
|
|
@ -4,12 +4,12 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace 'xdsopl.robot36'
|
||||
compileSdk 35
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId "xdsopl.robot36"
|
||||
minSdk 24
|
||||
targetSdk 35
|
||||
targetSdk 36
|
||||
versionCode 65
|
||||
versionName "2.15"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package xdsopl.robot36;
|
|||
import android.graphics.Bitmap;
|
||||
|
||||
public abstract class BaseMode implements Mode {
|
||||
@Override
|
||||
public Bitmap postProcessScopeImage(Bitmap bmp) {
|
||||
return Bitmap.createScaledBitmap(bmp, bmp.getWidth() / 3, bmp.getHeight() / 3, true);
|
||||
}
|
||||
@Override
|
||||
public Bitmap postProcessScopeImage(Bitmap bmp) {
|
||||
return bmp;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public class Decoder {
|
|||
this.scopeBuffer = scopeBuffer;
|
||||
this.imageBuffer = imageBuffer;
|
||||
imageBuffer.line = -1;
|
||||
pixelBuffer = new PixelBuffer(2000, 2);
|
||||
pixelBuffer = new PixelBuffer(800, 2);
|
||||
demodulator = new Demodulator(sampleRate);
|
||||
double pulseFilterSeconds = 0.0025;
|
||||
int pulseFilterSamples = (int) Math.round(pulseFilterSeconds * sampleRate) | 1;
|
||||
|
|
@ -96,7 +96,7 @@ public class Decoder {
|
|||
double scanLineToleranceSeconds = 0.001;
|
||||
scanLineToleranceSamples = (int) Math.round(scanLineToleranceSeconds * sampleRate);
|
||||
rawMode = new RawDecoder(rawName, sampleRate);
|
||||
hfFaxMode = new HFFax("HF Fax", sampleRate);
|
||||
hfFaxMode = new HFFax(sampleRate);
|
||||
Mode robot36 = new Robot_36_Color(sampleRate);
|
||||
currentMode = robot36;
|
||||
currentScanLineSamples = robot36.getScanLineSamples();
|
||||
|
|
@ -159,7 +159,7 @@ public class Decoder {
|
|||
|
||||
private Mode findMode(ArrayList<Mode> modes, int code) {
|
||||
for (Mode mode : modes)
|
||||
if (mode.getCode() == code)
|
||||
if (mode.getVISCode() == code)
|
||||
return mode;
|
||||
return null;
|
||||
}
|
||||
|
|
@ -334,7 +334,7 @@ public class Decoder {
|
|||
}
|
||||
if (lockMode && mode != currentMode)
|
||||
return false;
|
||||
mode.reset();
|
||||
mode.resetState();
|
||||
imageBuffer.width = mode.getWidth();
|
||||
imageBuffer.height = mode.getHeight();
|
||||
imageBuffer.line = 0;
|
||||
|
|
@ -348,35 +348,29 @@ public class Decoder {
|
|||
for (int i = 0; i < pulses.length; ++i)
|
||||
pulses[i] = oldestSyncPulseIndex + i * currentScanLineSamples;
|
||||
Arrays.fill(lines, currentScanLineSamples);
|
||||
shiftSamples(lastSyncPulseIndex + mode.getBegin());
|
||||
shiftSamples(lastSyncPulseIndex + mode.getFirstPixelSampleIndex());
|
||||
drawLines(0xff00ff00, 8);
|
||||
drawLines(0xff000000, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param freqOffs offsets from expected sync frequency
|
||||
* @param pulses positions of sync pulses
|
||||
* @param lines lengths of scan lines
|
||||
* @param index position of latest sync pulse
|
||||
*/
|
||||
private boolean processSyncPulse(ArrayList<Mode> modes, float[] freqOffs, int[] pulses, int[] lines, int index) {
|
||||
for (int i = 1; i < pulses.length; ++i)
|
||||
pulses[i - 1] = pulses[i];
|
||||
pulses[pulses.length - 1] = index;
|
||||
for (int i = 1; i < lines.length; ++i)
|
||||
lines[i - 1] = lines[i];
|
||||
lines[lines.length - 1] = pulses[pulses.length - 1] - pulses[pulses.length - 2];
|
||||
private boolean processSyncPulse(ArrayList<Mode> modes, float[] freqOffs, int[] syncIndexes, int[] lineLengths, int latestSyncIndex) {
|
||||
for (int i = 1; i < syncIndexes.length; ++i)
|
||||
syncIndexes[i - 1] = syncIndexes[i];
|
||||
syncIndexes[syncIndexes.length - 1] = latestSyncIndex;
|
||||
for (int i = 1; i < lineLengths.length; ++i)
|
||||
lineLengths[i - 1] = lineLengths[i];
|
||||
lineLengths[lineLengths.length - 1] = syncIndexes[syncIndexes.length - 1] - syncIndexes[syncIndexes.length - 2];
|
||||
for (int i = 1; i < freqOffs.length; ++i)
|
||||
freqOffs[i - 1] = freqOffs[i];
|
||||
freqOffs[pulses.length - 1] = demodulator.frequencyOffset;
|
||||
if (lines[0] == 0)
|
||||
freqOffs[syncIndexes.length - 1] = demodulator.frequencyOffset;
|
||||
if (lineLengths[0] == 0)
|
||||
return false;
|
||||
double mean = scanLineMean(lines);
|
||||
double mean = scanLineMean(lineLengths);
|
||||
int scanLineSamples = (int) Math.round(mean);
|
||||
if (scanLineSamples < scanLineMinSamples || scanLineSamples > scratchBuffer.length)
|
||||
return false;
|
||||
if (scanLineStdDev(lines, mean) > scanLineToleranceSamples)
|
||||
if (scanLineStdDev(lineLengths, mean) > scanLineToleranceSamples)
|
||||
return false;
|
||||
boolean pictureChanged = false;
|
||||
if (lockMode || imageBuffer.line >= 0 && imageBuffer.line < imageBuffer.height) {
|
||||
|
|
@ -387,7 +381,7 @@ public class Decoder {
|
|||
currentMode = detectMode(modes, scanLineSamples);
|
||||
pictureChanged = currentMode != prevMode
|
||||
|| 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) {
|
||||
drawLines(0xff000000, 10);
|
||||
|
|
@ -395,26 +389,24 @@ public class Decoder {
|
|||
drawLines(0xff000000, 10);
|
||||
}
|
||||
float frequencyOffset = (float) frequencyOffsetMean(freqOffs);
|
||||
if (pulses[0] >= scanLineSamples && pictureChanged) {
|
||||
int endPulse = pulses[0];
|
||||
if (syncIndexes[0] >= scanLineSamples && pictureChanged) {
|
||||
int endPulse = syncIndexes[0];
|
||||
int extrapolate = endPulse / scanLineSamples;
|
||||
int firstPulse = endPulse - extrapolate * scanLineSamples;
|
||||
for (int pulseIndex = firstPulse; pulseIndex < endPulse; pulseIndex += scanLineSamples)
|
||||
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulseIndex, scanLineSamples, frequencyOffset));
|
||||
}
|
||||
for (int i = pictureChanged ? 0 : lines.length - 1; i < lines.length; ++i)
|
||||
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulses[i], lines[i], frequencyOffset));
|
||||
lastSyncPulseIndex = pulses[pulses.length - 1];
|
||||
for (int i = pictureChanged ? 0 : lineLengths.length - 1; i < lineLengths.length; ++i)
|
||||
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, syncIndexes[i], lineLengths[i], frequencyOffset));
|
||||
lastSyncPulseIndex = syncIndexes[syncIndexes.length - 1];
|
||||
currentScanLineSamples = scanLineSamples;
|
||||
lastFrequencyOffset = frequencyOffset;
|
||||
shiftSamples(lastSyncPulseIndex + currentMode.getBegin());
|
||||
shiftSamples(lastSyncPulseIndex + currentMode.getFirstPixelSampleIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@return true if new lines present
|
||||
*/
|
||||
public boolean process(float[] recordBuffer, int channelSelect) {
|
||||
boolean newLinesPresent = false;
|
||||
boolean syncPulseDetected = demodulator.process(recordBuffer, channelSelect);
|
||||
int syncPulseIndex = currentSample + demodulator.syncPulseOffset;
|
||||
int channels = channelSelect > 0 ? 2 : 1;
|
||||
|
|
@ -428,25 +420,28 @@ public class Decoder {
|
|||
if (syncPulseDetected) {
|
||||
switch (demodulator.syncPulseWidth) {
|
||||
case FiveMilliSeconds:
|
||||
return processSyncPulse(syncPulse5msModes, last5msFrequencyOffsets, last5msSyncPulses, last5msScanLines, syncPulseIndex);
|
||||
newLinesPresent = processSyncPulse(syncPulse5msModes, last5msFrequencyOffsets, last5msSyncPulses, last5msScanLines, syncPulseIndex);
|
||||
break;
|
||||
case NineMilliSeconds:
|
||||
leaderBreakIndex = syncPulseIndex;
|
||||
return processSyncPulse(syncPulse9msModes, last9msFrequencyOffsets, last9msSyncPulses, last9msScanLines, syncPulseIndex);
|
||||
newLinesPresent = processSyncPulse(syncPulse9msModes, last9msFrequencyOffsets, last9msSyncPulses, last9msScanLines, syncPulseIndex);
|
||||
break;
|
||||
case TwentyMilliSeconds:
|
||||
leaderBreakIndex = syncPulseIndex;
|
||||
return processSyncPulse(syncPulse20msModes, last20msFrequencyOffsets, last20msSyncPulses, last20msScanLines, syncPulseIndex);
|
||||
newLinesPresent = processSyncPulse(syncPulse20msModes, last20msFrequencyOffsets, last20msSyncPulses, last20msScanLines, syncPulseIndex);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (handleHeader())
|
||||
return true;
|
||||
if (currentSample > lastSyncPulseIndex + (currentScanLineSamples * 5) / 4) {
|
||||
} else if (handleHeader()) {
|
||||
newLinesPresent = true;
|
||||
} else if (currentSample > lastSyncPulseIndex + (currentScanLineSamples * 5) / 4) {
|
||||
copyLines(currentMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, lastSyncPulseIndex, currentScanLineSamples, lastFrequencyOffset));
|
||||
lastSyncPulseIndex += currentScanLineSamples;
|
||||
return true;
|
||||
newLinesPresent = true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return newLinesPresent;
|
||||
}
|
||||
|
||||
public void setMode(String name) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ public class Demodulator {
|
|||
private final SchmittTrigger syncPulseTrigger;
|
||||
private final Phasor baseBandOscillator;
|
||||
private final Delay syncPulseValueDelay;
|
||||
private final double scanLineBandwidth;
|
||||
private final double centerFrequency;
|
||||
private final float syncPulseFrequencyValue;
|
||||
private final float syncPulseFrequencyTolerance;
|
||||
private final int syncPulse5msMinSamples;
|
||||
|
|
@ -38,7 +40,7 @@ public class Demodulator {
|
|||
public static final double whiteFrequency = 2300;
|
||||
|
||||
Demodulator(int sampleRate) {
|
||||
double scanLineBandwidth = whiteFrequency - blackFrequency;
|
||||
scanLineBandwidth = whiteFrequency - blackFrequency;
|
||||
frequencyModulation = new FrequencyModulation(scanLineBandwidth, sampleRate);
|
||||
double syncPulse5msSeconds = 0.005;
|
||||
double syncPulse9msSeconds = 0.009;
|
||||
|
|
@ -65,22 +67,23 @@ public class Demodulator {
|
|||
Kaiser kaiser = new Kaiser();
|
||||
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));
|
||||
double centerFrequency = (lowestFrequency + highestFrequency) / 2;
|
||||
centerFrequency = (lowestFrequency + highestFrequency) / 2;
|
||||
baseBandOscillator = new Phasor(-centerFrequency, sampleRate);
|
||||
syncPulseFrequencyValue = (float) ((syncPulseFrequency - centerFrequency) * 2 / scanLineBandwidth); //converts to range from -1 to 1
|
||||
syncPulseFrequencyValue = (float) normalizeFrequency(syncPulseFrequency);
|
||||
syncPulseFrequencyTolerance = (float) (50 * 2 / scanLineBandwidth);
|
||||
double syncPorchFrequency = 1500;
|
||||
double syncHighFrequency = (syncPulseFrequency + syncPorchFrequency) / 2;
|
||||
double syncLowFrequency = (syncPulseFrequency + syncHighFrequency) / 2;
|
||||
double syncLowValue = (syncLowFrequency - centerFrequency) * 2 / scanLineBandwidth;
|
||||
double syncHighValue = (syncHighFrequency - centerFrequency) * 2 / scanLineBandwidth;
|
||||
double syncLowValue = normalizeFrequency(syncLowFrequency);
|
||||
double syncHighValue = normalizeFrequency(syncHighFrequency);
|
||||
syncPulseTrigger = new SchmittTrigger((float) syncLowValue, (float) syncHighValue);
|
||||
baseBand = new Complex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if sync pulse detected
|
||||
*/
|
||||
private double normalizeFrequency(double frequency) {
|
||||
return (frequency - centerFrequency) * 2 / scanLineBandwidth;
|
||||
}
|
||||
|
||||
public boolean process(float[] buffer, int channelSelect) {
|
||||
boolean syncPulseDetected = false;
|
||||
int channels = channelSelect > 0 ? 2 : 1;
|
||||
|
|
|
|||
|
|
@ -9,123 +9,123 @@ 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;
|
||||
private final ExponentialMovingAverage lowPassFilter;
|
||||
private final String name;
|
||||
private final int sampleRate;
|
||||
private final float[] cumulated;
|
||||
private int horizontalShift = 0;
|
||||
|
||||
HFFax(String name, int sampleRate) {
|
||||
this.name = name;
|
||||
lowPassFilter = new ExponentialMovingAverage();
|
||||
this.sampleRate = sampleRate;
|
||||
cumulated = new float[getWidth()];
|
||||
}
|
||||
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);
|
||||
}
|
||||
private float freqToLevel(float frequency, float offset) {
|
||||
return 0.5f * (frequency - offset + 1.f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return -1;
|
||||
}
|
||||
@Override
|
||||
public int getVISCode() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return 1808;
|
||||
}
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return 640;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 1200;
|
||||
}
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 1200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int getFirstPixelSampleIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return -1;
|
||||
}
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return sampleRate / 2;
|
||||
}
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return sampleRate / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
@Override
|
||||
public void resetState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap postProcessScopeImage(Bitmap bmp) {
|
||||
if (horizontalShift > 0) {
|
||||
Bitmap bmpMutable = Bitmap.createBitmap(getWidth(), bmp.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bmpMutable);
|
||||
canvas.drawBitmap(
|
||||
bmp,
|
||||
new Rect(0, 0, horizontalShift, bmp.getHeight()),
|
||||
new Rect(getWidth() - horizontalShift, 0, getWidth(), bmp.getHeight()),
|
||||
null);
|
||||
canvas.drawBitmap(
|
||||
bmp,
|
||||
new Rect(horizontalShift, 0, getWidth(), bmp.getHeight()),
|
||||
new Rect(0, 1, getWidth() - horizontalShift, bmp.getHeight() + 1),
|
||||
null);
|
||||
@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;
|
||||
}
|
||||
return bmpMutable;
|
||||
}
|
||||
|
||||
return bmp;
|
||||
}
|
||||
@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;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
|
||||
horizontalShift = bestIndex;
|
||||
|
||||
pixelBuffer.width = horizontalPixels;
|
||||
pixelBuffer.height = 1;
|
||||
return true;
|
||||
}
|
||||
pixelBuffer.width = horizontalPixels;
|
||||
pixelBuffer.height = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
|
|
@ -44,7 +43,6 @@ import androidx.appcompat.app.AppCompatDelegate;
|
|||
import androidx.appcompat.widget.ShareActionProvider;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
|
|
@ -74,7 +72,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
private ImageView peakMeterView;
|
||||
private PixelBuffer imageBuffer;
|
||||
private ShortTimeFourierTransform stft;
|
||||
private final int binWidthHz = 10;
|
||||
private short[] shortBuffer;
|
||||
private float[] recordBuffer;
|
||||
private AudioRecord audioRecord;
|
||||
|
|
@ -92,6 +89,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
private int tintColor;
|
||||
private boolean autoSave;
|
||||
private boolean showSpectrogram;
|
||||
private final int binWidthHz = 10;
|
||||
private final int[] freqMarkers = { 1100, 1300, 1500, 2300 };
|
||||
|
||||
private void setStatus(int id) {
|
||||
setTitle(id);
|
||||
|
|
@ -226,22 +225,12 @@ public class MainActivity extends AppCompatActivity {
|
|||
double lowest = Math.log(1e-9);
|
||||
double highest = Math.log(1);
|
||||
double range = highest - lowest;
|
||||
int lowestBin = 14;
|
||||
int minFreq = 140;
|
||||
int minBin = minFreq / binWidthHz;
|
||||
for (int i = 0; i < stride; ++i)
|
||||
waterfallPlotBuffer.pixels[line + i] = rainbow((Math.log(stft.power[i + lowestBin]) - lowest) / range);
|
||||
|
||||
int[] markerFrequencies = new int[] {
|
||||
(int)Demodulator.syncPulseFrequency,
|
||||
(int)Demodulator.blackFrequency,
|
||||
(int)Demodulator.whiteFrequency,
|
||||
};
|
||||
for (int freq: markerFrequencies) {
|
||||
int marker = freq / binWidthHz - lowestBin;
|
||||
waterfallPlotBuffer.pixels[line + marker - 1] = Color.BLACK;
|
||||
waterfallPlotBuffer.pixels[line + marker] = ColorUtils.blendARGB(waterfallPlotBuffer.pixels[line + marker], Color.GREEN, 0.8f);
|
||||
waterfallPlotBuffer.pixels[line + marker + 1] = Color.BLACK;
|
||||
}
|
||||
|
||||
waterfallPlotBuffer.pixels[line + i] = rainbow((Math.log(stft.power[i + minBin]) - lowest) / range);
|
||||
for (int freq : freqMarkers)
|
||||
waterfallPlotBuffer.pixels[line + (freq - minFreq) / binWidthHz] = fgColor;
|
||||
System.arraycopy(waterfallPlotBuffer.pixels, line, waterfallPlotBuffer.pixels, line + stride * (waterfallPlotBuffer.height / 2), stride);
|
||||
}
|
||||
}
|
||||
|
|
@ -319,7 +308,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
int sampleSize = audioFormat == AudioFormat.ENCODING_PCM_FLOAT ? 4 : 2;
|
||||
int frameSize = sampleSize * channelCount;
|
||||
int readsPerSecond = 50;
|
||||
int bufferSize = Integer.highestOneBit(recordRate) * frameSize * 4;
|
||||
int bufferSize = Integer.highestOneBit(recordRate) * frameSize;
|
||||
int frameCount = recordRate / readsPerSecond;
|
||||
int bufferCount = frameCount * channelCount;
|
||||
recordBuffer = new float[bufferCount];
|
||||
|
|
@ -568,7 +557,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
fgColor = getColor(R.color.fg);
|
||||
thinColor = getColor(R.color.thin);
|
||||
tintColor = getColor(R.color.tint);
|
||||
scopeBuffer = new PixelBuffer(640 * 3, 2 * 1280 * 3);
|
||||
scopeBuffer = new PixelBuffer(640, 2 * 1280);
|
||||
waterfallPlotBuffer = new PixelBuffer(256, 2 * 256);
|
||||
peakMeterBuffer = new PixelBuffer(1, 16);
|
||||
imageBuffer = new PixelBuffer(800, 616);
|
||||
|
|
|
|||
|
|
@ -9,59 +9,26 @@ package xdsopl.robot36;
|
|||
import android.graphics.Bitmap;
|
||||
|
||||
public interface Mode {
|
||||
/**
|
||||
* @return mode name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return VIS code
|
||||
*/
|
||||
int getCode();
|
||||
int getVISCode();
|
||||
|
||||
/**
|
||||
* @return image width
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* @return image height
|
||||
*/
|
||||
int getHeight();
|
||||
|
||||
/**
|
||||
* @return number of samples from sync pulse to start of image data
|
||||
*/
|
||||
int getBegin();
|
||||
int getFirstPixelSampleIndex();
|
||||
|
||||
/**
|
||||
* @return number of samples from start of first scanline to first sync pulse?, nonzero for Scottie
|
||||
*/
|
||||
int getFirstSyncPulseIndex();
|
||||
|
||||
/**
|
||||
* @return number of samples in a scanline
|
||||
*/
|
||||
int getScanLineSamples();
|
||||
|
||||
/**
|
||||
* Adjust scope image before saving
|
||||
*/
|
||||
Bitmap postProcessScopeImage(Bitmap bmp);
|
||||
|
||||
/**
|
||||
* Reset internal state.
|
||||
*/
|
||||
void reset();
|
||||
void resetState();
|
||||
|
||||
/**
|
||||
* @param pixelBuffer buffer to store decoded pixels
|
||||
* @param scratchBuffer buffer for temporary data
|
||||
* @param scanLineBuffer raw samples to be decoded, can contain more than one scanline
|
||||
* @param scopeBufferWidth used in RawDecoder, initializes width?
|
||||
* @param syncPulseIndex number of samples from array start to sync pulse
|
||||
* @param scanLineSamples number of samples per scanline
|
||||
* @param frequencyOffset correction of frequency of expected vs actual sync pulse (normalized to range (-1, 1))
|
||||
* @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);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class PaulDon extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
public int getVISCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ public class PaulDon extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
public int getFirstPixelSampleIndex() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ public class PaulDon extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
public void resetState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class RGBDecoder extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
public int getVISCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ public class RGBDecoder extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
public int getFirstPixelSampleIndex() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ public class RGBDecoder extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
public void resetState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class RawDecoder extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
public int getVISCode() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ public class RawDecoder extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
public int getFirstPixelSampleIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ public class RawDecoder extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
public void resetState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class Robot_36_Color extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
public int getVISCode() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ public class Robot_36_Color extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
public int getFirstPixelSampleIndex() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ public class Robot_36_Color extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
public void resetState() {
|
||||
lastEven = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,12 +47,6 @@ public class Robot_72_Color extends BaseMode {
|
|||
lowPassFilter = new ExponentialMovingAverage();
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME same in other modes (copy&paste)
|
||||
* @param frequency frequency, range (-1,1)
|
||||
* @param offset correction, range (-1,1)
|
||||
* @return pixel value, range (0,1)
|
||||
*/
|
||||
private float freqToLevel(float frequency, float offset) {
|
||||
return 0.5f * (frequency - offset + 1.f);
|
||||
}
|
||||
|
|
@ -63,7 +57,7 @@ public class Robot_72_Color extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
public int getVISCode() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +72,7 @@ public class Robot_72_Color extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
public int getFirstPixelSampleIndex() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +87,7 @@ public class Robot_72_Color extends BaseMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
public void resetState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
[versions]
|
||||
agp = "8.9.0"
|
||||
agp = "8.12.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
junitVersion = "1.3.0"
|
||||
espressoCore = "3.7.0"
|
||||
appcompat = "1.7.1"
|
||||
material = "1.12.0"
|
||||
activity = "1.10.1"
|
||||
constraintlayout = "2.2.1"
|
||||
|
|
|
|||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#Fri Apr 12 11:35:07 CEST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
Loading…
Reference in a new issue