Skip to content

Commit

Permalink
Merge pull request NovaSquirrel#77 from Gumball2415/feature-add-bisqw…
Browse files Browse the repository at this point in the history
…it-ntsc-smpte-C-colorimetry

Add alternative YIQ to RGB conversion equation (SMPTE C)
  • Loading branch information
NovaSquirrel authored Feb 27, 2022
2 parents 2472437 + 5890ad3 commit ed1514f
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 26 deletions.
51 changes: 34 additions & 17 deletions Core/BisqwitNtscFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
#include "EmulationSettings.h"
#include "Console.h"

BisqwitNtscFilter::BisqwitNtscFilter(shared_ptr<Console> console, int resDivider) : BaseVideoFilter(console)
BisqwitNtscFilter::BisqwitNtscFilter(shared_ptr<Console> console, int resDivider, bool SMPTE_C) : BaseVideoFilter(console)
{
_resDivider = resDivider;
_stopThread = false;
_workDone = false;
_SMPTE_C = SMPTE_C;

const int8_t signalLumaLow[4] = { -29, -15, 22, 71 };
const int8_t signalLumaHigh[4] = { 32, 66, 105, 105 };
Expand Down Expand Up @@ -49,7 +50,7 @@ BisqwitNtscFilter::BisqwitNtscFilter(shared_ptr<Console> console, int resDivider
outputBuffer += GetOverscan().GetScreenWidth() * 64 / _resDivider / _resDivider * (120 - GetOverscan().Top);
}

DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, (IsOddFrame() ? 8 : 0) + 327360);
DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, (IsOddFrame() ? 8 : 0) + 327360, SMPTE_C);

_workDone = true;
}
Expand All @@ -69,7 +70,7 @@ void BisqwitNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer)

_workDone = false;
_waitWork.Signal();
DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), (IsOddFrame() ? 8 : 0) + GetOverscan().Top*341*8);
DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), (IsOddFrame() ? 8 : 0) + GetOverscan().Top*341*8, _SMPTE_C);
while(!_workDone) {}
}

Expand Down Expand Up @@ -103,14 +104,30 @@ void BisqwitNtscFilter::OnBeforeApplyFilter()

_y = contrast / _yWidth;

_ir = (int)(contrast * 1.994681e-6 * saturation / _iWidth);
_qr = (int)(contrast * 9.915742e-7 * saturation / _qWidth);

_ig = (int)(contrast * 9.151351e-8 * saturation / _iWidth);
_qg = (int)(contrast * -6.334805e-7 * saturation / _qWidth);

_ib = (int)(contrast * -1.012984e-6 * saturation / _iWidth);
_qb = (int)(contrast * 1.667217e-6 * saturation / _qWidth);
// magic numbers is corresponding values from the YIQ to RGB formula
// but divided by 13,995 * [arbitrary value]
/*
_ir = (int)(( 0.95599 / (13995 * 34.2457747)) * contrast * saturation / _iWidth);
_ig = (int)((-0.27201 / (13995 * 212.3864250)) * contrast * saturation / _iWidth);
_ib = (int)((-1.10674 / (13995 * 78.0674723)) * contrast * saturation / _iWidth);
_qr = (int)(( 0.62082 / (13995 * 44.7370743)) * contrast * saturation / _qWidth);
_qg = (int)((-0.64720 / (13995 * 73.0015960)) * contrast * saturation / _qWidth);
_qb = (int)(( 1.70423 / (13995 * 73.0404051)) * contrast * saturation / _qWidth);
*/
_ir = (int)(contrast * 1.994681e-6 * saturation / _iWidth);
_ig = (int)(contrast * 9.151351e-8 * saturation / _iWidth);
_ib = (int)(contrast * -1.012984e-6 * saturation / _iWidth);
_qr = (int)(contrast * 9.915742e-7 * saturation / _qWidth);
_qg = (int)(contrast * -6.334805e-7 * saturation / _qWidth);
_qb = (int)(contrast * 1.667217e-6 * saturation / _qWidth);

