acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

SSTV.cpp (9493B)

      1 #include "SSTV.h"
      2 #if !defined(RADIOLIB_EXCLUDE_SSTV)
      3 
      4 const SSTVMode_t Scottie1 {
      5   .visCode = RADIOLIB_SSTV_SCOTTIE_1,
      6   .width = 320,
      7   .height = 256,
      8   .scanPixelLen = 432,
      9   .numTones = 7,
     10   .tones = {
     11     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     12     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
     13     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     14     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    },
     15     { .type = tone_t::GENERIC,    .len = 9000, .freq = 1200 },
     16     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     17     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    }
     18   }
     19 };
     20 
     21 const SSTVMode_t Scottie2 {
     22   .visCode = RADIOLIB_SSTV_SCOTTIE_2,
     23   .width = 320,
     24   .height = 256,
     25   .scanPixelLen = 275,
     26   .numTones = 7,
     27   .tones = {
     28     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     29     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
     30     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     31     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    },
     32     { .type = tone_t::GENERIC,    .len = 9000, .freq = 1200 },
     33     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     34     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    }
     35   }
     36 };
     37 
     38 const SSTVMode_t ScottieDX {
     39   .visCode = RADIOLIB_SSTV_SCOTTIE_DX,
     40   .width = 320,
     41   .height = 256,
     42   .scanPixelLen = 1080,
     43   .numTones = 7,
     44   .tones = {
     45     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     46     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
     47     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     48     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    },
     49     { .type = tone_t::GENERIC,    .len = 9000, .freq = 1200 },
     50     { .type = tone_t::GENERIC,    .len = 1500, .freq = 1500 },
     51     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    }
     52   }
     53 };
     54 
     55 const SSTVMode_t Martin1 {
     56   .visCode = RADIOLIB_SSTV_MARTIN_1,
     57   .width = 320,
     58   .height = 256,
     59   .scanPixelLen = 458,
     60   .numTones = 8,
     61   .tones = {
     62     { .type = tone_t::GENERIC,    .len = 4862, .freq = 1200 },
     63     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 },
     64     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
     65     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 },
     66     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    },
     67     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 },
     68     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    },
     69     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 }
     70   }
     71 };
     72 
     73 const SSTVMode_t Martin2 {
     74   .visCode = RADIOLIB_SSTV_MARTIN_2,
     75   .width = 320,
     76   .height = 256,
     77   .scanPixelLen = 229,
     78   .numTones = 8,
     79   .tones = {
     80     { .type = tone_t::GENERIC,    .len = 4862, .freq = 1200 },
     81     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 },
     82     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
     83     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 },
     84     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    },
     85     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 },
     86     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    },
     87     { .type = tone_t::GENERIC,    .len = 572,  .freq = 1500 }
     88   }
     89 };
     90 
     91 const SSTVMode_t Wrasse {
     92   .visCode = RADIOLIB_SSTV_WRASSE_SC2_180,
     93   .width = 320,
     94   .height = 256,
     95   .scanPixelLen = 734,
     96   .numTones = 5,
     97   .tones = {
     98     { .type = tone_t::GENERIC,    .len = 5523, .freq = 1200 },
     99     { .type = tone_t::GENERIC,    .len = 500,  .freq = 1500 },
    100     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    },
    101     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
    102     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    }
    103   }
    104 };
    105 
    106 const SSTVMode_t PasokonP3 {
    107   .visCode = RADIOLIB_SSTV_PASOKON_P3,
    108   .width = 640,
    109   .height = 496,
    110   .scanPixelLen = 208,
    111   .numTones = 7,
    112   .tones = {
    113     { .type = tone_t::GENERIC,    .len = 5208, .freq = 1200 },
    114     { .type = tone_t::GENERIC,    .len = 1042, .freq = 1500 },
    115     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    },
    116     { .type = tone_t::GENERIC,    .len = 1042, .freq = 1500 },
    117     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
    118     { .type = tone_t::GENERIC,    .len = 1042, .freq = 1500 },
    119     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    }
    120   }
    121 };
    122 
    123 const SSTVMode_t PasokonP5 {
    124   .visCode = RADIOLIB_SSTV_PASOKON_P5,
    125   .width = 640,
    126   .height = 496,
    127   .scanPixelLen = 312,
    128   .numTones = 7,
    129   .tones = {
    130     { .type = tone_t::GENERIC,    .len = 7813, .freq = 1200 },
    131     { .type = tone_t::GENERIC,    .len = 1563, .freq = 1500 },
    132     { .type = tone_t::SCAN_RED,   .len = 0,    .freq = 0    },
    133     { .type = tone_t::GENERIC,    .len = 1563, .freq = 1500 },
    134     { .type = tone_t::SCAN_GREEN, .len = 0,    .freq = 0    },
    135     { .type = tone_t::GENERIC,    .len = 1563, .freq = 1500 },
    136     { .type = tone_t::SCAN_BLUE,  .len = 0,    .freq = 0    }
    137   }
    138 };
    139 
    140 const SSTVMode_t PasokonP7 {
    141   .visCode = RADIOLIB_SSTV_PASOKON_P7,
    142   .width = 640,
    143   .height = 496,
    144   .scanPixelLen = 417,
    145   .numTones = 7,
    146   .tones = {
    147     { .type = tone_t::GENERIC,    .len = 10417, .freq = 1200 },
    148     { .type = tone_t::GENERIC,    .len = 2083,  .freq = 1500 },
    149     { .type = tone_t::SCAN_RED,   .len = 0,     .freq = 0    },
    150     { .type = tone_t::GENERIC,    .len = 2083,  .freq = 1500 },
    151     { .type = tone_t::SCAN_GREEN, .len = 0,     .freq = 0    },
    152     { .type = tone_t::GENERIC,    .len = 2083,  .freq = 1500 },
    153     { .type = tone_t::SCAN_BLUE,  .len = 0,     .freq = 0    }
    154   }
    155 };
    156 
    157 SSTVClient::SSTVClient(PhysicalLayer* phy) {
    158   _phy = phy;
    159   #if !defined(RADIOLIB_EXCLUDE_AFSK)
    160   _audio = nullptr;
    161   #endif
    162 }
    163 
    164 #if !defined(RADIOLIB_EXCLUDE_AFSK)
    165 SSTVClient::SSTVClient(AFSKClient* audio) {
    166   _phy = audio->_phy;
    167   _audio = audio;
    168 }
    169 #endif
    170 
    171 #if !defined(RADIOLIB_EXCLUDE_AFSK)
    172 int16_t SSTVClient::begin(const SSTVMode_t& mode, float correction) {
    173   if(_audio == nullptr) {
    174     // this initialization method can only be used in AFSK mode
    175     return(RADIOLIB_ERR_WRONG_MODEM);
    176   }
    177 
    178   return(begin(0, mode, correction));
    179 }
    180 #endif
    181 
    182 int16_t SSTVClient::begin(float base, const SSTVMode_t& mode, float correction) {
    183   // save mode
    184   _mode = mode;
    185 
    186   // apply correction factor to all timings
    187   _mode.scanPixelLen *= correction;
    188   for(uint8_t i = 0; i < _mode.numTones; i++) {
    189     _mode.tones[i].len *= correction;
    190   }
    191 
    192   // calculate 24-bit frequency
    193   _base = (base * 1000000.0) / _phy->getFreqStep();
    194 
    195   // configure for direct mode
    196   return(_phy->startDirect());
    197 }
    198 
    199 void SSTVClient::idle() {
    200   _phy->transmitDirect();
    201   this->tone(RADIOLIB_SSTV_TONE_LEADER);
    202 }
    203 
    204 void SSTVClient::sendHeader() {
    205   // save first header flag for Scottie modes
    206   _firstLine = true;
    207   _phy->transmitDirect();
    208 
    209   // send the first part of header (leader-break-leader)
    210   this->tone(RADIOLIB_SSTV_TONE_LEADER, RADIOLIB_SSTV_HEADER_LEADER_LENGTH);
    211   this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BREAK_LENGTH);
    212   this->tone(RADIOLIB_SSTV_TONE_LEADER, RADIOLIB_SSTV_HEADER_LEADER_LENGTH);
    213 
    214   // VIS start bit
    215   this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
    216 
    217   // VIS code
    218   uint8_t parityCount = 0;
    219   for(uint8_t mask = 0x01; mask < 0x80; mask <<= 1) {
    220     if(_mode.visCode & mask) {
    221       this->tone(RADIOLIB_SSTV_TONE_VIS_1, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
    222       parityCount++;
    223     } else {
    224       this->tone(RADIOLIB_SSTV_TONE_VIS_0, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
    225     }
    226   }
    227 
    228   // VIS parity
    229   if(parityCount % 2 == 0) {
    230     // even parity
    231     this->tone(RADIOLIB_SSTV_TONE_VIS_0, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
    232   } else {
    233     // odd parity
    234     this->tone(RADIOLIB_SSTV_TONE_VIS_1, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
    235   }
    236 
    237   // VIS stop bit
    238   this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
    239 }
    240 
    241 void SSTVClient::sendLine(uint32_t* imgLine) {
    242   // check first line flag in Scottie modes
    243   if(_firstLine && ((_mode.visCode == RADIOLIB_SSTV_SCOTTIE_1) || (_mode.visCode == RADIOLIB_SSTV_SCOTTIE_2) || (_mode.visCode == RADIOLIB_SSTV_SCOTTIE_DX))) {
    244     _firstLine = false;
    245 
    246     // send start sync tone
    247     this->tone(RADIOLIB_SSTV_TONE_BREAK, 9000);
    248   }
    249 
    250   // send all tones in sequence
    251   for(uint8_t i = 0; i < _mode.numTones; i++) {
    252     if((_mode.tones[i].type == tone_t::GENERIC) && (_mode.tones[i].len > 0)) {
    253       // sync/porch tones
    254       this->tone(_mode.tones[i].freq, _mode.tones[i].len);
    255     } else {
    256       // scan lines
    257       for(uint16_t j = 0; j < _mode.width; j++) {
    258         uint32_t color = imgLine[j];
    259         switch(_mode.tones[i].type) {
    260           case(tone_t::SCAN_RED):
    261             color &= 0x00FF0000;
    262             color >>= 16;
    263             break;
    264           case(tone_t::SCAN_GREEN):
    265             color &= 0x0000FF00;
    266             color >>= 8;
    267             break;
    268           case(tone_t::SCAN_BLUE):
    269             color &= 0x000000FF;
    270             break;
    271           case(tone_t::GENERIC):
    272             break;
    273         }
    274         this->tone(RADIOLIB_SSTV_TONE_BRIGHTNESS_MIN + ((float)color * 3.1372549), _mode.scanPixelLen);
    275       }
    276     }
    277   }
    278 }
    279 
    280 uint16_t SSTVClient::getPictureHeight() const {
    281   return(_mode.height);
    282 }
    283 
    284 void SSTVClient::tone(float freq, uint32_t len) {
    285   Module* mod = _phy->getMod();
    286   uint32_t start = mod->micros();
    287   #if !defined(RADIOLIB_EXCLUDE_AFSK)
    288   if(_audio != nullptr) {
    289     _audio->tone(freq, false);
    290   } else {
    291     _phy->transmitDirect(_base + (freq / _phy->getFreqStep()));
    292   }
    293   #else
    294   _phy->transmitDirect(_base + (freq / _phy->getFreqStep()));
    295   #endif
    296   while(mod->micros() - start < len) {
    297     mod->yield();
    298   }
    299 }
    300 
    301 #endif