summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/imagedev/cassette.cpp
blob: ee618b33c793d97c4cd6102b1409444261b44234 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

                                                    

                                                                      
                
 
                                                    



                                                                      
                             
                     
 





                                               
 
                         
                                                                                 




                                                   
                                                                                                                             
                                                         
                                               
                                               
                            





                                  
                       
                                            
                               
                                       

                             
 

 




                                                   
 
 
 







                                                    


                                                                                           


 



                                                                      
                                         
 
                                                                  
                             
                                                                      
                             

                            



 
                                    
 
                                                       
 
                          
         
                                                                                                    
 
                                                                                                                     
                 
                                     
                                                                                                                   


                                   
                                       
                         
                                                                                                        
                                                             
                                                             

                                                          
                                                                                                        

                                                              

                                                          
                                                                                                        

                                                         


                              
                                          
         
                                   

 
                                                                                   
 
                                                                      


                                                                                  



 
                                     
 
                 
                                 
                                                          
 
                                                                                                      





                            
                                                
 
                                                                                         
         
                         
 

                                              
 
                                                         



         
                                            
 
                          



 
                                          
 

                                 
                                             


                                                                    


















                                                    

 
                                                         
 
                                     









                               
                                       









                                             
                          







                                                                      
                                          
 
                                  
                             
                                  
                    

                                                                                           

 
                                                                                                              
 
                                   

 
                                                    
 


                                    
                                                                      
 
                                  
                                                
                         
 
                                                                                        
         
                                    
                                                                                                                                                                    
                                                          



                                   
                                   
                           

                  






                                                                                       
                                                                                                                                       






                                                                                                                                  
                 
                             
 
                                                          



                                                                

                                                               

                                
                         
                                                       
 



                                                         
 
                                       

      


                                                       
                                                     

                                                      
                                                        

                                                         
                                                          

                                                         
                                                          






                                                          
                                       



 
                                         
 
                                                               

                                                                 

                                    
                                   
                             

                                                                
                                                              


 

                                                   
                                      
                                                   
 
                                                 
 





                                                                            
         
                                                                                                       

                                                          

                                                 
                                                                             

                                                                
                                                                               





                                                                    
                                                                                                        
                                                                                   

                                                                     





                                             
         
                      
 






                                                                                                                                       

                                                  



                                          
                                           



                                                                                                       

                                                                               




                                                                                                                                   
                                                      












                                                                                 
// license:BSD-3-Clause
// copyright-holders:Nathan Woods, Miodrag Milanovic
/*********************************************************************

    cassette.cpp

    Interface to the cassette image abstraction code

*********************************************************************/

#include "emu.h"
#include "formats/imageutl.h"
#include "cassette.h"

#define LOG_WARN          (1U<<1)   // Warnings
#define LOG_DETAIL        (1U<<2)   // Details

#define VERBOSE ( LOG_WARN )

#include "logmacro.h"

// device type definition
DEFINE_DEVICE_TYPE(CASSETTE, cassette_image_device, "cassette_image", "Cassette")

//-------------------------------------------------
//  cassette_image_device - constructor
//-------------------------------------------------

cassette_image_device::cassette_image_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, CASSETTE, tag, owner, clock),
	device_image_interface(mconfig, *this),
	device_sound_interface(mconfig, *this),
	m_cassette(nullptr),
	m_state(CASSETTE_STOPPED),
	m_position(0),
	m_position_time(0),
	m_value(0),
	m_channel(0),
	m_speed(0),
	m_direction(0),
	m_formats(cassette_default_formats),
	m_create_opts(nullptr),
	m_default_state(CASSETTE_PLAY),
	m_interface(nullptr),
	m_stereo(false)
{
}

//-------------------------------------------------
//  cassette_image_device - destructor
//-------------------------------------------------

cassette_image_device::~cassette_image_device()
{
}

//-------------------------------------------------
//  device_config_complete - perform any
//  operations now that the configuration is
//  complete
//-------------------------------------------------

void cassette_image_device::device_config_complete()
{
	m_extension_list[0] = '\0';
	for (int i = 0; m_formats[i]; i++ )
		image_specify_extension( m_extension_list, 256, m_formats[i]->extensions );
}


/*********************************************************************
    cassette IO
*********************************************************************/

bool cassette_image_device::is_motor_on()
{
	if ((m_state & CASSETTE_MASK_UISTATE) == CASSETTE_STOPPED)
		return false;
	if ((m_state & CASSETTE_MASK_MOTOR) != CASSETTE_MOTOR_ENABLED)
		return false;
	else
		return true;
}



