Update TODO list
[trut64.git] / gui / gui2023 / engine.cpp
blob5633e7e41d9664b1a0a3af303358c2dd96af1611
1 /*
2 * Copyright (C) 2023 Anton Blad
3 * Copyright (C) 2023 Fredrik Kuivinen
4 * Copyright (C) 2023 Jakob Rosén
6 * This file is licensed under GPL v2.
7 */
9 #include <iostream>
10 #include <fstream>
12 #include "engine.h"
13 #include "ziparchive.h"
15 extern "C" {
16 #include "commands.h"
17 #include "common.h"
18 #include "tap.h"
19 #include "trutusb.h"
20 #include "trutproto.h"
23 // -d option: Debug level.
24 static int opt_debug = 0;
26 enum TapTransferMode { TapIdle, TapSending, TapReceiving, TapCancellingSending };
28 // Specifies if a command was given on the command line. opt_singlecmd
29 // is 1 if a command was given on the command line.
30 static int opt_singlecmd = 0;
31 static enum CmdCode opt_cmd = CmdUnknown; //Jakob
32 static const char* opt_cmdarg = 0;
34 static enum TapTransferMode tap_mode = TapIdle;
35 static struct buffer* data_buffer = nullptr;
36 static bool crc_query_from_user;
38 static int quit = 0;
40 #include "bitcoach_routines.h"
42 static usbw_waitables* waitables;
44 void Engine::examine_msg(const char* data, int size)
46 int arglen = size - 1;
47 const char* args = data + 1;
49 switch(data[0])
51 case TRUT_MSG_MOTOR:
52 if(arglen != 1)
54 set_status("USB message error (motor), check console");
55 msg_error(data, size);
56 break;
58 switch(args[0])
60 case TRUT_MSG_MOTOR_OFF:
61 elapsed_timer.pause();
62 refresh_counter();
63 set_status("Motor is off");
64 break;
65 case TRUT_MSG_MOTOR_ON:
66 set_status("Motor is on");
67 elapsed_timer.start();
68 break;
69 default:
70 set_status("Motor in invalid state");
71 break;
73 break;
74 case TRUT_MSG_LOADFINISHED:
75 fprintf(stderr,"Load finished in %llu msec\n", elapsed_timer.getTime());
76 set_status("Load finished");
77 break;
78 default:
79 // For now, do nothing
80 break;
85 Engine::Engine() {
86 active_filename="";
89 void Engine::process() {
90 int ret;
91 // char cmd[256];
92 char data[256];
93 // char events[2];
95 if(trutusb_hasmsg())
97 ret = trutusb_getmsg((uint8_t*) data, sizeof(data));
98 if(ret > 0) {
99 print_msg(data, ret);
100 examine_msg(data, ret); // Added by Jakob for qload
102 else
103 printf("Error getting TRUT message\n");
106 if(trutusb_xferactive() && trutusb_xfercomplete())
108 trutusb_xferclean();
110 if(tap_mode == TapIdle)
112 printf("Cleaning transfer in idle mode (should not happen).\n");
114 else if(tap_mode == TapSending)
116 printf("Finished sending tap.\n");
117 free_buffer(data_buffer);
118 data_buffer = 0;
119 tap_mode = TapIdle;
120 quit=1; // Added by Jakob. Quit when finished!
122 else if(tap_mode == TapReceiving)
124 printf("Finished receiving tap.\n");
125 free_buffer(data_buffer);
126 data_buffer = 0;
127 tap_mode = TapIdle;
129 else
131 printf("Undefined mode while cleaning transfer.\n");
132 tap_mode = TapIdle;
134 printprompt();
137 if(opt_singlecmd > 0 && tap_mode == TapIdle)
139 switch(opt_cmd)
141 case CmdLoad:
142 start_tap_send(TRUT_MODE_LOAD, opt_cmdarg);
143 break;
144 case CmdDump:
145 start_tap_receive(TRUT_MODE_DUMP, opt_cmdarg);
146 break;
147 case CmdWrite:
148 start_tap_send(TRUT_MODE_WRITE, opt_cmdarg);
149 break;
150 case CmdSave:
151 start_tap_send(TRUT_MODE_SAVE, opt_cmdarg);
152 break;
153 case CmdQuit:
154 default:
155 quit = 1;
158 if(tap_mode == TapIdle) // an error occurred
159 quit = 1;
161 if(opt_singlecmd == 2)
162 opt_singlecmd = 0;
163 else
164 opt_cmd = CmdQuit;
167 if(tap_mode == TapCancellingSending)
169 printf("Tap send cancelled.\n");
170 set_status("Tape stopped");
171 // free_buffer(data_buffer);
172 // data_buffer = 0;
173 tap_mode = TapIdle;
176 waitables = trutusb_getwaitables();
177 if(waitables == 0)
179 printf("Error getting list of USB waitables\n");
180 // break;
184 // ret = wait_for_stuff(cmd, sizeof(cmd), waitables, events);
186 // waitables is now global
187 // The other ones are not needed since we are not using stdin anymore
189 // Before waiting for stuff, let's see if it's time to quit
190 if (quit!=0) emit application_quit_signal();
192 // Can you feel it?
193 wait_for_stuff();
196 trutusb_handleevents();
197 trutusb_freewaitables(waitables);
198 ...are handled by the wakeup(int) slot
200 fflush(stdout);
203 void Engine::set_gui(Ui::MainWindow* my_ui) {
204 ui=my_ui;
205 if (GUI_GHOST_BUTTONS) {
206 gui_enable_load_panel();
207 gui_disable_play_panel();
211 void Engine::wait_for_stuff() {
213 if(opt_debug >= 2)
214 fprintf(stderr, "Sleeping with %d readfd and %d writefd...\n", waitables->num_readobj,
215 waitables->num_writeobj);
217 // We do this the Bitcoach/select way, so all socket notifiers are cleared each time an event occurs.
218 // This is not nice when using QSocketNotifiers. Instead, we should keep track of them and only add
219 // new / delete expired notifiers. I suppose :)
221 // Substitute for FD_ZERO(&readfd);
222 for (int i=0; i<notify_read.size(); i++) {
223 notify_read[i]->setEnabled(false); // Should be redundant
224 delete notify_read[i];
226 notify_read.clear();
228 // No substitute for FD_SET(0, &readfd);
229 // Since we want to monitor stdin
231 // Substitute for FD_ZERO(&writefd);
232 for (int i=0; i<notify_write.size(); i++) {
233 notify_write[i]->setEnabled(false); // Should be redundant
234 delete notify_write[i];
236 notify_write.clear();
238 struct timeval timeout;
239 int fd;
240 int i;
242 for(i = 0; i < waitables->num_readobj; i++)
244 fd = waitables->readobj[i];
245 QSocketNotifier* qsn=new QSocketNotifier(fd,QSocketNotifier::Read);
246 QObject::connect(qsn, SIGNAL(activated(int)), this, SLOT(usb_event(int)));
247 qsn->setEnabled(true);
248 notify_read.push_back(qsn);
251 for(i = 0; i < waitables->num_writeobj; i++)
253 fd = waitables->writeobj[i];
254 QSocketNotifier* qsn=new QSocketNotifier(fd,QSocketNotifier::Write);
255 QObject::connect(qsn, SIGNAL(activated(int)), this, SLOT(usb_event(int)));
256 qsn->setEnabled(true);
257 notify_write.push_back(qsn);
260 if(waitables->timeout_pending == 1)
262 if(opt_debug >= 2)
263 fprintf(stderr, "timeout %d sec %d us\n", (int)waitables->timeout.tv_sec,
264 (int)waitables->timeout.tv_usec);
265 // ret = select(max_fd + 1, &readfd, &writefd, NULL, &timeout);
266 timeout = waitables->timeout;
267 timeout_timer.start((int)timeout.tv_sec*1000+ceil((double)timeout.tv_usec/1000.0));
271 int Engine::init() {
272 trutusb_init();
273 usbw_debug(opt_debug);
274 trutusb_scan();
276 if(trutusb_isattached())
278 printf("TRUT connected! Get ready to rambo!\n");
279 set_status("TRUT connected! Get ready to rambo!");
281 else
283 printf("Found no TRUT :(. You must always connect TRUT! :(\n");
284 set_status("Found no TRUT :(. You must always connect TRUT! :(");
285 return 0;
288 if(trutusb_claim())
290 printf("Can not claim TRUT :(\n");
291 set_status("Can not claim TRUT :(");
292 return 0;
295 if(trutusb_setmsg(true))
297 printf("Failed to enable messages\n");
298 set_status("Failed to enable messages");
301 init_timeout_timer();
302 start();
304 return 0;
307 void Engine::init_timeout_timer() {
308 timeout_timer.setSingleShot(true); // We don't want a periodic timer
309 connect(&timeout_timer, SIGNAL(timeout()), this, SLOT(timeout_event()));
312 // Called after an usb_event(int) or a timeout_event()
313 // This is the code that succeeds wait_for_stuff()
314 void Engine::wakeup() {
315 timeout_timer.stop(); // If an event was catched before a timeout, disable the timer
316 trutusb_freewaitables(waitables);
317 process();
320 // Slot called by QSocketNotifier::activate(int)
321 void Engine::usb_event(int fid) {
322 if (DEBUG>0) std::cout << "Processing USB event from descriptor " << fid << std::endl;
323 trutusb_handleevents();
324 wakeup();
327 // Slot called by QTimer::timeout()
328 void Engine::timeout_event() {
329 std::cout << "Timeout timeout occured!" << std::endl;
330 assert(!timeout_timer.isActive());
331 wakeup();
334 void Engine::start() {
335 process();
338 void Engine::shut_down() {
339 if(trutusb_setmsg(false))
340 printf("Failed to disable messages\n");
342 if(trutusb_isattached())
343 trutusb_release();
345 fprintf(stderr, "Good bye\n");
348 void Engine::print_message(std::string str) {
349 printf("%s\n", str.c_str());
352 QString Engine::get_file_name(QString str) {
353 str=str.replace("\\", "/");
354 int index=str.lastIndexOf("/");
355 return str.last(str.length()-index-1);
358 void Engine::load(QString filename) {
359 elapsed_timer.init();
360 refresh_counter();
362 bool is_zip=false;
363 if (filename.endsWith(".zip")) {
364 printf("Engine: Zip file detected\n");
365 is_zip=true;
366 } else if (filename.endsWith(".tap")) {
367 printf("Engine: Tap file detected\n");
368 } else {
369 fprintf(stderr, "Engine: Error! No valid file was selected\n");
372 active_filename=filename;
373 //set_status("Loaded "+get_file_name(active_filename));
375 ui->fileBox->blockSignals(true);
376 ui->fileBox->clear();
378 if (is_zip) {
379 ZipArchive zip(filename.toStdString());
380 if (!zip.isZipFile()) {
381 fprintf(stderr,"ERROR! Not a zip file\n");
384 std::vector<std::string> strvec=zip.getFilenames(".tap");
386 for (int i=0; i<(int)strvec.size(); i++) {
387 ui->fileBox->addItem(QString::fromStdString(strvec.at(i)));
390 } else {
391 ui->fileBox->addItem(get_file_name(filename));
394 if (ui->fileBox->count()<=1) {
395 ui->fileBox->setEnabled(false);
396 } else {
397 ui->fileBox->setEnabled(true);
399 ui->fileBox->blockSignals(false);
401 gui_disable_play_panel();
403 // Ok, the combobox is setup at this point
405 if (data_buffer != nullptr) free_buffer(data_buffer);
407 if (is_zip && ui->fileBox->count()==0) {
408 printf("No valid files in Zip archive\n");
409 return;
412 struct tape_data* tap;
414 // If it's a zip file, the item is loaded via on_fileBox_currentIndexChanged()
415 // that is triggered automatically
416 if (!is_zip) {
417 // Load tap file into buffer
418 tap=load_tap_file(filename.toStdString().c_str());
419 data_buffer = jakob_internal(tap);
420 free_tap(tap);
421 set_status("Loaded TAP file: "+ui->fileBox->currentText());
422 } else {
423 // Load the first tap file in the zip archive
424 change_zip_item();
426 set_status("PRESS PLAY ON TAPE");
427 gui_enable_play_panel();
430 void Engine::change_zip_item() {
431 assert(!active_filename.isEmpty());
432 if (active_filename.endsWith(".zip")) {
433 struct tape_data* tap;
434 QString selected_item=ui->fileBox->currentText();
435 tap=load_tap_in_zip(active_filename, selected_item);
436 data_buffer = jakob_internal(tap);
437 free_tap(tap);
438 set_status("Loaded from ZIP archive: "+ui->fileBox->currentText());
439 elapsed_timer.init();
440 refresh_counter();
444 struct tape_data* Engine::load_tap_in_zip(QString filename, QString itemname) {
445 //struct buffer* buf;
446 //buf = (struct buffer*)xmalloc(sizeof(struct buffer));
447 struct tape_data* tap;
448 struct ZipArchive::zip_buffer zipbuf;
450 ZipArchive zip(filename.toStdString());
451 zip.extractItem(itemname.toStdString(), &zipbuf);
452 struct buffer buf;
453 buf.data=zipbuf.data;
454 buf.length=(int)zipbuf.length;
456 tap=decode_tap(&buf);
457 free(zipbuf.data);
458 return tap;
461 void Engine::play() {
463 if(tap_mode != TapIdle)
465 fprintf(stderr,"TRUT64 is not in idle mode.\n");
466 set_status("TRUT64 is working. Do not disturb.");
467 return;
470 assert(data_buffer != nullptr); // Should not happen
472 fprintf(stderr,"Buffer time: %llu\n", calculate_buffer_time(data_buffer));
474 start_refresh_timer();
476 //uint64_t time_position_msec=elapsed_timer.getTime();
477 refresh_counter();
478 uint64_t time_position_msec=(uint64_t)ui->lcdNumber->intValue()*1000;
480 int buffer_index=calculate_buffer_index(data_buffer, time_position_msec*1000);
481 fprintf(stderr, "buffer_index: %d\n", buffer_index);
482 if(trutusb_xferinit(TRUT_MODE_LOAD, data_buffer->data+buffer_index, data_buffer->length-buffer_index) == 0)
484 printf("Initialized data transfer of %d bytes\n", data_buffer->length);
485 tap_mode = TapSending;
486 gui_disable_load_panel();
488 else
490 printf("Failed to initialize data transfer\n");
491 free_buffer(data_buffer);
492 gui_disable_play_panel();
496 void Engine::gui_enable_load_panel() {
497 ui->initButton->setEnabled(true);
498 ui->loadButton->setEnabled(true);
499 ui->setButton->setEnabled(true);
500 ui->remButton->setEnabled(true);
501 ui->saveButton->setEnabled(true);
502 ui->dumpButton->setEnabled(true);
503 ui->fileBox->setEnabled(true);
506 void Engine::gui_disable_load_panel() {
507 ui->initButton->setEnabled(false);
508 ui->loadButton->setEnabled(false);
509 ui->setButton->setEnabled(false);
510 ui->remButton->setEnabled(false);
511 ui->saveButton->setEnabled(false);
512 ui->dumpButton->setEnabled(false);
513 ui->fileBox->setEnabled(false);
516 void Engine::gui_enable_play_panel() {
517 ui->playButton->setEnabled(true);
518 ui->stopButton->setEnabled(true);
521 void Engine::gui_disable_play_panel() {
522 ui->playButton->setEnabled(false);
523 ui->stopButton->setEnabled(false);
527 void Engine::start_refresh_timer() {
528 refresh_timer.setSingleShot(false);
529 connect(&refresh_timer, SIGNAL(timeout()), this, SLOT(refresh_event()));
530 refresh_timer.setInterval(500);
531 refresh_timer.start();
534 void Engine::refresh_event() {
535 ui->lcdNumber->display((int)elapsed_timer.getTime()/1000);
538 int Engine::calculate_buffer_index(struct buffer* buf, uint64_t time_position_usec) {
539 struct buffer_position result;
540 calculate_internal_buffer_position(&result, buf, time_position_usec);
541 return result.index;
544 uint64_t Engine::calculate_buffer_time(struct buffer* buf) {
545 struct buffer_position result;
546 uint64_t time_position=std::numeric_limits<uint64_t>::max();
547 calculate_internal_buffer_position(&result, buf, time_position);
548 return result.elapsed_time;
551 bool Engine::calculate_internal_buffer_position(struct buffer_position* result, struct buffer* buf, uint64_t time_position_usec) {
552 uint64_t elapsed_time=0;
553 int current_pulse=0;
554 int i=0;
555 int bufpos=0;
556 assert((1.0*AVR_CLK_MHZ/AVR_PRESCALING)==1.0);
557 time_position_usec=time_position_usec*(1.0*AVR_CLK_MHZ/AVR_PRESCALING);
559 do {
560 elapsed_time+=current_pulse;
561 bufpos=i;
562 current_pulse=buf->data[i++] << 8;
563 current_pulse+=buf->data[i++];
564 if (current_pulse==0) {
565 int overflows=0;
566 do {
567 overflows++;
568 current_pulse=buf->data[i++] << 8;
569 current_pulse+=buf->data[i++];
570 } while (current_pulse==0);
571 current_pulse+=overflows*0xffff;
573 current_pulse <<= 1;
574 } while (elapsed_time+current_pulse<time_position_usec && i<buf->length);
575 if (i==buf->length) {
576 result->index=buf->length;
577 result->elapsed_time=elapsed_time+current_pulse;
578 return false; // We did not find time_position (but measured the total buffer time)
580 result->index=bufpos;
581 result->elapsed_time=elapsed_time;
582 return true;
585 // Used for debugging purposes. Does not take extension of short pulses
586 // (that is performed when converting to jakob_internal) into account.
587 uint64_t Engine::calculate_tape_data_time(struct tape_data* buf) {
588 uint64_t elapsed_time=0;
589 for (int i=0; i<buf->length; i++) {
590 elapsed_time+=floor(0.5+(1.0*buf->data[i]/C64_CLK_MHZ));
592 return elapsed_time;
595 void Engine::stop() {
597 if(tap_mode == TapIdle)
599 printf("No data transfer in progress.\n");
601 else if(tap_mode == TapSending)
603 if(trutusb_xferactive())
605 trutusb_xferclean();
606 printf("Cancelled sending tap, wait until completion..\n");
607 tap_mode = TapCancellingSending;
609 else
611 printf("Already finished?!\n");
614 else
616 printf("Later...\n");
618 elapsed_timer.pause();
619 refresh_counter();
620 gui_enable_load_panel();
623 void Engine::set_status(QString str) {
624 ui->plainTextEdit->appendPlainText(str);
627 void Engine::set_counter(int counter_sec) {
628 elapsed_timer.init();
629 elapsed_timer.setOffsetSeconds(counter_sec);
630 refresh_counter();
633 void Engine::refresh_counter() {
634 refresh_event();