// 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;
}