summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/sound/samples.cpp
blob: 40ff619cdce02d21cfb62b2ae7a0966faa710104 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                
                                                                            
 
             
 
                                     
 
                                                                            
 


                                                                       
 

                                             
 



                                                                      
 
                                                                            
 
                
                    


                    
                 
 
 


                                                                            
 
                         
                                                                 
 

 


                                                                            
 


                                                   
 
                                                                                                               
                                                             
 

 




                                                                                                                                 
 


 


                                                                            
 


                                                   
 
                                                                          

                                                       
                             
                       
 
                                            
                                     

                                          
                                             


                                
                                               
                                                                           
                                                
                                                                    


                                         
                                                                                    
                         


 
                                                   
                                           

                                                   
 
                                                                                                                           
 
                                     

                                          
                                             








                                     
                                                                                    

                         
 
 



                                                   
 
                                                                  
 
                                     
 
                                          
                                             
                              
                                                                           
 
 
 



                                                   
 
                                                              
 
                                     
 
                                          
                                             
                                                


 


                                                   
 
                                                       
 
                                     
 
                                          
                                             

                            
 
 


                                                   
 
                                          
 
                                     
 
                                          
                                             
                              
                             

 
 




                                                   
 
                                                   
                                                                  

                              
 
 



                                                   
 
                                                              
 
                                     
 
                                          
                                                   

                              

 
 

                                                   
                                

                                                   
                                                   
 
                                     
 
                                          
                                                   
                              
                                        
 
 
 
 


                                                                            
 


                                                   
 
                                   
 

                             
 





                                                              
                                                                          
                                      

                                     

                                

                                                      






                                                             
         
 
                                         
                                                      
 

                                         

 
 




                                                   
 

                   
 
 

                                                   
           
                                                   
 






                                                                  
                                                                              
                 
                                                                     


                                                                

                                                     
 
                                                                                         
                                                                             




                                                               
                                                      



                                                     

 
 


                                                   
 







                                                                                                                                
 
                                                                                 
                                                                   

                                                         




                                                                            



                                                                           


                                                                                            













                                                                                                               
                                                                              


                                                                                                             



                                                              
 







                                                                             

 
 
 


                                                                            
 
                                                   
                                              

                                                   
 
                                                                  
 
                                                                

                                            

                       
                                                                                         

                             
 




                                                      
 
                                                 
                                                                                  
                     

 
 


                                                   
 
                                                                      
 
                                                 
                            
 
                             
                          

                                          
         
                                                                                                
                             
         
                                                    




                                                                 
         
                                                                                                
                             
         
                                            
         
                                                                                         
                             
         

                                          
                        
                 
         

                                                
                                                        






                                                    
                 
                                                                                             
                                     
                 
         
 
                                                 
                        
                                        
                                                
                        
         
                                                                                                                    
                             
         


                                                       
                                                
                        
         
                                                                                                                                 
                             
         

                      
                      
                                      
                                            




                                                       
                      
                                      
                                            
                                    
         
                                                                                                                             
                             
         









                                                
                                                        






                                                    
                 
                                                                                              
                                     
                 
         
 

                                                         
         
                                                                               
                             
         
 
                                  
                                
 

                           
         
                                           
                                                   
 
                                                       


                                                                                   




                                               
                                                   


                                                           
                                                                                
                                                                                                  
         
                    


 


                                                   
 
                                                                       
 



                                                              
                                                      






                                                    
 
                                    
                                                    
                                                                             
                             
 


                                 


 


                                                   
                                                                    
                                                   
 
                                   
 
                       

                                                        
                             
 









                                                     
                                                                                                             
         

                                                                                 

                                                                                                  
                                                                                             
 
                                     
                                                    
                                                                                         
                                                                              


                                                                                            
                                                    
                                                           
                                                              
                 
                                                                                   

                                   
         
                  
 
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    samples.c

    Sound device for sample playback.

****************************************************************************

    Playback of pre-recorded samples. Used for high-level simulation of
    discrete sound circuits where proper low-level simulation isn't
    available.  Also used for tape loops and similar.

    Current limitations
      - Only supports single channel samples!

    Considerations
      - Maybe this should be part of the presentation layer
        (artwork etc.) with samples specified in .lay files instead of
        in drivers?

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

