// // main.cpp // Clock Signal // // Created by Thomas Harte on 04/11/2017. // Copyright © 2017 Thomas Harte. All rights reserved. // #include #include #include #include #include "../../StaticAnalyser/StaticAnalyser.hpp" #include "../../Machines/Utility/MachineForTarget.hpp" #include "../../Machines/ConfigurationTarget.hpp" #include "../../Machines/CRTMachine.hpp" #include "../../Concurrency/BestEffortUpdater.hpp" namespace { struct CRTMachineDelegate: public CRTMachine::Machine::Delegate { void machine_did_change_clock_rate(CRTMachine::Machine *machine) { best_effort_updater->set_clock_rate(machine->get_clock_rate()); } void machine_did_change_clock_is_unlimited(CRTMachine::Machine *machine) { } Concurrency::BestEffortUpdater *best_effort_updater; }; struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) { machine->crt_machine()->run_for(Cycles(cycles)); } Machine::DynamicMachine *machine; }; // This is set to a relatively large number for now. const int AudioBufferSize = 1024; struct SpeakerDelegate: public Outputs::Speaker::Delegate { void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector &buffer) { if(SDL_GetQueuedAudioSize(audio_device) < AudioBufferSize*3) SDL_QueueAudio(audio_device, reinterpret_cast(buffer.data()), static_cast(buffer.size() * sizeof(uint16_t))); updater->update(); } SDL_AudioDeviceID audio_device; Concurrency::BestEffortUpdater *updater; }; bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) { #define BIND(x, y) case SDL_SCANCODE_##x: key = Inputs::Keyboard::Key::y; break; switch(scancode) { default: return false; BIND(F1, F1) BIND(F2, F2) BIND(F3, F3) BIND(F4, F4) BIND(F5, F5) BIND(F6, F6) BIND(F7, F7) BIND(F8, F8) BIND(F9, F9) BIND(F10, F10) BIND(F11, F11) BIND(F12, F12) BIND(1, k1) BIND(2, k2) BIND(3, k3) BIND(4, k4) BIND(5, k5) BIND(6, k6) BIND(7, k7) BIND(8, k8) BIND(9, k9) BIND(0, k0) BIND(Q, Q) BIND(W, W) BIND(E, E) BIND(R, R) BIND(T, T) BIND(Y, Y) BIND(U, U) BIND(I, I) BIND(O, O) BIND(P, P) BIND(A, A) BIND(S, S) BIND(D, D) BIND(F, F) BIND(G, G) BIND(H, H) BIND(J, J) BIND(K, K) BIND(L, L) BIND(Z, Z) BIND(X, X) BIND(C, C) BIND(V, V) BIND(B, B) BIND(N, N) BIND(M, M) BIND(KP_7, KeyPad7) BIND(KP_8, KeyPad8) BIND(KP_9, KeyPad9) BIND(KP_4, KeyPad4) BIND(KP_5, KeyPad5) BIND(KP_6, KeyPad6) BIND(KP_1, KeyPad1) BIND(KP_2, KeyPad2) BIND(KP_3, KeyPad3) BIND(KP_0, KeyPad0) BIND(ESCAPE, Escape) BIND(PRINTSCREEN, PrintScreen) BIND(SCROLLLOCK, ScrollLock) BIND(PAUSE, Pause) BIND(GRAVE, BackTick) BIND(MINUS, Hyphen) BIND(EQUALS, Equals) BIND(BACKSPACE, BackSpace) BIND(TAB, Tab) BIND(LEFTBRACKET, OpenSquareBracket) BIND(RIGHTBRACKET, CloseSquareBracket) BIND(BACKSLASH, BackSlash) BIND(CAPSLOCK, CapsLock) BIND(SEMICOLON, Semicolon) BIND(APOSTROPHE, Quote) BIND(RETURN, Enter) BIND(LSHIFT, LeftShift) BIND(COMMA, Comma) BIND(PERIOD, FullStop) BIND(SLASH, ForwardSlash) BIND(RSHIFT, RightShift) BIND(LCTRL, LeftControl) BIND(LALT, LeftOption) BIND(LGUI, LeftMeta) BIND(SPACE, Space) BIND(RCTRL, RightControl) BIND(RALT, RightOption) BIND(RGUI, RightMeta) BIND(LEFT, Left) BIND(RIGHT, Right) BIND(UP, Up) BIND(DOWN, Down) BIND(INSERT, Insert) BIND(HOME, Home) BIND(PAGEUP, PageUp) BIND(DELETE, Delete) BIND(END, End) BIND(PAGEDOWN, PageDown) BIND(NUMLOCKCLEAR, NumLock) BIND(KP_DIVIDE, KeyPadSlash) BIND(KP_MULTIPLY, KeyPadAsterisk) BIND(KP_PLUS, KeyPadPlus) BIND(KP_MINUS, KeyPadMinus) BIND(KP_ENTER, KeyPadEnter) BIND(KP_DECIMAL, KeyPadDecimalPoint) BIND(KP_EQUALS, KeyPadEquals) BIND(HELP, Help) // SDL doesn't seem to have scancodes for hash or keypad delete? } #undef BIND return true; } } int main(int argc, char *argv[]) { SDL_Window *window = nullptr; // Perform a sanity check on arguments. if(argc < 2) { std::cerr << "Usage: " << argv[0] << " [file]" << std::endl; return -1; } // Determine the machine for the supplied file. std::list targets = StaticAnalyser::GetTargets(argv[1]); if(targets.empty()) { std::cerr << "Cannot open " << argv[1] << std::endl; return -1; } Concurrency::BestEffortUpdater updater; BestEffortUpdaterDelegate best_effort_updater_delegate; CRTMachineDelegate crt_delegate; SpeakerDelegate speaker_delegate; // Create and configure a machine. std::unique_ptr<::Machine::DynamicMachine> machine(::Machine::MachineForTarget(targets.front())); updater.set_clock_rate(machine->crt_machine()->get_clock_rate()); crt_delegate.best_effort_updater = &updater; best_effort_updater_delegate.machine = machine.get(); speaker_delegate.updater = &updater; machine->crt_machine()->set_delegate(&crt_delegate); updater.set_delegate(&best_effort_updater_delegate); // Attempt to set up video and audio. if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; return -1; } // Ask for no depth buffer, a core profile and vsync-aligned rendering. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetSwapInterval(1); window = SDL_CreateWindow( "Clock Signal", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 400, 300, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if(!window) { std::cerr << "Could not create window" << std::endl; return -1; } SDL_GLContext gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); // Setup output, assuming a CRT machine for now, and prepare a best-effort updater. machine->crt_machine()->setup_output(4.0 / 3.0); machine->crt_machine()->get_crt()->set_output_gamma(2.2f); // For vanilla SDL purposes, assume system ROMs can be found in one of: // // /usr/local/share/CLK/[system]; or // /usr/share/CLK/[system] machine->crt_machine()->install_roms( [] (const std::string &machine, const std::string &name) -> std::unique_ptr> { std::string local_path = "/usr/local/share/CLK/" + machine + "/" + name; FILE *file = fopen(local_path.c_str(), "r"); if(!file) { std::string path = "/usr/share/CLK/" + machine + "/" + name; file = fopen(path.c_str(), "r"); } if(!file) return nullptr; std::unique_ptr> data(new std::vector); fseek(file, 0, SEEK_END); data->resize(ftell(file)); fseek(file, 0, SEEK_SET); fread(data->data(), 1, data->size(), file); fclose(file); return data; }); machine->configuration_target()->configure_as_target(targets.front()); // For now, lie about audio output intentions. auto speaker = machine->crt_machine()->get_speaker(); if(speaker) { // Create an audio pipe. SDL_AudioSpec desired_audio_spec; SDL_AudioSpec obtained_audio_spec; SDL_zero(desired_audio_spec); desired_audio_spec.freq = 48000; // TODO: how can I get SDL to reveal the output rate of this machine? desired_audio_spec.format = AUDIO_S16; desired_audio_spec.channels = 1; desired_audio_spec.samples = AudioBufferSize; speaker_delegate.audio_device = SDL_OpenAudioDevice(nullptr, 0, &desired_audio_spec, &obtained_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); speaker->set_output_rate(obtained_audio_spec.freq, obtained_audio_spec.samples); speaker->set_delegate(&speaker_delegate); SDL_PauseAudioDevice(speaker_delegate.audio_device, 0); } // Run the main event loop until the OS tells us to quit. bool should_quit = false; while(!should_quit) { // Process all pending events. SDL_Event event; while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_QUIT: should_quit = true; break; case SDL_KEYDOWN: case SDL_KEYUP: KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); if(!keyboard_machine) break; Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space; if(!KeyboardKeyForSDLScancode(event.key.keysym.scancode, key)) break; keyboard_machine->get_keyboard().set_key_pressed(key, event.type == SDL_KEYDOWN); break; } } // Display a new frame and wait for vsync. updater.update(); int width, height; SDL_GetWindowSize(window, &width, &height); machine->crt_machine()->get_crt()->draw_frame(static_cast(width), static_cast(height), false); SDL_GL_SwapWindow(window); } // Clean up. SDL_DestroyWindow( window ); SDL_Quit(); return 0; }