void cassette_image_device::update()
{
	double cur_time = machine().time().as_double();

	if (is_motor_on())
	{
		double new_position = m_position + (cur_time - m_position_time)*m_speed*m_direction;

		switch (int(m_state & CASSETTE_MASK_UISTATE)) // cast to int to suppress unhandled enum value warning
		{
		case CASSETTE_RECORD:
			cassette_put_sample(m_cassette, m_channel, m_position, new_position - m_position, m_value);
			break;

		case CASSETTE_PLAY:
			if (m_cassette)
			{
				cassette_get_sample(m_cassette, m_channel, new_position, 0.0, &m_value);
				// See if reached end of tape
				double length = get_length();
				if (new_position > length)
				{
					m_state = (m_state & ~CASSETTE_MASK_UISTATE) | CASSETTE_STOPPED;
					new_position = length;
				}
				else if (new_position < 0)
				{
					m_state = (m_state & ~CASSETTE_MASK_UISTATE) | CASSETTE_STOPPED;
					new_position = 0;
				}
			}
			break;
		}
		m_position = new_position;
	}
	m_position_time = cur_time;
}

void cassette_image_device::change_state(cassette_state state, cassette_state mask)
{
	cassette_state new_state = (m_state & ~mask) | (state & mask);
	if ((m_state ^ new_state) & (CASSETTE_MASK_UISTATE | CASSETTE_MASK_MOTOR))
		m_position_time = machine().time().as_double();
	m_state = new_state;
}



double cassette_image_device::input()
{
	update();
	int32_t sample = m_value;
	double double_value = sample / double(0x7FFFFFFF);

	LOGMASKED(LOG_DETAIL, "cassette_input(): time_index=%g value=%g\n", m_position, double_value);

	return double_value;
}



void cassette_image_device::output(double value)
{
	if (((m_state & CASSETTE_MASK_UISTATE) == CASSETTE_RECORD) && (m_value != value))
	{
		update();

		value = std::min(value, 1.0);
		value = std::max(value, -1.0);

		m_value = (int32_t) (value * 0x7FFFFFFF);
	}
}


double cassette_image_device::get_position()
{
	return m_position;
}



double cassette_image_device::get_length()
{
	struct CassetteInfo info;

	cassette_get_info(m_cassette, &info);
	return ((double) info.sample_count) / info.sample_frequency;
}

void cassette_image_device::set_channel(int channel)
{
	m_channel = channel;
}

void cassette_image_device::set_speed(double speed)
{
	m_speed = speed;
}

void cassette_image_device::go_forward()
{
	m_direction = 1;
}

void cassette_image_device::go_reverse()
{
	m_direction = -1;
}


void cassette_image_device::seek(double time, int origin)
{
	double length = get_length();

	switch(origin) {
	case SEEK_SET:
		break;

	case SEEK_END:
		time += length;
		break;

	case SEEK_CUR:
		time += get_position();
		break;
	}

	/* clip position into legal bounds */
	if (time < 0)
		time = 0;
	else
	if (time > length)
		time = length;

	m_position = time;
}



/*********************************************************************
    cassette device init/load/unload/specify
*********************************************************************/

void cassette_image_device::device_start()
{
	/* set to default state */
	m_cassette = nullptr;
	m_state = m_default_state;
	m_value = 0;

	machine().sound().stream_alloc(*this, 0, (m_stereo? 2:1), machine().sample_rate());
}

image_init_result cassette_image_device::call_create(int format_type, util::option_resolution *format_options)
{
	return internal_load(true);
}

image_init_result cassette_image_device::call_load()
{
	return internal_load(false);
}

