diff options
Diffstat (limited to 'src/sequencer')
-rw-r--r-- | src/sequencer/ControlBlockMmapper.cpp | 81 | ||||
-rw-r--r-- | src/sequencer/ControlBlockMmapper.h | 94 | ||||
-rw-r--r-- | src/sequencer/MmappedSegment.cpp | 702 | ||||
-rw-r--r-- | src/sequencer/MmappedSegment.h | 185 | ||||
-rw-r--r-- | src/sequencer/RosegardenSequencerApp.cpp | 1850 | ||||
-rw-r--r-- | src/sequencer/RosegardenSequencerApp.h | 531 | ||||
-rw-r--r-- | src/sequencer/RosegardenSequencerIface.h | 364 | ||||
-rw-r--r-- | src/sequencer/SequencerMmapper.cpp | 146 | ||||
-rw-r--r-- | src/sequencer/SequencerMmapper.h | 103 | ||||
-rw-r--r-- | src/sequencer/main.cpp | 246 |
10 files changed, 4302 insertions, 0 deletions
diff --git a/src/sequencer/ControlBlockMmapper.cpp b/src/sequencer/ControlBlockMmapper.cpp new file mode 100644 index 0000000..d9bf83d --- /dev/null +++ b/src/sequencer/ControlBlockMmapper.cpp @@ -0,0 +1,81 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 "ControlBlockMmapper.h" + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <sys/mman.h> + +#include "misc/Debug.h" + +namespace Rosegarden +{ + +ControlBlockMmapper::ControlBlockMmapper(QString fileName) + : m_fileName(fileName), + m_fd( -1), + m_mmappedBuffer(0), + m_mmappedSize(sizeof(ControlBlock)), + m_controlBlock(0) +{ + m_fd = ::open(m_fileName.latin1(), O_RDWR); + + if (m_fd < 0) { + SEQMAN_DEBUG << "ControlBlockMmapper : Couldn't open " << m_fileName + << endl; + throw Exception(std::string("Couldn't open ") + + m_fileName.latin1()); + } + + // + // mmap() file for reading + // + m_mmappedBuffer = (char*)::mmap(0, m_mmappedSize, + PROT_READ, MAP_SHARED, m_fd, 0); + + if (m_mmappedBuffer == (void*) - 1) { + + SEQUENCER_DEBUG << QString("mmap failed : (%1) %2\n"). + arg(errno).arg(strerror(errno)); + + throw Exception("mmap failed"); + } + + SEQMAN_DEBUG << "ControlBlockMmapper : mmap size : " << m_mmappedSize + << " at " << (void*)m_mmappedBuffer << endl; + + // Create new control block on file + m_controlBlock = new (m_mmappedBuffer) ControlBlock; +} + +ControlBlockMmapper::~ControlBlockMmapper() +{ + ::munmap(m_mmappedBuffer, m_mmappedSize); + ::close(m_fd); +} + +} diff --git a/src/sequencer/ControlBlockMmapper.h b/src/sequencer/ControlBlockMmapper.h new file mode 100644 index 0000000..55d4c9f --- /dev/null +++ b/src/sequencer/ControlBlockMmapper.h @@ -0,0 +1,94 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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. +*/ + +#ifndef _MMAPPEDCONTROLBLOCK_H_ +#define _MMAPPEDCONTROLBLOCK_H_ + +#include <qstring.h> + +#include "sound/MappedEvent.h" +#include "sound/ControlBlock.h" + +namespace Rosegarden +{ + +class ControlBlockMmapper +{ +public: + ControlBlockMmapper(QString fileName); + ~ControlBlockMmapper(); + + QString getFileName() { return m_fileName; } + + // delegate ControlBlock's interface + InstrumentId getInstrumentForTrack(unsigned int trackId) + { return m_controlBlock->getInstrumentForTrack(trackId); } + + InstrumentId getInstrumentForEvent(unsigned int dev, + unsigned int chan) + { return m_controlBlock->getInstrumentForEvent(dev, chan); } + + bool isTrackMuted(unsigned int trackId) + { return m_controlBlock->isTrackMuted(trackId); } + + bool isTrackArmed(unsigned int trackId) + { return m_controlBlock->isTrackArmed(trackId); } + + InstrumentId getInstrumentForMetronome() + { return m_controlBlock->getInstrumentForMetronome(); } + + bool isMetronomeMuted() { return m_controlBlock->isMetronomeMuted(); } + + bool isSolo() { return m_controlBlock->isSolo(); } + + bool isMidiRoutingEnabled() + { return m_controlBlock->isMidiRoutingEnabled(); } + + TrackId getSelectedTrack() + { return m_controlBlock->getSelectedTrack(); } + + MidiFilter getThruFilter() + { return m_controlBlock->getThruFilter(); } + + MidiFilter getRecordFilter() + { return m_controlBlock->getRecordFilter(); } + + // for transfer to SequencerMmapper + ControlBlock *getControlBlock() + { return m_controlBlock; } + +protected: + + //--------------- Data members --------------------------------- + QString m_fileName; + int m_fd; + void* m_mmappedBuffer; + size_t m_mmappedSize; + ControlBlock* m_controlBlock; +}; + +} + +#endif // _MMAPPEDCONTROLBLOCK_H_ diff --git a/src/sequencer/MmappedSegment.cpp b/src/sequencer/MmappedSegment.cpp new file mode 100644 index 0000000..57b3dc9 --- /dev/null +++ b/src/sequencer/MmappedSegment.cpp @@ -0,0 +1,702 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 "MmappedSegment.h" +#include "misc/Debug.h" + +#include "sound/MappedComposition.h" +#include "sound/Midi.h" + +//#define DEBUG_META_ITERATOR 1 +//#define DEBUG_PLAYING_AUDIO_FILES 1 + +namespace Rosegarden +{ + +MmappedSegment::MmappedSegment(const QString filename) + : m_fd( -1), + m_mmappedSize(0), + m_mmappedRegion(0), + m_mmappedEventBuffer((MappedEvent*)0), + m_filename(filename) +{ + SEQUENCER_DEBUG << "mmapping " << filename << endl; + + map(); +} + +bool MmappedSegment::isMetronome() +{ + return (getFileName().contains("metronome", false) > 0); +} + + + +void MmappedSegment::map() +{ + QFileInfo fInfo(m_filename); + if (!fInfo.exists()) { + SEQUENCER_DEBUG << "MmappedSegment::map() : file " << m_filename << " doesn't exist\n"; + throw Exception("file not found"); + } + + m_mmappedSize = fInfo.size(); + + m_fd = ::open(m_filename.latin1(), O_RDWR); + + m_mmappedRegion = ::mmap(0, m_mmappedSize, PROT_READ, MAP_SHARED, m_fd, 0); + + if (m_mmappedRegion == (void*) - 1) { + + SEQUENCER_DEBUG << QString("mmap failed : (%1) %2\n"). + arg(errno).arg(strerror(errno)); + + throw Exception("mmap failed"); + } + + m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1); + + SEQUENCER_DEBUG << "MmappedSegment::map() : " + << (void*)m_mmappedRegion << "," << m_mmappedSize << endl; + +} + +MmappedSegment::~MmappedSegment() +{ + unmap(); +} + +void MmappedSegment::unmap() +{ + ::munmap(m_mmappedRegion, m_mmappedSize); + ::close(m_fd); +} + +size_t +MmappedSegment::getNbMappedEvents() const +{ + if (m_mmappedRegion || !m_mmappedSize) { + + // The shared memory area consists of one size_t giving the + // number of following mapped events, followed by the mapped + // events themselves. + + // So this is the number of mapped events that we expect: + size_t nominal = *(size_t *)m_mmappedRegion; + + // But we want to be sure not to read off the end of the + // shared memory area, so just in case, this is the number of + // events that can actually be accommodated in the memory area + // as we see it: + size_t actual = (m_mmappedSize - sizeof(size_t)) / + sizeof(MappedEvent); + + return std::min(nominal, actual); + + } else + return 0; +} + +bool MmappedSegment::remap(size_t newSize) +{ + SEQUENCER_DEBUG << "remap() from " << m_mmappedSize << " to " + << newSize << endl; + + if (m_mmappedSize == newSize) { + + SEQUENCER_DEBUG << "remap() : sizes are identical, remap not forced - " + << "nothing to do\n"; + return false; + } + +#ifdef linux + void *oldRegion = m_mmappedRegion; + m_mmappedRegion = (MappedEvent*)::mremap(m_mmappedRegion, m_mmappedSize, newSize, MREMAP_MAYMOVE); + if (m_mmappedRegion != oldRegion) { + SEQUENCER_DEBUG << "NOTE: buffer moved from " << oldRegion << + " to " << (void *)m_mmappedRegion << endl; + } +#else + ::munmap(m_mmappedRegion, m_mmappedSize); + m_mmappedRegion = (MappedEvent*)::mmap(0, newSize, PROT_READ, MAP_SHARED, m_fd, 0); +#endif + + if (m_mmappedRegion == (void*) - 1) { + + SEQUENCER_DEBUG << QString("mremap failed : (%1) %2\n"). + arg(errno).arg(strerror(errno)); + + throw Exception("mremap failed"); + } + + m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1); + m_mmappedSize = newSize; + + return true; +} + +MmappedSegment::iterator::iterator(MmappedSegment* s) + : m_s(s), m_currentEvent(m_s->getBuffer()) +{} + +MmappedSegment::iterator& MmappedSegment::iterator::operator=(const iterator& it) +{ + if (&it == this) + return * this; + + m_s = it.m_s; + m_currentEvent = it.m_currentEvent; + + return *this; +} + +MmappedSegment::iterator& MmappedSegment::iterator::operator++() +{ + if (!atEnd()) { + + do + ++m_currentEvent; + while (!atEnd() && (m_currentEvent->getType() == 0)); + // skip null events - there can be some if the file has been + // zeroed out after events have been deleted + + } else { + + SEQUENCER_DEBUG << "MmappedSegment::iterator::operator++() " << this + << " - reached end of stream\n"; + + } + + return *this; +} + +MmappedSegment::iterator MmappedSegment::iterator::operator++(int) +{ + iterator r = *this; + + if (!atEnd()) { + do + ++m_currentEvent; + while (!atEnd() && m_currentEvent->getType() == 0); + + } + + return r; +} + +MmappedSegment::iterator& MmappedSegment::iterator::operator+=(int offset) +{ + m_currentEvent += offset; + + if (atEnd()) { + m_currentEvent = m_s->getBuffer() + m_s->getNbMappedEvents(); + } + + return *this; +} + +MmappedSegment::iterator& MmappedSegment::iterator::operator-=(int offset) +{ + m_currentEvent -= offset; + if (m_currentEvent < m_s->getBuffer()) { + m_currentEvent = m_s->getBuffer(); + } + + return *this; +} + + +bool MmappedSegment::iterator::operator==(const iterator& it) +{ + return (m_currentEvent == it.m_currentEvent) || (atEnd() == it.atEnd()); +} + +void MmappedSegment::iterator::reset() +{ + m_currentEvent = m_s->getBuffer(); +} + +const MappedEvent &MmappedSegment::iterator::operator*() +{ + return *m_currentEvent; +} + +bool MmappedSegment::iterator::atEnd() const +{ + return (m_currentEvent == 0) || + (m_currentEvent > (m_s->getBuffer() + m_s->getNbMappedEvents() - 1)); +} + +//---------------------------------------- + +MmappedSegmentsMetaIterator::MmappedSegmentsMetaIterator( + mmappedsegments& segments, + ControlBlockMmapper* controlBlockMmapper) + : m_controlBlockMmapper(controlBlockMmapper), + m_segments(segments) +{ + for (mmappedsegments::iterator i = m_segments.begin(); + i != m_segments.end(); ++i) + m_iterators.push_back(new MmappedSegment::iterator(i->second)); +} + +MmappedSegmentsMetaIterator::~MmappedSegmentsMetaIterator() +{ + clear(); +} + +void MmappedSegmentsMetaIterator::addSegment(MmappedSegment* ms) +{ + MmappedSegment::iterator* iter = new MmappedSegment::iterator(ms); + moveIteratorToTime(*iter, m_currentTime); + m_iterators.push_back(iter); +} + +void MmappedSegmentsMetaIterator::deleteSegment(MmappedSegment* ms) +{ + for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) { + if ((*i)->getSegment() == ms) { + SEQUENCER_DEBUG << "deleteSegment : found segment to delete : " + << ms->getFileName() << endl; + delete (*i); + m_iterators.erase(i); + break; + } + } +} + +void MmappedSegmentsMetaIterator::clear() +{ + for (unsigned int i = 0; i < m_iterators.size(); ++i) + delete m_iterators[i]; + + m_iterators.clear(); +} + +void MmappedSegmentsMetaIterator::reset() +{ + m_currentTime.sec = m_currentTime.nsec = 0; + + for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) { + (*i)->reset(); + } + +} + +bool MmappedSegmentsMetaIterator::jumpToTime(const RealTime& startTime) +{ + SEQUENCER_DEBUG << "jumpToTime(" << startTime << ")" << endl; + + reset(); + + bool res = true; + + m_currentTime = startTime; + + for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) + if (!moveIteratorToTime(*(*i), startTime)) + res = false; + + return res; +} + +bool MmappedSegmentsMetaIterator::moveIteratorToTime(MmappedSegment::iterator& iter, + const RealTime& startTime) +{ + while ((!iter.atEnd()) && + (iter.peek()->getEventTime() < startTime) && + ((iter.peek()->getEventTime() + iter.peek()->getDuration()) < startTime) + ) { + ++iter; + } + bool res = !iter.atEnd(); + + return res; +} + +bool MmappedSegmentsMetaIterator::acceptEvent(MappedEvent *evt, bool evtIsFromMetronome) +{ + if (evt->getType() == 0) + return false; // discard those right away + + if (evtIsFromMetronome) { + if (evt->getType() == MappedEvent::MidiSystemMessage && + evt->getData1() == MIDI_TIMING_CLOCK) { + /* + std::cout << "MmappedSegmentsMetaIterator::acceptEvent - " + << "found clock" << std::endl; + */ + return true; + } + + return !m_controlBlockMmapper->isMetronomeMuted(); + } + + // else, evt is not from metronome : first check if we're soloing (i.e. playing only the selected track) + if (m_controlBlockMmapper->isSolo()) + return (evt->getTrackId() == m_controlBlockMmapper->getSelectedTrack()); + + // finally we're not soloing, so check if track is muted + TrackId track = evt->getTrackId(); + bool muted = m_controlBlockMmapper->isTrackMuted(evt->getTrackId()); + +#ifdef DEBUG_META_ITERATOR + + SEQUENCER_DEBUG << "MSMI::acceptEvent: track " << track << " muted status: " << muted << endl; +#endif + + return !muted; +} + + +bool +MmappedSegmentsMetaIterator::fillCompositionWithEventsUntil(bool /*firstFetch*/, + MappedComposition* c, + const RealTime& startTime, + const RealTime& endTime) +{ +#ifdef DEBUG_META_ITERATOR + SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil " << startTime << " -> " << endTime << endl; +#endif + + m_currentTime = endTime; + + // keep track of the segments which still have valid events + std::vector<bool> validSegments; + for (unsigned int i = 0; i < m_segments.size(); ++i) + validSegments.push_back(true); + + bool foundOneEvent = false, eventsRemaining = false; + + do { + foundOneEvent = false; + + for (unsigned int i = 0; i < m_iterators.size(); ++i) { + + MmappedSegment::iterator* iter = m_iterators[i]; + + //std::cerr << "Iterating on Segment #" << i << std::endl; + +#ifdef DEBUG_META_ITERATOR + + SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : " + << "checking segment #" << i << " " + << iter->getSegment()->getFileName() << endl; +#endif + + if (!validSegments[i]) { +#ifdef DEBUG_META_ITERATOR + SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : " + << "no more events to get for this slice " + << "in segment #" << i << endl; +#endif + + continue; // skip this segment + } + + bool evtIsFromMetronome = iter->getSegment()->isMetronome(); + + if (iter->atEnd()) { +#ifdef DEBUG_META_ITERATOR + SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : " + << endTime + << " reached end of segment #" + << i << endl; +#endif + + continue; + } else if (!evtIsFromMetronome) { + eventsRemaining = true; + } + + if ((**iter).getEventTime() < endTime) { + + MappedEvent *evt = new MappedEvent(*(*iter)); + + // set event's instrument + // + if (evtIsFromMetronome) { + + evt->setInstrument(m_controlBlockMmapper-> + getInstrumentForMetronome()); + + } else { + + evt->setInstrument(m_controlBlockMmapper-> + getInstrumentForTrack(evt->getTrackId())); + + } + +#ifdef DEBUG_META_ITERATOR + SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : " << endTime + << " inserting evt from segment #" + << i + << " : trackId: " << evt->getTrackId() + << " - inst: " << evt->getInstrument() + << " - type: " << evt->getType() + << " - time: " << evt->getEventTime() + << " - duration: " << evt->getDuration() + << " - data1: " << (unsigned int)evt->getData1() + << " - data2: " << (unsigned int)evt->getData2() + << " - metronome event: " << evtIsFromMetronome + << endl; +#endif + + if (evt->getType() == MappedEvent::TimeSignature) { + + // Process time sig and tempo changes along with + // everything else, as the sound driver probably + // wants to know when they happen + + c->insert(evt); + + } else if (evt->getType() == MappedEvent::Tempo) { + + c->insert(evt); + + } else if (evt->getType() == MappedEvent::MidiSystemMessage && + + // #1048388: + // Ensure sysex heeds mute status, but ensure + // clocks etc still get through + evt->getData1() != MIDI_SYSTEM_EXCLUSIVE) { + + c->insert(evt); + + } else if (acceptEvent(evt, evtIsFromMetronome) && + + ((evt->getEventTime() + evt->getDuration() > startTime) || + (evt->getDuration() == RealTime::zeroTime && + evt->getEventTime() == startTime))) { + + // std::cout << "inserting event" << std::endl; + + /* + std::cout << "Inserting event (type = " + << evt->getType() << ")" << std::endl; + */ + + + c->insert(evt); + + } else { + +#ifdef DEBUG_META_ITERATOR + std::cout << "MSMI: skipping event" + << " - event time = " << evt->getEventTime() + << ", duration = " << evt->getDuration() + << ", startTime = " << startTime << std::endl; +#endif + + delete evt; + } + + if (!evtIsFromMetronome) + foundOneEvent = true; + ++(*iter); + + } else { + validSegments[i] = false; // no more events to get from this segment +#ifdef DEBUG_META_ITERATOR + + SEQUENCER_DEBUG << "fillCompositionWithEventsUntil : no more events to get from segment #" + << i << endl; +#endif + + } + + } + + } while (foundOneEvent); + +#ifdef DEBUG_META_ITERATOR + + SEQUENCER_DEBUG << "fillCompositionWithEventsUntil : eventsRemaining = " << eventsRemaining << endl; +#endif + + return eventsRemaining || foundOneEvent; +} + +void MmappedSegmentsMetaIterator::resetIteratorForSegment(const QString& filename) +{ + for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) { + MmappedSegment::iterator* iter = *i; + + if (iter->getSegment()->getFileName() == filename) { + SEQUENCER_DEBUG << "MSMI::resetIteratorForSegment(" << filename << ") : found iterator\n"; + // delete iterator and create another one + MmappedSegment* ms = (*i)->getSegment(); + delete iter; + m_iterators.erase(i); + iter = new MmappedSegment::iterator(ms); + m_iterators.push_back(iter); + moveIteratorToTime(*iter, m_currentTime); + break; + } + + } +} + +void +MmappedSegmentsMetaIterator::getAudioEvents(std::vector<MappedEvent> &v) +{ + v.clear(); + + for (mmappedsegments::iterator i = m_segments.begin(); + i != m_segments.end(); ++i) { + + MmappedSegment::iterator itr(i->second); + + while (!itr.atEnd()) { + + if ((*itr).getType() != MappedEvent::Audio) { + ++itr; + continue; + } + + MappedEvent evt(*itr); + ++itr; + + if (m_controlBlockMmapper->isTrackMuted(evt.getTrackId())) { +#ifdef DEBUG_PLAYING_AUDIO_FILES + std::cout << "MSMI::getAudioEvents - " + << "track " << evt.getTrackId() << " is muted" << std::endl; +#endif + + continue; + } + + if (m_controlBlockMmapper->isSolo() == true && + evt.getTrackId() != m_controlBlockMmapper->getSelectedTrack()) { +#ifdef DEBUG_PLAYING_AUDIO_FILES + std::cout << "MSMI::getAudioEvents - " + << "track " << evt.getTrackId() << " is not solo track" << std::endl; +#endif + + continue; + } + + v.push_back(evt); + } + } +} + + +std::vector<MappedEvent>& +MmappedSegmentsMetaIterator::getPlayingAudioFiles(const RealTime & + songPosition) +{ + // Clear playing audio segments + // + m_playingAudioSegments.clear(); + +#ifdef DEBUG_PLAYING_AUDIO_FILES + + std::cout << "MSMI::getPlayingAudioFiles" << std::endl; +#endif + + for (mmappedsegments::iterator i = m_segments.begin(); + i != m_segments.end(); ++i) { + + MmappedSegment::iterator iter(i->second); + + bool found = false; + + //!!! any point to this loop at all? can found ever fail? + for (segmentiterators::iterator sI = m_iterators.begin(); + sI != m_iterators.end(); ++sI) { + if ((*sI)->getSegment() == iter.getSegment()) + found = true; + } + + if (!found) + continue; + + while (!iter.atEnd()) { + if ((*iter).getType() != MappedEvent::Audio) { + ++iter; + continue; + } + + //std::cout << "CONSTRUCTING MAPPEDEVENT" << std::endl; + MappedEvent evt(*iter); + + // Check for this track being muted or soloed + // + if (m_controlBlockMmapper->isTrackMuted(evt.getTrackId()) == true) { +#ifdef DEBUG_PLAYING_AUDIO_FILES + std::cout << "MSMI::getPlayingAudioFiles - " + << "track " << evt.getTrackId() << " is muted" << std::endl; +#endif + + ++iter; + continue; + } + + if (m_controlBlockMmapper->isSolo() == true && + evt.getTrackId() != m_controlBlockMmapper->getSelectedTrack()) { +#ifdef DEBUG_PLAYING_AUDIO_FILES + std::cout << "MSMI::getPlayingAudioFiles - " + << "track " << evt.getTrackId() << " is not solo track" << std::endl; +#endif + + ++iter; + continue; + } + + // If there's an audio event and it should be playing at this time + // then flag as such. + // + if (songPosition > evt.getEventTime() - RealTime(1, 0) && + songPosition < evt.getEventTime() + evt.getDuration()) { + +#ifdef DEBUG_PLAYING_AUDIO_FILES + std::cout << "MSMI::getPlayingAudioFiles - " + << "instrument id = " << evt.getInstrument() << std::endl; + + + std::cout << "MSMI::getPlayingAudioFiles - " + << " id " << evt.getRuntimeSegmentId() << ", audio event time = " << evt.getEventTime() << std::endl; + std::cout << "MSMI::getPlayingAudioFiles - " + << "audio event duration = " << evt.getDuration() << std::endl; + + +#endif // DEBUG_PLAYING_AUDIO_FILES + + m_playingAudioSegments.push_back(evt); + } + + ++iter; + } + + //std::cout << "END OF ITERATOR" << std::endl << std::endl; + + } + + return m_playingAudioSegments; +} + +} + diff --git a/src/sequencer/MmappedSegment.h b/src/sequencer/MmappedSegment.h new file mode 100644 index 0000000..32e2ea8 --- /dev/null +++ b/src/sequencer/MmappedSegment.h @@ -0,0 +1,185 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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. +*/ + +#ifndef _MMAPPED_SEGMENT_H_ +#define _MMAPPED_SEGMENT_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 <klocale.h> +#include <kstandarddirs.h> + +#include <dcopclient.h> +#include <qdatetime.h> +#include <qstring.h> +#include <qdir.h> +#include <qbuffer.h> + +#include "ControlBlockMmapper.h" +#include "sound/MappedInstrument.h" + +namespace Rosegarden { + +class MappedComposition; + +// Seems not to be properly defined under some gcc 2.95 setups +#ifndef MREMAP_MAYMOVE +# define MREMAP_MAYMOVE 1 +#endif + +/** + * An mmap()ed segment + */ +class MmappedSegment +{ +public: + MmappedSegment(const QString filename); + ~MmappedSegment(); + + bool remap(size_t newSize); + QString getFileName() const { return m_filename; } + bool isMetronome(); + MappedEvent* getBuffer() { return m_mmappedEventBuffer; } + size_t getSize() const { return m_mmappedSize; } + size_t getNbMappedEvents() const; + + class iterator + { + public: + iterator(MmappedSegment* s); + iterator& operator=(const iterator&); + bool operator==(const iterator&); + bool operator!=(const iterator& it) { return !operator==(it); } + + bool atEnd() const; + + /// go back to beginning of stream + void reset(); + + iterator& operator++(); + iterator operator++(int); + iterator& operator+=(int); + iterator& operator-=(int); + + const MappedEvent &operator*(); + const MappedEvent* peek() const { return m_currentEvent; } + + MmappedSegment* getSegment() { return m_s; } + const MmappedSegment* getSegment() const { return m_s; } + + private: + iterator(); + + protected: + //--------------- Data members --------------------------------- + + MmappedSegment* m_s; + MappedEvent* m_currentEvent; + }; + +protected: + void map(); + void unmap(); + + //--------------- Data members --------------------------------- + int m_fd; + size_t m_mmappedSize; +// unsigned int m_nbMappedEvents; + void *m_mmappedRegion; + MappedEvent* m_mmappedEventBuffer; + QString m_filename; +}; + +class MmappedSegmentsMetaIterator +{ +public: + + typedef std::map<QString, MmappedSegment*> mmappedsegments; + + MmappedSegmentsMetaIterator(mmappedsegments&, + ControlBlockMmapper*); + ~MmappedSegmentsMetaIterator(); + + /// reset all iterators to beginning + void reset(); + bool jumpToTime(const RealTime&); + + /** + * Fill mapped composition with events from current point until + * specified time @return true if there are non-metronome events + * remaining, false if end of composition was reached + */ + bool fillCompositionWithEventsUntil(bool firstFetch, + MappedComposition*, + const RealTime& start, + const RealTime& end); + + void resetIteratorForSegment(const QString& filename); + + void addSegment(MmappedSegment*); + void deleteSegment(MmappedSegment*); + + void getAudioEvents(std::vector<MappedEvent> &); + + // Manipulate a vector of currently mapped audio segments so that we + // can cross check them against PlayableAudioFiles (and stop if + // necessary). This will account for muting/soloing too I should + // hope. + // + //!!! to be obsoleted, hopefully + std::vector<MappedEvent>& getPlayingAudioFiles + (const RealTime &songPosition); + +protected: + bool acceptEvent(MappedEvent*, bool evtIsFromMetronome); + + /// Delete all iterators + void clear(); + bool moveIteratorToTime(MmappedSegment::iterator&, + const RealTime&); + + //--------------- Data members --------------------------------- + + ControlBlockMmapper* m_controlBlockMmapper; + + RealTime m_currentTime; + mmappedsegments& m_segments; + + typedef std::vector<MmappedSegment::iterator*> segmentiterators; + segmentiterators m_iterators; + + std::vector<MappedEvent> m_playingAudioSegments; +}; + +} + +#endif // _MMAPPED_SEGMENT_H_ diff --git a/src/sequencer/RosegardenSequencerApp.cpp b/src/sequencer/RosegardenSequencerApp.cpp new file mode 100644 index 0000000..4c26efb --- /dev/null +++ b/src/sequencer/RosegardenSequencerApp.cpp @@ -0,0 +1,1850 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <kapplication.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 <klocale.h> +#include <kstandarddirs.h> + +#include <dcopclient.h> +#include <qdatetime.h> +#include <qstring.h> +#include <qdir.h> +#include <qbuffer.h> +#include <qvaluevector.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(KGlobal::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 + QCString 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 QTimer(this); + connect(m_newClientTimer, SIGNAL(timeout()), + this, 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() +{ + QByteArray data, replyData; + QCString replyType; + QDataStream 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(KGlobal::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; + + QByteArray data; + QDataStream 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; + + QValueVector<InstrumentId> armedInstruments; + QValueVector<QString> audioFileNames; + + { + QByteArray data, replyData; + QCString replyType; + QDataStream 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; + } + + QDataStream reply(replyData, IO_ReadOnly); + if (replyType == "QValueVector<InstrumentId>") { + reply >> armedInstruments; + } else { + SEQUENCER_DEBUG << "RosegardenSequencer::record() - " + << "unrecognised type returned for getArmedInstruments" << endl; + } + } + + QValueVector<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) { + + QByteArray data, replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << audioInstruments; + + if (!kapp->dcopClient()->call(ROSEGARDEN_GUI_APP_NAME, + ROSEGARDEN_GUI_IFACE_NAME, + "createRecordAudioFiles(QValueVector<InstrumentId>)", + data, replyType, replyData, true)) { + SEQUENCER_DEBUG << "RosegardenSequencer::record()" + << " - can't call RosegardenGUI client for createNewAudioFiles" + << endl; + } + + QDataStream reply(replyData, IO_ReadOnly); + if (replyType == "QValueVector<QString>") { + 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<QString> 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 + // + QDir segmentsDir(m_segmentFilesPath, "segment_*"); + for (unsigned int i = 0; i < segmentsDir.count(); ++i) { + mmapSegment(m_segmentFilesPath + "/" + segmentsDir[i]); + } + + QString tmpDir = KGlobal::dirs()->resourceDirs("tmp").last(); + + // Map metronome + // + QString metronomeFileName = tmpDir + "/rosegarden_metronome"; + QFileInfo metronomeFileInfo(metronomeFileName); + if (metronomeFileInfo.exists()) + mmapSegment(metronomeFileName); + else + SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no metronome found\n"; + + // Map tempo segment + // + QString tempoSegmentFileName = tmpDir + "/rosegarden_tempo"; + QFileInfo tempoSegmentFileInfo(tempoSegmentFileName); + if (tempoSegmentFileInfo.exists()) + mmapSegment(tempoSegmentFileName); + else + SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no tempo segment found\n"; + + // Map time sig segment + // + QString timeSigSegmentFileName = tmpDir + "/rosegarden_timesig"; + QFileInfo 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 QString& 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 QString& 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 QString& 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 QString& 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 QString &guiVersion) +{ + unsigned int driverStatus = m_driver->getStatus(); + if (guiVersion == VERSION) + driverStatus |= VERSION_OK; + else { + std::cerr << "WARNING: RosegardenSequencerApp::getSoundDriverStatus: " + << "GUI version \"" << guiVersion + << "\" does not match sequencer version \"" << VERSION + << "\"" << std::endl; + } + return driverStatus; +} + + +// Add an audio file to the sequencer +int +RosegardenSequencerApp::addAudioFile(const QString &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, QString 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); +} + +QString +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, + QString connection) +{ + m_driver->setConnection(deviceId, connection); +} + +void +RosegardenSequencerApp::setPlausibleConnection(unsigned int deviceId, + QString connection) +{ + m_driver->setPlausibleConnection(deviceId, connection); +} + +unsigned int +RosegardenSequencerApp::getTimers() +{ + return m_driver->getTimers(); +} + +QString +RosegardenSequencerApp::getTimer(unsigned int n) +{ + return m_driver->getTimer(n); +} + +QString +RosegardenSequencerApp::getCurrentTimer() +{ + return m_driver->getCurrentTimer(); +} + +void +RosegardenSequencerApp::setCurrentTimer(QString timer) +{ + m_driver->setCurrentTimer(timer); +} + +void +RosegardenSequencerApp::setLowLatencyMode(bool ll) +{ + m_driver->setLowLatencyMode(ll); +} + +void +RosegardenSequencerApp::sequencerAlive() +{ + if (!kapp->dcopClient()-> + isApplicationRegistered(QCString(ROSEGARDEN_GUI_APP_NAME))) { + SEQUENCER_DEBUG << "RosegardenSequencerApp::sequencerAlive() - " + << "waiting for GUI to register" << endl; + return ; + } + + QByteArray 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 QString &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 QString &property, + const QString &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 QString &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 (QString err) { + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + arg << err; + kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME, + ROSEGARDEN_GUI_IFACE_NAME, + "showError(QString)", + 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<QString> +RosegardenSequencerApp::getPropertyList(int id, + const QString &property) +{ + std::vector<QString> 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<QString> +RosegardenSequencerApp::getPluginInformation() +{ + std::vector<QString> list; + + PluginFactory::enumerateAllPlugins(list); + + return list; +} + +QString +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 QString(); +} + +unsigned long +RosegardenSequencerApp::getPluginProgram(int id, const QString &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); +} + +QString +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(); + + QByteArray 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: { + QDataStream 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: { + QDataStream 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); + + QDataStream 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" diff --git a/src/sequencer/RosegardenSequencerApp.h b/src/sequencer/RosegardenSequencerApp.h new file mode 100644 index 0000000..bb72547 --- /dev/null +++ b/src/sequencer/RosegardenSequencerApp.h @@ -0,0 +1,531 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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. +*/ + +#ifndef _ROSEGARDEN_SEQUENCER_APP_H_ +#define _ROSEGARDEN_SEQUENCER_APP_H_ + +// RosegardenSequencerApp is the sequencer application for Rosegarden. +// It owns a Sequencer object which wraps the ALSA +// and JACK funtionality. At this level we deal with comms with +// the Rosegarden GUI application, the high level marshalling of data +// and main event loop of the sequencer. [rwb] +// + + +// include files for Qt +#include <qstrlist.h> + +// include files for KDE +#include <kapp.h> +#include <kmainwindow.h> +#include <kaccel.h> + +#include <qtimer.h> + +#include "base/Composition.h" +#include "gui/application/RosegardenDCOP.h" + +#include "RosegardenSequencerIface.h" + +#include "sound/MappedComposition.h" +#include "base/Event.h" +#include "sound/MappedStudio.h" +#include "sound/ExternalTransport.h" + +#include "MmappedSegment.h" +#include "SequencerMmapper.h" + +#include <deque> + +class KURL; +class KRecentFilesAction; + +namespace Rosegarden { + +// forward declaration of the RosegardenGUI classes +class RosegardenGUIDoc; +class RosegardenGUIView; +class ControlBlockMmapper; + +class MappedInstrument; +class SoundDriver; + +/** + * The sequencer application + */ +class RosegardenSequencerApp : public KMainWindow, + virtual public RosegardenSequencerIface, + public ExternalTransport +{ + Q_OBJECT + +public: + RosegardenSequencerApp(); + ~RosegardenSequencerApp(); + + // -------- START OF DCOP INTERFACE METHODS -------- + // + // + + + // Quit + virtual void quit(); + + // Based on RealTime timestamps + // + int play(const RealTime &position, + const RealTime &readAhead, + const RealTime &audioMix, + const RealTime &audioRead, + const RealTime &audioWrite, + long smallFileSize); + + // recording + int record(const RealTime &position, + const RealTime &readAhead, + const RealTime &audioMix, + const RealTime &audioRead, + const RealTime &audioWrite, + long smallFileSize, + long recordMode); + + virtual int punchOut(); + + // looping + void setLoop(const RealTime &loopStart, + const RealTime &loopEnd); + + + // Play wrapper for DCOP + // + virtual int play(long timeSec, + long timeNsec, + long readAheadSec, + long readAheadNsec, + long audioMixSec, + long audioMixNsec, + long audioReadSec, + long audioReadNsec, + long audioWriteSec, + long audioWriteNsec, + long smallFileSize); + + // Record wrapper for DCOP + // + virtual int 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); + + + // Jump to a pointer in the playback (uses longs instead + // of RealTime for DCOP) + // + // + virtual void jumpTo(long posSec, long posNsec); + + // Set a loop on the Sequencer + // + virtual void setLoop(long loopStartSec, long loopStartNsec, + long loopEndSec, long loopEndNsec); + + // Return the Sound system status (audio/MIDI) + // + virtual unsigned int getSoundDriverStatus(const QString &guiVersion); + + // Add and remove Audio files on the sequencer + // + virtual int addAudioFile(const QString &fileName, int id); + virtual int removeAudioFile(int id); + + // Deletes all the audio files and clears down any flapping i/o handles + // + virtual void clearAllAudioFiles(); + + // stops the sequencer + // + virtual void stop(); + + // Set a MappedInstrument at the Sequencer + // + virtual void setMappedInstrument(int type, unsigned char channel, + unsigned int id); + + // The sequencer will process the MappedComposition as soon as it + // gets the chance. + // + virtual void processSequencerSlice(MappedComposition mC); + + // Yeuch! + // + virtual void processMappedEvent(unsigned int id, + int type, + unsigned char pitch, + unsigned char velocity, + long absTimeSec, + long absTimeNsec, + long durationSec, + long durationNsec, + long audioStartMarkerSec, + long audioStartMarkerNsec); + + // And now do it properly + // + virtual void processMappedEvent(MappedEvent mE); + + virtual unsigned int getDevices(); + virtual MappedDevice getMappedDevice(unsigned int id); + + virtual int canReconnect(int deviceType); + virtual unsigned int addDevice(int type, unsigned int direction); + virtual void removeDevice(unsigned int id); + virtual void renameDevice(unsigned int id, QString name); + virtual unsigned int getConnections(int type, unsigned int direction); + virtual QString getConnection(int type, unsigned int direction, + unsigned int connectionNo); + virtual void setConnection(unsigned int deviceId, QString connection); + virtual void setPlausibleConnection(unsigned int deviceId, + QString idealConnection); + + virtual unsigned int getTimers(); + virtual QString getTimer(unsigned int n); + virtual QString getCurrentTimer(); + virtual void setCurrentTimer(QString timer); + + virtual void setLowLatencyMode(bool); + + // Audio latencies + // + virtual MappedRealTime getAudioPlayLatency(); + virtual MappedRealTime getAudioRecordLatency(); + + // Set a MappedObject + // + virtual void setMappedProperty(int id, + const QString &property, + float value); + + // Set many properties on many MappedObjects + // + virtual void setMappedProperties(const MappedObjectIdList &ids, + const MappedObjectPropertyList &properties, + const MappedObjectValueList &values); + + // Set a MappedObject to a string + // + virtual void setMappedProperty(int id, + const QString &property, + const QString &value); + + // Set a MappedObject to a property list + // + virtual void setMappedPropertyList(int id, + const QString &property, + const MappedObjectPropertyList &values); + + // Get a MappedObject for a type + // + virtual int getMappedObjectId(int type); + + // Get a Property list from an Object + // + virtual std::vector<QString> getPropertyList(int id, + const QString &property); + + virtual std::vector<QString> getPluginInformation(); + + virtual QString getPluginProgram(int id, int bank, int program); + + virtual unsigned long getPluginProgram(int id, const QString &name); + + // Set a plugin port + // + virtual void setMappedPort(int pluginId, + unsigned long portId, + float value); + + virtual float getMappedPort(int pluginId, + unsigned long portId); + + // Create a MappedObject + virtual int createMappedObject(int type); + + // Destroy an object + // + virtual int destroyMappedObject(int id); + + // Connect two objects + // + virtual void connectMappedObjects(int id1, int id2); + + // Disconnect two objects + // + virtual void disconnectMappedObjects(int id1, int id2); + + // Disconnect an object from everything + // + virtual void disconnectMappedObject(int id); + + // Sample rate + // + virtual unsigned int getSampleRate() const; + + // Clear the studio + // + virtual void clearStudio(); + + // Debug stuff, to check MmappedSegment::iterator + virtual void dumpFirstSegment(); + + virtual void remapSegment(const QString& filename, size_t newSize); + virtual void addSegment(const QString& filename); + virtual void deleteSegment(const QString& filename); + virtual void closeAllSegments(); + virtual void remapTracks(); + + // Set Quarter note length + // + virtual void setQuarterNoteLength(long timeSec, long timeNsec); + + // Get a status report + // + virtual QString getStatusLog(); + + // + // + // + // -------- END OF DCOP INTERFACE -------- + + + + + void setStatus(TransportStatus status) + { m_transportStatus = status; } + TransportStatus getStatus() { return m_transportStatus; } + + // Process the first chunk of Sequencer events + bool startPlaying(); + + // Process all subsequent events + bool keepPlaying(); + + // Update internal clock and send GUI position pointer movement + void updateClocks(); + + bool checkExternalTransport(); + + // Sends status changes up to GUI + void notifySequencerStatus(); + + // Send latest slice information back to GUI for display + void notifyVisuals(MappedComposition *mC); + + // These two methods process any pending MIDI or audio + // and send them up to the gui for storage and display + // + void processRecordedMidi(); + void processRecordedAudio(); + + // Called during stopped or playing operation to process + // any pending incoming MIDI events that aren't being + // recorded (i.e. for display in Transport or on Mixer) + // + void processAsynchronousEvents(); + + // Sleep for the given time, approximately. Called from the main + // loop in order to lighten CPU load (i.e. the timing quality of + // the sequencer does not depend on this being accurate). A good + // implementation of this call would return right away when an + // incoming MIDI event needed to be handled. + // + void sleep(const RealTime &rt); + + // Removes from a MappedComposition the events not matching + // the supplied filer. + // + void applyFiltering(MappedComposition *mC, + MidiFilter filter, + bool filterControlDevice); + + // This method assigns an Instrument to each MappedEvent + // belongin to the MappedComposition, and sends the + // transformed events to the driver to be played. + // + void routeEvents(MappedComposition *mC, bool useSelectedTrack); + + // Are we looping? + // + bool isLooping() const { return !(m_loopStart == m_loopEnd); } + + // the call itself + void sequencerAlive(); + + /* + // Audio latencies + // + RealTime getAudioPlaybackLatency() + { return m_audioPlayLatency; } + void setAudioPlaybackLatency(const RealTime &latency) + { m_audioPlayLatency = latency; } + + RealTime getAudioRecordLatency() + { return m_audioRecordLatency; } + void setAudioRecordLatency(const RealTime &latency) + { m_audioRecordLatency = latency; } + */ + + // Initialise the virtual studio at this end of the link + // + void initialiseStudio(); + + + // --------- EXTERNAL TRANSPORT INTERFACE METHODS -------- + // + // Whereas the DCOP interface (above) is for the GUI to call to + // make the sequencer follow its wishes, this interface is for + // external clients to call (via some low-level audio callback) + // and requires sychronising with the GUI. + + TransportToken transportChange(TransportRequest); + TransportToken transportJump(TransportRequest, RealTime); + bool isTransportSyncComplete(TransportToken token); + TransportToken getInvalidTransportToken() const { return 0; } + +public slots: + + // Check for new clients - on timeout + // + void slotCheckForNewClients(); + +protected: + + // get events whilst handling loop + // + void fetchEvents(MappedComposition &, + const RealTime &start, + const RealTime &end, + bool firstFetch); + + // just get a slice of events between markers + // + void getSlice(MappedComposition &, + const RealTime &start, + const RealTime &end, + bool firstFetch); + + // adjust event times according to relative instrument latencies + // + void applyLatencyCompensation(MappedComposition &); + + // mmap-related stuff + MmappedSegment* mmapSegment(const QString&); + void cleanupMmapData(); + void initMetaIterator(); + + void rationalisePlayingAudio(); + void setEndOfCompReached(bool e) { m_isEndOfCompReached = e; } + bool isEndOfCompReached() { return m_isEndOfCompReached; } + void incrementTransportToken(); + + //--------------- Data members --------------------------------- + + SoundDriver *m_driver; + TransportStatus m_transportStatus; + + // Position pointer + RealTime m_songPosition; + RealTime m_lastFetchSongPosition; + + RealTime m_readAhead; + RealTime m_audioMix; + RealTime m_audioRead; + RealTime m_audioWrite; + int m_smallFileSize; + + /* + + // Not required at the sequencer + + // Two more latencies for audio play and record - when we + // use an unsynchronised audio and MIDI system such as + // ALSA and JACK we need to use these additional values + // to help time-keeping. + // + RealTime m_audioPlayLatency; + RealTime m_audioRecordLatency; + + */ + + + RealTime m_loopStart; + RealTime m_loopEnd; + + std::vector<MappedInstrument*> m_instruments; + + // MappedStudio holds all of our session-persistent information - + // sliders and what have you. It's also streamable over DCOP + // so you can reconstruct it at either end of the link for + // presentation, storage etc. + // + MappedStudio *m_studio; + + // Slice revert storage + // + RealTime m_oldSliceSize; + QTimer *m_sliceTimer; + + // Timer to check for new clients + // + QTimer *m_newClientTimer; + + // mmap segments + // + QString m_segmentFilesPath; + MmappedSegmentsMetaIterator::mmappedsegments m_mmappedSegments; + MmappedSegmentsMetaIterator* m_metaIterator; + RealTime m_lastStartTime; + + MappedComposition m_mC; + ControlBlockMmapper *m_controlBlockMmapper; + SequencerMmapper m_sequencerMapper; + + typedef std::pair<TransportRequest, RealTime> TransportPair; + std::deque<TransportPair> m_transportRequests; + TransportToken m_transportToken; + + bool m_isEndOfCompReached; +}; + +} + +#endif // _ROSEGARDEN_SEQUENCER_APP_H_ diff --git a/src/sequencer/RosegardenSequencerIface.h b/src/sequencer/RosegardenSequencerIface.h new file mode 100644 index 0000000..47c0215 --- /dev/null +++ b/src/sequencer/RosegardenSequencerIface.h @@ -0,0 +1,364 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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. +*/ + +#ifndef _ROSEGARDENSEQUENCERIFACE_H_ +#define _ROSEGARDENSEQUENCERIFACE_H_ + +#include <dcopobject.h> +// #include <qvaluevector.h> +// #include <qpair.h> + +#include "gui/application/RosegardenDCOP.h" + +#include "base/Event.h" +#include "sound/MappedComposition.h" +#include "sound/MappedEvent.h" +#include "base/Instrument.h" +#include "sound/MappedDevice.h" +#include "sound/MappedRealTime.h" +#include "sound/MappedStudio.h" +#include "sound/MappedCommon.h" + +namespace Rosegarden { + +class RosegardenSequencerIface : virtual public DCOPObject +{ + K_DCOP +public: + k_dcop: + + // close the sequencer + // + virtual void quit() = 0; + + + + // play from a given time with given parameters + // + virtual int play(long timeSec, + long timeNsec, + long readAheadSec, + long readAheadNsec, + long audioMixSec, + long audioMixNsec, + long audioReadSec, + long audioReadNsec, + long audioWriteSec, + long audioWriteNsec, + long smallFileSize) = 0; + + // record from a given time with given parameters + // + virtual int 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) = 0; + + // stop the sequencer + // + virtual ASYNC stop() = 0; + + // punch out from recording to playback + // + virtual int punchOut() = 0; + + // Set the sequencer to a given time + // + virtual void jumpTo(long posSec, long posNsec) = 0; + + // Set a loop on the sequencer + // + virtual void setLoop(long loopStartSec, + long loopStartNsec, + long loopEndSec, + long loopEndNsec) = 0; + + // Get the status of the Sequencer + // + virtual unsigned int getSoundDriverStatus(const QString &guiVersion) = 0; + + // Add and delete audio files on the Sequencer + // + virtual int addAudioFile(const QString &fileName, int id) = 0; + virtual int removeAudioFile(int id) = 0; + virtual void clearAllAudioFiles() = 0; + + // Single set function as the MappedInstrument is so lightweight. + // Any mods on the GUI are sent only through this method. + // + virtual void setMappedInstrument(int type, + unsigned char channel, + unsigned int id) = 0; + + // The GUI can use this method to process an immediate selection + // of MappedEvents (Program Changes, SysExs, async Events etc). + // + virtual void processSequencerSlice(MappedComposition mC) = 0; + + + // Horrible ugly ugly ugly interface for single MappedEvents + // just until we implement the proper MappedEvent interface + // + virtual void processMappedEvent(unsigned int id, + int type, + unsigned char pitch, + unsigned char velocity, + long absTimeSec, + long absTimeNsec, + long durationSec, + long durationNsec, + long audioStartMarkerSec, + long audioStartMarkerNsec) = 0; + + // The proper implementation + // + virtual void processMappedEvent(MappedEvent mE) = 0; + + // Return device id following last existing one -- you can treat + // this as "number of devices" but there might be some holes if + // devices were deleted, which you will recognise because + // getMappedDevice(id) will return a device with id NO_DEVICE + // + virtual unsigned int getDevices() = 0; + + // Return device by number + // + virtual MappedDevice getMappedDevice(unsigned int id) = 0; + + // Query whether the driver implements device reconnection. + // Returns a non-zero value if the addDevice, removeDevice, + // getConnections, getConnection and setConnection methods + // may be used with devices of the given type. + // + virtual int canReconnect(int deviceType) = 0; + + // Create a device of the given type and direction (corresponding + // to MidiDevice::DeviceDirection enum) and return its id. + // The device will have no connection by default. Direction is + // currently ignored for non-MIDI devices. + // Do not use this unless canReconnect(type) returned true. + // + virtual unsigned int addDevice(int type, unsigned int direction) = 0; + + // Remove the device of the given id. + // Ignored if driver does not permit changing the number of devices + // (i.e. if canReconnect(type) would return false when given the + // type of the supplied device). + // + virtual void removeDevice(unsigned int id) = 0; + + // Rename the given device. + // Ignored if the driver does not permit this operation. + // + virtual void renameDevice(unsigned int id, QString name) = 0; + + // Return the number of permissible connections for a device of + // the given type and direction (corresponding to MidiDevice:: + // DeviceDirection enum). Direction is ignored for non-MIDI devices. + // Returns zero if devices of this type are non-reconnectable + // (i.e. if canReconnect(type) would return false). + // + virtual unsigned int getConnections(int type, unsigned int direction) = 0; + + // Return one of the set of permissible connections for a device of + // the given type and direction (corresponding to MidiDevice:: + // DeviceDirection enum). Direction is ignored for non-MIDI devices. + // Returns the empty string for invalid parameters. + // + virtual QString getConnection(int type, + unsigned int direction, + unsigned int connectionNo) = 0; + + // Reconnect a particular device. + // Ignored if driver does not permit reconnections or the connection + // is not one of the permissible set for that device. + // + virtual void setConnection(unsigned int deviceId, QString connection) = 0; + + // Reconnect a device to a particular connection or to the closest + // thing to that connection currently available (using some heuristic). + // Ignored if driver does not permit reconnections. + // + virtual void setPlausibleConnection(unsigned int deviceId, + QString idealConnection) = 0; + + // Return the number of different timers we are capable of + // sychronising against. This may return 0 if the driver has no + // ability to change the current timer. + // + virtual unsigned int getTimers() = 0; + + // Return the name of a timer from the available set (where + // n is between 0 and the return value from getTimers() - 1). + // + virtual QString getTimer(unsigned int n) = 0; + + // Return the name of the timer we are currently synchronising + // against. + // + virtual QString getCurrentTimer() = 0; + + // Set the timer we are currently synchronising against. + // Invalid arguments are simply ignored. + // + virtual void setCurrentTimer(QString timer) = 0; + + virtual void setLowLatencyMode(bool lowLatMode) = 0; + + // Fetch audio play latencies + // + virtual MappedRealTime getAudioPlayLatency() = 0; + virtual MappedRealTime getAudioRecordLatency() = 0; + + // Set a property on a MappedObject + // + virtual void setMappedProperty(int id, + const QString &property, + float value) = 0; + + // Set many properties on many MappedObjects + // + virtual void setMappedProperties(const MappedObjectIdList &ids, + const MappedObjectPropertyList &properties, + const MappedObjectValueList &values) = 0; + + // Set a string property on a MappedObject + // + virtual void setMappedProperty(int id, + const QString &property, + const QString &value) = 0; + + // Set a MappedObject to a property list + // + virtual void setMappedPropertyList( + int id, + const QString &property, + const MappedObjectPropertyList &values) = 0; + + // Get a mapped object id for a object type + // + virtual int getMappedObjectId(int type) = 0; + + // Get a list of properties of a certain type from an object + // + virtual std::vector<QString> getPropertyList(int id, + const QString &property) = 0; + + // Get a list of available plugins + // + virtual std::vector<QString> getPluginInformation() = 0; + + // Nasty hack: program name/number mappings are one thing that + // mapped object properties can't cope with + // + virtual QString getPluginProgram(int mappedId, int bank, int program) = 0; + + // Nastier hack: return value is bank << 16 + program + // + virtual unsigned long getPluginProgram(int mappedId, const QString &name) = 0; + + // Cheat - we can't use a call (getPropertyList) during playback + // so we use this method to set port N on plugin X. + // + virtual void setMappedPort(int pluginIn, + unsigned long id, + float value) = 0; + + virtual float getMappedPort(int pluginIn, + unsigned long id) = 0; + + // Create a (transient, writeable) object + // + virtual int createMappedObject(int type) = 0; + + // Destroy an object (returns a bool but for KDE2 DCOP compat we + // use an int of course). + // + virtual int destroyMappedObject(int id) = 0; + + // Connect two objects + // + virtual void connectMappedObjects(int id1, int id2) = 0; + + // Disconnect two objects + // + virtual void disconnectMappedObjects(int id1, int id2) = 0; + + // Disconnect an object from everything + // + virtual void disconnectMappedObject(int id) = 0; + + // Driver sample rate + // + virtual unsigned int getSampleRate() const = 0; + + // Initialise/Reinitialise the studio back down to read only objects + // and set to defaults. + // + virtual void clearStudio() = 0; + + // Allow the GUI to tell the sequence the duration of a quarter + // note when the TEMPO changes - this is to allow the sequencer + // to generate MIDI clock (at 24 PPQN). + // + virtual void setQuarterNoteLength(long timeSec, long timeNsec) = 0; + + // Return a (potentially lengthy) human-readable status log + // + virtual QString getStatusLog() = 0; + + // Debug stuff, to check MmappedSegment::iterator + virtual void dumpFirstSegment() = 0; + + /// Remap a segment while playing + virtual void remapSegment(const QString& filename, size_t newSize) = 0; + + /// Add a segment while playing + virtual void addSegment(const QString& filename) = 0; + + /// Delete a segment while playing + virtual void deleteSegment(const QString& filename) = 0; + + /// Close all mmapped segments + virtual void closeAllSegments() = 0; + + /** Update mute (etc) statuses while playing. The sequencer handles + this automatically (with no need for this call) for MIDI events, + but it needs to be prodded when an already-playing audio segment + drops in or out. + */ + virtual void remapTracks() = 0; +}; + +} + +#endif // _ROSEGARDENSEQUENCERIFACE_H_ diff --git a/src/sequencer/SequencerMmapper.cpp b/src/sequencer/SequencerMmapper.cpp new file mode 100644 index 0000000..3a634ba --- /dev/null +++ b/src/sequencer/SequencerMmapper.cpp @@ -0,0 +1,146 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <unistd.h> +#include <errno.h> + +#include <kstddirs.h> + +#include <qfile.h> + +#include "SequencerMmapper.h" +#include "misc/Debug.h" + +#include "base/RealTime.h" +#include "base/Exception.h" +#include "sound/MappedEvent.h" +#include "sound/MappedComposition.h" + +namespace Rosegarden +{ + +// Seems not to be properly defined under some gcc 2.95 setups +#ifndef MREMAP_MAYMOVE +#define MREMAP_MAYMOVE 1 +#endif + +SequencerMmapper::SequencerMmapper(): + m_fileName(createFileName()), + m_fd( -1), + m_mmappedBuffer(0), + m_mmappedSize(sizeof(SequencerDataBlock)) +{ + // just in case + QFile::remove + (m_fileName); + + m_fd = ::open(m_fileName.latin1(), + O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + + if (m_fd < 0) { + SEQUENCER_DEBUG << "SequencerMmapper : Couldn't open " << m_fileName + << endl; + throw Exception("Couldn't open " + std::string(m_fileName.data())); + } + + setFileSize(m_mmappedSize); + + // + // mmap() file for writing + // + m_mmappedBuffer = ::mmap(0, m_mmappedSize, + PROT_READ | PROT_WRITE, + MAP_SHARED, m_fd, 0); + + if (m_mmappedBuffer == (void*) - 1) { + SEQUENCER_DEBUG << + QString("mmap failed : (%1) %2\n").arg(errno).arg(strerror(errno)); + throw Exception("mmap failed"); + } + + SEQUENCER_DEBUG << "SequencerMmapper : mmap size : " << m_mmappedSize + << " at " << (void*)m_mmappedBuffer << endl; + + // initialise + init(); +} + +SequencerMmapper::~SequencerMmapper() +{ + ::munmap(m_mmappedBuffer, m_mmappedSize); + ::close(m_fd); + QFile::remove + (m_fileName); +} + +void +SequencerMmapper::init() +{ + SEQUENCER_DEBUG << "SequencerMmapper::init()\n"; + + m_sequencerDataBlock = new (m_mmappedBuffer) + SequencerDataBlock(true); + + ::msync(m_mmappedBuffer, m_mmappedSize, MS_ASYNC); +} + +void +SequencerMmapper::setFileSize(size_t size) +{ + SEQUENCER_DEBUG << "SequencerMmapper : setting size of " + << m_fileName << " to " << size << endl; + // rewind + ::lseek(m_fd, 0, SEEK_SET); + + // enlarge the file + // (seek() to wanted size, then write a byte) + // + if (::lseek(m_fd, size - 1, SEEK_SET) == -1) { + std::cerr << "WARNING: SequencerMmapper : Couldn't lseek in " << m_fileName + << " to " << size << std::endl; + throw Exception("lseek failed"); + } + + if (::write(m_fd, "\0", 1) != 1) { + std::cerr << "WARNING: SequencerMmapper : Couldn't write byte in " + << m_fileName << std::endl; + throw Exception("write failed"); + } +} + +QString +SequencerMmapper::createFileName() +{ + return KGlobal::dirs()->resourceDirs("tmp").last() + + "/rosegarden_sequencer_timing_block"; +} + +} + diff --git a/src/sequencer/SequencerMmapper.h b/src/sequencer/SequencerMmapper.h new file mode 100644 index 0000000..f35c2a7 --- /dev/null +++ b/src/sequencer/SequencerMmapper.h @@ -0,0 +1,103 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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. +*/ + +#ifndef _SEQUENCERMAPPER_H_ +#define _SEQUENCERMAPPER_H_ + +#include "sound/SequencerDataBlock.h" +#include "base/RealTime.h" + +namespace Rosegarden +{ + +class MappedEvent; +class MappedComposition; + + +class SequencerMmapper +{ +public: + SequencerMmapper(); + ~SequencerMmapper(); + + void updatePositionPointer(RealTime time) { + m_sequencerDataBlock->setPositionPointer(time); + } + + void updateVisual(MappedEvent *ev) { + m_sequencerDataBlock->setVisual(ev); + } + + void updateRecordingBuffer(MappedComposition *mC) { + m_sequencerDataBlock->addRecordedEvents(mC); + } + + void setTrackLevel(TrackId track, const LevelInfo &info) { + m_sequencerDataBlock->setTrackLevel(track, info); + } + + void setInstrumentLevel(InstrumentId id, + const LevelInfo &info) { + m_sequencerDataBlock->setInstrumentLevel(id, info); + } + + void setInstrumentRecordLevel(InstrumentId id, + const LevelInfo &info) { + m_sequencerDataBlock->setInstrumentRecordLevel(id, info); + } + + void setSubmasterLevel(int submaster, + const LevelInfo &info) { + m_sequencerDataBlock->setSubmasterLevel(submaster, info); + } + + void setMasterLevel(const LevelInfo &info) { + m_sequencerDataBlock->setMasterLevel(info); + } + + SequencerDataBlock *getSequencerDataBlock() { + return m_sequencerDataBlock; + } + void setControlBlock(ControlBlock *cb) { + m_sequencerDataBlock->setControlBlock(cb); + } + +protected: + void init(); + void setFileSize(size_t); + QString createFileName(); + + //--------------- Data members --------------------------------- + // + QString m_fileName; + int m_fd; + void* m_mmappedBuffer; + size_t m_mmappedSize; + SequencerDataBlock *m_sequencerDataBlock; +}; + +} + +#endif // _SEQUENCERMAPPER_H_ diff --git a/src/sequencer/main.cpp b/src/sequencer/main.cpp new file mode 100644 index 0000000..aee5bc5 --- /dev/null +++ b/src/sequencer/main.cpp @@ -0,0 +1,246 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 "RosegardenSequencerApp.h" + +#include <signal.h> +#include <iostream> +#include <unistd.h> +#include <sys/time.h> + +#include <kcmdlineargs.h> +#include <kaboutdata.h> +#include <klocale.h> +#include <dcopclient.h> + +#include "base/Profiler.h" +#include "sound/MappedComposition.h" + +#include "gui/application/RosegardenDCOP.h" +#include "misc/Debug.h" + +using std::cout; +using std::cerr; +using std::endl; + +using namespace Rosegarden; + +static const char *description = I18N_NOOP("RosegardenSequencer"); +static RosegardenSequencerApp *roseSeq = 0; + +static KCmdLineOptions options[] = + { + // { "+[File]", I18N_NOOP("file to open"), 0 }, + // INSERT YOUR COMMANDLINE OPTIONS HERE + { "+[playback_1 playback_2 capture_1 capture_2]", + I18N_NOOP("JACK playback and capture ports"), 0 }, + { 0, 0, 0 } + }; + +static bool _exiting = false; +static sigset_t _signals; + +static void +signalHandler(int /*sig*/) +{ + _exiting = true; + std::cerr << "Is that the time!?" << endl; +} + +int main(int argc, char *argv[]) +{ + srandom((unsigned int)time(0) * (unsigned int)getpid()); + + // Block signals during startup, so that child threads (inheriting + // this mask) ignore them; then after startup we can unblock them + // for this thread only. This trick picked up from the jackd code. + sigemptyset (&_signals); + sigaddset(&_signals, SIGHUP); + sigaddset(&_signals, SIGINT); + sigaddset(&_signals, SIGQUIT); + sigaddset(&_signals, SIGPIPE); + sigaddset(&_signals, SIGTERM); + sigaddset(&_signals, SIGUSR1); + sigaddset(&_signals, SIGUSR2); + pthread_sigmask(SIG_BLOCK, &_signals, 0); + + KAboutData aboutData( "rosegardensequencer", + I18N_NOOP("RosegardenSequencer"), + VERSION, description, KAboutData::License_GPL, + "(c) 2000-2008, Guillaume Laurent, Chris Cannam, Richard Bown"); + aboutData.addAuthor("Guillaume Laurent, Chris Cannam, Richard Bown", 0, "[email protected], [email protected], [email protected]"); + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + + // Parse cmd line args + // + /*KCmdLineArgs *args =*/ + KCmdLineArgs::parsedArgs(); + KApplication app; + + if (app.isRestored()) { + SEQUENCER_DEBUG << "RosegardenSequencer - we're being session-restored - that's not supposed to happen\n"; + app.quit(); // don't do session restore -- GUI will start a sequencer + } else { + roseSeq = new RosegardenSequencerApp; + } + + QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit())); + + app.disableSessionManagement(); // we don't want to be + // saved/restored by session + // management, only run by the GUI + + // Started OK + // + SEQUENCER_DEBUG << "RosegardenSequencer - started OK" << endl; + + // Register signal handlers and unblock signals + // + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + signal(SIGHUP, signalHandler); + signal(SIGQUIT, signalHandler); + pthread_sigmask(SIG_UNBLOCK, &_signals, 0); + + // Now we can enter our specialised event loop. + // For each pass through we wait for some pending + // events. We check status on the way through and + // act accordingly. DCOP events fire back and + // forth processed in the event loop changing + // state and hopefully controlling and providing + // feedback. We also put in some sleep time to + // make sure the loop doesn't eat up all the + // processor - we're not in that much of a rush! + // + TransportStatus lastSeqStatus = roseSeq->getStatus(); + + RealTime sleepTime = RealTime(0, 10000000); + + while (!_exiting && roseSeq && roseSeq->getStatus() != QUIT) { + + bool atLeisure = true; + + switch (roseSeq->getStatus()) { + case STARTING_TO_PLAY: + if (!roseSeq->startPlaying()) { + // send result failed and stop Sequencer + roseSeq->setStatus(STOPPING); + } else { + roseSeq->setStatus(PLAYING); + } + break; + + case PLAYING: + if (!roseSeq->keepPlaying()) { + // there's a problem or the piece has + // finished - so stop playing + roseSeq->setStatus(STOPPING); + } else { + // process any async events + // + roseSeq->processAsynchronousEvents(); + } + break; + + case STARTING_TO_RECORD: + if (!roseSeq->startPlaying()) { + roseSeq->setStatus(STOPPING); + } else { + roseSeq->setStatus(RECORDING); + } + break; + + case RECORDING: + if (!roseSeq->keepPlaying()) { + // there's a problem or the piece has + // finished - so stop playing + roseSeq->setStatus(STOPPING); + } else { + // Now process any incoming MIDI events + // and return them to the gui + // + roseSeq->processRecordedMidi(); + + // Now process any incoming audio + // and return it to the gui + // + roseSeq->processRecordedAudio(); + + // Still process these so we can send up + // audio levels as MappedEvents + // + roseSeq->processAsynchronousEvents(); + } + break; + + case STOPPING: + // There's no call to roseSeq to actually process the + // stop, because this arises from a DCOP call from the GUI + // direct to roseSeq to start with + roseSeq->setStatus(STOPPED); + SEQUENCER_DEBUG << "RosegardenSequencer - Stopped" << endl; + break; + + case RECORDING_ARMED: + SEQUENCER_DEBUG << "RosegardenSequencer - " + << "Sequencer can't enter \"" + << "RECORDING_ARMED\" state - " + << "internal error" + << endl; + break; + + case STOPPED: + default: + roseSeq->processAsynchronousEvents(); + break; + } + + // Update internal clock and send pointer position + // change event to GUI - this is the heartbeat of + // the Sequencer - it doesn't tick over without + // this call. + // + // Also attempt to send the MIDI clock at this point. + // + roseSeq->updateClocks(); + + // we want to process transport changes immediately + if (roseSeq->checkExternalTransport()) { + atLeisure = false; + } else if (lastSeqStatus != roseSeq->getStatus()) { + SEQUENCER_DEBUG << "Sequencer status changed from " << lastSeqStatus << " to " << roseSeq->getStatus() << endl; + roseSeq->notifySequencerStatus(); + lastSeqStatus = roseSeq->getStatus(); + atLeisure = false; + } + + app.processEvents(); + if (atLeisure) + roseSeq->sleep(sleepTime); + } + + delete roseSeq; + + std::cerr << "Toodle-pip." << endl; + return 0; +} + |