mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			2020-07-20
			...
			2020-07-27
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b1e062945e | ||
|  | 3db4a8c312 | ||
|  | db8e1b0edf | ||
|  | 71c3f58c99 | ||
|  | 7c05b1788e | ||
|  | a2db6ddea5 | ||
|  | 6ad1e3e17e | ||
|  | e097a841d2 | ||
|  | fa95a17af5 | ||
|  | b961665985 | ||
|  | 8af35bc6bb | ||
|  | 9b75287a52 | ||
|  | 84d5316aa7 | 
| @@ -63,6 +63,13 @@ class VSyncPredictor { | |||||||
| 			frame_duration_ = Nanos(1'000'000'000.0f / rate); | 			frame_duration_ = Nanos(1'000'000'000.0f / rate); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns The time this class currently believes a whole frame occupies. | ||||||
|  | 		*/ | ||||||
|  | 		Time::Nanos frame_duration() { | ||||||
|  | 			return frame_duration_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Adds a record of how much jitter was experienced in scheduling; these values will be | 			Adds a record of how much jitter was experienced in scheduling; these values will be | ||||||
| 			factored into the @c suggested_draw_time if supplied. | 			factored into the @c suggested_draw_time if supplied. | ||||||
| @@ -87,15 +94,13 @@ class VSyncPredictor { | |||||||
| 			(if those figures are being supplied). | 			(if those figures are being supplied). | ||||||
| 		*/ | 		*/ | ||||||
| 		Nanos suggested_draw_time() { | 		Nanos suggested_draw_time() { | ||||||
| 			const auto mean = redraw_period_.mean() - timer_jitter_.mean() - vsync_jitter_.mean(); | 			const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean(); | ||||||
| 			const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance(); | 			const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance(); | ||||||
|  |  | ||||||
| 			// Permit three standard deviations from the mean, to cover 99.9% of cases. | 			// Permit three standard deviations from the mean, to cover 99.9% of cases. | ||||||
| 			const auto period = mean - Nanos(3.0f * sqrt(float(variance))); | 			const auto period = mean + Nanos(3.0f * sqrt(float(variance))); | ||||||
|  |  | ||||||
| 			assert(abs(period) < 10'000'000'000); | 			return last_vsync_ + frame_duration_ - period; | ||||||
|  |  | ||||||
| 			return last_vsync_ + period; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| @@ -109,7 +114,6 @@ class VSyncPredictor { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				void post(Time::Nanos value) { | 				void post(Time::Nanos value) { | ||||||
| 					assert(abs(value) < 10'000'000'000);	// 10 seconds is a very liberal maximum. |  | ||||||
| 					sum_ -= history_[write_pointer_]; | 					sum_ -= history_[write_pointer_]; | ||||||
| 					sum_ += value; | 					sum_ += value; | ||||||
| 					history_[write_pointer_] = value; | 					history_[write_pointer_] = value; | ||||||
|   | |||||||
| @@ -316,6 +316,8 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { | |||||||
| 				} | 				} | ||||||
| 				LOG("Receive bit count: " << receive_bit_count); | 				LOG("Receive bit count: " << receive_bit_count); | ||||||
|  |  | ||||||
|  | 				(void)receive_bit_count; | ||||||
|  |  | ||||||
| 				/* | 				/* | ||||||
| 					b7,b6: | 					b7,b6: | ||||||
| 						00 = 5 receive bits per character | 						00 = 5 receive bits per character | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| QT += core gui multimedia widgets | QT += core gui multimedia widgets | ||||||
|  |  | ||||||
|  | # Be specific about C++17 but also try the vaguer C++1z for older | ||||||
|  | # versions of Qt. | ||||||
| CONFIG += c++17 | CONFIG += c++17 | ||||||
|  | CONFIG += c++1z | ||||||
|  |  | ||||||
| # Permit multiple source files in different directories to have the same file name. | # Permit multiple source files in different directories to have the same file name. | ||||||
| CONFIG += object_parallel_to_source | CONFIG += object_parallel_to_source | ||||||
|   | |||||||
| @@ -45,7 +45,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { | |||||||
|  |  | ||||||
| MainWindow::MainWindow(const QString &fileName) { | MainWindow::MainWindow(const QString &fileName) { | ||||||
| 	init(); | 	init(); | ||||||
| 	launchFile(fileName); | 	if(!launchFile(fileName)) { | ||||||
|  | 		setUIPhase(UIPhase::SelectingMachine); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::deleteMachine() { | void MainWindow::deleteMachine() { | ||||||
| @@ -210,11 +212,17 @@ void MainWindow::insertFile(const QString &fileName) { | |||||||
| 	mediaTarget->insert_media(media); | 	mediaTarget->insert_media(media); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::launchFile(const QString &fileName) { | bool MainWindow::launchFile(const QString &fileName) { | ||||||
| 	targets = Analyser::Static::GetTargets(fileName.toStdString()); | 	targets = Analyser::Static::GetTargets(fileName.toStdString()); | ||||||
| 	if(!targets.empty()) { | 	if(!targets.empty()) { | ||||||
| 		openFileName = QFileInfo(fileName).fileName(); | 		openFileName = QFileInfo(fileName).fileName(); | ||||||
| 		launchMachine(); | 		launchMachine(); | ||||||
|  | 		return true; | ||||||
|  | 	} else { | ||||||
|  | 		QMessageBox msgBox; | ||||||
|  | 		msgBox.setText("Unable to open file: " + fileName); | ||||||
|  | 		msgBox.exec(); | ||||||
|  | 		return false; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -707,6 +715,7 @@ void MainWindow::dropEvent(QDropEvent* event) { | |||||||
| 			bool foundROM = false; | 			bool foundROM = false; | ||||||
| 			const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString(); | 			const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString(); | ||||||
|  |  | ||||||
|  | 			QString unusedRoms; | ||||||
| 			for(const auto &url: event->mimeData()->urls()) { | 			for(const auto &url: event->mimeData()->urls()) { | ||||||
| 				const char *const name = url.toLocalFile().toUtf8(); | 				const char *const name = url.toLocalFile().toUtf8(); | ||||||
| 				FILE *const file = fopen(name, "rb"); | 				FILE *const file = fopen(name, "rb"); | ||||||
| @@ -716,6 +725,7 @@ void MainWindow::dropEvent(QDropEvent* event) { | |||||||
| 				CRC::CRC32 generator; | 				CRC::CRC32 generator; | ||||||
| 				const uint32_t crc = generator.compute_crc(*contents); | 				const uint32_t crc = generator.compute_crc(*contents); | ||||||
|  |  | ||||||
|  | 				bool wasUsed = false; | ||||||
| 				for(const auto &rom: missingRoms) { | 				for(const auto &rom: missingRoms) { | ||||||
| 					if(std::find(rom.crc32s.begin(), rom.crc32s.end(), crc) != rom.crc32s.end()) { | 					if(std::find(rom.crc32s.begin(), rom.crc32s.end(), crc) != rom.crc32s.end()) { | ||||||
| 						foundROM = true; | 						foundROM = true; | ||||||
| @@ -731,10 +741,22 @@ void MainWindow::dropEvent(QDropEvent* event) { | |||||||
| 						FILE *const target = fopen(destination.c_str(), "wb"); | 						FILE *const target = fopen(destination.c_str(), "wb"); | ||||||
| 						fwrite(contents->data(), 1, contents->size(), target); | 						fwrite(contents->data(), 1, contents->size(), target); | ||||||
| 						fclose(target); | 						fclose(target); | ||||||
|  |  | ||||||
|  | 						wasUsed = true; | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				if(!wasUsed) { | ||||||
|  | 					if(!unusedRoms.isEmpty()) unusedRoms += ", "; | ||||||
|  | 					unusedRoms += url.fileName(); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if(!unusedRoms.isEmpty()) { | ||||||
|  | 				QMessageBox msgBox; | ||||||
|  | 				msgBox.setText("Couldn't identify ROMs: " + unusedRoms); | ||||||
|  | 				msgBox.exec(); | ||||||
|  | 			} | ||||||
| 			if(foundROM) launchMachine(); | 			if(foundROM) launchMachine(); | ||||||
| 		} break; | 		} break; | ||||||
| 	} | 	} | ||||||
| @@ -1338,24 +1360,25 @@ void MainWindow::addActivityObserver() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::register_led(const std::string &name) { | void MainWindow::register_led(const std::string &name) { | ||||||
|  | 	std::lock_guard guard(ledStatusesLock); | ||||||
| 	ledStatuses[name] = false; | 	ledStatuses[name] = false; | ||||||
| 	updateStatusBarText(); | 	QMetaObject::invokeMethod(this, "updateStatusBarText"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::set_led_status(const std::string &name, bool isLit) { | void MainWindow::set_led_status(const std::string &name, bool isLit) { | ||||||
|  | 	std::lock_guard guard(ledStatusesLock); | ||||||
| 	ledStatuses[name] = isLit; | 	ledStatuses[name] = isLit; | ||||||
| 	updateStatusBarText();	// Assumption here: Qt's attempt at automatic thread confinement will work here. | 	QMetaObject::invokeMethod(this, "updateStatusBarText"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::updateStatusBarText() { | void MainWindow::updateStatusBarText() { | ||||||
| 	QString fullText; | 	QString fullText; | ||||||
| 	bool isFirst = true; | 	std::lock_guard guard(ledStatusesLock); | ||||||
| 	for(const auto &pair: ledStatuses) { | 	for(const auto &pair: ledStatuses) { | ||||||
| 		if(!isFirst) fullText += " | "; | 		if(!fullText.isEmpty()) fullText += " | "; | ||||||
| 		fullText += QString::fromStdString(pair.first); | 		fullText += QString::fromStdString(pair.first); | ||||||
| 		fullText += " "; | 		fullText += " "; | ||||||
| 		fullText += pair.second ? "■" : "□"; | 		fullText += pair.second ? "■" : "□"; | ||||||
| 		isFirst = false; |  | ||||||
| 	} | 	} | ||||||
| 	statusBar()->showMessage(fullText); | 	statusBar()->showMessage(fullText); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| #include <QMainWindow> | #include <QMainWindow> | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <mutex> | ||||||
| #include <optional> | #include <optional> | ||||||
|  |  | ||||||
| #include "audiobuffer.h" | #include "audiobuffer.h" | ||||||
| @@ -80,6 +81,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat | |||||||
|  |  | ||||||
| 	private slots: | 	private slots: | ||||||
| 		void startMachine(); | 		void startMachine(); | ||||||
|  | 		void updateStatusBarText(); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void start_appleII(); | 		void start_appleII(); | ||||||
| @@ -100,7 +102,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat | |||||||
| 		QAction *insertAction = nullptr; | 		QAction *insertAction = nullptr; | ||||||
| 		void insertFile(const QString &fileName); | 		void insertFile(const QString &fileName); | ||||||
|  |  | ||||||
| 		void launchFile(const QString &fileName); | 		bool launchFile(const QString &fileName); | ||||||
| 		void launchTarget(std::unique_ptr<Analyser::Static::Target> &&); | 		void launchTarget(std::unique_ptr<Analyser::Static::Target> &&); | ||||||
|  |  | ||||||
| 		void restoreSelections(); | 		void restoreSelections(); | ||||||
| @@ -144,9 +146,11 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat | |||||||
|  |  | ||||||
| 		void register_led(const std::string &) override; | 		void register_led(const std::string &) override; | ||||||
| 		void set_led_status(const std::string &, bool) override; | 		void set_led_status(const std::string &, bool) override; | ||||||
|  |  | ||||||
|  | 		std::recursive_mutex ledStatusesLock; | ||||||
| 		std::map<std::string, bool> ledStatuses; | 		std::map<std::string, bool> ledStatuses; | ||||||
|  |  | ||||||
| 		void addActivityObserver(); | 		void addActivityObserver(); | ||||||
| 		void updateStatusBarText(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // MAINWINDOW_H | #endif // MAINWINDOW_H | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ void ScanTargetWidget::vsync() { | |||||||
| 	const auto time_now = Time::nanos_now(); | 	const auto time_now = Time::nanos_now(); | ||||||
| 	requestedRedrawTime = vsyncPredictor.suggested_draw_time(); | 	requestedRedrawTime = vsyncPredictor.suggested_draw_time(); | ||||||
| 	const auto delay_time = (requestedRedrawTime - time_now) / 1'000'000; | 	const auto delay_time = (requestedRedrawTime - time_now) / 1'000'000; | ||||||
| 	if(delay_time > 0) { | 	if(delay_time > 0 && delay_time < vsyncPredictor.frame_duration()) { | ||||||
| 		QTimer::singleShot(delay_time, this, SLOT(repaint())); | 		QTimer::singleShot(delay_time, this, SLOT(repaint())); | ||||||
| 	} else { | 	} else { | ||||||
| 		requestedRedrawTime = 0; | 		requestedRedrawTime = 0; | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
| import glob | import glob | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| # establish UTF-8 encoding for Python 2 | # Establish UTF-8 encoding for Python 2. | ||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
| 	reload(sys) | 	reload(sys) | ||||||
| 	sys.setdefaultencoding('utf-8') | 	sys.setdefaultencoding('utf-8') | ||||||
|  |  | ||||||
| # create build environment | # Create build environment. | ||||||
| env = Environment() | env = Environment() | ||||||
|  |  | ||||||
| # determine compiler and linker flags for SDL | # Determine compiler and linker flags for SDL. | ||||||
| env.ParseConfig('sdl2-config --cflags') | env.ParseConfig('sdl2-config --cflags') | ||||||
| env.ParseConfig('sdl2-config --libs') | env.ParseConfig('sdl2-config --libs') | ||||||
|  |  | ||||||
| # gather a list of source files | # Gather a list of source files. | ||||||
| SOURCES = glob.glob('*.cpp') | SOURCES = glob.glob('*.cpp') | ||||||
|  |  | ||||||
| SOURCES += glob.glob('../../Analyser/Dynamic/*.cpp') | SOURCES += glob.glob('../../Analyser/Dynamic/*.cpp') | ||||||
| @@ -117,11 +117,11 @@ SOURCES += glob.glob('../../Storage/Tape/*.cpp') | |||||||
| SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp') | SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp') | ||||||
| SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp') | SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp') | ||||||
|  |  | ||||||
| # add additional compiler flags | # Add additional compiler flags; c++1z is insurance in case c++17 isn't fully implemented. | ||||||
| env.Append(CCFLAGS = ['--std=c++17', '-Wall', '-O2', '-DNDEBUG']) | env.Append(CCFLAGS = ['--std=c++17', '--std=c++1z', '-Wall', '-O2', '-DNDEBUG']) | ||||||
|  |  | ||||||
| # add additional libraries to link against | # Add additional libraries to link against. | ||||||
| env.Append(LIBS = ['libz', 'pthread', 'GL']) | env.Append(LIBS = ['libz', 'pthread', 'GL']) | ||||||
|  |  | ||||||
| # build target | # Build target. | ||||||
| env.Program(target = 'clksignal', source = SOURCES) | env.Program(target = 'clksignal', source = SOURCES) | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| # Clock Signal | # Clock Signal | ||||||
| Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve. | Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve. | ||||||
|  |  | ||||||
| [Releases](https://github.com/TomHarte/CLK/releases) are hosted on GitHub. | macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal). | ||||||
|  |  | ||||||
| On the Mac it is a native Cocoa application; under Linux, BSD and other UNIXes and UNIX-alikes it can be built either with Qt or with SDL; the Qt build should be considered preliminary and is currently closely bound to X11 as Qt doesn't abstract game-like keyboard handling. | On the Mac it is a native Cocoa application; under Linux, BSD and other UNIXes and UNIX-alikes it can be built either with Qt or with SDL; the Qt build should be considered preliminary and is currently closely bound to X11 as Qt doesn't abstract game-like keyboard handling. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -166,6 +166,7 @@ std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) { | |||||||
| 				number_of_bits = std::min(file_.get16le(), uint16_t(6646*8)); | 				number_of_bits = std::min(file_.get16le(), uint16_t(6646*8)); | ||||||
| 			break; | 			break; | ||||||
|  |  | ||||||
|  | 			default: | ||||||
| 			case Type::WOZ2: { | 			case Type::WOZ2: { | ||||||
| 				// In WOZ 2 an extra level of indirection allows for variable track sizes. | 				// In WOZ 2 an extra level of indirection allows for variable track sizes. | ||||||
| 				const uint16_t starting_block = file_.get16le(); | 				const uint16_t starting_block = file_.get16le(); | ||||||
|   | |||||||
| @@ -247,10 +247,10 @@ void TZX::get_data_block(const DataBlock &data_block) { | |||||||
|  |  | ||||||
| void TZX::get_data(const Data &data) { | void TZX::get_data(const Data &data) { | ||||||
| 	// Output data. | 	// Output data. | ||||||
| 	for(unsigned int c = 0; c < data.data_length; c++) { | 	for(decltype(data.data_length) c = 0; c < data.data_length; c++) { | ||||||
| 		uint8_t next_byte = file_.get8(); | 		uint8_t next_byte = file_.get8(); | ||||||
|  |  | ||||||
| 		unsigned int bits = (c != data.data_length-1) ? 8 : data.number_of_bits_in_final_byte; | 		auto bits = (c != data.data_length-1) ? 8 : data.number_of_bits_in_final_byte; | ||||||
| 		while(bits--) { | 		while(bits--) { | ||||||
| 			unsigned int pulse_length = (next_byte & 0x80) ? data.length_of_one_bit_pulse : data.length_of_zero_bit_pulse; | 			unsigned int pulse_length = (next_byte & 0x80) ? data.length_of_one_bit_pulse : data.length_of_zero_bit_pulse; | ||||||
| 			next_byte <<= 1; | 			next_byte <<= 1; | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ class TZX: public PulseQueuedTape { | |||||||
| 			unsigned int length_of_one_bit_pulse; | 			unsigned int length_of_one_bit_pulse; | ||||||
| 			unsigned int number_of_bits_in_final_byte; | 			unsigned int number_of_bits_in_final_byte; | ||||||
| 			unsigned int pause_after_block; | 			unsigned int pause_after_block; | ||||||
| 			long data_length; | 			uint32_t data_length; | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		struct DataBlock { | 		struct DataBlock { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user