#include "emu.h"
#include "samples.h"

#include "emuopts.h"

#include "flac.h"


//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

// device type definition
DEFINE_DEVICE_TYPE(SAMPLES, samples_device, "samples", "Samples")



//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  samples_device - constructors
//-------------------------------------------------

samples_device::samples_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: samples_device(mconfig, SAMPLES, tag, owner, clock)
{
}

samples_device::samples_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, type, tag, owner, clock)
	, device_sound_interface(mconfig, *this)
	, m_channels(0)
	, m_names(nullptr)
{
}


//**************************************************************************
//  PUBLIC INTERFACE
//**************************************************************************

//-------------------------------------------------
//  start - start playing a loaded sample
//-------------------------------------------------

void samples_device::start(uint8_t channel, uint32_t samplenum, bool loop)
{
	// if samples are disabled, just return quietly
	if (m_sample.empty())
		return;

	assert(samplenum < m_sample.size());
	assert(channel < m_channels);

	// force an update before we start
	channel_t &chan = m_channel[channel];
	chan.stream->update();

	// update the parameters
	sample_t &sample = m_sample[samplenum];
	chan.source = (sample.data.size() > 0) ? &sample.data[0] : nullptr;
	chan.source_length = sample.data.size();
	chan.source_num = (chan.source_length > 0) ? samplenum : -1;
	chan.pos = 0;
	chan.frac = 0;
	chan.basefreq = sample.frequency;
	chan.step = (int64_t(chan.basefreq) << FRAC_BITS) / machine().sample_rate();
	chan.loop = loop;
}


//-------------------------------------------------
//  start_raw - start playing an externally
//  provided sample
//-------------------------------------------------

void samples_device::start_raw(uint8_t channel, const int16_t *sampledata, uint32_t samples, uint32_t frequency, bool loop)
{
	assert(channel < m_channels);

	// force an update before we start
	channel_t &chan = m_channel[channel];
	chan.stream->update();

	// update the parameters
	chan.source = sampledata;
	chan.source_length = samples;
	chan.source_num = -1;
	chan.pos = 0;
	chan.frac = 0;
	chan.basefreq = frequency;
	chan.step = (int64_t(chan.basefreq) << FRAC_BITS) / machine().sample_rate();
	chan.loop = loop;
}


//-------------------------------------------------
//  set_frequency - set the playback frequency of
//  a sample
//-------------------------------------------------

void samples_device::set_frequency(uint8_t channel, uint32_t freq)
{
	assert(channel < m_channels);

	// force an update before we start
	channel_t &chan = m_channel[channel];
	chan.stream->update();
	chan.step = (int64_t(freq) << FRAC_BITS) / machine().sample_rate();
}


//-------------------------------------------------
//  set_volume - set the playback volume of a
//  sample
//-------------------------------------------------

void samples_device::set_volume(uint8_t channel, float volume)
{
	assert(channel < m_channels);

	// force an update before we start
	channel_t &chan = m_channel[channel];
	chan.stream->set_output_gain(0, volume);
}


//-------------------------------------------------
//  pause - pause playback on a channel
//-------------------------------------------------

void samples_device::pause(uint8_t channel, bool pause)
{
	assert(channel < m_channels);

	// force an update before we start
	channel_t &chan = m_channel[channel];
	chan.paused = pause;
}


//-------------------------------------------------
//  stop - stop playback on a channel
//-------------------------------------------------

void samples_device::stop(uint8_t channel)
{
	assert(channel < m_channels);

	// force an update before we start
	channel_t &chan = m_channel[channel];
	chan.source = nullptr;
	chan.source_num = -1;
}


//-------------------------------------------------
//  stop_all - stop playback on all channels
//-------------------------------------------------

void samples_device::stop_all()
{
	// just iterate over channels and stop them
	for (uint8_t channel = 0; channel < m_channels; channel++)
		stop(channel);
}


