MuseScore/mscore/pa.cpp

405 lines
13 KiB
C++
Raw Normal View History

2012-05-26 14:49:10 +02:00
//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id: pa.cpp 5662 2012-05-23 07:35:47Z wschweer $
//
// Copyright (C) 2002-2010 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "preferences.h"
#include "libmscore/score.h"
#include "musescore.h"
#include "seq.h"
#include "pa.h"
#ifdef USE_ALSA
#include "alsa.h"
#include "alsamidi.h"
#endif
#include <portaudio.h>
#include "mididriver.h"
2016-08-09 00:25:42 +02:00
#ifdef USE_PORTMIDI
2012-05-26 14:49:10 +02:00
#include "pm.h"
2016-08-09 00:25:42 +02:00
#endif
2012-05-26 14:49:10 +02:00
2013-05-13 18:49:17 +02:00
namespace Ms {
2012-05-26 14:49:10 +02:00
static PaStream* stream;
//---------------------------------------------------------
// paCallback
//---------------------------------------------------------
int paCallback(const void*, void* out, long unsigned frames,
const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void *)
{
2017-05-14 17:43:28 +02:00
seq->setInitialMillisecondTimestampWithLatency();
2012-05-26 14:49:10 +02:00
seq->process((unsigned)frames, (float*)out);
return 0;
}
//---------------------------------------------------------
// Portaudio
//---------------------------------------------------------
Portaudio::Portaudio(Seq* s)
: Driver(s)
{
_sampleRate = 48000; // will be replaced by device default sample rate
2012-05-26 14:49:10 +02:00
initialized = false;
state = Transport::STOP;
2012-05-26 14:49:10 +02:00
seekflag = false;
midiDriver = 0;
}
//---------------------------------------------------------
// ~Portaudio
//---------------------------------------------------------
Portaudio::~Portaudio()
{
if (initialized) {
PaError err = Pa_CloseStream(stream);
if (err != paNoError)
2013-04-03 20:20:36 +02:00
qDebug("Portaudio close stream failed: %s", Pa_GetErrorText(err));
2012-05-26 14:49:10 +02:00
Pa_Terminate();
}
}
//---------------------------------------------------------
// init
// return false on error
//---------------------------------------------------------
bool Portaudio::init(bool)
2012-05-26 14:49:10 +02:00
{
PaError err = Pa_Initialize();
if (err != paNoError) {
2013-04-03 20:20:36 +02:00
qDebug("Portaudio initialize failed: %s", Pa_GetErrorText(err));
2012-05-26 14:49:10 +02:00
return false;
}
initialized = true;
if (MScore::debugMode)
2013-04-03 20:20:36 +02:00
qDebug("using PortAudio Version: %s", Pa_GetVersionText());
2012-05-26 14:49:10 +02:00
PaDeviceIndex idx = preferences.getInt(PREF_IO_PORTAUDIO_DEVICE);
2017-05-14 17:43:28 +02:00
if (idx < 0) {
2012-05-26 14:49:10 +02:00
idx = Pa_GetDefaultOutputDevice();
2017-05-14 17:43:28 +02:00
qDebug("No device selected. PortAudio detected %d devices. Will use the default device (index %d).", Pa_GetDeviceCount(), idx);
}
2012-05-26 14:49:10 +02:00
const PaDeviceInfo* di = Pa_GetDeviceInfo(idx);
Implemented caching signals to prevent not generated sound in case of framesBuff from PortAudio is too small If we don't specify framesPerBuffer parameter in Pa_OpenStream, PortAudio will choose optimal value for particular callback call. It can vary from run to run even on the same hardware depending on available system resources. While generating signal, interpolating and applying effects, we assume that framesBuffer contains more than minimal number of frames to generate envelope. BTW, it is not true. If framesBuffer is smaller, algos cannot generate correct sound and just keep silence. I've implemented cache which keeps generated values from dsp algorithms and applies it step-by-step to buffer values from pa_callback. Cache is filled each time algos generate dsp values. If buffer frames are not enough to generate envelope, algos generate values for further calls and keep it in cache. Required number of frames has been selected as a number of frames for one phase multiplying by number of phases. Actually, smaller numbers of this value generates good results, but it is better to keep it as max as possible to provide perfect sound. Code changes: - Replaced C-like variables with std containers for comfortable debugging and better usage - Extracted similar code calls to separate methods - Implemented cache as std constainers, so also implemented convertion from std::vector to C-like float* to fill the pa buffer - Changed the logic of applying effects and interpolation, it is now possible to use them separately. This is required to fill effects several times after calculating the interpolation is finished. Removed std::vector<float> to keep cache - process buff values on the fly. Performance is better, but still glitches on https://musescore.com/user/166080/scores/175421. BuffSize = 64 with my wired headphones.
2018-02-24 15:58:22 +01:00
//select default output device if no device or device without output channels have been selected
if (di == nullptr || di->maxOutputChannels < 1)
di = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice());
if (!di)
return false; // Portaudio is not properly initialized; disable audio
_sampleRate = int(di->defaultSampleRate);
2012-05-26 14:49:10 +02:00
/* Open an audio I/O stream. */
struct PaStreamParameters out;
memset(&out, 0, sizeof(out));
out.device = idx;
out.channelCount = 2;
out.sampleFormat = paFloat32;
Implemented caching signals to prevent not generated sound in case of framesBuff from PortAudio is too small If we don't specify framesPerBuffer parameter in Pa_OpenStream, PortAudio will choose optimal value for particular callback call. It can vary from run to run even on the same hardware depending on available system resources. While generating signal, interpolating and applying effects, we assume that framesBuffer contains more than minimal number of frames to generate envelope. BTW, it is not true. If framesBuffer is smaller, algos cannot generate correct sound and just keep silence. I've implemented cache which keeps generated values from dsp algorithms and applies it step-by-step to buffer values from pa_callback. Cache is filled each time algos generate dsp values. If buffer frames are not enough to generate envelope, algos generate values for further calls and keep it in cache. Required number of frames has been selected as a number of frames for one phase multiplying by number of phases. Actually, smaller numbers of this value generates good results, but it is better to keep it as max as possible to provide perfect sound. Code changes: - Replaced C-like variables with std containers for comfortable debugging and better usage - Extracted similar code calls to separate methods - Implemented cache as std constainers, so also implemented convertion from std::vector to C-like float* to fill the pa buffer - Changed the logic of applying effects and interpolation, it is now possible to use them separately. This is required to fill effects several times after calculating the interpolation is finished. Removed std::vector<float> to keep cache - process buff values on the fly. Performance is better, but still glitches on https://musescore.com/user/166080/scores/175421. BuffSize = 64 with my wired headphones.
2018-02-24 15:58:22 +01:00
out.suggestedLatency = di->defaultLowOutputLatency;
2012-05-26 14:49:10 +02:00
out.hostApiSpecificStreamInfo = 0;
err = Pa_OpenStream(&stream, 0, &out, double(_sampleRate), 0, 0, paCallback, (void*)this);
if (err != paNoError) {
// fall back to default device:
out.device = Pa_GetDefaultOutputDevice();
err = Pa_OpenStream(&stream, 0, &out, double(_sampleRate), 0, 0, paCallback, (void*)this);
if (err != paNoError) {
2013-04-03 20:20:36 +02:00
qDebug("Portaudio open stream %d failed: %s", idx, Pa_GetErrorText(err));
2012-05-26 14:49:10 +02:00
return false;
}
}
const PaStreamInfo* si = Pa_GetStreamInfo(stream);
if (si)
_sampleRate = int(si->sampleRate);
2012-05-26 14:49:10 +02:00
#ifdef USE_ALSA
midiDriver = new AlsaMidiDriver(seq);
#endif
#ifdef USE_PORTMIDI
midiDriver = new PortMidiDriver(seq);
#endif
if (midiDriver && !midiDriver->init()) {
2013-04-03 20:20:36 +02:00
qDebug("Init midi driver failed");
2012-05-26 14:49:10 +02:00
delete midiDriver;
midiDriver = 0;
#ifdef USE_PORTMIDI
return true; // return OK for audio driver; midi is only input
#else
return false;
#endif
}
return true;
}
//---------------------------------------------------------
// apiList
//---------------------------------------------------------
QStringList Portaudio::apiList() const
{
QStringList al;
PaHostApiIndex apis = Pa_GetHostApiCount();
for (PaHostApiIndex i = 0; i < apis; ++i) {
const PaHostApiInfo* info = Pa_GetHostApiInfo(i);
if (info)
al.append(QString::fromLocal8Bit(info->name));
2012-05-26 14:49:10 +02:00
}
return al;
}
//---------------------------------------------------------
// deviceList
//---------------------------------------------------------
QStringList Portaudio::deviceList(int apiIdx)
{
QStringList dl;
const PaHostApiInfo* info = Pa_GetHostApiInfo(apiIdx);
if (info) {
for (int i = 0; i < info->deviceCount; ++i) {
PaDeviceIndex idx = Pa_HostApiDeviceIndexToDeviceIndex(apiIdx, i);
const PaDeviceInfo* di = Pa_GetDeviceInfo(idx);
if (di)
dl.append(QString::fromLocal8Bit(di->name));
2012-05-26 14:49:10 +02:00
}
}
return dl;
}
//---------------------------------------------------------
// deviceIndex
//---------------------------------------------------------
int Portaudio::deviceIndex(int apiIdx, int apiDevIdx)
{
return Pa_HostApiDeviceIndexToDeviceIndex(apiIdx, apiDevIdx);
}
//---------------------------------------------------------
// start
//---------------------------------------------------------
bool Portaudio::start(bool)
2012-05-26 14:49:10 +02:00
{
PaError err = Pa_StartStream(stream);
if (err != paNoError) {
2013-04-03 20:20:36 +02:00
qDebug("Portaudio: start stream failed: %s", Pa_GetErrorText(err));
2012-05-26 14:49:10 +02:00
return false;
}
return true;
}
//---------------------------------------------------------
// stop
//---------------------------------------------------------
bool Portaudio::stop()
{
PaError err = Pa_StopStream(stream); // sometimes the program hangs here on exit
if (err != paNoError) {
2013-04-03 20:20:36 +02:00
qDebug("Portaudio: stop failed: %s", Pa_GetErrorText(err));
2012-05-26 14:49:10 +02:00
return false;
}
return true;
}
//---------------------------------------------------------
// framePos
//---------------------------------------------------------
int Portaudio::framePos() const
{
return 0;
}
//---------------------------------------------------------
// startTransport
//---------------------------------------------------------
void Portaudio::startTransport()
{
state = Transport::PLAY;
2012-05-26 14:49:10 +02:00
}
//---------------------------------------------------------
// stopTransport
//---------------------------------------------------------
void Portaudio::stopTransport()
{
state = Transport::STOP;
2012-05-26 14:49:10 +02:00
}
//---------------------------------------------------------
// getState
//---------------------------------------------------------
Transport Portaudio::getState()
2012-05-26 14:49:10 +02:00
{
return state;
}
//---------------------------------------------------------
// midiRead
//---------------------------------------------------------
void Portaudio::midiRead()
{
if (midiDriver)
midiDriver->read();
}
2017-05-14 17:43:28 +02:00
//---------------------------------------------------------
// putEvent
//---------------------------------------------------------
2018-04-02 01:35:20 +02:00
#ifdef USE_PORTMIDI
2017-05-14 17:43:28 +02:00
// Prevent killing sequencer with wrong data
#define less128(__less) ((__less >=0 && __less <= 127) ? __less : 0)
// TODO: this was copied from Jack version...I'd like to eventually unify these two, so that they handle midi event types in the same manner
void Portaudio::putEvent(const NPlayEvent& e, unsigned framePos)
{
PortMidiDriver* portMidiDriver = static_cast<PortMidiDriver*>(midiDriver);
if (!portMidiDriver || !portMidiDriver->getOutputStream() || !portMidiDriver->canOutput())
return;
int portIdx = seq->score()->midiPort(e.channel());
int chan = seq->score()->midiChannel(e.channel());
if (portIdx < 0 ) {
qDebug("Portaudio::putEvent: invalid port %d", portIdx);
return;
}
if (midiOutputTrace) {
int a = e.dataA();
int b = e.dataB();
qDebug("MidiOut<%d>: Portaudio: %02x %02x %02x, chan: %i", portIdx, e.type(), a, b, chan);
}
switch(e.type()) {
case ME_NOTEON:
case ME_NOTEOFF:
case ME_POLYAFTER:
case ME_CONTROLLER:
// Catch CTRL_PROGRAM and let other ME_CONTROLLER events to go
if (e.dataA() == CTRL_PROGRAM) {
// Convert CTRL_PROGRAM event to ME_PROGRAM
long msg = Pm_Message(ME_PROGRAM | chan, less128(e.dataB()), 0);
PmError error = Pm_WriteShort(portMidiDriver->getOutputStream(), seq->getCurrentMillisecondTimestampWithLatency(framePos), msg);
if (error != pmNoError) {
qDebug("Portaudio: error %d", error);
return;
}
break;
}
// fall through
2017-05-14 17:43:28 +02:00
case ME_PITCHBEND:
{
long msg = Pm_Message(e.type() | chan, less128(e.dataA()), less128(e.dataB()));
PmError error = Pm_WriteShort(portMidiDriver->getOutputStream(), seq->getCurrentMillisecondTimestampWithLatency(framePos), msg);
if (error != pmNoError) {
qDebug("Portaudio: error %d", error);
return;
}
}
break;
case ME_PROGRAM:
case ME_AFTERTOUCH:
{
long msg = Pm_Message(e.type() | chan, less128(e.dataA()), 0);
PmError error = Pm_WriteShort(portMidiDriver->getOutputStream(), seq->getCurrentMillisecondTimestampWithLatency(framePos), msg);
if (error != pmNoError) {
qDebug("Portaudio: error %d", error);
return;
}
}
break;
case ME_SONGPOS:
case ME_CLOCK:
case ME_START:
case ME_CONTINUE:
case ME_STOP:
qDebug("Portaudio: event type %x not supported", e.type());
break;
}
}
2018-04-02 01:35:20 +02:00
#endif
2017-05-14 17:43:28 +02:00
2012-05-26 14:49:10 +02:00
//---------------------------------------------------------
// currentApi
//---------------------------------------------------------
int Portaudio::currentApi() const
{
PaDeviceIndex idx = preferences.getInt(PREF_IO_PORTAUDIO_DEVICE);
2012-05-26 14:49:10 +02:00
if (idx < 0)
idx = Pa_GetDefaultOutputDevice();
for (int api = 0; api < Pa_GetHostApiCount(); ++api) {
const PaHostApiInfo* info = Pa_GetHostApiInfo(api);
if (info) {
for (int k = 0; k < info->deviceCount; ++k) {
PaDeviceIndex i = Pa_HostApiDeviceIndexToDeviceIndex(api, k);
if (i == idx)
return api;
}
}
}
2013-04-03 20:20:36 +02:00
qDebug("Portaudio: no current api found for device %d", idx);
2012-05-26 14:49:10 +02:00
return -1;
}
//---------------------------------------------------------
// currentDevice
//---------------------------------------------------------
int Portaudio::currentDevice() const
{
PaDeviceIndex idx = preferences.getInt(PREF_IO_PORTAUDIO_DEVICE);
2012-05-26 14:49:10 +02:00
if (idx < 0)
idx = Pa_GetDefaultOutputDevice();
for (int api = 0; api < Pa_GetHostApiCount(); ++api) {
const PaHostApiInfo* info = Pa_GetHostApiInfo(api);
if (info) {
for (int k = 0; k < info->deviceCount; ++k) {
PaDeviceIndex i = Pa_HostApiDeviceIndexToDeviceIndex(api, k);
if (i == idx)
return k;
}
}
}
2013-04-03 20:20:36 +02:00
qDebug("Portaudio: no current ApiDevice found for device %d", idx);
2012-05-26 14:49:10 +02:00
return -1;
}
2013-05-13 18:49:17 +02:00
}
2012-05-26 14:49:10 +02:00