f844e846b7
either libsamplerate or the SoX resampler library installed. If not installed, the data is only copied to the output buffer and sample rate of the output driver is changed similar to legacy code. Related changes: - Added check for libsamplerate or SoX resampler header files in configure script. - Added functions for converting source format to float (requied by resampler) and float to output format. - Added support for float type data in the audio buffer code. Buffer chain #0 receives float data from sendwavepacket() and buffer #1 receives data for mixing and output in format "16-bit signed little endian stereo". - ALSA: Disable builtin resampling feature if resampler is present to avoid doing it twice.
387 lines
10 KiB
C++
387 lines
10 KiB
C++
/////////////////////////////////////////////////////////////////////////
|
|
// $Id$
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2001-2017 The Bochs Project
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
// Support for sound output to files (based on SB16 code)
|
|
|
|
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
|
|
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
|
|
// is used to know when we are exporting symbols and when we are importing.
|
|
#define BX_PLUGGABLE
|
|
|
|
#include "iodev.h"
|
|
#include "soundlow.h"
|
|
#include "soundfile.h"
|
|
|
|
#if BX_SUPPORT_SOUNDLOW
|
|
|
|
#define BX_SOUNDFILE_RAW 0
|
|
#define BX_SOUNDFILE_VOC 1
|
|
#define BX_SOUNDFILE_WAV 2
|
|
#define BX_SOUNDFILE_MID 3
|
|
|
|
#define LOG_THIS
|
|
|
|
// bx_soundlow_waveout_file_c class implemenzation
|
|
|
|
bx_soundlow_waveout_file_c::bx_soundlow_waveout_file_c()
|
|
:bx_soundlow_waveout_c()
|
|
{
|
|
wavefile = NULL;
|
|
type = BX_SOUNDFILE_RAW;
|
|
}
|
|
|
|
bx_soundlow_waveout_file_c::~bx_soundlow_waveout_file_c()
|
|
{
|
|
closewaveoutput();
|
|
}
|
|
|
|
void bx_soundlow_waveout_file_c::initwavfile()
|
|
{
|
|
Bit8u waveheader[44] =
|
|
{0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d,
|
|
0x74, 0x20, 0x10, 0, 0, 0, 0x1, 0, 0x2, 0, 0x44, 0xac, 0, 0, 0x10, 0xb1,
|
|
0x2, 0, 0x4, 0, 0x10, 0, 0x64, 0x61, 0x74, 0x61, 0, 0, 0, 0};
|
|
|
|
fwrite(waveheader, 1, 44, wavefile);
|
|
}
|
|
|
|
void bx_soundlow_waveout_file_c::write_32bit(Bit32u pos, Bit32u value)
|
|
{
|
|
Bit8u size[4] = {(Bit8u)(value & 0xff), (Bit8u)(value >> 8),
|
|
(Bit8u)(value >> 16), (Bit8u)(value >> 24)};
|
|
|
|
fseek(wavefile, pos, SEEK_SET);
|
|
fwrite(size, 1, 4, wavefile);
|
|
}
|
|
|
|
int bx_soundlow_waveout_file_c::openwaveoutput(const char *wavedev)
|
|
{
|
|
size_t len = strlen(wavedev);
|
|
char ext[4];
|
|
|
|
if ((wavefile == NULL) && (len > 0)) {
|
|
if (len > 4) {
|
|
if (wavedev[len-4] == '.') {
|
|
strcpy(ext, wavedev+len-3);
|
|
if (!stricmp(ext, "voc")) {
|
|
type = BX_SOUNDFILE_VOC;
|
|
} else if (!stricmp(ext, "wav")) {
|
|
type = BX_SOUNDFILE_WAV;
|
|
}
|
|
}
|
|
}
|
|
wavefile = fopen(wavedev, "wb");
|
|
if (wavefile == NULL) {
|
|
BX_ERROR(("Failed to open WAVE output file %s.", wavedev));
|
|
} else if (type == BX_SOUNDFILE_VOC) {
|
|
VOC_init_file();
|
|
} else if (type == BX_SOUNDFILE_WAV) {
|
|
initwavfile();
|
|
}
|
|
set_pcm_params(&real_pcm_param);
|
|
if (resampler_control != 1) {
|
|
start_resampler_thread();
|
|
}
|
|
if (mixer_control != 1) {
|
|
pcm_callback_id = register_wave_callback(this, pcm_callback);
|
|
start_mixer_thread();
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
} else {
|
|
return BX_SOUNDLOW_ERR;
|
|
}
|
|
}
|
|
|
|
int bx_soundlow_waveout_file_c::set_pcm_params(bx_pcm_param_t *param)
|
|
{
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
int bx_soundlow_waveout_file_c::output(int length, Bit8u data[])
|
|
{
|
|
Bit8u temparray[12] =
|
|
{ (Bit8u)(real_pcm_param.samplerate & 0xff), (Bit8u)(real_pcm_param.samplerate >> 8), 0, 0,
|
|
(Bit8u)real_pcm_param.bits, real_pcm_param.channels, 0, 0, 0, 0, 0, 0 };
|
|
|
|
if (wavefile != NULL) {
|
|
if (type == BX_SOUNDFILE_VOC) {
|
|
switch ((real_pcm_param.format >> 1) & 7) {
|
|
case 2:
|
|
temparray[6] = 3;
|
|
break;
|
|
case 3:
|
|
temparray[6] = 2;
|
|
break;
|
|
case 4:
|
|
temparray[6] = 1;
|
|
break;
|
|
}
|
|
if (real_pcm_param.bits == 16)
|
|
temparray[6] = 4;
|
|
|
|
VOC_write_block(9, 12, temparray, length, data);
|
|
} else {
|
|
fwrite(data, 1, length, wavefile);
|
|
}
|
|
if (pcm_callback_id >= 0) {
|
|
BX_MSLEEP(100);
|
|
}
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
int bx_soundlow_waveout_file_c::closewaveoutput()
|
|
{
|
|
if (wavefile != NULL) {
|
|
if (type == BX_SOUNDFILE_VOC) {
|
|
fputc(0, wavefile);
|
|
} else if (type == BX_SOUNDFILE_WAV) {
|
|
Bit32u tracklen = ftell(wavefile);
|
|
write_32bit(4, tracklen - 8);
|
|
write_32bit(24, real_pcm_param.samplerate);
|
|
write_32bit(28, (Bit32u)real_pcm_param.samplerate * 4);
|
|
write_32bit(40, tracklen - 44);
|
|
}
|
|
fclose(wavefile);
|
|
wavefile = NULL;
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
/* Handlers for the VOC file output */
|
|
|
|
// Write the header of the VOC file.
|
|
|
|
void bx_soundlow_waveout_file_c::VOC_init_file()
|
|
{
|
|
struct {
|
|
char id[20];
|
|
Bit16u headerlen; // All in LITTLE Endian!
|
|
Bit16u version;
|
|
Bit16u chksum;
|
|
} vocheader =
|
|
{ "Creative Voice File",
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
0x1a, 0x0114, 0x111f };
|
|
#else
|
|
0x1a00, 0x1401, 0x1f11 };
|
|
#endif
|
|
|
|
vocheader.id[19] = 26; // Replace string end with 26
|
|
|
|
fwrite(&vocheader, 1, sizeof vocheader, wavefile);
|
|
}
|
|
|
|
// write one block to the VOC file
|
|
void bx_soundlow_waveout_file_c::VOC_write_block(int block, Bit32u headerlen,
|
|
Bit8u header[], Bit32u datalen, Bit8u data[])
|
|
{
|
|
Bit32u i;
|
|
|
|
if (block > 9) {
|
|
BX_ERROR(("VOC Block %d not recognized, ignored.", block));
|
|
return;
|
|
}
|
|
|
|
fputc(block, wavefile);
|
|
|
|
i = headerlen + datalen;
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
fwrite(&i, 1, 3, wavefile); // write the length in 24-bit little endian
|
|
#else
|
|
Bit8u lengthbytes[3];
|
|
lengthbytes[0] = i & 0xff; i >>= 8;
|
|
lengthbytes[1] = i & 0xff; i >>= 8;
|
|
lengthbytes[2] = i & 0xff;
|
|
fwrite(lengthbytes, 1, 3, wavefile);
|
|
#endif
|
|
BX_DEBUG(("Voc block %d; Headerlen %d; Datalen %d",
|
|
block, headerlen, datalen));
|
|
if (headerlen > 0)
|
|
fwrite(header, 1, headerlen, wavefile);
|
|
if (datalen > 0)
|
|
fwrite(data, 1, datalen, wavefile);
|
|
}
|
|
|
|
// bx_soundlow_midiout_file_c class implemenzation
|
|
|
|
bx_soundlow_midiout_file_c::bx_soundlow_midiout_file_c()
|
|
:bx_soundlow_midiout_c()
|
|
{
|
|
midifile = NULL;
|
|
type = BX_SOUNDFILE_RAW;
|
|
}
|
|
|
|
bx_soundlow_midiout_file_c::~bx_soundlow_midiout_file_c()
|
|
{
|
|
closemidioutput();
|
|
}
|
|
|
|
int bx_soundlow_midiout_file_c::openmidioutput(const char *mididev)
|
|
{
|
|
struct {
|
|
Bit8u chunk[4];
|
|
Bit32u chunklen; // all values in BIG Endian!
|
|
Bit16u smftype;
|
|
Bit16u tracknum;
|
|
Bit16u timecode; // 0x80 + deltatimesperquarter << 8
|
|
} midiheader =
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
{ "MTh", 0x06000000, 0, 0x0100, 0x8001 };
|
|
#else
|
|
{ "MTh", 6, 0, 1, 0x180 };
|
|
#endif
|
|
midiheader.chunk[3] = 'd';
|
|
|
|
struct {
|
|
Bit8u chunk[4];
|
|
Bit32u chunklen;
|
|
Bit8u data[15];
|
|
} trackheader =
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
{ "MTr", 0xffffff7f,
|
|
#else
|
|
{ "MTr", 0x7fffffff,
|
|
#endif
|
|
{ 0x00,0xff,0x51,3,0x07,0xa1,0x20, // set tempo 120 (0x7a120 us per quarter)
|
|
0x00,0xff,0x58,4,4,2,0x18,0x08 }}; // time sig 4/4
|
|
trackheader.chunk[3] = 'k';
|
|
|
|
size_t len = strlen(mididev);
|
|
char ext[4];
|
|
|
|
if ((midifile == NULL) && (len > 0)) {
|
|
if (len > 4) {
|
|
if (mididev[len-4] == '.') {
|
|
strcpy(ext, mididev+len-3);
|
|
if (!stricmp(ext, "mid")) {
|
|
type = BX_SOUNDFILE_MID;
|
|
}
|
|
}
|
|
}
|
|
midifile = fopen(mididev, "wb");
|
|
if (midifile == NULL) {
|
|
BX_ERROR(("Failed to open MIDI output file %s.", mididev));
|
|
return BX_SOUNDLOW_ERR;
|
|
} else if (type == BX_SOUNDFILE_MID) {
|
|
fwrite(&midiheader, 1, 14, midifile);
|
|
fwrite(&trackheader, 1, 23, midifile);
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
} else {
|
|
return BX_SOUNDLOW_ERR;
|
|
}
|
|
}
|
|
|
|
int bx_soundlow_midiout_file_c::sendmidicommand(int delta, int command, int length, Bit8u data[])
|
|
{
|
|
if (midifile != NULL) {
|
|
if (type == BX_SOUNDFILE_MID) {
|
|
writedeltatime(delta);
|
|
}
|
|
fputc(command, midifile);
|
|
if ((command == 0xf0) ||
|
|
(command == 0xf7)) // write event length for sysex/meta events
|
|
writedeltatime(length);
|
|
|
|
fwrite(data, 1, length, midifile);
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
int bx_soundlow_midiout_file_c::closemidioutput()
|
|
{
|
|
struct {
|
|
Bit8u delta, statusbyte, metaevent, length;
|
|
} metatrackend = { 0, 0xff, 0x2f, 0 };
|
|
|
|
if (midifile != NULL) {
|
|
if (type == BX_SOUNDFILE_MID) {
|
|
// Meta event track end (0xff 0x2f 0x00) plus leading delta time
|
|
fwrite(&metatrackend, 1, sizeof(metatrackend), midifile);
|
|
|
|
int trlen = ftell(midifile);
|
|
if (trlen < 0)
|
|
BX_PANIC (("ftell failed in closemidioutput()"));
|
|
Bit32u tracklen = (Bit32u)trlen;
|
|
if (tracklen < 22)
|
|
BX_PANIC (("MIDI track length too short"));
|
|
tracklen -= 22; // subtract the midi file and track header
|
|
fseek(midifile, 22 - 4, SEEK_SET);
|
|
// value has to be in big endian
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
tracklen = bx_bswap32(tracklen);
|
|
#endif
|
|
fwrite(&tracklen, 4, 1, midifile);
|
|
}
|
|
fclose(midifile);
|
|
midifile = NULL;
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
void bx_soundlow_midiout_file_c::writedeltatime(Bit32u deltatime)
|
|
{
|
|
int i, count = 0;
|
|
Bit8u outbytes[4], value[4];
|
|
|
|
if (deltatime == 0) {
|
|
count = 1;
|
|
value[0] = 0;
|
|
} else {
|
|
while ((deltatime > 0) && (count < 4)) { // split into parts of seven bits
|
|
outbytes[count++] = deltatime & 0x7f;
|
|
deltatime >>= 7;
|
|
}
|
|
for (i=0; i<count; i++) // reverse order and
|
|
value[i] = outbytes[count - i - 1] | 0x80; // set eighth bit on
|
|
value[count - 1] &= 0x7f; // all but last byte
|
|
}
|
|
for (int i=0; i<count; i++)
|
|
fputc(value[i], midifile);
|
|
}
|
|
|
|
// bx_sound_oss_c class implemenzation
|
|
|
|
bx_sound_file_c::bx_sound_file_c()
|
|
:bx_sound_lowlevel_c()
|
|
{
|
|
BX_INFO(("Sound lowlevel module 'file' initialized"));
|
|
}
|
|
|
|
bx_soundlow_waveout_c* bx_sound_file_c::get_waveout()
|
|
{
|
|
if (waveout == NULL) {
|
|
waveout = new bx_soundlow_waveout_file_c();
|
|
}
|
|
return waveout;
|
|
}
|
|
|
|
bx_soundlow_midiout_c* bx_sound_file_c::get_midiout()
|
|
{
|
|
if (midiout == NULL) {
|
|
midiout = new bx_soundlow_midiout_file_c();
|
|
}
|
|
return midiout;
|
|
}
|
|
|
|
#endif
|