//-------------------------------------------------
//  base_frequency - return the base frequency of
//  a channel being played
//-------------------------------------------------

uint32_t samples_device::base_frequency(uint8_t channel) const
{
	assert(channel < m_channels);

	// force an update before we start
	const channel_t &chan = m_channel[channel];
	chan.stream->update();
	return chan.basefreq;
}


//-------------------------------------------------
//  playing - return true if a sample is still
//  playing on the given channel
//-------------------------------------------------

bool samples_device::playing(uint8_t channel) const
{
	assert(channel < m_channels);

	// force an update before we start
	const channel_t &chan = m_channel[channel];
	chan.stream->update();
	return (chan.source != nullptr);
}



//**************************************************************************
//  DEVICE INTERFACE
//**************************************************************************

//-------------------------------------------------
//  device_start - handle device startup
//-------------------------------------------------

void samples_device::device_start()
{
	// read audio samples
	load_samples();

	// allocate channels
	m_channel.resize(m_channels);
	for (int channel = 0; channel < m_channels; channel++)
	{
		// initialize channel
		channel_t &chan = m_channel[channel];
		chan.stream = stream_alloc(0, 1, machine().sample_rate());
		chan.source = nullptr;
		chan.source_num = -1;
		chan.step = 0;
		chan.loop = 0;
		chan.paused = 0;

		// register with the save state system
		save_item(NAME(chan.source_length), channel);
		save_item(NAME(chan.source_num), channel);
		save_item(NAME(chan.pos), channel);
		save_item(NAME(chan.frac), channel);
		save_item(NAME(chan.step), channel);
		save_item(NAME(chan.loop), channel);
		save_item(NAME(chan.paused), channel);
	}

	// initialize any custom handlers
	m_samples_start_cb.bind_relative_to(*owner());

	if (!m_samples_start_cb.isnull())
		m_samples_start_cb();
}


//-------------------------------------------------
//  device_reset - handle device reset
//-------------------------------------------------

void samples_device::device_reset()
{
	stop_all();
}


//-------------------------------------------------
//  device_post_load - handle updating after a
//  restore
//-------------------------------------------------

void samples_device::device_post_load()
{
	// loop over channels
	for (int channel = 0; channel < m_channels; channel++)
	{
		// attach any samples that were loaded and playing
		channel_t &chan = m_channel[channel];
		if (chan.source_num >= 0 && chan.source_num < m_sample.size())
		{
			sample_t &sample = m_sample[chan.source_num];
			chan.source = &sample.data[0];
			chan.source_length = sample.data.size();
			if (sample.data.empty())
				chan.source_num = -1;
		}

		// validate the position against the length in case the sample is smaller
		if (chan.source != nullptr && chan.pos >= chan.source_length)
		{
			if (chan.loop)
				chan.pos %= chan.source_length;
			else
			{
				chan.source = nullptr;
				chan.source_num = -1;
			}
		}
	}
}


//-------------------------------------------------
//  sound_stream_update - update a sound stream
//-------------------------------------------------

void samples_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	// find the channel with this stream
	for (int channel = 0; channel < m_channels; channel++)
		if (&stream == m_channel[channel].stream)
		{
			channel_t &chan = m_channel[channel];
			stream_sample_t *buffer = outputs[0];

			// process if we still have a source and we're not paused
			if (chan.source != nullptr && !chan.paused)
			{
				// load some info locally
				uint32_t pos = chan.pos;
				uint32_t frac = chan.frac;
				uint32_t step = chan.step;
				const int16_t *sample = chan.source;
				uint32_t sample_length = chan.source_length;

				while (samples--)
				{
					// do a linear interp on the sample
					int32_t sample1 = sample[pos];
					int32_t sample2 = sample[(pos + 1) % sample_length];
					int32_t fracmult = frac >> (FRAC_BITS - 14);
					*buffer++ = ((0x4000 - fracmult) * sample1 + fracmult * sample2) >> 14;

					// advance
					frac += step;
					pos += frac >> FRAC_BITS;
					frac = frac & ((1 << FRAC_BITS) - 1);

					// handle looping/ending
					if (pos >= sample_length)
					{
						if (chan.loop)
							pos %= sample_length;
						else
						{
							chan.source = nullptr;
							chan.source_num = -1;
							if (samples > 0)
								memset(buffer, 0, samples * sizeof(*buffer));
							break;
						}
					}
				}

				// push position back out
				chan.pos = pos;
				chan.frac = frac;
			}
			else
				memset(buffer, 0, samples * sizeof(*buffer));
			break;
		}
}



