MESS's C64 driver is able to load tapes sampled into WAV files.
However, the feature has some problems.
Here's a sequence of steps to reproduce the problem.
- Start MESS c64p driver (Commodore 64 PAL)
- type LOAD(Enter) in the emulated C64
- press Scroll Lock to enter partial keyboard emulation
- press TAB to enter the menu
- Choose File Manager
- Choose Cassette (cass)
- Select a WAV file with a sampled C64 tape
- Press ESC to get back to the main menu
- Choose Tape Control
- Choose Play
The cassette will go into playing state, then immediately to stopped state. You need to choose Play once (sometimes twice) more to have the cassette running.
After a while, the emulated C64 will show the FOUND message and stop the motor of the emulated cassette player. After a few seconds, the motor of the emulated cassette player will restart. At that point, the cassette will go into stopped state, and rewind to zero.
I investigated the problem a bit. c2n_device sets a timer (m_read_timer) that goes off 44100 times a second, and, every time, updates the state of the cassette input line with the WAV sample. When the C64 stops the motor of the cassette player, c2n_device::m_read_timer is disabled, so its timer does not go off. When the C64 restarts the motor of the cassette player, c2n_device::m_read_timer.m_expire still has the value of when the motor was stopped. When the timer is enabled again, the line
m_callback_timer_expire_time = timer.m_expire;
in schedule.c changes the current time, setting it backwards (because timer.m_expire was not progressing while the cassette motor was stopped).
The next time cassette_image_device::update() is called,
cur_time = device().machine().time().as_double();
assigns cur_time the
new current time, while m_position_time still contains the value of the current time at the previous call. The line
double new_position = m_position + (cur_time - m_position_time)*m_speed*m_direction;
assigns a negative value to new_position (since cur_time < m_position_time), and, a few lines below,
else if (new_position < 0)
{
m_state = (cassette_state)(( m_state & ~CASSETTE_MASK_UISTATE ) | CASSETTE_STOPPED);
new_position = 0;
}
stops the cassette.