image_init_result cassette_image_device::internal_load(bool is_create)
{
	cassette_image::error err;
	device_image_interface *image = nullptr;
	interface(image);

	if (is_create || (length()==0)) // empty existing images are fine to write over.
	{
		// creating an image
		err = cassette_create((void *)image, &image_ioprocs, &wavfile_format, m_create_opts, CASSETTE_FLAG_READWRITE|CASSETTE_FLAG_SAVEONEXIT, &m_cassette);
		if (err != cassette_image::error::SUCCESS)
			goto error;
	}
	else
	{
		// opening an image
		bool retry;
		do
		{
			// we probably don't want to retry...
			retry = false;

			// try opening the cassette
			int cassette_flags = is_readonly()
				? CASSETTE_FLAG_READONLY
				: (CASSETTE_FLAG_READWRITE | CASSETTE_FLAG_SAVEONEXIT);
			err = cassette_open_choices((void *)image, &image_ioprocs, filetype(), m_formats, cassette_flags, &m_cassette);

			// special case - if we failed due to readwrite not being supported, make the image be read only and retry
			if (err == cassette_image::error::READ_WRITE_UNSUPPORTED)
			{
				make_readonly();
				retry = true;
			}
		}
		while(retry);

		if (err != cassette_image::error::SUCCESS)
			goto error;
	}

	/* set to default state, but only change the UI state */
	//change_state(m_default_state, CASSETTE_MASK_UISTATE);
	m_state = m_default_state;

	/* reset the position */
	m_position = 0.0;
	m_position_time = machine().time().as_double();

	/* default channel to 0, speed multiplier to 1 */
	m_channel = 0;
	m_speed = 1;
	m_direction = 1;

	return image_init_result::PASS;

error:
	image_error_t imgerr = IMAGE_ERROR_UNSPECIFIED;
	switch(err)
	{
		case cassette_image::error::INTERNAL:
			imgerr = IMAGE_ERROR_INTERNAL;
			break;
		case cassette_image::error::UNSUPPORTED:
			imgerr = IMAGE_ERROR_UNSUPPORTED;
			break;
		case cassette_image::error::OUT_OF_MEMORY:
			imgerr = IMAGE_ERROR_OUTOFMEMORY;
			break;
		case cassette_image::error::INVALID_IMAGE:
			imgerr = IMAGE_ERROR_INVALIDIMAGE;
			break;
		default:
			imgerr = IMAGE_ERROR_UNSPECIFIED;
			break;
	}
	image->seterror(imgerr, "" );
	return image_init_result::FAIL;
}



void cassette_image_device::call_unload()
{
	/* if we are recording, write the value to the image */
	if ((m_state & CASSETTE_MASK_UISTATE) == CASSETTE_RECORD)
		update();

	/* close out the cassette */
	cassette_close(m_cassette);
	m_cassette = nullptr;

	/* set to default state, but only change the UI state */
	change_state(CASSETTE_STOPPED, CASSETTE_MASK_UISTATE);
}


//-------------------------------------------------
//  display a small tape animation, with the
//  current position in the tape image
//-------------------------------------------------

std::string cassette_image_device::call_display()
{
	const int ANIMATION_FPS = 1;

	std::string result;

	// only show the image when a cassette is loaded and the motor is on
	if (exists() && is_motor_on())
	{
		static char const *const shapes[] = { u8"\u2500", u8"\u2572", u8"\u2502", u8"\u2571" };

		// figure out where we are in the cassette
		double position = get_position();
		double length = get_length();
		cassette_state uistate = get_state() & CASSETTE_MASK_UISTATE;

		// choose which frame of the animation we are at
		int n = (int(position) / ANIMATION_FPS) % ARRAY_LENGTH(shapes);

		// play or record
		const char *status_icon = (uistate == CASSETTE_PLAY)
			? u8"\u25BA"
			: u8"\u25CF";

		// Since you can have anything in a BDF file, we will use crude ascii characters instead
		result = string_format("%s %s %02d:%02d (%04d) [%02d:%02d (%04d)]",
			shapes[n],                  // animation
			status_icon,                // play or record
			((int)position / 60),
			((int)position % 60),
			(int)position,
			((int)length / 60),
			((int)length % 60),
			(int)length);
	}
	return result;
}

//-------------------------------------------------
//  Cassette sound
//-------------------------------------------------

void cassette_image_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	stream_sample_t *left_buffer = outputs[0];
	stream_sample_t *right_buffer = nullptr;

	if (m_stereo)
		right_buffer = outputs[1];

	cassette_state state = get_state();

	if (exists() && (state == (CASSETTE_PLAY | CASSETTE_MOTOR_ENABLED | CASSETTE_SPEAKER_ENABLED)))
	{
		cassette_image *cassette = get_image();
		double time_index = get_position();
		double duration = ((double) samples) / machine().sample_rate();

		cassette_get_samples(cassette, 0, time_index, duration, samples, 2, left_buffer, CASSETTE_WAVEFORM_16BIT);
		if (m_stereo)
			cassette_get_samples(cassette, 1, time_index, duration, samples, 2, right_buffer, CASSETTE_WAVEFORM_16BIT);

		for (int i = samples - 1; i >= 0; i--)
		{
			left_buffer[i] = ((int16_t *) left_buffer)[i];
			if (m_stereo)
				right_buffer[i] = ((int16_t *) right_buffer)[i];
		}
	}
	else
	{
		memset(left_buffer, 0, sizeof(*left_buffer) * samples);
		if (m_stereo)
			memset(right_buffer, 0, sizeof(*right_buffer) * samples);
	}
}