//**************************************************************************
//  INTERNAL HELPERS
//**************************************************************************

//-------------------------------------------------
//  read_sample - read a WAV or FLAC file as a
//  sample
//-------------------------------------------------

bool samples_device::read_sample(emu_file &file, sample_t &sample)
{
	// read the core header and make sure it's a proper file
	uint8_t buf[4];
	uint32_t offset = file.read(buf, 4);
	if (offset < 4)
	{
		osd_printf_warning("Unable to read %s, 0-byte file?\n", file.filename());
		return false;
	}

	// look for the appropriate RIFF tag
	if (memcmp(&buf[0], "RIFF", 4) == 0)
		return read_wav_sample(file, sample);
	else if (memcmp(&buf[0], "fLaC", 4) == 0)
		return read_flac_sample(file, sample);

	// if nothing appropriate, emit a warning
	osd_printf_warning("Unable to read %s, corrupt file?\n", file.filename());
	return false;
}


//-------------------------------------------------
//  read_wav_sample - read a WAV file as a sample
//-------------------------------------------------

bool samples_device::read_wav_sample(emu_file &file, sample_t &sample)
{
	// we already read the opening 'RIFF' tag
	uint32_t offset = 4;

	// get the total size
	uint32_t filesize;
	offset += file.read(&filesize, 4);
	if (offset < 8)
	{
		osd_printf_warning("Unexpected size offset %u (%s)\n", offset, file.filename());
		return false;
	}
	filesize = little_endianize_int32(filesize);

	// read the RIFF file type and make sure it's a WAVE file
	char buf[32];
	offset += file.read(buf, 4);
	if (offset < 12)
	{
		osd_printf_warning("Unexpected WAVE offset %u (%s)\n", offset, file.filename());
		return false;
	}
	if (memcmp(&buf[0], "WAVE", 4) != 0)
	{
		osd_printf_warning("Could not find WAVE header (%s)\n", file.filename());
		return false;
	}

	// seek until we find a format tag
	uint32_t length;
	while (1)
	{
		offset += file.read(buf, 4);
		offset += file.read(&length, 4);
		length = little_endianize_int32(length);
		if (memcmp(&buf[0], "fmt ", 4) == 0)
			break;

		// seek to the next block
		file.seek(length, SEEK_CUR);
		offset += length;
		if (offset >= filesize)
		{
			osd_printf_warning("Could not find fmt tag (%s)\n", file.filename());
			return false;
		}
	}

	// read the format -- make sure it is PCM
	uint16_t temp16;
	offset += file.read(&temp16, 2);
	temp16 = little_endianize_int16(temp16);
	if (temp16 != 1)
	{
		osd_printf_warning("unsupported format %u - only PCM is supported (%s)\n", temp16, file.filename());
		return false;
	}

	// number of channels -- only mono is supported
	offset += file.read(&temp16, 2);
	temp16 = little_endianize_int16(temp16);
	if (temp16 != 1)
	{
		osd_printf_warning("unsupported number of channels %u - only mono is supported (%s)\n", temp16, file.filename());
		return false;
	}

	// sample rate
	uint32_t rate;
	offset += file.read(&rate, 4);
	rate = little_endianize_int32(rate);

	// bytes/second and block alignment are ignored
	offset += file.read(buf, 6);

	// bits/sample
	uint16_t bits;
	offset += file.read(&bits, 2);
	bits = little_endianize_int16(bits);
	if (bits != 8 && bits != 16)
	{
		osd_printf_warning("unsupported bits/sample %u - only 8 and 16 are supported (%s)\n", bits, file.filename());
		return false;
	}

	// seek past any extra data
	file.seek(length - 16, SEEK_CUR);
	offset += length - 16;

	// seek until we find a data tag
	while (1)
	{
		offset += file.read(buf, 4);
		offset += file.read(&length, 4);
		length = little_endianize_int32(length);
		if (memcmp(&buf[0], "data", 4) == 0)
			break;

		// seek to the next block
		file.seek(length, SEEK_CUR);
		offset += length;
		if (offset >= filesize)
		{
			osd_printf_warning("Could not find data tag (%s)\n", file.filename());
			return false;
		}
	}

	// if there was a 0 length data block, we're done
	if (length == 0)
	{
		osd_printf_warning("empty data block (%s)\n", file.filename());
		return false;
	}

	// fill in the sample data
	sample.frequency = rate;

	// read the data in
	if (bits == 8)
	{
		sample.data.resize(length);
		file.read(&sample.data[0], length);

		// convert 8-bit data to signed samples
		uint8_t *tempptr = reinterpret_cast<uint8_t *>(&sample.data[0]);
		for (int32_t sindex = length - 1; sindex >= 0; sindex--)
			sample.data[sindex] = int8_t(tempptr[sindex] ^ 0x80) * 256;
	}
	else
	{
		// 16-bit data is fine as-is
		sample.data.resize(length / 2);
		file.read(&sample.data[0], length);

		// swap high/low on big-endian systems
		if (ENDIANNESS_NATIVE != ENDIANNESS_LITTLE)
			for (uint32_t sindex = 0; sindex < length / 2; sindex++)
				sample.data[sindex] = little_endianize_int16(sample.data[sindex]);
	}
	return true;
}