// alternate values based on the SMPTE C color primaries
_irC = (int)((0.95599 / (13995 * 80)) * contrast * saturation / _iWidth);
_igC = (int)((-0.27201 / (13995 * 80)) * contrast * saturation / _iWidth);
_ibC = (int)((-1.10674 / (13995 * 80)) * contrast * saturation / _iWidth);
_qrC = (int)((0.62082 / (13995 * 80)) * contrast * saturation / _qWidth);
_qgC = (int)((-0.64720 / (13995 * 80)) * contrast * saturation / _qWidth);
_qbC = (int)((1.70423 / (13995 * 80)) * contrast * saturation / _qWidth);
}

void BisqwitNtscFilter::RecursiveBlend(int iterationCount, uint64_t *output, uint64_t *currentLine, uint64_t *nextLine, int pixelsPerCycle, bool verticalBlend)
Expand Down Expand Up @@ -192,7 +209,7 @@ void BisqwitNtscFilter::GenerateNtscSignal(int8_t *ntscSignal, int &phase, int r
phase += (341 - 256 - _paddingSize * 2) * _signalsPerPixel;
}

void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase)
void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase, bool SMPTE_C)
{
int pixelsPerCycle = 8 / _resDivider;
int phase = startPhase;
Expand All @@ -212,7 +229,7 @@ void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint16_t *ppuOutpu
GenerateNtscSignal(rowSignal, phase, y);

//Convert the NTSC signal to RGB
NtscDecodeLine(lineWidth * _signalsPerPixel, rowSignal, outputBuffer, (startCycle + 7) % 12);
NtscDecodeLine(lineWidth * _signalsPerPixel, rowSignal, outputBuffer, (startCycle + 7) % 12, SMPTE_C);

outputBuffer += rowPixelGap;
}
Expand Down Expand Up @@ -264,7 +281,7 @@ void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint16_t *ppuOutpu
* In essence it conveys in one integer the same information that real NTSC signal
* would convey in the colorburst period in the beginning of each scanline.
*/
void BisqwitNtscFilter::NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0)
void BisqwitNtscFilter::NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0, bool SMPTE_C)
{
auto Read = [=](int pos) -> char { return pos >= 0 ? signal[pos] : 0; };
auto Cos = [=](int pos) -> char { return _sinetable[(pos + 36) % 12 + phase0]; };
Expand All @@ -282,9 +299,9 @@ void BisqwitNtscFilter::NtscDecodeLine(int width, const int8_t* signal, uint32_t
qsum += Read(s) * Sin(s) - Read(s - _qWidth) * Sin(s - _qWidth);

if(!(s % _resDivider) && s >= leftOverscan) {
int r = std::min(255, std::max(0, (ysum*_y + isum*_ir + qsum*_qr) / 65536));
int g = std::min(255, std::max(0, (ysum*_y + isum*_ig + qsum*_qg) / 65536));
int b = std::min(255, std::max(0, (ysum*_y + isum*_ib + qsum*_qb) / 65536));
int r = std::min(255, std::max(0, (ysum*_y + isum*(SMPTE_C ? _irC : _ir) + qsum*(SMPTE_C ? _qrC : _qr)) / 65536));
int g = std::min(255, std::max(0, (ysum*_y + isum*(SMPTE_C ? _igC : _ig) + qsum*(SMPTE_C ? _qgC : _qg)) / 65536));
int b = std::min(255, std::max(0, (ysum*_y + isum*(SMPTE_C ? _ibC : _ib) + qsum*(SMPTE_C ? _qbC : _qb)) / 65536));

*target = 0xFF000000 | (r << 16) | (g << 8) | b;
target++;
Expand Down
11 changes: 6 additions & 5 deletions Core/BisqwitNtscFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class BisqwitNtscFilter : public BaseVideoFilter
atomic<bool> _workDone;

bool _keepVerticalRes = false;
bool _SMPTE_C = false;

int _resDivider = 1;
uint16_t *_ppuOutputBuffer = nullptr;
Expand All @@ -33,8 +34,8 @@ class BisqwitNtscFilter : public BaseVideoFilter
*/
int _yWidth, _iWidth, _qWidth;
int _y;
int _ir, _ig, _ib;
int _qr, _qg, _qb;
int _ir, _ig, _ib, _irC, _igC, _ibC;
int _qr, _qg, _qb, _qrC, _qgC, _qbC;

//To finetune hue, you would have to recalculate sinetable[]. (Coarse changes can be made with Phase0.)
int8_t _sinetable[27]; // 8*sin(x*2pi/12)
Expand All @@ -43,14 +44,14 @@ class BisqwitNtscFilter : public BaseVideoFilter

void RecursiveBlend(int iterationCount, uint64_t *output, uint64_t *currentLine, uint64_t *nextLine, int pixelsPerCycle, bool verticalBlend);

void NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0);
void NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0, bool SMPTE_C);

void GenerateNtscSignal(int8_t *ntscSignal, int &phase, int rowNumber);
void DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase);
void DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase, bool SMPTE_C);
void OnBeforeApplyFilter();

