diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/document | |
download | rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip |
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/document')
30 files changed, 13564 insertions, 0 deletions
diff --git a/src/document/BasicCommand.cpp b/src/document/BasicCommand.cpp new file mode 100644 index 0000000..db56564 --- /dev/null +++ b/src/document/BasicCommand.cpp @@ -0,0 +1,171 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "BasicCommand.h" + +#include "base/Segment.h" +#include <qstring.h> + +namespace Rosegarden +{ + +BasicCommand::BasicCommand(const QString &name, Segment &segment, + timeT start, timeT end, bool bruteForceRedo) : + KNamedCommand(name), + m_startTime(calculateStartTime(start, segment)), + m_endTime(calculateEndTime(end, segment)), + m_segment(segment), + m_savedEvents(segment.getType(), m_startTime), + m_doBruteForceRedo(false), + m_redoEvents(0) +{ + if (m_endTime == m_startTime) ++m_endTime; + + if (bruteForceRedo) { + m_redoEvents = new Segment(segment.getType(), m_startTime); + } +} + +BasicCommand::~BasicCommand() +{ + m_savedEvents.clear(); + if (m_redoEvents) m_redoEvents->clear(); + delete m_redoEvents; +} + +timeT +BasicCommand::calculateStartTime(timeT given, Segment &segment) +{ + timeT actual = given; + Segment::iterator i = segment.findTime(given); + + while (i != segment.end() && (*i)->getAbsoluteTime() == given) { + timeT notation = (*i)->getNotationAbsoluteTime(); + if (notation < given) actual = notation; + ++i; + } + + return actual; +} + +timeT +BasicCommand::calculateEndTime(timeT given, Segment &segment) +{ + timeT actual = given; + Segment::iterator i = segment.findTime(given); + + while (i != segment.end() && (*i)->getAbsoluteTime() == given) { + timeT notation = (*i)->getNotationAbsoluteTime(); + if (notation > given) actual = notation; + ++i; + } + + return actual; +} + +Rosegarden::Segment& BasicCommand::getSegment() +{ + return m_segment; +} + +Rosegarden::timeT BasicCommand::getRelayoutEndTime() +{ + return getEndTime(); +} + +void +BasicCommand::beginExecute() +{ + copyTo(&m_savedEvents); +} + +void +BasicCommand::execute() +{ + beginExecute(); + + if (!m_doBruteForceRedo) { + + modifySegment(); + + } else { + copyFrom(m_redoEvents); + } + + m_segment.updateRefreshStatuses(getStartTime(), getRelayoutEndTime()); + RG_DEBUG << "BasicCommand(" << name() << "): updated refresh statuses " + << getStartTime() << " -> " << getRelayoutEndTime() << endl; +} + +void +BasicCommand::unexecute() +{ + if (m_redoEvents) { + copyTo(m_redoEvents); + m_doBruteForceRedo = true; + } + + copyFrom(&m_savedEvents); + + m_segment.updateRefreshStatuses(getStartTime(), getRelayoutEndTime()); +} + +void +BasicCommand::copyTo(Rosegarden::Segment *events) +{ + RG_DEBUG << "BasicCommand(" << name() << ")::copyTo: " << &m_segment << " to " + << events << ", range (" + << m_startTime << "," << m_endTime + << ")" << endl; + + Segment::iterator from = m_segment.findTime(m_startTime); + Segment::iterator to = m_segment.findTime(m_endTime); + + for (Segment::iterator i = from; i != m_segment.end() && i != to; ++i) { +// RG_DEBUG << "Found event of type " << (*i)->getType() << " and duration " << (*i)->getDuration() << endl; + events->insert(new Event(**i)); + } +} + +void +BasicCommand::copyFrom(Rosegarden::Segment *events) +{ + RG_DEBUG << "BasicCommand(" << name() << ")::copyFrom: " << events << " to " + << &m_segment << ", range (" + << m_startTime << "," << m_endTime + << ")" << endl; + + m_segment.erase(m_segment.findTime(m_startTime), + m_segment.findTime(m_endTime)); + + for (Segment::iterator i = events->begin(); i != events->end(); ++i) { +// RG_DEBUG << "Found event of type " << (*i)->getType() << " and duration " << (*i)->getDuration() << endl; + m_segment.insert(new Event(**i)); + } + + events->clear(); +} + +} diff --git a/src/document/BasicCommand.h b/src/document/BasicCommand.h new file mode 100644 index 0000000..eab9ea0 --- /dev/null +++ b/src/document/BasicCommand.h @@ -0,0 +1,112 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_BASICCOMMAND_H_ +#define _RG_BASICCOMMAND_H_ + +#include "base/Segment.h" +#include <kcommand.h> +#include "base/Event.h" +#include "misc/Debug.h" + +class QString; + + +namespace Rosegarden +{ + + + +/** + * BasicCommand is an abstract subclass of Command that manages undo, + * redo and notification of changes within a contiguous region of a + * single Rosegarden Segment, by brute force. When a subclass + * of BasicCommand executes, it stores a copy of the events that are + * modified by the command, ready to be restored verbatim on undo. + */ + +class BasicCommand : public KNamedCommand +{ +public: + virtual ~BasicCommand(); + + virtual void execute(); + virtual void unexecute(); + + virtual Segment &getSegment(); + + timeT getStartTime() { return m_startTime; } + timeT getEndTime() { return m_endTime; } + virtual timeT getRelayoutEndTime(); + +protected: + /** + * You should pass "bruteForceRedoRequired = true" if your + * subclass's implementation of modifySegment uses discrete + * event pointers or segment iterators to determine which + * events to modify, in which case it won't work when + * replayed for redo because the pointers may no longer be + * valid. In which case, BasicCommand will implement redo + * much like undo, and will only call your modifySegment + * the very first time the command object is executed. + * + * It is always safe to pass bruteForceRedoRequired true, + * it's just normally a waste of memory. + */ + BasicCommand(const QString &name, + Segment &segment, + timeT start, timeT end, + bool bruteForceRedoRequired = false); + + virtual void modifySegment() = 0; + + virtual void beginExecute(); + +private: + //--------------- Data members --------------------------------- + + void copyTo(Segment *); + void copyFrom(Segment *); + + timeT calculateStartTime(timeT given, + Segment &segment); + timeT calculateEndTime(timeT given, + Segment &segment); + + timeT m_startTime; + timeT m_endTime; + + Segment &m_segment; + Segment m_savedEvents; + + bool m_doBruteForceRedo; + Segment *m_redoEvents; +}; + + + +} + +#endif diff --git a/src/document/BasicSelectionCommand.cpp b/src/document/BasicSelectionCommand.cpp new file mode 100644 index 0000000..2216223 --- /dev/null +++ b/src/document/BasicSelectionCommand.cpp @@ -0,0 +1,66 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "BasicSelectionCommand.h" + +#include "base/Segment.h" +#include "base/Selection.h" +#include "BasicCommand.h" +#include <qstring.h> + + +namespace Rosegarden +{ + +BasicSelectionCommand::BasicSelectionCommand(const QString &name, + EventSelection &selection, + bool bruteForceRedo) : + BasicCommand(name, + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime(), + bruteForceRedo) +{ + // nothing +} + +BasicSelectionCommand::BasicSelectionCommand(const QString &name, + Segment &segment, + bool bruteForceRedo) : + BasicCommand(name, + segment, + segment.getStartTime(), + segment.getEndMarkerTime(), + bruteForceRedo) +{ + // nothing +} + +BasicSelectionCommand::~BasicSelectionCommand() +{ + // nothing +} + +} diff --git a/src/document/BasicSelectionCommand.h b/src/document/BasicSelectionCommand.h new file mode 100644 index 0000000..825955d --- /dev/null +++ b/src/document/BasicSelectionCommand.h @@ -0,0 +1,67 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_BASICSELECTIONCOMMAND_H_ +#define _RG_BASICSELECTIONCOMMAND_H_ + +#include "BasicCommand.h" + + +class QString; + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +/** + * Subclass of BasicCommand that manages the brute-force undo and redo + * extends based on a given selection. + */ + +class BasicSelectionCommand : public BasicCommand +{ +public: + virtual ~BasicSelectionCommand(); + +protected: + /// selection from segment + BasicSelectionCommand(const QString &name, + EventSelection &selection, + bool bruteForceRedoRequired = false); + + /// entire segment + BasicSelectionCommand(const QString &name, + Segment &segment, + bool bruteForceRedoRequired = false); +}; + + +} + +#endif diff --git a/src/document/ConfigGroups.cpp b/src/document/ConfigGroups.cpp new file mode 100644 index 0000000..8e164c3 --- /dev/null +++ b/src/document/ConfigGroups.cpp @@ -0,0 +1,53 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <bownie@bownie.com> + + The moral right of the authors to claim authorship of this work + has been asserted. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ConfigGroups.h" + +namespace Rosegarden +{ + + // + // KConfig group names + // + const char* const GeneralOptionsConfigGroup = "General Options"; + const char* const LatencyOptionsConfigGroup = "Latency Options"; + const char* const SequencerOptionsConfigGroup = "Sequencer Options"; + const char* const NotationViewConfigGroup = "Notation Options"; + const char* const AudioManagerDialogConfigGroup = "AudioManagerDialog"; + const char* const SynthPluginManagerConfigGroup = "Synth Plugin Manager"; + const char* const BankEditorConfigGroup = "Bank Editor"; + const char* const ColoursConfigGroup = "coloursconfiggroup"; + const char* const ControlEditorConfigGroup = "Control Editor"; + const char* const DeviceManagerConfigGroup = "Device Manager"; + const char* const EventFilterDialogConfigGroup = "EventFilter Dialog"; + const char* const EventViewLayoutConfigGroupName = "EventList Layout"; + const char* const EventViewConfigGroup = "EventList Options"; + const char* const MarkerEditorConfigGroup = "Marker Editor"; + const char* const MatrixViewConfigGroup = "Matrix Options"; + const char* const PlayListConfigGroup = "PLAY_LIST"; + const char* const MainWindowConfigGroup = "MainView"; + const char* const TransportDialogConfigGroup = "Transport Controls"; + const char* const TempoViewLayoutConfigGroupName = "TempoView Layout"; + const char* const TempoViewConfigGroup = "TempoView Options"; + const char* const TriggerManagerConfigGroup = "Trigger Editor"; + const char* const EditViewConfigGroup = "Edit View"; + +} diff --git a/src/document/ConfigGroups.h b/src/document/ConfigGroups.h new file mode 100644 index 0000000..d960e4d --- /dev/null +++ b/src/document/ConfigGroups.h @@ -0,0 +1,56 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <bownie@bownie.com> + + The moral right of the authors to claim authorship of this work + has been asserted. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/** + * Miscellaneous constants + */ + + +#ifndef _CONSTANTS_H_ +#define _CONSTANTS_H_ + +namespace Rosegarden +{ + extern const char* const GeneralOptionsConfigGroup; + extern const char* const LatencyOptionsConfigGroup; + extern const char* const SequencerOptionsConfigGroup; + extern const char* const NotationViewConfigGroup; + extern const char* const AudioManagerDialogConfigGroup; + extern const char* const SynthPluginManagerConfigGroup; + extern const char* const BankEditorConfigGroup; + extern const char* const ColoursConfigGroup; + extern const char* const ControlEditorConfigGroup; + extern const char* const DeviceManagerConfigGroup; + extern const char* const EventFilterDialogConfigGroup; + extern const char* const EventViewLayoutConfigGroupName; + extern const char* const EventViewConfigGroup; + extern const char* const MarkerEditorConfigGroup; + extern const char* const MatrixViewConfigGroup; + extern const char* const PlayListConfigGroup; + extern const char* const MainWindowConfigGroup; + extern const char* const TransportDialogConfigGroup; + extern const char* const TempoViewLayoutConfigGroupName; + extern const char* const TempoViewConfigGroup; + extern const char* const TriggerManagerConfigGroup; + extern const char* const EditViewConfigGroup; +} + +#endif diff --git a/src/document/MultiViewCommandHistory.cpp b/src/document/MultiViewCommandHistory.cpp new file mode 100644 index 0000000..f8cddeb --- /dev/null +++ b/src/document/MultiViewCommandHistory.cpp @@ -0,0 +1,386 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "MultiViewCommandHistory.h" + +#include <klocale.h> +#include "misc/Debug.h" +#include <kactioncollection.h> +#include <kaction.h> +#include <kcommand.h> +#include <kstdaction.h> +#include <qobject.h> +#include <qpopupmenu.h> +#include <qregexp.h> +#include <qstring.h> +#include <kpopupmenu.h> + + +namespace Rosegarden +{ + +MultiViewCommandHistory::MultiViewCommandHistory() : + m_undoLimit(50), + m_redoLimit(50), + m_savedAt(0) +{ + // nothing +} + +MultiViewCommandHistory::~MultiViewCommandHistory() +{ + m_savedAt = -1; + clearStack(m_undoStack); + clearStack(m_redoStack); +} + +void +MultiViewCommandHistory::clear() +{ + m_savedAt = -1; + clearStack(m_undoStack); + clearStack(m_redoStack); +} + +void +MultiViewCommandHistory::attachView(KActionCollection *collection) +{ + if (m_views.find(collection) != m_views.end()) + return ; + + RG_DEBUG << "MultiViewCommandHistory::attachView() : setting up undo/redo actions\n"; + + KToolBarPopupAction *undo = dynamic_cast<KToolBarPopupAction*>(collection->action(KStdAction::stdName(KStdAction::Undo))); + + if (undo) { + connect(undo, SIGNAL(activated()), + this, SLOT(slotUndo())); + + connect + (undo->popupMenu(), + SIGNAL(aboutToShow()), + this, + SLOT(slotUndoAboutToShow())); + + connect + (undo->popupMenu(), + SIGNAL(activated(int)), + this, + SLOT(slotUndoActivated(int))); + } + + KToolBarPopupAction *redo = dynamic_cast<KToolBarPopupAction*>(collection->action(KStdAction::stdName(KStdAction::Redo))); + + if (redo) { + + connect(redo, SIGNAL(activated()), + this, SLOT(slotRedo())); + + connect + (redo->popupMenu(), + SIGNAL(aboutToShow()), + this, + SLOT(slotRedoAboutToShow())); + + connect + (redo->popupMenu(), + SIGNAL(activated(int)), + this, + SLOT(slotRedoActivated(int))); + } + + m_views.insert(collection); + updateButtons(); + +} + +void +MultiViewCommandHistory::detachView(KActionCollection *collection) +{ + ViewSet::iterator i = m_views.find(collection); + if (i != m_views.end()) + m_views.erase(collection); +} + +void +MultiViewCommandHistory::addCommand(KCommand *command, bool execute) +{ + if (!command) + return ; + + RG_DEBUG << "MultiViewCommandHistory::addCommand: " << command->name() << endl; + + // We can't redo after adding a command + clearStack(m_redoStack); + + // can we reach savedAt? + if ((int)m_undoStack.size() < m_savedAt) + m_savedAt = -1; // nope + + m_undoStack.push(command); + clipCommands(); + + if (execute) { + command->execute(); + emit commandExecuted(); + emit commandExecuted(command); + } + + updateButtons(); +} + +void +MultiViewCommandHistory::slotUndo() +{ + if (m_undoStack.empty()) + return ; + + KCommand *command = m_undoStack.top(); + command->unexecute(); + emit commandExecuted(); + emit commandExecuted(command); + + m_redoStack.push(command); + m_undoStack.pop(); + + clipCommands(); + updateButtons(); + + if ((int)m_undoStack.size() == m_savedAt) + emit documentRestored(); +} + +void +MultiViewCommandHistory::slotRedo() +{ + if (m_redoStack.empty()) + return ; + + KCommand *command = m_redoStack.top(); + command->execute(); + emit commandExecuted(); + emit commandExecuted(command); + + m_undoStack.push(command); + m_redoStack.pop(); + // no need to clip + updateButtons(); +} + +void +MultiViewCommandHistory::setUndoLimit(int limit) +{ + if (limit > 0 && limit != m_undoLimit) { + m_undoLimit = limit; + clipCommands(); + } +} + +void +MultiViewCommandHistory::setRedoLimit(int limit) +{ + if (limit > 0 && limit != m_redoLimit) { + m_redoLimit = limit; + clipCommands(); + } +} + +void +MultiViewCommandHistory::documentSaved() +{ + m_savedAt = m_undoStack.size(); +} + +void +MultiViewCommandHistory::clipCommands() +{ + if ((int)m_undoStack.size() > m_undoLimit) { + m_savedAt -= (m_undoStack.size() - m_undoLimit); + } + + clipStack(m_undoStack, m_undoLimit); + clipStack(m_redoStack, m_redoLimit); +} + +void +MultiViewCommandHistory::clipStack(CommandStack &stack, int limit) +{ + int i; + + if ((int)stack.size() > limit) { + + CommandStack tempStack; + for (i = 0; i < limit; ++i) { + KCommand *togo = stack.top(); + KNamedCommand *named = dynamic_cast<KNamedCommand *>(togo); + if (named) { + RG_DEBUG << "MVCH::clipStack: Saving recent command: " << named->name() << " at " << togo << endl; + } else { + RG_DEBUG << "MVCH::clipStack: Saving recent unnamed command" << " at " << togo << endl; + } + tempStack.push(stack.top()); + stack.pop(); + } + clearStack(stack); + for (i = 0; i < m_undoLimit; ++i) { + stack.push(tempStack.top()); + tempStack.pop(); + } + } +} + +void +MultiViewCommandHistory::clearStack(CommandStack &stack) +{ + while (!stack.empty()) { + KCommand *togo = stack.top(); + KNamedCommand *named = dynamic_cast<KNamedCommand *>(togo); + if (named) { + RG_DEBUG << "MVCH::clearStack: About to delete command: " << named->name() << " at " << togo << endl; + } else { + RG_DEBUG << "MVCH::clearStack: About to delete unnamed command" << " at " << togo << endl; + } + delete togo; + stack.pop(); + } +} + +void +MultiViewCommandHistory::slotUndoActivated(int pos) +{ + for (int i = 0 ; i <= pos; ++i) + slotUndo(); +} + +void +MultiViewCommandHistory::slotRedoActivated(int pos) +{ + for (int i = 0 ; i <= pos; ++i) + slotRedo(); +} + +void +MultiViewCommandHistory::slotUndoAboutToShow() +{ + updateMenu(true, KStdAction::stdName(KStdAction::Undo), m_undoStack); +} + +void +MultiViewCommandHistory::slotRedoAboutToShow() +{ + updateMenu(false, KStdAction::stdName(KStdAction::Redo), m_redoStack); +} + +void +MultiViewCommandHistory::updateButtons() +{ + updateButton(true, KStdAction::stdName(KStdAction::Undo), m_undoStack); + updateButton(false, KStdAction::stdName(KStdAction::Redo), m_redoStack); +} + +void +MultiViewCommandHistory::updateButton(bool undo, + const QString &name, + CommandStack &stack) +{ + for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { + + KAction *action = (*i)->action(name); + if (!action) + continue; + QString text; + + if (stack.empty()) { + action->setEnabled(false); + if (undo) + text = i18n("Nothing to undo"); + else + text = i18n("Nothing to redo"); + action->setText(text); + } else { + action->setEnabled(true); + QString commandName = stack.top()->name(); + commandName.replace(QRegExp("&"), ""); + commandName.replace(QRegExp("\\.\\.\\.$"), ""); + if (undo) + text = i18n("Und&o %1").arg(commandName); + else + text = i18n("Re&do %1").arg(commandName); + action->setText(text); + } + } +} + +void +MultiViewCommandHistory::updateMenu(bool undo, + const QString &name, + CommandStack &stack) +{ + for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { + + KAction *action = (*i)->action(name); + if (!action) + continue; + + KToolBarPopupAction *popupAction = + dynamic_cast<KToolBarPopupAction *>(action); + if (!popupAction) + continue; + + QPopupMenu *menu = popupAction->popupMenu(); + if (!menu) + continue; + menu->clear(); + + CommandStack tempStack; + int j = 0; + + while (j < 10 && !stack.empty()) { + + KCommand *command = stack.top(); + tempStack.push(command); + stack.pop(); + + QString commandName = command->name(); + commandName.replace(QRegExp("&"), ""); + commandName.replace(QRegExp("\\.\\.\\.$"), ""); + + QString text; + if (undo) + text = i18n("Und&o %1").arg(commandName); + else + text = i18n("Re&do %1").arg(commandName); + menu->insertItem(text, j++); + } + + while (!tempStack.empty()) { + stack.push(tempStack.top()); + tempStack.pop(); + } + } +} + +} +#include "MultiViewCommandHistory.moc" diff --git a/src/document/MultiViewCommandHistory.h b/src/document/MultiViewCommandHistory.h new file mode 100644 index 0000000..d122d08 --- /dev/null +++ b/src/document/MultiViewCommandHistory.h @@ -0,0 +1,152 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_MULTIVIEWCOMMANDHISTORY_H_ +#define _RG_MULTIVIEWCOMMANDHISTORY_H_ + +#include <set> +#include <stack> +#include <qobject.h> + + +class QString; +class KCommand; +class KActionCollection; + + +namespace Rosegarden +{ + + + +/** + * The MultiViewCommandHistory class is much like KCommandHistory in + * that it stores a list of executed commands and maintains Undo and + * Redo actions synchronised with those commands. + * + * The difference is that MultiViewCommandHistory allows you to + * associate more than one Undo and Redo action with the same command + * history, and it keeps them all up-to-date at once. This makes it + * effective in systems where multiple views may be editing the same + * data at once. + */ + +class MultiViewCommandHistory : public QObject +{ + Q_OBJECT +public: + + MultiViewCommandHistory(); + virtual ~MultiViewCommandHistory(); + + void clear(); + + void attachView(KActionCollection *collection); + void detachView(KActionCollection *collection); + + void addCommand(KCommand *command, bool execute = true); + + /// @return the maximum number of items in the undo history + int undoLimit() { return m_undoLimit; } + + /// Set the maximum number of items in the undo history + void setUndoLimit(int limit); + + /// @return the maximum number of items in the redo history + int redoLimit() { return m_redoLimit; } + + /// Set the maximum number of items in the redo history + void setRedoLimit(int limit); + +public slots: + /** + * Remember when you saved the document. + * Call this right after saving the document. As soon as + * the history reaches the current index again (via some + * undo/redo operations) it will emit @ref documentRestored + * If you implemented undo/redo properly the document is + * the same you saved before. + */ + virtual void documentSaved(); + +protected slots: + void slotUndo(); + void slotRedo(); + void slotUndoAboutToShow(); + void slotUndoActivated(int); + void slotRedoAboutToShow(); + void slotRedoActivated(int); + +signals: + /** + * This is emitted every time a command is executed + * (whether by addCommand, undo or redo). + * You can use this to update the GUI, for instance. + */ + void commandExecuted(KCommand *); + + /** + * This is emitted every time a command is executed + * (whether by addCommand, undo or redo). + * + * It should be connected to the update() slot of widgets + * which need to repaint after a command + */ + void commandExecuted(); + + /** + * This is emitted every time we reach the index where you + * saved the document for the last time. See @ref documentSaved + */ + void documentRestored(); + +private: + //--------------- Data members --------------------------------- + + typedef std::set<KActionCollection *> ViewSet; + ViewSet m_views; + + typedef std::stack<KCommand *> CommandStack; + CommandStack m_undoStack; + CommandStack m_redoStack; + + int m_undoLimit; + int m_redoLimit; + int m_savedAt; + + void updateButtons(); + void updateButton(bool undo, const QString &name, CommandStack &stack); + void updateMenu(bool undo, const QString &name, CommandStack &stack); + void clipCommands(); + + void clipStack(CommandStack &stack, int limit); + void clearStack(CommandStack &stack); +}; + + + +} + +#endif diff --git a/src/document/RoseXmlHandler.cpp b/src/document/RoseXmlHandler.cpp new file mode 100644 index 0000000..028c89a --- /dev/null +++ b/src/document/RoseXmlHandler.cpp @@ -0,0 +1,2368 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "RoseXmlHandler.h" + +#include "sound/Midi.h" +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/AudioLevel.h" +#include "base/AudioPluginInstance.h" +#include "base/BaseProperties.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "base/Composition.h" +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/Instrument.h" +#include "base/Marker.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/TriggerSegment.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/dialogs/FileLocateDialog.h" +#include "gui/general/ProgressReporter.h" +#include "gui/kdeext/KStartupLogo.h" +#include "gui/studio/AudioPlugin.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressDialog.h" +#include "RosegardenGUIDoc.h" +#include "sound/AudioFileManager.h" +#include <kfiledialog.h> +#include <kmessagebox.h> +#include <qcstring.h> +#include <qdatastream.h> +#include <qdialog.h> +#include <qfileinfo.h> +#include <qstring.h> +#include <qstringlist.h> +#include "XmlStorableEvent.h" +#include "XmlSubHandler.h" + +namespace Rosegarden +{ + +using namespace BaseProperties; + +class ConfigurationXmlSubHandler : public XmlSubHandler +{ +public: + ConfigurationXmlSubHandler(const QString &elementName, + Rosegarden::Configuration *configuration); + + virtual bool startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts); + + virtual bool endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + bool& finished); + + virtual bool characters(const QString& ch); + + //--------------- Data members --------------------------------- + + Rosegarden::Configuration *m_configuration; + + QString m_elementName; + QString m_propertyName; + QString m_propertyType; +}; + +ConfigurationXmlSubHandler::ConfigurationXmlSubHandler(const QString &elementName, + Rosegarden::Configuration *configuration) + : m_configuration(configuration), + m_elementName(elementName) +{ +} + +bool ConfigurationXmlSubHandler::startElement(const QString&, const QString&, + const QString& lcName, + const QXmlAttributes& atts) +{ + m_propertyName = lcName; + m_propertyType = atts.value("type"); + + if (m_propertyName == "property") { + // handle alternative encoding for properties with arbitrary names + m_propertyName = atts.value("name"); + QString value = atts.value("value"); + if (value) { + m_propertyType = "String"; + m_configuration->set<String>(qstrtostr(m_propertyName), + qstrtostr(value)); + } + } + + return true; +} + +bool ConfigurationXmlSubHandler::characters(const QString& chars) +{ + QString ch = chars.stripWhiteSpace(); + // this method is also called on newlines - skip these cases + if (ch.isEmpty()) return true; + + + if (m_propertyType == "Int") { + long i = ch.toInt(); + RG_DEBUG << "\"" << m_propertyName << "\" " + << "value = " << i << endl; + m_configuration->set<Int>(qstrtostr(m_propertyName), i); + + return true; + } + + if (m_propertyType == "RealTime") { + Rosegarden::RealTime rt; + int sepIdx = ch.find(','); + + rt.sec = ch.left(sepIdx).toInt(); + rt.nsec = ch.mid(sepIdx + 1).toInt(); + + RG_DEBUG << "\"" << m_propertyName << "\" " + << "sec = " << rt.sec << ", nsec = " << rt.nsec << endl; + + m_configuration->set<Rosegarden::RealTimeT>(qstrtostr(m_propertyName), rt); + + return true; + } + + if (m_propertyType == "Bool") { + QString chLc = ch.lower(); + + bool b = (chLc == "true" || + chLc == "1" || + chLc == "on"); + + m_configuration->set<Rosegarden::Bool>(qstrtostr(m_propertyName), b); + + return true; + } + + if (!m_propertyType || + m_propertyType == "String") { + + m_configuration->set<Rosegarden::String>(qstrtostr(m_propertyName), + qstrtostr(ch)); + + return true; + } + + + return true; +} + +bool +ConfigurationXmlSubHandler::endElement(const QString&, + const QString&, + const QString& lcName, + bool& finished) +{ + m_propertyName = ""; + m_propertyType = ""; + finished = (lcName == m_elementName); + return true; +} + + +//---------------------------------------- + + + +RoseXmlHandler::RoseXmlHandler(RosegardenGUIDoc *doc, + unsigned int elementCount, + bool createNewDevicesWhenNeeded) + : ProgressReporter(0), + m_doc(doc), + m_currentSegment(0), + m_currentEvent(0), + m_currentTime(0), + m_chordDuration(0), + m_segmentEndMarkerTime(0), + m_inChord(false), + m_inGroup(false), + m_inComposition(false), + m_groupId(0), + m_foundTempo(false), + m_section(NoSection), + m_device(0), + m_deviceRunningId(Device::NO_DEVICE), + m_msb(0), + m_lsb(0), + m_instrument(0), + m_plugin(0), + m_pluginInBuss(false), + m_colourMap(0), + m_keyMapping(0), + m_pluginId(0), + m_totalElements(elementCount), + m_elementsSoFar(0), + m_subHandler(0), + m_deprecation(false), + m_createDevices(createNewDevicesWhenNeeded), + m_haveControls(false), + m_cancelled(false), + m_skipAllAudio(false), + m_hasActiveAudio(false) +{} + +RoseXmlHandler::~RoseXmlHandler() +{ + delete m_subHandler; +} + +Composition & +RoseXmlHandler::getComposition() +{ + return m_doc->getComposition(); +} + +Studio & +RoseXmlHandler::getStudio() +{ + return m_doc->getStudio(); +} + +AudioFileManager & +RoseXmlHandler::getAudioFileManager() +{ + return m_doc->getAudioFileManager(); +} + +AudioPluginManager * +RoseXmlHandler::getAudioPluginManager() +{ + return m_doc->getPluginManager(); +} + +bool +RoseXmlHandler::startDocument() +{ + // Clear tracks + // + getComposition().clearTracks(); + + // And the loop + // + getComposition().setLoopStart(0); + getComposition().setLoopEnd(0); + + // All plugins + // + m_doc->clearAllPlugins(); + + // reset state + return true; +} + +bool +RoseXmlHandler::startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, const QXmlAttributes& atts) +{ + // First check if user pressed cancel button on the progress + // dialog + // + if (isOperationCancelled()) { + // Ideally, we'd throw here, but at this point Qt is in the stack + // and Qt is very often compiled without exception support. + // + m_cancelled = true; + return false; + } + + QString lcName = qName.lower(); + + if (getSubHandler()) { + return getSubHandler()->startElement(namespaceURI, localName, lcName, atts); + } + + if (lcName == "event") { + + // RG_DEBUG << "RoseXmlHandler::startElement: found event, current time is " << m_currentTime << endl; + + if (m_currentEvent) { + RG_DEBUG << "RoseXmlHandler::startElement: Warning: new event found at time " << m_currentTime << " before previous event has ended; previous event will be lost" << endl; + delete m_currentEvent; + } + + m_currentEvent = new XmlStorableEvent(atts, m_currentTime); + + if (m_currentEvent->has(BEAMED_GROUP_ID)) { + + // remap -- we want to ensure that the segment's nextId + // is always used (and incremented) in preference to the + // stored id + + if (!m_currentSegment) { + m_errorString = "Got grouped event outside of a segment"; + return false; + } + + long storedId = m_currentEvent->get + <Int>(BEAMED_GROUP_ID); + + if (m_groupIdMap.find(storedId) == m_groupIdMap.end()) { + m_groupIdMap[storedId] = m_currentSegment->getNextId(); + } + + m_currentEvent->set + <Int>(BEAMED_GROUP_ID, m_groupIdMap[storedId]); + + } else if (m_inGroup) { + m_currentEvent->set + <Int>(BEAMED_GROUP_ID, m_groupId); + m_currentEvent->set + <String>(BEAMED_GROUP_TYPE, m_groupType); + if (m_groupType == GROUP_TYPE_TUPLED) { + m_currentEvent->set + <Int> + (BEAMED_GROUP_TUPLET_BASE, m_groupTupletBase); + m_currentEvent->set + <Int> + (BEAMED_GROUP_TUPLED_COUNT, m_groupTupledCount); + m_currentEvent->set + <Int> + (BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount); + } + } + + timeT duration = m_currentEvent->getDuration(); + + if (!m_inChord) { + + m_currentTime = m_currentEvent->getAbsoluteTime() + duration; + + // RG_DEBUG << "RoseXmlHandler::startElement: (we're not in a chord) " << endl; + + } else if (duration != 0) { + + // set chord duration to the duration of the shortest + // element with a non-null duration (if no such elements, + // leave it as 0). + + if (m_chordDuration == 0 || duration < m_chordDuration) { + m_chordDuration = duration; + } + } + + } else if (lcName == "property") { + + if (!m_currentEvent) { + RG_DEBUG << "RoseXmlHandler::startElement: Warning: Found property outside of event at time " << m_currentTime << ", ignoring" << endl; + } else { + m_currentEvent->setPropertyFromAttributes(atts, true); + } + + } else if (lcName == "nproperty") { + + if (!m_currentEvent) { + RG_DEBUG << "RoseXmlHandler::startElement: Warning: Found nproperty outside of event at time " << m_currentTime << ", ignoring" << endl; + } else { + m_currentEvent->setPropertyFromAttributes(atts, false); + } + + } else if (lcName == "chord") { + + m_inChord = true; + + } else if (lcName == "group") { + + if (!m_currentSegment) { + m_errorString = "Got group outside of a segment"; + return false; + } + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"group\". We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + m_inGroup = true; + m_groupId = m_currentSegment->getNextId(); + m_groupType = qstrtostr(atts.value("type")); + + if (m_groupType == GROUP_TYPE_TUPLED) { + m_groupTupletBase = atts.value("base").toInt(); + m_groupTupledCount = atts.value("tupled").toInt(); + m_groupUntupledCount = atts.value("untupled").toInt(); + } + + } else if (lcName == "rosegarden-data") { + + // FILE FORMAT VERSIONING -- see comments in + // rosegardenguidoc.cpp. We only care about major and minor + // here, not point. + + QString version = atts.value("version"); + QString smajor = atts.value("format-version-major"); + QString sminor = atts.value("format-version-minor"); + +// std::cerr << "\n\n\nRosegarden file version = \"" << version << "\"\n\n\n" << std::endl; + + if (smajor) { + + int major = smajor.toInt(); + int minor = sminor.toInt(); + + if (major > RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR) { + m_errorString = i18n("This file was written by Rosegarden %1, and it uses\na different file format that cannot be read by this version.").arg(version); + return false; + } + + if (major == RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR && + minor > RosegardenGUIDoc::FILE_FORMAT_VERSION_MINOR) { + + CurrentProgressDialog::freeze(); + KStartupLogo::hideIfStillThere(); + + KMessageBox::information(0, i18n("This file was written by Rosegarden %1, which is more recent than this version.\nThere may be some incompatibilities with the file format.").arg(version)); + + CurrentProgressDialog::thaw(); + } + } + + } else if (lcName == "studio") { + + if (m_section != NoSection) { + m_errorString = "Found Studio in another section"; + return false; + } + + // In the Studio we clear down everything apart from Devices and + // Instruments before we reload. Instruments are derived from + // the Sequencer, the bank/program information is loaded from + // the file we're currently examining. + // + getStudio().clearMidiBanksAndPrograms(); + getStudio().clearBusses(); + getStudio().clearRecordIns(); + + m_section = InStudio; // set top level section + + // Get and set MIDI filters + // + QString thruStr = atts.value("thrufilter"); + + if (thruStr) + getStudio().setMIDIThruFilter(thruStr.toInt()); + + QString recordStr = atts.value("recordfilter"); + + if (recordStr) + getStudio().setMIDIRecordFilter(recordStr.toInt()); + + QString inputStr = atts.value("audioinputpairs"); + + if (inputStr) { + int inputs = inputStr.toInt(); + if (inputs < 1) + inputs = 1; // we simply don't permit no inputs + while (int(getStudio().getRecordIns().size()) < inputs) { + getStudio().addRecordIn(new RecordIn()); + } + } + + QString mixerStr = atts.value("mixerdisplayoptions"); + + if (mixerStr) { + unsigned int mixer = mixerStr.toUInt(); + getStudio().setMixerDisplayOptions(mixer); + } + + QString metronomeStr = atts.value("metronomedevice"); + + if (metronomeStr) { + DeviceId metronome = metronomeStr.toUInt(); + getStudio().setMetronomeDevice(metronome); + } + + } else if (lcName == "timesignature") { + + if (m_inComposition == false) { + m_errorString = "TimeSignature object found outside Composition"; + return false; + } + + timeT t = 0; + QString timeStr = atts.value("time"); + if (timeStr) + t = timeStr.toInt(); + + int num = 4; + QString numStr = atts.value("numerator"); + if (numStr) + num = numStr.toInt(); + + int denom = 4; + QString denomStr = atts.value("denominator"); + if (denomStr) + denom = denomStr.toInt(); + + bool common = false; + QString commonStr = atts.value("common"); + if (commonStr) + common = (commonStr == "true"); + + bool hidden = false; + QString hiddenStr = atts.value("hidden"); + if (hiddenStr) + hidden = (hiddenStr == "true"); + + bool hiddenBars = false; + QString hiddenBarsStr = atts.value("hiddenbars"); + if (hiddenBarsStr) + hiddenBars = (hiddenBarsStr == "true"); + + getComposition().addTimeSignature + (t, TimeSignature(num, denom, common, hidden, hiddenBars)); + + } else if (lcName == "tempo") { + + timeT t = 0; + QString timeStr = atts.value("time"); + if (timeStr) + t = timeStr.toInt(); + + tempoT tempo = Composition::getTempoForQpm(120.0); + QString tempoStr = atts.value("tempo"); + QString targetStr = atts.value("target"); + QString bphStr = atts.value("bph"); + if (tempoStr) { + tempo = tempoStr.toInt(); + } else if (bphStr) { + tempo = Composition::getTempoForQpm + (double(bphStr.toInt()) / 60.0); + } + + if (targetStr) { + getComposition().addTempoAtTime(t, tempo, targetStr.toInt()); + } else { + getComposition().addTempoAtTime(t, tempo); + } + + } else if (lcName == "composition") { + + if (m_section != NoSection) { + m_errorString = "Found Composition in another section"; + return false; + } + + // set Segment + m_section = InComposition; + + // Get and set the record track + // + QString recordStr = atts.value("recordtrack"); + if (recordStr) { + getComposition().setTrackRecording(recordStr.toInt(), true); + } + + QString recordPlStr = atts.value("recordtracks"); + if (recordPlStr) { + RG_DEBUG << "Record tracks: " << recordPlStr << endl; + QStringList recordList = QStringList::split(',', recordPlStr); + for (QStringList::iterator i = recordList.begin(); + i != recordList.end(); ++i) { + RG_DEBUG << "Record track: " << (*i).toInt() << endl; + getComposition().setTrackRecording((*i).toInt(), true); + } + } + + // Get and set the position pointer + // + int position = 0; + QString positionStr = atts.value("pointer"); + if (positionStr) { + position = positionStr.toInt(); + } + + getComposition().setPosition(position); + + + // Get and (eventually) set the default tempo. + // We prefer the new compositionDefaultTempo over the + // older defaultTempo. + // + QString tempoStr = atts.value("compositionDefaultTempo"); + if (tempoStr) { + tempoT tempo = tempoT(tempoStr.toInt()); + getComposition().setCompositionDefaultTempo(tempo); + } else { + tempoStr = atts.value("defaultTempo"); + if (tempoStr) { + double tempo = qstrtodouble(tempoStr); + getComposition().setCompositionDefaultTempo + (Composition::getTempoForQpm(tempo)); + } + } + + // set the composition flag + m_inComposition = true; + + + // Set the loop + // + QString loopStartStr = atts.value("loopstart"); + QString loopEndStr = atts.value("loopend"); + + if (loopStartStr && loopEndStr) { + int loopStart = loopStartStr.toInt(); + int loopEnd = loopEndStr.toInt(); + + getComposition().setLoopStart(loopStart); + getComposition().setLoopEnd(loopEnd); + } + + QString selectedTrackStr = atts.value("selected"); + + if (selectedTrackStr) { + TrackId selectedTrack = + (TrackId)selectedTrackStr.toInt(); + + getComposition().setSelectedTrack(selectedTrack); + } + + QString soloTrackStr = atts.value("solo"); + if (soloTrackStr) { + if (soloTrackStr.toInt() == 1) + getComposition().setSolo(true); + else + getComposition().setSolo(false); + } + + + QString playMetStr = atts.value("playmetronome"); + if (playMetStr) { + if (playMetStr.toInt()) + getComposition().setPlayMetronome(true); + else + getComposition().setPlayMetronome(false); + } + + QString recMetStr = atts.value("recordmetronome"); + if (recMetStr) { + if (recMetStr.toInt()) + getComposition().setRecordMetronome(true); + else + getComposition().setRecordMetronome(false); + } + + QString nextTriggerIdStr = atts.value("nexttriggerid"); + if (nextTriggerIdStr) { + getComposition().setNextTriggerSegmentId(nextTriggerIdStr.toInt()); + } + + QString copyrightStr = atts.value("copyright"); + if (copyrightStr) { + getComposition().setCopyrightNote(qstrtostr(copyrightStr)); + } + + QString startMarkerStr = atts.value("startMarker"); + QString endMarkerStr = atts.value("endMarker"); + + if (startMarkerStr) { + getComposition().setStartMarker(startMarkerStr.toInt()); + } + + if (endMarkerStr) { + getComposition().setEndMarker(endMarkerStr.toInt()); + } + + } else if (lcName == "track") { + + if (m_section != InComposition) { + m_errorString = "Track object found outside Composition"; + return false; + } + + int id = -1; + int position = -1; + int instrument = -1; + std::string label; + bool muted = false; + + QString trackNbStr = atts.value("id"); + if (trackNbStr) { + id = trackNbStr.toInt(); + } + + QString labelStr = atts.value("label"); + if (labelStr) { + label = qstrtostr(labelStr); + } + + QString mutedStr = atts.value("muted"); + if (mutedStr) { + if (mutedStr == "true") + muted = true; + else + muted = false; + } + + QString positionStr = atts.value("position"); + if (positionStr) { + position = positionStr.toInt(); + } + + QString instrumentStr = atts.value("instrument"); + if (instrumentStr) { + instrument = instrumentStr.toInt(); + } + + Track *track = new Track(id, + instrument, + position, + label, + muted); + + // track properties affecting newly created segments are initialized + // to default values in the ctor, so they don't need to be initialized + // here + + QString presetLabelStr = atts.value("defaultLabel"); + if (labelStr) { + track->setPresetLabel(presetLabelStr); + } + + QString clefStr = atts.value("defaultClef"); + if (clefStr) { + track->setClef(clefStr.toInt()); + } + + QString transposeStr = atts.value("defaultTranspose"); + if (transposeStr) { + track->setTranspose(transposeStr.toInt()); + } + + QString colorStr = atts.value("defaultColour"); + if (colorStr) { + track->setColor(colorStr.toInt()); + } + + QString highplayStr = atts.value("defaultHighestPlayable"); + if (highplayStr) { + track->setHighestPlayable(highplayStr.toInt()); + } + + QString lowplayStr = atts.value("defaultLowestPlayable"); + if (lowplayStr) { + track->setLowestPlayable(lowplayStr.toInt()); + } + + QString staffSizeStr = atts.value("staffSize"); + if (staffSizeStr) { + track->setStaffSize(staffSizeStr.toInt()); + } + + QString staffBracketStr = atts.value("staffBracket"); + if (staffBracketStr) { + track->setStaffBracket(staffBracketStr.toInt()); + } + + getComposition().addTrack(track); + + + } else if (lcName == "segment") { + + if (m_section != NoSection) { + m_errorString = "Found Segment in another section"; + return false; + } + + // set Segment + m_section = InSegment; + + int track = -1, startTime = 0; + unsigned int colourindex = 0; + QString trackNbStr = atts.value("track"); + if (trackNbStr) { + track = trackNbStr.toInt(); + } + + QString startIdxStr = atts.value("start"); + if (startIdxStr) { + startTime = startIdxStr.toInt(); + } + + QString segmentType = (atts.value("type")).lower(); + if (segmentType) { + if (segmentType == "audio") { + int audioFileId = atts.value("file").toInt(); + + // check this file id exists on the AudioFileManager + + if (getAudioFileManager().fileExists(audioFileId) == false) { + // We don't report an error as this audio file might've + // been excluded deliberately as we could't actually + // find the audio file itself. + // + return true; + } + + // Create an Audio segment and add its reference + // + m_currentSegment = new Segment(Segment::Audio); + m_currentSegment->setAudioFileId(audioFileId); + m_currentSegment->setStartTime(startTime); + } else { + // Create a (normal) internal Segment + m_currentSegment = new Segment(Segment::Internal); + } + + } else { + // for the moment we default + m_currentSegment = new Segment(Segment::Internal); + } + + QString repeatStr = atts.value("repeat"); + if (repeatStr.lower() == "true") { + m_currentSegment->setRepeating(true); + } + + QString delayStr = atts.value("delay"); + if (delayStr) { + RG_DEBUG << "Delay string is \"" << delayStr << "\"" << endl; + long delay = delayStr.toLong(); + RG_DEBUG << "Delay is " << delay << endl; + m_currentSegment->setDelay(delay); + } + + QString rtDelaynSec = atts.value("rtdelaynsec"); + QString rtDelayuSec = atts.value("rtdelayusec"); + QString rtDelaySec = atts.value("rtdelaysec"); + if (rtDelaySec && (rtDelaynSec || rtDelayuSec)) { + if (rtDelaynSec) { + m_currentSegment->setRealTimeDelay + (RealTime(rtDelaySec.toInt(), + rtDelaynSec.toInt())); + } else { + m_currentSegment->setRealTimeDelay + (RealTime(rtDelaySec.toInt(), + rtDelayuSec.toInt() * 1000)); + } + } + + QString transposeStr = atts.value("transpose"); + if (transposeStr) + m_currentSegment->setTranspose(transposeStr.toInt()); + + // fill in the label + QString labelStr = atts.value("label"); + if (labelStr) + m_currentSegment->setLabel(qstrtostr(labelStr)); + + m_currentSegment->setTrack(track); + //m_currentSegment->setStartTime(startTime); + + QString colourIndStr = atts.value("colourindex"); + if (colourIndStr) { + colourindex = colourIndStr.toInt(); + } + + m_currentSegment->setColourIndex(colourindex); + + QString snapGridSizeStr = atts.value("snapgridsize"); + if (snapGridSizeStr) { + m_currentSegment->setSnapGridSize(snapGridSizeStr.toInt()); + } + + QString viewFeaturesStr = atts.value("viewfeatures"); + if (viewFeaturesStr) { + m_currentSegment->setViewFeatures(viewFeaturesStr.toInt()); + } + + m_currentTime = startTime; + + QString triggerIdStr = atts.value("triggerid"); + QString triggerPitchStr = atts.value("triggerbasepitch"); + QString triggerVelocityStr = atts.value("triggerbasevelocity"); + QString triggerRetuneStr = atts.value("triggerretune"); + QString triggerAdjustTimeStr = atts.value("triggeradjusttimes"); + + if (triggerIdStr) { + int pitch = -1; + if (triggerPitchStr) + pitch = triggerPitchStr.toInt(); + int velocity = -1; + if (triggerVelocityStr) + velocity = triggerVelocityStr.toInt(); + TriggerSegmentRec *rec = + getComposition().addTriggerSegment(m_currentSegment, + triggerIdStr.toInt(), + pitch, velocity); + if (rec) { + if (triggerRetuneStr) + rec->setDefaultRetune(triggerRetuneStr.lower() == "true"); + if (triggerAdjustTimeStr) + rec->setDefaultTimeAdjust(qstrtostr(triggerAdjustTimeStr)); + } + m_currentSegment->setStartTimeDataMember(startTime); + } else { + getComposition().addSegment(m_currentSegment); + getComposition().setSegmentStartTime(m_currentSegment, startTime); + } + + QString endMarkerStr = atts.value("endmarker"); + if (endMarkerStr) { + delete m_segmentEndMarkerTime; + m_segmentEndMarkerTime = new timeT(endMarkerStr.toInt()); + } + + m_groupIdMap.clear(); + + } else if (lcName == "gui") { + + if (m_section != InSegment) { + m_errorString = "Found GUI element outside Segment"; + return false; + } + + } else if (lcName == "controller") { + + if (m_section != InSegment) { + m_errorString = "Found Controller element outside Segment"; + return false; + } + + QString type = atts.value("type"); + //RG_DEBUG << "RoseXmlHandler::startElement - controller type = " << type << endl; + + if (type == strtoqstr(PitchBend::EventType)) + m_currentSegment->addEventRuler(PitchBend::EventType); + else if (type == strtoqstr(Controller::EventType)) { + QString value = atts.value("value"); + + if (value != "") + m_currentSegment->addEventRuler(Controller::EventType, value.toInt()); + } + + } else if (lcName == "resync") { + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"resync\". We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + QString time(atts.value("time")); + bool isNumeric; + int numTime = time.toInt(&isNumeric); + if (isNumeric) + m_currentTime = numTime; + + } else if (lcName == "audio") { + + if (m_section != InAudioFiles) { + m_errorString = "Audio object found outside Audio section"; + return false; + } + + if (m_skipAllAudio) { + std::cout << "SKIPPING audio file" << std::endl; + return true; + } + + QString id(atts.value("id")); + QString file(atts.value("file")); + QString label(atts.value("label")); + + if (id.isEmpty() || file.isEmpty() || label.isEmpty()) { + m_errorString = "Audio object has empty parameters"; + return false; + } + + m_hasActiveAudio = true; + + // attempt to insert file into AudioFileManager + // (this checks the integrity of the file at the + // same time) + // + if (getAudioFileManager().insertFile(qstrtostr(label), + qstrtostr(file), + id.toInt()) == false) { + // Ok, now attempt to use the KFileDialog saved default + // value for the AudioPath. + // + QString thing; + KURL url = KFileDialog::getStartURL(QString(":WAVS"), thing); + getAudioFileManager().setAudioPath(url.path().latin1()); + + /* + RG_DEBUG << "ATTEMPTING TO FIND IN PATH = " + << url.path() << endl; + */ + + if (getAudioFileManager(). + insertFile(qstrtostr(label), + qstrtostr(file), id.toInt()) == false) { + + // Freeze the progress dialog + CurrentProgressDialog::freeze(); + + // Hide splash screen if present on startup + KStartupLogo::hideIfStillThere(); + + // Create a locate file dialog - give it the file name + // and the AudioFileManager path that we've already + // tried. If we manually locate the file then we reset + // the audiofilepath to the new value and see if this + // helps us locate the rest of the files. + // + + QString newFilename = ""; + QString newPath = ""; + + do { + + FileLocateDialog fL((RosegardenGUIApp *)m_doc->parent(), + file, + QString(getAudioFileManager().getAudioPath().c_str())); + int result = fL.exec(); + + if (result == QDialog::Accepted) { + newFilename = fL.getFilename(); + newPath = fL.getDirectory(); + } else if (result == QDialog::Rejected) { + // just skip the file + break; + } else { + // don't process any more audio files + m_skipAllAudio = true; + CurrentProgressDialog::thaw(); + return true; + } + + + } while (getAudioFileManager().insertFile(qstrtostr(label), + qstrtostr(newFilename), + id.toInt()) == false); + + if (newPath != "") { + getAudioFileManager().setAudioPath(qstrtostr(newPath)); + // Set a document post-modify flag + //m_doc->setModified(true); + } + + getAudioFileManager().print(); + + // Restore progress dialog's normal state + CurrentProgressDialog::thaw(); + } else { + // AudioPath is modified so set a document post modify flag + // + //m_doc->setModified(true); + } + + } + + } else if (lcName == "audiopath") { + + if (m_section != InAudioFiles) { + m_errorString = "Audiopath object found outside AudioFiles section"; + return false; + } + + QString search(atts.value("value")); + + if (search.isEmpty()) { + m_errorString = "Audiopath has no value"; + return false; + } + + if (!search.startsWith("/") && !search.startsWith("~")) { + QString docPath = m_doc->getAbsFilePath(); + QString dirPath = QFileInfo(docPath).dirPath(); + if (QFileInfo(dirPath).exists()) { + search = dirPath + "/" + search; + } + } + + getAudioFileManager().setAudioPath(qstrtostr(search)); + + } else if (lcName == "begin") { + + double marker = qstrtodouble(atts.value("index")); + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "Found audio begin index in non audio segment"; + return false; + } + + // convert to RealTime from float + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + m_currentSegment->setAudioStartTime(RealTime(sec, usec * 1000)); + + + } else if (lcName == "end") { + + double marker = qstrtodouble(atts.value("index")); + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "found audio end index in non audio segment"; + return false; + } + + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + RealTime markerTime(sec, usec * 1000); + + if (markerTime < m_currentSegment->getAudioStartTime()) { + m_errorString = "Audio end index before audio start marker"; + return false; + } + + m_currentSegment->setAudioEndTime(markerTime); + + // Ensure we set end time according to correct RealTime end of Segment + // + RealTime realEndTime = getComposition(). + getElapsedRealTime(m_currentSegment->getStartTime()) + + m_currentSegment->getAudioEndTime() - + m_currentSegment->getAudioStartTime(); + + timeT absEnd = getComposition().getElapsedTimeForRealTime(realEndTime); + m_currentSegment->setEndTime(absEnd); + + } else if (lcName == "fadein") { + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "found fade in time in non audio segment"; + return false; + } + + double marker = qstrtodouble(atts.value("time")); + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + RealTime markerTime(sec, usec * 1000); + + m_currentSegment->setFadeInTime(markerTime); + m_currentSegment->setAutoFade(true); + + + } else if (lcName == "fadeout") { + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "found fade out time in non audio segment"; + return false; + } + + double marker = qstrtodouble(atts.value("time")); + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + RealTime markerTime(sec, usec * 1000); + + m_currentSegment->setFadeOutTime(markerTime); + m_currentSegment->setAutoFade(true); + + } else if (lcName == "device") { + + if (m_section != InStudio) { + m_errorString = "Found Device outside Studio"; + return false; + } + + m_haveControls = false; + + QString type = (atts.value("type")).lower(); + QString idString = atts.value("id"); + QString nameStr = atts.value("name"); + + if (idString.isNull()) { + m_errorString = "No ID on Device tag"; + return false; + } + int id = idString.toInt(); + + if (type == "midi") { + QString direction = atts.value("direction").lower(); + + if (direction.isNull() || + direction == "" || + direction == "play") { // ignore inputs + + // This will leave m_device set only if there is a + // valid play midi device to modify: + skipToNextPlayDevice(); + + if (m_device) { + if (nameStr && nameStr != "") { + m_device->setName(qstrtostr(nameStr)); + } + } else if (nameStr && nameStr != "") { + addMIDIDevice(nameStr, m_createDevices); // also sets m_device + } + } + + QString connection = atts.value("connection"); + if (m_createDevices && m_device && + !connection.isNull() && connection != "") { + setMIDIDeviceConnection(connection); + } + + setMIDIDeviceName(nameStr); + + QString vstr = atts.value("variation").lower(); + MidiDevice::VariationType variation = + MidiDevice::NoVariations; + if (!vstr.isNull()) { + if (vstr == "lsb") { + variation = MidiDevice::VariationFromLSB; + } else if (vstr == "msb") { + variation = MidiDevice::VariationFromMSB; + } else if (vstr == "") { + variation = MidiDevice::NoVariations; + } + } + MidiDevice *md = dynamic_cast<MidiDevice *> + (m_device); + if (md) { + md->setVariationType(variation); + } + } else if (type == "softsynth") { + m_device = getStudio().getDevice(id); + + if (m_device && m_device->getType() == Device::SoftSynth) + m_device->setName(qstrtostr(nameStr)); + } else if (type == "audio") { + m_device = getStudio().getDevice(id); + + if (m_device && m_device->getType() == Device::Audio) + m_device->setName(qstrtostr(nameStr)); + } else { + m_errorString = "Found unknown Device type"; + return false; + } + + } else if (lcName == "librarian") { + + // The contact details for the maintainer of the banks/programs + // information. + // + if (m_device && m_device->getType() == Device::Midi) { + QString name = atts.value("name"); + QString email = atts.value("email"); + + dynamic_cast<MidiDevice*>(m_device)-> + setLibrarian(qstrtostr(name), qstrtostr(email)); + } + + } else if (lcName == "bank") { + + if (m_device) // only if we have a device + { + if (m_section != InStudio && m_section != InInstrument) + { + m_errorString = "Found Bank outside Studio or Instrument"; + return false; + } + + QString nameStr = atts.value("name"); + m_percussion = (atts.value("percussion").lower() == "true"); + m_msb = (atts.value("msb")).toInt(); + m_lsb = (atts.value("lsb")).toInt(); + + // To actually create a bank + // + if (m_section == InStudio) + { + // Create a new bank + MidiBank bank(m_percussion, + m_msb, + m_lsb, + qstrtostr(nameStr)); + + if (m_device->getType() == Device::Midi) { + // Insert the bank + // + dynamic_cast<MidiDevice*>(m_device)->addBank(bank); + } + } else // otherwise we're referencing it in an instrument + if (m_section == InInstrument) + { + if (m_instrument) { + m_instrument->setPercussion(m_percussion); + m_instrument->setMSB(m_msb); + m_instrument->setLSB(m_lsb); + m_instrument->setSendBankSelect(true); + } + } + } + + } else if (lcName == "program") { + + if (m_device) // only if we have a device + { + if (m_section == InStudio) + { + QString nameStr = (atts.value("name")); + MidiByte pc = atts.value("id").toInt(); + QString keyMappingStr = (atts.value("keymapping")); + + // Create a new program + MidiProgram program + (MidiBank(m_percussion, + m_msb, + m_lsb), + pc, + qstrtostr(nameStr), + keyMappingStr ? qstrtostr(keyMappingStr) : ""); + + if (m_device->getType() == Device::Midi) { + // Insert the program + // + dynamic_cast<MidiDevice*>(m_device)-> + addProgram(program); + } + + } else if (m_section == InInstrument) + { + if (m_instrument) { + MidiByte id = atts.value("id").toInt(); + m_instrument->setProgramChange(id); + m_instrument->setSendProgramChange(true); + } + } else + { + m_errorString = "Found Program outside Studio and Instrument"; + return false; + } + } + + } else if (lcName == "keymapping") { + + if (m_section == InInstrument) { + RG_DEBUG << "Old-style keymapping in instrument found, ignoring" << endl; + } else { + + if (m_section != InStudio) { + m_errorString = "Found Keymapping outside Studio"; + return false; + } + + if (m_device && (m_device->getType() == Device::Midi)) { + QString name = atts.value("name"); + m_keyMapping = new MidiKeyMapping(qstrtostr(name)); + m_keyNameMap.clear(); + } + } + + } else if (lcName == "key") { + + if (m_keyMapping) { + QString numStr = atts.value("number"); + QString namStr = atts.value("name"); + if (numStr && namStr) { + m_keyNameMap[numStr.toInt()] = qstrtostr(namStr); + } + } + + } else if (lcName == "controls") { + + // Only clear down the controllers list if we have found some controllers in the RG file + // + if (m_device) { + dynamic_cast<MidiDevice*>(m_device)->clearControlList(); + } + + m_haveControls = true; + + } else if (lcName == "control") { + + if (m_section != InStudio) { + m_errorString = "Found ControlParameter outside Studio"; + return false; + } + + if (!m_device) { + //!!! ach no, we can't give this warning -- we might be in a <device> elt + // but have no sequencer support, for example. we need a separate m_inDevice + // flag + // m_deprecation = true; + // std::cerr << "WARNING: This Rosegarden file uses a deprecated control parameter structure. We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + + } else if (m_device->getType() == Device::Midi) { + + if (!m_haveControls) { + m_errorString = "Found ControlParameter outside Controls block"; + return false; + } + + QString name = atts.value("name"); + QString type = atts.value("type"); + QString descr = atts.value("description"); + QString min = atts.value("min"); + QString max = atts.value("max"); + QString def = atts.value("default"); + QString conVal = atts.value("controllervalue"); + QString colour = atts.value("colourindex"); + QString ipbPosition = atts.value("ipbposition"); + + ControlParameter con(qstrtostr(name), + qstrtostr(type), + qstrtostr(descr), + min.toInt(), + max.toInt(), + def.toInt(), + MidiByte(conVal.toInt()), + colour.toInt(), + ipbPosition.toInt()); + + dynamic_cast<MidiDevice*>(m_device)-> + addControlParameter(con); + } + + } else if (lcName == "reverb") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"reverb\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Reverb outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_REVERB, value); + + + } else if (lcName == "chorus") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"chorus\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Chorus outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_CHORUS, value); + + } else if (lcName == "filter") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"filter\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Filter outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_FILTER, value); + + + } else if (lcName == "resonance") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"resonance\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Resonance outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_RESONANCE, value); + + + } else if (lcName == "attack") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"attack\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Attack outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_ATTACK, value); + + } else if (lcName == "release") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"release\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Release outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_RELEASE, value); + + } else if (lcName == "pan") { + + if (m_section != InInstrument && m_section != InBuss) { + m_errorString = "Found Pan outside Instrument or Buss"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_section == InInstrument) { + if (m_instrument) { + m_instrument->setPan(value); + m_instrument->setSendPan(true); + } + } else if (m_section == InBuss) { + if (m_buss) { + m_buss->setPan(value); + } + } + + // keep "velocity" so we're backwards compatible + } else if (lcName == "velocity" || lcName == "volume") { + + if (lcName == "velocity") { + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"velocity\" for an overall MIDI instrument level (now replaced by \"volume\"). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + } + + if (m_section != InInstrument) { + m_errorString = "Found Volume outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) { + if (m_instrument->getType() == Instrument::Audio || + m_instrument->getType() == Instrument::SoftSynth) { + // Backward compatibility: "volume" was in a 0-127 + // range and we now store "level" (float dB) instead. + // Note that we have no such compatibility for + // "recordLevel", whose range has changed silently. + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"volume\" for an audio instrument (now replaced by \"level\"). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + m_instrument->setLevel + (AudioLevel::multiplier_to_dB(float(value) / 100.0)); + } else { + m_instrument->setVolume(value); + m_instrument->setSendVolume(true); + } + } + + } else if (lcName == "level") { + + if (m_section != InBuss && + (m_section != InInstrument || + (m_instrument && + m_instrument->getType() != Instrument::Audio && + m_instrument->getType() != Instrument::SoftSynth))) { + m_errorString = "Found Level outside (audio) Instrument or Buss"; + return false; + } + + double value = qstrtodouble(atts.value("value")); + + if (m_section == InBuss) { + if (m_buss) + m_buss->setLevel(value); + } else { + if (m_instrument) + m_instrument->setLevel(value); + } + + } else if (lcName == "controlchange") { + + if (m_section != InInstrument) { + m_errorString = "Found ControlChange outside Instrument"; + return false; + } + + MidiByte type = atts.value("type").toInt(); + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) { + m_instrument->setControllerValue(type, value); + } + + } else if (lcName == "plugin" || lcName == "synth") { + + PluginContainer *container = 0; + + if (m_section == InInstrument) { +// std::cerr << "Found plugin in instrument" << std::endl; + container = m_instrument; + m_pluginInBuss = false; + } else if (m_section == InBuss) { +// std::cerr << "Found plugin in buss" << std::endl; + container = m_buss; + m_pluginInBuss = true; + } else { + m_errorString = "Found Plugin outside Instrument or Buss"; + return false; + } + + // Despite being InInstrument or InBuss we might not actually + // have a valid one. + // + if (container) { + +// std::cerr << "Have container" << std::endl; + + emit setOperationName(i18n("Loading plugins...")); + ProgressDialog::processEvents(); + + // Get the details + int position; + if (lcName == "synth") { + position = Instrument::SYNTH_PLUGIN_POSITION; + } else { + position = atts.value("position").toInt(); + } + + bool bypassed = false; + QString bpStr = atts.value("bypassed"); + if (bpStr.lower() == "true") + bypassed = true; + + std::string program = ""; + QString progStr = atts.value("program"); + if (progStr) { + program = qstrtostr(progStr); + } + + // Plugins are identified by a structured identifier + // string, but we will accept a LADSPA UniqueId if there's + // no identifier, for backward compatibility + + QString identifier = atts.value("identifier"); + + AudioPlugin *plugin = 0; + AudioPluginManager *apm = getAudioPluginManager(); + + if (!identifier) { + if (atts.value("id")) { + unsigned long id = atts.value("id").toULong(); + if (apm) + plugin = apm->getPluginByUniqueId(id); + } + } else { + if (apm) + plugin = apm->getPluginByIdentifier(identifier); + } + +// std::cerr << "Plugin identifier " << identifier << " -> plugin " << plugin << std::endl; + + // If we find the plugin all is well and good but if + // we don't we just skip it. + // + if (plugin) { + m_plugin = container->getPlugin(position); + if (!m_plugin) { + RG_DEBUG << "WARNING: RoseXmlHandler: instrument/buss " + << container->getId() << " has no plugin position " + << position << endl; + } else { + m_plugin->setAssigned(true); + m_plugin->setBypass(bypassed); + m_plugin->setIdentifier(plugin->getIdentifier().data()); +// std::cerr << "set identifier to plugin at position " << position << std::endl; + if (program != "") { + m_plugin->setProgram(program); + } + } + } else { + // we shouldn't be halting import of the RG file just because + // we can't match a plugin + // + if (identifier) { + RG_DEBUG << "WARNING: RoseXmlHandler: plugin " << identifier << " not found" << endl; + m_pluginsNotFound.insert(identifier); + } else if (atts.value("id")) { + RG_DEBUG << "WARNING: RoseXmlHandler: plugin uid " << atts.value("id") << " not found" << endl; + } else { + m_errorString = "No plugin identifier or uid specified"; + return false; + } + } + } else { // no instrument + + if (lcName == "synth") { + QString identifier = atts.value("identifier"); + if (identifier) { + RG_DEBUG << "WARNING: RoseXmlHandler: no instrument for plugin " << identifier << endl; + m_pluginsNotFound.insert(identifier); + } + } + } + + m_section = InPlugin; + + } else if (lcName == "port") { + + if (m_section != InPlugin) { + m_errorString = "Found Port outside Plugin"; + return false; + } + unsigned long portId = atts.value("id").toULong(); + double value = qstrtodouble(atts.value("value")); + + QString changed = atts.value("changed"); + bool changedSinceProgram = (changed == "true"); + + if (m_plugin) { + m_plugin->addPort(portId, value); + if (changedSinceProgram) { + PluginPortInstance *ppi = m_plugin->getPort(portId); + if (ppi) + ppi->changedSinceProgramChange = true; + } + } + + } else if (lcName == "configure") { + + if (m_section != InPlugin) { + m_errorString = "Found Configure outside Plugin"; + return false; + } + + QString key = atts.value("key"); + QString value = atts.value("value"); + + if (m_plugin) { + m_plugin->setConfigurationValue(qstrtostr(key), qstrtostr(value)); + } + + } else if (lcName == "metronome") { + + if (m_section != InStudio) { + m_errorString = "Found Metronome outside Studio"; + return false; + } + + // Only create if we have a device + // + if (m_device && m_device->getType() == Device::Midi) { + InstrumentId instrument = + atts.value("instrument").toInt(); + + MidiMetronome metronome(instrument); + + if (atts.value("barpitch")) + metronome.setBarPitch(atts.value("barpitch").toInt()); + if (atts.value("beatpitch")) + metronome.setBeatPitch(atts.value("beatpitch").toInt()); + if (atts.value("subbeatpitch")) + metronome.setSubBeatPitch(atts.value("subbeatpitch").toInt()); + if (atts.value("depth")) + metronome.setDepth(atts.value("depth").toInt()); + if (atts.value("barvelocity")) + metronome.setBarVelocity(atts.value("barvelocity").toInt()); + if (atts.value("beatvelocity")) + metronome.setBeatVelocity(atts.value("beatvelocity").toInt()); + if (atts.value("subbeatvelocity")) + metronome.setSubBeatVelocity(atts.value("subbeatvelocity").toInt()); + + dynamic_cast<MidiDevice*>(m_device)-> + setMetronome(metronome); + } + + } else if (lcName == "instrument") { + + if (m_section != InStudio) { + m_errorString = "Found Instrument outside Studio"; + return false; + } + + m_section = InInstrument; + + InstrumentId id = atts.value("id").toInt(); + std::string stringType = qstrtostr(atts.value("type")); + Instrument::InstrumentType type; + + if (stringType == "midi") + type = Instrument::Midi; + else if (stringType == "audio") + type = Instrument::Audio; + else if (stringType == "softsynth") + type = Instrument::SoftSynth; + else { + m_errorString = "Found unknown Instrument type"; + return false; + } + + // Try and match an Instrument in the file with one in + // our studio + // + Instrument *instrument = getStudio().getInstrumentById(id); + + // If we've got an instrument and the types match then + // we use it from now on. + // + if (instrument && instrument->getType() == type) { + m_instrument = instrument; + + // We can also get the channel from this tag + // + MidiByte channel = + (MidiByte)atts.value("channel").toInt(); + m_instrument->setMidiChannel(channel); + } + + } else if (lcName == "buss") { + + if (m_section != InStudio) { + m_errorString = "Found Buss outside Studio"; + return false; + } + + m_section = InBuss; + + BussId id = atts.value("id").toInt(); + Buss *buss = getStudio().getBussById(id); + + // If we've got a buss then we use it from now on. + // + if (buss) { + m_buss = buss; + } else { + m_buss = new Buss(id); + getStudio().addBuss(m_buss); + } + + } else if (lcName == "audiofiles") { + + if (m_section != NoSection) { + m_errorString = "Found AudioFiles inside another section"; + return false; + } + + m_section = InAudioFiles; + + int rate = atts.value("expectedRate").toInt(); + if (rate) { + getAudioFileManager().setExpectedSampleRate(rate); + } + + } else if (lcName == "configuration") { + + setSubHandler(new ConfigurationXmlSubHandler + (lcName, &m_doc->getConfiguration())); + + } else if (lcName == "metadata") { + + if (m_section != InComposition) { + m_errorString = "Found Metadata outside Composition"; + return false; + } + + setSubHandler(new ConfigurationXmlSubHandler + (lcName, &getComposition().getMetadata())); + + } else if (lcName == "recordlevel") { + + if (m_section != InInstrument) { + m_errorString = "Found recordLevel outside Instrument"; + return false; + } + + double value = qstrtodouble(atts.value("value")); + + // if the value retrieved is greater than (say) 15 then we + // must have an old-style 0-127 value instead of a shiny new + // dB value, so convert it + if (value > 15.0) { + value = AudioLevel::multiplier_to_dB(value / 100); + } + + if (m_instrument) + m_instrument->setRecordLevel(value); + + } else if (lcName == "audioinput") { + + if (m_section != InInstrument) { + m_errorString = "Found audioInput outside Instrument"; + return false; + } + + int value = atts.value("value").toInt(); + int channel = atts.value("channel").toInt(); + + QString type = atts.value("type"); + if (type) { + if (type.lower() == "buss") { + if (m_instrument) + m_instrument->setAudioInputToBuss(value, channel); + } else if (type.lower() == "record") { + if (m_instrument) + m_instrument->setAudioInputToRecord(value, channel); + } + } + + } else if (lcName == "audiooutput") { + + if (m_section != InInstrument) { + m_errorString = "Found audioOutput outside Instrument"; + return false; + } + + int value = atts.value("value").toInt(); + if (m_instrument) + m_instrument->setAudioOutput(value); + + } else if (lcName == "appearance") { + + m_section = InAppearance; + + } else if (lcName == "colourmap") { + + if (m_section == InAppearance) { + QString mapName = atts.value("name"); + m_inColourMap = true; + if (mapName == "segmentmap") { + m_colourMap = &m_doc->getComposition().getSegmentColourMap(); + } else + if (mapName == "generalmap") { + m_colourMap = &m_doc->getComposition().getGeneralColourMap(); + } else { // This will change later once we get more of the Appearance code sorted out + RG_DEBUG << "RoseXmlHandler::startElement : Found colourmap with unknown name\n"; + } + } else { + m_errorString = "Found colourmap outside Appearance"; + return false; + } + + } else if (lcName == "colourpair") { + + if (m_inColourMap && m_colourMap) { + unsigned int id = atts.value("id").toInt(); + QString name = atts.value("name"); + unsigned int red = atts.value("red").toInt(); + unsigned int blue = atts.value("blue").toInt(); + unsigned int green = atts.value("green").toInt(); + Colour colour(red, green, blue); + m_colourMap->addItem(colour, qstrtostr(name), id); + } else { + m_errorString = "Found colourpair outside ColourMap"; + return false; + } + + } else if (lcName == "markers") { + + if (!m_inComposition) { + m_errorString = "Found Markers outside Composition"; + return false; + } + + // clear down any markers + getComposition().clearMarkers(); + + } else if (lcName == "marker") { + if (!m_inComposition) { + m_errorString = "Found Marker outside Composition"; + return false; + } + int time = atts.value("time").toInt(); + QString name = atts.value("name"); + QString descr = atts.value("description"); + + Marker *marker = + new Marker(time, + qstrtostr(name), + qstrtostr(descr)); + + getComposition().addMarker(marker); + } else { + RG_DEBUG << "RoseXmlHandler::startElement : Don't know how to parse this : " << qName << endl; + } + + return true; +} + +bool +RoseXmlHandler::endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName) +{ + if (getSubHandler()) { + bool finished; + bool res = getSubHandler()->endElement(namespaceURI, localName, qName.lower(), finished); + if (finished) + setSubHandler(0); + return res; + } + + // Set percentage done + // + if ((m_totalElements > m_elementsSoFar) && + (++m_elementsSoFar % 300 == 0)) { + + emit setProgress(int(double(m_elementsSoFar) / double(m_totalElements) * 100.0)); + ProgressDialog::processEvents(); + } + + QString lcName = qName.lower(); + + if (lcName == "rosegarden-data") { + + getComposition().updateTriggerSegmentReferences(); + + } else if (lcName == "event") { + + if (m_currentSegment && m_currentEvent) { + m_currentSegment->insert(m_currentEvent); + m_currentEvent = 0; + } else if (!m_currentSegment && m_currentEvent) { + m_errorString = "Got event outside of a Segment"; + return false; + } + + } else if (lcName == "chord") { + + m_currentTime += m_chordDuration; + m_inChord = false; + m_chordDuration = 0; + + } else if (lcName == "group") { + + m_inGroup = false; + + } else if (lcName == "segment") { + + if (m_currentSegment && m_segmentEndMarkerTime) { + m_currentSegment->setEndMarkerTime(*m_segmentEndMarkerTime); + delete m_segmentEndMarkerTime; + m_segmentEndMarkerTime = 0; + } + + m_currentSegment = 0; + m_section = NoSection; + + } else if (lcName == "bar-segment" || lcName == "tempo-segment") { + + m_currentSegment = 0; + + } else if (lcName == "composition") { + m_inComposition = false; + m_section = NoSection; + + } else if (lcName == "studio") { + + m_section = NoSection; + + } else if (lcName == "buss") { + + m_section = InStudio; + m_buss = 0; + + } else if (lcName == "instrument") { + + m_section = InStudio; + m_instrument = 0; + + } else if (lcName == "plugin") { + + if (m_pluginInBuss) { + m_section = InBuss; + } else { + m_section = InInstrument; + } + m_plugin = 0; + m_pluginId = 0; + + } else if (lcName == "device") { + + m_device = 0; + + } else if (lcName == "keymapping") { + + if (m_section == InStudio) { + if (m_keyMapping) { + if (!m_keyNameMap.empty()) { + MidiDevice *md = dynamic_cast<MidiDevice *> + (m_device); + if (md) { + m_keyMapping->setMap(m_keyNameMap); + md->addKeyMapping(*m_keyMapping); + } + } + m_keyMapping = 0; + } + } + + } else if (lcName == "audiofiles") { + + m_section = NoSection; + + } else if (lcName == "appearance") { + + m_section = NoSection; + + } else if (lcName == "colourmap") { + m_inColourMap = false; + m_colourMap = 0; + } + + return true; +} + +bool +RoseXmlHandler::characters(const QString& s) +{ + if (m_subHandler) + return m_subHandler->characters(s); + + return true; +} + +QString +RoseXmlHandler::errorString() +{ + return m_errorString; +} + +bool +RoseXmlHandler::error(const QXmlParseException& exception) +{ + m_errorString = QString("%1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + return QXmlDefaultHandler::error( exception ); +} + +bool +RoseXmlHandler::fatalError(const QXmlParseException& exception) +{ + m_errorString = QString("%1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + return QXmlDefaultHandler::fatalError( exception ); +} + +bool +RoseXmlHandler::endDocument() +{ + if (m_foundTempo == false) { + getComposition().setCompositionDefaultTempo + (Composition::getTempoForQpm(120.0)); + } + + return true; +} + +void +RoseXmlHandler::setSubHandler(XmlSubHandler* sh) +{ + delete m_subHandler; + m_subHandler = sh; +} + +void +RoseXmlHandler::addMIDIDevice(QString name, bool createAtSequencer) +{ + unsigned int deviceId = 0; + + if (createAtSequencer) { + + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (int)Device::Midi; + arg << (unsigned int)MidiDevice::Play; + + if (!rgapp->sequencerCall("addDevice(int, unsigned int)", replyType, replyData, data)) { + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << "can't call sequencer addDevice" << endl; + return ; + } + + if (replyType == "unsigned int") { + QDataStream reply(replyData, IO_ReadOnly); + reply >> deviceId; + } else { + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << "got unknown returntype from addDevice()" << endl; + return ; + } + + if (deviceId == Device::NO_DEVICE) { + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << "sequencer addDevice failed" << endl; + return ; + } + + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << " added device " << deviceId << endl; + + } else { + // Generate a new device id at the base Studio side only. + // This may not correspond to any given device id at the + // sequencer side. We should _never_ do this in a document + // that's actually intended to be retained for use, only + // in temporary documents for device import etc. + int tempId = -1; + for (DeviceListIterator i = getStudio().getDevices()->begin(); + i != getStudio().getDevices()->end(); ++i) { + if (int((*i)->getId()) > tempId) + tempId = int((*i)->getId()); + } + deviceId = tempId + 1; + } + + // add the device, so we can name it and set our pointer to it -- + // instruments will be sync'd later in the natural course of things + getStudio().addDevice(qstrtostr(name), deviceId, Device::Midi); + m_device = getStudio().getDevice(deviceId); + m_deviceRunningId = deviceId; +} + +void +RoseXmlHandler::skipToNextPlayDevice() +{ + SEQMAN_DEBUG << "RoseXmlHandler::skipToNextPlayDevice; m_deviceRunningId is " << m_deviceRunningId << endl; + + for (DeviceList::iterator i = getStudio().getDevices()->begin(); + i != getStudio().getDevices()->end(); ++i) { + + MidiDevice *md = + dynamic_cast<MidiDevice *>(*i); + + if (md && md->getDirection() == MidiDevice::Play) { + if (m_deviceRunningId == Device::NO_DEVICE || + md->getId() > m_deviceRunningId) { + + SEQMAN_DEBUG << "RoseXmlHandler::skipToNextPlayDevice: found next device: id " << md->getId() << endl; + + m_device = md; + m_deviceRunningId = md->getId(); + return ; + } + } + } + + SEQMAN_DEBUG << "RoseXmlHandler::skipToNextPlayDevice: fresh out of devices" << endl; + + m_device = 0; +} + +void +RoseXmlHandler::setMIDIDeviceConnection(QString connection) +{ + SEQMAN_DEBUG << "RoseXmlHandler::setMIDIDeviceConnection(" << connection << ")" << endl; + + MidiDevice *md = dynamic_cast<MidiDevice *>(m_device); + if (!md) + return ; + + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)md->getId(); + arg << connection; + + rgapp->sequencerSend("setPlausibleConnection(unsigned int, QString)", + data); + // connection should be sync'd later in the natural course of things +} + +void +RoseXmlHandler::setMIDIDeviceName(QString name) +{ + SEQMAN_DEBUG << "RoseXmlHandler::setMIDIDeviceName(" << name << ")" << endl; + + MidiDevice *md = dynamic_cast<MidiDevice *>(m_device); + if (!md) + return ; + + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)md->getId(); + arg << name; + + std::cerr << "Renaming device " << md->getId() << " to " << name << std::endl; + + rgapp->sequencerSend("renameDevice(unsigned int, QString)", + data); +} + +} diff --git a/src/document/RoseXmlHandler.h b/src/document/RoseXmlHandler.h new file mode 100644 index 0000000..ab06e3a --- /dev/null +++ b/src/document/RoseXmlHandler.h @@ -0,0 +1,192 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_ROSEXMLHANDLER_H_ +#define _RG_ROSEXMLHANDLER_H_ + +#include "base/Device.h" +#include "base/MidiProgram.h" +#include "gui/general/ProgressReporter.h" +#include <map> +#include <set> +#include <string> +#include <qstring.h> +#include "base/Event.h" +#include <qxml.h> + + +class QXmlParseException; +class QXmlAttributes; + + +namespace Rosegarden +{ + +class XmlStorableEvent; +class XmlSubHandler; +class Studio; +class Segment; +class RosegardenGUIDoc; +class Instrument; +class Device; +class Composition; +class ColourMap; +class Buss; +class AudioPluginManager; +class AudioPluginInstance; +class AudioFileManager; + + +/** + * Handler for the Rosegarden XML format + */ +class RoseXmlHandler : public ProgressReporter, public QXmlDefaultHandler +{ +public: + + typedef enum + { + NoSection, + InComposition, + InSegment, + InStudio, + InInstrument, + InBuss, + InAudioFiles, + InPlugin, + InAppearance + } RosegardenFileSection; + + /** + * Construct a new RoseXmlHandler which will put the data extracted + * from the XML file into the specified composition + */ + RoseXmlHandler(RosegardenGUIDoc *doc, + unsigned int elementCount, + bool createNewDevicesWhenNeeded); + + virtual ~RoseXmlHandler(); + + /// overloaded handler functions + virtual bool startDocument(); + virtual bool startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts); + + virtual bool endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName); + + virtual bool characters(const QString& ch); + + virtual bool endDocument (); // [rwb] - for tempo element catch + + bool isDeprecated() { return m_deprecation; } + + bool isCancelled() { return m_cancelled; } + + /// Return the error string set during the parsing (if any) + QString errorString(); + + bool hasActiveAudio() const { return m_hasActiveAudio; } + std::set<QString> &pluginsNotFound() { return m_pluginsNotFound; } + + bool error(const QXmlParseException& exception); + bool fatalError(const QXmlParseException& exception); + +protected: + + // just for convenience -- just call to the document + // + Composition& getComposition(); + Studio& getStudio(); + AudioFileManager& getAudioFileManager(); + AudioPluginManager* getAudioPluginManager(); + + void setSubHandler(XmlSubHandler* sh); + XmlSubHandler* getSubHandler() { return m_subHandler; } + + void addMIDIDevice(QString name, bool createAtSequencer); + void setMIDIDeviceConnection(QString connection); + void setMIDIDeviceName(QString name); + void skipToNextPlayDevice(); + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc *m_doc; + Segment *m_currentSegment; + XmlStorableEvent *m_currentEvent; + + timeT m_currentTime; + timeT m_chordDuration; + timeT *m_segmentEndMarkerTime; + + bool m_inChord; + bool m_inGroup; + bool m_inComposition; + bool m_inColourMap; + std::string m_groupType; + int m_groupId; + int m_groupTupletBase; + int m_groupTupledCount; + int m_groupUntupledCount; + std::map<long, long> m_groupIdMap; + + bool m_foundTempo; + + QString m_errorString; + std::set<QString> m_pluginsNotFound; + + RosegardenFileSection m_section; + Device *m_device; + DeviceId m_deviceRunningId; + bool m_percussion; + MidiByte m_msb; + MidiByte m_lsb; + Instrument *m_instrument; + Buss *m_buss; + AudioPluginInstance *m_plugin; + bool m_pluginInBuss; + ColourMap *m_colourMap; + MidiKeyMapping *m_keyMapping; + MidiKeyMapping::KeyNameMap m_keyNameMap; + unsigned int m_pluginId; + unsigned int m_totalElements; + unsigned int m_elementsSoFar; + + XmlSubHandler *m_subHandler; + bool m_deprecation; + bool m_createDevices; + bool m_haveControls; + bool m_cancelled; + bool m_skipAllAudio; + bool m_hasActiveAudio; +}; + + +} + +#endif diff --git a/src/document/RosegardenGUIDoc.cpp b/src/document/RosegardenGUIDoc.cpp new file mode 100644 index 0000000..f89a83f --- /dev/null +++ b/src/document/RosegardenGUIDoc.cpp @@ -0,0 +1,3117 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "RosegardenGUIDoc.h" +#include <kapplication.h> + +#include <qxml.h> +#include "sound/Midi.h" +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/TrackButtons.h" +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/general/ClefIndex.h" +#include "document/ConfigGroups.h" +#include "base/AudioDevice.h" +#include "base/AudioPluginInstance.h" +#include "base/BaseProperties.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/Configuration.h" +#include "base/Device.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/SoftSynthDevice.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/XmlExportable.h" +#include "commands/edit/EventQuantizeCommand.h" +#include "commands/notation/NormalizeRestsCommand.h" +#include "commands/segment/AddTracksCommand.h" +#include "commands/segment/SegmentInsertCommand.h" +#include "commands/segment/SegmentRecordCommand.h" +#include "commands/segment/ChangeCompositionLengthCommand.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/application/RosegardenGUIView.h" +#include "gui/dialogs/UnusedAudioSelectionDialog.h" +#include "gui/editors/segment/segmentcanvas/AudioPreviewThread.h" +#include "gui/editors/segment/TrackLabel.h" +#include "gui/general/EditViewBase.h" +#include "gui/general/GUIPalette.h" +#include "gui/kdeext/KStartupLogo.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressDialog.h" +#include "MultiViewCommandHistory.h" +#include "RoseXmlHandler.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" +#include "sound/MappedCommon.h" +#include "sound/MappedComposition.h" +#include "sound/MappedDevice.h" +#include "sound/MappedInstrument.h" +#include "sound/MappedEvent.h" +#include "sound/MappedRealTime.h" +#include "sound/MappedStudio.h" +#include "sound/PluginIdentifier.h" +#include "sound/SoundDriver.h" +#include <kcommand.h> +#include <kconfig.h> +#include <kfilterdev.h> +#include <kglobal.h> +#include <kmessagebox.h> +#include <kprocess.h> +#include <kprogress.h> +#include <ktempfile.h> +#include <qcstring.h> +#include <qdatastream.h> +#include <qdialog.h> +#include <qdir.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtextstream.h> +#include <qwidget.h> +#include "gui/widgets/ProgressBar.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +RosegardenGUIDoc::RosegardenGUIDoc(QWidget *parent, + AudioPluginManager *pluginManager, + bool skipAutoload, + const char *name) + : QObject(parent, name), + m_modified(false), + m_autoSaved(false), + m_audioPreviewThread(&m_audioFileManager), + m_commandHistory(new MultiViewCommandHistory()), + m_pluginManager(pluginManager), + m_audioRecordLatency(0, 0), + m_autoSavePeriod(0), + m_quickMarkerTime(-1), + m_beingDestroyed(false) +{ + syncDevices(); + + m_viewList.setAutoDelete(false); + m_editViewList.setAutoDelete(false); + + connect(m_commandHistory, SIGNAL(commandExecuted(KCommand *)), + this, SLOT(slotDocumentModified())); + + connect(m_commandHistory, SIGNAL(documentRestored()), + this, SLOT(slotDocumentRestored())); + + // autoload a new document + if (!skipAutoload) + performAutoload(); + + // now set it up as a "new document" + newDocument(); +} + +RosegardenGUIDoc::~RosegardenGUIDoc() +{ + RG_DEBUG << "~RosegardenGUIDoc()\n"; + m_beingDestroyed = true; + + m_audioPreviewThread.finish(); + m_audioPreviewThread.wait(); + + deleteEditViews(); + + // ControlRulerCanvasRepository::clear(); + + delete m_commandHistory; // must be deleted before the Composition is +} + +unsigned int +RosegardenGUIDoc::getAutoSavePeriod() const +{ + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + return config->readUnsignedNumEntry("autosaveinterval", 60); +} + +void RosegardenGUIDoc::attachView(RosegardenGUIView *view) +{ + m_viewList.append(view); +} + +void RosegardenGUIDoc::detachView(RosegardenGUIView *view) +{ + m_viewList.remove(view); +} + +void RosegardenGUIDoc::attachEditView(EditViewBase *view) +{ + m_editViewList.append(view); +} + +void RosegardenGUIDoc::detachEditView(EditViewBase *view) +{ + // auto-deletion is disabled, as + // the editview detaches itself when being deleted + m_editViewList.remove(view); +} + +void RosegardenGUIDoc::deleteEditViews() +{ + // enabled auto-deletion : edit views will be deleted + m_editViewList.setAutoDelete(true); + m_editViewList.clear(); +} + +void RosegardenGUIDoc::setAbsFilePath(const QString &filename) +{ + m_absFilePath = filename; +} + +void RosegardenGUIDoc::setTitle(const QString &_t) +{ + m_title = _t; +} + +const QString &RosegardenGUIDoc::getAbsFilePath() const +{ + return m_absFilePath; +} + +const QString& RosegardenGUIDoc::getTitle() const +{ + return m_title; +} + +void RosegardenGUIDoc::slotUpdateAllViews(RosegardenGUIView *sender) +{ + RosegardenGUIView *w; + + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + if (w != sender) + w->repaint(); + } +} + +void RosegardenGUIDoc::setModified(bool m) +{ + m_modified = m; + RG_DEBUG << "RosegardenGUIDoc[" << this << "]::setModified(" << m << ")\n"; +} + +void RosegardenGUIDoc::clearModifiedStatus() +{ + setModified(false); + setAutoSaved(true); + emit documentModified(false); +} + +void RosegardenGUIDoc::slotDocumentModified() +{ + RG_DEBUG << "RosegardenGUIDoc::slotDocumentModified()" << endl; + setModified(true); + setAutoSaved(false); + emit documentModified(true); +} + +void RosegardenGUIDoc::slotDocumentRestored() +{ + RG_DEBUG << "RosegardenGUIDoc::slotDocumentRestored()\n"; + setModified(false); +} + +void +RosegardenGUIDoc::setQuickMarker() +{ + RG_DEBUG << "RosegardenGUIDoc::setQuickMarker" << endl; + + m_quickMarkerTime = getComposition().getPosition(); +} + +void +RosegardenGUIDoc::jumpToQuickMarker() +{ + RG_DEBUG << "RosegardenGUIDoc::jumpToQuickMarker" << endl; + + if (m_quickMarkerTime >= 0) + slotSetPointerPosition(m_quickMarkerTime); +} + +QString RosegardenGUIDoc::getAutoSaveFileName() +{ + QString filename = getAbsFilePath(); + if (filename.isEmpty()) + filename = QDir::currentDirPath() + "/" + getTitle(); + + QString autoSaveFileName = kapp->tempSaveName(filename); + + return autoSaveFileName; +} + +void RosegardenGUIDoc::slotAutoSave() +{ + // RG_DEBUG << "RosegardenGUIDoc::slotAutoSave()\n" << endl; + + if (isAutoSaved() || !isModified()) + return ; + + QString autoSaveFileName = getAutoSaveFileName(); + + RG_DEBUG << "RosegardenGUIDoc::slotAutoSave() - doc modified - saving '" + << getAbsFilePath() << "' as " + << autoSaveFileName << endl; + + QString errMsg; + + saveDocument(autoSaveFileName, errMsg, true); + +} + +bool RosegardenGUIDoc::isRegularDotRGFile() +{ + return getAbsFilePath().right(3).lower() == ".rg"; +} + +bool RosegardenGUIDoc::saveIfModified() +{ + RG_DEBUG << "RosegardenGUIDoc::saveIfModified()" << endl; + bool completed = true; + + if (!isModified()) + return completed; + + + RosegardenGUIApp *win = (RosegardenGUIApp *)parent(); + + int wantSave = KMessageBox::warningYesNoCancel + (win, + i18n("The current file has been modified.\n" + "Do you want to save it?"), + i18n("Warning")); + + RG_DEBUG << "wantSave = " << wantSave << endl; + + switch (wantSave) { + + case KMessageBox::Yes: + + if (!isRegularDotRGFile()) { + + RG_DEBUG << "RosegardenGUIDoc::saveIfModified() : new or imported file\n"; + completed = win->slotFileSaveAs(); + + } else { + + RG_DEBUG << "RosegardenGUIDoc::saveIfModified() : regular file\n"; + QString errMsg; + completed = saveDocument(getAbsFilePath(), errMsg); + + if (!completed) { + if (errMsg) { + KMessageBox::error(0, i18n(QString("Could not save document at %1\n(%2)") + .arg(getAbsFilePath()).arg(errMsg))); + } else { + KMessageBox::error(0, i18n(QString("Could not save document at %1") + .arg(getAbsFilePath()))); + } + } + } + + break; + + case KMessageBox::No: + // delete the autosave file so it won't annoy + // the user when reloading the file. + QFile::remove + (getAutoSaveFileName()); + completed = true; + break; + + case KMessageBox::Cancel: + completed = false; + break; + + default: + completed = false; + break; + } + + if (completed) { + completed = deleteOrphanedAudioFiles(wantSave == KMessageBox::No); + if (completed) { + m_audioFileManager.resetRecentlyCreatedFiles(); + } + } + + if (completed) + setModified(false); + return completed; +} + +bool +RosegardenGUIDoc::deleteOrphanedAudioFiles(bool documentWillNotBeSaved) +{ + std::vector<QString> recordedOrphans; + std::vector<QString> derivedOrphans; + + if (documentWillNotBeSaved) { + + // All audio files recorded or derived in this session are + // about to become orphans + + for (std::vector<AudioFile *>::const_iterator i = + m_audioFileManager.begin(); + i != m_audioFileManager.end(); ++i) { + + if (m_audioFileManager.wasAudioFileRecentlyRecorded((*i)->getId())) { + recordedOrphans.push_back(strtoqstr((*i)->getFilename())); + } + + if (m_audioFileManager.wasAudioFileRecentlyDerived((*i)->getId())) { + derivedOrphans.push_back(strtoqstr((*i)->getFilename())); + } + } + } + + // Whether we save or not, explicitly orphaned (i.e. recorded in + // this session and then unloaded) recorded files are orphans. + // Make sure they are actually unknown to the audio file manager + // (i.e. they haven't been loaded more than once, or reloaded + // after orphaning). + + for (std::vector<QString>::iterator i = m_orphanedRecordedAudioFiles.begin(); + i != m_orphanedRecordedAudioFiles.end(); ++i) { + + bool stillHave = false; + + for (std::vector<AudioFile *>::const_iterator j = + m_audioFileManager.begin(); + j != m_audioFileManager.end(); ++j) { + if (strtoqstr((*j)->getFilename()) == *i) { + stillHave = true; + break; + } + } + + if (!stillHave) recordedOrphans.push_back(*i); + } + + // Derived orphans get deleted whatever happens + //!!! Should we orphan any file derived during this session that + //is not currently used in a segment? Probably: we have no way to + //reuse them + + for (std::vector<QString>::iterator i = m_orphanedDerivedAudioFiles.begin(); + i != m_orphanedDerivedAudioFiles.end(); ++i) { + + bool stillHave = false; + + for (std::vector<AudioFile *>::const_iterator j = + m_audioFileManager.begin(); + j != m_audioFileManager.end(); ++j) { + if (strtoqstr((*j)->getFilename()) == *i) { + stillHave = true; + break; + } + } + + if (!stillHave) derivedOrphans.push_back(*i); + } + + for (size_t i = 0; i < derivedOrphans.size(); ++i) { + QFile file(derivedOrphans[i]); + if (!file.remove()) { + std::cerr << "WARNING: Failed to remove orphaned derived audio file \"" << derivedOrphans[i] << std::endl; + } + QFile peakFile(QString("%1.pk").arg(derivedOrphans[i])); + peakFile.remove(); + } + + m_orphanedDerivedAudioFiles.clear(); + + if (recordedOrphans.empty()) + return true; + + if (documentWillNotBeSaved) { + + int reply = KMessageBox::warningYesNoCancel + (0, + i18n("Delete the 1 audio file recorded during the unsaved session?", + "Delete the %n audio files recorded during the unsaved session?", + recordedOrphans.size())); + + switch (reply) { + + case KMessageBox::Yes: + break; + + case KMessageBox::No: + return true; + + default: + case KMessageBox::Cancel: + return false; + } + + } else { + + UnusedAudioSelectionDialog *dialog = + new UnusedAudioSelectionDialog + (0, + i18n("The following audio files were recorded during this session but have been unloaded\nfrom the audio file manager, and so are no longer in use in the document you are saving.\n\nYou may want to clean up these files to save disk space.\n\nPlease select any you wish to delete permanently from the hard disk.\n"), + recordedOrphans); + + if (dialog->exec() != QDialog::Accepted) { + delete dialog; + return true; + } + + recordedOrphans = dialog->getSelectedAudioFileNames(); + delete dialog; + } + + if (recordedOrphans.empty()) + return true; + + QString question = + i18n("<qt>About to delete 1 audio file permanently from the hard disk.<br>There will be no way to recover this file.<br>Are you sure?</qt>\n", "<qt>About to delete %n audio files permanently from the hard disk.<br>There will be no way to recover these files.<br>Are you sure?</qt>", recordedOrphans.size()); + + int reply = KMessageBox::warningContinueCancel(0, question); + + if (reply == KMessageBox::Continue) { + for (size_t i = 0; i < recordedOrphans.size(); ++i) { + QFile file(recordedOrphans[i]); + if (!file.remove()) { + KMessageBox::error(0, i18n("File %1 could not be deleted.") + .arg(recordedOrphans[i])); + } + + QFile peakFile(QString("%1.pk").arg(recordedOrphans[i])); + peakFile.remove(); + } + } + + return true; +} + +void RosegardenGUIDoc::newDocument() +{ + setModified(false); + setAbsFilePath(QString::null); + setTitle(i18n("Untitled")); + m_commandHistory->clear(); +} + +void RosegardenGUIDoc::performAutoload() +{ + QString autoloadFile = + KGlobal::dirs()->findResource("appdata", "autoload.rg"); + + QFileInfo autoloadFileInfo(autoloadFile); + + if (!autoloadFileInfo.isReadable()) { + RG_DEBUG << "RosegardenGUIDoc::performAutoload - " + << "can't find autoload file - defaulting" << endl; + return ; + } + + openDocument(autoloadFile); + +} + +bool RosegardenGUIDoc::openDocument(const QString& filename, + bool permanent, + const char* /*format*/ /*=0*/) +{ + RG_DEBUG << "RosegardenGUIDoc::openDocument(" + << filename << ")" << endl; + + if (!filename || filename.isEmpty()) + return false; + + newDocument(); + + QFileInfo fileInfo(filename); + setTitle(fileInfo.fileName()); + + // Check if file readable with fileInfo ? + if (!fileInfo.isReadable() || fileInfo.isDir()) { + KStartupLogo::hideIfStillThere(); + QString msg(i18n("Can't open file '%1'").arg(filename)); + KMessageBox::sorry(0, msg); + return false; + } + + ProgressDialog progressDlg(i18n("Reading file..."), + 100, + (QWidget*)parent()); + + connect(&progressDlg, SIGNAL(cancelClicked()), + &m_audioFileManager, SLOT(slotStopPreview())); + + progressDlg.setMinimumDuration(500); + progressDlg.setAutoReset(true); // we're re-using it for the preview generation + setAbsFilePath(fileInfo.absFilePath()); + + QString errMsg; + QString fileContents; + bool cancelled = false, okay = true; + + KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(KFilterDev::deviceForFile(filename, "application/x-gzip")); + if (fileCompressedDevice == 0) { + + errMsg = i18n("Could not open Rosegarden file"); + + } else { + fileCompressedDevice->open(IO_ReadOnly); + + unsigned int elementCount = fileInfo.size() / 4; // approx. guess + // RG_DEBUG << "RosegardenGUIDoc::xmlParse() : elementCount = " << elementCount + // << " - file size : " << file->size() + // << endl; + + + // Fugly work-around in case of broken rg files + // + int c = 0; + std::vector<char> baseBuffer; + + while (c != -1) { + c = fileCompressedDevice->getch(); + if (c != -1) + baseBuffer.push_back(c); + } + + fileCompressedDevice->close(); + + QString fileContents = QString::fromUtf8(&baseBuffer[0], + baseBuffer.size()); + + // parse xml file + okay = xmlParse(fileContents, errMsg, &progressDlg, + elementCount, permanent, cancelled); + // okay = xmlParse(fileCompressedDevice, errMsg, &progressDlg, + // elementCount, permanent, cancelled); + delete fileCompressedDevice; + + } + + if (!okay) { + KStartupLogo::hideIfStillThere(); + QString msg(i18n("Error when parsing file '%1': \"%2\"") + .arg(filename) + .arg(errMsg)); + + CurrentProgressDialog::freeze(); + KMessageBox::sorry(0, msg); + CurrentProgressDialog::thaw(); + + return false; + + } else if (cancelled) { + newDocument(); + return false; + } + + RG_DEBUG << "RosegardenGUIDoc::openDocument() end - " + << "m_composition : " << &m_composition + << " - m_composition->getNbSegments() : " + << m_composition.getNbSegments() + << " - m_composition->getDuration() : " + << m_composition.getDuration() << endl; + + if (m_composition.begin() != m_composition.end()) { + RG_DEBUG << "First segment starts at " << (*m_composition.begin())->getStartTime() << endl; + } + + // Ensure a minimum of 64 tracks + // + // unsigned int nbTracks = m_composition.getNbTracks(); + // TrackId maxTrackId = m_composition.getMaxTrackId(); + // InstrumentId instBase = MidiInstrumentBase; + + // for(unsigned int i = nbTracks; i < MinNbOfTracks; ++i) { + + // Track *track; + + // track = new Track(maxTrackId + 1, // id + // (i + instBase) % 16, // instrument + // i, // position + // "untitled", + // false); // mute + + // m_composition.addTrack(track); + // ++maxTrackId; + // } + + // We might need a progress dialog when we generate previews, + // reuse the previous one + progressDlg.setLabel(i18n("Generating audio previews...")); + + connect(&m_audioFileManager, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + try { + // generate any audio previews after loading the files + m_audioFileManager.generatePreviews(); + } catch (Exception e) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::error(0, strtoqstr(e.getMessage())); + CurrentProgressDialog::thaw(); + } + + if (isSequencerRunning()) { + // Initialise the whole studio - faders, plugins etc. + // + initialiseStudio(); + + // Initialise the MIDI controllers (reaches through to MIDI devices + // to set them up) + // + initialiseControllers(); + } + + return true; +} + +void +RosegardenGUIDoc::mergeDocument(RosegardenGUIDoc *doc, + int options) +{ + KMacroCommand *command = new KMacroCommand(i18n("Merge")); + + timeT time0 = 0; + if (options & MERGE_AT_END) { + time0 = getComposition().getBarEndForTime(getComposition().getDuration()); + } + + int myMaxTrack = getComposition().getNbTracks(); + int yrMinTrack = 0; + int yrMaxTrack = doc->getComposition().getNbTracks(); + int yrNrTracks = yrMaxTrack - yrMinTrack + 1; + + int firstAlteredTrack = yrMinTrack; + + if (options & MERGE_IN_NEW_TRACKS) { + + //!!! worry about instruments and other studio stuff later... if at all + command->addCommand(new AddTracksCommand + (&getComposition(), + yrNrTracks, + MidiInstrumentBase, + -1)); + + firstAlteredTrack = myMaxTrack + 1; + + } else if (yrMaxTrack > myMaxTrack) { + + command->addCommand(new AddTracksCommand + (&getComposition(), + yrMaxTrack - myMaxTrack, + MidiInstrumentBase, + -1)); + } + + TrackId firstNewTrackId = getComposition().getNewTrackId(); + timeT lastSegmentEndTime = 0; + + for (Composition::iterator i = doc->getComposition().begin(), j = i; + i != doc->getComposition().end(); i = j) { + + ++j; + Segment *s = *i; + timeT segmentEndTime = s->getEndMarkerTime(); + + int yrTrack = s->getTrack(); + Track *t = doc->getComposition().getTrackById(yrTrack); + if (t) yrTrack = t->getPosition(); + + int myTrack = yrTrack; + + if (options & MERGE_IN_NEW_TRACKS) { + myTrack = yrTrack - yrMinTrack + myMaxTrack + 1; + } + + doc->getComposition().detachSegment(s); + + if (options & MERGE_AT_END) { + s->setStartTime(s->getStartTime() + time0); + segmentEndTime += time0; + } + if (segmentEndTime > lastSegmentEndTime) { + lastSegmentEndTime = segmentEndTime; + } + + Track *track = getComposition().getTrackByPosition(myTrack); + TrackId tid = 0; + if (track) tid = track->getId(); + else tid = firstNewTrackId + yrTrack - yrMinTrack; + + command->addCommand(new SegmentInsertCommand(&getComposition(), s, tid)); + } + + if (!(options & MERGE_KEEP_OLD_TIMINGS)) { + for (int i = getComposition().getTimeSignatureCount() - 1; i >= 0; --i) { + getComposition().removeTimeSignature(i); + } + for (int i = getComposition().getTempoChangeCount() - 1; i >= 0; --i) { + getComposition().removeTempoChange(i); + } + } + + if (options & MERGE_KEEP_NEW_TIMINGS) { + for (int i = 0; i < doc->getComposition().getTimeSignatureCount(); ++i) { + std::pair<timeT, TimeSignature> ts = + doc->getComposition().getTimeSignatureChange(i); + getComposition().addTimeSignature(ts.first + time0, ts.second); + } + for (int i = 0; i < doc->getComposition().getTempoChangeCount(); ++i) { + std::pair<timeT, tempoT> t = + doc->getComposition().getTempoChange(i); + getComposition().addTempoAtTime(t.first + time0, t.second); + } + } + + if (lastSegmentEndTime > getComposition().getEndMarker()) { + command->addCommand(new ChangeCompositionLengthCommand + (&getComposition(), + getComposition().getStartMarker(), + lastSegmentEndTime)); + } + + m_commandHistory->addCommand(command); + + emit makeTrackVisible(firstAlteredTrack + yrNrTracks/2 + 1); +} + +void RosegardenGUIDoc::clearStudio() +{ + QCString replyType; + QByteArray replyData; + rgapp->sequencerCall("clearStudio()", replyType, replyData); + RG_DEBUG << "cleared studio\n"; +} + +void RosegardenGUIDoc::initialiseStudio() +{ + Profiler profiler("initialiseStudio", true); + + RG_DEBUG << "RosegardenGUIDoc::initialiseStudio - " + << "clearing down and initialising" << endl; + + clearStudio(); + + InstrumentList list = m_studio.getAllInstruments(); + InstrumentList::iterator it = list.begin(); + int audioCount = 0; + + BussList busses = m_studio.getBusses(); + RecordInList recordIns = m_studio.getRecordIns(); + + // To reduce the number of DCOP calls at this stage, we put some + // of the float property values in a big list and commit in one + // single call at the end. We can only do this with properties + // that aren't depended on by other port, connection, or non-float + // properties during the initialisation process. + MappedObjectIdList ids; + MappedObjectPropertyList properties; + MappedObjectValueList values; + + std::vector<PluginContainer *> pluginContainers; + + for (unsigned int i = 0; i < busses.size(); ++i) { + + // first one is master + MappedObjectId mappedId = + StudioControl::createStudioObject( + MappedObject::AudioBuss); + + StudioControl::setStudioObjectProperty + (mappedId, + MappedAudioBuss::BussId, + MappedObjectValue(i)); + + ids.push_back(mappedId); + properties.push_back(MappedAudioBuss::Level); + values.push_back(MappedObjectValue(busses[i]->getLevel())); + + ids.push_back(mappedId); + properties.push_back(MappedAudioBuss::Pan); + values.push_back(MappedObjectValue(busses[i]->getPan()) - 100.0); + + busses[i]->setMappedId(mappedId); + + pluginContainers.push_back(busses[i]); + } + + for (unsigned int i = 0; i < recordIns.size(); ++i) { + + MappedObjectId mappedId = + StudioControl::createStudioObject( + MappedObject::AudioInput); + + StudioControl::setStudioObjectProperty + (mappedId, + MappedAudioInput::InputNumber, + MappedObjectValue(i)); + + recordIns[i]->setMappedId(mappedId); + } + + for (; it != list.end(); it++) { + if ((*it)->getType() == Instrument::Audio || + (*it)->getType() == Instrument::SoftSynth) { + MappedObjectId mappedId = + StudioControl::createStudioObject( + MappedObject::AudioFader); + + // Set the object id against the instrument + // + (*it)->setMappedId(mappedId); + + /* + cout << "SETTING MAPPED OBJECT ID = " << mappedId + << " - on Instrument " << (*it)->getId() << endl; + */ + + + // Set the instrument id against this object + // + StudioControl::setStudioObjectProperty + (mappedId, + MappedObject::Instrument, + MappedObjectValue((*it)->getId())); + + // Set the level + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::FaderLevel); + values.push_back(MappedObjectValue((*it)->getLevel())); + + // Set the record level + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::FaderRecordLevel); + values.push_back(MappedObjectValue((*it)->getRecordLevel())); + + // Set the number of channels + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::Channels); + values.push_back(MappedObjectValue((*it)->getAudioChannels())); + + // Set the pan - 0 based + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::Pan); + values.push_back(MappedObjectValue(float((*it)->getPan())) - 100.0); + + // Set up connections: first clear any existing ones (shouldn't + // be necessary, but) + // + StudioControl::disconnectStudioObject(mappedId); + + // then handle the output connection + // + BussId outputBuss = (*it)->getAudioOutput(); + if (outputBuss < busses.size()) { + MappedObjectId bmi = busses[outputBuss]->getMappedId(); + + if (bmi > 0) { + StudioControl::connectStudioObjects(mappedId, bmi); + } + } + + // then the input + // + bool isBuss; + int channel; + int input = (*it)->getAudioInput(isBuss, channel); + MappedObjectId rmi = 0; + + if (isBuss) { + if (input < int(busses.size())) { + rmi = busses[input]->getMappedId(); + } + } else { + if (input < int(recordIns.size())) { + rmi = recordIns[input]->getMappedId(); + } + } + + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::InputChannel); + values.push_back(MappedObjectValue(channel)); + + if (rmi > 0) { + StudioControl::connectStudioObjects(rmi, mappedId); + } + + pluginContainers.push_back(*it); + + audioCount++; + } + } + + for (std::vector<PluginContainer *>::iterator pci = + pluginContainers.begin(); pci != pluginContainers.end(); ++pci) { + + // Initialise all the plugins for this Instrument or Buss + + for (PluginInstanceIterator pli = (*pci)->beginPlugins(); + pli != (*pci)->endPlugins(); ++pli) { + + AudioPluginInstance *plugin = *pli; + + if (plugin->isAssigned()) { + // Create the plugin slot at the sequencer Studio + // + MappedObjectId pluginMappedId = + StudioControl::createStudioObject( + MappedObject::PluginSlot); + + // Create the back linkage from the instance to the + // studio id + // + plugin->setMappedId(pluginMappedId); + + //RG_DEBUG << "CREATING PLUGIN ID = " + //<< pluginMappedId << endl; + + // Set the position + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedObject::Position, + MappedObjectValue(plugin->getPosition())); + + // Set the id of this instrument or buss on the plugin + // + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedObject::Instrument, + (*pci)->getId()); + + // Set the plugin type id - this will set it up ready + // for the rest of the settings. String value, so can't + // go in the main property list. + // + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Identifier, + plugin->getIdentifier().c_str()); + + plugin->setConfigurationValue + (qstrtostr(PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY), + getAudioFileManager().getAudioPath()); + + // Set opaque string configuration data (e.g. for DSSI plugin) + // + MappedObjectPropertyList config; + for (AudioPluginInstance::ConfigMap::const_iterator + i = plugin->getConfiguration().begin(); + i != plugin->getConfiguration().end(); ++i) { + config.push_back(strtoqstr(i->first)); + config.push_back(strtoqstr(i->second)); + } + + StudioControl::setStudioObjectPropertyList + (pluginMappedId, + MappedPluginSlot::Configuration, + config); + + // Set the bypass + // + ids.push_back(pluginMappedId); + properties.push_back(MappedPluginSlot::Bypassed); + values.push_back(MappedObjectValue(plugin->isBypassed())); + + // Set all the port values + // + PortInstanceIterator portIt; + + for (portIt = plugin->begin(); + portIt != plugin->end(); ++portIt) { + StudioControl::setStudioPluginPort + (pluginMappedId, + (*portIt)->number, + (*portIt)->value); + } + + // Set the program + // + if (plugin->getProgram() != "") { + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Program, + strtoqstr(plugin->getProgram())); + } + + // Set the post-program port values + // + for (portIt = plugin->begin(); + portIt != plugin->end(); ++portIt) { + if ((*portIt)->changedSinceProgramChange) { + StudioControl::setStudioPluginPort + (pluginMappedId, + (*portIt)->number, + (*portIt)->value); + } + } + } + } + } + + // Now commit all the remaining changes + StudioControl::setStudioObjectProperties(ids, properties, values); + + KConfig* config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + + bool faderOuts = config->readBoolEntry("audiofaderouts", false); + bool submasterOuts = config->readBoolEntry("audiosubmasterouts", false); + unsigned int audioFileFormat = config->readUnsignedNumEntry("audiorecordfileformat", 1); + + MidiByte ports = 0; + if (faderOuts) { + ports |= MappedEvent::FaderOuts; + } + if (submasterOuts) { + ports |= MappedEvent::SubmasterOuts; + } + MappedEvent mEports + (MidiInstrumentBase, + MappedEvent::SystemAudioPorts, + ports); + + StudioControl::sendMappedEvent(mEports); + + MappedEvent mEff + (MidiInstrumentBase, + MappedEvent::SystemAudioFileFormat, + audioFileFormat); + StudioControl::sendMappedEvent(mEff); +} + +int RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR = 1; + +int RosegardenGUIDoc::FILE_FORMAT_VERSION_MINOR = 4; + +int RosegardenGUIDoc::FILE_FORMAT_VERSION_POINT = 0; + + + +bool RosegardenGUIDoc::saveDocument(const QString& filename, + QString& errMsg, + bool autosave) +{ + if (!QFileInfo(filename).exists()) { // safe to write directly + return saveDocumentActual(filename, errMsg, autosave); + } + + KTempFile temp(filename + ".", "", 0644); // will be umask'd + + int status = temp.status(); + if (status != 0) { + errMsg = i18n(QString("Could not create temporary file in directory of '%1': %2").arg(filename).arg(strerror(status))); + return false; + } + + QString tempFileName = temp.name(); + + RG_DEBUG << "Temporary file name is: \"" << tempFileName << "\"" << endl; + + // KTempFile creates a temporary file that is already open: close it + if (!temp.close()) { + status = temp.status(); + errMsg = i18n(QString("Failure in temporary file handling for file '%1': %2") + .arg(tempFileName).arg(strerror(status))); + return false; + } + + bool success = saveDocumentActual(tempFileName, errMsg, autosave); + + if (!success) { + // errMsg should be already set + return false; + } + + QDir dir(QFileInfo(tempFileName).dir()); + if (!dir.rename(tempFileName, filename)) { + errMsg = i18n(QString("Failed to rename temporary output file '%1' to desired output file '%2'").arg(tempFileName).arg(filename)); + return false; + } + + return true; +} + + +bool RosegardenGUIDoc::saveDocumentActual(const QString& filename, + QString& errMsg, + bool autosave) +{ + Profiler profiler("RosegardenGUIDoc::saveDocumentActual"); + RG_DEBUG << "RosegardenGUIDoc::saveDocumentActual(" << filename << ")\n"; + + KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(KFilterDev::deviceForFile(filename, "application/x-gzip")); + fileCompressedDevice->setOrigFileName("audio/x-rosegarden"); + bool rc = fileCompressedDevice->open(IO_WriteOnly); + + if (!rc) { + // do some error report + errMsg = i18n(QString("Could not open file '%1' for writing").arg(filename)); + delete fileCompressedDevice; + return false; // couldn't open file + } + + + QTextStream outStream(fileCompressedDevice); + outStream.setEncoding(QTextStream::UnicodeUTF8); + + // output XML header + // + outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<!DOCTYPE rosegarden-data>\n" + << "<rosegarden-data version=\"" << VERSION + << "\" format-version-major=\"" << FILE_FORMAT_VERSION_MAJOR + << "\" format-version-minor=\"" << FILE_FORMAT_VERSION_MINOR + << "\" format-version-point=\"" << FILE_FORMAT_VERSION_POINT + << "\">\n"; + + ProgressDialog *progressDlg = 0; + KProgress *progress = 0; + + if (!autosave) { + + progressDlg = new ProgressDialog(i18n("Saving file..."), + 100, + (QWidget*)parent()); + progress = progressDlg->progressBar(); + + progressDlg->setMinimumDuration(500); + progressDlg->setAutoReset(true); + + } else { + + progress = ((RosegardenGUIApp *)parent())->getProgressBar(); + } + + // Send out Composition (this includes Tracks, Instruments, Tempo + // and Time Signature changes and any other sub-objects) + // + outStream << strtoqstr(getComposition().toXmlString()) + << endl << endl; + + outStream << strtoqstr(getAudioFileManager().toXmlString()) + << endl << endl; + + outStream << strtoqstr(getConfiguration().toXmlString()) + << endl << endl; + + long totalEvents = 0; + for (Composition::iterator segitr = m_composition.begin(); + segitr != m_composition.end(); ++segitr) { + totalEvents += (*segitr)->size(); + } + + for (Composition::triggersegmentcontaineriterator ci = + m_composition.getTriggerSegments().begin(); + ci != m_composition.getTriggerSegments().end(); ++ci) { + totalEvents += (*ci)->getSegment()->size(); + } + + // output all elements + // + // Iterate on segments + long eventCount = 0; + + for (Composition::iterator segitr = m_composition.begin(); + segitr != m_composition.end(); ++segitr) { + + Segment *segment = *segitr; + + saveSegment(outStream, segment, progress, totalEvents, eventCount); + + } + + // Put a break in the file + // + outStream << endl << endl; + + for (Composition::triggersegmentcontaineriterator ci = + m_composition.getTriggerSegments().begin(); + ci != m_composition.getTriggerSegments().end(); ++ci) { + + QString triggerAtts = QString + ("triggerid=\"%1\" triggerbasepitch=\"%2\" triggerbasevelocity=\"%3\" triggerretune=\"%4\" triggeradjusttimes=\"%5\" ") + .arg((*ci)->getId()) + .arg((*ci)->getBasePitch()) + .arg((*ci)->getBaseVelocity()) + .arg((*ci)->getDefaultRetune()) + .arg(strtoqstr((*ci)->getDefaultTimeAdjust())); + + Segment *segment = (*ci)->getSegment(); + saveSegment(outStream, segment, progress, totalEvents, eventCount, triggerAtts); + } + + // Put a break in the file + // + outStream << endl << endl; + + // Send out the studio - a self contained command + // + outStream << strtoqstr(m_studio.toXmlString()) << endl << endl; + + + // Send out the appearance data + outStream << "<appearance>" << endl; + outStream << strtoqstr(getComposition().getSegmentColourMap().toXmlString("segmentmap")); + outStream << strtoqstr(getComposition().getGeneralColourMap().toXmlString("generalmap")); + outStream << "</appearance>" << endl << endl << endl; + + // close the top-level XML tag + // + outStream << "</rosegarden-data>\n"; + + // check that all went ok + // + if (fileCompressedDevice->status() != IO_Ok) { + errMsg = i18n(QString("Error while writing on '%1'").arg(filename)); + delete fileCompressedDevice; + return false; + } + + fileCompressedDevice->close(); + + delete fileCompressedDevice; // DO NOT USE outStream AFTER THIS POINT + + RG_DEBUG << endl << "RosegardenGUIDoc::saveDocument() finished\n"; + + if (!autosave) { + emit documentModified(false); + setModified(false); + m_commandHistory->documentSaved(); + delete progressDlg; + } else { + progress->setProgress(0); + } + + setAutoSaved(true); + + return true; +} + +bool RosegardenGUIDoc::exportStudio(const QString& filename, + std::vector<DeviceId> devices) +{ + Profiler profiler("RosegardenGUIDoc::exportStudio"); + RG_DEBUG << "RosegardenGUIDoc::exportStudio(" + << filename << ")\n"; + + KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(KFilterDev::deviceForFile(filename, "application/x-gzip")); + fileCompressedDevice->setOrigFileName("audio/x-rosegarden-device"); + fileCompressedDevice->open(IO_WriteOnly); + QTextStream outStream(fileCompressedDevice); + outStream.setEncoding(QTextStream::UnicodeUTF8); + + // output XML header + // + outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<!DOCTYPE rosegarden-data>\n" + << "<rosegarden-data version=\"" << VERSION << "\">\n"; + + // Send out the studio - a self contained command + // + outStream << strtoqstr(m_studio.toXmlString(devices)) << endl << endl; + + // close the top-level XML tag + // + outStream << "</rosegarden-data>\n"; + + delete fileCompressedDevice; + + RG_DEBUG << endl << "RosegardenGUIDoc::exportStudio() finished\n"; + return true; +} + +void RosegardenGUIDoc::saveSegment(QTextStream& outStream, Segment *segment, + KProgress* progress, long totalEvents, long &count, + QString extraAttributes) +{ + QString time; + + outStream << QString("<segment track=\"%1\" start=\"%2\" ") + .arg(segment->getTrack()) + .arg(segment->getStartTime()); + + if (extraAttributes) + outStream << extraAttributes << " "; + + outStream << "label=\"" << + strtoqstr(XmlExportable::encode(segment->getLabel())); + + if (segment->isRepeating()) { + outStream << "\" repeat=\"true"; + } + + if (segment->getTranspose() != 0) { + outStream << "\" transpose=\"" << segment->getTranspose(); + } + + if (segment->getDelay() != 0) { + outStream << "\" delay=\"" << segment->getDelay(); + } + + if (segment->getRealTimeDelay() != RealTime::zeroTime) { + outStream << "\" rtdelaysec=\"" << segment->getRealTimeDelay().sec + << "\" rtdelaynsec=\"" << segment->getRealTimeDelay().nsec; + } + + if (segment->getColourIndex() != 0) { + outStream << "\" colourindex=\"" << segment->getColourIndex(); + } + + if (segment->getSnapGridSize() != -1) { + outStream << "\" snapgridsize=\"" << segment->getSnapGridSize(); + } + + if (segment->getViewFeatures() != 0) { + outStream << "\" viewfeatures=\"" << segment->getViewFeatures(); + } + + const timeT *endMarker = segment->getRawEndMarkerTime(); + if (endMarker) { + outStream << "\" endmarker=\"" << *endMarker; + } + + if (segment->getType() == Segment::Audio) { + + outStream << "\" type=\"audio\" " + << "file=\"" + << segment->getAudioFileId(); + + if (segment->getStretchRatio() != 1.f && + segment->getStretchRatio() != 0.f) { + + outStream << "\" unstretched=\"" + << segment->getUnstretchedFileId() + << "\" stretch=\"" + << segment->getStretchRatio(); + } + + outStream << "\">\n"; + + // convert out - should do this as XmlExportable really + // once all this code is centralised + // + time.sprintf("%d.%06d", segment->getAudioStartTime().sec, + segment->getAudioStartTime().usec()); + + outStream << " <begin index=\"" + << time + << "\"/>\n"; + + time.sprintf("%d.%06d", segment->getAudioEndTime().sec, + segment->getAudioEndTime().usec()); + + outStream << " <end index=\"" + << time + << "\"/>\n"; + + if (segment->isAutoFading()) { + time.sprintf("%d.%06d", segment->getFadeInTime().sec, + segment->getFadeInTime().usec()); + + outStream << " <fadein time=\"" + << time + << "\"/>\n"; + + time.sprintf("%d.%06d", segment->getFadeOutTime().sec, + segment->getFadeOutTime().usec()); + + outStream << " <fadeout time=\"" + << time + << "\"/>\n"; + } + + } else // Internal type + { + outStream << "\">\n"; + + bool inChord = false; + timeT chordStart = 0, chordDuration = 0; + timeT expectedTime = segment->getStartTime(); + + for (Segment::iterator i = segment->begin(); + i != segment->end(); ++i) { + + timeT absTime = (*i)->getAbsoluteTime(); + + Segment::iterator nextEl = i; + ++nextEl; + + if (nextEl != segment->end() && + (*nextEl)->getAbsoluteTime() == absTime && + (*i)->getDuration() != 0 && + !inChord) { + outStream << "<chord>" << endl; + inChord = true; + chordStart = absTime; + chordDuration = 0; + } + + if (inChord && (*i)->getDuration() > 0) + if (chordDuration == 0 || (*i)->getDuration() < chordDuration) + chordDuration = (*i)->getDuration(); + + outStream << '\t' + << strtoqstr((*i)->toXmlString(expectedTime)) << endl; + + if (nextEl != segment->end() && + (*nextEl)->getAbsoluteTime() != absTime && + inChord) { + outStream << "</chord>\n"; + inChord = false; + expectedTime = chordStart + chordDuration; + } else if (inChord) { + expectedTime = absTime; + } else { + expectedTime = absTime + (*i)->getDuration(); + } + + if ((++count % 500 == 0) && progress) { + progress->setValue(count * 100 / totalEvents); + } + } + + if (inChord) { + outStream << "</chord>\n"; + } + + // Add EventRulers to segment - we call them controllers because of + // a historical mistake in naming them. My bad. RWB. + // + Segment::EventRulerList list = segment->getEventRulerList(); + + if (list.size()) { + outStream << "<gui>\n"; // gui elements + Segment::EventRulerListConstIterator it; + for (it = list.begin(); it != list.end(); ++it) { + outStream << " <controller type=\"" << strtoqstr((*it)->m_type); + + if ((*it)->m_type == Controller::EventType) { + outStream << "\" value =\"" << (*it)->m_controllerValue; + } + + outStream << "\"/>\n"; + } + outStream << "</gui>\n"; + } + + } + + + outStream << "</segment>\n"; //------------------------- + +} + +bool RosegardenGUIDoc::isSequencerRunning() +{ + RosegardenGUIApp* parentApp = dynamic_cast<RosegardenGUIApp*>(parent()); + if (!parentApp) { + RG_DEBUG << "RosegardenGUIDoc::isSequencerRunning() : parentApp == 0\n"; + return false; + } + + return parentApp->isSequencerRunning(); +} + +bool +RosegardenGUIDoc::xmlParse(QString fileContents, QString &errMsg, + ProgressDialog *progress, + unsigned int elementCount, + bool permanent, + bool &cancelled) +{ + cancelled = false; + + RoseXmlHandler handler(this, elementCount, permanent); + + if (progress) { + connect(&handler, SIGNAL(setProgress(int)), + progress->progressBar(), SLOT(setValue(int))); + connect(&handler, SIGNAL(setOperationName(QString)), + progress, SLOT(slotSetOperationName(QString))); + connect(&handler, SIGNAL(incrementProgress(int)), + progress->progressBar(), SLOT(advance(int))); + connect(progress, SIGNAL(cancelClicked()), + &handler, SLOT(slotCancel())); + } + + QXmlInputSource source; + source.setData(fileContents); + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + + START_TIMING; + bool ok = reader.parse(source); + PRINT_ELAPSED("RosegardenGUIDoc::xmlParse (reader.parse())"); + + if (!ok) { + + if (handler.isCancelled()) { + RG_DEBUG << "File load cancelled\n"; + KStartupLogo::hideIfStillThere(); + KMessageBox::information(0, i18n("File load cancelled")); + cancelled = true; + return true; + } else { + errMsg = handler.errorString(); + } + + } else { + + if (getSequenceManager() && + !(getSequenceManager()->getSoundDriverStatus() & AUDIO_OK)) { + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + if (handler.hasActiveAudio() || + (m_pluginManager && !handler.pluginsNotFound().empty())) { + +#ifdef HAVE_LIBJACK + KMessageBox::information + (0, i18n("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but Rosegarden is currently running without audio because the JACK audio server was not available on startup.</p><p>Please exit Rosegarden, start the JACK audio server and re-start Rosegarden if you wish to load this complete composition.</p><p><b>WARNING:</b> If you re-save this composition, all audio and plugin data and settings in it will be lost.</p>")); +#else + KMessageBox::information + (0, i18n("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but you are running a version of Rosegarden that was compiled without audio support.</p><p><b>WARNING:</b> If you re-save this composition from this version of Rosegarden, all audio and plugin data and settings in it will be lost.</p>")); +#endif + } + CurrentProgressDialog::thaw(); + + } else { + + bool shownWarning = false; + + int sr = 0; + if (getSequenceManager()) { + sr = getSequenceManager()->getSampleRate(); + } + + int er = m_audioFileManager.getExpectedSampleRate(); + + std::set<int> rates = m_audioFileManager.getActualSampleRates(); + bool other = false; + bool mixed = (rates.size() > 1); + for (std::set<int>::iterator i = rates.begin(); + i != rates.end(); ++i) { + if (*i != sr) { + other = true; + break; + } + } + + if (sr != 0 && + handler.hasActiveAudio() && + ((er != 0 && er != sr) || + (other && !mixed))) { + + if (er == 0) er = *rates.begin(); + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + KMessageBox::information(0, i18n("<h3>Incorrect audio sample rate</h3><p>This composition contains audio files that were recorded or imported with the audio server running at a different sample rate (%1 Hz) from the current JACK server sample rate (%2 Hz).</p><p>Rosegarden will play this composition at the correct speed, but any audio files in it will probably sound awful.</p><p>Please consider re-starting the JACK server at the correct rate (%3 Hz) and re-loading this composition before you do any more work with it.</p>").arg(er).arg(sr).arg(er)); + + CurrentProgressDialog::thaw(); + shownWarning = true; + + } else if (sr != 0 && mixed) { + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + KMessageBox::information(0, i18n("<h3>Inconsistent audio sample rates</h3><p>This composition contains audio files at more than one sample rate.</p><p>Rosegarden will play them at the correct speed, but any audio files that were recorded or imported at rates different from the current JACK server sample rate (%1 Hz) will probably sound awful.</p><p>Please see the audio file manager dialog for more details, and consider resampling any files that are at the wrong rate.</p>").arg(sr), + i18n("Inconsistent sample rates"), + "file-load-inconsistent-samplerates"); + + CurrentProgressDialog::thaw(); + shownWarning = true; + } + + if (m_pluginManager && !handler.pluginsNotFound().empty()) { + + // We only warn if a plugin manager is present, so as + // to avoid warnings when importing a studio from + // another file (which is the normal case in which we + // have no plugin manager). + + QString msg(i18n("<h3>Plugins not found</h3><p>The following audio plugins could not be loaded:</p><ul>")); + + for (std::set<QString>::iterator i = handler.pluginsNotFound().begin(); + i != handler.pluginsNotFound().end(); ++i) { + QString ident = *i; + QString type, soName, label; + PluginIdentifier::parseIdentifier(ident, type, soName, label); + QString pluginFileName = QFileInfo(soName).fileName(); + msg += i18n("<li>%1 (from %2)</li>").arg(label).arg(pluginFileName); + } + msg += "</ul>"; + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::information(0, msg); + CurrentProgressDialog::thaw(); + shownWarning = true; + + } + + if (handler.isDeprecated() && !shownWarning) { + + QString msg(i18n("This file contains one or more old element types that are now deprecated.\nSupport for these elements may disappear in future versions of Rosegarden.\nWe recommend you re-save this file from this version of Rosegarden to ensure that it can still be re-loaded in future versions.")); + slotDocumentModified(); // so file can be re-saved immediately + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::information(0, msg); + CurrentProgressDialog::thaw(); + } + } + } + + return ok; +} + +void +RosegardenGUIDoc::insertRecordedMidi(const MappedComposition &mC) +{ + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi: " << mC.size() << " events" << endl; + + // Just create a new record Segment if we don't have one already. + // Make sure we don't recreate the record segment if it's already + // freed. + // + + //Track *midiRecordTrack = 0; + + const Composition::recordtrackcontainer &tr = + getComposition().getRecordTracks(); + + bool haveMIDIRecordTrack = false; + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + TrackId tid = (*i); + Track *track = getComposition().getTrackById(tid); + if (track) { + Instrument *instrument = + m_studio.getInstrumentById(track->getInstrument()); + if (instrument->getType() == Instrument::Midi || + instrument->getType() == Instrument::SoftSynth) { + haveMIDIRecordTrack = true; + if (!m_recordMIDISegments[track->getInstrument()]) { + addRecordMIDISegment(track->getId()); + } + break; + } + } + } + + if (!haveMIDIRecordTrack) + return ; + + if (mC.size() > 0) { + MappedComposition::const_iterator i; + Event *rEvent = 0; + timeT duration, absTime; + timeT updateFrom = m_composition.getDuration(); + bool haveNotes = false; + + // process all the incoming MappedEvents + // + for (i = mC.begin(); i != mC.end(); ++i) { + if ((*i)->getRecordedDevice() == Device::CONTROL_DEVICE) { + // send to GUI + RosegardenGUIView *v; + for (v = m_viewList.first(); v != 0; v = m_viewList.next()) { + v->slotControllerDeviceEventReceived(*i); + } + continue; + } + + absTime = m_composition.getElapsedTimeForRealTime((*i)->getEventTime()); + + /* This is incorrect, unless the tempo at absTime happens to + be the same as the tempo at zero and there are no tempo + changes within the given duration after either zero or + absTime + + duration = m_composition.getElapsedTimeForRealTime((*i)->getDuration()); + */ + duration = m_composition. + getElapsedTimeForRealTime((*i)->getEventTime() + + (*i)->getDuration()) - absTime; + + rEvent = 0; + bool isNoteOn = false; + int pitch = 0; + int channel = (*i)->getRecordedChannel(); + int device = (*i)->getRecordedDevice(); + + TrackId tid = (*i)->getTrackId(); + Track *track = getComposition().getTrackById(tid); + + switch ((*i)->getType()) { + case MappedEvent::MidiNote: + + // adjust the notation by the opposite of track transpose so the + // resulting recording will play correctly, and notation will + // read correctly; tentative fix for #1597279 + pitch = (*i)->getPitch() - track->getTranspose(); + + if ((*i)->getDuration() < RealTime::zeroTime) { + + // it's a note-on; give it a default duration + // for insertion into the segment, and make a + // mental note to stick it in the note-on map + // for when we see the corresponding note-off + + duration = + Note(Note::Crotchet).getDuration(); + isNoteOn = true; + + rEvent = new Event(Note::EventType, + absTime, + duration); + + rEvent->set + <Int>(PITCH, pitch); + rEvent->set + <Int>(VELOCITY, (*i)->getVelocity()); + + } else { + + // it's a note-off + + //NoteOnMap::iterator mi = m_noteOnEvents.find((*i)->getPitch()); + PitchMap *pm = &m_noteOnEvents[device][channel]; + PitchMap::iterator mi = pm->find(pitch); + + if (mi != pm->end()) { + // modify the previously held note-on event, + // instead of assigning to rEvent + NoteOnRecSet rec_vec = mi->second; + Event *oldEv = *rec_vec[0].m_segmentIterator; + Event *newEv = new Event + (*oldEv, oldEv->getAbsoluteTime(), duration); + + newEv->set + <Int>(RECORDED_CHANNEL, channel); + NoteOnRecSet *replaced = + replaceRecordedEvent(rec_vec, newEv); + delete replaced; + pm->erase(mi); + if (updateFrom > newEv->getAbsoluteTime()) { + updateFrom = newEv->getAbsoluteTime(); + } + haveNotes = true; + delete newEv; + // at this point we could quantize the bar if we were + // tracking in a notation view + } else { + std::cerr << " WARNING: NOTE OFF received without corresponding NOTE ON" << std::endl; + } + } + + break; + + case MappedEvent::MidiPitchBend: + rEvent = PitchBend + ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime); + rEvent->set + <Int>(RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiController: + rEvent = Controller + ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime); + rEvent->set + <Int>(RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiProgramChange: + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi()" + << " - got Program Change (unsupported)" + << endl; + break; + + case MappedEvent::MidiKeyPressure: + rEvent = KeyPressure + ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime); + rEvent->set + <Int>(RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiChannelPressure: + rEvent = ChannelPressure + ((*i)->getData1()).getAsEvent(absTime); + rEvent->set + <Int>(RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiSystemMessage: + channel = -1; + if ((*i)->getData1() == MIDI_SYSTEM_EXCLUSIVE) { + rEvent = SystemExclusive + (DataBlockRepository::getDataBlockForEvent((*i))).getAsEvent(absTime); + } + + // Ignore other SystemMessage events for the moment + // + + break; + + case MappedEvent::MidiNoteOneShot: + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi() - " + << "GOT UNEXPECTED MappedEvent::MidiNoteOneShot" + << endl; + break; + + // Audio control signals - ignore these + case MappedEvent::Audio: + case MappedEvent::AudioCancel: + case MappedEvent::AudioLevel: + case MappedEvent::AudioStopped: + case MappedEvent::AudioGeneratePreview: + case MappedEvent::SystemUpdateInstruments: + break; + + default: + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi() - " + << "GOT UNSUPPORTED MAPPED EVENT" + << endl; + break; + } + + // sanity check + // + if (rEvent == 0) + continue; + + // Set the recorded input port + // + rEvent->set + <Int>(RECORDED_PORT, device); + + // Set the proper start index (if we haven't before) + // + for ( RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin(); + it != m_recordMIDISegments.end(); ++it) { + Segment *recordMIDISegment = it->second; + if (recordMIDISegment->size() == 0) { + recordMIDISegment->setStartTime (m_composition.getBarStartForTime(absTime)); + recordMIDISegment->fillWithRests(absTime); + } + } + + // Now insert the new event + // + insertRecordedEvent(rEvent, device, channel, isNoteOn); + delete rEvent; + } + + if (haveNotes) { + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + int tracking = config->readUnsignedNumEntry("recordtracking", 0); + if (tracking == 1) { // notation + for ( RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin(); + it != m_recordMIDISegments.end(); ++it) { + Segment *recordMIDISegment = it->second; + + EventQuantizeCommand *command = new EventQuantizeCommand + (*recordMIDISegment, + updateFrom, + recordMIDISegment->getEndTime(), + "Notation Options", + true); + // don't add to history + command->execute(); + } + } + + // this signal is currently unused - leaving just in case + // recording segments are updated through the SegmentObserver::eventAdded() interface + // emit recordMIDISegmentUpdated(m_recordMIDISegment, updateFrom); + } + } +} + +void +RosegardenGUIDoc::updateRecordingMIDISegment() +{ + //RG_DEBUG << "RosegardenGUIDoc::updateRecordingMIDISegment" << endl; + + if (m_recordMIDISegments.size() == 0) { + // make this call once to create one + insertRecordedMidi(MappedComposition()); + if (m_recordMIDISegments.size() == 0) + return ; // not recording any MIDI + } + + //RG_DEBUG << "RosegardenGUIDoc::updateRecordingMIDISegment: have record MIDI segment" << endl; + + NoteOnMap tweakedNoteOnEvents; + for (NoteOnMap::iterator mi = m_noteOnEvents.begin(); + mi != m_noteOnEvents.end(); ++mi) + for (ChanMap::iterator cm = mi->second.begin(); + cm != mi->second.end(); ++cm) + for (PitchMap::iterator pm = cm->second.begin(); + pm != cm->second.end(); ++pm) { + + // anything in the note-on map should be tweaked so as to end + // at the recording pointer + NoteOnRecSet rec_vec = pm->second; + if (rec_vec.size() > 0) { + Event *oldEv = *rec_vec[0].m_segmentIterator; + Event *newEv = new Event( + *oldEv, oldEv->getAbsoluteTime(), + m_composition.getPosition() - oldEv->getAbsoluteTime() ); + + tweakedNoteOnEvents[mi->first][cm->first][pm->first] = + *replaceRecordedEvent(rec_vec, newEv); + delete newEv; + } + } + m_noteOnEvents = tweakedNoteOnEvents; +} + +RosegardenGUIDoc::NoteOnRecSet * + +RosegardenGUIDoc::replaceRecordedEvent(NoteOnRecSet& rec_vec, Event *fresh) +{ + NoteOnRecSet *new_vector = new NoteOnRecSet(); + for ( NoteOnRecSet::const_iterator i = rec_vec.begin(); i != rec_vec.end(); ++i) { + Segment *recordMIDISegment = i->m_segment; + recordMIDISegment->erase(i->m_segmentIterator); + NoteOnRec noteRec; + noteRec.m_segment = recordMIDISegment; + noteRec.m_segmentIterator = recordMIDISegment->insert(new Event(*fresh)); + new_vector->push_back(noteRec); + } + return new_vector; +} + +void +RosegardenGUIDoc::storeNoteOnEvent(Segment *s, Segment::iterator it, int device, int channel) +{ + NoteOnRec record; + record.m_segment = s; + record.m_segmentIterator = it; + int pitch = (*it)->get + <Int>(PITCH); + m_noteOnEvents[device][channel][pitch].push_back(record); +} + +void +RosegardenGUIDoc::insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn) +{ + Segment::iterator it; + for ( RecordingSegmentMap::const_iterator i = m_recordMIDISegments.begin(); + i != m_recordMIDISegments.end(); ++i) { + Segment *recordMIDISegment = i->second; + TrackId tid = recordMIDISegment->getTrack(); + Track *track = getComposition().getTrackById(tid); + if (track) { + //Instrument *instrument = + // m_studio.getInstrumentById(track->getInstrument()); + int chan_filter = track->getMidiInputChannel(); + int dev_filter = track->getMidiInputDevice(); + if (((chan_filter < 0) || (chan_filter == channel)) && + ((dev_filter == int(Device::ALL_DEVICES)) || (dev_filter == device))) { + it = recordMIDISegment->insert(new Event(*ev)); + if (isNoteOn) { + storeNoteOnEvent(recordMIDISegment, it, device, channel); + } + RG_DEBUG << "RosegardenGUIDoc::insertRecordedEvent() - matches filter" << endl; + } else { + RG_DEBUG << "RosegardenGUIDoc::insertRecordedEvent() - unmatched event discarded" << endl; + } + } + } +} + +void +RosegardenGUIDoc::stopRecordingMidi() +{ + RG_DEBUG << "RosegardenGUIDoc::stopRecordingMidi" << endl; + + Composition &c = getComposition(); + + timeT endTime = c.getBarEnd(0); + + bool haveMeaning = false; + timeT earliestMeaning = 0; + + std::vector<RecordingSegmentMap::iterator> toErase; + + for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin(); + i != m_recordMIDISegments.end(); + ++i) { + + Segment *s = i->second; + + bool meaningless = true; + + for (Segment::iterator i = s->begin(); i != s->end(); ++i) { + + if ((*i)->isa(Clef::EventType)) continue; + + // no rests in the segment yet, so anything else is meaningful + meaningless = false; + + if (!haveMeaning || (*i)->getAbsoluteTime() < earliestMeaning) { + earliestMeaning = (*i)->getAbsoluteTime(); + } + + haveMeaning = true; + break; + } + + if (meaningless) { + if (!c.deleteSegment(s)) delete s; + toErase.push_back(i); + } else { + if (endTime < s->getEndTime()) { + endTime = s->getEndTime(); + } + } + } + + for (int i = 0; i < toErase.size(); ++i) { + m_recordMIDISegments.erase(toErase[i]); + } + + if (!haveMeaning) return; + + RG_DEBUG << "RosegardenGUIDoc::stopRecordingMidi: have something" << endl; + + // adjust the clef timings so as not to leave a clef stranded at + // the start of an otherwise empty count-in + + timeT meaningfulBarStart = c.getBarStartForTime(earliestMeaning); + + for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin(); + i != m_recordMIDISegments.end(); + ++i) { + + Segment *s = i->second; + Segment::iterator i = s->begin(); + + if (i == s->end() || !(*i)->isa(Clef::EventType)) continue; + + if ((*i)->getAbsoluteTime() < meaningfulBarStart) { + Event *e = new Event(**i, meaningfulBarStart); + s->erase(i); + s->insert(e); + } + } + + for (NoteOnMap::iterator mi = m_noteOnEvents.begin(); + mi != m_noteOnEvents.end(); ++mi) { + + for (ChanMap::iterator cm = mi->second.begin(); + cm != mi->second.end(); ++cm) { + + for (PitchMap::iterator pm = cm->second.begin(); + pm != cm->second.end(); ++pm) { + + // anything remaining in the note-on map should be + // made to end at the end of the segment + + NoteOnRecSet rec_vec = pm->second; + + if (rec_vec.size() > 0) { + Event *oldEv = *rec_vec[0].m_segmentIterator; + Event *newEv = new Event + (*oldEv, oldEv->getAbsoluteTime(), + endTime - oldEv->getAbsoluteTime()); + NoteOnRecSet *replaced = + replaceRecordedEvent(rec_vec, newEv); + delete newEv; + delete replaced; + } + } + } + } + m_noteOnEvents.clear(); + + while (!m_recordMIDISegments.empty()) { + + Segment *s = m_recordMIDISegments.begin()->second; + m_recordMIDISegments.erase(m_recordMIDISegments.begin()); + + // the record segment will have already been added to the + // composition if there was anything in it; otherwise we don't + // need to do so + + if (s->getComposition() == 0) { + delete s; + continue; + } + + // Quantize for notation only -- doesn't affect performance timings. + KMacroCommand *command = new KMacroCommand(i18n("Insert Recorded MIDI")); + + command->addCommand(new EventQuantizeCommand + (*s, + s->getStartTime(), + s->getEndTime(), + "Notation Options", + true)); + + command->addCommand(new NormalizeRestsCommand + (*s, + c.getBarStartForTime(s->getStartTime()), + c.getBarEndForTime(s->getEndTime()))); + + command->addCommand(new SegmentRecordCommand(s)); + + m_commandHistory->addCommand(command); + } + + emit stoppedMIDIRecording(); + + slotUpdateAllViews(0); +} + +void +RosegardenGUIDoc::prepareAudio() +{ + if (!isSequencerRunning()) + return ; + + QCString replyType; + QByteArray replyData; + + // Clear down the sequencer AudioFilePlayer object + // + rgapp->sequencerSend("clearAllAudioFiles()"); + + for (AudioFileManagerIterator it = m_audioFileManager.begin(); + it != m_audioFileManager.end(); it++) { + + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + // We have to pass the filename as a QString + // + streamOut << QString(strtoqstr((*it)->getFilename())); + streamOut << (int)(*it)->getId(); + + rgapp->sequencerCall("addAudioFile(QString, int)", replyType, replyData, data); + QDataStream streamIn(replyData, IO_ReadOnly); + int result; + streamIn >> result; + if (!result) { + RG_DEBUG << "prepareAudio() - failed to add file \"" + << (*it)->getFilename() << "\"" << endl; + } + } +} + +void +RosegardenGUIDoc::slotSetPointerPosition(timeT t) +{ + m_composition.setPosition(t); + emit pointerPositionChanged(t); +} + +void +RosegardenGUIDoc::setPlayPosition(timeT t) +{ + emit playPositionChanged(t); +} + +void +RosegardenGUIDoc::setLoop(timeT t0, timeT t1) +{ + m_composition.setLoopStart(t0); + m_composition.setLoopEnd(t1); + emit loopChanged(t0, t1); +} + +void +RosegardenGUIDoc::syncDevices() +{ + Profiler profiler("RosegardenGUIDoc::syncDevices", true); + + // Start up the sequencer + // + int timeout = 60; + + while (isSequencerRunning() && !rgapp->isSequencerRegistered() && timeout > 0) { + RG_DEBUG << "RosegardenGUIDoc::syncDevices - " + << "waiting for Sequencer to come up" << endl; + + ProgressDialog::processEvents(); + sleep(1); // 1s + --timeout; + } + + if (isSequencerRunning() && !rgapp->isSequencerRegistered() && timeout == 0) { + + // Give up, kill sequencer if possible, and report + KProcess *proc = new KProcess; + *proc << "/usr/bin/killall"; + *proc << "rosegardensequencer"; + *proc << "lt-rosegardensequencer"; + + proc->start(KProcess::Block, KProcess::All); + + if (proc->exitStatus()) { + RG_DEBUG << "couldn't kill any sequencer processes" << endl; + } + + delete proc; + RosegardenGUIApp *app = (RosegardenGUIApp*)parent(); + app->slotSequencerExited(0); + return ; + } + + if (!isSequencerRunning()) + return ; + + // Set the default timer first. We only do this first time and + // when changed in the configuration dialog. + static bool setTimer = false; + if (!setTimer) { + kapp->config()->setGroup(SequencerOptionsConfigGroup); + QString currentTimer = getCurrentTimer(); + currentTimer = kapp->config()->readEntry("timer", currentTimer); + setCurrentTimer(currentTimer); + setTimer = true; + } + + QByteArray replyData; + QCString replyType; + + // Get number of devices the sequencer has found + // + rgapp->sequencerCall("getDevices()", replyType, replyData, RosegardenApplication::Empty, true); + + unsigned int devices = 0; + + if (replyType == "unsigned int") { + QDataStream reply(replyData, IO_ReadOnly); + reply >> devices; + } else { + RG_DEBUG << "RosegardenGUIDoc::syncDevices - " + << "got unknown returntype from getDevices()" << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIDoc::syncDevices - devices = " + << devices << endl; + + for (unsigned int i = 0; i < devices; i++) { + + RG_DEBUG << "RosegardenGUIDoc::syncDevices - i = " + << i << endl; + + getMappedDevice(i); + } + + RG_DEBUG << "RosegardenGUIDoc::syncDevices - " + << "Sequencer alive - Instruments synced" << endl; + + + // Force update of view on current track selection + // + kapp->config()->setGroup(GeneralOptionsConfigGroup); + bool opt = kapp->config()->readBoolEntry("Show Track labels", true); + TrackLabel::InstrumentTrackLabels labels = TrackLabel::ShowInstrument; + if (opt) + labels = TrackLabel::ShowTrack; + + RosegardenGUIView *w; + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + w->slotSelectTrackSegments(m_composition.getSelectedTrack()); + w->getTrackEditor()->getTrackButtons()->changeTrackInstrumentLabels(labels); + } + + emit devicesResyncd(); +} + +void +RosegardenGUIDoc::getMappedDevice(DeviceId id) +{ + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)id; + + rgapp->sequencerCall("getMappedDevice(unsigned int)", + replyType, replyData, data); + + MappedDevice *mD = new MappedDevice(); + QDataStream reply(replyData, IO_ReadOnly); + + if (replyType == "MappedDevice") + // unfurl + reply >> mD; + else + return ; + + // See if we've got this device already + // + Device *device = m_studio.getDevice(id); + + if (mD->getId() == Device::NO_DEVICE) { + if (device) + m_studio.removeDevice(id); + delete mD; + return ; + } + + if (mD->size() == 0) { + // no instruments is OK for a record device + if (mD->getType() != Device::Midi || + mD->getDirection() != MidiDevice::Record) { + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice() - " + << "no instruments found" << endl; + if (device) + m_studio.removeDevice(id); + delete mD; + return ; + } + } + + bool hadDeviceAlready = (device != 0); + + if (!hadDeviceAlready) { + if (mD->getType() == Device::Midi) { + device = + new MidiDevice + (id, + mD->getName(), + mD->getDirection()); + + dynamic_cast<MidiDevice *>(device) + ->setRecording(mD->isRecording()); + + m_studio.addDevice(device); + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "adding MIDI Device \"" + << device->getName() << "\" id = " << id + << " direction = " << mD->getDirection() + << " recording = " << mD->isRecording() + << endl; + } else if (mD->getType() == Device::SoftSynth) { + device = new SoftSynthDevice(id, mD->getName()); + m_studio.addDevice(device); + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "adding soft synth Device \"" + << device->getName() << "\" id = " << id << endl; + } else if (mD->getType() == Device::Audio) { + device = new AudioDevice(id, mD->getName()); + m_studio.addDevice(device); + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "adding audio Device \"" + << device->getName() << "\" id = " << id << endl; + } else { + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "unknown device - \"" << mD->getName() + << "\" (type = " + << mD->getType() << ")\n"; + return ; + } + } + + if (hadDeviceAlready) { + // direction might have changed + if (mD->getType() == Device::Midi) { + MidiDevice *midid = + dynamic_cast<MidiDevice *>(device); + if (midid) { + midid->setDirection(mD->getDirection()); + midid->setRecording(mD->isRecording()); + } + } + } + + std::string connection(mD->getConnection()); + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - got \"" << connection + << "\", direction " << mD->getDirection() + << " recording " << mD->isRecording() + << endl; + device->setConnection(connection); + + Instrument *instrument; + MappedDeviceIterator it; + + InstrumentList existingInstrs(device->getAllInstruments()); + + for (it = mD->begin(); it != mD->end(); it++) { + InstrumentId instrumentId = (*it)->getId(); + + bool haveInstrument = false; + for (InstrumentList::iterator iit = existingInstrs.begin(); + iit != existingInstrs.end(); ++iit) { + + if ((*iit)->getId() == instrumentId) { + haveInstrument = true; + break; + } + } + + if (!haveInstrument) { + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice: new instr " << (*it)->getId() << endl; + instrument = new Instrument((*it)->getId(), + (*it)->getType(), + (*it)->getName(), + (*it)->getChannel(), + device); + device->addInstrument(instrument); + } + } + + delete mD; +} + +void +RosegardenGUIDoc::addRecordMIDISegment(TrackId tid) +{ + RG_DEBUG << "RosegardenGUIDoc::addRecordMIDISegment(" << tid << ")" << endl; +// std::cerr << kdBacktrace() << std::endl; + + Segment *recordMIDISegment; + + recordMIDISegment = new Segment(); + recordMIDISegment->setTrack(tid); + recordMIDISegment->setStartTime(m_recordStartTime); + + // Set an appropriate segment label + // + std::string label = ""; + + Track *track = m_composition.getTrackById(tid); + if (track) { + if (track->getPresetLabel() != "") { + label = track->getPresetLabel(); + } else if (track->getLabel() == "") { + Instrument *instr = + m_studio.getInstrumentById(track->getInstrument()); + if (instr) { + label = m_studio.getSegmentName(instr->getId()); + } + } else { + label = track->getLabel(); + } + label = qstrtostr(i18n("%1 (recorded)").arg(strtoqstr(label))); + } + + recordMIDISegment->setLabel(label); + + Clef clef = clefIndexToClef(track->getClef()); + recordMIDISegment->insert(clef.getAsEvent + (recordMIDISegment->getStartTime())); + + // set segment transpose, color, highest/lowest playable from track parameters + recordMIDISegment->setTranspose(track->getTranspose()); + recordMIDISegment->setColourIndex(track->getColor()); + recordMIDISegment->setHighestPlayable(track->getHighestPlayable()); + recordMIDISegment->setLowestPlayable(track->getLowestPlayable()); + + m_composition.addSegment(recordMIDISegment); + + m_recordMIDISegments[track->getInstrument()] = recordMIDISegment; + + RosegardenGUIView *w; + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + w->getTrackEditor()->getTrackButtons()->slotUpdateTracks(); + } + + emit newMIDIRecordingSegment(recordMIDISegment); +} + +void +RosegardenGUIDoc::addRecordAudioSegment(InstrumentId iid, + AudioFileId auid) +{ + Segment *recordSegment = new Segment + (Segment::Audio); + + // Find the right track + + Track *recordTrack = 0; + + const Composition::recordtrackcontainer &tr = + getComposition().getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + TrackId tid = (*i); + Track *track = getComposition().getTrackById(tid); + if (track) { + if (iid == track->getInstrument()) { + recordTrack = track; + break; + } + } + } + + if (!recordTrack) { + RG_DEBUG << "RosegardenGUIDoc::addRecordAudioSegment(" << iid << ", " + << auid << "): No record-armed track found for instrument!" + << endl; + return ; + } + + recordSegment->setTrack(recordTrack->getId()); + recordSegment->setStartTime(m_recordStartTime); + recordSegment->setAudioStartTime(RealTime::zeroTime); + + // Set an appropriate segment label + // + std::string label = ""; + + if (recordTrack) { + if (recordTrack->getLabel() == "") { + + Instrument *instr = + m_studio.getInstrumentById(recordTrack->getInstrument()); + + if (instr) { + label = instr->getName() + std::string(" "); + } + + } else { + label = recordTrack->getLabel() + std::string(" "); + } + + label += std::string("(recorded audio)"); + } + + recordSegment->setLabel(label); + recordSegment->setAudioFileId(auid); + + // set color for audio segment to distinguish it from a MIDI segment on an + // audio track drawn with the pencil (depends on having the current + // autoload.rg or a file derived from it to deliever predictable results, + // but the worst case here is segments drawn in the wrong color when + // adding new segments to old files, which I don't forsee as being enough + // of a problem to be worth cooking up a more robust implementation of + // this new color for new audio segments (DMM) + recordSegment->setColourIndex(GUIPalette::AudioDefaultIndex); + + RG_DEBUG << "RosegardenGUIDoc::addRecordAudioSegment: adding record segment for instrument " << iid << " on track " << recordTrack->getId() << endl; + m_recordAudioSegments[iid] = recordSegment; + + RosegardenGUIView *w; + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + w->getTrackEditor()->getTrackButtons()->slotUpdateTracks(); + } + + emit newAudioRecordingSegment(recordSegment); +} + +void +RosegardenGUIDoc::updateRecordingAudioSegments() +{ + const Composition::recordtrackcontainer &tr = + getComposition().getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + + TrackId tid = (*i); + Track *track = getComposition().getTrackById(tid); + + if (track) { + + InstrumentId iid = track->getInstrument(); + + if (m_recordAudioSegments[iid]) { + + Segment *recordSegment = m_recordAudioSegments[iid]; + if (!recordSegment->getComposition()) { + + // always insert straight away for audio + m_composition.addSegment(recordSegment); + } + + recordSegment->setAudioEndTime( + m_composition.getRealTimeDifference(recordSegment->getStartTime(), + m_composition.getPosition())); + + } else { + // RG_DEBUG << "RosegardenGUIDoc::updateRecordingAudioSegments: no segment for instr " + // << iid << endl; + } + } + } +} + +void +RosegardenGUIDoc::stopRecordingAudio() +{ + RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio" << endl; + + for (RecordingSegmentMap::iterator ri = m_recordAudioSegments.begin(); + ri != m_recordAudioSegments.end(); ++ri) { + + Segment *recordSegment = ri->second; + + if (!recordSegment) + continue; + + // set the audio end time + // + recordSegment->setAudioEndTime( + m_composition.getRealTimeDifference(recordSegment->getStartTime(), + m_composition.getPosition())); + + // now add the Segment + RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio - " + << "got recorded segment" << endl; + + // now move the segment back by the record latency + // + /*!!! + No. I don't like this. + + The record latency doesn't always exist -- for example, if recording + from a synth plugin there is no record latency, and we have no way + here to distinguish. + + The record latency is a total latency figure that actually includes + some play latency, and we compensate for that again on playback (see + bug #1378766). + + The timeT conversion of record latency is approximate in frames, + giving potential phase error. + + Cutting this out won't break any existing files, as the latency + compensation there is already encoded into the file. + + RealTime adjustedStartTime = + m_composition.getElapsedRealTime(recordSegment->getStartTime()) - + m_audioRecordLatency; + + timeT shiftedStartTime = + m_composition.getElapsedTimeForRealTime(adjustedStartTime); + + RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio - " + << "shifted recorded audio segment by " + << recordSegment->getStartTime() - shiftedStartTime + << " clicks (from " << recordSegment->getStartTime() + << " to " << shiftedStartTime << ")" << endl; + + recordSegment->setStartTime(shiftedStartTime); + */ + } + emit stoppedAudioRecording(); +} + +void +RosegardenGUIDoc::finalizeAudioFile(InstrumentId iid) +{ + RG_DEBUG << "RosegardenGUIDoc::finalizeAudioFile(" << iid << ")" << endl; + + Segment *recordSegment = 0; + recordSegment = m_recordAudioSegments[iid]; + + if (!recordSegment) { + RG_DEBUG << "RosegardenGUIDoc::finalizeAudioFile: Failed to find segment" << endl; + return ; + } + + AudioFile *newAudioFile = m_audioFileManager.getAudioFile + (recordSegment->getAudioFileId()); + if (!newAudioFile) { + std::cerr << "WARNING: RosegardenGUIDoc::finalizeAudioFile: No audio file found for instrument " << iid << " (audio file id " << recordSegment->getAudioFileId() << ")" << std::endl; + return ; + } + + // Create a progress dialog + // + ProgressDialog *progressDlg = new ProgressDialog + (i18n("Generating audio preview..."), 100, (QWidget*)parent()); + progressDlg->setAutoClose(false); + progressDlg->setAutoReset(false); + progressDlg->show(); + + connect(progressDlg, SIGNAL(cancelClicked()), + &m_audioFileManager, SLOT(slotStopPreview())); + + connect(&m_audioFileManager, SIGNAL(setProgress(int)), + progressDlg->progressBar(), SLOT(setValue(int))); + + try { + m_audioFileManager.generatePreview(newAudioFile->getId()); + //!!! mtr just for now?: or better to do this once after the fact? + //!!! m_audioFileManager.generatePreviews(); + } catch (Exception e) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::error(0, strtoqstr(e.getMessage())); + CurrentProgressDialog::thaw(); + } + + delete progressDlg; + + if (!recordSegment->getComposition()) { + getComposition().addSegment(recordSegment); + } + + m_commandHistory->addCommand + (new SegmentRecordCommand(recordSegment)); + + // update views + slotUpdateAllViews(0); + + // Now install the file in the sequencer + // + // We're playing fast and loose with DCOP here - we just send + // this request and carry on regardless otherwise the sequencer + // can just hang our request. We don't risk a call() and we + // don't get a return type. Ugly and hacky but it appears to + // work for me - so hey. + // + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + streamOut << QString(strtoqstr(newAudioFile->getFilename())); + streamOut << (int)newAudioFile->getId(); + rgapp->sequencerSend("addAudioFile(QString, int)", data); + + // clear down + m_recordAudioSegments.erase(iid); + emit audioFileFinalized(recordSegment); +} + +RealTime +RosegardenGUIDoc::getAudioPlayLatency() +{ + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getAudioPlayLatency()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getAudioPlayLatency - " + << "Playback failed to contact Rosegarden sequencer" + << endl; + return RealTime::zeroTime; + } + + // ensure the return type is ok + QDataStream streamIn(replyData, IO_ReadOnly); + MappedRealTime result; + streamIn >> result; + + return (result.getRealTime()); +} + +RealTime +RosegardenGUIDoc::getAudioRecordLatency() +{ + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getAudioRecordLatency()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getAudioRecordLatency - " + << "Playback failed to contact Rosegarden sequencer" + << endl; + return RealTime::zeroTime; + } + + // ensure the return type is ok + QDataStream streamIn(replyData, IO_ReadOnly); + MappedRealTime result; + streamIn >> result; + + return (result.getRealTime()); +} + +void +RosegardenGUIDoc::updateAudioRecordLatency() +{ + m_audioRecordLatency = getAudioRecordLatency(); +} + +QStringList +RosegardenGUIDoc::getTimers() +{ + QStringList list; + + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getTimers()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "failed to contact Rosegarden sequencer" << endl; + return list; + } + + if (replyType != "unsigned int") { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "wrong reply type (" << replyType << ") from sequencer" << endl; + return list; + } + + QDataStream streamIn(replyData, IO_ReadOnly); + unsigned int count = 0; + streamIn >> count; + + for (unsigned int i = 0; i < count; ++i) { + + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + streamOut << i; + + if (!rgapp->sequencerCall("getTimer(unsigned int)", + replyType, replyData, data)) { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "failed to contact Rosegarden sequencer" << endl; + return list; + } + + if (replyType != "QString") { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "wrong reply type (" << replyType << ") from sequencer" << endl; + return list; + } + + QDataStream streamIn(replyData, IO_ReadOnly); + QString name; + streamIn >> name; + + list.push_back(name); + } + + return list; +} + +QString +RosegardenGUIDoc::getCurrentTimer() +{ + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getCurrentTimer()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getCurrentTimer - " + << "failed to contact Rosegarden sequencer" << endl; + return ""; + } + + if (replyType != "QString") { + RG_DEBUG << "RosegardenGUIDoc::getCurrentTimer - " + << "wrong reply type (" << replyType << ") from sequencer" << endl; + return ""; + } + + QDataStream streamIn(replyData, IO_ReadOnly); + QString name; + streamIn >> name; + return name; +} + +void +RosegardenGUIDoc::setCurrentTimer(QString name) +{ + QCString replyType; + QByteArray replyData; + + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + streamOut << name; + + if (!rgapp->sequencerCall("setCurrentTimer(QString)", + replyType, replyData, data)) { + RG_DEBUG << "RosegardenGUIDoc::setCurrentTimer - " + << "failed to contact Rosegarden sequencer" << endl; + } +} + +void +RosegardenGUIDoc::initialiseControllers() +{ + InstrumentList list = m_studio.getAllInstruments(); + MappedComposition mC; + MappedEvent *mE; + + InstrumentList::iterator it = list.begin(); + for (; it != list.end(); it++) { + if ((*it)->getType() == Instrument::Midi) { + std::vector<MidiControlPair> advancedControls; + + // push all the advanced static controls + // + StaticControllers &list = (*it)->getStaticControllers(); + for (StaticControllerConstIterator cIt = list.begin(); cIt != list.end(); ++cIt) { + advancedControls.push_back(MidiControlPair(cIt->first, cIt->second)); + } + + advancedControls. + push_back( + MidiControlPair(MIDI_CONTROLLER_PAN, + (*it)->getPan())); + advancedControls. + push_back( + MidiControlPair(MIDI_CONTROLLER_VOLUME, + (*it)->getVolume())); + + + std::vector<MidiControlPair>::iterator + iit = advancedControls.begin(); + for (; iit != advancedControls.end(); iit++) { + try { + mE = + new MappedEvent((*it)->getId(), + MappedEvent::MidiController, + iit->first, + iit->second); + } catch (...) { + continue; + } + + mC.insert(mE); + } + } + } + + StudioControl::sendMappedComposition(mC); +} + +void +RosegardenGUIDoc::clearAllPlugins() +{ + //RG_DEBUG << "clearAllPlugins" << endl; + + InstrumentList list = m_studio.getAllInstruments(); + MappedComposition mC; + + InstrumentList::iterator it = list.begin(); + for (; it != list.end(); it++) { + if ((*it)->getType() == Instrument::Audio) { + PluginInstanceIterator pIt = (*it)->beginPlugins(); + + for (; pIt != (*it)->endPlugins(); pIt++) { + if ((*pIt)->getMappedId() != -1) { + if (StudioControl:: + destroyStudioObject((*pIt)->getMappedId()) == false) { + RG_DEBUG << "RosegardenGUIDoc::clearAllPlugins - " + << "couldn't find plugin instance " + << (*pIt)->getMappedId() << endl; + } + } + (*pIt)->clearPorts(); + } + (*it)->emptyPlugins(); + + /* + RG_DEBUG << "RosegardenGUIDoc::clearAllPlugins - " + << "cleared " << (*it)->getName() << endl; + */ + } + } +} + +Clipboard* +RosegardenGUIDoc::getClipboard() +{ + RosegardenGUIApp *app = (RosegardenGUIApp*)parent(); + return app->getClipboard(); +} + +void RosegardenGUIDoc::slotDocColoursChanged() +{ + RG_DEBUG << "RosegardenGUIDoc::slotDocColoursChanged(): emitting docColoursChanged()" << endl; + + emit docColoursChanged(); +} + +void +RosegardenGUIDoc::addOrphanedRecordedAudioFile(QString fileName) +{ + m_orphanedRecordedAudioFiles.push_back(fileName); + slotDocumentModified(); +} + +void +RosegardenGUIDoc::addOrphanedDerivedAudioFile(QString fileName) +{ + m_orphanedDerivedAudioFiles.push_back(fileName); + slotDocumentModified(); +} + +void +RosegardenGUIDoc::notifyAudioFileRemoval(AudioFileId id) +{ + AudioFile *file = 0; + + if (m_audioFileManager.wasAudioFileRecentlyRecorded(id)) { + file = m_audioFileManager.getAudioFile(id); + if (file) addOrphanedRecordedAudioFile(file->getFilename()); + return; + } + + if (m_audioFileManager.wasAudioFileRecentlyDerived(id)) { + file = m_audioFileManager.getAudioFile(id); + if (file) addOrphanedDerivedAudioFile(file->getFilename()); + return; + } +} + +} +#include "RosegardenGUIDoc.moc" diff --git a/src/document/RosegardenGUIDoc.h b/src/document/RosegardenGUIDoc.h new file mode 100644 index 0000000..1cdc88e --- /dev/null +++ b/src/document/RosegardenGUIDoc.h @@ -0,0 +1,733 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_ROSEGARDENGUIDOC_H_ +#define _RG_ROSEGARDENGUIDOC_H_ + +#include "base/Composition.h" +#include "base/Configuration.h" +#include "base/Device.h" +#include "base/MidiProgram.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/editors/segment/segmentcanvas/AudioPreviewThread.h" +#include <map> +#include "sound/AudioFileManager.h" +// #include <qlist.h> (fixes problem for Adam Dingle) +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> +#include <vector> +#include "base/Event.h" + + +class QWidget; +class QTextStream; +class NoteOnRecSet; +class KProgress; + + +namespace Rosegarden +{ + +class SequenceManager; +class RosegardenGUIView; +class ProgressDialog; +class MultiViewCommandHistory; +class MappedComposition; +class Event; +class EditViewBase; +class Clipboard; +class AudioPluginManager; + + +static const int MERGE_AT_END = (1 << 0); +static const int MERGE_IN_NEW_TRACKS = (1 << 1); +static const int MERGE_KEEP_OLD_TIMINGS = (1 << 2); +static const int MERGE_KEEP_NEW_TIMINGS = (1 << 3); + + +/** + * RosegardenGUIDoc provides a document object for a document-view model. + * + * The RosegardenGUIDoc class provides a document object that can be + * used in conjunction with the classes RosegardenGUIApp and + * RosegardenGUIView to create a document-view model for standard KDE + * applications based on KApplication and KTMainWindow. Thereby, the + * document object is created by the RosegardenGUIApp instance and + * contains the document structure with the according methods for + * manipulation of the document data by RosegardenGUIView + * objects. Also, RosegardenGUIDoc contains the methods for + * serialization of the document data from and to files. + * + * RosegardenGUIDoc owns the Composition in the document. + */ + +class RosegardenGUIDoc : public QObject +{ + Q_OBJECT +public: + + /** + * Constructor for the fileclass of the application + */ + RosegardenGUIDoc(QWidget *parent, + AudioPluginManager *audioPluginManager = 0, + bool skipAutoload = false, + const char *name=0); + +private: + RosegardenGUIDoc(RosegardenGUIDoc *doc); + RosegardenGUIDoc& operator=(const RosegardenGUIDoc &doc); + +public: + static int FILE_FORMAT_VERSION_MAJOR; + static int FILE_FORMAT_VERSION_MINOR; + static int FILE_FORMAT_VERSION_POINT; + + /** + * Destructor for the fileclass of the application + */ + ~RosegardenGUIDoc(); + + /** + * adds a view to the document which represents the document + * contents. Usually this is your main view. + */ + void attachView(RosegardenGUIView *view); + + /** + * removes a view from the list of currently connected views + */ + void detachView(RosegardenGUIView *view); + + /** + * adds an Edit View (notation, matrix, event list) + */ + void attachEditView(EditViewBase*); + + /** + * removes a view from the list of currently connected edit views + */ + void detachEditView(EditViewBase*); + + /** + * delete all Edit Views + */ + void deleteEditViews(); + +protected: + /** + * sets the modified flag for the document after a modifying + * action on the view connected to the document. + * + * this is just an accessor, other components should call + * slotDocumentModified() and clearModifiedStatus() instead of + * this method, which perform all the related housework. + * + */ + void setModified(bool m=true); + +public: + /** + * returns if the document is modified or not. Use this to + * determine if your document needs saving by the user on closing. + */ + bool isModified() const { return m_modified; }; + + /** + * clears the 'modified' status of the document (sets it back to false). + * + */ + void clearModifiedStatus(); + + /** + * "save modified" - asks the user for saving if the document is + * modified + */ + bool saveIfModified(); + + /** + * get the autosave interval in seconds + */ + unsigned int getAutoSavePeriod() const; + + /** + * Load the document by filename and format and emit the + * updateViews() signal. The "permanent" argument should be true + * if this document is intended to be loaded to the GUI for real + * editing work: in this case, any necessary device-synchronisation + * with the sequencer will be carried out. If permanent is false, + * the sequencer's device list will be left alone. + */ + bool openDocument(const QString &filename, bool permanent = true, + const char *format=0); + + /** + * merge another document into this one + */ + void mergeDocument(RosegardenGUIDoc *doc, int options); + + /** + * saves the document under filename and format. + * + * errMsg will be set to a user-readable error message if save fails + */ + bool saveDocument(const QString &filename, QString& errMsg, + bool autosave = false); + + /** + * exports all or part of the studio to a file. If devices is + * empty, exports all devices. + */ + bool exportStudio(const QString &filename, + std::vector<DeviceId> devices = + std::vector<DeviceId>()); + + /** + * sets the path to the file connected with the document + */ + void setAbsFilePath(const QString &filename); + + /** + * returns the pathname of the current document file + */ + const QString &getAbsFilePath() const; + + /** + * sets the filename of the document + */ + void setTitle(const QString &_t); + + /** + * returns the title of the document + */ + const QString &getTitle() const; + + /** + * Returns true if the file is a regular Rosegarden ".rg" file, + * false if it's an imported file or a new file (not yet saved) + */ + bool isRegularDotRGFile(); + + void setQuickMarker(); + void jumpToQuickMarker(); + timeT getQuickMarkerTime() { return m_quickMarkerTime; } + + /** + * returns the global command history + */ + MultiViewCommandHistory *getCommandHistory() { + return m_commandHistory; + } + + /** + * returns the composition (the principal constituent of the document) + */ + Composition& getComposition() { return m_composition; } + + /** + * returns the composition (the principal constituent of the document) + */ + const Composition& getComposition() const { return m_composition; } + + /* + * return the Studio + */ + Studio& getStudio() { return m_studio;} + + const Studio& getStudio() const { return m_studio;} + + /* + * return the AudioPreviewThread + */ + AudioPreviewThread& getAudioPreviewThread() + { return m_audioPreviewThread; } + + const AudioPreviewThread& getAudioPreviewThread() const + { return m_audioPreviewThread; } + + /* + * return the AudioFileManager + */ + AudioFileManager& getAudioFileManager() + { return m_audioFileManager; } + + const AudioFileManager& getAudioFileManager() const + { return m_audioFileManager; } + + /* + * return the Configuration object + */ + DocumentConfiguration& getConfiguration() { return m_config; } + + const DocumentConfiguration& getConfiguration() const + { return m_config; } + + /** + * returns the cut/copy/paste clipboard + */ + Clipboard *getClipboard(); + + /** + * Returns whether the sequencer us running + */ + bool isSequencerRunning(); + + /** + * insert some recorded MIDI events into our recording Segment + */ + void insertRecordedMidi(const MappedComposition &mc); + + /** + * Update the recording progress -- called regularly from + * RosegardenGUIApp::slotUpdatePlaybackPosition() while recording + */ + void updateRecordingMIDISegment(); + + /** + * Update the recording progress for audio + */ + void updateRecordingAudioSegments(); + + /** + * Tidy up the recording SegmentItems and other post record jobs + */ + void stopRecordingMidi(); + void stopRecordingAudio(); + + /** + * Register audio samples at the sequencer + */ + void prepareAudio(); + + /** + * Cause the playPositionChanged signal to be emitted and any + * associated internal work in the document to happen + */ + void setPlayPosition(timeT); + + /** + * Cause the loopChanged signal to be emitted and any + * associated internal work in the document to happen + */ + void setLoop(timeT, timeT); + + /** + * Cause the document to use the given time as the origin + * when inserting any subsequent recorded data + */ + void setRecordStartTime(timeT t) { m_recordStartTime = t; } + + /* + * Sync device information with sequencer + */ + void syncDevices(); + + /* + * Get a MappedDevice from the sequencer and add the + * results to our Studio + */ + void getMappedDevice(DeviceId id); + + void addRecordMIDISegment(TrackId); + void addRecordAudioSegment(InstrumentId, AudioFileId); + + // Audio play and record latencies direct from the sequencer + // + RealTime getAudioPlayLatency(); + RealTime getAudioRecordLatency(); + void updateAudioRecordLatency(); + + // Complete the add of an audio file when a new file has finished + // being recorded at the sequencer. This method will ensure that + // the audio file is added to the AudioFileManager, that + // a preview is generated and that the sequencer also knows to add + // the new file to its own hash table. Flow of control is a bit + // awkward around new audio files as timing is crucial - the gui can't + // access the file until lead-out information has been written by the + // sequencer. + // + // Note that the sequencer doesn't know the audio file id (yet), + // only the instrument it was recorded to. (It does know the + // filename, but the instrument id is enough for us.) + // + void finalizeAudioFile(InstrumentId instrument); + + // Tell the document that an audio file has been orphaned. An + // orphaned audio file is a file that was created by recording in + // Rosegarden during the current session, but that has been + // unloaded from the audio file manager. It's therefore likely + // that no other application will be using it, and that that user + // doesn't want to keep it. We can offer to delete these files + // permanently when the document is saved. + // + void addOrphanedRecordedAudioFile(QString fileName); + void addOrphanedDerivedAudioFile(QString fileName); + + // Consider whether to orphan the given audio file which is about + // to be removed from the audio file manager. + // + void notifyAudioFileRemoval(AudioFileId id); + + /* + void setAudioRecordLatency(const RealTime &latency) + { m_audioRecordLatency = latency; } + void setAudioPlayLatency(const RealTime &latency) + { m_audioPlayLatency = latency; } + */ + + // Return the AudioPluginManager + // + AudioPluginManager* getPluginManager() + { return m_pluginManager; } + + // Clear all plugins from sequencer and from gui + // + void clearAllPlugins(); + + // Initialise the MIDI controllers after we've loaded a file + // + void initialiseControllers(); + + // Clear the studio at the sequencer + // + void clearStudio(); + + // Initialise the Studio with a new document's settings + // + void initialiseStudio(); + + // Get the sequence manager from the app + // + SequenceManager* getSequenceManager() + { return (dynamic_cast<RosegardenGUIApp*>(parent())) + ->getSequenceManager(); } + + //Obsolete: multitrack MIDI recording. plcl 06/2006. + //Segment *getRecordMIDISegment() { return m_recordMIDISegment; } + + QStringList getTimers(); + QString getCurrentTimer(); + void setCurrentTimer(QString); + + /** + * return the list of the views currently connected to the document + */ + QList<RosegardenGUIView>& getViewList() { return m_viewList; } + + bool isBeingDestroyed() { return m_beingDestroyed; } + + static const unsigned int MinNbOfTracks; // 64 + +public slots: + /** + * calls repaint() on all views connected to the document object + * and is called by the view by which the document has been + * changed. As this view normally repaints itself, it is excluded + * from the paintEvent. + */ + void slotUpdateAllViews(RosegardenGUIView *sender); + + /** + * set the 'modified' flag of the document to true, + * clears the 'autosaved' flag, emits the 'documentModified' signal. + * + * always call this when changes have occurred on the document. + */ + void slotDocumentModified(); + void slotDocumentRestored(); + + /** + * saves the document to a suitably-named backup file + */ + void slotAutoSave(); + + void slotSetPointerPosition(timeT); + void slotSetPlayPosition(timeT t) { setPlayPosition(t); } + void slotSetLoop(timeT s, timeT e) {setLoop(s,e);} + + void slotDocColoursChanged(); + +signals: + /** + * Emitted when document is modified or saved + */ + void documentModified(bool); + + /** + * Emitted during playback, to suggest that views should track along, + * as well as when pointer is moved via a click on the loop ruler. + */ + void pointerPositionChanged(timeT); + + /** + * Emitted during recording, to indicate that some new notes (it's + * only emitted for notes) have appeared in the recording segment + * and anything tracking should track. updatedFrom gives the + * start of the new region, which is presumed to extend up to the + * end of the segment. + */ + void recordMIDISegmentUpdated(Segment *recordSegment, + timeT updatedFrom); + + /** + * Emitted when a new MIDI recording segment is set + */ + void newMIDIRecordingSegment(Segment*); + + /** + * Emitted when a new audio recording segment is set + */ + void newAudioRecordingSegment(Segment*); + + void makeTrackVisible(int trackPosition); + + void stoppedAudioRecording(); + void stoppedMIDIRecording(); + void audioFileFinalized(Segment*); + + void playPositionChanged(timeT); + void loopChanged(timeT, timeT); + void docColoursChanged(); + void devicesResyncd(); + +protected: + /** + * initializes the document generally + */ + void newDocument(); + + /** + * Autoload + */ + void performAutoload(); + + /** + * Parse the Rosegarden file in \a file + * + * \a errMsg will contains the error messages + * if parsing failed. + * + * @return false if parsing failed + * @see RoseXmlHandler + */ + bool xmlParse(QString fileContents, QString &errMsg, + ProgressDialog *progress, + unsigned int elementCount, + bool permanent, + bool &cancelled); + + /** + * Set the "auto saved" status of the document + * Doc. modification sets it to false, autosaving + * sets it to true + */ + void setAutoSaved(bool s) { m_autoSaved = s; } + + /** + * Returns whether the document should be auto-saved + */ + bool isAutoSaved() const { return m_autoSaved; } + + /** + * Returns the name of the autosave file + */ + QString getAutoSaveFileName(); + + /** + * Save document to the given file. This function does the actual + * save of the file to the given filename; saveDocument() wraps + * this, saving to a temporary file and then renaming to the + * required file, so as not to lose the original if a failure + * occurs during overwriting. + */ + bool saveDocumentActual(const QString &filename, QString& errMsg, + bool autosave = false); + + /** + * Save one segment to the given text stream + */ + void saveSegment(QTextStream&, Segment*, KProgress*, + long totalNbOfEvents, long &count, + QString extraAttributes = QString::null); + + bool deleteOrphanedAudioFiles(bool documentWillNotBeSaved); + + + /** + * A struct formed by a Segment pointer and an iterator to the same + * Segment, used in NoteOn calculations when recording MIDI. + */ + struct NoteOnRec { + Segment *m_segment; + Segment::iterator m_segmentIterator; + }; + + /** + * A vector of NoteOnRec elements, necessary in multitrack MIDI + * recording for NoteOn calculations + */ + typedef std::vector<NoteOnRec> NoteOnRecSet; + + /** + * Store a single NoteOnRec element in the m_noteOnEvents map + */ + void storeNoteOnEvent( Segment *s, Segment::iterator it, + int device, int channel ); + + /** + * Replace recorded Note events in one or several segments, returning the + * resulting NoteOnRecSet + */ + NoteOnRecSet* replaceRecordedEvent(NoteOnRecSet &rec_vec, Event *fresh); + + /** + * Insert a recorded event in one or several segments + */ + void insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn); + + //--------------- Data members --------------------------------- + + /** + * the list of the views currently connected to the document + */ + QList<RosegardenGUIView> m_viewList; + + /** + * the list of the edit views currently editing a part of this document + */ + QList<EditViewBase> m_editViewList; + + /** + * the modified flag of the current document + */ + bool m_modified; + + /** + * the autosaved status of the current document + */ + bool m_autoSaved; + + /** + * the title of the current document + */ + QString m_title; + + /** + * absolute file path of the current document + */ + QString m_absFilePath; + + /** + * the composition this document is wrapping + */ + Composition m_composition; + + /** + * stores AudioFile mappings + */ + AudioFileManager m_audioFileManager; + + /** + * calculates AudioFile previews + */ + AudioPreviewThread m_audioPreviewThread; + + typedef std::map<InstrumentId, Segment *> RecordingSegmentMap; + + /** + * Segments onto which we can record MIDI events + */ + //Segment *m_recordMIDISegment; + RecordingSegmentMap m_recordMIDISegments; + + /** + * Segments for recording audio (per instrument) + */ + RecordingSegmentMap m_recordAudioSegments; + + /** + * a map[Pitch] of NoteOnRecSet elements, for NoteOn calculations + */ + typedef std::map<int, NoteOnRecSet> PitchMap; + + /** + * a map[Channel] of PitchMap + */ + typedef std::map<int, PitchMap> ChanMap; + + /** + * a map[Port] of ChanMap + */ + typedef std::map<int, ChanMap> NoteOnMap; + + /** + * During recording, we collect note-ons that haven't yet had a note-off + * in here + */ + NoteOnMap m_noteOnEvents; + + + MultiViewCommandHistory *m_commandHistory; + + /** + * the Studio + */ + Studio m_studio; + + /* + * A configuration object + * + */ + DocumentConfiguration m_config; + + // AudioPluginManager - sequencer and local plugin management + // + AudioPluginManager *m_pluginManager; + + RealTime m_audioRecordLatency; + + timeT m_recordStartTime; + + timeT m_quickMarkerTime; + + std::vector<QString> m_orphanedRecordedAudioFiles; + std::vector<QString> m_orphanedDerivedAudioFiles; + + // Autosave period for this document in seconds + // + int m_autoSavePeriod; + + // Set to true when the dtor starts + bool m_beingDestroyed; +}; + + +} + +#endif diff --git a/src/document/XmlStorableEvent.cpp b/src/document/XmlStorableEvent.cpp new file mode 100644 index 0000000..7688b2a --- /dev/null +++ b/src/document/XmlStorableEvent.cpp @@ -0,0 +1,188 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "XmlStorableEvent.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "gui/editors/notation/NotationStrings.h" +#include <qstring.h> + + +namespace Rosegarden +{ + +XmlStorableEvent::XmlStorableEvent(const QXmlAttributes &attributes, + timeT &absoluteTime) +{ + setDuration(0); + + for (int i = 0; i < attributes.length(); ++i) { + + QString attrName(attributes.qName(i)), + attrVal(attributes.value(i)); + + if (attrName == "package") { + + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Warning: XML still uses deprecated \"package\" attribute" << endl; + + } else if (attrName == "type") { + + setType(qstrtostr(attrVal)); + + } else if (attrName == "subordering") { + + bool isNumeric = true; + int o = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad subordering: " << attrVal << endl; + } else { + if (o != 0) + setSubOrdering(o); + } + + } else if (attrName == "duration") { + + bool isNumeric = true; + timeT d = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + try { + Note n(NotationStrings::getNoteForName(attrVal)); + setDuration(n.getDuration()); + } catch (NotationStrings::MalformedNoteName m) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad duration: " << attrVal << " (" << m.getMessage() << ")" << endl; + } + } else { + setDuration(d); + } + + } else if (attrName == "absoluteTime") { + + bool isNumeric = true; + timeT t = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad absolute time: " << attrVal << endl; + } else { + absoluteTime = t; + } + + } else if (attrName == "timeOffset") { + + bool isNumeric = true; + timeT t = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad time offset: " << attrVal << endl; + } else { + absoluteTime += t; + } + + } else { + + // set generic property + // + QString val(attrVal); + + // Check if boolean val + QString valLowerCase(val.lower()); + bool isNumeric; + int numVal; + + if (valLowerCase == "true" || valLowerCase == "false") { + + set + <Bool>(qstrtostr(attrName), valLowerCase == "true"); + + } else { + + // Not a bool, check if integer val + numVal = val.toInt(&isNumeric); + if (isNumeric) { + set + <Int>(qstrtostr(attrName), numVal); + } else { + // not an int either, default to string + set + <String>(qstrtostr(attrName), qstrtostr(attrVal)); + } + } + } + } + + setAbsoluteTime(absoluteTime); +} + +XmlStorableEvent::XmlStorableEvent(Event &e) : + Event(e) +{} + +void +XmlStorableEvent::setPropertyFromAttributes(const QXmlAttributes &attributes, + bool persistent) +{ + bool have = false; + QString name = attributes.value("name"); + if (name == "") { + RG_DEBUG << "XmlStorableEvent::setProperty: no property name found, ignoring" << endl; + return ; + } + + for (int i = 0; i < attributes.length(); ++i) { + QString attrName(attributes.qName(i)), + attrVal(attributes.value(i)); + + if (attrName == "name") { + continue; + } else if (have) { + RG_DEBUG << "XmlStorableEvent::setProperty: multiple values found, ignoring all but the first" << endl; + continue; + } else if (attrName == "bool") { + set + <Bool>(qstrtostr(name), attrVal.lower() == "true", + persistent); + have = true; + } else if (attrName == "int") { + set + <Int>(qstrtostr(name), attrVal.toInt(), persistent); + have = true; + } else if (attrName == "string") { + set + <String>(qstrtostr(name), qstrtostr(attrVal), persistent); + have = true; + } else { + RG_DEBUG << "XmlStorableEvent::setProperty: unknown attribute name \"" << name << "\", ignoring" << endl; + } + } + + if (!have) + RG_DEBUG << "XmlStorableEvent::setProperty: Warning: no property value found for property " << name << endl; +} + +} diff --git a/src/document/XmlStorableEvent.h b/src/document/XmlStorableEvent.h new file mode 100644 index 0000000..197c9cb --- /dev/null +++ b/src/document/XmlStorableEvent.h @@ -0,0 +1,75 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_XMLSTORABLEEVENT_H_ +#define _RG_XMLSTORABLEEVENT_H_ + +#include "base/Event.h" +#include <qxml.h> + + +class QXmlAttributes; + + +namespace Rosegarden +{ + + + +/** + * An Event which can generate an XML representation of itself, + * or which can be constructed from a set of XML attributes + * + * @see RoseXmlHandler + */ +class XmlStorableEvent : public Event +{ +public: + /** + * Construct an XmlStorableEvent out of the XML attributes \a atts. + * If the attributes do not include absoluteTime, use the given + * value plus the value of any timeOffset attribute. If the + * attributes include absoluteTime or timeOffset, update the given + * absoluteTime reference accordingly. + */ + XmlStorableEvent(const QXmlAttributes& atts, + timeT &absoluteTime); + + /** + * Construct an XmlStorableEvent from the specified Event. + */ + XmlStorableEvent(Event&); + + /** + * Set a property from the XML attributes \a atts + */ + void setPropertyFromAttributes(const QXmlAttributes& atts, + bool persistent); +}; + + +} + +#endif diff --git a/src/document/XmlSubHandler.cpp b/src/document/XmlSubHandler.cpp new file mode 100644 index 0000000..eef2199 --- /dev/null +++ b/src/document/XmlSubHandler.cpp @@ -0,0 +1,37 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "XmlSubHandler.h" + +namespace Rosegarden { + +XmlSubHandler::XmlSubHandler() +{ +} + +XmlSubHandler::~XmlSubHandler() +{ +} + +} diff --git a/src/document/XmlSubHandler.h b/src/document/XmlSubHandler.h new file mode 100644 index 0000000..30ba784 --- /dev/null +++ b/src/document/XmlSubHandler.h @@ -0,0 +1,58 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_XMLSUBHANDLER_H_ +#define _RG_XMLSUBHANDLER_H_ + +#include <qstring.h> +#include <qxml.h> + +namespace Rosegarden { + +class XmlSubHandler +{ +public: + XmlSubHandler(); + virtual ~XmlSubHandler(); + + virtual bool startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts) = 0; + + /** + * @param finished : if set to true on return, means that + * the handler should be deleted + */ + virtual bool endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + bool& finished) = 0; + + virtual bool characters(const QString& ch) = 0; +}; + +} + +#endif /*_RG_XMLSUBHANDLER_H_*/ diff --git a/src/document/io/CsoundExporter.cpp b/src/document/io/CsoundExporter.cpp new file mode 100644 index 0000000..9b61372 --- /dev/null +++ b/src/document/io/CsoundExporter.cpp @@ -0,0 +1,154 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "CsoundExporter.h" + +#include "base/Event.h" +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Track.h" +#include "gui/general/ProgressReporter.h" +#include <qobject.h> +#include <fstream> +#include "gui/application/RosegardenApplication.h" + + +namespace Rosegarden +{ + +CsoundExporter::CsoundExporter(QObject *parent, + Composition *composition, + std::string fileName) : + ProgressReporter(parent, "csoundExporter"), + m_composition(composition), + m_fileName(fileName) +{ + // nothing else +} + +CsoundExporter::~CsoundExporter() +{ + // nothing +} + +static double +convertTime(Rosegarden::timeT t) +{ + return double(t) / double(Note(Note::Crotchet).getDuration()); +} + +bool +CsoundExporter::write() +{ + std::ofstream str(m_fileName.c_str(), std::ios::out); + if (!str) { + //std::cerr << "CsoundExporter::write() - can't write file" << std::endl; + return false; + } + + str << ";; Csound score file written by Rosegarden\n\n"; + if (m_composition->getCopyrightNote() != "") { + str << ";; Copyright note:\n;; " + //!!! really need to remove newlines from copyright note + << m_composition->getCopyrightNote() << "\n"; + } + + int trackNo = 0; + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + + emit setProgress(int(double(trackNo++) / double(m_composition->getNbTracks()) * 100.0)); + rgapp->refreshGUI(50); + + str << "\n;; Segment: \"" << (*i)->getLabel() << "\"\n"; + str << ";; on Track: \"" + << m_composition->getTrackById((*i)->getTrack())->getLabel() + << "\"\n"; + str << ";;\n;; Inst\tTime\tDur\tPitch\tVely\n" + << ";; ----\t----\t---\t-----\t----\n"; + + for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + + if ((*j)->isa(Note::EventType)) { + + long pitch = 0; + (*j)->get + <Int>(BaseProperties::PITCH, pitch); + + long velocity = 127; + (*j)->get + <Int>(BaseProperties::VELOCITY, velocity); + + str << " i" + << (*i)->getTrack() << "\t" + << convertTime((*j)->getAbsoluteTime()) << "\t" + << convertTime((*j)->getDuration()) << "\t" + << 3 + (pitch / 12) << ((pitch % 12) < 10 ? ".0" : ".") + << pitch % 12 << "\t" + << velocity << "\t\n"; + + } else { + str << ";; Event type: " << (*j)->getType() << std::endl; + } + } + } + + int tempoCount = m_composition->getTempoChangeCount(); + + if (tempoCount > 0) { + + str << "\nt "; + + for (int i = 0; i < tempoCount - 1; ++i) { + + std::pair<timeT, tempoT> tempoChange = + m_composition->getTempoChange(i); + + timeT myTime = tempoChange.first; + timeT nextTime = myTime; + if (i < m_composition->getTempoChangeCount() - 1) { + nextTime = m_composition->getTempoChange(i + 1).first; + } + + int tempo = int(Composition::getTempoQpm(tempoChange.second)); + + str << convertTime( myTime) << " " << tempo << " " + << convertTime(nextTime) << " " << tempo << " "; + } + + str << convertTime(m_composition->getTempoChange(tempoCount - 1).first) + << " " + << int(Composition::getTempoQpm(m_composition->getTempoChange(tempoCount - 1).second)) + << std::endl; + } + + str << "\ne" << std::endl; + str.close(); + return true; +} + +} diff --git a/src/document/io/CsoundExporter.h b/src/document/io/CsoundExporter.h new file mode 100644 index 0000000..0e8c2ac --- /dev/null +++ b/src/document/io/CsoundExporter.h @@ -0,0 +1,63 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_CSOUNDEXPORTER_H_ +#define _RG_CSOUNDEXPORTER_H_ + +#include "gui/general/ProgressReporter.h" +#include <string> + + +class QObject; + + +namespace Rosegarden +{ + +class Composition; + + +/** + * Csound scorefile export + */ + +class CsoundExporter : public ProgressReporter +{ +public: + CsoundExporter(QObject *parent, Composition *, std::string fileName); + ~CsoundExporter(); + + bool write(); + +protected: + Composition *m_composition; + std::string m_fileName; +}; + + + +} + +#endif diff --git a/src/document/io/HydrogenLoader.cpp b/src/document/io/HydrogenLoader.cpp new file mode 100644 index 0000000..38f85fe --- /dev/null +++ b/src/document/io/HydrogenLoader.cpp @@ -0,0 +1,74 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "HydrogenLoader.h" + +#include <qxml.h> +#include "base/Composition.h" +#include "base/PropertyName.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "gui/general/ProgressReporter.h" +#include "HydrogenXMLHandler.h" +#include <qfile.h> +#include <qobject.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +HydrogenLoader::HydrogenLoader(Studio *studio, + QObject *parent, const char *name): + ProgressReporter(parent, name), + m_studio(studio) +{} + +bool +HydrogenLoader::load(const QString& fileName, Composition &comp) +{ + m_composition = ∁ + comp.clear(); + + QFile file(fileName); + if (!file.open(IO_ReadOnly)) { + return false; + } + + m_studio->unassignAllInstruments(); + + HydrogenXMLHandler handler(m_composition); + + QXmlInputSource source(file); + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + + bool ok = reader.parse(source); + + return ok; +} + +} diff --git a/src/document/io/HydrogenLoader.h b/src/document/io/HydrogenLoader.h new file mode 100644 index 0000000..f0cd724 --- /dev/null +++ b/src/document/io/HydrogenLoader.h @@ -0,0 +1,83 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_HYDROGENLOADER_H_ +#define _RG_HYDROGENLOADER_H_ + +#include "base/PropertyName.h" +#include "gui/general/ProgressReporter.h" +#include <string> +#include <vector> + + +class QString; +class QObject; + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class Composition; + + +/** + * Hydrogen drum machine file importer - should work for 0.8.1 and above + * assuming they don't change the file spec without telling us. + * + */ + +class HydrogenLoader : public ProgressReporter +{ +public: + HydrogenLoader(Studio *, + QObject *parent = 0, const char *name = 0); + + /** + * Load and parse the Hydrogen file \a fileName, and write it into the + * given Composition (clearing the existing segment data first). + * Return true for success. + */ + bool load(const QString& fileName, Composition &); + +protected: + Composition *m_composition; + Studio *m_studio; + std::string m_fileName; + +private: + static const int MAX_DOTS = 4; + static const PropertyName SKIP_PROPERTY; +}; + +typedef std::vector<std::pair<std::string, Segment*> > SegmentMap; +typedef std::vector<std::pair<std::string, Segment*> >::iterator SegmentMapIterator; +typedef std::vector<std::pair<std::string, Segment*> >::const_iterator SegmentMapConstIterator; + + +} + +#endif diff --git a/src/document/io/HydrogenXMLHandler.cpp b/src/document/io/HydrogenXMLHandler.cpp new file mode 100644 index 0000000..68e1b20 --- /dev/null +++ b/src/document/io/HydrogenXMLHandler.cpp @@ -0,0 +1,403 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "HydrogenXMLHandler.h" + +#include "base/Event.h" +#include "base/BaseProperties.h" +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Track.h" +#include <qstring.h> + + +namespace Rosegarden +{ + +HydrogenXMLHandler::HydrogenXMLHandler(Composition *composition, + InstrumentId drumIns): + m_composition(composition), + m_drumInstrument(drumIns), + m_inNote(false), + m_inInstrument(false), + m_inPattern(false), + m_inSequence(false), + m_patternName(""), + m_patternSize(0), + m_sequenceName(""), + m_position(0), + m_velocity(0.0), + m_panL(0.0), + m_panR(0.0), + m_pitch(0.0), + m_instrument(0), + m_id(0), + m_muted(false), + m_fileName(""), + m_bpm(0), + m_volume(0.0), + m_name(""), + m_author(""), + m_notes(""), + m_songMode(false), + m_version(""), + m_currentProperty(""), + m_segment(0), + m_currentTrackNb(0), + m_segmentAdded(false), + m_currentBar(0), + m_newSegment(false) +{} + +bool +HydrogenXMLHandler::startDocument() +{ + RG_DEBUG << "HydrogenXMLHandler::startDocument" << endl; + + m_inNote = false; + m_inInstrument = false; + m_inPattern = false; + m_inSequence = false; + + // Pattern attributes + // + m_patternName = ""; + m_patternSize = 0; + + // Sequence attributes + // + m_sequenceName = ""; + + // Note attributes + // + m_position = 0; + m_velocity = 0.0; + m_panL = 0.0; + m_panR = 0.0; + m_pitch = 0.0; + m_instrument = 0; + + // Instrument attributes + // + m_id = 0; + m_muted = false; + m_instrumentVolumes.clear(); + m_fileName = ""; + + // Global attributes + // + m_bpm = 0; + m_volume = 0.0; + m_name = ""; + m_author = ""; + m_notes = ""; + m_songMode = false; + m_version = ""; + + m_currentProperty = ""; + + m_segment = 0; + m_currentTrackNb = 0; + m_segmentAdded = 0; + m_currentBar = 0; + m_newSegment = false; + + return true; +} + +bool +HydrogenXMLHandler::startElement(const QString& /*namespaceURI*/, + const QString& /*localName*/, + const QString& qName, + const QXmlAttributes& /*atts*/) +{ + QString lcName = qName.lower(); + + if (lcName == "note") { + + if (m_inInstrument) + return false; + + m_inNote = true; + + } else if (lcName == "instrument") { + + // Beware instrument attributes inside Notes + if (!m_inNote) + m_inInstrument = true; + } else if (lcName == "pattern") { + m_inPattern = true; + m_segmentAdded = false; // flag the segments being added + } else if (lcName == "sequence") { + + // Create a new segment and set some flags + // + m_segment = new Segment(); + m_newSegment = true; + m_inSequence = true; + } + + m_currentProperty = lcName; + + return true; +} + +bool +HydrogenXMLHandler::endElement(const QString& /*namespaceURI*/, + const QString& /*localName*/, + const QString& qName) +{ + QString lcName = qName.lower(); + + if (lcName == "note") { + + RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Note : position = " << m_position + << ", velocity = " << m_velocity + << ", panL = " << m_panL + << ", panR = " << m_panR + << ", pitch = " << m_pitch + << ", instrument = " << m_instrument + << endl; + + timeT barLength = m_composition->getBarEnd(m_currentBar) - + m_composition->getBarStart(m_currentBar); + + timeT pos = m_composition->getBarStart(m_currentBar) + + timeT( + double(m_position) / double(m_patternSize) * double(barLength)); + + // Insert a rest if we've got a new segment + // + if (m_newSegment) { + Event *restEvent = new Event(Note::EventRestType, + m_composition->getBarStart(m_currentBar), + pos - m_composition->getBarStart(m_currentBar), + Note::EventRestSubOrdering); + m_segment->insert(restEvent); + m_newSegment = false; + } + + // Create and insert this event + // + Event *noteEvent = new Event(Note::EventType, + pos, Note(Note::Semiquaver).getDuration()); + + // get drum mapping from instrument and calculate velocity + noteEvent->set + <Int>( + BaseProperties::PITCH, 36 + m_instrument); + noteEvent->set + <Int>(BaseProperties::VELOCITY, + int(127.0 * m_velocity * m_volume * + m_instrumentVolumes[m_instrument])); + m_segment->insert(noteEvent); + + m_inNote = false; + + } else if (lcName == "instrument" && m_inInstrument) { + + RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Instrument : id = " << m_id + << ", muted = " << m_muted + << ", volume = " << m_instrumentVolumes[m_instrument] + << ", filename = \"" << m_fileName << "\"" + << endl; + + m_inInstrument = false; + + } else if (lcName == "pattern") { + m_inPattern = false; + + if (m_segmentAdded) { + + // Add a blank track to demarcate patterns + // + Track *track = new Track + (m_currentTrackNb, m_drumInstrument, m_currentTrackNb, + "<blank spacer>", false); + m_currentTrackNb++; + m_composition->addTrack(track); + + m_segmentAdded = false; + + // Each pattern has it's own bar so that the imported + // song shows off each pattern a bar at a time. + // + m_currentBar++; + } + + } else if (lcName == "sequence") { + + // If we're closing out a sequencer tab and we have a m_segment then + // we should close up and add that segment. Only create if we have + // some Events in it + // + if (m_segment->size() > 0) { + + m_segment->setTrack(m_currentTrackNb); + + Track *track = new Track + (m_currentTrackNb, m_drumInstrument, m_currentTrackNb, + m_patternName, false); + m_currentTrackNb++; + + // Enforce start and end markers for this bar so that we have a + // whole bar unit segment. + // + m_segment->setEndMarkerTime(m_composition->getBarEnd(m_currentBar)); + QString label = QString("%1 - %2 %3 %4").arg(strtoqstr(m_patternName)) + .arg(strtoqstr(m_sequenceName)) + .arg(i18n(" imported from Hydrogen ")).arg(strtoqstr(m_version)); + m_segment->setLabel(qstrtostr(label)); + + m_composition->addTrack(track); + m_composition->addSegment(m_segment); + m_segment = 0; + + m_segmentAdded = true; + } + + m_inSequence = false; + + } + + return true; +} + +bool +HydrogenXMLHandler::characters(const QString& chars) +{ + QString ch = chars.stripWhiteSpace(); + if (ch == "") + return true; + + if (m_inNote) { + if (m_currentProperty == "position") { + m_position = ch.toInt(); + } else if (m_currentProperty == "velocity") { + m_velocity = qstrtodouble(ch); + } else if (m_currentProperty == "pan_L") { + m_panL = qstrtodouble(ch); + } else if (m_currentProperty == "pan_R") { + m_panR = qstrtodouble(ch); + } else if (m_currentProperty == "pitch") { + m_pitch = qstrtodouble(ch); + } else if (m_currentProperty == "instrument") { + m_instrument = ch.toInt(); + + // Standard kit conversion - hardcoded conversion for Hyrdogen's default + // drum kit. The m_instrument mapping for low values maps well onto the + // kick drum GM kit starting point (MIDI pitch = 36). + // + switch (m_instrument) { + case 11: // Cowbell + m_instrument = 20; + break; + case 12: // Ride Jazz + m_instrument = 15; + break; + case 14: // Ride Rock + m_instrument = 17; + break; + case 15: // Crash Jazz + m_instrument = 16; + break; + + default: + break; + } + + } + } else if (m_inInstrument) { + if (m_currentProperty == "id") { + m_id = ch.toInt(); + } else if (m_currentProperty == "ismuted") { + if (ch.lower() == "true") + m_muted = true; + else + m_muted = false; + } else if (m_currentProperty == "filename") { + m_fileName = qstrtostr(chars); // don't strip whitespace from the filename + } else if (m_currentProperty == "volume") { + m_instrumentVolumes.push_back(qstrtodouble(ch)); + } + + + } else if (m_inPattern) { + + // Pattern attributes + + if (m_currentProperty == "name") { + if (m_inSequence) + m_sequenceName = qstrtostr(chars); + else + m_patternName = qstrtostr(chars); + } else if (m_currentProperty == "size") { + m_patternSize = ch.toInt(); + } + + } else { + + // Global attributes + if (m_currentProperty == "version") { + m_version = qstrtostr(chars); + } else if (m_currentProperty == "bpm") { + + m_bpm = qstrtodouble(ch); + m_composition->addTempoAtTime + (0, Composition::getTempoForQpm(m_bpm)); + + } else if (m_currentProperty == "volume") { + m_volume = qstrtodouble(ch); + } else if (m_currentProperty == "name") { + m_name = qstrtostr(chars); + } else if (m_currentProperty == "author") { + m_author = qstrtostr(chars); + } else if (m_currentProperty == "notes") { + m_notes = qstrtostr(chars); + } else if (m_currentProperty == "mode") { + if (ch.lower() == "song") + m_songMode = true; + else + m_songMode = false; + } + } + + return true; +} + +bool +HydrogenXMLHandler::endDocument() +{ + RG_DEBUG << "HydrogenXMLHandler::endDocument" << endl; + return true; +} + +} diff --git a/src/document/io/HydrogenXMLHandler.h b/src/document/io/HydrogenXMLHandler.h new file mode 100644 index 0000000..0bce68b --- /dev/null +++ b/src/document/io/HydrogenXMLHandler.h @@ -0,0 +1,132 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_HYDROGENXMLHANDLER_H_ +#define _RG_HYDROGENXMLHANDLER_H_ + +#include "HydrogenLoader.h" +#include "base/MidiProgram.h" +#include "base/Track.h" +#include <string> +#include <qstring.h> +#include <vector> +#include <qxml.h> + + +class QXmlAttributes; + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class HydrogenXMLHandler : public QXmlDefaultHandler +{ +public: + HydrogenXMLHandler(Composition *comp, + InstrumentId drumInstrument = MidiInstrumentBase + 9); + + /** + * Overloaded handler functions + */ + virtual bool startDocument(); + virtual bool startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts); + + virtual bool endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName); + + virtual bool characters(const QString& ch); + + virtual bool endDocument (); + +protected: + Composition *m_composition; + InstrumentId m_drumInstrument; + + bool m_inNote; + bool m_inInstrument; + bool m_inPattern; + bool m_inSequence; + + // Pattern attributes + // + std::string m_patternName; + int m_patternSize; + + // Sequence attributes + // + std::string m_sequenceName; + + // Note attributes + // + int m_position; + double m_velocity; + double m_panL; + double m_panR; + double m_pitch; + int m_instrument; + + // Instrument attributes + // + int m_id; + bool m_muted; + std::vector<double> m_instrumentVolumes; + std::string m_fileName; + + // Global attributes + // + double m_bpm; + double m_volume; + std::string m_name; + std::string m_author; + std::string m_notes; + bool m_songMode; // Song mode or pattern mode? + std::string m_version; + + // + QString m_currentProperty; + + Segment *m_segment; + TrackId m_currentTrackNb; + bool m_segmentAdded; + int m_currentBar; + bool m_newSegment; + + SegmentMap m_segmentMap; + +}; + + + +} + +#endif diff --git a/src/document/io/LilyPondExporter.cpp b/src/document/io/LilyPondExporter.cpp new file mode 100644 index 0000000..68731f8 --- /dev/null +++ b/src/document/io/LilyPondExporter.cpp @@ -0,0 +1,2419 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + This file is Copyright 2002 + Hans Kieserman <hkieserman@mail.com> + with heavy lifting from csoundio as it was on 13/5/2002. + + Numerous additions and bug fixes by + Michael McIntyre <dmmcintyr@users.sourceforge.net> + + Some restructuring by Chris Cannam. + + Massive brain surgery, fixes, improvements, and additions by + Heikki Junes + + 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 "LilyPondExporter.h" + +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/Configuration.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/NotationTypes.h" +#include "base/PropertyName.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Sets.h" +#include "base/Staff.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/NotationQuantizer.h" +#include "base/Marker.h" +#include "base/StaffExportTypes.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/application/RosegardenGUIView.h" +#include "gui/editors/notation/NotationProperties.h" +#include "gui/editors/notation/NotationView.h" +#include "gui/editors/guitar/Chord.h" +#include "gui/general/ProgressReporter.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include <kconfig.h> +#include <kmessagebox.h> +#include <qfileinfo.h> +#include <qobject.h> +#include <qregexp.h> +#include <qstring.h> +#include <qtextcodec.h> +#include <kapplication.h> +#include <sstream> +#include <algorithm> + +namespace Rosegarden +{ + +using namespace BaseProperties; + +const PropertyName LilyPondExporter::SKIP_PROPERTY + = "LilyPondExportSkipThisEvent"; + +LilyPondExporter::LilyPondExporter(RosegardenGUIApp *parent, + RosegardenGUIDoc *doc, + std::string fileName) : + ProgressReporter((QObject *)parent, "lilypondExporter"), + m_doc(doc), + m_fileName(fileName), + m_lastClefFound(Clef::Treble) +{ + m_composition = &m_doc->getComposition(); + m_studio = &m_doc->getStudio(); + m_view = ((RosegardenGUIApp *)parent)->getView(); + m_notationView = NULL; + + readConfigVariables(); +} + +LilyPondExporter::LilyPondExporter(NotationView *parent, + RosegardenGUIDoc *doc, + std::string fileName) : + ProgressReporter((QObject *)parent, "lilypondExporter"), + m_doc(doc), + m_fileName(fileName), + m_lastClefFound(Clef::Treble) + +{ + m_composition = &m_doc->getComposition(); + m_studio = &m_doc->getStudio(); + m_view = NULL; + m_notationView = ((NotationView *)parent); + + readConfigVariables(); +} + +void +LilyPondExporter::readConfigVariables(void) +{ + // grab config info + KConfig *cfg = kapp->config(); + cfg->setGroup(NotationViewConfigGroup); + + m_paperSize = cfg->readUnsignedNumEntry("lilypapersize", PAPER_A4); + m_paperLandscape = cfg->readBoolEntry("lilypaperlandscape", false); + m_fontSize = cfg->readUnsignedNumEntry("lilyfontsize", FONT_20); + m_raggedBottom = cfg->readBoolEntry("lilyraggedbottom", false); + m_exportSelection = cfg->readUnsignedNumEntry("lilyexportselection", EXPORT_NONMUTED_TRACKS); + m_exportLyrics = cfg->readBoolEntry("lilyexportlyrics", true); + m_exportMidi = cfg->readBoolEntry("lilyexportmidi", false); + m_exportTempoMarks = cfg->readUnsignedNumEntry("lilyexporttempomarks", EXPORT_NONE_TEMPO_MARKS); + m_exportPointAndClick = cfg->readBoolEntry("lilyexportpointandclick", false); + m_exportBeams = cfg->readBoolEntry("lilyexportbeamings", false); + m_exportStaffMerge = cfg->readBoolEntry("lilyexportstaffmerge", false); + m_exportStaffGroup = cfg->readBoolEntry("lilyexportstaffbrackets", true); + m_lyricsHAlignment = cfg->readBoolEntry("lilylyricshalignment", LEFT_ALIGN); + + m_languageLevel = cfg->readUnsignedNumEntry("lilylanguage", LILYPOND_VERSION_2_6); + m_exportMarkerMode = cfg->readUnsignedNumEntry("lilyexportmarkermode", EXPORT_NO_MARKERS ); +} + +LilyPondExporter::~LilyPondExporter() +{ + // nothing +} + +void +LilyPondExporter::handleStartingEvents(eventstartlist &eventsToStart, + std::ofstream &str) +{ + eventstartlist::iterator m = eventsToStart.begin(); + + while (m != eventsToStart.end()) { + + try { + Indication i(**m); + + if (i.getIndicationType() == Indication::Slur) { + if ((*m)->get + <Bool>(NotationProperties::SLUR_ABOVE)) + str << "^( "; + else + str << "_( "; + } else if (i.getIndicationType() == Indication::PhrasingSlur) { + if ((*m)->get + <Bool>(NotationProperties::SLUR_ABOVE)) + str << "^\\( "; + else + str << "_\\( "; + } else if (i.getIndicationType() == Indication::Crescendo) { + str << "\\< "; + } else if (i.getIndicationType() == Indication::Decrescendo) { + str << "\\> "; + } + + } catch (Event::BadType) { + // Not an indication + } catch (Event::NoData e) { + std::cerr << "Bad indication: " << e.getMessage() << std::endl; + } + + eventstartlist::iterator n(m); + ++n; + eventsToStart.erase(m); + m = n; + } +} + +void +LilyPondExporter::handleEndingEvents(eventendlist &eventsInProgress, + const Segment::iterator &j, + std::ofstream &str) +{ + eventendlist::iterator k = eventsInProgress.begin(); + + while (k != eventsInProgress.end()) { + + eventendlist::iterator l(k); + ++l; + + // Handle and remove all the relevant events in progress + // This assumes all deferred events are indications + + try { + Indication i(**k); + + timeT indicationEnd = + (*k)->getNotationAbsoluteTime() + i.getIndicationDuration(); + timeT eventEnd = + (*j)->getNotationAbsoluteTime() + (*j)->getNotationDuration(); + + if (indicationEnd < eventEnd || + ((i.getIndicationType() == Indication::Slur || + i.getIndicationType() == Indication::PhrasingSlur) && + indicationEnd == eventEnd)) { + + if (i.getIndicationType() == Indication::Slur) { + str << ") "; + } else if (i.getIndicationType() == Indication::PhrasingSlur) { + str << "\\) "; + } else if (i.getIndicationType() == Indication::Crescendo || + i.getIndicationType() == Indication::Decrescendo) { + str << "\\! "; + } + + eventsInProgress.erase(k); + } + + } catch (Event::BadType) { + // not an indication + + } catch (Event::NoData e) { + std::cerr << "Bad indication: " << e.getMessage() << std::endl; + } + + k = l; + } +} + +std::string +LilyPondExporter::convertPitchToLilyNote(int pitch, Accidental accidental, + const Rosegarden::Key &key) +{ + Pitch p(pitch, accidental); + std::string lilyNote = ""; + + lilyNote += (char)tolower(p.getNoteName(key)); + // std::cout << "lilyNote: " << lilyNote << std::endl; + Accidental acc = p.getAccidental(key); + if (acc == Accidentals::DoubleFlat) + lilyNote += "eses"; + else if (acc == Accidentals::Flat) + lilyNote += "es"; + else if (acc == Accidentals::Sharp) + lilyNote += "is"; + else if (acc == Accidentals::DoubleSharp) + lilyNote += "isis"; + + return lilyNote; +} + +std::string +LilyPondExporter::composeLilyMark(std::string eventMark, bool stemUp) +{ + + std::string inStr = "", outStr = ""; + std::string prefix = (stemUp) ? "_" : "^"; + + // shoot text mark straight through unless it's sf or rf + if (Marks::isTextMark(eventMark)) { + inStr = protectIllegalChars(Marks::getTextFromMark(eventMark)); + + if (inStr == "sf") { + inStr = "\\sf"; + } else if (inStr == "rf") { + inStr = "\\rfz"; + } else { + inStr = "\\markup { \\italic " + inStr + " } "; + } + + outStr = prefix + inStr; + + } else if (Marks::isFingeringMark(eventMark)) { + + // fingering marks: use markup syntax only for non-trivial fingerings + + inStr = protectIllegalChars(Marks::getFingeringFromMark(eventMark)); + + if (inStr != "0" && inStr != "1" && inStr != "2" && inStr != "3" && inStr != "4" && inStr != "5" && inStr != "+" ) { + inStr = "\\markup { \\finger \"" + inStr + "\" } "; + } + + outStr = prefix + inStr; + + } else { + outStr = "-"; + + // use full \accent format for everything, even though some shortcuts + // exist, for the sake of consistency + if (eventMark == Marks::Accent) { + outStr += "\\accent"; + } else if (eventMark == Marks::Tenuto) { + outStr += "\\tenuto"; + } else if (eventMark == Marks::Staccato) { + outStr += "\\staccato"; + } else if (eventMark == Marks::Staccatissimo) { + outStr += "\\staccatissimo"; + } else if (eventMark == Marks::Marcato) { + outStr += "\\marcato"; + } else if (eventMark == Marks::Trill) { + outStr += "\\trill"; + } else if (eventMark == Marks::LongTrill) { + // span trill up to the next note: + // tweak the beginning of the next note using an invisible rest having zero length + outStr += "\\startTrillSpan s4*0 \\stopTrillSpan"; + } else if (eventMark == Marks::Turn) { + outStr += "\\turn"; + } else if (eventMark == Marks::Pause) { + outStr += "\\fermata"; + } else if (eventMark == Marks::UpBow) { + outStr += "\\upbow"; + } else if (eventMark == Marks::DownBow) { + outStr += "\\downbow"; + } else { + outStr = ""; + std::cerr << "LilyPondExporter::composeLilyMark() - unhandled mark: " + << eventMark << std::endl; + } + } + + return outStr; +} + +std::string +LilyPondExporter::indent(const int &column) +{ + std::string outStr = ""; + for (int c = 1; c <= column; c++) { + outStr += " "; + } + return outStr; +} + +std::string +LilyPondExporter::protectIllegalChars(std::string inStr) +{ + + QString tmpStr = strtoqstr(inStr); + + tmpStr.replace(QRegExp("&"), "\\&"); + tmpStr.replace(QRegExp("\\^"), "\\^"); + tmpStr.replace(QRegExp("%"), "\\%"); + tmpStr.replace(QRegExp("<"), "\\<"); + tmpStr.replace(QRegExp(">"), "\\>"); + tmpStr.replace(QRegExp("\\["), ""); + tmpStr.replace(QRegExp("\\]"), ""); + tmpStr.replace(QRegExp("\\{"), ""); + tmpStr.replace(QRegExp("\\}"), ""); + + // + // LilyPond uses utf8 encoding. + // + return tmpStr.utf8().data(); +} + +struct MarkerComp { + // Sort Markers by time + // Perhaps this should be made generic with a template? + bool operator()( Marker *a, Marker *b ) { + return a->getTime() < b->getTime(); + } +}; + +bool +LilyPondExporter::write() +{ + QString tmpName = strtoqstr(m_fileName); + + // dmm - modified to act upon the filename itself, rather than the whole + // path; fixes bug #855349 + + // split name into parts: + QFileInfo nfo(tmpName); + QString dirName = nfo.dirPath(); + QString baseName = nfo.fileName(); + + // sed LilyPond-choking chars out of the filename proper + bool illegalFilename = (baseName.contains(' ') || baseName.contains("\\")); + baseName.replace(QRegExp(" "), ""); + baseName.replace(QRegExp("\\\\"), ""); + baseName.replace(QRegExp("'"), ""); + baseName.replace(QRegExp("\""), ""); + + // cat back together + tmpName = dirName + '/' + baseName; + + if (illegalFilename) { + CurrentProgressDialog::freeze(); + int reply = KMessageBox::warningContinueCancel( + 0, i18n("LilyPond does not allow spaces or backslashes in filenames.\n\n" + "Would you like to use\n\n %1\n\n instead?").arg(baseName)); + if (reply != KMessageBox::Continue) + return false; + } + + std::ofstream str(qstrtostr(tmpName).c_str(), std::ios::out); + if (!str) { + std::cerr << "LilyPondExporter::write() - can't write file " << tmpName << std::endl; + return false; + } + + str << "% This LilyPond file was generated by Rosegarden " << protectIllegalChars(VERSION) << std::endl; + + switch (m_languageLevel) { + + case LILYPOND_VERSION_2_6: + str << "\\version \"2.6.0\"" << std::endl; + break; + + case LILYPOND_VERSION_2_8: + str << "\\version \"2.8.0\"" << std::endl; + break; + + case LILYPOND_VERSION_2_10: + str << "\\version \"2.10.0\"" << std::endl; + break; + + case LILYPOND_VERSION_2_12: + str << "\\version \"2.12.0\"" << std::endl; + break; + + default: + // force the default version if there was an error + std::cerr << "ERROR: Unknown language level " << m_languageLevel + << ", using \\version \"2.6.0\" instead" << std::endl; + str << "\\version \"2.6.0\"" << std::endl; + m_languageLevel = LILYPOND_VERSION_2_6; + } + + // enable "point and click" debugging via pdf to make finding the + // unfortunately inevitable errors easier + if (m_exportPointAndClick) { + str << "% point and click debugging is enabled" << std::endl; + } else { + str << "% point and click debugging is disabled" << std::endl; + str << "#(ly:set-option 'point-and-click #f)" << std::endl; + } + + // LilyPond \header block + + // set indention level to make future changes to horizontal layout less + // tedious, ++col to indent a new level, --col to de-indent + int col = 0; + + // grab user headers from metadata + Configuration metadata = m_composition->getMetadata(); + std::vector<std::string> propertyNames = metadata.getPropertyNames(); + + // open \header section if there's metadata to grab, and if the user + // wishes it + if (!propertyNames.empty()) { + str << "\\header {" << std::endl; + col++; // indent+ + + bool userTagline = false; + + for (unsigned int index = 0; index < propertyNames.size(); ++index) { + std::string property = propertyNames [index]; + if (property == headerDedication || property == headerTitle || + property == headerSubtitle || property == headerSubsubtitle || + property == headerPoet || property == headerComposer || + property == headerMeter || property == headerOpus || + property == headerArranger || property == headerInstrument || + property == headerPiece || property == headerCopyright || + property == headerTagline) { + std::string header = protectIllegalChars(metadata.get<String>(property)); + if (header != "") { + str << indent(col) << property << " = \"" << header << "\"" << std::endl; + // let users override defaults, but allow for providing + // defaults if they don't: + if (property == headerTagline) + userTagline = true; + } + } + } + + // default tagline + if (!userTagline) { + str << indent(col) << "tagline = \"" + << "Created using Rosegarden " << protectIllegalChars(VERSION) << " and LilyPond" + << "\"" << std::endl; + } + + // close \header + str << indent(--col) << "}" << std::endl; + } + + // LilyPond \paper block (optional) + if (m_raggedBottom) { + str << indent(col) << "\\paper {" << std::endl; + str << indent(++col) << "ragged-bottom=##t" << std::endl; + str << indent(--col) << "}" << std::endl; + } + + // LilyPond music data! Mapping: + // LilyPond Voice = Rosegarden Segment + // LilyPond Staff = Rosegarden Track + // (not the cleanest output but maybe the most reliable) + + // paper/font sizes + int font; + switch (m_fontSize) { + case 0 : + font = 11; + break; + case 1 : + font = 13; + break; + case 2 : + font = 16; + break; + case 3 : + font = 19; + break; + case 4 : + font = 20; + break; + case 5 : + font = 23; + break; + case 6 : + font = 26; + break; + default : + font = 20; // if config problem + } + + str << indent(col) << "#(set-global-staff-size " << font << ")" << std::endl; + + // write user-specified paper type as default paper size + std::string paper = ""; + switch (m_paperSize) { + case PAPER_A3 : + paper += "a3"; + break; + case PAPER_A4 : + paper += "a4"; + break; + case PAPER_A5 : + paper += "a5"; + break; + case PAPER_A6 : + paper += "a6"; + break; + case PAPER_LEGAL : + paper += "legal"; + break; + case PAPER_LETTER : + paper += "letter"; + break; + case PAPER_TABLOID : + paper += "tabloid"; + break; + case PAPER_NONE : + paper = ""; + break; // "do not specify" + } + if (paper != "") { + str << indent(col) << "#(set-default-paper-size \"" << paper << "\"" + << (m_paperLandscape ? " 'landscape" : "") << ")" + << std::endl; + } + + // Find out the printed length of the composition + Composition::iterator i = m_composition->begin(); + if ((*i) == NULL) { + str << indent(col) << "\\score {" << std::endl; + str << indent(++col) << "% no segments found" << std::endl; + // bind staffs with or without staff group bracket + str << indent(col) // indent + << "<<" << " s4 " << ">>" << std::endl; + str << indent(col) << "\\layout { }" << std::endl; + str << indent(--col) << "}" << std::endl; + return true; + } + timeT compositionStartTime = (*i)->getStartTime(); + timeT compositionEndTime = (*i)->getEndMarkerTime(); + for (; i != m_composition->end(); ++i) { + if (compositionStartTime > (*i)->getStartTime() && (*i)->getTrack() >= 0) { + compositionStartTime = (*i)->getStartTime(); + } + if (compositionEndTime < (*i)->getEndMarkerTime()) { + compositionEndTime = (*i)->getEndMarkerTime(); + } + } + + // define global context which is common for all staffs + str << indent(col++) << "global = { " << std::endl; + TimeSignature timeSignature = m_composition-> + getTimeSignatureAt(m_composition->getStartMarker()); + if (m_composition->getBarStart(m_composition->getBarNumber(compositionStartTime)) < compositionStartTime) { + str << indent(col) << "\\partial "; + // Arbitrary partial durations are handled by the following way: + // split the partial duration to 64th notes: instead of "4" write "64*16". (hjj) + Note partialNote = Note::getNearestNote(1, MAX_DOTS); + int partialDuration = m_composition->getBarStart(m_composition->getBarNumber(compositionStartTime) + 1) - compositionStartTime; + writeDuration(1, str); + str << "*" << ((int)(partialDuration / partialNote.getDuration())) + << std::endl; + } + int leftBar = 0; + int rightBar = leftBar; + do { + bool isNew = false; + m_composition->getTimeSignatureInBar(rightBar + 1, isNew); + + if (isNew || (m_composition->getBarStart(rightBar + 1) >= compositionEndTime)) { + // - set initial time signature; further time signature changes + // are defined within the segments, because they may be hidden + str << indent(col) << (leftBar == 0 ? "" : "% ") << "\\time " + << timeSignature.getNumerator() << "/" + << timeSignature.getDenominator() << std::endl; + // - place skips upto the end of the composition; + // this justifies the printed staffs + str << indent(col); + timeT leftTime = m_composition->getBarStart(leftBar); + timeT rightTime = m_composition->getBarStart(rightBar + 1); + if (leftTime < compositionStartTime) { + leftTime = compositionStartTime; + } + writeSkip(timeSignature, leftTime, rightTime - leftTime, false, str); + str << " %% " << (leftBar + 1) << "-" << (rightBar + 1) << std::endl; + + timeSignature = m_composition->getTimeSignatureInBar(rightBar + 1, isNew); + leftBar = rightBar + 1; + } + } while (m_composition->getBarStart(++rightBar) < compositionEndTime); + str << indent(--col) << "}" << std::endl; + + // time signatures changes are in segments, reset initial value + timeSignature = m_composition-> + getTimeSignatureAt(m_composition->getStartMarker()); + + // All the tempo changes are included in "globalTempo" context. + // This context contains only skip notes between the tempo changes. + // First tempo marking should still be include in \midi{ } block. + // If tempo marks are printed in future, they should probably be + // included in this context and the note duration in the tempo + // mark should be according to the time signature. (hjj) + int tempoCount = m_composition->getTempoChangeCount(); + + if (tempoCount > 0) { + + timeT prevTempoChangeTime = m_composition->getStartMarker(); + int tempo = int(Composition::getTempoQpm(m_composition->getTempoAtTime(prevTempoChangeTime))); + bool tempoMarksInvisible = false; + + str << indent(col++) << "globalTempo = {" << std::endl; + if (m_exportTempoMarks == EXPORT_NONE_TEMPO_MARKS && tempoMarksInvisible == false) { + str << indent(col) << "\\override Score.MetronomeMark #'transparent = ##t" << std::endl; + tempoMarksInvisible = true; + } + str << indent(col) << "\\tempo 4 = " << tempo << " "; + int prevTempo = tempo; + + for (int i = 0; i < tempoCount; ++i) { + + std::pair<timeT, long> tempoChange = + m_composition->getTempoChange(i); + + timeT tempoChangeTime = tempoChange.first; + + tempo = int(Composition::getTempoQpm(tempoChange.second)); + + // First tempo change may be before the first segment. + // Do not apply it before the first segment appears. + if (tempoChangeTime < compositionStartTime) { + tempoChangeTime = compositionStartTime; + } else if (tempoChangeTime >= compositionEndTime) { + tempoChangeTime = compositionEndTime; + } + if (prevTempoChangeTime < compositionStartTime) { + prevTempoChangeTime = compositionStartTime; + } else if (prevTempoChangeTime >= compositionEndTime) { + prevTempoChangeTime = compositionEndTime; + } + writeSkip(m_composition->getTimeSignatureAt(tempoChangeTime), + tempoChangeTime, tempoChangeTime - prevTempoChangeTime, false, str); + // add new \tempo only if tempo was changed + if (tempo != prevTempo) { + if (m_exportTempoMarks == EXPORT_FIRST_TEMPO_MARK && tempoMarksInvisible == false) { + str << std::endl << indent(col) << "\\override Score.MetronomeMark #'transparent = ##t"; + tempoMarksInvisible = true; + } + str << std::endl << indent(col) << "\\tempo 4 = " << tempo << " "; + } + + prevTempo = tempo; + prevTempoChangeTime = tempoChangeTime; + if (prevTempoChangeTime == compositionEndTime) + break; + } + // First tempo change may be before the first segment. + // Do not apply it before the first segment appears. + if (prevTempoChangeTime < compositionStartTime) { + prevTempoChangeTime = compositionStartTime; + } + writeSkip(m_composition->getTimeSignatureAt(prevTempoChangeTime), + prevTempoChangeTime, compositionEndTime - prevTempoChangeTime, false, str); + str << std::endl; + str << indent(--col) << "}" << std::endl; + } + // Markers + // Skip until marker, make sure there's only one marker per measure + if ( m_exportMarkerMode != EXPORT_NO_MARKERS ) { + str << indent(col++) << "markers = {" << std::endl; + timeT prevMarkerTime = 0; + + // Need the markers sorted by time + Composition::markercontainer markers( m_composition->getMarkers() ); // copy + std::sort( markers.begin(), markers.end(), MarkerComp() ); + Composition::markerconstiterator i_marker = markers.begin(); + + while ( i_marker != markers.end() ) { + timeT markerTime = m_composition->getBarStartForTime((*i_marker)->getTime()); + RG_DEBUG << "Marker: " << (*i_marker)->getTime() << " previous: " << prevMarkerTime << endl; + // how to cope with time signature changes? + if ( markerTime > prevMarkerTime ) { + str << indent(col); + writeSkip(m_composition->getTimeSignatureAt(markerTime), + markerTime, markerTime - prevMarkerTime, false, str); + str << "\\mark "; + switch (m_exportMarkerMode) { + case EXPORT_DEFAULT_MARKERS: + // Use the marker name for text + str << "\\default %% " << (*i_marker)->getName() << std::endl; + break; + case EXPORT_TEXT_MARKERS: + // Raise the text above the staff as not to clash with the other stuff + str << "\\markup { \\hspace #0 \\raise #1.5 \"" << (*i_marker)->getName() << "\" }" << std::endl; + break; + default: + break; + } + prevMarkerTime = markerTime; + } + ++i_marker; + } + str << indent(--col) << "}" << std::endl; + } + + // open \score section + str << "\\score {" << std::endl; + + int lastTrackIndex = -1; + int voiceCounter = 0; + bool firstTrack = true; + int staffGroupCounter = 0; + int pianoStaffCounter = 0; + int bracket = 0; + int prevBracket = -1; + + // Write out all segments for each Track, in track order. + // This involves a hell of a lot of loops through all tracks + // and segments, but the time spent doing that should still + // be relatively small in the greater scheme. + + Track *track = 0; + + for (int trackPos = 0; + (track = m_composition->getTrackByPosition(trackPos)) != 0; ++trackPos) { + + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + + if ((*i)->getTrack() != track->getId()) + continue; + + // handle the bracket(s) for the first track, and if no brackets + // present, open with a << + prevBracket = bracket; + bracket = track->getStaffBracket(); + + //!!! how will all these indentions work out? Probably not well, + // but maybe if users always provide sensible input, this will work + // out sensibly. Maybe. If not, we'll need some tracking gizmos to + // figure out the indention, or just skip the indention for these or + // something. TBA. + if (firstTrack) { + // seems to be common to every case now + str << indent(col++) << "<< % common" << std::endl; + } + + if (firstTrack && m_exportStaffGroup) { + + if (bracket == Brackets::SquareOn) { + str << indent(col++) << "\\context StaffGroup = \"" << staffGroupCounter++ + << "\" << " << std::endl; //indent+ + } else if (bracket == Brackets::CurlyOn) { + str << indent(col++) << "\\context PianoStaff = \"" << pianoStaffCounter++ + << "\" << " << std::endl; //indent+ + } else if (bracket == Brackets::CurlySquareOn) { + str << indent(col++) << "\\context StaffGroup = \"" << staffGroupCounter++ + << "\" << " << std::endl; //indent+ + str << indent(col++) << "\\context PianoStaff = \"" << pianoStaffCounter++ + << "\" << " << std::endl; //indent+ + } + + // Make chords offset colliding notes by default (only write for + // first track) + str << indent(++col) << "% force offset of colliding notes in chords:" + << std::endl; + str << indent(col) << "\\override Score.NoteColumn #\'force-hshift = #1.0" + << std::endl; + } + + emit setProgress(int(double(trackPos) / + double(m_composition->getNbTracks()) * 100.0)); + rgapp->refreshGUI(50); + + bool currentSegmentSelected = false; + if ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && + (m_view != NULL) && (m_view->haveSelection())) { + // + // Check whether the current segment is in the list of selected segments. + // + SegmentSelection selection = m_view->getSelection(); + for (SegmentSelection::iterator it = selection.begin(); it != selection.end(); it++) { + if ((*it) == (*i)) currentSegmentSelected = true; + } + } else if ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && (m_notationView != NULL)) { + currentSegmentSelected = m_notationView->hasSegment(*i); + } + + // Check whether the track is a non-midi track. + InstrumentId instrumentId = track->getInstrument(); + bool isMidiTrack = instrumentId >= MidiInstrumentBase; + + if (isMidiTrack && ( // Skip non-midi tracks. + (m_exportSelection == EXPORT_ALL_TRACKS) || + ((m_exportSelection == EXPORT_NONMUTED_TRACKS) && (!track->isMuted())) || + ((m_exportSelection == EXPORT_SELECTED_TRACK) && (m_view != NULL) && + (track->getId() == m_composition->getSelectedTrack())) || + ((m_exportSelection == EXPORT_SELECTED_TRACK) && (m_notationView != NULL) && + (track->getId() == m_notationView->getCurrentSegment()->getTrack())) || + ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && (currentSegmentSelected)))) { + if ((int) (*i)->getTrack() != lastTrackIndex) { + if (lastTrackIndex != -1) { + // close the old track (Staff context) + str << indent(--col) << ">> % Staff ends" << std::endl; //indent- + } + lastTrackIndex = (*i)->getTrack(); + + + // handle any necessary bracket closures with a rude + // hack, because bracket closures need to be handled + // right under staff closures, but at this point in the + // loop we are one track too early for closing, so we use + // the bracket setting for the previous track for closing + // purposes (I'm not quite sure why this works, but it does) + if (m_exportStaffGroup) { + if (prevBracket == Brackets::SquareOff || + prevBracket == Brackets::SquareOnOff) { + str << indent(--col) << ">> % StaffGroup " << staffGroupCounter + << std::endl; //indent- + } else if (prevBracket == Brackets::CurlyOff) { + str << indent(--col) << ">> % PianoStaff " << pianoStaffCounter + << std::endl; //indent- + } else if (prevBracket == Brackets::CurlySquareOff) { + str << indent(--col) << ">> % PianoStaff " << pianoStaffCounter + << std::endl; //indent- + str << indent(--col) << ">> % StaffGroup " << staffGroupCounter + << std::endl; //indent- + } + } + + // handle any bracket start events (unless track staff + // brackets are being ignored, as when printing single parts + // out of a bigger score one by one) + if (!firstTrack && m_exportStaffGroup) { + if (bracket == Brackets::SquareOn || + bracket == Brackets::SquareOnOff) { + str << indent(col++) << "\\context StaffGroup = \"" + << ++staffGroupCounter << "\" <<" << std::endl; + } else if (bracket == Brackets::CurlyOn) { + str << indent(col++) << "\\context PianoStaff = \"" + << ++pianoStaffCounter << "\" <<" << std::endl; + } else if (bracket == Brackets::CurlySquareOn) { + str << indent(col++) << "\\context StaffGroup = \"" + << ++staffGroupCounter << "\" <<" << std::endl; + str << indent(col++) << "\\context PianoStaff = \"" + << ++pianoStaffCounter << "\" <<" << std::endl; + } + } + + // avoid problem with <untitled> tracks yielding a + // .ly file that jumbles all notes together on a + // single staff... every Staff context has to + // have a unique name, even if the + // Staff.instrument property is the same for + // multiple staffs... + // Added an option to merge staffs with the same, non-empty + // name. This option makes it possible to produce staffs + // with polyphonic, and polyrhytmic, music. Polyrhytmic + // music in a single staff is typical in piano, or + // guitar music. (hjj) + // In the case of colliding note heads, user may define + // - DISPLACED_X -- for a note/chord + // - INVISIBLE -- for a rest + std::ostringstream staffName; + staffName << protectIllegalChars(m_composition-> + getTrackById(lastTrackIndex)->getLabel()); + + if (!m_exportStaffMerge || staffName.str() == "") { + str << std::endl << indent(col) + << "\\context Staff = \"track " + << (trackPos + 1) << "\" "; + } else { + str << std::endl << indent(col) + << "\\context Staff = \"" << staffName.str() + << "\" "; + } + + str << "<< " << std::endl; + + // The octavation is omitted in the instrument name. + // HJJ: Should it be automatically added to the clef: G^8 ? + // What if two segments have different transpose in a track? + std::ostringstream staffNameWithTranspose; + staffNameWithTranspose << "\\markup { \\column { \"" << staffName.str() << " \""; + if (((*i)->getTranspose() % 12) != 0) { + staffNameWithTranspose << " \\line { "; + switch ((*i)->getTranspose() % 12) { + case 1 : staffNameWithTranspose << "\"in D\" \\smaller \\flat"; break; + case 2 : staffNameWithTranspose << "\"in D\""; break; + case 3 : staffNameWithTranspose << "\"in E\" \\smaller \\flat"; break; + case 4 : staffNameWithTranspose << "\"in E\""; break; + case 5 : staffNameWithTranspose << "\"in F\""; break; + case 6 : staffNameWithTranspose << "\"in G\" \\smaller \\flat"; break; + case 7 : staffNameWithTranspose << "\"in G\""; break; + case 8 : staffNameWithTranspose << "\"in A\" \\smaller \\flat"; break; + case 9 : staffNameWithTranspose << "\"in A\""; break; + case 10 : staffNameWithTranspose << "\"in B\" \\smaller \\flat"; break; + case 11 : staffNameWithTranspose << "\"in B\""; break; + } + staffNameWithTranspose << " }"; + } + staffNameWithTranspose << " } }"; + if (m_languageLevel < LILYPOND_VERSION_2_10) { + str << indent(++col) << "\\set Staff.instrument = " << staffNameWithTranspose.str() + << std::endl; + } else { + str << indent(++col) << "\\set Staff.instrumentName = " + << staffNameWithTranspose.str() << std::endl; + } + + if (m_exportMidi) { + // Set midi instrument for the Staff + std::ostringstream staffMidiName; + Instrument *instr = m_studio->getInstrumentById( + m_composition->getTrackById(lastTrackIndex)->getInstrument()); + staffMidiName << instr->getProgramName(); + + str << indent(col) << "\\set Staff.midiInstrument = \"" << staffMidiName.str() + << "\"" << std::endl; + } + + // multi measure rests are used by default + str << indent(col) << "\\set Score.skipBars = ##t" << std::endl; + + // turn off the stupid accidental cancelling business, + // because we don't do that ourselves, and because my 11 + // year old son pointed out to me that it "Looks really + // stupid. Why is it cancelling out four flats and then + // adding five flats back? That's brain damaged." + str << indent(col) << "\\set Staff.printKeyCancellation = ##f" << std::endl; + str << indent(col) << "\\new Voice \\global" << std::endl; + if (tempoCount > 0) { + str << indent(col) << "\\new Voice \\globalTempo" << std::endl; + } + if ( m_exportMarkerMode != EXPORT_NO_MARKERS ) { + str << indent(col) << "\\new Voice \\markers" << std::endl; + } + + } + + // Temporary storage for non-atomic events (!BOOM) + // ex. LilyPond expects signals when a decrescendo starts + // as well as when it ends + eventendlist eventsInProgress; + eventstartlist eventsToStart; + + // If the segment doesn't start at 0, add a "skip" to the start + // No worries about overlapping segments, because Voices can overlap + // voiceCounter is a hack because LilyPond does not by default make + // them unique + std::ostringstream voiceNumber; + voiceNumber << "voice " << ++voiceCounter; + + str << std::endl << indent(col++) << "\\context Voice = \"" << voiceNumber.str() + << "\" {"; // indent+ + + str << std::endl << indent(col) << "\\override Voice.TextScript #'padding = #2.0"; + str << std::endl << indent(col) << "\\override MultiMeasureRest #'expand-limit = 1" << std::endl; + + // staff notation size + int staffSize = track->getStaffSize(); + if (staffSize == StaffTypes::Small) str << indent(col) << "\\small" << std::endl; + else if (staffSize == StaffTypes::Tiny) str << indent(col) << "\\tiny" << std::endl; + + SegmentNotationHelper helper(**i); + helper.setNotationProperties(); + + int firstBar = m_composition->getBarNumber((*i)->getStartTime()); + + if (firstBar > 0) { + // Add a skip for the duration until the start of the first + // bar in the segment. If the segment doesn't start on a bar + // line, an additional skip will be written (in the form of + // a series of rests) at the start of writeBar, below. + //!!! This doesn't cope correctly yet with time signature changes + // during this skipped section. + str << std::endl << indent(col); + writeSkip(timeSignature, compositionStartTime, + m_composition->getBarStart(firstBar) - compositionStartTime, + false, str); + } + + std::string lilyText = ""; // text events + std::string prevStyle = ""; // track note styles + + Rosegarden::Key key; + + bool haveRepeating = false; + bool haveAlternates = false; + + bool nextBarIsAlt1 = false; + bool nextBarIsAlt2 = false; + bool prevBarWasAlt2 = false; + + int MultiMeasureRestCount = 0; + + bool nextBarIsDouble = false; + bool nextBarIsEnd = false; + bool nextBarIsDot = false; + + for (int barNo = m_composition->getBarNumber((*i)->getStartTime()); + barNo <= m_composition->getBarNumber((*i)->getEndMarkerTime()); + ++barNo) { + + timeT barStart = m_composition->getBarStart(barNo); + timeT barEnd = m_composition->getBarEnd(barNo); + if (barStart < compositionStartTime) { + barStart = compositionStartTime; + } + + // open \repeat section if this is the first bar in the + // repeat + if ((*i)->isRepeating() && !haveRepeating) { + + haveRepeating = true; + + //!!! calculate the number of times this segment + //repeats and make the following variable meaningful + int numRepeats = 2; + + str << std::endl << indent(col++) << "\\repeat volta " << numRepeats << " {"; + } + + // open the \alternative section if this bar is alternative ending 1 + // ending (because there was an "Alt1" flag in the + // previous bar to the left of where we are right now) + // + // Alt1 remains in effect until we run into Alt2, which + // runs to the end of the segment + if (nextBarIsAlt1 && haveRepeating) { + str << std::endl << indent(--col) << "} \% repeat close (before alternatives) "; + str << std::endl << indent(col++) << "\\alternative {"; + str << std::endl << indent(col++) << "{ \% open alternative 1 "; + nextBarIsAlt1 = false; + haveAlternates = true; + } else if (nextBarIsAlt2 && haveRepeating) { + if (!prevBarWasAlt2) { + col--; + // add an extra str to the following to shut up + // compiler warning from --ing and ++ing it in the + // same statement + str << std::endl << indent(--col) << "} \% close alternative 1 "; + str << std::endl << indent(col++) << "{ \% open alternative 2"; + col++; + } + prevBarWasAlt2 = true; + } + + // write out a bar's worth of events + writeBar(*i, barNo, barStart, barEnd, col, key, + lilyText, + prevStyle, eventsInProgress, str, + MultiMeasureRestCount, + nextBarIsAlt1, nextBarIsAlt2, nextBarIsDouble, nextBarIsEnd, nextBarIsDot); + + } + + // close \repeat + if (haveRepeating) { + + // close \alternative section if present + if (haveAlternates) { + str << std::endl << indent(--col) << " } \% close alternative 2 "; + } + + // close \repeat section in either case + str << std::endl << indent(--col) << " } \% close " + << (haveAlternates ? "alternatives" : "repeat"); + } + + // closing bar + if (((*i)->getEndMarkerTime() == compositionEndTime) && !haveRepeating) { + str << std::endl << indent(col) << "\\bar \"|.\""; + } + + // close Voice context + str << std::endl << indent(--col) << "} % Voice" << std::endl; // indent- + + // + // Write accumulated lyric events to the Lyric context, if desired. + // + // Sync the code below with LyricEditDialog::unparse() !! + // + if (m_exportLyrics) { + for (long currentVerse = 0, lastVerse = 0; + currentVerse <= lastVerse; + currentVerse++) { + bool haveLyric = false; + bool firstNote = true; + QString text = ""; + + timeT lastTime = (*i)->getStartTime(); + for (Segment::iterator j = (*i)->begin(); + (*i)->isBeforeEndMarker(j); ++j) { + + bool isNote = (*j)->isa(Note::EventType); + bool isLyric = false; + + if (!isNote) { + if ((*j)->isa(Text::EventType)) { + std::string textType; + if ((*j)->get + <String>(Text::TextTypePropertyName, textType) && + textType == Text::Lyric) { + isLyric = true; + } + } + } + + if (!isNote && !isLyric) continue; + + timeT myTime = (*j)->getNotationAbsoluteTime(); + + if (isNote) { + if ((myTime > lastTime) || firstNote) { + if (!haveLyric) + text += " _"; + lastTime = myTime; + haveLyric = false; + firstNote = false; + } + } + + if (isLyric) { + long verse; + (*j)->get<Int>(Text::LyricVersePropertyName, verse); + + if (verse == currentVerse) { + std::string ssyllable; + (*j)->get<String>(Text::TextPropertyName, ssyllable); + text += " "; + + QString syllable(strtoqstr(ssyllable)); + syllable.replace(QRegExp("\\s+"), ""); + text += "\"" + syllable + "\""; + haveLyric = true; + } else if (verse > lastVerse) { + lastVerse = verse; + } + } + } + + text.replace( QRegExp(" _+([^ ])") , " \\1" ); + text.replace( "\"_\"" , " " ); + + // Do not create empty context for lyrics. + // Does this save some vertical space, as was written + // in earlier comment? + QRegExp rx( "\"" ); + if ( rx.search( text ) != -1 ) { + + str << indent(col) << "\\lyricsto \"" << voiceNumber.str() << "\"" + << " \\new Lyrics \\lyricmode {" << std::endl; + if (m_lyricsHAlignment == RIGHT_ALIGN) { + str << indent(++col) << "\\override LyricText #'self-alignment-X = #RIGHT" + << std::endl; + } else if (m_lyricsHAlignment == CENTER_ALIGN) { + str << indent(++col) << "\\override LyricText #'self-alignment-X = #CENTER" + << std::endl; + } else { + str << indent(++col) << "\\override LyricText #'self-alignment-X = #LEFT" + << std::endl; + } + str << indent(col) << "\\set ignoreMelismata = ##t" << std::endl; + str << indent(col) << text.utf8() << " " << std::endl; + str << indent(col) << "\\unset ignoreMelismata" << std::endl; + str << indent(--col) << "} % Lyrics " << (currentVerse+1) << std::endl; + // close the Lyrics context + } // if ( rx.search( text.... + } // for (long currentVerse = 0.... + } // if (m_exportLyrics.... + } // if (isMidiTrack.... + firstTrack = false; + } // for (Composition::iterator i = m_composition->begin().... + } // for (int trackPos = 0.... + + // close the last track (Staff context) + if (voiceCounter > 0) { + str << indent(--col) << ">> % Staff (final) ends" << std::endl; // indent- + + // handle any necessary final bracket closures (if brackets are being + // exported) + if (m_exportStaffGroup) { + if (bracket == Brackets::SquareOff || + bracket == Brackets::SquareOnOff) { + str << indent(--col) << ">> % StaffGroup " << staffGroupCounter + << std::endl; //indent- + } else if (bracket == Brackets::CurlyOff) { + str << indent(--col) << ">> % PianoStaff (final) " << pianoStaffCounter + << std::endl; //indent- + } else if (bracket == Brackets::CurlySquareOff) { + str << indent(--col) << ">> % PianoStaff (final) " << pianoStaffCounter + << std::endl; //indent- + str << indent(--col) << ">> % StaffGroup (final) " << staffGroupCounter + << std::endl; //indent- + } + } + } else { + str << indent(--col) << "% (All staffs were muted.)" << std::endl; + } + + // close \notes section + str << std::endl << indent(--col) << ">> % notes" << std::endl << std::endl; // indent- +// str << std::endl << indent(col) << ">> % global wrapper" << std::endl; + + // write \layout block + str << indent(col) << "\\layout { }" << std::endl; + + // write initial tempo in Midi block, if user wishes (added per user request... + // makes debugging the .ly file easier because fewer "noisy" errors are + // produced during the process of rendering MIDI...) + if (m_exportMidi) { + int tempo = int(Composition::getTempoQpm(m_composition->getTempoAtTime(m_composition->getStartMarker()))); + // Incomplete? Can I get away without converting tempo relative to the time + // signature for this purpose? we'll see... + str << indent(col++) << "\\midi {" << std::endl; + str << indent(col) << "\\tempo 4 = " << tempo << std::endl; + str << indent(--col) << "} " << std::endl; + } + + // close \score section and close out the file + str << "} % score" << std::endl; + str.close(); + return true; +} + +timeT +LilyPondExporter::calculateDuration(Segment *s, + const Segment::iterator &i, + timeT barEnd, + timeT &soundingDuration, + const std::pair<int, int> &tupletRatio, + bool &overlong) +{ + timeT duration = (*i)->getNotationDuration(); + timeT absTime = (*i)->getNotationAbsoluteTime(); + + RG_DEBUG << "LilyPondExporter::calculateDuration: first duration, absTime: " + << duration << ", " << absTime << endl; + + timeT durationCorrection = 0; + + if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType)) { + try { + // tuplet compensation, etc + Note::Type type = (*i)->get<Int>(NOTE_TYPE); + int dots = (*i)->get<Int>(NOTE_DOTS); + durationCorrection = Note(type, dots).getDuration() - duration; + } catch (Exception e) { // no properties + } + } + + duration += durationCorrection; + + RG_DEBUG << "LilyPondExporter::calculateDuration: now duration is " + << duration << " after correction of " << durationCorrection << endl; + + soundingDuration = duration * tupletRatio.first/ tupletRatio.second; + + timeT toNext = barEnd - absTime; + if (soundingDuration > toNext) { + soundingDuration = toNext; + duration = soundingDuration * tupletRatio.second/ tupletRatio.first; + overlong = true; + } + + RG_DEBUG << "LilyPondExporter::calculateDuration: time to barEnd is " + << toNext << endl; + + // Examine the following event, and truncate our duration + // if we overlap it. + Segment::iterator nextElt = s->end(); + toNext = soundingDuration; + + if ((*i)->isa(Note::EventType)) { + + Chord chord(*s, i, m_composition->getNotationQuantizer()); + Segment::iterator nextElt = chord.getFinalElement(); + ++nextElt; + + if (s->isBeforeEndMarker(nextElt)) { + // The quantizer sometimes sticks a rest at the same time + // as this note -- don't use that one here, and mark it as + // not to be exported -- it's just a heavy-handed way of + // rendering counterpoint in RG + if ((*nextElt)->isa(Note::EventRestType) && + (*nextElt)->getNotationAbsoluteTime() == absTime) { + (*nextElt)->set<Bool>(SKIP_PROPERTY, true); + ++nextElt; + } + } + + } else { + nextElt = i; + ++nextElt; + while (s->isBeforeEndMarker(nextElt)) { + if ((*nextElt)->isa(Controller::EventType) || + (*nextElt)->isa(ProgramChange::EventType) || + (*nextElt)->isa(SystemExclusive::EventType) || + (*nextElt)->isa(ChannelPressure::EventType) || + (*nextElt)->isa(KeyPressure::EventType) || + (*nextElt)->isa(PitchBend::EventType)) + ++nextElt; + else + break; + } + } + + if (s->isBeforeEndMarker(nextElt)) { + RG_DEBUG << "LilyPondExporter::calculateDuration: inside conditional " << endl; + toNext = (*nextElt)->getNotationAbsoluteTime() - absTime; + // if the note was lengthened, assume it was lengthened to the left + // when truncating to the beginning of the next note + if (durationCorrection > 0) { + toNext += durationCorrection; + } + if (soundingDuration > toNext) { + soundingDuration = toNext; + duration = soundingDuration * tupletRatio.second/ tupletRatio.first; + } + } + + RG_DEBUG << "LilyPondExporter::calculateDuration: second toNext is " + << toNext << endl; + + RG_DEBUG << "LilyPondExporter::calculateDuration: final duration, soundingDuration: " << duration << ", " << soundingDuration << endl; + + return duration; +} + +void +LilyPondExporter::writeBar(Segment *s, + int barNo, int barStart, int barEnd, int col, + Rosegarden::Key &key, + std::string &lilyText, + std::string &prevStyle, + eventendlist &eventsInProgress, + std::ofstream &str, + int &MultiMeasureRestCount, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot) +{ + int lastStem = 0; // 0 => unset, -1 => down, 1 => up + int isGrace = 0; + + Segment::iterator i = s->findTime(barStart); + if (!s->isBeforeEndMarker(i)) + return ; + + if (MultiMeasureRestCount == 0) { + str << std::endl; + + if ((barNo + 1) % 5 == 0) { + str << "%% " << barNo + 1 << std::endl << indent(col); + } else { + str << indent(col); + } + } + + bool isNew = false; + TimeSignature timeSignature = m_composition->getTimeSignatureInBar(barNo, isNew); + if (isNew) { + if (timeSignature.isHidden()) { + str << "\\once \\override Staff.TimeSignature #'break-visibility = #(vector #f #f #f) "; + } + str << "\\time " + << timeSignature.getNumerator() << "/" + << timeSignature.getDenominator() + << std::endl << indent(col); + } + + timeT absTime = (*i)->getNotationAbsoluteTime(); + timeT writtenDuration = 0; + std::pair<int,int> barDurationRatio(timeSignature.getNumerator(),timeSignature.getDenominator()); + std::pair<int,int> durationRatioSum(0,1); + static std::pair<int,int> durationRatio(0,1); + + if (absTime > barStart) { + Note note(Note::getNearestNote(absTime - barStart, MAX_DOTS)); + writtenDuration += note.getDuration(); + durationRatio = writeSkip(timeSignature, 0, note.getDuration(), true, str); + durationRatioSum = fractionSum(durationRatioSum,durationRatio); + // str << qstrtostr(QString(" %{ %1/%2 %} ").arg(durationRatio.first).arg(durationRatio.second)); // DEBUG + } + + timeT prevDuration = -1; + eventstartlist eventsToStart; + + long groupId = -1; + std::string groupType = ""; + std::pair<int, int> tupletRatio(1, 1); + + bool overlong = false; + bool newBeamedGroup = false; + int notesInBeamedGroup = 0; + + while (s->isBeforeEndMarker(i)) { + + if ((*i)->getNotationAbsoluteTime() >= barEnd) + break; + + // First test whether we're entering or leaving a group, + // before we consider how to write the event itself (at least + // for pre-2.0 LilyPond output) + QString startGroupBeamingsStr = ""; + QString endGroupBeamingsStr = ""; + + if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType) || + (*i)->isa(Clef::EventType) || (*i)->isa(Rosegarden::Key::EventType)) { + + long newGroupId = -1; + if ((*i)->get + <Int>(BEAMED_GROUP_ID, newGroupId)) { + + if (newGroupId != groupId) { + // entering a new beamed group + + if (groupId != -1) { + // and leaving an old one + if (groupType == GROUP_TYPE_TUPLED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + endGroupBeamingsStr += "} "; + } else if (groupType == GROUP_TYPE_BEAMED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + } + } + + groupId = newGroupId; + groupType = ""; + (void)(*i)->get + <String>(BEAMED_GROUP_TYPE, groupType); + + if (groupType == GROUP_TYPE_TUPLED) { + long numerator = 0; + long denominator = 0; + (*i)->get + <Int>(BEAMED_GROUP_TUPLED_COUNT, numerator); + (*i)->get + <Int>(BEAMED_GROUP_UNTUPLED_COUNT, denominator); + if (numerator == 0 || denominator == 0) { + std::cerr << "WARNING: LilyPondExporter::writeBar: " + << "tupled event without tupled/untupled counts" + << std::endl; + groupId = -1; + groupType = ""; + } else { + startGroupBeamingsStr += QString("\\times %1/%2 { ").arg(numerator).arg(denominator); + tupletRatio = std::pair<int, int>(numerator, denominator); + // Require explicit beamed groups, + // fixes bug #1683205. + // HJJ: Why line below was originally present? + // newBeamedGroup = true; + notesInBeamedGroup = 0; + } + } else if (groupType == GROUP_TYPE_BEAMED) { + newBeamedGroup = true; + notesInBeamedGroup = 0; + // there can currently be only on group type, reset tuplet ratio + tupletRatio = std::pair<int, int>(1,1); + } + } + + } + else { + + if (groupId != -1) { + // leaving a beamed group + if (groupType == GROUP_TYPE_TUPLED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + endGroupBeamingsStr += "} "; + tupletRatio = std::pair<int, int>(1, 1); + } else if (groupType == GROUP_TYPE_BEAMED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + } + groupId = -1; + groupType = ""; + } + } + } + + // Test whether the next note is grace note or not. + // The start or end of beamed grouping should be put in proper places. + str << endGroupBeamingsStr.utf8(); + if ((*i)->has(IS_GRACE_NOTE) && (*i)->get<Bool>(IS_GRACE_NOTE)) { + if (isGrace == 0) { + isGrace = 1; + str << "\\grace { "; + // str << "%{ grace starts %} "; // DEBUG + } + } else if (isGrace == 1) { + isGrace = 0; + // str << "%{ grace ends %} "; // DEBUG + str << "} "; + } + str << startGroupBeamingsStr.utf8(); + + timeT soundingDuration = -1; + timeT duration = calculateDuration + (s, i, barEnd, soundingDuration, tupletRatio, overlong); + + if (soundingDuration == -1) { + soundingDuration = duration * tupletRatio.first / tupletRatio.second; + } + + if ((*i)->has(SKIP_PROPERTY)) { + (*i)->unset(SKIP_PROPERTY); + ++i; + continue; + } + + bool needsSlashRest = false; + + if ((*i)->isa(Note::EventType)) { + + Chord chord(*s, i, m_composition->getNotationQuantizer()); + Event *e = *chord.getInitialNote(); + bool tiedForward = false; + bool tiedUp = false; + + // Examine the following event, and truncate our duration + // if we overlap it. + + if (e->has(DISPLACED_X)) { + double xDisplacement = 1 + ((double) e->get + <Int>(DISPLACED_X)) / 1000; + str << "\\once \\override NoteColumn #'force-hshift = #" + << xDisplacement << " "; + } + + bool hiddenNote = false; + if (e->has(INVISIBLE)) { + if (e->get + <Bool>(INVISIBLE)) { + hiddenNote = true; + } + } + + if ( hiddenNote ) { + str << "\\hideNotes "; + } + + if (e->has(NotationProperties::STEM_UP)) { + if (e->get + <Bool>(NotationProperties::STEM_UP)) { + if (lastStem != 1) { + str << "\\stemUp "; + lastStem = 1; + } + } + else { + if (lastStem != -1) { + str << "\\stemDown "; + lastStem = -1; + } + } + } else { + if (lastStem != 0) { + str << "\\stemNeutral "; + lastStem = 0; + } + } + + if (chord.size() > 1) + str << "< "; + + Segment::iterator stylei = s->end(); + + for (i = chord.getInitialElement(); s->isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Text::EventType)) { + if (!handleDirective(*i, lilyText, nextBarIsAlt1, nextBarIsAlt2, + nextBarIsDouble, nextBarIsEnd, nextBarIsDot)) { + + handleText(*i, lilyText); + } + + } else if ((*i)->isa(Note::EventType)) { + + if (m_languageLevel >= LILYPOND_VERSION_2_8) { + // one \tweak per each chord note + if (chord.size() > 1) + writeStyle(*i, prevStyle, col, str, true); + else + writeStyle(*i, prevStyle, col, str, false); + } else { + // only one override per chord, and that outside the <> + stylei = i; + } + writePitch(*i, key, str); + + bool noteHasCautionaryAccidental = false; + (*i)->get + <Bool>(NotationProperties::USE_CAUTIONARY_ACCIDENTAL, noteHasCautionaryAccidental); + if (noteHasCautionaryAccidental) + str << "?"; + + // get TIED_FORWARD and TIE_IS_ABOVE for later + (*i)->get<Bool>(TIED_FORWARD, tiedForward); + (*i)->get<Bool>(TIE_IS_ABOVE, tiedUp); + + str << " "; + } else if ((*i)->isa(Indication::EventType)) { + eventsToStart.insert(*i); + eventsInProgress.insert(*i); + } + + if (i == chord.getFinalElement()) + break; + } + + if (chord.size() > 1) + str << "> "; + + if (duration != prevDuration) { + durationRatio = writeDuration(duration, str); + str << " "; + prevDuration = duration; + } + + if (m_languageLevel == LILYPOND_VERSION_2_6) { + // only one override per chord, and that outside the <> + if (stylei != s->end()) { + writeStyle(*stylei, prevStyle, col, str, false); + stylei = s->end(); + } + } + + if (lilyText != "") { + str << lilyText; + lilyText = ""; + } + writeSlashes(*i, str); + + writtenDuration += soundingDuration; + std::pair<int,int> ratio = fractionProduct(durationRatio,tupletRatio); + durationRatioSum = fractionSum(durationRatioSum, ratio); + // str << qstrtostr(QString(" %{ %1/%2 * %3/%4 = %5/%6 %} ").arg(durationRatio.first).arg(durationRatio.second).arg(tupletRatio.first).arg(tupletRatio.second).arg(ratio.first).arg(ratio.second)); // DEBUG + + std::vector<Mark> marks(chord.getMarksForChord()); + // problem here: stem direction unavailable (it's a view-local property) + bool stemUp = true; + e->get + <Bool>(NotationProperties::STEM_UP, stemUp); + for (std::vector<Mark>::iterator j = marks.begin(); j != marks.end(); ++j) { + str << composeLilyMark(*j, stemUp); + } + if (marks.size() > 0) + str << " "; + + handleEndingEvents(eventsInProgress, i, str); + handleStartingEvents(eventsToStart, str); + + if (tiedForward) + if (tiedUp) + str << "^~ "; + else + str << "_~ "; + + if ( hiddenNote ) { + str << "\\unHideNotes "; + } + + if (newBeamedGroup) { + // This is a workaround for bug #1705430: + // Beaming groups erroneous after merging notes + // There will be fewer "e4. [ ]" errors in LilyPond-compiling. + // HJJ: This should be fixed in notation engine, + // after which the workaround below should be removed. + Note note(Note::getNearestNote(duration, MAX_DOTS)); + + switch (note.getNoteType()) { + case Note::SixtyFourthNote: + case Note::ThirtySecondNote: + case Note::SixteenthNote: + case Note::EighthNote: + notesInBeamedGroup++; + break; + } + } + // // Old version before the workaround for bug #1705430: + // if (newBeamedGroup) + // notesInBeamedGroup++; + } else if ((*i)->isa(Note::EventRestType)) { + + bool hiddenRest = false; + if ((*i)->has(INVISIBLE)) { + if ((*i)->get + <Bool>(INVISIBLE)) { + hiddenRest = true; + } + } + + bool offsetRest = false; + int restOffset = 0; + if ((*i)->has(DISPLACED_Y)) { + restOffset = (*i)->get<Int>(DISPLACED_Y); + offsetRest = true; + } + + if (offsetRest) { + std::cout << "REST OFFSET: " << restOffset << std::endl; + } else { + std::cout << "NO REST OFFSET" << std::endl; + } + + if (MultiMeasureRestCount == 0) { + if (hiddenRest) { + str << "s"; + } else if (duration == timeSignature.getBarDuration()) { + // Look ahead the segment in order to detect + // the number of measures in the multi measure rest. + Segment::iterator mm_i = i; + while (s->isBeforeEndMarker(++mm_i)) { + if ((*mm_i)->isa(Note::EventRestType) && + (*mm_i)->getNotationDuration() == (*i)->getNotationDuration() && + timeSignature == m_composition->getTimeSignatureAt((*mm_i)->getNotationAbsoluteTime())) { + MultiMeasureRestCount++; + } else { + break; + } + } + str << "R"; + } else { + if (offsetRest) { + // use offset height to get an approximate corresponding + // height on staff + restOffset = restOffset / 1000; + restOffset -= restOffset * 2; + + // use height on staff to get a MIDI pitch + // get clef from whatever the last clef event was + Rosegarden::Key k; + Accidental a; + Pitch helper(restOffset, m_lastClefFound, k, a); + + // port some code from writePitch() here, rather than + // rewriting writePitch() to do both jobs, which + // somebody could conceivably clean up one day if anyone + // is bored + + // use MIDI pitch to get a named note + int p = helper.getPerformancePitch(); + std::string n = convertPitchToLilyNote(p, a, k); + + // write named note + str << n; + + // generate and write octave marks + std::string m = ""; + int o = (int)(p / 12); + + // mystery hack (it was always aiming too low) + o++; + + if (o < 4) { + for (; o < 4; o++) + m += ","; + } else { + for (; o > 4; o--) + m += "\'"; + } + + str << m; + + // defer the \rest until after any duration, because it + // can't come before a duration if a duration change is + // necessary, which is all determined a bit further on + needsSlashRest = true; + + + std::cout << "using pitch letter:" + << n << m + << " for offset: " + << restOffset + << " for calculated octave: " + << o + << " in clef: " + << m_lastClefFound.getClefType() + << std::endl; + } else { + str << "r"; + } + } + + if (duration != prevDuration) { + durationRatio = writeDuration(duration, str); + if (MultiMeasureRestCount > 0) { + str << "*" << (1 + MultiMeasureRestCount); + } + prevDuration = duration; + } + + // have to add \rest to a fake rest note after any required + // duration change + if (needsSlashRest) { + str << "\\rest"; + needsSlashRest = false; + } + + if (lilyText != "") { + str << lilyText; + lilyText = ""; + } + + str << " "; + + handleEndingEvents(eventsInProgress, i, str); + handleStartingEvents(eventsToStart, str); + + if (newBeamedGroup) + notesInBeamedGroup++; + } else { + MultiMeasureRestCount--; + } + writtenDuration += soundingDuration; + std::pair<int,int> ratio = fractionProduct(durationRatio,tupletRatio); + durationRatioSum = fractionSum(durationRatioSum, ratio); + // str << qstrtostr(QString(" %{ %1/%2 * %3/%4 = %5/%6 %} ").arg(durationRatio.first).arg(durationRatio.second).arg(tupletRatio.first).arg(tupletRatio.second).arg(ratio.first).arg(ratio.second)); // DEBUG + } else if ((*i)->isa(Clef::EventType)) { + + try { + // Incomplete: Set which note the clef should center on (DMM - why?) + // To allow octavation of the clef, enclose the clefname always with quotes. + str << "\\clef \""; + + Clef clef(**i); + + if (clef.getClefType() == Clef::Treble) { + str << "treble"; + } else if (clef.getClefType() == Clef::French) { + str << "french"; + } else if (clef.getClefType() == Clef::Soprano) { + str << "soprano"; + } else if (clef.getClefType() == Clef::Mezzosoprano) { + str << "mezzosoprano"; + } else if (clef.getClefType() == Clef::Alto) { + str << "alto"; + } else if (clef.getClefType() == Clef::Tenor) { + str << "tenor"; + } else if (clef.getClefType() == Clef::Baritone) { + str << "baritone"; + } else if (clef.getClefType() == Clef::Varbaritone) { + str << "varbaritone"; + } else if (clef.getClefType() == Clef::Bass) { + str << "bass"; + } else if (clef.getClefType() == Clef::Subbass) { + str << "subbass"; + } + + // save clef for later use by rests that need repositioned + m_lastClefFound = clef; + std::cout << "getting clef" + << std::endl + << "clef: " + << clef.getClefType() + << " lastClefFound: " + << m_lastClefFound.getClefType() + << std::endl; + + // Transpose the clef one or two octaves up or down, if specified. + int octaveOffset = clef.getOctaveOffset(); + if (octaveOffset > 0) { + str << "^" << 8*octaveOffset; + } else if (octaveOffset < 0) { + str << "_" << -8*octaveOffset; + } + + str << "\"" << std::endl << indent(col); + + } catch (Exception e) { + std::cerr << "Bad clef: " << e.getMessage() << std::endl; + } + + } else if ((*i)->isa(Rosegarden::Key::EventType)) { + // ignore hidden key signatures + bool hiddenKey = false; + if ((*i)->has(INVISIBLE)) { + (*i)->get <Bool>(INVISIBLE, hiddenKey); + } + + if (!hiddenKey) { + try { + str << "\\key "; + key = Rosegarden::Key(**i); + + Accidental accidental = Accidentals::NoAccidental; + + str << convertPitchToLilyNote(key.getTonicPitch(), accidental, key); + + if (key.isMinor()) { + str << " \\minor"; + } else { + str << " \\major"; + } + str << std::endl << indent(col); + + } catch (Exception e) { + std::cerr << "Bad key: " << e.getMessage() << std::endl; + } + } + + } else if ((*i)->isa(Text::EventType)) { + + if (!handleDirective(*i, lilyText, nextBarIsAlt1, nextBarIsAlt2, + nextBarIsDouble, nextBarIsEnd, nextBarIsDot)) { + handleText(*i, lilyText); + } + + } else if ((*i)->isa(Guitar::Chord::EventType)) { + + try { + Guitar::Chord chord = Guitar::Chord(**i); + const Guitar::Fingering& fingering = chord.getFingering(); + + int barreStart = 0, barreEnd = 0, barreFret = 0; + + // + // Check if there is a barre. + // + if (fingering.hasBarre()) { + Guitar::Fingering::Barre barre = fingering.getBarre(); + barreStart = barre.start; + barreEnd = barre.end; + barreFret = barre.fret; + } + + if (barreStart == 0) { + str << " s4*0^\\markup \\fret-diagram #\""; + } else { + str << " s4*0^\\markup \\override #'(barre-type . straight) \\fret-diagram #\""; + } + // + // Check each string individually. + // Note: LilyPond numbers strings differently. + // + for (int stringNum = 6; stringNum >= 1; --stringNum) { + if (barreStart == stringNum) { + str << "c:" << barreStart << "-" << barreEnd << "-" << barreFret << ";"; + } + + if (fingering.getStringStatus( 6-stringNum ) == Guitar::Fingering::MUTED) { + str << stringNum << "-x;"; + } else if (fingering.getStringStatus( 6-stringNum ) == Guitar::Fingering::OPEN) { + str << stringNum << "-o;"; + } else { + int stringStatus = fingering.getStringStatus(6-stringNum); + if ((stringNum <= barreStart) && (stringNum >= barreEnd)) { + str << stringNum << "-" << barreFret << ";"; + } else { + str << stringNum << "-" << stringStatus << ";"; + } + } + } + str << "\" "; + + } catch (Exception e) { // GuitarChord ctor failed + RG_DEBUG << "Bad GuitarChord event in LilyPond export" << endl; + } + } + + // LilyPond 2.0 introduces required postfix syntax for beaming + if (m_exportBeams && newBeamedGroup && notesInBeamedGroup > 0) { + str << "[ "; + newBeamedGroup = false; + } + + if ((*i)->isa(Indication::EventType)) { + eventsToStart.insert(*i); + eventsInProgress.insert(*i); + } + + ++i; + } + + if (groupId != -1) { + if (groupType == GROUP_TYPE_TUPLED) { + if (m_exportBeams && notesInBeamedGroup > 0) + str << "] "; + str << "} "; + tupletRatio = std::pair<int, int>(1, 1); + } else if (groupType == GROUP_TYPE_BEAMED) { + if (m_exportBeams && notesInBeamedGroup > 0) + str << "] "; + } + } + + if (isGrace == 1) { + isGrace = 0; + // str << "%{ grace ends %} "; // DEBUG + str << "} "; + } + + if (lastStem != 0) { + str << "\\stemNeutral "; + } + + if (overlong) { + str << std::endl << indent(col) << + qstrtostr(QString("% %1"). + arg(i18n("warning: overlong bar truncated here"))); + } + + if (fractionSmaller(durationRatioSum, barDurationRatio)) { + str << std::endl << indent(col) << + qstrtostr(QString("% %1"). + arg(i18n("warning: bar too short, padding with rests"))); + str << std::endl << indent(col) << + qstrtostr(QString("% %1/%2 < %3/%4"). + arg(durationRatioSum.first). + arg(durationRatioSum.second). + arg(barDurationRatio.first). + arg(barDurationRatio.second)) + << std::endl << indent(col); + durationRatio = writeSkip(timeSignature, writtenDuration, + (barEnd - barStart) - writtenDuration, true, str); + durationRatioSum = fractionSum(durationRatioSum,durationRatio); + } + // + // Export bar and bar checks. + // + if (nextBarIsDouble) { + str << "\\bar \"||\" "; + nextBarIsDouble = false; + } else if (nextBarIsEnd) { + str << "\\bar \"|.\" "; + nextBarIsEnd = false; + } else if (nextBarIsDot) { + str << "\\bar \":\" "; + nextBarIsDot = false; + } else if (MultiMeasureRestCount == 0) { + str << " |"; + } +} + +std::pair<int,int> +LilyPondExporter::writeSkip(const TimeSignature &timeSig, + timeT offset, + timeT duration, + bool useRests, + std::ofstream &str) +{ + DurationList dlist; + timeSig.getDurationListForInterval(dlist, duration, offset); + std::pair<int,int> durationRatioSum(0,1); + std::pair<int,int> durationRatio(0,1); + + int t = 0, count = 0; + + for (DurationList::iterator i = dlist.begin(); ; ++i) { + + if (i == dlist.end() || (*i) != t) { + + if (count > 0) { + + if (!useRests) + str << "\\skip "; + else if (t == timeSig.getBarDuration()) + str << "R"; + else + str << "r"; + + durationRatio = writeDuration(t, str); + + if (count > 1) { + str << "*" << count; + durationRatio = fractionProduct(durationRatio,count); + } + str << " "; + + durationRatioSum = fractionSum(durationRatioSum,durationRatio); + } + + if (i != dlist.end()) { + t = *i; + count = 1; + } + + } else { + ++count; + } + + if (i == dlist.end()) + break; + } + return durationRatioSum; +} + +bool +LilyPondExporter::handleDirective(const Event *textEvent, + std::string &lilyText, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot) +{ + Text text(*textEvent); + + if (text.getTextType() == Text::LilyPondDirective) { + std::string directive = text.getText(); + if (directive == Text::Segno) { + lilyText += "^\\markup { \\musicglyph #\"scripts.segno\" } "; + } else if (directive == Text::Coda) { + lilyText += "^\\markup { \\musicglyph #\"scripts.coda\" } "; + } else if (directive == Text::Alternate1) { + nextBarIsAlt1 = true; + } else if (directive == Text::Alternate2) { + nextBarIsAlt1 = false; + nextBarIsAlt2 = true; + } else if (directive == Text::BarDouble) { + nextBarIsDouble = true; + } else if (directive == Text::BarEnd) { + nextBarIsEnd = true; + } else if (directive == Text::BarDot) { + nextBarIsDot = true; + } else { + // pass along less special directives for handling as plain text, + // so they can be attached to chords and whatlike without + // redundancy + return false; + } + return true; + } else { + return false; + } +} + +void +LilyPondExporter::handleText(const Event *textEvent, + std::string &lilyText) +{ + try { + + Text text(*textEvent); + std::string s = text.getText(); + + // only protect illegal chars if this is Text, rather than + // LilyPondDirective + if ((*textEvent).isa(Text::EventType)) + s = protectIllegalChars(s); + + if (text.getTextType() == Text::Tempo) { + + // print above staff, bold, large + lilyText += "^\\markup { \\bold \\large \"" + s + "\" } "; + + } else if (text.getTextType() == Text::LocalTempo || + text.getTextType() == Text::Chord) { + + // print above staff, bold, small + lilyText += "^\\markup { \\bold \"" + s + "\" } "; + + } else if (text.getTextType() == Text::Dynamic) { + + // supported dynamics first + if (s == "ppppp" || s == "pppp" || s == "ppp" || + s == "pp" || s == "p" || s == "mp" || + s == "mf" || s == "f" || s == "ff" || + s == "fff" || s == "ffff" || s == "rfz" || + s == "sf") { + + lilyText += "-\\" + s + " "; + + } else { + // export as a plain markup: + // print below staff, bold italics, small + lilyText += "_\\markup { \\bold \\italic \"" + s + "\" } "; + } + + } else if (text.getTextType() == Text::Direction) { + + // print above staff, large + lilyText += "^\\markup { \\large \"" + s + "\" } "; + + } else if (text.getTextType() == Text::LocalDirection) { + + // print below staff, bold italics, small + lilyText += "_\\markup { \\bold \\italic \"" + s + "\" } "; + + // LilyPond directives that don't require special handling across + // barlines are handled here along with ordinary text types. These + // can be injected wherever they happen to occur, and should get + // attached to the right bits in due course without extra effort. + // + } else if (text.getText() == Text::Gliss) { + lilyText += "\\glissando "; + } else if (text.getText() == Text::Arpeggio) { + lilyText += "\\arpeggio "; + } else if (text.getText() == Text::Tiny) { + lilyText += "\\tiny "; + } else if (text.getText() == Text::Small) { + lilyText += "\\small "; + } else if (text.getText() == Text::NormalSize) { + lilyText += "\\normalsize "; + } else { + textEvent->get + <String>(Text::TextTypePropertyName, s); + std::cerr << "LilyPondExporter::write() - unhandled text type: " + << s << std::endl; + } + } catch (Exception e) { + std::cerr << "Bad text: " << e.getMessage() << std::endl; + } +} + +void +LilyPondExporter::writePitch(const Event *note, + const Rosegarden::Key &key, + std::ofstream &str) +{ + // Note pitch (need name as well as octave) + // It is also possible to have "relative" pitches, + // but for simplicity we always use absolute pitch + // 60 is middle C, one unit is a half-step + + long pitch = 60; + note->get + <Int>(PITCH, pitch); + + Accidental accidental = Accidentals::NoAccidental; + note->get + <String>(ACCIDENTAL, accidental); + + // format of LilyPond note is: + // name + octave + (duration) + text markup + + // calculate note name and write note + std::string lilyNote; + + lilyNote = convertPitchToLilyNote(pitch, accidental, key); + + str << lilyNote; + + // generate and write octave marks + std::string octaveMarks = ""; + int octave = (int)(pitch / 12); + + // tweak the octave break for B# / Cb + if ((lilyNote == "bisis") || (lilyNote == "bis")) { + octave--; + } else if ((lilyNote == "ceses") || (lilyNote == "ces")) { + octave++; + } + + if (octave < 4) { + for (; octave < 4; octave++) + octaveMarks += ","; + } else { + for (; octave > 4; octave--) + octaveMarks += "\'"; + } + + str << octaveMarks; +} + +void +LilyPondExporter::writeStyle(const Event *note, std::string &prevStyle, + int col, std::ofstream &str, bool isInChord) +{ + // some hard-coded styles in order to provide rudimentary style export support + // note that this is technically bad practice, as style names are not supposed + // to be fixed but deduced from the style files actually present on the system + const std::string styleMensural = "Mensural"; + const std::string styleTriangle = "Triangle"; + const std::string styleCross = "Cross"; + const std::string styleClassical = "Classical"; + + // handle various note styles before opening any chord + // brackets + std::string style = ""; + note->get + <String>(NotationProperties::NOTE_STYLE, style); + + if (style != prevStyle) { + + if (style == styleClassical && prevStyle == "") + return ; + + if (!isInChord) + prevStyle = style; + + if (style == styleMensural) { + style = "mensural"; + } else if (style == styleTriangle) { + style = "triangle"; + } else if (style == styleCross) { + style = "cross"; + } else { + style = "default"; // failsafe default or explicit + } + + if (!isInChord) { + str << std::endl << indent(col) << "\\override Voice.NoteHead #'style = #'" << style << std::endl << indent(col); + } else { + str << "\\tweak #'style #'" << style << " "; + } + } +} + +std::pair<int,int> +LilyPondExporter::writeDuration(timeT duration, + std::ofstream &str) +{ + Note note(Note::getNearestNote(duration, MAX_DOTS)); + std::pair<int,int> durationRatio(0,1); + + switch (note.getNoteType()) { + + case Note::SixtyFourthNote: + str << "64"; durationRatio = std::pair<int,int>(1,64); + break; + + case Note::ThirtySecondNote: + str << "32"; durationRatio = std::pair<int,int>(1,32); + break; + + case Note::SixteenthNote: + str << "16"; durationRatio = std::pair<int,int>(1,16); + break; + + case Note::EighthNote: + str << "8"; durationRatio = std::pair<int,int>(1,8); + break; + + case Note::QuarterNote: + str << "4"; durationRatio = std::pair<int,int>(1,4); + break; + + case Note::HalfNote: + str << "2"; durationRatio = std::pair<int,int>(1,2); + break; + + case Note::WholeNote: + str << "1"; durationRatio = std::pair<int,int>(1,1); + break; + + case Note::DoubleWholeNote: + str << "\\breve"; durationRatio = std::pair<int,int>(2,1); + break; + } + + for (int numDots = 0; numDots < note.getDots(); numDots++) { + str << "."; + } + durationRatio = fractionProduct(durationRatio, + std::pair<int,int>((1<<(note.getDots()+1))-1,1<<note.getDots())); + return durationRatio; +} + +void +LilyPondExporter::writeSlashes(const Event *note, std::ofstream &str) +{ + // write slashes after text + // / = 8 // = 16 /// = 32, etc. + long slashes = 0; + note->get + <Int>(NotationProperties::SLASHES, slashes); + if (slashes > 0) { + str << ":"; + int length = 4; + for (int c = 1; c <= slashes; c++) { + length *= 2; + } + str << length; + } +} + +} diff --git a/src/document/io/LilyPondExporter.h b/src/document/io/LilyPondExporter.h new file mode 100644 index 0000000..ffb831d --- /dev/null +++ b/src/document/io/LilyPondExporter.h @@ -0,0 +1,262 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + This file is Copyright 2002 + Hans Kieserman <hkieserman@mail.com> + with heavy lifting from csoundio as it was on 13/5/2002. + + Numerous additions and bug fixes by + Michael McIntyre <dmmcintyr@users.sourceforge.net> + + Some restructuring by Chris Cannam. + + Brain surgery to support LilyPond 2.x export by Heikki Junes. + + 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 _RG_LILYPONDEXPORTER_H_ +#define _RG_LILYPONDEXPORTER_H_ + +#include "base/Event.h" +#include "base/PropertyName.h" +#include "base/Segment.h" +#include "gui/general/ProgressReporter.h" +#include <fstream> +#include <set> +#include <string> +#include <utility> + + +class QObject; + + +namespace Rosegarden +{ + +class TimeSignature; +class Studio; +class RosegardenGUIApp; +class RosegardenGUIView; +class RosegardenGUIDoc; +class NotationView; +class Key; +class Composition; + +const std::string headerDedication = "dedication"; +const std::string headerTitle = "title"; +const std::string headerSubtitle = "subtitle"; +const std::string headerSubsubtitle = "subsubtitle"; +const std::string headerPoet = "poet"; +const std::string headerComposer = "composer"; +const std::string headerMeter = "meter"; +const std::string headerOpus = "opus"; +const std::string headerArranger = "arranger"; +const std::string headerInstrument = "instrument"; +const std::string headerPiece = "piece"; +const std::string headerCopyright = "copyright"; +const std::string headerTagline = "tagline"; + +/** + * LilyPond scorefile export + */ + +class LilyPondExporter : public ProgressReporter +{ +public: + typedef std::multiset<Event*, Event::EventCmp> eventstartlist; + typedef std::multiset<Event*, Event::EventEndCmp> eventendlist; + +public: + LilyPondExporter(RosegardenGUIApp *parent, RosegardenGUIDoc *, std::string fileName); + LilyPondExporter(NotationView *parent, RosegardenGUIDoc *, std::string fileName); + ~LilyPondExporter(); + + bool write(); + +protected: + RosegardenGUIView *m_view; + NotationView *m_notationView; + RosegardenGUIDoc *m_doc; + Composition *m_composition; + Studio *m_studio; + std::string m_fileName; + Clef m_lastClefFound; + + void readConfigVariables(void); + void writeBar(Segment *, int barNo, int barStart, int barEnd, int col, + Rosegarden::Key &key, std::string &lilyText, + std::string &prevStyle, eventendlist &eventsInProgress, + std::ofstream &str, int &MultiMeasureRestCount, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot); + + timeT calculateDuration(Segment *s, + const Segment::iterator &i, + timeT barEnd, + timeT &soundingDuration, + const std::pair<int, int> &tupletRatio, + bool &overlong); + + void handleStartingEvents(eventstartlist &eventsToStart, std::ofstream &str); + void handleEndingEvents(eventendlist &eventsInProgress, + const Segment::iterator &j, std::ofstream &str); + + // convert note pitch into LilyPond format note string + std::string convertPitchToLilyNote(int pitch, + Accidental accidental, + const Rosegarden::Key &key); + + // compose an appropriate LilyPond representation for various Marks + std::string composeLilyMark(std::string eventMark, bool stemUp); + + // find/protect illegal characters in user-supplied strings + std::string protectIllegalChars(std::string inStr); + + // return a string full of column tabs + std::string indent(const int &column); + + std::pair<int,int> writeSkip(const TimeSignature &timeSig, + timeT offset, + timeT duration, + bool useRests, + std::ofstream &); + + /* + * Handle LilyPond directive. Returns true if the event was a directive, + * so subsequent code does not bother to process the event twice + */ + bool handleDirective(const Event *textEvent, + std::string &lilyText, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot); + + void handleText(const Event *, std::string &lilyText); + void writePitch(const Event *note, const Rosegarden::Key &key, std::ofstream &); + void writeStyle(const Event *note, std::string &prevStyle, int col, std::ofstream &, bool isInChord); + std::pair<int,int> writeDuration(timeT duration, std::ofstream &); + void writeSlashes(const Event *note, std::ofstream &); + +private: + static const int MAX_DOTS = 4; + static const PropertyName SKIP_PROPERTY; + + unsigned int m_paperSize; + static const unsigned int PAPER_A3 = 0; + static const unsigned int PAPER_A4 = 1; + static const unsigned int PAPER_A5 = 2; + static const unsigned int PAPER_A6 = 3; + static const unsigned int PAPER_LEGAL = 4; + static const unsigned int PAPER_LETTER = 5; + static const unsigned int PAPER_TABLOID = 6; + static const unsigned int PAPER_NONE = 7; + + bool m_paperLandscape; + unsigned int m_fontSize; + static const unsigned int FONT_11 = 0; + static const unsigned int FONT_13 = 1; + static const unsigned int FONT_16 = 2; + static const unsigned int FONT_19 = 3; + static const unsigned int FONT_20 = 4; + static const unsigned int FONT_23 = 5; + static const unsigned int FONT_26 = 6; + + bool m_exportLyrics; + bool m_exportMidi; + + unsigned int m_lyricsHAlignment; + static const unsigned int LEFT_ALIGN = 0; + static const unsigned int CENTER_ALIGN = 1; + static const unsigned int RIGHT_ALIGN = 2; + + unsigned int m_exportTempoMarks; + static const unsigned int EXPORT_NONE_TEMPO_MARKS = 0; + static const unsigned int EXPORT_FIRST_TEMPO_MARK = 1; + static const unsigned int EXPORT_ALL_TEMPO_MARKS = 2; + + unsigned int m_exportSelection; + static const unsigned int EXPORT_ALL_TRACKS = 0; + static const unsigned int EXPORT_NONMUTED_TRACKS = 1; + static const unsigned int EXPORT_SELECTED_TRACK = 2; + static const unsigned int EXPORT_SELECTED_SEGMENTS = 3; + + bool m_exportPointAndClick; + bool m_exportBeams; + bool m_exportStaffGroup; + bool m_exportStaffMerge; + bool m_raggedBottom; + + unsigned int m_exportMarkerMode; + + static const unsigned int EXPORT_NO_MARKERS = 0; + static const unsigned int EXPORT_DEFAULT_MARKERS = 1; + static const unsigned int EXPORT_TEXT_MARKERS = 2; + + int m_languageLevel; + static const int LILYPOND_VERSION_2_6 = 0; + static const int LILYPOND_VERSION_2_8 = 1; + static const int LILYPOND_VERSION_2_10 = 2; + static const int LILYPOND_VERSION_2_12 = 3; + + std::pair<int,int> fractionSum(std::pair<int,int> x,std::pair<int,int> y) { + std::pair<int,int> z( + x.first * y.second + x.second * y.first, + x.second * y.second); + return fractionSimplify(z); + } + std::pair<int,int> fractionProduct(std::pair<int,int> x,std::pair<int,int> y) { + std::pair<int,int> z( + x.first * y.first, + x.second * y.second); + return fractionSimplify(z); + } + std::pair<int,int> fractionProduct(std::pair<int,int> x,int y) { + std::pair<int,int> z( + x.first * y, + x.second); + return fractionSimplify(z); + } + bool fractionSmaller(std::pair<int,int> x,std::pair<int,int> y) { + return (x.first * y.second < x.second * y.first); + } + std::pair<int,int> fractionSimplify(std::pair<int,int> x) { + return std::pair<int,int>(x.first/gcd(x.first,x.second), + x.second/gcd(x.first,x.second)); + } + int gcd(int a, int b) { + // Euclid's algorithm to find the greatest common divisor + while ( 1 ) { + int r = a % b; + if ( r == 0 ) + return (b == 0 ? 1 : b); + a = b; + b = r; + } + } +}; + + + +} + +#endif + diff --git a/src/document/io/MupExporter.cpp b/src/document/io/MupExporter.cpp new file mode 100644 index 0000000..067c909 --- /dev/null +++ b/src/document/io/MupExporter.cpp @@ -0,0 +1,453 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "MupExporter.h" + +#include "misc/Debug.h" +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/NotationQuantizer.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Sets.h" +#include "base/Track.h" +#include "gui/general/ProgressReporter.h" +#include <qobject.h> + +using std::string; + +namespace Rosegarden +{ +using namespace BaseProperties; + +MupExporter::MupExporter(QObject *parent, + Composition *composition, + string fileName) : + ProgressReporter(parent, "mupExporter"), + m_composition(composition), + m_fileName(fileName) +{ + // nothing else +} + +MupExporter::~MupExporter() +{ + // nothing +} + +bool +MupExporter::write() +{ + Composition *c = m_composition; + + std::ofstream str(m_fileName.c_str(), std::ios::out); + if (!str) { + std::cerr << "MupExporter::write() - can't write file " << m_fileName + << std::endl; + return false; + } + + str << "score\n"; + str << "\tstaffs=" << c->getNbTracks() << "\n"; + + int ts = c->getTimeSignatureCount(); + std::pair<timeT, TimeSignature> tspair; + if (ts > 0) + tspair = c->getTimeSignatureChange(0); + str << "\ttime=" + << tspair.second.getNumerator() << "/" + << tspair.second.getDenominator() << "\n"; + + for (int barNo = -1; barNo < c->getNbBars(); ++barNo) { + + for (TrackId trackNo = c->getMinTrackId(); + trackNo <= c->getMaxTrackId(); ++trackNo) { + + if (barNo < 0) { + writeClefAndKey(str, trackNo); + continue; + } + + if (barNo == 0 && trackNo == 0) { + str << "\nmusic\n"; + } + + str << "\t" << trackNo + 1 << ":"; + + Segment *s = 0; + timeT barStart = c->getBarStart(barNo); + timeT barEnd = c->getBarEnd(barNo); + + for (Composition::iterator ci = c->begin(); ci != c->end(); ++ci) { + if ((*ci)->getTrack() == trackNo && + (*ci)->getStartTime() < barEnd && + (*ci)->getEndMarkerTime() > barStart) { + s = *ci; + break; + } + } + + TimeSignature timeSig(c->getTimeSignatureAt(barStart)); + + if (!s) { + // write empty bar + writeInventedRests(str, timeSig, 0, barEnd - barStart); + continue; + } + + if (s->getStartTime() > barStart) { + writeInventedRests(str, timeSig, + 0, s->getStartTime() - barStart); + } + + // Mup insists that every bar has the correct duration, and won't + // recover if one goes wrong. Keep careful tabs on this: it means + // that for example we have to round chord durations down where + // the next chord starts too soon + //!!! we _really_ can't cope with time sig changes yet! + + timeT writtenDuration = writeBar(str, c, s, barStart, barEnd, + timeSig, trackNo); + + if (writtenDuration < timeSig.getBarDuration()) { + RG_DEBUG << "writtenDuration: " << writtenDuration + << ", bar duration " << timeSig.getBarDuration() + << endl; + writeInventedRests(str, timeSig, writtenDuration, + timeSig.getBarDuration() - writtenDuration); + + } else if (writtenDuration > timeSig.getBarDuration()) { + std::cerr << "WARNING: overfull bar in Mup export: duration " << writtenDuration + << " into bar of duration " << timeSig.getBarDuration() + << std::endl; + //!!! warn user + } + + str << "\n"; + } + + if (barNo >= 0) + str << "bar" << std::endl; + } + + str << "\n" << std::endl; + str.close(); + return true; +} + +timeT +MupExporter::writeBar(std::ofstream &str, + Composition *c, + Segment *s, + timeT barStart, timeT barEnd, + TimeSignature &timeSig, + TrackId trackNo) +{ + timeT writtenDuration = 0; + SegmentNotationHelper helper(*s); + helper.setNotationProperties(); + + long currentGroupId = -1; + string currentGroupType = ""; + long currentTupletCount = 3; + bool first = true; + bool openBeamWaiting = false; + + for (Segment::iterator si = s->findTime(barStart); + s->isBeforeEndMarker(si) && + (*si)->getNotationAbsoluteTime() < barEnd; ++si) { + + if ((*si)->isa(Note::EventType)) { + + Chord chord(*s, si, c->getNotationQuantizer()); + Event *e = *chord.getInitialNote(); + + timeT absTime = e->getNotationAbsoluteTime(); + timeT duration = e->getNotationDuration(); + try { + // tuplet compensation, etc + Note::Type type = e->get<Int>(NOTE_TYPE); + int dots = e->get + <Int>(NOTE_DOTS); + duration = Note(type, dots).getDuration(); + } catch (Exception e) { // no properties + std::cerr << "WARNING: MupExporter::writeBar: incomplete note properties: " << e.getMessage() << std::endl; + } + + timeT toNext = duration; + Segment::iterator nextElt = chord.getFinalElement(); + if (s->isBeforeEndMarker(++nextElt)) { + toNext = (*nextElt)->getNotationAbsoluteTime() - absTime; + if (toNext < duration) + duration = toNext; + } + + bool enteringGroup = false; + + if (e->has(BEAMED_GROUP_ID) && e->has(BEAMED_GROUP_TYPE)) { + + long id = e->get + <Int>(BEAMED_GROUP_ID); + string type = e->get + <String>(BEAMED_GROUP_TYPE); + + if (id != currentGroupId) { + + // leave previous group first + if (currentGroupId >= 0) { + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + } + + currentGroupId = id; + currentGroupType = type; + enteringGroup = true; + } + } else { + + if (currentGroupId >= 0) { + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + + currentGroupId = -1; + currentGroupType = ""; + } + } + + if (openBeamWaiting) + str << " bm"; + if (!first) + str << ";"; + str << " "; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + e->get + <Int>(BEAMED_GROUP_UNTUPLED_COUNT, currentTupletCount); + if (enteringGroup) + str << "{ "; + //!!! duration = helper.getCompensatedNotationDuration(e); + + } + + writeDuration(str, duration); + + if (toNext > duration && currentGroupType != GROUP_TYPE_TUPLED) { + writeInventedRests + (str, timeSig, + absTime + duration - barStart, toNext - duration); + } + + writtenDuration += toNext; + + for (Chord::iterator chi = chord.begin(); + chi != chord.end(); ++chi) { + writePitch(str, trackNo, **chi); + } + + openBeamWaiting = false; + if (currentGroupType == GROUP_TYPE_BEAMED || + currentGroupType == GROUP_TYPE_TUPLED) { + if (enteringGroup) + openBeamWaiting = true; + } + + si = chord.getFinalElement(); + + first = false; + + } else if ((*si)->isa(Note::EventRestType)) { + + if (currentGroupId >= 0) { + + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + + currentGroupId = -1; + currentGroupType = ""; + } + + if (openBeamWaiting) + str << " bm"; + if (!first) + str << ";"; + str << " "; + + writeDuration(str, (*si)->getNotationDuration()); + writtenDuration += (*si)->getNotationDuration(); + str << "r"; + + first = false; + openBeamWaiting = false; + + } // ignore all other sorts of events for now + } + + if (currentGroupId >= 0) { + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + } + + if (openBeamWaiting) + str << " bm"; + if (!first) + str << ";"; + + return writtenDuration; +} + +void +MupExporter::writeClefAndKey(std::ofstream &str, TrackId trackNo) +{ + Composition *c = m_composition; + + for (Composition::iterator i = c->begin(); i != c->end(); ++i) { + if ((*i)->getTrack() == trackNo) { + + Clef clef((*i)->getClefAtTime((*i)->getStartTime())); + Rosegarden::Key key((*i)->getKeyAtTime((*i)->getStartTime())); + + + str << "staff " << trackNo + 1 << "\n"; + + if (clef.getClefType() == Clef::Treble) { + str << "\tclef=treble\n"; + } else if (clef.getClefType() == Clef::Alto) { + str << "\tclef=alto\n"; + } else if (clef.getClefType() == Clef::Tenor) { + str << "\tclef=tenor\n"; + } else if (clef.getClefType() == Clef::Bass) { + str << "\tclef=bass\n"; + } + + str << "\tkey=" << key.getAccidentalCount() + << (key.isSharp() ? "#" : "&") + << (key.isMinor() ? "minor" : "major") << std::endl; + + m_clefKeyMap[trackNo] = ClefKeyPair(clef, key); + + return ; + } + } +} + +void +MupExporter::writeInventedRests(std::ofstream &str, + TimeSignature &timeSig, + timeT offset, + timeT duration) +{ + str << " "; + DurationList dlist; + timeSig.getDurationListForInterval(dlist, duration, offset); + for (DurationList::iterator i = dlist.begin(); + i != dlist.end(); ++i) { + writeDuration(str, *i); + str << "r;"; + } +} + +void +MupExporter::writePitch(std::ofstream &str, TrackId trackNo, + Event *event) +{ + long pitch = 0; + if (!event->get + <Int>(PITCH, pitch)) { + str << "c"; // have to write something, or it won't parse + return ; + } + + Accidental accidental = Accidentals::NoAccidental; + (void)event->get + <String>(ACCIDENTAL, accidental); + + // mup octave: treble clef is in octave 4? + + ClefKeyPair ck; + ClefKeyMap::iterator ckmi = m_clefKeyMap.find(trackNo); + if (ckmi != m_clefKeyMap.end()) + ck = ckmi->second; + + Pitch p(pitch, accidental); + Accidental acc(p.getDisplayAccidental(ck.second)); + char note(p.getNoteName(ck.second)); + int octave(p.getOctave()); + + // just to avoid assuming that the note names returned by Pitch are in + // the same set as those expected by Mup -- in practice they are the same + // letters but this changes the case + str << "cdefgab"[Pitch::getIndexForNote(note)]; + + if (acc == Accidentals::DoubleFlat) + str << "&&"; + else if (acc == Accidentals::Flat) + str << "&"; + else if (acc == Accidentals::Sharp) + str << "#"; + else if (acc == Accidentals::DoubleSharp) + str << "##"; + else if (acc == Accidentals::Natural) + str << "n"; + + str << octave + 1; +} + +void +MupExporter::writeDuration(std::ofstream &str, timeT duration) +{ + Note note(Note::getNearestNote(duration, 2)); + int n = Note::Semibreve - note.getNoteType(); + if (n < 0) + str << "1/" << (1 << ( -n)); + else + str << (1 << n); + for (int d = 0; d < note.getDots(); ++d) + str << "."; +} + +} diff --git a/src/document/io/MupExporter.h b/src/document/io/MupExporter.h new file mode 100644 index 0000000..3740252 --- /dev/null +++ b/src/document/io/MupExporter.h @@ -0,0 +1,89 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_MUPEXPORTER_H_ +#define _RG_MUPEXPORTER_H_ + +#include "base/Track.h" +#include "gui/general/ProgressReporter.h" +#include <map> +#include <string> +#include <utility> +#include "base/Event.h" +#include "base/NotationTypes.h" +#include <fstream> + + +class QObject; + + +namespace Rosegarden +{ + +class TimeSignature; +class Segment; +class Event; +class Composition; + + +/** + * Mup file export + */ + +class MupExporter : public ProgressReporter +{ +public: + MupExporter(QObject *parent, Composition *, std::string fileName); + ~MupExporter(); + + bool write(); + +protected: + timeT writeBar(std::ofstream &, + Composition *, + Segment *, + timeT, timeT, + TimeSignature &, + TrackId); + void writeClefAndKey(std::ofstream &, TrackId trackNo); + void writeInventedRests(std::ofstream &, + TimeSignature &timeSig, + timeT offset, + timeT duration); + void writePitch(std::ofstream &, TrackId, Event *event); + void writeDuration(std::ofstream &, timeT duration); + + typedef std::pair<Clef, Rosegarden::Key> ClefKeyPair; + typedef std::map<TrackId, ClefKeyPair> ClefKeyMap; + ClefKeyMap m_clefKeyMap; + + Composition *m_composition; + std::string m_fileName; +}; + + +} + +#endif diff --git a/src/document/io/MusicXmlExporter.cpp b/src/document/io/MusicXmlExporter.cpp new file mode 100644 index 0000000..e1384c6 --- /dev/null +++ b/src/document/io/MusicXmlExporter.cpp @@ -0,0 +1,555 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + This file is Copyright 2002 + Hans Kieserman <hkieserman@mail.com> + with heavy lifting from csoundio as it was on 13/5/2002. + + 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 "MusicXmlExporter.h" + +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Event.h" +#include "base/Instrument.h" +#include "base/NotationTypes.h" +#include "base/XmlExportable.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/general/ProgressReporter.h" +#include <qobject.h> + +namespace Rosegarden +{ + +using namespace BaseProperties; + +MusicXmlExporter::MusicXmlExporter(QObject *parent, + RosegardenGUIDoc *doc, + std::string fileName) : + ProgressReporter(parent, "musicXmlExporter"), + m_doc(doc), + m_fileName(fileName) +{ + // nothing else +} + +MusicXmlExporter::~MusicXmlExporter() +{ + // nothing +} + +void +MusicXmlExporter::writeNote(Event *e, timeT lastNoteTime, + AccidentalTable &accTable, + const Clef &clef, + const Rosegarden::Key &key, + std::ofstream &str) +{ + str << "\t\t\t<note>" << std::endl; + + Pitch pitch(64); + Accidental acc; + Accidental displayAcc; + bool cautionary; + Accidental processedDisplayAcc; + + if (e->isa(Note::EventRestType)) { + str << "\t\t\t\t<rest/>" << std::endl; + + } else { + + // Order of MusicXML elements within a note: + // chord + // pitch + // duration + // tie + // instrument + // voice + // type + // dot(s) + // accidental + // time modification + // stem + // notehead + // staff + // beam + // notations + // lyric + + if (e->getNotationAbsoluteTime() == lastNoteTime) { + str << "\t\t\t\t<chord/>" << std::endl; + } else { + accTable.update(); + } + + str << "\t\t\t\t<pitch>" << std::endl; + + long p = 0; + e->get<Int>(PITCH, p); + pitch = p; + + str << "\t\t\t\t\t<step>" << pitch.getNoteName(key) << "</step>" << std::endl; + + acc = pitch.getAccidental(key.isSharp()); + displayAcc = pitch.getDisplayAccidental(key); + + cautionary = false; + processedDisplayAcc = + accTable.processDisplayAccidental + (displayAcc, pitch.getHeightOnStaff(clef, key), cautionary); + + // don't handle cautionary accidentals here: + if (cautionary) + processedDisplayAcc = Accidentals::NoAccidental; + + if (acc == Accidentals::DoubleFlat) { + str << "\t\t\t\t\t<alter>-2</alter>" << std::endl; + } else if (acc == Accidentals::Flat) { + str << "\t\t\t\t\t<alter>-1</alter>" << std::endl; + } else if (acc == Accidentals::Sharp) { + str << "\t\t\t\t\t<alter>1</alter>" << std::endl; + } else if (acc == Accidentals::DoubleSharp) { + str << "\t\t\t\t\t<alter>2</alter>" << std::endl; + } + + int octave = pitch.getOctave( -1); + str << "\t\t\t\t\t<octave>" << octave << "</octave>" << std::endl; + + str << "\t\t\t\t</pitch>" << std::endl; + } + + // Since there's no way to provide the performance absolute time + // for a note, there's also no point in providing the performance + // duration, even though it might in principle be of interest + str << "\t\t\t\t<duration>" << e->getNotationDuration() << "</duration>" << std::endl; + + if (!e->isa(Note::EventRestType)) { + + if (e->has(TIED_BACKWARD) && + e->get + <Bool>(TIED_BACKWARD)) { + str << "\t\t\t\t<tie type=\"stop\"/>" << std::endl; + } + if (e->has(TIED_FORWARD) && + e->get + <Bool>(TIED_FORWARD)) { + str << "\t\t\t\t<tie type=\"start\"/>" << std::endl; + } + + // Incomplete: will RG ever use this? + str << "\t\t\t\t<voice>" << "1" << "</voice>" << std::endl; + } + + Note note = Note::getNearestNote(e->getNotationDuration()); + + static const char *noteNames[] = { + "64th", "32nd", "16th", "eighth", "quarter", "half", "whole", "breve" + }; + + int noteType = note.getNoteType(); + if (noteType < 0 || noteType >= int(sizeof(noteNames) / sizeof(noteNames[0]))) { + std::cerr << "WARNING: MusicXmlExporter::writeNote: bad note type " + << noteType << std::endl; + noteType = 4; + } + + str << "\t\t\t\t<type>" << noteNames[noteType] << "</type>" << std::endl; + for (int i = 0; i < note.getDots(); ++i) { + str << "\t\t\t\t<dot/>" << std::endl; + } + + if (!e->isa(Note::EventRestType)) { + + if (processedDisplayAcc == Accidentals::DoubleFlat) { + str << "\t\t\t\t<accidental>flat-flat</accidental>" << std::endl; + } else if (processedDisplayAcc == Accidentals::Flat) { + str << "\t\t\t\t<accidental>flat</accidental>" << std::endl; + } else if (processedDisplayAcc == Accidentals::Natural) { + str << "\t\t\t\t<accidental>natural</accidental>" << std::endl; + } else if (processedDisplayAcc == Accidentals::Sharp) { + str << "\t\t\t\t<accidental>sharp</accidental>" << std::endl; + } else if (processedDisplayAcc == Accidentals::DoubleSharp) { + str << "\t\t\t\t<accidental>double-sharp</accidental>" << std::endl; + } + + bool haveNotations = false; + if (e->has(TIED_BACKWARD) && + e->get + <Bool>(TIED_BACKWARD)) { + if (!haveNotations) { + str << "\t\t\t\t<notations>" << std::endl; + haveNotations = true; + } + str << "\t\t\t\t\t<tied type=\"stop\"/>" << std::endl; + } + if (e->has(TIED_FORWARD) && + e->get + <Bool>(TIED_FORWARD)) { + if (!haveNotations) { + str << "\t\t\t\t<notations>" << std::endl; + haveNotations = true; + } + str << "\t\t\t\t\t<tied type=\"start\"/>" << std::endl; + } + if (haveNotations) { + str << "\t\t\t\t</notations>" << std::endl; + } + } + + // could also do <stem>down</stem> if you wanted + str << "\t\t\t</note>" << std::endl; +} + +void +MusicXmlExporter::writeKey(Rosegarden::Key whichKey, std::ofstream &str) +{ + str << "\t\t\t\t<key>" << std::endl; + str << "\t\t\t\t<fifths>" + << (whichKey.isSharp() ? "" : "-") + << (whichKey.getAccidentalCount()) << "</fifths>" << std::endl; + str << "\t\t\t\t<mode>"; + if (whichKey.isMinor()) { + str << "minor"; + } else { + str << "major"; + } + str << "</mode>" << std::endl; + str << "\t\t\t\t</key>" << std::endl; +} + +void +MusicXmlExporter::writeTime(TimeSignature timeSignature, std::ofstream &str) +{ + str << "\t\t\t\t<time>" << std::endl; + str << "\t\t\t\t<beats>" << timeSignature.getNumerator() << "</beats>" << std::endl; + str << "\t\t\t\t<beat-type>" << timeSignature.getDenominator() << "</beat-type>" << std::endl; + str << "\t\t\t\t</time>" << std::endl; +} + +void +MusicXmlExporter::writeClef(Clef whichClef, std::ofstream &str) +{ + str << "\t\t\t\t<clef>" << std::endl; + if (whichClef == Clef::Treble) { + str << "\t\t\t\t<sign>G</sign>" << std::endl; + str << "\t\t\t\t<line>2</line>" << std::endl; + } else if (whichClef == Clef::Alto) { + str << "\t\t\t\t<sign>C</sign>" << std::endl; + str << "\t\t\t\t<line>3</line>" << std::endl; + } else if (whichClef == Clef::Tenor) { + str << "\t\t\t\t<sign>C</sign>" << std::endl; + str << "\t\t\t\t<line>4</line>" << std::endl; + } else if (whichClef == Clef::Bass) { + str << "\t\t\t\t<sign>F</sign>" << std::endl; + str << "\t\t\t\t<line>4</line>" << std::endl; + } + str << "\t\t\t\t</clef>" << std::endl; +} + +std::string +MusicXmlExporter::numToId(int num) +{ + int base = num % 52; + char c; + if (base < 26) c = 'A' + char(base); + else c = 'a' + char(base - 26); + std::string s; + s += c; + while (num / 52 > 0) { + s += c; + num /= 52; + } + return s; +} + +bool +MusicXmlExporter::write() +{ + Composition *composition = &m_doc->getComposition(); + + std::ofstream str(m_fileName.c_str(), std::ios::out); + if (!str) { + std::cerr << "MusicXmlExporter::write() - can't write file " << m_fileName << std::endl; + return false; + } + + // XML header information + str << "<?xml version=\"1.0\"?>" << std::endl; + str << "<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 1.1 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">" << std::endl; + // MusicXml header information + str << "<score-partwise>" << std::endl; + str << "\t<work> <work-title>" << XmlExportable::encode(m_fileName) + << "</work-title></work> " << std::endl; + // Movement, etc. info goes here + str << "\t<identification> " << std::endl; + if (composition->getCopyrightNote() != "") { + str << "\t\t<rights>" + << XmlExportable::encode(composition->getCopyrightNote()) + << "</rights>" << std::endl; + } + str << "\t\t<encoding>" << std::endl; + // Incomplete: Insert date! + // str << "\t\t\t<encoding-date>" << << "</encoding-date>" << std::endl; + str << "\t\t\t<software>Rosegarden v" VERSION "</software>" << std::endl; + str << "\t\t</encoding>" << std::endl; + str << "\t</identification> " << std::endl; + + // MIDI information + str << "\t<part-list>" << std::endl; + Composition::trackcontainer& tracks = composition->getTracks(); + + int trackNo = 0; + timeT lastNoteTime = -1; + + for (Composition::trackiterator i = tracks.begin(); + i != tracks.end(); ++i) { + // Incomplete: What about all the other Midi stuff? + // Incomplete: (Future) GUI to set labels if they're not already + Instrument * trackInstrument = (&m_doc->getStudio())->getInstrumentById((*i).second->getInstrument()); + str << "\t\t<score-part id=\"" << numToId((*i).first) << "\">" << std::endl; + str << "\t\t\t<part-name>" << XmlExportable::encode((*i).second->getLabel()) << "</part-name>" << std::endl; + if (trackInstrument) { +/* + Removing this stuff for now. It doesn't work, because the ids are + are expected to be non-numeric names that refer to elements + elsewhere that define the actual instruments. I think. + + str << "\t\t\t<score-instrument id=\"" << trackInstrument->getName() << "\">" << std::endl; + str << "\t\t\t\t<instrument-name>" << trackInstrument->getType() << "</instrument-name>" << std::endl; + str << "\t\t\t</score-instrument>" << std::endl; + str << "\t\t\t<midi-instrument id=\"" << trackInstrument->getName() << "\">" << std::endl; + str << "\t\t\t\t<midi-channel>" << ((unsigned int)trackInstrument->getMidiChannel() + 1) << "</midi-channel>" << std::endl; + if (trackInstrument->sendsProgramChange()) { + str << "\t\t\t\t<midi-program>" << ((unsigned int)trackInstrument->getProgramChange() + 1) << "</midi-program>" << std::endl; + } + str << "\t\t\t</midi-instrument>" << std::endl; +*/ + } + str << "\t\t</score-part>" << std::endl; + + emit setProgress(int(double(trackNo++) / double(tracks.size()) * 20.0)); + rgapp->refreshGUI(50); + + } // end track iterator + str << "\t</part-list>" << std::endl; + + // Notes! + // Write out all segments for each Track + trackNo = 0; + + for (Composition::trackiterator j = tracks.begin(); + j != tracks.end(); ++j) { + + bool startedPart = false; + + // Code courtesy docs/code/iterators.txt + CompositionTimeSliceAdapter::TrackSet trackSet; + + // Incomplete: get the track info for each track (i.e. this should + // be in an iterator loop) into the track set + trackSet.insert((*j).first); + CompositionTimeSliceAdapter adapter(composition, trackSet); + + int oldMeasureNumber = -1; + bool startedAttributes = false; + Rosegarden::Key key; + Clef clef; + AccidentalTable accTable(key, clef); + TimeSignature prevTimeSignature; + + bool timeSigPending = false; + bool keyPending = false; + bool clefPending = false; + + for (CompositionTimeSliceAdapter::iterator k = adapter.begin(); + k != adapter.end(); ++k) { + + Event *event = *k; + timeT absoluteTime = event->getNotationAbsoluteTime(); + + if (!startedPart) { + str << "\t<part id=\"" << numToId((*j).first) << "\">" << std::endl; + startedPart = true; + } + + // Open a new measure if necessary + // Incomplete: How does MusicXML handle non-contiguous measures? + + int measureNumber = composition->getBarNumber(absoluteTime); + + TimeSignature timeSignature = composition->getTimeSignatureAt(absoluteTime); + + if (measureNumber != oldMeasureNumber) { + + if (startedAttributes) { + + // rather bizarrely, MusicXML appears to require + // key, time, clef in that order + + if (keyPending) { + writeKey(key, str); + keyPending = false; + } + if (timeSigPending) { + writeTime(prevTimeSignature, str); + timeSigPending = false; + } + if (clefPending) { + writeClef(clef, str); + clefPending = false; + } + + str << "\t\t\t</attributes>" << std::endl; + startedAttributes = false; + } + + while (measureNumber > oldMeasureNumber) { + + bool first = (oldMeasureNumber < 0); + + if (!first) { + if (startedAttributes) { + str << "\t\t\t</attributes>" << std::endl; + } + str << "\t\t</measure>\n" << std::endl; + } + + ++oldMeasureNumber; + + str << "\t\t<measure number=\"" << (oldMeasureNumber + 1) << "\">" << std::endl; + + if (first) { + str << "\t\t\t<attributes>" << std::endl; + // Divisions is divisions of crotchet (quarter-note) on which all + // note-lengths are based + str << "\t\t\t\t<divisions>" << Note(Note::Crotchet).getDuration() << "</divisions>" << std::endl; + startedAttributes = true; + timeSigPending = true; + } + } + + accTable = AccidentalTable(key, clef); + } + + oldMeasureNumber = measureNumber; + + if (timeSignature != prevTimeSignature) { + prevTimeSignature = timeSignature; + timeSigPending = true; + if (!startedAttributes) { + str << "\t\t\t<attributes>" << std::endl; + startedAttributes = true; + } + } + + // process event + if (event->isa(Rosegarden::Key::EventType)) { + + if (!startedAttributes) { + str << "\t\t\t<attributes>" << std::endl; + startedAttributes = true; + } + key = Rosegarden::Key(*event); + keyPending = true; + accTable = AccidentalTable(key, clef); + + } else if (event->isa(Clef::EventType)) { + + if (!startedAttributes) { + str << "\t\t\t<attributes>" << std::endl; + startedAttributes = true; + } + clef = Clef(*event); + clefPending = true; + accTable = AccidentalTable(key, clef); + + } else if (event->isa(Note::EventRestType) || + event->isa(Note::EventType)) { + + if (startedAttributes) { + + if (keyPending) { + writeKey(key, str); + keyPending = false; + } + if (timeSigPending) { + writeTime(prevTimeSignature, str); + timeSigPending = false; + } + if (clefPending) { + writeClef(clef, str); + clefPending = false; + } + + str << "\t\t\t</attributes>" << std::endl; + startedAttributes = false; + } + + writeNote(event, lastNoteTime, accTable, clef, key, str); + + if (event->isa(Note::EventType)) { + lastNoteTime = event->getNotationAbsoluteTime(); + } else if (event->isa(Note::EventRestType)) { + lastNoteTime = -1; + } + } + } + + if (startedPart) { + if (startedAttributes) { + + if (keyPending) { + writeKey(key, str); + keyPending = false; + } + if (timeSigPending) { + writeTime(prevTimeSignature, str); + timeSigPending = false; + } + if (clefPending) { + writeClef(clef, str); + clefPending = false; + } + + str << "\t\t\t</attributes>" << std::endl; + startedAttributes = false; + } + + str << "\t\t</measure>" << std::endl; + str << "\t</part>" << std::endl; + } + + emit setProgress(20 + + int(double(trackNo++) / double(tracks.size()) * 80.0)); + rgapp->refreshGUI(50); + } + + str << "</score-partwise>" << std::endl; + str.close(); + return true; +} + +} diff --git a/src/document/io/MusicXmlExporter.h b/src/document/io/MusicXmlExporter.h new file mode 100644 index 0000000..f730de8 --- /dev/null +++ b/src/document/io/MusicXmlExporter.h @@ -0,0 +1,87 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + This file is Copyright 2002 + Hans Kieserman <hkieserman@mail.com> + with heavy lifting from csoundio as it was on 13/5/2002. + + 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 _RG_MUSICXMLEXPORTER_H_ +#define _RG_MUSICXMLEXPORTER_H_ + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "gui/general/ProgressReporter.h" +#include <fstream> +#include <set> +#include <string> + + +class QObject; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class Key; +class Clef; +class AccidentalTable; + + +/** + * MusicXml scorefile export + */ + +class MusicXmlExporter : public ProgressReporter +{ +public: + typedef std::multiset<Event*, Event::EventCmp> eventstartlist; + typedef std::multiset<Event*, Event::EventEndCmp> eventendlist; +public: + MusicXmlExporter(QObject *parent, RosegardenGUIDoc *, std::string fileName); + ~MusicXmlExporter(); + + bool write(); + +protected: + RosegardenGUIDoc *m_doc; + std::string m_fileName; + void writeClef(Rosegarden::Clef, std::ofstream &str); + void writeKey(Rosegarden::Key, std::ofstream &str); + void writeTime(TimeSignature timeSignature, std::ofstream &str); + void writeNote(Event *e, timeT lastNoteTime, + AccidentalTable &table, + const Clef &clef, + const Rosegarden::Key &key, + std::ofstream &str); + + std::string numToId(int); +}; + + + +} + +#endif diff --git a/src/document/io/RG21Loader.cpp b/src/document/io/RG21Loader.cpp new file mode 100644 index 0000000..84f3d03 --- /dev/null +++ b/src/document/io/RG21Loader.cpp @@ -0,0 +1,797 @@ +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "RG21Loader.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/BaseProperties.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/general/ProgressReporter.h" +#include <qfile.h> +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtextstream.h> +#include <string> +#include <vector> + +using std::vector; +using std::string; + + +namespace Rosegarden +{ + +using namespace BaseProperties; +using namespace Accidentals; +using namespace Marks; + +RG21Loader::RG21Loader(Studio *studio, + QObject *parent, const char* name) + : ProgressReporter(parent, name), + m_stream(0), + m_studio(studio), + m_composition(0), + m_currentSegment(0), + m_currentSegmentTime(0), + m_currentSegmentNb(0), + m_currentClef(Clef::Treble), + m_currentInstrumentId(MidiInstrumentBase), + m_inGroup(false), + m_tieStatus(0), + m_nbStaves(0) +{} + +RG21Loader::~RG21Loader() +{} + +bool RG21Loader::parseClef() +{ + if (m_tokens.count() != 3 || !m_currentSegment) + return false; + + std::string clefName = qstrtostr(m_tokens[2].lower()); + + m_currentClef = Clef(clefName); + Event *clefEvent = m_currentClef.getAsEvent(m_currentSegmentTime); + m_currentSegment->insert(clefEvent); + + return true; +} + +bool RG21Loader::parseKey() +{ + if (m_tokens.count() < 3 || !m_currentSegment) + return false; + + QString keyBase = m_tokens[2]; + if (keyBase.length() > 1) { + // Deal correctly with e.g. Bb major + keyBase = + keyBase.left(1).upper() + + keyBase.right(keyBase.length() - 1).lower(); + } else { + keyBase = keyBase.upper(); + } + + QString keyName = QString("%1 %2or") + .arg(keyBase) + .arg(m_tokens[3].lower()); + + m_currentKey = Rosegarden::Key(qstrtostr(keyName)); + Event *keyEvent = m_currentKey.getAsEvent(m_currentSegmentTime); + m_currentSegment->insert(keyEvent); + + return true; +} + +bool RG21Loader::parseMetronome() +{ + if (m_tokens.count() < 2) + return false; + if (!m_composition) + return false; + + QStringList::Iterator i = m_tokens.begin(); + timeT duration = convertRG21Duration(i); + + bool isNumeric = false; + int count = (*i).toInt(&isNumeric); + if (!count || !isNumeric) + return false; + + // we need to take into account the fact that "duration" might not + // be a crotchet + + double qpm = (count * duration) / Note(Note::Crotchet).getDuration(); + m_composition->addTempoAtTime(m_currentSegmentTime, + m_composition->getTempoForQpm(qpm)); + return true; +} + +bool RG21Loader::parseChordItem() +{ + if (m_tokens.count() < 4) + return false; + + QStringList::Iterator i = m_tokens.begin(); + timeT duration = convertRG21Duration(i); + + // get chord mod flags and nb of notes. chord mod is hex + int chordMods = (*i).toInt(0, 16); + ++i; + /*int nbNotes = (*i).toInt();*/ + ++i; + + vector<string> marks = convertRG21ChordMods(chordMods); + + // now get notes + for (;i != m_tokens.end(); ++i) { + + long pitch = (*i).toInt(); + ++i; + + // The noteMods field is nominally a hex integer. As it + // happens its value can never exceed 7, but I guess we + // should do the right thing anyway + int noteMods = (*i).toInt(0, 16); + pitch = convertRG21Pitch(pitch, noteMods); + + Event *noteEvent = new Event(Note::EventType, + m_currentSegmentTime, duration); + noteEvent->set + <Int>(PITCH, pitch); + + if (m_tieStatus == 1) { + noteEvent->set + <Bool>(TIED_FORWARD, true); + } else if (m_tieStatus == 2) { + noteEvent->set + <Bool>(TIED_BACKWARD, true); + } + + if (marks.size() > 0) { + noteEvent->set + <Int>(MARK_COUNT, marks.size()); + for (unsigned int j = 0; j < marks.size(); ++j) { + noteEvent->set + <String>(getMarkPropertyName(j), marks[j]); + } + } + + // RG_DEBUG << "RG21Loader::parseChordItem() : insert note pitch " << pitch + // << " at time " << m_currentSegmentTime << endl; + + setGroupProperties(noteEvent); + + m_currentSegment->insert(noteEvent); + } + + m_currentSegmentTime += duration; + if (m_tieStatus == 2) + m_tieStatus = 0; + else if (m_tieStatus == 1) + m_tieStatus = 2; + + return true; +} + +bool RG21Loader::parseRest() +{ + if (m_tokens.count() < 2) + return false; + + QStringList::Iterator i = m_tokens.begin(); + timeT duration = convertRG21Duration(i); + + Event *restEvent = new Event(Note::EventRestType, + m_currentSegmentTime, duration, + Note::EventRestSubOrdering); + + setGroupProperties(restEvent); + + m_currentSegment->insert(restEvent); + m_currentSegmentTime += duration; + + return true; +} + +bool RG21Loader::parseText() +{ + if (!m_currentSegment) + return false; + + std::string s; + for (unsigned int i = 1; i < m_tokens.count(); ++i) { + if (i > 1) + s += " "; + s += qstrtostr(m_tokens[i]); + } + + if (!readNextLine() || + m_tokens.count() != 2 || m_tokens[0].lower() != "position") { + return false; + } + + int rg21posn = m_tokens[1].toInt(); + std::string type = Text::UnspecifiedType; + + switch (rg21posn) { + + case TextAboveStave: + type = Text::LocalTempo; + break; + + case TextAboveStaveLarge: + type = Text::Tempo; + break; + + case TextAboveBarLine: + type = Text::Direction; + break; + + case TextBelowStave: + type = Text::Lyric; // perhaps + break; + + case TextBelowStaveItalic: + type = Text::LocalDirection; + break; + + case TextChordName: + type = Text::ChordName; + break; + + case TextDynamic: + type = Text::Dynamic; + break; + } + + Text text(s, type); + Event *textEvent = text.getAsEvent(m_currentSegmentTime); + m_currentSegment->insert(textEvent); + + return true; +} + +void RG21Loader::setGroupProperties(Event *e) +{ + if (m_inGroup) { + + e->set + <Int>(BEAMED_GROUP_ID, m_groupId); + e->set + <String>(BEAMED_GROUP_TYPE, m_groupType); + + m_groupUntupledLength += e->getDuration(); + } +} + +bool RG21Loader::parseGroupStart() +{ + m_groupType = qstrtostr(m_tokens[0].lower()); + m_inGroup = true; + m_groupId = m_currentSegment->getNextId(); + m_groupStartTime = m_currentSegmentTime; + + if (m_groupType == GROUP_TYPE_BEAMED) { + + // no more to do + + } else if (m_groupType == GROUP_TYPE_TUPLED) { + + // RG2.1 records two figures A and B, of which A is a time + // value indicating the total duration of the group _after_ + // tupling (which we would call the tupled length), and B is + // the count that appears above the group (which we call the + // untupled count). We need to know C, the total duration of + // the group _before_ tupling; then we can calculate the + // tuplet base (C / B) and tupled count (A * B / C). + + m_groupTupledLength = m_tokens[1].toUInt() * + Note(Note::Hemidemisemiquaver).getDuration(); + + m_groupUntupledCount = m_tokens[2].toUInt(); + m_groupUntupledLength = 0; + + } else { + + RG_DEBUG + << "RG21Loader::parseGroupStart: WARNING: Unknown group type " + << m_groupType << ", ignoring" << endl; + m_inGroup = false; + } + + return true; +} + +bool RG21Loader::parseIndicationStart() +{ + if (m_tokens.count() < 4) + return false; + + unsigned int indicationId = m_tokens[2].toUInt(); + std::string indicationType = qstrtostr(m_tokens[3].lower()); + + // RG_DEBUG << "Indication start: type is \"" << indicationType << "\"" << endl; + + if (indicationType == "tie") { + + if (m_tieStatus != 0) { + RG_DEBUG + << "RG21Loader:: parseIndicationStart: WARNING: Found tie within " + << "tie, ignoring" << endl; + return true; + } + // m_tieStatus = 1; + + Segment::iterator i = m_currentSegment->end(); + if (i != m_currentSegment->begin()) { + --i; + timeT t = (*i)->getAbsoluteTime(); + while ((*i)->getAbsoluteTime() == t) { + (*i)->set + <Bool>(TIED_FORWARD, true); + if (i == m_currentSegment->begin()) + break; + --i; + } + } + m_tieStatus = 2; + + RG_DEBUG << "rg21io: Indication start: it's a tie" << endl; + + } else { + + // Jeez. Whose great idea was it to place marks _after_ the + // events they're marking in the RG2.1 file format? + + timeT indicationTime = m_currentSegmentTime; + Segment::iterator i = m_currentSegment->end(); + if (i != m_currentSegment->begin()) { + --i; + indicationTime = (*i)->getAbsoluteTime(); + } + + Indication indication(indicationType, 0); + Event *e = indication.getAsEvent(indicationTime); + e->setMaybe<Int>("indicationId", indicationId); + setGroupProperties(e); + m_indicationsExtant[indicationId] = e; + + // place the indication in the segment now; don't wait for the + // close-indication, because some things may need to know about it + // before then (e.g. close-group) + + m_currentSegment->insert(e); + + RG_DEBUG << "rg21io: Indication start: it's a real indication; id is " << indicationId << ", event is:" << endl; + e->dump(std::cerr); + + } + + // other indications not handled yet + return true; +} + +void RG21Loader::closeIndication() +{ + if (m_tokens.count() < 3) + return ; + + unsigned int indicationId = m_tokens[2].toUInt(); + EventIdMap::iterator i = m_indicationsExtant.find(indicationId); + + RG_DEBUG << "rg21io: Indication close: indication id is " << indicationId << endl; + + // this is normal (for ties): + if (i == m_indicationsExtant.end()) + return ; + + Event *indicationEvent = i->second; + m_indicationsExtant.erase(i); + + indicationEvent->set + <Int> + //!!! (Indication::IndicationDurationPropertyName, + ("indicationduration", + m_currentSegmentTime - indicationEvent->getAbsoluteTime()); +} + +void RG21Loader::closeGroup() +{ + if (m_groupType == GROUP_TYPE_TUPLED) { + + Segment::iterator i = m_currentSegment->end(); + vector<Event *> toInsert; + vector<Segment::iterator> toErase; + + if (i != m_currentSegment->begin()) { + + --i; + long groupId; + timeT prev = m_groupStartTime + m_groupTupledLength; + + while ((*i)->get + <Int>(BEAMED_GROUP_ID, groupId) && + groupId == m_groupId) { + + timeT absoluteTime = (*i)->getAbsoluteTime(); + timeT offset = absoluteTime - m_groupStartTime; + timeT intended = + (offset * m_groupTupledLength) / m_groupUntupledLength; + + RG_DEBUG + << "RG21Loader::closeGroup:" + << " m_groupStartTime = " << m_groupStartTime + << ", m_groupTupledLength = " << m_groupTupledLength + << ", m_groupUntupledCount = " << m_groupUntupledCount + << ", m_groupUntupledLength = " << m_groupUntupledLength + << ", absoluteTime = " << (*i)->getAbsoluteTime() + << ", offset = " << offset + << ", intended = " << intended + << ", new absolute time = " + << (absoluteTime + intended - offset) + << ", new duration = " + << (prev - absoluteTime) + << endl; + + absoluteTime = absoluteTime + intended - offset; + Event *e(new Event(**i, absoluteTime, prev - absoluteTime)); + prev = absoluteTime; + + // See comment in parseGroupStart + e->set + <Int>(BEAMED_GROUP_TUPLET_BASE, + m_groupUntupledLength / m_groupUntupledCount); + e->set + <Int>(BEAMED_GROUP_TUPLED_COUNT, + m_groupTupledLength * m_groupUntupledCount / + m_groupUntupledLength); + e->set + <Int>(BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount); + + // To change the time of an event, we need to erase & + // re-insert it. But erasure will delete the event, and + // if it's an indication event that will invalidate our + // indicationsExtant entry. Hence this unpleasantness: + + if ((*i)->isa(Indication::EventType)) { + long indicationId = 0; + if ((*i)->get + <Int>("indicationId", indicationId)) { + EventIdMap::iterator ei = + m_indicationsExtant.find(indicationId); + if (ei != m_indicationsExtant.end()) { + m_indicationsExtant.erase(ei); + m_indicationsExtant[indicationId] = e; + } + } + } + + toInsert.push_back(e); + toErase.push_back(i); + + if (i == m_currentSegment->begin()) + break; + --i; + } + } + + for (unsigned int i = 0; i < toInsert.size(); ++i) { + m_currentSegment->insert(toInsert[i]); + } + for (unsigned int i = 0; i < toErase.size(); ++i) { + m_currentSegment->erase(toErase[i]); + } + + m_currentSegmentTime = m_groupStartTime + m_groupTupledLength; + } + + m_inGroup = false; +} + +bool RG21Loader::parseBarType() +{ + if (m_tokens.count() < 5) + return false; + if (!m_composition) + return false; + + int staffNo = m_tokens[1].toInt(); + if (staffNo > 0) { + RG_DEBUG + << "RG21Loader::parseBarType: We don't support different time\n" + << "signatures on different staffs; disregarding time signature for staff " << staffNo << endl; + return true; + } + + // barNo is a hex integer + int barNo = m_tokens[2].toInt(0, 16); + + int numerator = m_tokens[4].toInt(); + int denominator = m_tokens[5].toInt(); + + timeT sigTime = m_composition->getBarRange(barNo).first; + TimeSignature timeSig(numerator, denominator); + m_composition->addTimeSignature(sigTime, timeSig); + + return true; +} + +bool RG21Loader::parseStaveType() +{ + //!!! tags & connected are not yet implemented + + if (m_tokens.count() < 9) + return false; + if (!m_composition) + return false; + + bool isNumeric = false; + + int staffNo = m_tokens[1].toInt(&isNumeric); + if (!isNumeric) + return false; + + int programNo = m_tokens[8].toInt(); + + if (staffNo >= (int)m_composition->getMinTrackId() && + staffNo <= (int)m_composition->getMaxTrackId()) { + + Track *track = m_composition->getTrackById(staffNo); + + if (track) { + Instrument *instr = + m_studio->assignMidiProgramToInstrument(programNo, false); + if (instr) + track->setInstrument(instr->getId()); + } + } + + return true; +} + +timeT RG21Loader::convertRG21Duration(QStringList::Iterator& i) +{ + QString durationString = (*i).lower(); + ++i; + + if (durationString == "dotted") { + durationString += ' '; + durationString += (*i).lower(); + ++i; + } + + try { + + Note n(NotationStrings::getNoteForName(durationString)); + return n.getDuration(); + + } catch (NotationStrings::MalformedNoteName m) { + + RG_DEBUG << "RG21Loader::convertRG21Duration: Bad duration: " + << durationString << endl; + return 0; + } + +} + +void RG21Loader::closeSegment() +{ + if (m_currentSegment) { + + TrackId trackId = m_currentSegmentNb - 1; + + m_currentSegment->setTrack(trackId); + + Track *track = new Track + (trackId, m_currentInstrumentId, trackId, + qstrtostr(m_currentStaffName), false); + m_currentInstrumentId = (++m_currentInstrumentId) % 16; + + m_composition->addTrack(track); + m_composition->addSegment(m_currentSegment); + m_currentSegment = 0; + m_currentSegmentTime = 0; + m_currentClef = Clef(Clef::Treble); + + } else { + // ?? + } +} + +long RG21Loader::convertRG21Pitch(long pitch, int noteModifier) +{ + Accidental accidental = + (noteModifier & ModSharp) ? Sharp : + (noteModifier & ModFlat) ? Flat : + (noteModifier & ModNatural) ? Natural : NoAccidental; + + long rtn = Pitch::getPerformancePitchFromRG21Pitch + (pitch, accidental, m_currentClef, m_currentKey); + + return rtn; +} + +bool RG21Loader::readNextLine() +{ + bool inComment = false; + + do { + inComment = false; + + m_currentLine = m_stream->readLine(); + + if (m_stream->eof()) + return false; + + m_currentLine = m_currentLine.simplifyWhiteSpace(); + + if (m_currentLine[0] == '#' || + m_currentLine.length() == 0) { + inComment = true; + continue; // skip comments + } + + m_tokens = QStringList::split(' ', m_currentLine); + + } while (inComment); + + return true; +} + +bool RG21Loader::load(const QString &fileName, Composition &comp) +{ + m_composition = ∁ + comp.clear(); + + QFile file(fileName); + if (file.open(IO_ReadOnly)) { + m_stream = new QTextStream(&file); + } else { + return false; + } + + m_studio->unassignAllInstruments(); + + while (!m_stream->eof()) { + + if (!readNextLine()) + break; + + QString firstToken = m_tokens.first(); + + if (firstToken == "Staves" || firstToken == "Staffs") { // nb staves + + m_nbStaves = m_tokens[1].toUInt(); + + } else if (firstToken == "Name") { // Staff name + + m_currentStaffName = m_tokens[1]; // we don't do anything with it yet + m_currentSegment = new Segment; + ++m_currentSegmentNb; + + } else if (firstToken == "Clef") { + + parseClef(); + + } else if (firstToken == "Key") { + + parseKey(); + + } else if (firstToken == "Metronome") { + + if (!readNextLine()) + break; + parseMetronome(); + + } else if (firstToken == ":") { // chord + + m_tokens.remove(m_tokens.begin()); // get rid of 1st token ':' + parseChordItem(); + + } else if (firstToken == "Rest") { // rest + + if (!readNextLine()) + break; + + parseRest(); + + } else if (firstToken == "Text") { + + if (!readNextLine()) + break; + + parseText(); + + } else if (firstToken == "Group") { + + if (!readNextLine()) + break; + + parseGroupStart(); + + } else if (firstToken == "Mark") { + + if (m_tokens[1] == "start") + parseIndicationStart(); + else if (m_tokens[1] == "end") + closeIndication(); + + } else if (firstToken == "Bar") { + + parseBarType(); + + } else if (firstToken == "Stave") { + + parseStaveType(); + + } else if (firstToken == "End") { + + if (m_inGroup) + closeGroup(); + else + closeSegment(); + + } else { + + RG_DEBUG << "RG21Loader::parse: Unsupported element type \"" << firstToken << "\", ignoring" << endl; + } + } + + delete m_stream; + m_stream = 0; + + return true; +} + +vector<string> RG21Loader::convertRG21ChordMods(int chordMods) +{ + vector<string> marks; + + // bit laborious! + if (chordMods & ModDot) marks.push_back(Staccato); + if (chordMods & ModLegato) marks.push_back(Tenuto); + if (chordMods & ModAccent) marks.push_back(Accent); + if (chordMods & ModSfz) marks.push_back(Sforzando); + if (chordMods & ModRfz) marks.push_back(Rinforzando); + if (chordMods & ModTrill) marks.push_back(Trill); + if (chordMods & ModTurn) marks.push_back(Turn); + if (chordMods & ModPause) marks.push_back(Pause); + + return marks; +} + +} diff --git a/src/document/io/RG21Loader.h b/src/document/io/RG21Loader.h new file mode 100644 index 0000000..1e944af --- /dev/null +++ b/src/document/io/RG21Loader.h @@ -0,0 +1,162 @@ + +/* -*- 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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RG_RG21LOADER_H_ +#define _RG_RG21LOADER_H_ + +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "gui/general/ProgressReporter.h" +#include <map> +#include <string> +#include <qstring.h> +#include <qstringlist.h> +#include <vector> +#include "base/Event.h" + + +class QTextStream; +class QObject; +class Iterator; + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class Event; +class Composition; + + +/** + * Rosegarden 2.1 file import + */ +class RG21Loader : public ProgressReporter +{ +public: + RG21Loader(Studio *, + QObject *parent = 0, const char *name = 0); + ~RG21Loader(); + + /** + * Load and parse the RG2.1 file \a fileName, and write it into the + * given Composition (clearing the existing segment data first). + * Return true for success. + */ + bool load(const QString& fileName, Composition &); + +protected: + + // RG21 note mods + enum { ModSharp = (1<<0), + ModFlat = (1<<1), + ModNatural = (1<<2) + }; + + // RG21 chord mods + enum { ModDot = (1<<0), + ModLegato = (1<<1), + ModAccent = (1<<2), + ModSfz = (1<<3), + ModRfz = (1<<4), + ModTrill = (1<<5), + ModTurn = (1<<6), + ModPause = (1<<7) + }; + + // RG21 text positions + enum { TextAboveStave = 0, + TextAboveStaveLarge, + TextAboveBarLine, + TextBelowStave, + TextBelowStaveItalic, + TextChordName, + TextDynamic + }; + + bool parseClef(); + bool parseKey(); + bool parseMetronome(); + bool parseChordItem(); + bool parseRest(); + bool parseText(); + bool parseGroupStart(); + bool parseIndicationStart(); + bool parseBarType(); + bool parseStaveType(); + + void closeGroup(); + void closeIndication(); + void closeSegment(); + + void setGroupProperties(Event *); + + long convertRG21Pitch(long rg21pitch, int noteModifier); + timeT convertRG21Duration(QStringList::Iterator&); + std::vector<std::string> convertRG21ChordMods(int chordMod); + + bool readNextLine(); + + //--------------- Data members --------------------------------- + + QTextStream *m_stream; + + Studio *m_studio; + Composition* m_composition; + Segment* m_currentSegment; + unsigned int m_currentSegmentTime; + unsigned int m_currentSegmentNb; + Clef m_currentClef; + Rosegarden::Key m_currentKey; + InstrumentId m_currentInstrumentId; + + typedef std::map<int, Event *> EventIdMap; + EventIdMap m_indicationsExtant; + + bool m_inGroup; + long m_groupId; + std::string m_groupType; + timeT m_groupStartTime; + int m_groupTupledLength; + int m_groupTupledCount; + int m_groupUntupledLength; + int m_groupUntupledCount; + + int m_tieStatus; // 0 -> none, 1 -> tie started, 2 -> seen one note + + QString m_currentLine; + QString m_currentStaffName; + + QStringList m_tokens; + + unsigned int m_nbStaves; +}; + + + +} + +#endif |