mirror of
https://github.com/holub/mame
synced 2025-10-06 17:08:28 +03:00
642 lines
17 KiB
C++
642 lines
17 KiB
C++
// 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 "emuopts.h"
|
|
#include "samples.h"
|
|
#include "flac.h"
|
|
|
|
|
|
//**************************************************************************
|
|
// GLOBAL VARIABLES
|
|
//**************************************************************************
|
|
|
|
// device type definition
|
|
const device_type SAMPLES = &device_creator<samples_device>;
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// LIVE DEVICE
|
|
//**************************************************************************
|
|
|
|
//-------------------------------------------------
|
|
// samples_device - constructors
|
|
//-------------------------------------------------
|
|
|
|
samples_device::samples_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
|
: device_t(mconfig, SAMPLES, "Samples", tag, owner, clock, "samples", __FILE__),
|
|
device_sound_interface(mconfig, *this),
|
|
m_channels(0),
|
|
m_names(nullptr)
|
|
{
|
|
}
|
|
|
|
samples_device::samples_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, uint32_t clock, const char *shortname, const char *source)
|
|
: device_t(mconfig, type, name, tag, owner, clock, shortname, source),
|
|
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;
|
|
}
|