Qt 4.8 和製MESS その3

画面の拡大、キーボード入力、CMT機能などを実装してみました。

f:id:tanam:20130527005152p:image:w360

f:id:tanam:20130527005151p:image:w360

f:id:tanam:20130527005150p:image:w360

f:id:tanam:20130527005147p:image:w360

emu.h

/*
	Skelton for retropc emulator

	Qt Version : tanam
	Date   : 2013.05.18 -

	[ Qt4.8 emulation i/f ]

*/

#ifndef _EMU_H_
#define _EMU_H_

#include <QApplication>
#include <QtGui>
#include <QKeyEvent>
#include <QPainter>
#include <QTimer>
#include <windows.h>
#include "common.h"
#include "vm/vm.h"
#include "res/resource.h"
#include "config.h"

#ifndef Q_OS_ANDROID
#include <dsound.h>
#endif

#ifndef SCREEN_WIDTH_ASPECT
#define SCREEN_WIDTH_ASPECT SCREEN_WIDTH
#endif
#ifndef SCREEN_HEIGHT_ASPECT
#define SCREEN_HEIGHT_ASPECT SCREEN_HEIGHT
#endif
#ifndef WINDOW_WIDTH
#define WINDOW_WIDTH SCREEN_WIDTH_ASPECT
#endif
#ifndef WINDOW_HEIGHT
#define WINDOW_HEIGHT SCREEN_HEIGHT_ASPECT
#endif

class EMU : public QWidget
{
    Q_OBJECT
protected:
    VM* vm;
    void paintEvent(QPaintEvent *);
    void keyPressEvent(QKeyEvent *);
    void keyReleaseEvent(QKeyEvent *);
    void mousePressEvent(QMouseEvent *);
protected slots:
    void onTimer();
private:
    QTimer *m_timer;
    int skip_frames;
    void ShowPopup();

    // ----------------------------------------
    // input
    // ----------------------------------------
    void initialize_input();
    void release_input();
    void update_input();
    uint8 key_status[256];	// windows key code mapping
    uint32 joy_status[2];	// joystick #1, #2 (b0 = up, b1 = down, b2 = left, b3 = right, b4- = buttons
    int joy_num;
    uint32 joy_mask[2];

    // ----------------------------------------
    // screen
    // ----------------------------------------
    void initialize_screen();
    void release_screen();

    // screen settings
    int screen_width, screen_height;
    int screen_width_aspect, screen_height_aspect;
    int window_width, window_height;

    // screen buffer
    unsigned char *scrbuf;

    // ----------------------------------------
    // sound
    // ----------------------------------------
    void initialize_sound();
    void release_sound();
    void update_sound(int* extra_frames);
    int sound_rate, sound_samples;
    bool sound_ok, sound_started, now_mute;

#ifndef Q_OS_ANDROID
    // direct sound
    LPDIRECTSOUND lpds;
    LPDIRECTSOUNDBUFFER lpdsb, lpdsp;
    bool first_half;
#endif

    // ----------------------------------------
    // media
    // ----------------------------------------
    typedef struct {
        _TCHAR path[_MAX_PATH];
        bool play;
        int offset;
        int wait_count;
    } media_status_t;

#ifdef USE_CART
    media_status_t cart_status;
#endif
#ifdef USE_FD1
    media_status_t disk_status[8];
#endif
#ifdef USE_TAPE
    media_status_t tape_status;
#endif

    void initialize_media();
    void update_media();
    void restore_media();
    void clear_media_status(media_status_t *status) {
        status->path[0] = _T('\0');
        status->wait_count = 0;
    }

public:
    // ----------------------------------------
    // initialize
    // ----------------------------------------
    EMU(QWidget *parent = 0);
    ~EMU();
    _TCHAR app_path[_MAX_PATH];
    _TCHAR* bios_path(_TCHAR* file_name);
    // drive virtual machine
    int run();
    void reset();

    // user interface
#ifdef USE_CART
    void open_cart(_TCHAR* file_path);
    void close_cart();
    bool cart_inserted();
#endif
#ifdef USE_FD1
    void open_disk(int drv, _TCHAR* file_path, int offset);
    void close_disk(int drv);
    bool disk_inserted(int drv);
#endif
#ifdef USE_TAPE
    void play_tape(_TCHAR* file_path);
    void rec_tape(_TCHAR* file_path);
    void close_tape();
    bool tape_inserted();
#endif

    // screen
    int get_window_width(int mode);
    int get_window_height(int mode);
    void draw_screen();

    // sound
    void mute_sound();

    // ----------------------------------------
    // for virtual machine
    // ----------------------------------------

    // input device
    uint8* key_buffer() {
        return key_status;
    }
    uint32* joy_buffer() {
        return joy_status;
    }

    // screen
    scrntype* screen_buffer(int y);

    // debug log
    void out_message(const _TCHAR* format);
};
#endif

emu.cpp

/*
	Skelton for retropc emulator

	Qt Version : tanam
	Date   : 2013.05.18 -

	[ Qt4.8 emulation i/f ]

*/

#include "emu.h"

