From 836df9df68b95100722e4e83f2cdacc63fe40bb4 Mon Sep 17 00:00:00 2001 From: "Christopher A. Mosher" Date: Mon, 14 Sep 2020 16:50:42 -0400 Subject: [PATCH] work in progress: video tweaks (and reformat source code) --- src/analogtv.cpp | 17 +- src/picturegenerator.cpp | 413 +++++++++++++++++---------------------- src/picturegenerator.h | 8 +- src/powerupreset.cpp | 34 ++-- src/powerupreset.h | 15 +- src/video.h | 4 +- src/videomode.cpp | 90 ++++++--- src/videomode.h | 37 ++-- 8 files changed, 305 insertions(+), 313 deletions(-) diff --git a/src/analogtv.cpp b/src/analogtv.cpp index 451372c..dd6d41f 100644 --- a/src/analogtv.cpp +++ b/src/analogtv.cpp @@ -455,9 +455,20 @@ void AnalogTV::ntsc_to_yiq(const int isignal, const int siglen, const IQ& iq_fac int inline AnalogTV::yiq2rgb(const int yiq) { - double r = (((yiq)&0xFF)-IQINTOFF) + 0.956 * (((yiq>>8)&0xFF)-IQINTOFF) + 0.621 * (((yiq>>16)&0xFF)-IQINTOFF); - double g = (((yiq)&0xFF)-IQINTOFF) - 0.272 * (((yiq>>8)&0xFF)-IQINTOFF) - 0.647 * (((yiq>>16)&0xFF)-IQINTOFF); - double b = (((yiq)&0xFF)-IQINTOFF) - 1.105 * (((yiq>>8)&0xFF)-IQINTOFF) + 1.702 * (((yiq>>16)&0xFF)-IQINTOFF); + /* + * https://en.wikipedia.org/wiki/YIQ (2019-09-19): + * To convert from FCC YIQ to RGB: + * E_R = E_Y + 0.9469*E_I + 0.6236*E_Q + * E_G = E_Y - 0.2748*E_I - 0.6357*E_Q + * E_B = E_Y - 1.1 *E_I + 1.7 *E_Q + */ + const double y = (yiq&0xFF)-IQINTOFF; + const double i = ((yiq>>8)&0xFF)-IQINTOFF; + const double q = ((yiq>>16)&0xFF)-IQINTOFF; + + double r = y + 0.9469*i + 0.6236*q; + double g = y - 0.2748*i - 0.6357*q; + double b = y - 1.1000*i + 1.7000*q; const int rgb = (calc_color(r) << 16)| diff --git a/src/picturegenerator.cpp b/src/picturegenerator.cpp index 8cc55e5..600df1b 100644 --- a/src/picturegenerator.cpp +++ b/src/picturegenerator.cpp @@ -22,265 +22,215 @@ #include "e2const.h" PictureGenerator::PictureGenerator(AnalogTV& display, VideoMode& mode, const int& revision): - display(display), mode(mode), itestsig(testsig), itestsiglim(testsig+AppleNTSC::SIGNAL_LEN), - VISIBLE_X_OFFSET(E2Const::BYTES_PER_ROW-E2Const::VISIBLE_BYTES_PER_ROW), - revision(revision) -{ + display(display), mode(mode), itestsig(testsig), itestsiglim(testsig+AppleNTSC::SIGNAL_LEN), + VISIBLE_X_OFFSET(E2Const::BYTES_PER_ROW-E2Const::VISIBLE_BYTES_PER_ROW), + revision(revision) { } -PictureGenerator::~PictureGenerator() -{ +PictureGenerator::~PictureGenerator() { } -void PictureGenerator::powerOn() -{ - this->hpos = 0; - this->line = 0; - this->display.signal = this->testsig; - this->itestsig = this->testsig; +void PictureGenerator::powerOn() { + this->hpos = 0; + this->line = 0; + this->display.signal = this->testsig; + this->itestsig = this->testsig; } -void inline PictureGenerator::shiftLoRes() -{ - /* - * For byte ABCDEFGH in register, perform - * the following 4-bit end-around shifts: - * - * +---<----+ +---<----+ - * | | | | - * +->ABCD->+ +->EFGH->+ - * - * Therefore: - * - * ABCDEFGH --> DABCHEFG - */ +void inline PictureGenerator::shiftLoRes() { + /* + * For byte ABCDEFGH in register, perform + * the following 4-bit end-around shifts: + * + * +---<----+ +---<----+ + * | | | | + * +->ABCD->+ +->EFGH->+ + * + * Therefore: + * + * ABCDEFGH --> DABCHEFG + */ - unsigned char rot_bits = this->latchGraphics & 0x11; - // 000D000H - rot_bits <<= 3; - // D000H000 + unsigned char rot_bits = this->latchGraphics & 0x11; + // 000D000H + rot_bits <<= 3; + // D000H000 - this->latchGraphics &= 0xEE; - // ABC0EFG0 - this->latchGraphics >>= 1; - // 0ABC0EFG - this->latchGraphics |= rot_bits; - // DABCHEFG + this->latchGraphics &= 0xEE; + // ABC0EFG0 + this->latchGraphics >>= 1; + // 0ABC0EFG + this->latchGraphics |= rot_bits; + // DABCHEFG } -void inline PictureGenerator::shiftHiRes() -{ - /* - * For byte ABCDEFGH in register, perform - * the following shift: - * - * +---<----+ - * | | - * +->ABCD->+--->EFGH-> - * - * Therefore: - * - * ABCDEFGH --> DABCDEFG - */ +void inline PictureGenerator::shiftHiRes() { + /* + * For byte ABCDEFGH in register, perform + * the following shift: + * + * +---<----+ + * | | + * +->ABCD->+--->EFGH-> + * + * Therefore: + * + * ABCDEFGH --> DABCDEFG + */ - unsigned char rot_bits = this->latchGraphics & 0x10; - // 000D0000 - rot_bits <<= 3; - // D0000000 + unsigned char rot_bits = this->latchGraphics & 0x10; + // 000D0000 + rot_bits <<= 3; + // D0000000 - this->latchGraphics >>= 1; - // 0ABCDEFG - this->latchGraphics |= rot_bits; - // DABCDEFG + this->latchGraphics >>= 1; + // 0ABCDEFG + this->latchGraphics |= rot_bits; + // DABCDEFG } -void inline PictureGenerator::shiftText() -{ - this->latchText >>= 1; +void inline PictureGenerator::shiftText() { + this->latchText >>= 1; } -bool inline PictureGenerator::getTextBit() -{ - return this->latchText & 1; +bool inline PictureGenerator::getTextBit() { + return this->latchText & 1; } -bool inline PictureGenerator::getHiResBit() -{ - return this->latchGraphics & 1; +bool inline PictureGenerator::getHiResBit() { + return this->latchGraphics & 1; } -bool inline PictureGenerator::getLoResBit(const bool odd, const bool vc) -{ - const int nibble = (this->latchGraphics >> (vc ? 4 : 0)) & 0x0F; - return (nibble >> (odd ? 2 : 0)) & 1; +bool inline PictureGenerator::getLoResBit(const bool odd, const bool vc) { + const int nibble = (this->latchGraphics >> (vc ? 4 : 0)) & 0x0F; + return (nibble >> (odd ? 2 : 0)) & 1; } -void inline PictureGenerator::loadGraphics(const unsigned char value) -{ - this->latchGraphics = value; - this->d7 = this->latchGraphics & 0x80; +void inline PictureGenerator::loadGraphics(const unsigned char value) { + this->latchGraphics = value; + this->d7 = this->latchGraphics & 0x80; } -void inline PictureGenerator::loadText(const int value) -{ - this->latchText = value; +void inline PictureGenerator::loadText(const unsigned char value) { + this->latchText = value; } -// TODO can we hand-optimize the main picture generator algorithm any more? -// Note that the innermost loop (which calls writeVideoSignal) has to execute -// at 14MHz, in order to maintain authentic Apple ][ speed. -void PictureGenerator::tick(const int t, const unsigned char rowToPlot) -{ - const bool isText(this->mode.isDisplayingText(t)); - const bool isHiRes(this->mode.isHiRes()); +void PictureGenerator::tick(const int t, const unsigned char c) { + this->mode.tick(); - signed char* is = this->itestsig; + const bool isText(this->mode.isDisplayingText(t)); + const bool isHiRes(this->mode.isHiRes()); - if (isText) - loadText(rowToPlot); - else - loadGraphics(rowToPlot); + signed char* is = this->itestsig; - if (t==0) - { - this->line = 0; - } + if (isText) { + loadText(c); + } else { + loadGraphics(c); + } - int cycles = E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE; - if (this->hpos == E2Const::HORIZ_CYCLES-1) - { - cycles += E2Const::EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE; - } + if (t==0) { + this->line = 0; + } - // hi-res half-pixel shift: - const bool shift = !isText && isHiRes && this->d7 && this->line < E2Const::VISIBLE_ROWS_PER_FIELD && !(this->hpos < VISIBLE_X_OFFSET) && this->revision > 0; - const bool showLastHiRes = shift && this->lasthires; + int cycles = E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE; + if (this->hpos == E2Const::HORIZ_CYCLES-1) { + cycles += E2Const::EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE; + } - int xtra(0); - if (shift) - { - --cycles; - ++xtra; - } - const int firstBlankedCycle(E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE-xtra); + // hi-res half-pixel shift: + const bool shift = !isText && isHiRes && this->d7 && this->line < E2Const::VISIBLE_ROWS_PER_FIELD && !(this->hpos < VISIBLE_X_OFFSET) && this->revision > 0; + const bool showLastHiRes = shift && this->lasthires; - int hcycle(this->hpos*E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE); - const bool lineVis(this->line < E2Const::VISIBLE_ROWS_PER_FIELD); - const bool hVis(this->hpos >= VISIBLE_X_OFFSET); - for (int cycle(0); cycle < cycles-1; ++cycle) - { - const bool bit = shiftLatch(t,cycle,isText,isHiRes); - is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is); - ++hcycle; - } - // optimization: pull the last iteration of the loop out, so we don't getHiResBit every time - { - this->lasthires = getHiResBit(); // save it for the next plotted byte, just in case we need it - const int cycle = cycles-1; - const bool bit = shiftLatch(t,cycle,isText,isHiRes); - is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is); - } + int xtra(0); + if (shift) { + --cycles; + ++xtra; + } + const int firstBlankedCycle(E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE-xtra); - this->itestsig = is; + unsigned int hcycle(this->hpos*E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE); + const bool lineVis(this->line < E2Const::VISIBLE_ROWS_PER_FIELD); + const bool hVis(this->hpos >= VISIBLE_X_OFFSET); + for (int cycle(0); cycle < cycles; ++cycle) { + this->lasthires = getHiResBit(); // save it for the next plotted byte, just in case we need it + const bool bit = shiftLatch(t,cycle,isText,isHiRes); + is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is); + ++hcycle; + } - ++this->hpos; - if (this->hpos >= E2Const::HORIZ_CYCLES) - { - this->hpos = 0; - ++this->line; - if (this->itestsig >= this->itestsiglim) - { - this->itestsig = this->testsig; - this->display.drawCurrent(); - } - } + this->itestsig = is; + + ++this->hpos; + if (this->hpos >= E2Const::HORIZ_CYCLES) { + this->hpos = 0; + ++this->line; + if (this->itestsig >= this->itestsiglim) { + this->itestsig = this->testsig; + this->display.drawCurrent(); + } + } } -bool inline PictureGenerator::shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes) -{ - bool bit; - if (isText) - { - bit = getTextBit(); - if (cycle & 1) // @ 7MHz - { - shiftText(); - } - } - else if (isHiRes) - { - bit = getHiResBit(); - if (cycle & 1) // @ 7MHz - { - shiftHiRes(); - } - } - else // LO-RES - { - const int y = t / E2Const::BYTES_PER_ROW; - bit = getLoResBit((t & 1) == (this->line & 1), y & 4); - shiftLoRes(); - } - return bit; +bool inline PictureGenerator::shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes) { + bool bit; + if (isText) { + bit = getTextBit(); + if (cycle & 1) { // @ 7MHz + shiftText(); + } + } else if (isHiRes) { + bit = getHiResBit(); + if (cycle & 1) { // @ 7MHz + shiftHiRes(); + } + } else { // LO-RES + const int y = t / E2Const::BYTES_PER_ROW; + bit = getLoResBit((t & 1) == (this->line & 1), y & 4); + shiftLoRes(); + } + return bit; } -inline signed char* PictureGenerator::writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is) -{ - if (shift && !cycle) - { - *is++ = showLastHiRes ? AppleNTSC::WHITE_LEVEL : AppleNTSC::BLANK_LEVEL; - } +inline signed char* PictureGenerator::writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const unsigned int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is) { + if (shift && !cycle) { + *is++ = showLastHiRes ? AppleNTSC::WHITE_LEVEL : AppleNTSC::BLANK_LEVEL; + } - signed char sig; - if (lineVis) - { - if (hVis) - { - if (bit && cycle < firstBlankedCycle) - { - sig = AppleNTSC::WHITE_LEVEL; - } - else - { - sig = AppleNTSC::BLANK_LEVEL; - } - } - else - { - sig = hbl(hcycle); - } - } - else - { - sig = vbl(hcycle); - } - *is++ = sig; - return is; + signed char sig; + if (lineVis) { + if (hVis) { + if (bit && cycle < firstBlankedCycle) { + sig = AppleNTSC::WHITE_LEVEL; + } else { + sig = AppleNTSC::BLANK_LEVEL; + } + } else { + sig = hbl(hcycle); + } + } else { + sig = vbl(hcycle); + } + *is++ = sig; + return is; } // TODO Just to be extremely accurate, fix picture signal values during HBL and VBL // (note that they vary by motherboard revision... there is a whole section in U.A.2) -signed char inline PictureGenerator::vbl(const int hcycle) -{ - signed char sig; - if (224 <= this->line && this->line < 240) // VSYNC // TODO symbolize constants - { - sig = AppleNTSC::SYNC_LEVEL; - } - else - { - if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) - { - sig = AppleNTSC::SYNC_LEVEL; - } - else - { - sig = AppleNTSC::BLANK_LEVEL; - } - } - return sig; +signed char inline PictureGenerator::vbl(const unsigned int hcycle) { + signed char sig; + if (224 <= this->line && this->line < 240) { // VSYNC // TODO symbolize constants + sig = AppleNTSC::SYNC_LEVEL; + } else { + if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) { + sig = AppleNTSC::SYNC_LEVEL; + } else { + sig = AppleNTSC::BLANK_LEVEL; + } + } + return sig; } @@ -297,27 +247,18 @@ const signed char PictureGenerator::lutCB[] = { 0, -AppleNTSC::CB_LEVEL, 0, +AppleNTSC::CB_LEVEL }; //{ +AppleNTSC::CB_LEVEL/2, -AppleNTSC::CB_LEVEL/2, -AppleNTSC::CB_LEVEL/2, +AppleNTSC::CB_LEVEL/2 }; -signed char inline PictureGenerator::hbl(const int hcycle) -{ - signed char cb; - if (AppleNTSC::CB_START <= hcycle && hcycle < AppleNTSC::CB_END) - { - if (this->mode.isText() && this->revision > 0) - { - cb = AppleNTSC::BLANK_LEVEL; - } - else - { - cb = lutCB[(hcycle-AppleNTSC::CB_START)%4]; - } - } - else if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) - { - cb = AppleNTSC::SYNC_LEVEL; - } - else - { - cb = AppleNTSC::BLANK_LEVEL; - } - return cb; +signed char inline PictureGenerator::hbl(const unsigned int hcycle) { + signed char cb; + if (AppleNTSC::CB_START <= hcycle && hcycle < AppleNTSC::CB_END) { + if (this->mode.isText() && this->revision > 0) { + cb = AppleNTSC::BLANK_LEVEL; + } else { + cb = lutCB[(hcycle-AppleNTSC::CB_START)%4]; + } + } else if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) { + cb = AppleNTSC::SYNC_LEVEL; + } else { + cb = AppleNTSC::BLANK_LEVEL; + } + return cb; } diff --git a/src/picturegenerator.h b/src/picturegenerator.h index 7c7d9c7..5595b27 100644 --- a/src/picturegenerator.h +++ b/src/picturegenerator.h @@ -46,11 +46,11 @@ private: bool getHiResBit(); bool getLoResBit(const bool odd, const bool vc); void loadGraphics(const unsigned char value); - void loadText(const int value); + void loadText(const unsigned char value); bool shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes); - signed char* writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is); - signed char vbl(const int hcycle); - signed char hbl(const int hcycle); + signed char* writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const unsigned int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is); + signed char vbl(const unsigned int hcycle); + signed char hbl(const unsigned int hcycle); const unsigned int VISIBLE_X_OFFSET; diff --git a/src/powerupreset.cpp b/src/powerupreset.cpp index f99bf44..de7a405 100644 --- a/src/powerupreset.cpp +++ b/src/powerupreset.cpp @@ -18,35 +18,25 @@ #include "powerupreset.h" #include "apple2.h" #include "e2const.h" +#include PowerUpReset::PowerUpReset(Apple2& apple): - apple(apple) -{ + apple(apple) { } -PowerUpReset::~PowerUpReset() -{ +PowerUpReset::~PowerUpReset() { } - -void PowerUpReset::tick() -{ - if (this->pendingTicks > 0) - { - --this->pendingTicks; - if (this->pendingTicks == 0) - { - this->apple.reset(); - } - } +void PowerUpReset::tick() { + if (this->pendingTicks > 0) { + --this->pendingTicks; + if (this->pendingTicks == 0) { + this->apple.reset(); + } + } } -void PowerUpReset::powerOn() -{ -#ifdef USE_EMU - this->pendingTicks = 99; // TODO REMOVE THIS -#else - this->pendingTicks = (int)(E2Const::AVG_CPU_HZ*.3); // U.A.II, p. 7-15 -#endif +void PowerUpReset::powerOn() { + this->pendingTicks = std::lround(E2Const::AVG_CPU_HZ*.3); // U.A.II, p. 7-15 } diff --git a/src/powerupreset.h b/src/powerupreset.h index 4083e23..a71b601 100644 --- a/src/powerupreset.h +++ b/src/powerupreset.h @@ -20,17 +20,16 @@ class Apple2; -class PowerUpReset -{ +class PowerUpReset { private: - Apple2& apple; - int pendingTicks; + Apple2& apple; + long pendingTicks; public: - PowerUpReset(Apple2& apple); - ~PowerUpReset(); - void powerOn(); - void tick(); + PowerUpReset(Apple2& apple); + ~PowerUpReset(); + void powerOn(); + void tick(); }; #endif diff --git a/src/video.h b/src/video.h index 18c9483..c20cbb4 100644 --- a/src/video.h +++ b/src/video.h @@ -39,8 +39,8 @@ private: std::vector lutTEXT[2]; // [0] is page 1, [1] is page 2 std::vector lutHRES[2]; // [0] is page 1, [1] is page 2 - VideoMode& mode; - AddressBus& addressBus; + VideoMode& mode; + AddressBus& addressBus; PictureGenerator& picgen; TextCharacters& textRows; diff --git a/src/videomode.cpp b/src/videomode.cpp index 7db19ed..39a8e05 100644 --- a/src/videomode.cpp +++ b/src/videomode.cpp @@ -17,32 +17,74 @@ */ #include "videomode.h" -VideoMode::VideoMode() -{ +VideoMode::VideoMode(): + pendText(0), + pendMixed(0), + pendHiRes(0) { } -unsigned char VideoMode::io(const unsigned short addr, const unsigned char b) -{ - const int sw = (addr & 0xE) >> 1; - const bool on = addr & 0x1; - switch (sw) - { - case 0: - this->swText = on; break; - case 1: - this->swMixed = on; break; - case 2: - this->swPage2 = on ? 1 : 0; break; - case 3: - this->swHiRes = on; break; - } - return b; +unsigned char VideoMode::io(const unsigned short addr, const unsigned char b) { + const int sw = (addr & 0x6) >> 1; + const bool on = addr & 0x1; + + // TODO verify all soft-switch change timings + switch (sw) { + case 0: + if (this->swText != on) { + this->pendText = 1; + } + break; + case 1: + if (this->swMixed != on) { + this->pendMixed = 1; + } + break; + case 2: + this->pendPage2 = on; +// if (this->swPage2 != on) { +// this->pendPage2 = 1; +// } + break; + case 3: + this->swHiRes = on; +// if (this->swHiRes != on) { +// if (on) { +// this->pendHiRes = 1; +// } else { +// this->pendHiRes = 2; +// } +// } + break; + } + return b; } -void VideoMode::powerOn() -{ - this->swText = false; - this->swMixed = false; - this->swPage2 = 0; - this->swHiRes = false; +void VideoMode::tick() { + if (this->pendText) { + if (--this->pendText <= 0) { + this->swText = !this->swText; + } + } + if (this->pendMixed) { + if (--this->pendMixed <= 0) { + this->swMixed = !this->swMixed; + } + } + if (this->pendPage2) { + if (--this->pendPage2 <= 0) { + this->swPage2 = !this->swPage2; + } + } + if (this->pendHiRes) { + if (--this->pendHiRes <= 0) { + this->swHiRes = !this->swHiRes; + } + } +} + +void VideoMode::powerOn() { + this->swText = false; + this->swMixed = false; + this->swPage2 = 0; + this->swHiRes = false; } diff --git a/src/videomode.h b/src/videomode.h index 572386e..e1897a0 100644 --- a/src/videomode.h +++ b/src/videomode.h @@ -21,24 +21,33 @@ #include "videoaddressing.h" #include "e2const.h" -class VideoMode -{ +class VideoMode { private: - bool swText; - bool swMixed; - int swPage2; - bool swHiRes; + bool swText; + int pendText; + + bool swMixed; + int pendMixed; + + bool swPage2; + int pendPage2; + + bool swHiRes; + int pendHiRes; + public: - VideoMode(); - unsigned char io(const unsigned short addr, const unsigned char b); - void powerOn(); + VideoMode(); + unsigned char io(const unsigned short addr, const unsigned char b); + void powerOn(); - bool isText() const { return this->swText; } - bool isHiRes() const { return this->swHiRes; } - bool isMixed() const { return this->swMixed; } - int getPage() const { return this->swPage2; } - bool isDisplayingText(const int atTickInField) const { return this->swText || (this->swMixed && atTickInField >= E2Const::MIXED_TEXT_CYCLE); } + void tick(); + + bool isText() const { return this->swText; } + bool isHiRes() const { return this->swHiRes; } + bool isMixed() const { return this->swMixed; } + int getPage() const { return this->swPage2 ? 1 : 0; } + bool isDisplayingText(const int atTickInField) const { return this->swText || (this->swMixed && atTickInField >= E2Const::MIXED_TEXT_CYCLE); } }; #endif