//-------------------------------------------------
//  read_flac_sample - read a FLAC file as a sample
//-------------------------------------------------

bool samples_device::read_flac_sample(emu_file &file, sample_t &sample)
{
	// seek back to the start of the file
	file.seek(0, SEEK_SET);

	// create the FLAC decoder and fill in the sample data
	flac_decoder decoder((util::core_file &)file);
	sample.frequency = decoder.sample_rate();

	// error if more than 1 channel or not 16bpp
	if (decoder.channels() != 1)
		return false;
	if (decoder.bits_per_sample() != 16)
		return false;

	// resize the array and read
	sample.data.resize(decoder.total_samples());
	if (!decoder.decode_interleaved(&sample.data[0], sample.data.size()))
		return false;

	// finish up and clean up
	decoder.finish();
	return true;
}


//-------------------------------------------------
//  load_samples - load all the samples in our
//  attached interface
//  Returns true when all samples were successfully read, else false
//-------------------------------------------------

bool samples_device::load_samples()
{
	bool ok = true;
	// if the user doesn't want to use samples, bail
	if (!machine().options().samples())
		return false;

	// iterate over ourself
	const char *basename = machine().basename();
	samples_iterator iter(*this);
	const char *altbasename = iter.altbasename();

	// pre-size the array
	m_sample.resize(iter.count());

	// load the samples
	int index = 0;
	for (const char *samplename = iter.first(); samplename != nullptr; index++, samplename = iter.next())
	{
		// attempt to open as FLAC first
		emu_file file(machine().options().sample_path(), OPEN_FLAG_READ);
		osd_file::error filerr = file.open(basename, PATH_SEPARATOR, samplename, ".flac");
		if (filerr != osd_file::error::NONE && altbasename != nullptr)
			filerr = file.open(altbasename, PATH_SEPARATOR, samplename, ".flac");

		// if not, try as WAV
		if (filerr != osd_file::error::NONE)
			filerr = file.open(basename, PATH_SEPARATOR, samplename, ".wav");
		if (filerr != osd_file::error::NONE && altbasename != nullptr)
			filerr = file.open(altbasename, PATH_SEPARATOR, samplename, ".wav");

		// if opened, read it
		if (filerr == osd_file::error::NONE)
			read_sample(file, m_sample[index]);
		else if (filerr == osd_file::error::NOT_FOUND)
		{
			logerror("%s: Sample '%s' NOT FOUND\n", tag(), samplename);
			ok = false;
		}
	}
	return ok;
}