You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
11 KiB
391 lines
11 KiB
/*
|
|
Rosegarden
|
|
A sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <bownie@bownie.com>
|
|
|
|
The moral right of the authors to claim authorship of this work
|
|
has been asserted.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "SoundDriver.h"
|
|
#include "WAVAudioFile.h"
|
|
#include "MappedStudio.h"
|
|
#include "AudioPlayQueue.h"
|
|
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#include <pthread.h> // for mutex
|
|
|
|
//#define DEBUG_SOUND_DRIVER 1
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
// ---------- SoundDriver -----------
|
|
//
|
|
|
|
|
|
SoundDriver::SoundDriver(MappedStudio *studio, const std::string &name):
|
|
m_name(name),
|
|
m_driverStatus(NO_DRIVER),
|
|
m_playStartPosition(0, 0),
|
|
m_startPlayback(false),
|
|
m_playing(false),
|
|
m_midiRecordDevice(0),
|
|
m_recordStatus(RECORD_OFF),
|
|
m_midiRunningId(MidiInstrumentBase),
|
|
m_audioRunningId(AudioInstrumentBase),
|
|
// m_audioQueueScavenger(4, 50),
|
|
m_audioQueue(0),
|
|
m_lowLatencyMode(true),
|
|
m_audioRecFileFormat(RIFFAudioFile::FLOAT),
|
|
m_studio(studio),
|
|
m_sequencerDataBlock(0),
|
|
m_externalTransport(0),
|
|
m_mmcStatus(TRANSPORT_OFF),
|
|
m_mtcStatus(TRANSPORT_OFF),
|
|
m_mmcId(0), // default MMC id of 0
|
|
m_midiClockEnabled(false),
|
|
m_midiClockInterval(0, 0),
|
|
m_midiClockSendTime(RealTime::zeroTime),
|
|
m_midiSongPositionPointer(0)
|
|
{
|
|
m_audioQueue = new AudioPlayQueue();
|
|
}
|
|
|
|
|
|
SoundDriver::~SoundDriver()
|
|
{
|
|
std::cout << "SoundDriver::~SoundDriver (exiting)" << std::endl;
|
|
delete m_audioQueue;
|
|
}
|
|
|
|
MappedInstrument*
|
|
SoundDriver::getMappedInstrument(InstrumentId id)
|
|
{
|
|
std::vector<MappedInstrument*>::const_iterator it;
|
|
|
|
for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
|
|
if ((*it)->getId() == id)
|
|
return (*it);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
SoundDriver::initialiseAudioQueue(const std::vector<MappedEvent> &events)
|
|
{
|
|
AudioPlayQueue *newQueue = new AudioPlayQueue();
|
|
|
|
for (std::vector<MappedEvent>::const_iterator i = events.begin();
|
|
i != events.end(); ++i) {
|
|
|
|
// Check for existence of file - if the sequencer has died
|
|
// and been restarted then we're not always loaded up with
|
|
// the audio file references we should have. In the future
|
|
// we could make this just get the gui to reload our files
|
|
// when (or before) this fails.
|
|
//
|
|
AudioFile *audioFile = getAudioFile(i->getAudioID());
|
|
|
|
if (audioFile) {
|
|
MappedAudioFader *fader =
|
|
dynamic_cast<MappedAudioFader*>
|
|
(getMappedStudio()->getAudioFader(i->getInstrument()));
|
|
|
|
if (!fader) {
|
|
std::cerr << "WARNING: SoundDriver::initialiseAudioQueue: no fader for audio instrument " << i->getInstrument() << std::endl;
|
|
continue;
|
|
}
|
|
|
|
unsigned int channels = fader->getPropertyList(
|
|
MappedAudioFader::Channels)[0].toInt();
|
|
|
|
//#define DEBUG_PLAYING_AUDIO
|
|
#ifdef DEBUG_PLAYING_AUDIO
|
|
|
|
std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << i->getEventTime() << ", time now " << getSequencerTime() << ", start marker " << i->getAudioStartMarker() << ", duration " << i->getDuration() << ", instrument " << i->getInstrument() << " channels " << channels << std::endl;
|
|
#endif
|
|
|
|
RealTime bufferLength = getAudioReadBufferLength();
|
|
int bufferFrames = RealTime::realTime2Frame
|
|
(bufferLength, getSampleRate());
|
|
|
|
PlayableAudioFile *paf = 0;
|
|
|
|
try {
|
|
paf = new PlayableAudioFile(i->getInstrument(),
|
|
audioFile,
|
|
i->getEventTime(),
|
|
i->getAudioStartMarker(),
|
|
i->getDuration(),
|
|
bufferFrames,
|
|
getSmallFileSize() * 1024,
|
|
channels,
|
|
getSampleRate());
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
|
|
paf->setRuntimeSegmentId(i->getRuntimeSegmentId());
|
|
|
|
if (i->isAutoFading()) {
|
|
paf->setAutoFade(true);
|
|
paf->setFadeInTime(i->getFadeInTime());
|
|
paf->setFadeOutTime(i->getFadeInTime());
|
|
|
|
//#define DEBUG_AUTOFADING
|
|
#ifdef DEBUG_AUTOFADING
|
|
|
|
std::cout << "SoundDriver::initialiseAudioQueue - "
|
|
<< "PlayableAudioFile is AUTOFADING - "
|
|
<< "in = " << i->getFadeInTime()
|
|
<< ", out = " << i->getFadeOutTime()
|
|
<< std::endl;
|
|
#endif
|
|
|
|
}
|
|
#ifdef DEBUG_AUTOFADING
|
|
else {
|
|
std::cout << "PlayableAudioFile has no AUTOFADE"
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
|
|
newQueue->addScheduled(paf);
|
|
} else {
|
|
std::cerr << "SoundDriver::initialiseAudioQueue - "
|
|
<< "can't find audio file reference for id " << i->getAudioID()
|
|
<< std::endl;
|
|
|
|
std::cerr << "SoundDriver::initialiseAudioQueue - "
|
|
<< "try reloading the current Rosegarden file"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
std::cout << "SoundDriver::initialiseAudioQueue -- new queue has "
|
|
<< newQueue->size() << " files"
|
|
<< std::endl;
|
|
|
|
if (newQueue->empty()) {
|
|
if (m_audioQueue->empty()) {
|
|
delete newQueue;
|
|
return ;
|
|
}
|
|
}
|
|
|
|
AudioPlayQueue *oldQueue = m_audioQueue;
|
|
m_audioQueue = newQueue;
|
|
if (oldQueue)
|
|
m_audioQueueScavenger.claim(oldQueue);
|
|
}
|
|
|
|
void
|
|
SoundDriver::clearAudioQueue()
|
|
{
|
|
std::cout << "SoundDriver::clearAudioQueue" << std::endl;
|
|
|
|
if (m_audioQueue->empty())
|
|
return ;
|
|
|
|
AudioPlayQueue *newQueue = new AudioPlayQueue();
|
|
AudioPlayQueue *oldQueue = m_audioQueue;
|
|
m_audioQueue = newQueue;
|
|
if (oldQueue)
|
|
m_audioQueueScavenger.claim(oldQueue);
|
|
}
|
|
void
|
|
SoundDriver::cancelAudioFile(MappedEvent *mE)
|
|
{
|
|
std::cout << "SoundDriver::cancelAudioFile" << std::endl;
|
|
|
|
if (!m_audioQueue)
|
|
return ;
|
|
|
|
// For now we only permit cancelling unscheduled files.
|
|
|
|
const AudioPlayQueue::FileList &files = m_audioQueue->getAllUnscheduledFiles();
|
|
for (AudioPlayQueue::FileList::const_iterator fi = files.begin();
|
|
fi != files.end(); ++fi) {
|
|
PlayableAudioFile *file = *fi;
|
|
if (mE->getRuntimeSegmentId() == -1) {
|
|
|
|
// ERROR? The comparison between file->getAudioFile()->getId() of type unsigned int
|
|
// and mE->getAudioID() of type int.
|
|
if (file->getInstrument() == mE->getInstrument() &&
|
|
int(file->getAudioFile()->getId() == mE->getAudioID())) {
|
|
file->cancel();
|
|
}
|
|
} else {
|
|
if (file->getRuntimeSegmentId() == mE->getRuntimeSegmentId() &&
|
|
file->getStartTime() == mE->getEventTime()) {
|
|
file->cancel();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const AudioPlayQueue *
|
|
SoundDriver::getAudioQueue() const
|
|
{
|
|
return m_audioQueue;
|
|
}
|
|
|
|
|
|
void
|
|
SoundDriver::setMappedInstrument(MappedInstrument *mI)
|
|
{
|
|
std::vector<MappedInstrument*>::iterator it;
|
|
|
|
// If we match then change existing entry
|
|
for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
|
|
if ((*it)->getId() == mI->getId()) {
|
|
(*it)->setChannel(mI->getChannel());
|
|
(*it)->setType(mI->getType());
|
|
delete mI;
|
|
return ;
|
|
}
|
|
}
|
|
|
|
// else create a new one
|
|
m_instruments.push_back(mI);
|
|
|
|
std::cout << "SoundDriver: setMappedInstrument() : "
|
|
<< "type = " << mI->getType() << " : "
|
|
<< "channel = " << (int)(mI->getChannel()) << " : "
|
|
<< "id = " << mI->getId() << std::endl;
|
|
|
|
}
|
|
|
|
unsigned int
|
|
SoundDriver::getDevices()
|
|
{
|
|
return m_devices.size();
|
|
}
|
|
|
|
MappedDevice
|
|
SoundDriver::getMappedDevice(DeviceId id)
|
|
{
|
|
MappedDevice retDevice;
|
|
std::vector<MappedInstrument*>::iterator it;
|
|
|
|
std::vector<MappedDevice*>::iterator dIt = m_devices.begin();
|
|
for (; dIt != m_devices.end(); dIt++) {
|
|
if ((*dIt)->getId() == id)
|
|
retDevice = **dIt;
|
|
}
|
|
|
|
// If we match then change existing entry
|
|
for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
|
|
if ((*it)->getDevice() == id)
|
|
retDevice.push_back(*it);
|
|
}
|
|
|
|
#ifdef DEBUG_SOUND_DRIVER
|
|
std::cout << "SoundDriver::getMappedDevice(" << id << ") - "
|
|
<< "name = \"" << retDevice.getName()
|
|
<< "\" type = " << retDevice.getType()
|
|
<< " direction = " << retDevice.getDirection()
|
|
<< " connection = \"" << retDevice.getConnection() << "\""
|
|
<< " recording = " << retDevice.isRecording()
|
|
<< std::endl;
|
|
#endif
|
|
|
|
return retDevice;
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
SoundDriver::addAudioFile(const std::string &fileName, unsigned int id)
|
|
{
|
|
AudioFile *ins = 0;
|
|
|
|
try {
|
|
ins = new WAVAudioFile(id, fileName, fileName);
|
|
ins->open();
|
|
m_audioFiles.push_back(ins);
|
|
|
|
// std::cout << "Sequencer::addAudioFile() = \"" << fileName << "\"" << std::endl;
|
|
|
|
return true;
|
|
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
std::cerr << "SoundDriver::addAudioFile: Failed to add audio file " << fileName << ": " << e.getMessage() << std::endl;
|
|
delete ins;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
SoundDriver::removeAudioFile(unsigned int id)
|
|
{
|
|
std::vector<AudioFile*>::iterator it;
|
|
for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++) {
|
|
if ((*it)->getId() == id) {
|
|
std::cout << "Sequencer::removeAudioFile() = \"" <<
|
|
(*it)->getFilename() << "\"" << std::endl;
|
|
|
|
delete (*it);
|
|
m_audioFiles.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
AudioFile*
|
|
SoundDriver::getAudioFile(unsigned int id)
|
|
{
|
|
std::vector<AudioFile*>::iterator it;
|
|
for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++) {
|
|
if ((*it)->getId() == id)
|
|
return *it;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
SoundDriver::clearAudioFiles()
|
|
{
|
|
// std::cout << "SoundDriver::clearAudioFiles() - clearing down audio files"
|
|
// << std::endl;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++)
|
|
delete(*it);
|
|
|
|
m_audioFiles.erase(m_audioFiles.begin(), m_audioFiles.end());
|
|
}
|
|
|
|
void
|
|
SoundDriver::sleep(const RealTime &rt)
|
|
{
|
|
// The usleep man page says it's deprecated and we should use
|
|
// nanosleep. And that's what we did. But it seems quite a few
|
|
// people don't have nanosleep, so we're reverting to usleep.
|
|
|
|
unsigned long usec = rt.sec * 1000000 + rt.usec();
|
|
usleep(usec);
|
|
}
|
|
|
|
|
|
}
|
|
|