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.
1849 lines
52 KiB
1849 lines
52 KiB
/*
|
|
Rosegarden
|
|
A MIDI and audio 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 <richard.bown@ferventsoftware.com>
|
|
|
|
The moral rights of Guillaume Laurent, Chris Cannam, and Richard
|
|
Bown to claim authorship of this work have been asserted.
|
|
|
|
Other copyrights also apply to some parts of this work. Please
|
|
see the AUTHORS file and individual file headers for details.
|
|
|
|
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 "RosegardenSequencerApp.h"
|
|
#include <tdeapplication.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include <iostream>
|
|
|
|
#include <tdelocale.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <dcopclient.h>
|
|
#include <tqdatetime.h>
|
|
#include <tqstring.h>
|
|
#include <tqdir.h>
|
|
#include <tqbuffer.h>
|
|
#include <tqvaluevector.h>
|
|
|
|
#include "misc/Debug.h"
|
|
#include "ControlBlockMmapper.h"
|
|
#include "MmappedSegment.h"
|
|
#include "gui/application/RosegardenDCOP.h"
|
|
#include "sound/ControlBlock.h"
|
|
#include "sound/SoundDriver.h"
|
|
#include "sound/SoundDriverFactory.h"
|
|
#include "sound/MappedInstrument.h"
|
|
#include "base/Profiler.h"
|
|
#include "sound/PluginFactory.h"
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
// The default latency and read-ahead values are actually sent
|
|
// down from the GUI every time playback or recording starts
|
|
// so the local values are kind of meaningless.
|
|
//
|
|
//
|
|
RosegardenSequencerApp::RosegardenSequencerApp() :
|
|
DCOPObject("RosegardenSequencerIface"),
|
|
m_driver(0),
|
|
m_transportStatus(STOPPED),
|
|
m_songPosition(0, 0),
|
|
m_lastFetchSongPosition(0, 0),
|
|
m_readAhead(0, 80000000), // default value
|
|
m_audioMix(0, 60000000), // default value
|
|
m_audioRead(0, 100000000), // default value
|
|
m_audioWrite(0, 200000000), // default value
|
|
m_smallFileSize(128),
|
|
m_loopStart(0, 0),
|
|
m_loopEnd(0, 0),
|
|
m_studio(new MappedStudio()),
|
|
m_segmentFilesPath(TDEGlobal::dirs()->resourceDirs("tmp").last()),
|
|
m_metaIterator(0),
|
|
m_controlBlockMmapper(0),
|
|
m_transportToken(1),
|
|
m_isEndOfCompReached(false)
|
|
{
|
|
SEQUENCER_DEBUG << "Registering with DCOP server" << endl;
|
|
|
|
// Without DCOP we are nothing
|
|
TQCString realAppId = kapp->dcopClient()->registerAs(kapp->name(), false);
|
|
|
|
if (realAppId.isNull()) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer cannot register "
|
|
<< "with DCOP server" << endl;
|
|
close();
|
|
}
|
|
|
|
// Initialise the MappedStudio
|
|
//
|
|
initialiseStudio();
|
|
|
|
// Creating this object also initialises the Rosegarden ALSA/JACK
|
|
// interface for both playback and recording. MappedStudio
|
|
// audio faders are also created.
|
|
//
|
|
m_driver = SoundDriverFactory::createDriver(m_studio);
|
|
m_studio->setSoundDriver(m_driver);
|
|
|
|
if (!m_driver) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer object could not be allocated"
|
|
<< endl;
|
|
close();
|
|
}
|
|
|
|
m_driver->setAudioBufferSizes(m_audioMix, m_audioRead, m_audioWrite,
|
|
m_smallFileSize);
|
|
|
|
m_driver->setSequencerDataBlock(m_sequencerMapper.getSequencerDataBlock());
|
|
m_driver->setExternalTransportControl(this);
|
|
|
|
// Check for new clients every so often
|
|
//
|
|
m_newClientTimer = new TQTimer(this);
|
|
connect(m_newClientTimer, TQ_SIGNAL(timeout()),
|
|
this, TQ_SLOT(slotCheckForNewClients()));
|
|
|
|
m_newClientTimer->start(3000); // every 3 seconds
|
|
}
|
|
|
|
RosegardenSequencerApp::~RosegardenSequencerApp()
|
|
{
|
|
SEQUENCER_DEBUG << "RosegardenSequencer - shutting down" << endl;
|
|
m_driver->shutdown();
|
|
delete m_studio;
|
|
delete m_driver;
|
|
delete m_controlBlockMmapper;
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::quit()
|
|
{
|
|
std::cerr << "RosegardenSequencerApp::quit()" << std::endl;
|
|
|
|
close();
|
|
|
|
// and break out of the loop next time around
|
|
m_transportStatus = QUIT;
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::stop()
|
|
{
|
|
// set our state at this level to STOPPING (pending any
|
|
// unfinished NOTES)
|
|
m_transportStatus = STOPPING;
|
|
|
|
// report
|
|
//
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::stop() - stopping" << endl;
|
|
|
|
// process pending NOTE OFFs and stop the Sequencer
|
|
m_driver->stopPlayback();
|
|
|
|
// the Sequencer doesn't need to know these once
|
|
// we've stopped.
|
|
//
|
|
m_songPosition.sec = 0;
|
|
m_songPosition.nsec = 0;
|
|
m_lastFetchSongPosition.sec = 0;
|
|
m_lastFetchSongPosition.nsec = 0;
|
|
|
|
cleanupMmapData();
|
|
|
|
Profiles::getInstance()->dump();
|
|
|
|
incrementTransportToken();
|
|
}
|
|
|
|
// Get a slice of events from the GUI
|
|
//
|
|
void
|
|
RosegardenSequencerApp::fetchEvents(MappedComposition &composition,
|
|
const RealTime &start,
|
|
const RealTime &end,
|
|
bool firstFetch)
|
|
{
|
|
// Always return nothing if we're stopped
|
|
//
|
|
if ( m_transportStatus == STOPPED || m_transportStatus == STOPPING )
|
|
return ;
|
|
|
|
// If we're looping then we should get as much of the rest of
|
|
// the right hand of the loop as possible and also events from
|
|
// the beginning of the loop. We can do this in two fetches.
|
|
// Make sure that we delete all returned pointers when we've
|
|
// finished with them.
|
|
//
|
|
//
|
|
/*
|
|
if (isLooping() == true && end >= m_loopEnd)
|
|
{
|
|
RealTime loopOverlap = end - m_loopEnd;
|
|
|
|
MappedComposition *endLoop = 0;
|
|
|
|
if (m_loopEnd > start) {
|
|
endLoop = getSlice(start, m_loopEnd, firstFetch);
|
|
}
|
|
|
|
if (loopOverlap > RealTime::zeroTime) {
|
|
|
|
MappedComposition *beginLoop =
|
|
getSlice(m_loopStart, m_loopStart + loopOverlap, true);
|
|
|
|
// move the start time of the begin section one loop width
|
|
// into the future and ensure that we keep the clocks level
|
|
// until this time has passed
|
|
//
|
|
beginLoop->moveStartTime(m_loopEnd - m_loopStart);
|
|
|
|
if (endLoop) {
|
|
(*endLoop) = (*endLoop) + (*beginLoop);
|
|
delete beginLoop;
|
|
} else {
|
|
endLoop = beginLoop;
|
|
}
|
|
}
|
|
|
|
if (endLoop) return endLoop;
|
|
else return new MappedComposition();
|
|
}
|
|
else
|
|
*/
|
|
getSlice(composition, start, end, firstFetch);
|
|
applyLatencyCompensation(composition);
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::getSlice(MappedComposition &composition,
|
|
const RealTime &start,
|
|
const RealTime &end,
|
|
bool firstFetch)
|
|
{
|
|
// SEQUENCER_DEBUG << "RosegardenSequencerApp::getSlice (" << start << " -> " << end << ", " << firstFetch << ")" << endl;
|
|
|
|
if (firstFetch || (start < m_lastStartTime)) {
|
|
SEQUENCER_DEBUG << "[calling jumpToTime on start]" << endl;
|
|
m_metaIterator->jumpToTime(start);
|
|
}
|
|
|
|
(void)m_metaIterator->fillCompositionWithEventsUntil
|
|
(firstFetch, &composition, start, end);
|
|
|
|
// setEndOfCompReached(eventsRemaining); // don't do that, it breaks recording because
|
|
// playing stops right after it starts.
|
|
|
|
m_lastStartTime = start;
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::applyLatencyCompensation(MappedComposition &composition)
|
|
{
|
|
RealTime maxLatency = m_driver->getMaximumPlayLatency();
|
|
if (maxLatency == RealTime::zeroTime)
|
|
return ;
|
|
|
|
for (MappedComposition::iterator i = composition.begin();
|
|
i != composition.end(); ++i) {
|
|
|
|
RealTime instrumentLatency =
|
|
m_driver->getInstrumentPlayLatency((*i)->getInstrument());
|
|
|
|
// std::cerr << "RosegardenSequencerApp::applyLatencyCompensation: maxLatency " << maxLatency << ", instrumentLatency " << instrumentLatency << ", moving " << (*i)->getEventTime() << " to " << (*i)->getEventTime() + maxLatency - instrumentLatency << std::endl;
|
|
|
|
(*i)->setEventTime((*i)->getEventTime() +
|
|
maxLatency - instrumentLatency);
|
|
}
|
|
}
|
|
|
|
|
|
// The first fetch of events from the core/ and initialisation for
|
|
// this session of playback. We fetch up to m_readAhead ahead at
|
|
// first at then top up at each slice.
|
|
//
|
|
bool
|
|
RosegardenSequencerApp::startPlaying()
|
|
{
|
|
// Fetch up to m_readHead microseconds worth of events
|
|
//
|
|
m_lastFetchSongPosition = m_songPosition + m_readAhead;
|
|
|
|
// This will reset the Sequencer's internal clock
|
|
// ready for new playback
|
|
m_driver->initialisePlayback(m_songPosition);
|
|
|
|
m_mC.clear();
|
|
fetchEvents(m_mC, m_songPosition, m_songPosition + m_readAhead, true);
|
|
|
|
// process whether we need to or not as this also processes
|
|
// the audio queue for us
|
|
//
|
|
m_driver->processEventsOut(m_mC, m_songPosition, m_songPosition + m_readAhead);
|
|
|
|
std::vector<MappedEvent> audioEvents;
|
|
m_metaIterator->getAudioEvents(audioEvents);
|
|
m_driver->initialiseAudioQueue(audioEvents);
|
|
|
|
// SEQUENCER_DEBUG << "RosegardenSequencerApp::startPlaying: pausing to simulate high-load environment" << endl;
|
|
// ::sleep(2);
|
|
|
|
// and only now do we signal to start the clock
|
|
//
|
|
m_driver->startClocks();
|
|
|
|
incrementTransportToken();
|
|
|
|
return true; // !isEndOfCompReached();
|
|
}
|
|
|
|
bool
|
|
RosegardenSequencerApp::keepPlaying()
|
|
{
|
|
Profiler profiler("RosegardenSequencerApp::keepPlaying");
|
|
|
|
m_mC.clear();
|
|
|
|
RealTime fetchEnd = m_songPosition + m_readAhead;
|
|
if (isLooping() && fetchEnd >= m_loopEnd) {
|
|
fetchEnd = m_loopEnd - RealTime(0, 1);
|
|
}
|
|
if (fetchEnd > m_lastFetchSongPosition) {
|
|
fetchEvents(m_mC, m_lastFetchSongPosition, fetchEnd, false);
|
|
}
|
|
|
|
// Again, process whether we need to or not to keep
|
|
// the Sequencer up-to-date with audio events
|
|
//
|
|
m_driver->processEventsOut(m_mC, m_lastFetchSongPosition, fetchEnd);
|
|
|
|
if (fetchEnd > m_lastFetchSongPosition) {
|
|
m_lastFetchSongPosition = fetchEnd;
|
|
}
|
|
|
|
return true; // !isEndOfCompReached(); - until we sort this out, we don't stop at end of comp.
|
|
}
|
|
|
|
// Return current Sequencer time in GUI compatible terms
|
|
//
|
|
void
|
|
RosegardenSequencerApp::updateClocks()
|
|
{
|
|
Profiler profiler("RosegardenSequencerApp::updateClocks");
|
|
|
|
m_driver->runTasks();
|
|
|
|
checkExternalTransport();
|
|
|
|
//SEQUENCER_DEBUG << "RosegardenSequencerApp::updateClocks" << endl;
|
|
|
|
// If we're not playing etc. then that's all we need to do
|
|
//
|
|
if (m_transportStatus != PLAYING &&
|
|
m_transportStatus != RECORDING)
|
|
return ;
|
|
|
|
RealTime newPosition = m_driver->getSequencerTime();
|
|
|
|
// Go around the loop if we've reached the end
|
|
//
|
|
if (isLooping() && newPosition >= m_loopEnd) {
|
|
|
|
RealTime oldPosition = m_songPosition;
|
|
|
|
// Remove the loop width from the song position and send
|
|
// this position to the GUI
|
|
//
|
|
newPosition = m_songPosition = m_lastFetchSongPosition = m_loopStart;
|
|
|
|
m_driver->stopClocks();
|
|
|
|
// Reset playback using this jump
|
|
//
|
|
m_driver->resetPlayback(oldPosition, m_songPosition);
|
|
|
|
m_mC.clear();
|
|
fetchEvents(m_mC, m_songPosition, m_songPosition + m_readAhead, true);
|
|
|
|
m_driver->processEventsOut(m_mC, m_songPosition, m_songPosition + m_readAhead);
|
|
|
|
m_driver->startClocks();
|
|
} else {
|
|
m_songPosition = newPosition;
|
|
|
|
if (m_songPosition <= m_driver->getStartPosition())
|
|
newPosition = m_driver->getStartPosition();
|
|
}
|
|
|
|
RealTime maxLatency = m_driver->getMaximumPlayLatency();
|
|
if (maxLatency != RealTime::zeroTime) {
|
|
// std::cerr << "RosegardenSequencerApp::updateClocks: latency compensation moving " << newPosition << " to " << newPosition - maxLatency << std::endl;
|
|
newPosition = newPosition - maxLatency;
|
|
}
|
|
|
|
// Remap the position pointer
|
|
//
|
|
m_sequencerMapper.updatePositionPointer(newPosition);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::notifySequencerStatus()
|
|
{
|
|
TQByteArray data, replyData;
|
|
TQCString replyType;
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
|
|
arg << (int)m_transportStatus;
|
|
|
|
if (!kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"notifySequencerStatus(int)",
|
|
data)) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::notifySequencerStatus()"
|
|
<< " - can't send to RosegardenGUI client"
|
|
<< endl;
|
|
|
|
// Stop the sequencer
|
|
//
|
|
stop();
|
|
}
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::sleep(const RealTime &rt)
|
|
{
|
|
m_driver->sleep(rt);
|
|
}
|
|
|
|
|
|
// Sets the Sequencer object and this object to the new time
|
|
// from where playback can continue.
|
|
//
|
|
void
|
|
RosegardenSequencerApp::jumpTo(long posSec, long posNsec)
|
|
{
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::jumpTo(" << posSec << ", " << posNsec << ")\n";
|
|
|
|
if (posSec < 0 && posNsec < 0)
|
|
return ;
|
|
|
|
m_driver->stopClocks();
|
|
|
|
RealTime oldPosition = m_songPosition;
|
|
|
|
m_songPosition = m_lastFetchSongPosition = RealTime(posSec, posNsec);
|
|
|
|
if (m_sequencerMapper.getSequencerDataBlock()) {
|
|
m_sequencerMapper.getSequencerDataBlock()->setPositionPointer
|
|
(m_songPosition);
|
|
}
|
|
|
|
m_driver->resetPlayback(oldPosition, m_songPosition);
|
|
|
|
if (m_driver->isPlaying()) {
|
|
|
|
// Now prebuffer as in startPlaying:
|
|
|
|
m_mC.clear();
|
|
fetchEvents(m_mC, m_songPosition, m_songPosition + m_readAhead, true);
|
|
|
|
// process whether we need to or not as this also processes
|
|
// the audio queue for us
|
|
//
|
|
m_driver->processEventsOut(m_mC, m_songPosition, m_songPosition + m_readAhead);
|
|
}
|
|
|
|
incrementTransportToken();
|
|
|
|
// SEQUENCER_DEBUG << "RosegardenSequencerApp::jumpTo: pausing to simulate high-load environment" << endl;
|
|
// ::sleep(1);
|
|
|
|
m_driver->startClocks();
|
|
|
|
return ;
|
|
}
|
|
|
|
// Send the last recorded MIDI block
|
|
//
|
|
void
|
|
RosegardenSequencerApp::processRecordedMidi()
|
|
{
|
|
MappedComposition *mC = m_driver->getMappedComposition();
|
|
|
|
if (mC->empty() || !m_controlBlockMmapper)
|
|
return ;
|
|
|
|
applyFiltering(mC, m_controlBlockMmapper->getRecordFilter(), false);
|
|
m_sequencerMapper.updateRecordingBuffer(mC);
|
|
|
|
if (m_controlBlockMmapper->isMidiRoutingEnabled()) {
|
|
applyFiltering(mC, m_controlBlockMmapper->getThruFilter(), true);
|
|
routeEvents(mC, false);
|
|
}
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::routeEvents(MappedComposition *mC, bool useSelectedTrack)
|
|
{
|
|
InstrumentId instrumentId;
|
|
|
|
if (useSelectedTrack) {
|
|
instrumentId = m_controlBlockMmapper->getInstrumentForTrack
|
|
(m_controlBlockMmapper->getSelectedTrack());
|
|
for (MappedComposition::iterator i = mC->begin();
|
|
i != mC->end(); ++i) {
|
|
(*i)->setInstrument(instrumentId);
|
|
}
|
|
} else {
|
|
for (MappedComposition::iterator i = mC->begin();
|
|
i != mC->end(); ++i) {
|
|
instrumentId = m_controlBlockMmapper->getInstrumentForEvent
|
|
((*i)->getRecordedDevice(), (*i)->getRecordedChannel());
|
|
(*i)->setInstrument(instrumentId);
|
|
}
|
|
}
|
|
m_driver->processEventsOut(*mC);
|
|
}
|
|
|
|
// Send an update
|
|
//
|
|
void
|
|
RosegardenSequencerApp::processRecordedAudio()
|
|
{
|
|
// Nothing to do here: the recording time is sent back to the GUI
|
|
// in the sequencer mapper as a normal case.
|
|
}
|
|
|
|
|
|
// This method is called during STOPPED or PLAYING operations
|
|
// to mop up any async (unexpected) incoming MIDI or Audio events
|
|
// and forward them to the GUI for display
|
|
//
|
|
void
|
|
RosegardenSequencerApp::processAsynchronousEvents()
|
|
{
|
|
if (!m_controlBlockMmapper) {
|
|
|
|
// If the control block mmapper doesn't exist, we'll just
|
|
// return here. But we want to ensure we don't check again
|
|
// immediately, because we're probably waiting for the GUI to
|
|
// start up.
|
|
|
|
static bool lastChecked = false;
|
|
static struct timeval lastCheckedAt;
|
|
|
|
struct timeval tv;
|
|
(void)gettimeofday(&tv, 0);
|
|
|
|
if (lastChecked &&
|
|
tv.tv_sec == lastCheckedAt.tv_sec) {
|
|
lastCheckedAt = tv;
|
|
return ;
|
|
}
|
|
|
|
lastChecked = true;
|
|
lastCheckedAt = tv;
|
|
|
|
try {
|
|
m_controlBlockMmapper = new ControlBlockMmapper(TDEGlobal::dirs()->resourceDirs("tmp").last()
|
|
+ "/rosegarden_control_block");
|
|
} catch (Exception e) {
|
|
// Assume that the control block simply hasn't been
|
|
// created yet because the GUI's still starting up.
|
|
// If there's a real problem with the mmapper, it
|
|
// will show up in play() instead.
|
|
return ;
|
|
}
|
|
m_sequencerMapper.setControlBlock(m_controlBlockMmapper->getControlBlock());
|
|
}
|
|
|
|
MappedComposition *mC = m_driver->getMappedComposition();
|
|
|
|
if (mC->empty()) {
|
|
m_driver->processPending();
|
|
return ;
|
|
}
|
|
|
|
// std::cerr << "processAsynchronousEvents: have " << mC->size() << " events" << std::endl;
|
|
|
|
TQByteArray data;
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
arg << mC;
|
|
|
|
if (m_controlBlockMmapper->isMidiRoutingEnabled()) {
|
|
applyFiltering(mC, m_controlBlockMmapper->getThruFilter(), true);
|
|
routeEvents(mC, true);
|
|
}
|
|
|
|
// std::cerr << "processAsynchronousEvents: sent " << mC->size() << " events" << std::endl;
|
|
|
|
if (!kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"processAsynchronousMidi(MappedComposition)", data)) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::processAsynchronousEvents() - "
|
|
<< "can't call RosegardenGUI client" << endl;
|
|
|
|
// Stop the sequencer so we can see if we can try again later
|
|
//
|
|
stop();
|
|
}
|
|
|
|
// Process any pending events (Note Offs or Audio) as part of
|
|
// same procedure.
|
|
//
|
|
m_driver->processPending();
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::applyFiltering(MappedComposition *mC,
|
|
MidiFilter filter,
|
|
bool filterControlDevice)
|
|
{
|
|
for (MappedComposition::iterator i = mC->begin();
|
|
i != mC->end(); ) { // increment in loop
|
|
MappedComposition::iterator j = i;
|
|
++j;
|
|
if (((*i)->getType() & filter) ||
|
|
(filterControlDevice && ((*i)->getRecordedDevice() ==
|
|
Device::CONTROL_DEVICE))) {
|
|
mC->erase(i);
|
|
}
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
RosegardenSequencerApp::record(const RealTime &time,
|
|
const RealTime &readAhead,
|
|
const RealTime &audioMix,
|
|
const RealTime &audioRead,
|
|
const RealTime &audioWrite,
|
|
long smallFileSize,
|
|
long recordMode)
|
|
{
|
|
TransportStatus localRecordMode = (TransportStatus) recordMode;
|
|
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::record - recordMode is " << recordMode << ", transport status is " << m_transportStatus << endl;
|
|
|
|
// punch in recording
|
|
if (m_transportStatus == PLAYING) {
|
|
if (localRecordMode == STARTING_TO_RECORD) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::record: punching in" << endl;
|
|
localRecordMode = RECORDING; // no need to start playback
|
|
}
|
|
}
|
|
|
|
// For audio recording we need to retrieve audio
|
|
// file names from the GUI
|
|
//
|
|
if (localRecordMode == STARTING_TO_RECORD ||
|
|
localRecordMode == RECORDING) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::record()"
|
|
<< " - starting to record" << endl;
|
|
|
|
TQValueVector<InstrumentId> armedInstruments;
|
|
TQValueVector<TQString> audioFileNames;
|
|
|
|
{
|
|
TQByteArray data, replyData;
|
|
TQCString replyType;
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
|
|
if (!kapp->dcopClient()->call(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"getArmedInstruments()",
|
|
data, replyType, replyData, true)) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::record()"
|
|
<< " - can't call RosegardenGUI client for getArmedInstruments"
|
|
<< endl;
|
|
}
|
|
|
|
TQDataStream reply(replyData, IO_ReadOnly);
|
|
if (replyType == "TQValueVector<InstrumentId>") {
|
|
reply >> armedInstruments;
|
|
} else {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::record() - "
|
|
<< "unrecognised type returned for getArmedInstruments" << endl;
|
|
}
|
|
}
|
|
|
|
TQValueVector<InstrumentId> audioInstruments;
|
|
|
|
for (unsigned int i = 0; i < armedInstruments.size(); ++i) {
|
|
if (armedInstruments[i] >= AudioInstrumentBase &&
|
|
armedInstruments[i] < MidiInstrumentBase) {
|
|
audioInstruments.push_back(armedInstruments[i]);
|
|
}
|
|
}
|
|
|
|
if (audioInstruments.size() > 0) {
|
|
|
|
TQByteArray data, replyData;
|
|
TQCString replyType;
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
|
|
arg << audioInstruments;
|
|
|
|
if (!kapp->dcopClient()->call(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"createRecordAudioFiles(TQValueVector<InstrumentId>)",
|
|
data, replyType, replyData, true)) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::record()"
|
|
<< " - can't call RosegardenGUI client for createNewAudioFiles"
|
|
<< endl;
|
|
}
|
|
|
|
TQDataStream reply(replyData, IO_ReadOnly);
|
|
if (replyType == "TQValueVector<TQString>") {
|
|
reply >> audioFileNames;
|
|
} else {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::record() - "
|
|
<< "unrecognised type returned for createNewAudioFiles" << endl;
|
|
}
|
|
|
|
if (audioFileNames.size() != audioInstruments.size()) {
|
|
std::cerr << "ERROR: RosegardenSequencer::record(): Failed to create correct number of audio files (wanted " << audioInstruments.size() << ", got " << audioFileNames.size() << ")" << std::endl;
|
|
stop();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
std::vector<InstrumentId> armedInstrumentsVec;
|
|
std::vector<TQString> audioFileNamesVec;
|
|
for (int i = 0; i < armedInstruments.size(); ++i) {
|
|
armedInstrumentsVec.push_back(armedInstruments[i]);
|
|
}
|
|
for (int i = 0; i < audioFileNames.size(); ++i) {
|
|
audioFileNamesVec.push_back(audioFileNames[i]);
|
|
}
|
|
|
|
// Get the Sequencer to prepare itself for recording - if
|
|
// this fails we stop.
|
|
//
|
|
if (m_driver->record(RECORD_ON,
|
|
&armedInstrumentsVec,
|
|
&audioFileNamesVec) == false) {
|
|
stop();
|
|
return 0;
|
|
}
|
|
} else {
|
|
// unrecognised type - return a problem
|
|
return 0;
|
|
}
|
|
|
|
// Now set the local transport status to the record mode
|
|
//
|
|
//
|
|
m_transportStatus = localRecordMode;
|
|
|
|
if (localRecordMode == RECORDING) { // punch in
|
|
return 1;
|
|
} else {
|
|
|
|
// Ensure that playback is initialised
|
|
//
|
|
m_driver->initialisePlayback(m_songPosition);
|
|
|
|
return play(time, readAhead, audioMix, audioRead, audioWrite, smallFileSize);
|
|
}
|
|
}
|
|
|
|
// We receive a starting time from the GUI which we use as the
|
|
// basis of our first fetch of events from the GUI core. Assuming
|
|
// this works we set our internal state to PLAYING and go ahead
|
|
// and play the piece until we get a signal to stop.
|
|
//
|
|
// DCOP wants us to use an int as a return type instead of a bool.
|
|
//
|
|
int
|
|
RosegardenSequencerApp::play(const RealTime &time,
|
|
const RealTime &readAhead,
|
|
const RealTime &audioMix,
|
|
const RealTime &audioRead,
|
|
const RealTime &audioWrite,
|
|
long smallFileSize)
|
|
{
|
|
if (m_transportStatus == PLAYING ||
|
|
m_transportStatus == STARTING_TO_PLAY)
|
|
return true;
|
|
|
|
// Check for record toggle (punch out)
|
|
//
|
|
if (m_transportStatus == RECORDING) {
|
|
m_transportStatus = PLAYING;
|
|
return punchOut();
|
|
}
|
|
|
|
// To play from the given song position sets up the internal
|
|
// play state to "STARTING_TO_PLAY" which is then caught in
|
|
// the main event loop
|
|
//
|
|
m_songPosition = time;
|
|
|
|
if (m_sequencerMapper.getSequencerDataBlock()) {
|
|
m_sequencerMapper.getSequencerDataBlock()->setPositionPointer
|
|
(m_songPosition);
|
|
}
|
|
|
|
if (m_transportStatus != RECORDING &&
|
|
m_transportStatus != STARTING_TO_RECORD) {
|
|
m_transportStatus = STARTING_TO_PLAY;
|
|
}
|
|
|
|
m_driver->stopClocks();
|
|
|
|
// Set up buffer size
|
|
//
|
|
m_readAhead = readAhead;
|
|
if (m_readAhead == RealTime::zeroTime)
|
|
m_readAhead.sec = 1;
|
|
|
|
m_audioMix = audioMix;
|
|
m_audioRead = audioRead;
|
|
m_audioWrite = audioWrite;
|
|
m_smallFileSize = smallFileSize;
|
|
|
|
m_driver->setAudioBufferSizes(m_audioMix, m_audioRead, m_audioWrite,
|
|
m_smallFileSize);
|
|
|
|
cleanupMmapData();
|
|
|
|
// Map all segments
|
|
//
|
|
TQDir segmentsDir(m_segmentFilesPath, "segment_*");
|
|
for (unsigned int i = 0; i < segmentsDir.count(); ++i) {
|
|
mmapSegment(m_segmentFilesPath + "/" + segmentsDir[i]);
|
|
}
|
|
|
|
TQString tmpDir = TDEGlobal::dirs()->resourceDirs("tmp").last();
|
|
|
|
// Map metronome
|
|
//
|
|
TQString metronomeFileName = tmpDir + "/rosegarden_metronome";
|
|
TQFileInfo metronomeFileInfo(metronomeFileName);
|
|
if (metronomeFileInfo.exists())
|
|
mmapSegment(metronomeFileName);
|
|
else
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no metronome found\n";
|
|
|
|
// Map tempo segment
|
|
//
|
|
TQString tempoSegmentFileName = tmpDir + "/rosegarden_tempo";
|
|
TQFileInfo tempoSegmentFileInfo(tempoSegmentFileName);
|
|
if (tempoSegmentFileInfo.exists())
|
|
mmapSegment(tempoSegmentFileName);
|
|
else
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no tempo segment found\n";
|
|
|
|
// Map time sig segment
|
|
//
|
|
TQString timeSigSegmentFileName = tmpDir + "/rosegarden_timesig";
|
|
TQFileInfo timeSigSegmentFileInfo(timeSigSegmentFileName);
|
|
if (timeSigSegmentFileInfo.exists())
|
|
mmapSegment(timeSigSegmentFileName);
|
|
else
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no time sig segment found\n";
|
|
|
|
// Map control block if necessary
|
|
//
|
|
if (!m_controlBlockMmapper) {
|
|
m_controlBlockMmapper = new ControlBlockMmapper(tmpDir + "/rosegarden_control_block");
|
|
m_sequencerMapper.setControlBlock(m_controlBlockMmapper->getControlBlock());
|
|
}
|
|
|
|
initMetaIterator();
|
|
|
|
// report
|
|
//
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - starting to play\n";
|
|
|
|
// Test bits
|
|
// m_metaIterator = new MmappedSegmentsMetaIterator(m_mmappedSegments);
|
|
// MappedComposition testCompo;
|
|
// m_metaIterator->fillCompositionWithEventsUntil(&testCompo,
|
|
// RealTime(2,0));
|
|
|
|
// dumpFirstSegment();
|
|
|
|
// keep it simple
|
|
return true;
|
|
}
|
|
|
|
int
|
|
RosegardenSequencerApp::punchOut()
|
|
{
|
|
// Check for record toggle (punch out)
|
|
//
|
|
if (m_transportStatus == RECORDING) {
|
|
m_driver->punchOut();
|
|
m_transportStatus = PLAYING;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MmappedSegment* RosegardenSequencerApp::mmapSegment(const TQString& file)
|
|
{
|
|
MmappedSegment* m = 0;
|
|
|
|
try {
|
|
m = new MmappedSegment(file);
|
|
} catch (Exception e) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::mmapSegment() - couldn't map file " << file
|
|
<< " : " << e.getMessage().c_str() << endl;
|
|
return 0;
|
|
}
|
|
|
|
|
|
m_mmappedSegments[file] = m;
|
|
return m;
|
|
}
|
|
|
|
void RosegardenSequencerApp::initMetaIterator()
|
|
{
|
|
delete m_metaIterator;
|
|
m_metaIterator = new MmappedSegmentsMetaIterator(m_mmappedSegments, m_controlBlockMmapper);
|
|
}
|
|
|
|
void RosegardenSequencerApp::cleanupMmapData()
|
|
{
|
|
for (MmappedSegmentsMetaIterator::mmappedsegments::iterator i =
|
|
m_mmappedSegments.begin(); i != m_mmappedSegments.end(); ++i)
|
|
delete i->second;
|
|
|
|
m_mmappedSegments.clear();
|
|
|
|
delete m_metaIterator;
|
|
m_metaIterator = 0;
|
|
}
|
|
|
|
void RosegardenSequencerApp::remapSegment(const TQString& filename, size_t newSize)
|
|
{
|
|
if (m_transportStatus != PLAYING)
|
|
return ;
|
|
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::remapSegment(" << filename << ")\n";
|
|
|
|
MmappedSegment* m = m_mmappedSegments[filename];
|
|
if (m->remap(newSize) && m_metaIterator)
|
|
m_metaIterator->resetIteratorForSegment(filename);
|
|
}
|
|
|
|
void RosegardenSequencerApp::addSegment(const TQString& filename)
|
|
{
|
|
if (m_transportStatus != PLAYING)
|
|
return ;
|
|
|
|
SEQUENCER_DEBUG << "MmappedSegment::addSegment(" << filename << ")\n";
|
|
|
|
MmappedSegment* m = mmapSegment(filename);
|
|
|
|
if (m_metaIterator)
|
|
m_metaIterator->addSegment(m);
|
|
}
|
|
|
|
void RosegardenSequencerApp::deleteSegment(const TQString& filename)
|
|
{
|
|
if (m_transportStatus != PLAYING)
|
|
return ;
|
|
|
|
SEQUENCER_DEBUG << "MmappedSegment::deleteSegment(" << filename << ")\n";
|
|
|
|
MmappedSegment* m = m_mmappedSegments[filename];
|
|
|
|
if (m_metaIterator)
|
|
m_metaIterator->deleteSegment(m);
|
|
|
|
delete m;
|
|
|
|
// #932415
|
|
m_mmappedSegments.erase(filename);
|
|
}
|
|
|
|
void RosegardenSequencerApp::closeAllSegments()
|
|
{
|
|
SEQUENCER_DEBUG << "MmappedSegment::closeAllSegments()\n";
|
|
|
|
for (MmappedSegmentsMetaIterator::mmappedsegments::iterator
|
|
i = m_mmappedSegments.begin();
|
|
i != m_mmappedSegments.end(); ++i) {
|
|
if (m_metaIterator)
|
|
m_metaIterator->deleteSegment(i->second);
|
|
|
|
delete i->second;
|
|
}
|
|
|
|
m_mmappedSegments.clear();
|
|
|
|
m_sequencerMapper.setControlBlock(0);
|
|
delete m_controlBlockMmapper;
|
|
m_controlBlockMmapper = 0;
|
|
}
|
|
|
|
void RosegardenSequencerApp::remapTracks()
|
|
{
|
|
// SEQUENCER_DEBUG << "RosegardenSequencerApp::remapTracks" << endl;
|
|
std::cout << "RosegardenSequencerApp::remapTracks" << std::endl;
|
|
|
|
rationalisePlayingAudio();
|
|
}
|
|
|
|
// DCOP Wrapper for play(RealTime,
|
|
// RealTime,
|
|
// RealTime)
|
|
//
|
|
//
|
|
int
|
|
RosegardenSequencerApp::play(long timeSec,
|
|
long timeNSec,
|
|
long readAheadSec,
|
|
long readAheadNSec,
|
|
long audioMixSec,
|
|
long audioMixNsec,
|
|
long audioReadSec,
|
|
long audioReadNsec,
|
|
long audioWriteSec,
|
|
long audioWriteNsec,
|
|
long smallFileSize)
|
|
|
|
{
|
|
return play(RealTime(timeSec, timeNSec),
|
|
RealTime(readAheadSec, readAheadNSec),
|
|
RealTime(audioMixSec, audioMixNsec),
|
|
RealTime(audioReadSec, audioReadNsec),
|
|
RealTime(audioWriteSec, audioWriteNsec),
|
|
smallFileSize);
|
|
}
|
|
|
|
|
|
|
|
// Wrapper for record(RealTime,
|
|
// RealTime,
|
|
// RealTime,
|
|
// recordMode);
|
|
//
|
|
//
|
|
int
|
|
RosegardenSequencerApp::record(long timeSec,
|
|
long timeNSec,
|
|
long readAheadSec,
|
|
long readAheadNSec,
|
|
long audioMixSec,
|
|
long audioMixNsec,
|
|
long audioReadSec,
|
|
long audioReadNsec,
|
|
long audioWriteSec,
|
|
long audioWriteNsec,
|
|
long smallFileSize,
|
|
long recordMode)
|
|
|
|
{
|
|
return record(RealTime(timeSec, timeNSec),
|
|
RealTime(readAheadSec, readAheadNSec),
|
|
RealTime(audioMixSec, audioMixNsec),
|
|
RealTime(audioReadSec, audioReadNsec),
|
|
RealTime(audioWriteSec, audioWriteNsec),
|
|
smallFileSize,
|
|
recordMode);
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::setLoop(const RealTime &loopStart,
|
|
const RealTime &loopEnd)
|
|
{
|
|
m_loopStart = loopStart;
|
|
m_loopEnd = loopEnd;
|
|
|
|
m_driver->setLoop(loopStart, loopEnd);
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::setLoop(long loopStartSec,
|
|
long loopStartNSec,
|
|
long loopEndSec,
|
|
long loopEndNSec)
|
|
{
|
|
setLoop(RealTime(loopStartSec, loopStartNSec),
|
|
RealTime(loopEndSec, loopEndNSec));
|
|
}
|
|
|
|
|
|
// Return the status of the sound systems (audio and MIDI)
|
|
//
|
|
unsigned int
|
|
RosegardenSequencerApp::getSoundDriverStatus(const TQString &guiVersion)
|
|
{
|
|
unsigned int driverStatus = m_driver->getStatus();
|
|
if (guiVersion == VERSION)
|
|
driverStatus |= VERSION_OK;
|
|
else {
|
|
std::cerr << "WARNING: RosegardenSequencerApp::getSoundDriverStatus: "
|
|
<< "GUI version \"" << guiVersion.ascii()
|
|
<< "\" does not match sequencer version \"" << VERSION
|
|
<< "\"" << std::endl;
|
|
}
|
|
return driverStatus;
|
|
}
|
|
|
|
|
|
// Add an audio file to the sequencer
|
|
int
|
|
RosegardenSequencerApp::addAudioFile(const TQString &fileName, int id)
|
|
{
|
|
return ((int)m_driver->addAudioFile(fileName.utf8().data(), id));
|
|
}
|
|
|
|
int
|
|
RosegardenSequencerApp::removeAudioFile(int id)
|
|
{
|
|
return ((int)m_driver->removeAudioFile(id));
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::clearAllAudioFiles()
|
|
{
|
|
m_driver->clearAudioFiles();
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setMappedInstrument(int type, unsigned char channel,
|
|
unsigned int id)
|
|
{
|
|
InstrumentId mID = (InstrumentId)id;
|
|
Instrument::InstrumentType mType =
|
|
(Instrument::InstrumentType)type;
|
|
MidiByte mChannel = (MidiByte)channel;
|
|
|
|
m_driver->setMappedInstrument(
|
|
new MappedInstrument (mType, mChannel, mID));
|
|
|
|
}
|
|
|
|
// Process a MappedComposition sent from Sequencer with
|
|
// immediate effect
|
|
//
|
|
void
|
|
RosegardenSequencerApp::processSequencerSlice(MappedComposition mC)
|
|
{
|
|
// Use the "now" API
|
|
//
|
|
m_driver->processEventsOut(mC);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::processMappedEvent(unsigned int id,
|
|
int type,
|
|
unsigned char pitch,
|
|
unsigned char velocity,
|
|
long absTimeSec,
|
|
long absTimeNsec,
|
|
long durationSec,
|
|
long durationNsec,
|
|
long audioStartMarkerSec,
|
|
long audioStartMarkerNSec)
|
|
{
|
|
MappedEvent *mE =
|
|
new MappedEvent(
|
|
(InstrumentId)id,
|
|
(MappedEvent::MappedEventType)type,
|
|
(MidiByte)pitch,
|
|
(MidiByte)velocity,
|
|
RealTime(absTimeSec, absTimeNsec),
|
|
RealTime(durationSec, durationNsec),
|
|
RealTime(audioStartMarkerSec, audioStartMarkerNSec));
|
|
|
|
MappedComposition mC;
|
|
|
|
// SEQUENCER_DEBUG << "processMappedEvent(data) - sending out single event at time " << mE->getEventTime() << endl;
|
|
|
|
/*
|
|
std::cout << "ID = " << mE->getInstrument() << std::endl;
|
|
std::cout << "TYPE = " << mE->getType() << std::endl;
|
|
std::cout << "D1 = " << (int)mE->getData1() << std::endl;
|
|
std::cout << "D2 = " << (int)mE->getData2() << std::endl;
|
|
*/
|
|
|
|
mC.insert(mE);
|
|
|
|
m_driver->processEventsOut(mC);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::processMappedEvent(MappedEvent mE)
|
|
{
|
|
MappedComposition mC;
|
|
mC.insert(new MappedEvent(mE));
|
|
SEQUENCER_DEBUG << "processMappedEvent(ev) - sending out single event at time " << mE.getEventTime() << endl;
|
|
|
|
m_driver->processEventsOut(mC);
|
|
}
|
|
|
|
// Get the MappedDevice (DCOP wrapped vector of MappedInstruments)
|
|
//
|
|
MappedDevice
|
|
RosegardenSequencerApp::getMappedDevice(unsigned int id)
|
|
{
|
|
return m_driver->getMappedDevice(id);
|
|
}
|
|
|
|
unsigned int
|
|
RosegardenSequencerApp::getDevices()
|
|
{
|
|
return m_driver->getDevices();
|
|
}
|
|
|
|
int
|
|
RosegardenSequencerApp::canReconnect(int type)
|
|
{
|
|
return m_driver->canReconnect((Device::DeviceType)type);
|
|
}
|
|
|
|
unsigned int
|
|
RosegardenSequencerApp::addDevice(int type, unsigned int direction)
|
|
{
|
|
return m_driver->addDevice((Device::DeviceType)type,
|
|
(MidiDevice::DeviceDirection)direction);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::removeDevice(unsigned int deviceId)
|
|
{
|
|
m_driver->removeDevice(deviceId);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::renameDevice(unsigned int deviceId, TQString name)
|
|
{
|
|
m_driver->renameDevice(deviceId, name);
|
|
}
|
|
|
|
unsigned int
|
|
RosegardenSequencerApp::getConnections(int type, unsigned int direction)
|
|
{
|
|
return m_driver->getConnections((Device::DeviceType)type,
|
|
(MidiDevice::DeviceDirection)direction);
|
|
}
|
|
|
|
TQString
|
|
RosegardenSequencerApp::getConnection(int type, unsigned int direction,
|
|
unsigned int connectionNo)
|
|
{
|
|
return m_driver->getConnection((Device::DeviceType)type,
|
|
(MidiDevice::DeviceDirection)direction,
|
|
connectionNo);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setConnection(unsigned int deviceId,
|
|
TQString connection)
|
|
{
|
|
m_driver->setConnection(deviceId, connection);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setPlausibleConnection(unsigned int deviceId,
|
|
TQString connection)
|
|
{
|
|
m_driver->setPlausibleConnection(deviceId, connection);
|
|
}
|
|
|
|
unsigned int
|
|
RosegardenSequencerApp::getTimers()
|
|
{
|
|
return m_driver->getTimers();
|
|
}
|
|
|
|
TQString
|
|
RosegardenSequencerApp::getTimer(unsigned int n)
|
|
{
|
|
return m_driver->getTimer(n);
|
|
}
|
|
|
|
TQString
|
|
RosegardenSequencerApp::getCurrentTimer()
|
|
{
|
|
return m_driver->getCurrentTimer();
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setCurrentTimer(TQString timer)
|
|
{
|
|
m_driver->setCurrentTimer(timer);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setLowLatencyMode(bool ll)
|
|
{
|
|
m_driver->setLowLatencyMode(ll);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::sequencerAlive()
|
|
{
|
|
if (!kapp->dcopClient()->
|
|
isApplicationRegistered(TQCString(ROSEGARDEN_GUI_APP_NAME))) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::sequencerAlive() - "
|
|
<< "waiting for GUI to register" << endl;
|
|
return ;
|
|
}
|
|
|
|
TQByteArray data;
|
|
|
|
if (!kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"alive()",
|
|
data)) {
|
|
SEQUENCER_DEBUG << "RosegardenSequencer::alive()"
|
|
<< " - can't call RosegardenGUI client"
|
|
<< endl;
|
|
}
|
|
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::sequencerAlive() - "
|
|
<< "trying to tell GUI that we're alive" << endl;
|
|
}
|
|
|
|
MappedRealTime
|
|
RosegardenSequencerApp::getAudioPlayLatency()
|
|
{
|
|
return MappedRealTime(m_driver->getAudioPlayLatency());
|
|
}
|
|
|
|
MappedRealTime
|
|
RosegardenSequencerApp::getAudioRecordLatency()
|
|
{
|
|
return MappedRealTime(m_driver->getAudioRecordLatency());
|
|
}
|
|
|
|
// Initialise the virtual studio with a few audio faders and
|
|
// create a plugin manager. For the moment this is pretty
|
|
// arbitrary but eventually we'll drive this from the gui
|
|
// and rg file "Studio" entries.
|
|
//
|
|
void
|
|
RosegardenSequencerApp::initialiseStudio()
|
|
{
|
|
// clear down the studio before we start adding anything
|
|
//
|
|
m_studio->clear();
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::setMappedProperty(int id,
|
|
const TQString &property,
|
|
float value)
|
|
{
|
|
|
|
// SEQUENCER_DEBUG << "setProperty: id = " << id
|
|
// << " : property = \"" << property << "\""
|
|
// << ", value = " << value << endl;
|
|
|
|
|
|
MappedObject *object = m_studio->getObjectById(id);
|
|
|
|
if (object)
|
|
object->setProperty(property, value);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setMappedProperties(const MappedObjectIdList &ids,
|
|
const MappedObjectPropertyList &properties,
|
|
const MappedObjectValueList &values)
|
|
{
|
|
MappedObject *object = 0;
|
|
MappedObjectId prevId = 0;
|
|
|
|
for (size_t i = 0;
|
|
i < ids.size() && i < properties.size() && i < values.size();
|
|
++i) {
|
|
|
|
if (i == 0 || ids[i] != prevId) {
|
|
object = m_studio->getObjectById(ids[i]);
|
|
prevId = ids[i];
|
|
}
|
|
|
|
if (object) {
|
|
object->setProperty(properties[i], values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setMappedProperty(int id,
|
|
const TQString &property,
|
|
const TQString &value)
|
|
{
|
|
|
|
SEQUENCER_DEBUG << "setProperty: id = " << id
|
|
<< " : property = \"" << property << "\""
|
|
<< ", value = " << value << endl;
|
|
|
|
|
|
MappedObject *object = m_studio->getObjectById(id);
|
|
|
|
if (object)
|
|
object->setProperty(property, value);
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setMappedPropertyList(int id, const TQString &property,
|
|
const MappedObjectPropertyList &values)
|
|
{
|
|
SEQUENCER_DEBUG << "setPropertyList: id = " << id
|
|
<< " : property list size = \"" << values.size()
|
|
<< "\"" << endl;
|
|
|
|
MappedObject *object = m_studio->getObjectById(id);
|
|
|
|
if (object) {
|
|
try {
|
|
object->setPropertyList(property, values);
|
|
} catch (TQString err) {
|
|
TQByteArray data;
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
arg << err;
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"showError(TQString)",
|
|
data);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
RosegardenSequencerApp::getMappedObjectId(int type)
|
|
{
|
|
int value = -1;
|
|
|
|
MappedObject *object =
|
|
m_studio->getObjectOfType(
|
|
MappedObject::MappedObjectType(type));
|
|
|
|
if (object) {
|
|
value = int(object->getId());
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
std::vector<TQString>
|
|
RosegardenSequencerApp::getPropertyList(int id,
|
|
const TQString &property)
|
|
{
|
|
std::vector<TQString> list;
|
|
|
|
MappedObject *object =
|
|
m_studio->getObjectById(id);
|
|
|
|
if (object) {
|
|
list = object->getPropertyList(property);
|
|
}
|
|
|
|
SEQUENCER_DEBUG << "getPropertyList - return " << list.size()
|
|
<< " items" << endl;
|
|
|
|
return list;
|
|
}
|
|
|
|
std::vector<TQString>
|
|
RosegardenSequencerApp::getPluginInformation()
|
|
{
|
|
std::vector<TQString> list;
|
|
|
|
PluginFactory::enumerateAllPlugins(list);
|
|
|
|
return list;
|
|
}
|
|
|
|
TQString
|
|
RosegardenSequencerApp::getPluginProgram(int id, int bank, int program)
|
|
{
|
|
MappedObject *object = m_studio->getObjectById(id);
|
|
|
|
if (object) {
|
|
MappedPluginSlot *slot =
|
|
dynamic_cast<MappedPluginSlot *>(object);
|
|
if (slot) {
|
|
return slot->getProgram(bank, program);
|
|
}
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
unsigned long
|
|
RosegardenSequencerApp::getPluginProgram(int id, const TQString &name)
|
|
{
|
|
MappedObject *object = m_studio->getObjectById(id);
|
|
|
|
if (object) {
|
|
MappedPluginSlot *slot =
|
|
dynamic_cast<MappedPluginSlot *>(object);
|
|
if (slot) {
|
|
return slot->getProgram(name);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int
|
|
RosegardenSequencerApp::getSampleRate() const
|
|
{
|
|
if (m_driver)
|
|
return m_driver->getSampleRate();
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Creates an object of a type
|
|
//
|
|
int
|
|
RosegardenSequencerApp::createMappedObject(int type)
|
|
{
|
|
MappedObject *object =
|
|
m_studio->createObject(
|
|
MappedObject::MappedObjectType(type));
|
|
|
|
if (object) {
|
|
SEQUENCER_DEBUG << "createMappedObject - type = "
|
|
<< type << ", object id = "
|
|
<< object->getId() << endl;
|
|
return object->getId();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Destroy an object
|
|
//
|
|
int
|
|
RosegardenSequencerApp::destroyMappedObject(int id)
|
|
{
|
|
return (int(m_studio->destroyObject(MappedObjectId(id))));
|
|
}
|
|
|
|
// Connect two objects
|
|
//
|
|
void
|
|
RosegardenSequencerApp::connectMappedObjects(int id1, int id2)
|
|
{
|
|
m_studio->connectObjects(MappedObjectId(id1),
|
|
MappedObjectId(id2));
|
|
|
|
// When this happens we need to resynchronise our audio processing,
|
|
// and this is the easiest (and most brutal) way to do it.
|
|
if (m_transportStatus == PLAYING ||
|
|
m_transportStatus == RECORDING) {
|
|
RealTime seqTime = m_driver->getSequencerTime();
|
|
jumpTo(seqTime.sec, seqTime.nsec);
|
|
}
|
|
}
|
|
|
|
// Disconnect two objects
|
|
//
|
|
void
|
|
RosegardenSequencerApp::disconnectMappedObjects(int id1, int id2)
|
|
{
|
|
m_studio->disconnectObjects(MappedObjectId(id1),
|
|
MappedObjectId(id2));
|
|
}
|
|
|
|
// Disconnect an object from everything
|
|
//
|
|
void
|
|
RosegardenSequencerApp::disconnectMappedObject(int id)
|
|
{
|
|
m_studio->disconnectObject(MappedObjectId(id));
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::clearStudio()
|
|
{
|
|
SEQUENCER_DEBUG << "clearStudio()" << endl;
|
|
m_studio->clear();
|
|
m_sequencerMapper.getSequencerDataBlock()->clearTemporaries();
|
|
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::setMappedPort(int pluginId,
|
|
unsigned long portId,
|
|
float value)
|
|
{
|
|
MappedObject *object =
|
|
m_studio->getObjectById(pluginId);
|
|
|
|
MappedPluginSlot *slot =
|
|
dynamic_cast<MappedPluginSlot *>(object);
|
|
|
|
if (slot) {
|
|
slot->setPort(portId, value);
|
|
} else {
|
|
SEQUENCER_DEBUG << "no such slot" << endl;
|
|
}
|
|
}
|
|
|
|
float
|
|
RosegardenSequencerApp::getMappedPort(int pluginId,
|
|
unsigned long portId)
|
|
{
|
|
MappedObject *object =
|
|
m_studio->getObjectById(pluginId);
|
|
|
|
MappedPluginSlot *slot =
|
|
dynamic_cast<MappedPluginSlot *>(object);
|
|
|
|
if (slot) {
|
|
return slot->getPort(portId);
|
|
} else {
|
|
SEQUENCER_DEBUG << "no such slot" << endl;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::slotCheckForNewClients()
|
|
{
|
|
// Don't do this check if any of these conditions hold
|
|
//
|
|
if (m_transportStatus == PLAYING ||
|
|
m_transportStatus == RECORDING)
|
|
return ;
|
|
|
|
if (m_driver->checkForNewClients()) {
|
|
SEQUENCER_DEBUG << "client list changed" << endl;
|
|
}
|
|
}
|
|
|
|
|
|
// Set the MIDI Clock period in microseconds
|
|
//
|
|
void
|
|
RosegardenSequencerApp::setQuarterNoteLength(long timeSec, long timeNSec)
|
|
{
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::setQuarterNoteLength"
|
|
<< RealTime(timeSec, timeNSec) << endl;
|
|
|
|
m_driver->setMIDIClockInterval(
|
|
RealTime(timeSec, timeNSec) / 24);
|
|
}
|
|
|
|
TQString
|
|
RosegardenSequencerApp::getStatusLog()
|
|
{
|
|
return m_driver->getStatusLog();
|
|
}
|
|
|
|
|
|
void RosegardenSequencerApp::dumpFirstSegment()
|
|
{
|
|
SEQUENCER_DEBUG << "Dumping 1st segment data :\n";
|
|
|
|
unsigned int i = 0;
|
|
MmappedSegment* firstMappedSegment = (*(m_mmappedSegments.begin())).second;
|
|
|
|
MmappedSegment::iterator it(firstMappedSegment);
|
|
|
|
for (; !it.atEnd(); ++it) {
|
|
|
|
MappedEvent evt = (*it);
|
|
SEQUENCER_DEBUG << i << " : inst = " << evt.getInstrument()
|
|
<< " - type = " << evt.getType()
|
|
<< " - data1 = " << (unsigned int)evt.getData1()
|
|
<< " - data2 = " << (unsigned int)evt.getData2()
|
|
<< " - time = " << evt.getEventTime()
|
|
<< " - duration = " << evt.getDuration()
|
|
<< " - audio mark = " << evt.getAudioStartMarker()
|
|
<< endl;
|
|
|
|
++i;
|
|
}
|
|
|
|
SEQUENCER_DEBUG << "Dumping 1st segment data - done\n";
|
|
|
|
}
|
|
|
|
|
|
void
|
|
RosegardenSequencerApp::rationalisePlayingAudio()
|
|
{
|
|
std::vector<MappedEvent> audioEvents;
|
|
m_metaIterator->getAudioEvents(audioEvents);
|
|
m_driver->initialiseAudioQueue(audioEvents);
|
|
}
|
|
|
|
|
|
ExternalTransport::TransportToken
|
|
RosegardenSequencerApp::transportChange(TransportRequest request)
|
|
{
|
|
TransportPair pair(request, RealTime::zeroTime);
|
|
m_transportRequests.push_back(pair);
|
|
|
|
std::cout << "RosegardenSequencerApp::transportChange: " << request << std::endl;
|
|
|
|
if (request == TransportNoChange)
|
|
return m_transportToken;
|
|
else
|
|
return m_transportToken + 1;
|
|
}
|
|
|
|
ExternalTransport::TransportToken
|
|
RosegardenSequencerApp::transportJump(TransportRequest request,
|
|
RealTime rt)
|
|
{
|
|
TransportPair pair(request, rt);
|
|
m_transportRequests.push_back(pair);
|
|
|
|
std::cout << "RosegardenSequencerApp::transportJump: " << request << ", " << rt << std::endl;
|
|
|
|
if (request == TransportNoChange)
|
|
return m_transportToken + 1;
|
|
else
|
|
return m_transportToken + 2;
|
|
}
|
|
|
|
bool
|
|
RosegardenSequencerApp::isTransportSyncComplete(TransportToken token)
|
|
{
|
|
std::cout << "RosegardenSequencerApp::isTransportSyncComplete: token " << token << ", current token " << m_transportToken << std::endl;
|
|
return m_transportToken >= token;
|
|
}
|
|
|
|
bool
|
|
RosegardenSequencerApp::checkExternalTransport()
|
|
{
|
|
bool rv = (!m_transportRequests.empty());
|
|
|
|
while (!m_transportRequests.empty()) {
|
|
|
|
TransportPair pair = *m_transportRequests.begin();
|
|
m_transportRequests.pop_front();
|
|
|
|
TQByteArray data;
|
|
|
|
switch (pair.first) {
|
|
|
|
case TransportNoChange:
|
|
break;
|
|
|
|
case TransportStop:
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"stop()",
|
|
data);
|
|
break;
|
|
|
|
case TransportStart:
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"play()",
|
|
data);
|
|
break;
|
|
|
|
case TransportPlay:
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"play()",
|
|
data);
|
|
break;
|
|
|
|
case TransportRecord:
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"record()",
|
|
data);
|
|
break;
|
|
|
|
case TransportJumpToTime: {
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
arg << (int)pair.second.sec;
|
|
arg << (int)pair.second.usec();
|
|
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"jumpToTime(int, int)",
|
|
data);
|
|
|
|
if (m_transportStatus == PLAYING ||
|
|
m_transportStatus != RECORDING) {
|
|
jumpTo(pair.second.sec, pair.second.usec() * 1000);
|
|
}
|
|
|
|
incrementTransportToken();
|
|
break;
|
|
}
|
|
|
|
case TransportStartAtTime: {
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
arg << (int)pair.second.sec;
|
|
arg << (int)pair.second.usec();
|
|
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"startAtTime(int, int)",
|
|
data);
|
|
break;
|
|
}
|
|
|
|
case TransportStopAtTime: {
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"stop()",
|
|
data);
|
|
|
|
TQDataStream arg(data, IO_WriteOnly);
|
|
arg << (int)pair.second.sec;
|
|
arg << (int)pair.second.usec();
|
|
|
|
kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
|
|
ROSEGARDEN_GUI_IFACE_NAME,
|
|
"jumpToTime(int, int)",
|
|
data);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
RosegardenSequencerApp::incrementTransportToken()
|
|
{
|
|
++m_transportToken;
|
|
SEQUENCER_DEBUG << "RosegardenSequencerApp::incrementTransportToken: incrementing to " << m_transportToken << endl;
|
|
}
|
|
|
|
}
|
|
|
|
#include "RosegardenSequencerApp.moc"
|