Rev 2184 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* audio.cc
* DIN Is Noise is copyright (c) 2006-2025 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/
#include "main.h"
#include "audio.h"
#include "chrono.h"
#include "command.h"
#include <math.h>
#include <fstream>
#include <cstring>
#include <limits.h>
#include <stdlib.h>
#include <string>
#include "utils.h"
#include "RtAudio.h"
#include "RtError.h"
#include "log.h"
#include "console.h"
#include "oscilloscope.h"
using namespace std;
extern string user_data_dir;
extern string APP_NAME;
extern oscilloscope scope;
#if defined __UNIX_JACK__
#define SOUND_API RtAudio::UNIX_JACK
#elif defined __LINUX_ALSA__
#define SOUND_API RtAudio::LINUX_ALSA
#elif defined __MACOSX_CORE__
#define SOUND_API RtAudio::MACOSX_CORE
#elif defined __WINDOWS_DS__
#define SOUND_API RtAudio::WINDOWS_DS
#endif
audio_out::audio_out () : dac (SOUND_API) {
samples_buffers = 0;
available = 0;
result = ams = fms = vol = gatr = mix = mixa = bufL = bufR = fdr1 = fdr2 = 0;
prefs_name = user_data_dir + "audio_prefs";
load_prefs ();
probe ();
open ();
}
audio_out::~audio_out () {
save_prefs ();
int num_ptrs = 11;
sample_t* ptr [] = {samples_buffers, result, ams, fms, vol, gatr, mix, bufL, bufR, fdr1, fdr2, mixa};
for (int i = 0; i < num_ptrs; ++i) if (ptr[i]) delete[] ptr[i];
if (available) delete[] available;
dlog << "--- destroyed audio ---" << endl;
}
void audio_out::alloc () {
// alloc memory for various buffers
//
samples_per_buffer = num_channels * samples_per_channel;
samples_buffer_size = sizeof (sample_t) * samples_per_buffer;
samples_channel_size = sizeof (sample_t) * samples_per_channel;
if (samples_buffers) delete[] samples_buffers;
samples_buffers = new sample_t [num_samples_buffers * samples_per_buffer];
if (available) delete[] available;
available = new int [num_samples_buffers];
memset (available, 0, num_samples_buffers * sizeof (int));
readi = writei = 0;
readp = writep = samples_buffers;
int num_ptrs = 11;
sample_t** ptr [] = {&result, &ams, &fms, &vol, &gatr, &mix, &bufL, &bufR, &fdr1, &fdr2, &mixa};
for (int i = 0; i < num_ptrs; ++i) {
if (ptr[i]) delete[] *ptr[i];
*ptr[i] = new sample_t [samples_per_channel];
memset (*ptr[i], 0, samples_channel_size);
}
}
int audio_out::open () {
return open (current_device, sample_rate, samples_per_channel);
}
int audio_out::open (int id, unsigned int sr, unsigned int spc) {
RtAudio::StreamParameters parameters;
if (id > -1 && id < num_devices) {
parameters.deviceId = id;
} else {
id = default_device;
parameters.deviceId = id;
}
current_device = id;
// current sample rate exists?
find_sample_rate_id (sr);
sr = infos[id].sampleRates[i_sample_rate];
parameters.nChannels = num_channels;
RtAudio::StreamOptions options;
options.flags = RTAUDIO_NONINTERLEAVED;
try {
dac.openStream (¶meters, 0, RTAUDIO_FLOAT32, sr, &spc, audio_wanted, 0, &options);
set_samples_per_channel (spc);
alloc ();
set_sample_rate (sr);
dlog << "+++ opened audio stream +++" << endl;
return 1;
} catch (RtError& e) {
dlog << "!!! couldnt open audio stream !!!" << endl;
}
return 0;
}
int audio_out::start () {
try {
dac.startStream ();
dlog << "+++ " << APP_NAME << " is now streaming audio +++" << endl;
} catch (RtError& e) {
dlog << "!!! couldnt start the audio stream !!!" << endl;
}
return 0;
}
int audio_out::close () {
if (dac.isStreamOpen ()) {
dac.stopStream ();
dac.closeStream ();
dlog << "+++ stopped and closed audio stream +++" << endl;
return 1;
}
return 0;
}
void audio_out::load_prefs () {
//
// load audio preferences from disk
//
ifstream file (prefs_name.c_str(), ios::in);
string ignore;
if (!file) {
dlog << "!!! bad audio_prefs file. sorry, have to abort !!!" << endl;
exit (1);
} else {
file >> ignore >> current_device;
file >> ignore >> num_channels;
int spc; file >> ignore >> spc; set_samples_per_channel (spc);
file >> ignore >> sample_rate;
file >> ignore >> num_samples_buffers;
}
}
void audio_out::save_prefs () {
ofstream file (prefs_name.c_str(), ios::out);
if (file) {
if (current_device == default_device) current_device = -1;
file << "current_device " << current_device << endl;
file << "num_channels " << num_channels << endl;
file << "samples_per_channel " << samples_per_channel << endl;
file << "sample_rate " << sample_rate << endl;
file << "num_samples_buffers " << num_samples_buffers << endl;
dlog << "+++ saved audio prefrences in " << prefs_name << " +++" << endl;
} else dlog << "!!! couldnt write audio preferences file: " << prefs_name << " !!!" << endl;
}
void audio_out::set_samples_per_channel (int spc) {
samples_per_channel = spc;
last_sample = samples_per_channel - 1;
scope.alloc (spc);
}
void audio_out::set_sample_rate (int s) {
SAMPLE_RATE = sample_rate = s;
SAMPLE_DURATION = 1.0f / SAMPLE_RATE;
clk.delta_ticks = samples_per_channel;
clk.delta_secs = samples_per_channel * SAMPLE_DURATION;
}
int audio_out::audio_wanted (void *ob, void *ib, unsigned int spc, double t, RtAudioStreamStatus status, void *data) {
// stream audio buffer to audio card
//
if (aout.available[aout.readi]) { // samples buffer written by main thread ready for streaming
memcpy (ob, aout.readp, aout.samples_buffer_size); // copy written buffer into audio card
// update samples buffer status to let main thread write
aout.available [aout.readi] = 0;
if (++aout.readi >= aout.num_samples_buffers) {
aout.readi = 0;
aout.readp = aout.samples_buffers;
} else aout.readp += aout.samples_per_buffer;
++clk; // update audio clock
}
return 0;
}
void audio_out::probe () {
names.clear ();
num_devices = dac.getDeviceCount ();
if (num_devices == 0) {
dlog << "!!! no audio devices found !!!" << endl;
#ifdef __UNIX_JACK__
cout << "!!! Please start JACK before running DIN Is Noise !!!" << endl;
cout << "!!! DIN Is Noise aborted !!!" << endl;
exit (1);
#endif
} else {
last_device = num_devices - 1;
dlog << "+++ found " << num_devices << " audio devices +++" << endl;
infos.resize (num_devices);
default_device = dac.getDefaultOutputDevice ();
dlog << "+++ default output device = " << default_device << " +++" << endl;
if (current_device == INVALID) current_device = default_device;
next_device = current_device;
for (int i = 0; i < num_devices; ++i) {
RtAudio::DeviceInfo& info = infos[i];
info = dac.getDeviceInfo (i);
names.push_back (info.name);
char br = '[';
dlog << "name = " << info.name << " input channels [default?] = " << info.inputChannels << br << info.isDefaultInput << "], output channels [default?] = " << info.outputChannels << br << info.isDefaultOutput << "] duplex = " << info.duplexChannels << spc << endl;
int k = info.sampleRates.size ();
dlog << "sample rates [" << k << "]: ";
for (int i = 0, j = info.sampleRates.size (); i < j; ++i) {
dlog << info.sampleRates[i] << spc;
}
dlog << endl;
}
}
}
int audio_out::goto_next_device (int i) {
next_device = current_device + i;
wrap (0, next_device, last_device);
if (infos[next_device].outputChannels == 0) {
cons << RED << names[next_device] << " has no output channels" << eol;
return 0;
}
current_device = next_device;
find_sample_rate_id (sample_rate);
return 1;
}
void audio_out::find_sample_rate_id (unsigned int sr) {
i_sample_rate = 0;
std::vector<unsigned int> srates = infos[current_device].sampleRates;
for (int i = 0, j = srates.size (); i < j; ++i) {
if (sr == srates[i]) {
i_sample_rate = i;
break;
}
}
}
int audio_out::goto_next_sample_rate_id (int i) {
i_sample_rate += i;
vector<unsigned int> srates = infos[current_device].sampleRates;
int nrates = srates.size ();
if (i_sample_rate < 0) i_sample_rate = nrates - 1; else if (i_sample_rate >= nrates) i_sample_rate = 0;
return srates[i_sample_rate];
}
void audio_out::list () {
default_device = dac.getDefaultOutputDevice ();
for (int i = 0; i < num_devices; ++i) {
RtAudio::DeviceInfo& info = infos[i];
cons << GREEN << "name = " << info.name << " input = " << info.inputChannels << " output = " << info.outputChannels \
<< " duplex = " << info.duplexChannels << eol;
cons << CYAN << " sample rates: ";
for (int i = 0, j = info.sampleRates.size (); i < j; ++i) cons << int (info.sampleRates[i]) << spc;
cons << eol;
}
cons << YELLOW << "number of devices = " << num_devices << eol;
cons << "final device = " << last_device << ", next device = " << next_device << eol;
cons << "default device = " << default_device << ", current device = " << current_device << eol;
}