public:
BisqwitNtscFilter(shared_ptr<Console> console, int resDivider);
BisqwitNtscFilter(shared_ptr<Console> console, int resDivider, bool SMPTE_C);
virtual ~BisqwitNtscFilter();

virtual void ApplyFilter(uint16_t *ppuOutputBuffer);
Expand Down
3 changes: 3 additions & 0 deletions Core/EmulationSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ enum class VideoFilterType
Prescale8x = 23,
Prescale10x = 24,
Raw = 25,
BisqwitNtscSMPTECQuarterRes = 26,
BisqwitNtscSMPTECHalfRes = 27,
BisqwitNtscSMPTEC = 28,
HdPack = 999
};

Expand Down
3 changes: 3 additions & 0 deletions Core/ScaleFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ shared_ptr<ScaleFilter> ScaleFilter::GetScaleFilter(VideoFilterType filter)
case VideoFilterType::BisqwitNtsc:
case VideoFilterType::BisqwitNtscHalfRes:
case VideoFilterType::BisqwitNtscQuarterRes:
case VideoFilterType::BisqwitNtscSMPTEC:
case VideoFilterType::BisqwitNtscSMPTECHalfRes:
case VideoFilterType::BisqwitNtscSMPTECQuarterRes:
case VideoFilterType::NTSC:
case VideoFilterType::HdPack:
case VideoFilterType::Raw:
Expand Down
9 changes: 6 additions & 3 deletions Core/VideoDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,12 @@ void VideoDecoder::UpdateVideoFilter()
switch(_videoFilterType) {
case VideoFilterType::None: break;
case VideoFilterType::NTSC: _videoFilter.reset(new NtscFilter(_console)); break;
case VideoFilterType::BisqwitNtsc: _videoFilter.reset(new BisqwitNtscFilter(_console, 1)); break;
case VideoFilterType::BisqwitNtscHalfRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 2)); break;
case VideoFilterType::BisqwitNtscQuarterRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 4)); break;
case VideoFilterType::BisqwitNtsc: _videoFilter.reset(new BisqwitNtscFilter(_console, 1, false)); break;
case VideoFilterType::BisqwitNtscHalfRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 2, false)); break;
case VideoFilterType::BisqwitNtscQuarterRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 4, false)); break;
case VideoFilterType::BisqwitNtscSMPTEC: _videoFilter.reset(new BisqwitNtscFilter(_console, 1, true)); break;
case VideoFilterType::BisqwitNtscSMPTECHalfRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 2, true)); break;
case VideoFilterType::BisqwitNtscSMPTECQuarterRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 4, true)); break;
case VideoFilterType::Raw: _videoFilter.reset(new RawVideoFilter(_console)); break;
default: _scaleFilter = ScaleFilter::GetScaleFilter(_videoFilterType); break;
}
Expand Down
7 changes: 6 additions & 1 deletion GUI.NET/Forms/Config/frmVideoConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ protected override bool ValidateInput()
tlpNtscFilter2.Visible = false;
chkMergeFields.Visible = true;
grpNtscFilter.Visible = true;
} else if(filter == VideoFilterType.BisqwitNtsc || filter == VideoFilterType.BisqwitNtscHalfRes || filter == VideoFilterType.BisqwitNtscQuarterRes) {
} else if(filter == VideoFilterType.BisqwitNtsc ||
filter == VideoFilterType.BisqwitNtscHalfRes ||
filter == VideoFilterType.BisqwitNtscQuarterRes ||
filter == VideoFilterType.BisqwitNtscSMPTEC ||
filter == VideoFilterType.BisqwitNtscSMPTECHalfRes ||
filter == VideoFilterType.BisqwitNtscSMPTECQuarterRes) {
tlpNtscFilter1.Visible = true;
tlpNtscFilter2.Visible = true;
chkMergeFields.Visible = false;
Expand Down
30 changes: 30 additions & 0 deletions GUI.NET/Forms/frmMain.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions GUI.NET/Forms/frmMain.Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ private void UpdateFilterMenu(VideoFilterType filterType)
mnuNtscBisqwitFullFilter.Checked = (filterType == VideoFilterType.BisqwitNtsc);
mnuNtscBisqwitHalfFilter.Checked = (filterType == VideoFilterType.BisqwitNtscHalfRes);
mnuNtscBisqwitQuarterFilter.Checked = (filterType == VideoFilterType.BisqwitNtscQuarterRes);
mnuNtscSMPTECBisqwitFullFilter.Checked = (filterType == VideoFilterType.BisqwitNtscSMPTEC);
mnuNtscSMPTECBisqwitHalfFilter.Checked = (filterType == VideoFilterType.BisqwitNtscSMPTECHalfRes);
mnuNtscSMPTECBisqwitQuarterFilter.Checked = (filterType == VideoFilterType.BisqwitNtscSMPTECQuarterRes);

mnuXBRZ2xFilter.Checked = (filterType == VideoFilterType.xBRZ2x);
mnuXBRZ3xFilter.Checked = (filterType == VideoFilterType.xBRZ3x);
Expand Down Expand Up @@ -370,5 +373,20 @@ private void mnuNtscBisqwitQuarterFilter_Click(object sender, EventArgs e)
{
SetVideoFilter(VideoFilterType.BisqwitNtscQuarterRes);
}

private void mnuNtscSMPTECBisqwitFullFilter_Click(object sender, EventArgs e)
{
SetVideoFilter(VideoFilterType.BisqwitNtscSMPTEC);
}

private void mnuNtscSMPTECBisqwitHalfFilter_Click(object sender, EventArgs e)
{
SetVideoFilter(VideoFilterType.BisqwitNtscSMPTECHalfRes);
}

private void mnuNtscSMPTECBisqwitQuarterFilter_Click(object sender, EventArgs e)
{
SetVideoFilter(VideoFilterType.BisqwitNtscSMPTECQuarterRes);
}
}
}
3 changes: 3 additions & 0 deletions GUI.NET/Forms/frmMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,9 @@ private void UpdateMenus()
mnuNtscBisqwitQuarterFilter.Enabled = !isHdPackLoader;
mnuNtscBisqwitHalfFilter.Enabled = !isHdPackLoader;
mnuNtscBisqwitFullFilter.Enabled = !isHdPackLoader;
mnuNtscSMPTECBisqwitQuarterFilter.Enabled = !isHdPackLoader;
mnuNtscSMPTECBisqwitHalfFilter.Enabled = !isHdPackLoader;
mnuNtscSMPTECBisqwitFullFilter.Enabled = !isHdPackLoader;
}
} catch { }
}
Expand Down
3 changes: 3 additions & 0 deletions GUI.NET/InteropEmu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,9 @@ public enum VideoFilterType
Prescale6x = 22,
Prescale8x = 23,
Prescale10x = 24,
BisqwitNtscSMPTECQuarterRes = 26,
BisqwitNtscSMPTECHalfRes = 27,
BisqwitNtscSMPTEC = 28,
}

public enum HDPackOuputTileType
Expand Down

0 comments on commit ed1514f

Please sign in to comment.