// ----------------------------------------------------------------------------
// initialize
// ----------------------------------------------------------------------------
EMU::EMU(QWidget *)
{
    // timer event
    skip_frames=0;
    m_timer = new QTimer();
    connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimer()));
    m_timer->start(1000/FPS);

    // load sound config
    static int freq_table[8] = {2000, 4000, 8000, 11025, 22050, 44100, 48000, 96000};
    static double late_table[5] = {0.05, 0.1, 0.2, 0.3, 0.4};

    if(!(0 <= config.sound_frequency && config.sound_frequency < 8)) {
        config.sound_frequency = 6;	// default: 48KHz
    }
    if(!(0 <= config.sound_latency && config.sound_latency < 5)) {
        config.sound_latency = 1;	// default: 100msec
    }
    sound_rate = freq_table[config.sound_frequency];
    sound_samples = (int)(sound_rate * late_table[config.sound_latency] + 0.5);

    // initialize
    vm = new VM(this);
    initialize_input();
    initialize_screen();
    initialize_sound();
    initialize_media();
    vm->initialize_sound(sound_rate, sound_samples);
    vm->reset();

    config.window_mode = 1;
    open_cart((_TCHAR *)"/sdcard/rom/SC3000.ROM");
}

EMU::~EMU()
{
    release_input();
    release_screen();
    release_sound();
    delete vm;
}

// ----------------------------------------------------------------------------
// drive machine
// ----------------------------------------------------------------------------
int EMU::run()
{
    update_input();
    update_media();

    // virtual machine may be driven to fill sound buffer
    int extra_frames = 0;
    update_sound(&extra_frames);

    // drive virtual machine
    if(extra_frames == 0) {
        vm->run();
        extra_frames = 1;
    }
    return extra_frames;
}

void EMU::reset()
{
    // reset virtual machine
    vm->reset();
}

// ----------------------------------------------------------------------------
// user interface
// ----------------------------------------------------------------------------

void EMU::initialize_media()
{
#ifdef USE_CART
    memset(&cart_status, 0, sizeof(cart_status));
#endif
#ifdef USE_FD1
    memset(disk_status, 0, sizeof(disk_status));
#endif
#ifdef USE_TAPE
    memset(&tape_status, 0, sizeof(tape_status));
#endif
}

void EMU::update_media()
{
#ifdef USE_FD1
    for(int drv = 0; drv < 8; drv++) {
        if(disk_status[drv].wait_count != 0 && --disk_status[drv].wait_count == 0) {
            vm->open_disk(drv, disk_status[drv].path, disk_status[drv].offset);
        }
    }
#endif
#ifdef USE_TAPE
    if(tape_status.wait_count != 0 && --tape_status.wait_count == 0) {
        if(tape_status.play) {
            vm->play_tape(tape_status.path);
        } else {
            vm->rec_tape(tape_status.path);
        }
    }
#endif
}

void EMU::restore_media()
{
#ifdef USE_CART
    if(cart_status.path[0] != _T('\0')) {
        vm->open_cart(cart_status.path);
    }
#endif
#ifdef USE_FD1
    for(int drv = 0; drv < 8; drv++) {
        if(disk_status[drv].path[0] != _T('\0')) {
            vm->open_disk(drv, disk_status[drv].path, disk_status[drv].offset);
        }
    }
#endif
#ifdef USE_TAPE
    if(tape_status.path[0] != _T('\0')) {
        if(tape_status.play) {
            vm->play_tape(tape_status.path);
        } else {
            tape_status.path[0] = _T('\0');
        }
    }
#endif
}

#ifdef USE_CART
void EMU::open_cart(_TCHAR* file_path)
{
    vm->open_cart(file_path);
    _tcscpy(cart_status.path, file_path);
}

void EMU::close_cart()
{
    vm->close_cart();
    clear_media_status(&cart_status);
}

bool EMU::cart_inserted()
{
    return vm->cart_inserted();
}
#endif

#ifdef USE_FD1
void EMU::open_disk(int drv, _TCHAR* file_path, int offset)
{
    if(vm->disk_inserted(drv)) {
        vm->close_disk(drv);
        // wait 0.5sec
        disk_status[drv].wait_count = (int)(FRAMES_PER_SEC / 2);
    } else if(disk_status[drv].wait_count == 0) {
        vm->open_disk(drv, file_path, offset);
    }
    _tcscpy(disk_status[drv].path, file_path);
    disk_status[drv].offset = offset;
}

void EMU::close_disk(int drv)
{
    vm->close_disk(drv);
    clear_media_status(&disk_status[drv]);
}

bool EMU::disk_inserted(int drv)
{
    return vm->disk_inserted(drv);
}
#endif

#ifdef USE_TAPE
void EMU::play_tape(_TCHAR* file_path)
{
    if(vm->tape_inserted()) {
        vm->close_tape();
        // wait 0.5sec
        tape_status.wait_count = (int)(FRAMES_PER_SEC / 2);
    } else if(tape_status.wait_count == 0) {
        vm->play_tape(file_path);
    }
    _tcscpy(tape_status.path, file_path);
    tape_status.play = true;
}

void EMU::rec_tape(_TCHAR* file_path)
{
    if(vm->tape_inserted()) {
        vm->close_tape();
        // wait 0.5sec
        tape_status.wait_count = (int)(FRAMES_PER_SEC / 2);
    } else if(tape_status.wait_count == 0) {
        vm->rec_tape(file_path);
    }
    _tcscpy(tape_status.path, file_path);
    tape_status.play = false;
}

void EMU::close_tape()
{
    vm->close_tape();
    clear_media_status(&tape_status);
}

bool EMU::tape_inserted()
{
    return vm->tape_inserted();
}
#endif

void EMU::onTimer()
{
    run();
    if (++skip_frames >MAX_SKIP_FRAMES) {
        draw_screen();
        skip_frames=0;
    }
    update();
    return;
}

_TCHAR* EMU::bios_path(_TCHAR* file_name)
{
	static _TCHAR file_path[_MAX_PATH];
	_stprintf(file_path, _T("%s%s"), app_path, file_name);
	return file_path;
}

void EMU::out_message(const _TCHAR* format)
{
    qDebug(format);
    return;
}