1 /*
2 Copyright (C) 2005-2007 Remon Sijrier
3 
4 This file is part of Traverso
5 
6 Traverso is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
19 
20 */
21 
22 #include <QFile>
23 #include <QDir>
24 #include <QTextStream>
25 #include <QMessageBox>
26 #include <QString>
27 
28 #include <cfloat>
29 
30 #include "Project.h"
31 #include "Sheet.h"
32 #include "ProjectManager.h"
33 #include "Information.h"
34 #include "InputEngine.h"
35 #include "ResourcesManager.h"
36 #include "Export.h"
37 #include "AudioDevice.h"
38 #include "Config.h"
39 #include "ContextPointer.h"
40 #include "Utils.h"
41 #include <AddRemove.h>
42 #include "FileHelpers.h"
43 
44 #define PROJECT_FILE_VERSION 	3
45 
46 // Always put me below _all_ includes, this is needed
47 // in case we run with memory leak detection enabled!
48 #include "Debugger.h"
49 
50 /**	\class Project
51 	\brief Project restores and saves the state of a Traverso Project
52 
53 	A Project can have as much Sheet's as one likes. A Project with one Sheet acts like a 'Session'
54 	where the Sheet can be turned into a CD with various Tracks using Marker 's
55 	When a Project has multiple Sheet's, each Sheet will be a CD Track, this can be usefull if each
56 	Track on a CD is independend of the other CD Tracks.
57 
58  */
59 
60 
Project(const QString & title)61 Project::Project(const QString& title)
62 	: ContextItem(), m_title(title)
63 {
64 	PENTERCONS;
65 	m_currentSheetId = 0;
66 	m_exportThread = 0;
67 	engineer = "";
68 
69 	m_useResampling = config().get_property("Conversion", "DynamicResampling", true).toBool();
70 	m_rootDir = config().get_property("Project", "directory", "/directory/unknown/").toString() + "/" + m_title;
71 	m_sourcesDir = m_rootDir + "/audiosources";
72 	m_rate = audiodevice().get_sample_rate();
73 	m_bitDepth = audiodevice().get_bit_depth();
74 
75 	m_resourcesManager = new ResourcesManager(this);
76 	m_hs = new QUndoStack(pm().get_undogroup());
77 
78 	cpointer().add_contextitem(this);
79 }
80 
81 
~Project()82 Project::~Project()
83 {
84 	PENTERDES;
85 	cpointer().remove_contextitem(this);
86 
87 	foreach(Sheet* sheet, m_sheets) {
88 		sheet->schedule_for_deletion();
89 		sheet->disconnect_from_audiodevice();
90 	}
91 
92 	delete m_hs;
93 }
94 
95 
create(int sheetcount,int numtracks)96 int Project::create(int sheetcount, int numtracks)
97 {
98 	PENTER;
99 	PMESG("Creating new project %s  NumSheets=%d", QS_C(m_title), sheetcount);
100 
101 	QDir dir;
102 	if (dir.mkdir(m_rootDir) < 0) {
103 		info().critical(tr("Cannot create dir %1").arg(m_rootDir));
104 		return -1;
105 	}
106 
107 	if (create_peakfiles_dir() < 0) {
108 		return -1;
109 	}
110 
111 	if (create_audiosources_dir() < 0) {
112 		return -1;
113 	}
114 
115 	if (pm().create_projectfilebackup_dir(m_rootDir) < 0) {
116 		return -1;
117 	}
118 
119 	for (int i=0; i< sheetcount; i++) {
120 		Sheet* sheet = new Sheet(this, numtracks);
121 		m_sheets.append(sheet);
122 		sheet->connect_to_audiodevice();
123 	}
124 
125 	if (m_sheets.size()) {
126 		set_current_sheet(m_sheets.first()->get_id());
127 	}
128 
129 	m_id = create_id();
130 	m_importDir = QDir::homePath();
131 
132 	info().information(tr("Created new Project %1").arg(m_title));
133 	return 1;
134 }
135 
create_audiosources_dir()136 int Project::create_audiosources_dir()
137 {
138 	QDir dir;
139 	if (dir.mkdir(m_sourcesDir) < 0) {
140 		info().critical(tr("Cannot create dir %1").arg(m_sourcesDir));
141 		return -1;
142 	}
143 
144 	return 1;
145 
146 }
147 
create_peakfiles_dir()148 int Project::create_peakfiles_dir()
149 {
150 	QDir dir;
151 	QString peaksDir = m_rootDir + "/peakfiles/";
152 
153 	if (dir.mkdir(peaksDir) < 0) {
154 		info().critical(tr("Cannot create dir %1").arg(peaksDir));
155 		return -1;
156 	}
157 
158 	return 1;
159 }
160 
load(QString projectfile)161 int Project::load(QString projectfile)
162 {
163 	PENTER;
164 	QDomDocument doc("Project");
165 
166 	QFile file;
167 	QString filename;
168 
169 	if (projectfile.isEmpty()) {
170 		filename = m_rootDir + "/project.tpf";
171 		file.setFileName(filename);
172 	} else {
173 		filename = projectfile;
174 		file.setFileName(filename);
175 	}
176 
177 	if (!file.open(QIODevice::ReadOnly)) {
178 		m_errorString = tr("Project %1: Cannot open project.tpf file! (Reason: %2)").arg(m_title).arg(file.errorString());
179 		info().critical(m_errorString);
180 		return PROJECT_FILE_COULD_NOT_BE_OPENED;
181 	}
182 
183 	// Check if important directories still exist!
184 	QDir dir;
185 	if (!dir.exists(m_rootDir + "/peakfiles")) {
186 		create_peakfiles_dir();
187 	}
188 	if (!dir.exists(m_rootDir + "/audiosources")) {
189 		create_audiosources_dir();
190 	}
191 
192 
193 	// Start setting and parsing the content of the xml file
194 	QString errorMsg;
195 	if (!doc.setContent(&file, &errorMsg)) {
196 		m_errorString = tr("Project %1: Failed to parse project.tpf file! (Reason: %2)").arg(m_title).arg(errorMsg);
197 		info().critical(m_errorString);
198 		return SETTING_XML_CONTENT_FAILED;
199 	}
200 
201 	QDomElement docElem = doc.documentElement();
202 	QDomNode propertiesNode = docElem.firstChildElement("Properties");
203 	QDomElement e = propertiesNode.toElement();
204 
205 	if (e.attribute("projectfileversion", "-1").toInt() != PROJECT_FILE_VERSION) {
206 		m_errorString = tr("Project File Version does not match, unable to load Project!");
207 		info().warning(m_errorString);
208 		return PROJECT_FILE_VERSION_MISMATCH;
209 	}
210 
211 	m_title = e.attribute( "title", "" );
212 	engineer = e.attribute( "engineer", "" );
213 	m_description = e.attribute( "description", "No description set");
214 	m_discid = e.attribute( "discId", "" );
215 	m_upcEan = e.attribute( "upc_ean", "" );
216 	m_genre = e.attribute( "genre", "" ).toInt();
217 	m_performer = e.attribute( "performer", "" );
218 	m_arranger = e.attribute( "arranger", "" );
219 	m_songwriter = e.attribute( "songwriter", "" );
220 	m_message = e.attribute( "message", "" );
221 	m_rate = e.attribute( "rate", "" ).toInt();
222 	m_bitDepth = e.attribute( "bitdepth", "" ).toInt();
223 	m_id = e.attribute("id", "0").toLongLong();
224 	if (m_id == 0) {
225 		m_id = create_id();
226 	}
227 	m_importDir = e.attribute("importdir", QDir::homePath());
228 
229 
230 	// Load all the AudioSources for this project
231 	QDomNode asmNode = docElem.firstChildElement("ResourcesManager");
232 	m_resourcesManager->set_state(asmNode);
233 
234 
235 	QDomNode sheetsNode = docElem.firstChildElement("Sheets");
236 	QDomNode sheetNode = sheetsNode.firstChild();
237 
238 	// Load all the Sheets
239 	while(!sheetNode.isNull())
240 	{
241 		Sheet* sheet = new Sheet(this, sheetNode);
242 		Command::process_command(add_sheet(sheet, false));
243 		sheetNode = sheetNode.nextSibling();
244 	}
245 
246 	qint64 id = e.attribute("currentsheetid", "0" ).toLongLong();
247 
248 	if ( id == 0) {
249 		if (m_sheets.size()) {
250 			id = m_sheets.first()->get_id();
251 		}
252 	}
253 
254 	set_current_sheet(id);
255 
256 	info().information( tr("Project %1 loaded").arg(m_title) );
257 
258 	emit projectLoadFinished();
259 
260 	return 1;
261 }
262 
263 
save(bool autosave)264 int Project::save(bool autosave)
265 {
266 	PENTER;
267 	QDomDocument doc("Project");
268 	QString fileName = m_rootDir + "/project.tpf";
269 
270 	QFile data( fileName );
271 
272 	if (!data.open( QIODevice::WriteOnly ) ) {
273 		QString errorstring = FileHelper::fileerror_to_string(data.error());
274 		info().critical( tr("Couldn't open Project properties file for writing! (File %1. Reason: %2)").arg(fileName).arg(errorstring) );
275 		return -1;
276 	}
277 
278 	get_state(doc);
279 	QTextStream stream(&data);
280 	doc.save(stream, 4);
281 	data.close();
282 
283 	if (!autosave) {
284 		info().information( tr("Project %1 saved ").arg(m_title) );
285 	}
286 
287 	pm().start_incremental_backup(m_title);
288 
289 	return 1;
290 }
291 
292 
get_state(QDomDocument doc,bool istemplate)293 QDomNode Project::get_state(QDomDocument doc, bool istemplate)
294 {
295 	PENTER;
296 
297 	QDomElement projectNode = doc.createElement("Project");
298 	QDomElement properties = doc.createElement("Properties");
299 
300 	properties.setAttribute("title", m_title);
301 	properties.setAttribute("engineer", engineer);
302 	properties.setAttribute("description", m_description);
303 	properties.setAttribute("discId", m_discid );
304 	properties.setAttribute("upc_ean", m_upcEan);
305 	properties.setAttribute("genre", QString::number(m_genre));
306 	properties.setAttribute("performer", m_performer);
307 	properties.setAttribute("arranger", m_arranger);
308 	properties.setAttribute("songwriter", m_songwriter);
309 	properties.setAttribute("message", m_message);
310 	properties.setAttribute("currentsheetid", m_currentSheetId);
311 	properties.setAttribute("rate", m_rate);
312 	properties.setAttribute("bitdepth", m_bitDepth);
313 	properties.setAttribute("projectfileversion", PROJECT_FILE_VERSION);
314 	if (! istemplate) {
315 		properties.setAttribute("id", m_id);
316 	} else {
317 		properties.setAttribute("title", "Template Project File!!");
318 	}
319 
320 	properties.setAttribute("importdir", m_importDir);
321 
322 	projectNode.appendChild(properties);
323 
324 	doc.appendChild(projectNode);
325 
326 	// Get the AudioSources Node, and append
327 	if (! istemplate) {
328 		projectNode.appendChild(m_resourcesManager->get_state(doc));
329 	}
330 
331 	// Get all the Sheets
332 	QDomNode sheetsNode = doc.createElement("Sheets");
333 
334 	foreach(Sheet* sheet, m_sheets) {
335 		sheetsNode.appendChild(sheet->get_state(doc, istemplate));
336 	}
337 
338 	projectNode.appendChild(sheetsNode);
339 
340 	return projectNode;
341 }
342 
343 
set_title(const QString & title)344 void Project::set_title(const QString& title)
345 {
346 	if (title == m_title) {
347 		// do nothing if the title is the same as the current one
348 		return;
349 	}
350 
351 	if (pm().project_exists(title)) {
352 		info().critical(tr("Project with title '%1' allready exists, not setting new title!").arg(title));
353 		return;
354 	}
355 
356 	QString newrootdir = config().get_property("Project", "directory", "/directory/unknown/").toString() + "/" + title;
357 
358 	QDir dir(m_rootDir);
359 
360 	if ( ! dir.exists() ) {
361 		info().critical(tr("Project directory %1 no longer exists, did you rename it? "
362 				"Shame on you! Please undo that, and come back later to rename your Project...").arg(m_rootDir));
363 		return;
364 	}
365 
366 	m_title = title;
367 
368 	save();
369 
370 	if (pm().rename_project_dir(m_rootDir, newrootdir) < 0 ) {
371 		return;
372 	}
373 
374 	QMessageBox::information( 0,
375 			tr("Traverso - Information"),
376 			tr("Project title changed, Project will to be reloaded to ensure proper operation"),
377 			QMessageBox::Ok);
378 
379 	pm().load_renamed_project(m_title);
380 }
381 
382 
set_engineer(const QString & pEngineer)383 void Project::set_engineer(const QString& pEngineer)
384 {
385 	engineer=pEngineer;
386 }
387 
set_description(const QString & des)388 void Project::set_description(const QString& des)
389 {
390 	m_description = des;
391 }
392 
set_discid(const QString & pId)393 void Project::set_discid(const QString& pId)
394 {
395 	m_discid = pId;
396 }
397 
set_performer(const QString & pPerformer)398 void Project::set_performer(const QString& pPerformer)
399 {
400 	m_performer = pPerformer;
401 }
402 
set_arranger(const QString & pArranger)403 void Project::set_arranger(const QString& pArranger)
404 {
405 	m_arranger = pArranger;
406 }
407 
set_songwriter(const QString & sw)408 void Project::set_songwriter(const QString& sw)
409 {
410 	m_songwriter = sw;
411 }
412 
set_message(const QString & pMessage)413 void Project::set_message(const QString& pMessage)
414 {
415 	m_message = pMessage;
416 }
417 
set_upc_ean(const QString & pUpc)418 void Project::set_upc_ean(const QString& pUpc)
419 {
420 	m_upcEan = pUpc;
421 }
422 
set_genre(int pGenre)423 void Project::set_genre(int pGenre)
424 {
425 	m_genre = pGenre;
426 }
427 
has_changed()428 bool Project::has_changed()
429 {
430 	foreach(Sheet* sheet, m_sheets) {
431 		if(sheet->is_changed())
432 			return true;
433 	}
434 	return false;
435 }
436 
437 
add_sheet(Sheet * sheet,bool historable)438 Command* Project::add_sheet(Sheet* sheet, bool historable)
439 {
440 	PENTER;
441 
442 	AddRemove* cmd;
443 	cmd = new AddRemove(this, sheet, historable, 0,
444 		"private_add_sheet(Sheet*)", "sheetAdded(Sheet*)",
445        		"private_remove_sheet(Sheet*)", "sheetRemoved(Sheet*)",
446        		tr("Sheet %1 added").arg(sheet->get_title()));
447 
448 	cmd->set_instantanious(true);
449 
450 	return cmd;
451 }
452 
453 
set_current_sheet(qint64 id)454 void Project::set_current_sheet(qint64 id)
455 {
456 	PENTER;
457 
458 	if (m_currentSheetId == id) {
459 		return;
460 	}
461 
462 	Sheet* newcurrent = 0;
463 
464 	foreach(Sheet* sheet, m_sheets) {
465 		if (sheet->get_id() == id) {
466 			newcurrent = sheet;
467 			break;
468 		}
469 	}
470 
471 	if (!newcurrent) {
472 		info().information( tr("Sheet '%1' doesn't exist!").arg(id) );
473 		emit currentSheetChanged(0);
474 		return;
475 	}
476 
477 	m_currentSheetId=id;
478 
479 	emit currentSheetChanged(newcurrent);
480 }
481 
482 
get_current_sheet() const483 Sheet* Project::get_current_sheet() const
484 {
485 	Sheet* current = 0;
486 
487 	foreach(Sheet* sheet, m_sheets) {
488 		if (sheet->get_id() == m_currentSheetId) {
489 			current = sheet;
490 			break;
491 		}
492 	}
493 
494 	return current;
495 }
496 
497 
get_sheet(qint64 id) const498 Sheet* Project::get_sheet(qint64 id) const
499 {
500 	Sheet* current = 0;
501 
502 	foreach(Sheet* sheet, m_sheets) {
503 		if (sheet->get_id() == id) {
504 			current = sheet;
505 			break;
506 		}
507 	}
508 
509 	return current;
510 }
511 
512 
remove_sheet(Sheet * sheet,bool historable)513 Command* Project::remove_sheet(Sheet* sheet, bool historable)
514 {
515 	AddRemove* cmd;
516 	cmd = new AddRemove(this, sheet, historable, 0,
517 		"private_remove_sheet(Sheet*)", "sheetRemoved(Sheet*)",
518 		"private_add_sheet(Sheet*)", "sheetAdded(Sheet*)",
519 		tr("Remove Sheet %1").arg(sheet->get_title()));
520 
521 	cmd->set_instantanious(true);
522 
523 	return cmd;
524 }
525 
526 
export_project(ExportSpecification * spec)527 int Project::export_project(ExportSpecification* spec)
528 {
529 	PENTER;
530 
531 	if (!m_exportThread) {
532 		m_exportThread = new ExportThread(this);
533 	}
534 
535 	if (m_exportThread->isRunning()) {
536 		info().warning(tr("Export already in progress, cannot start it twice!"));
537 		return -1;
538 	}
539 
540 	QDir dir(spec->exportdir);
541 	if (!spec->exportdir.isEmpty() && !dir.exists()) {
542 		if (!dir.mkdir(spec->exportdir)) {
543 			info().warning(tr("Unable to create export directory! Please check permissions for this directory: %1").arg(spec->exportdir));
544 			return -1;
545 		}
546 	}
547 
548 	spec->progress = 0;
549 	spec->running = true;
550 	spec->stop = false;
551 	spec->breakout = false;
552 
553 	m_exportThread->set_specification(spec);
554 	m_exportThread->start();
555 
556 	return 0;
557 }
558 
start_export(ExportSpecification * spec)559 int Project::start_export(ExportSpecification* spec)
560 {
561 	PMESG("Starting export, rate is %d bitdepth is %d", spec->sample_rate, spec->data_width );
562 
563 	spec->blocksize = 32768;
564 
565 	spec->dataF = new audio_sample_t[spec->blocksize * spec->channels];
566 	audio_sample_t* readbuffer = new audio_sample_t[spec->blocksize * spec->channels];
567 
568 	overallExportProgress = renderedSheets = 0;
569 	sheetsToRender.clear();
570 
571 	if (spec->allSheets) {
572 		foreach(Sheet* sheet, m_sheets) {
573 			sheetsToRender.append(sheet);
574 		}
575 	} else {
576 		Sheet* sheet = get_current_sheet();
577 		if (sheet) {
578 			sheetsToRender.append(sheet);
579 		}
580 	}
581 
582 	foreach(Sheet* sheet, sheetsToRender) {
583 		PMESG("Starting export for sheet %lld", sheet->get_id());
584 		emit exportStartedForSheet(sheet);
585 		spec->resumeTransport = false;
586 		spec->resumeTransportLocation = sheet->get_transport_location();
587 		sheet->readbuffer = readbuffer;
588 
589 		if (spec->normalize) {
590 			spec->peakvalue = 0.0;
591 			spec->renderpass = ExportSpecification::CALC_NORM_FACTOR;
592 
593 
594 			if (sheet->prepare_export(spec) < 0) {
595 				PERROR("Failed to prepare sheet for export");
596 				continue;
597 			}
598 
599 			while(sheet->render(spec) > 0) {}
600 
601 			spec->normvalue = (1.0 - FLT_EPSILON) / spec->peakvalue;
602 
603 			if (spec->peakvalue > 1.0) {
604 				info().critical(tr("Detected clipping in exported audio! (%1)")
605 						.arg(coefficient_to_dbstring(spec->peakvalue)));
606 			}
607 
608 			if (!spec->breakout) {
609 				info().information(tr("calculated norm factor: %1").arg(coefficient_to_dbstring(spec->normvalue)));
610 			}
611 		}
612 
613 		spec->renderpass = ExportSpecification::WRITE_TO_HARDDISK;
614 
615 		if (sheet->prepare_export(spec) < 0) {
616 			PERROR("Failed to prepare sheet for export");
617 			break;
618 		}
619 
620 		while(sheet->render(spec) > 0) {}
621 
622 		if (!QMetaObject::invokeMethod(sheet, "set_transport_pos",  Qt::QueuedConnection, Q_ARG(TimeRef, spec->resumeTransportLocation))) {
623 			printf("Invoking Sheet::set_transport_pos() failed\n");
624 		}
625 		if (spec->resumeTransport) {
626 			if (!QMetaObject::invokeMethod(sheet, "start_transport",  Qt::QueuedConnection)) {
627 				printf("Invoking Sheet::start_transport() failed\n");
628 			}
629 		}
630 		if (spec->breakout) {
631 			break;
632 		}
633 		renderedSheets++;
634 	}
635 
636 	PMESG("Export Finished");
637 
638 	spec->running = false;
639 	overallExportProgress = 0;
640 
641 	delete [] spec->dataF;
642 	delete [] readbuffer;
643 	spec->dataF = 0;
644 
645 	emit exportFinished();
646 
647 	return 1;
648 }
649 
create_cdrdao_toc(ExportSpecification * spec)650 int Project::create_cdrdao_toc(ExportSpecification* spec)
651 {
652 	QList<Sheet* > sheets;
653 	QString filename = spec->exportdir;
654 
655 	if (spec->allSheets) {
656 		foreach(Sheet* sheet, m_sheets) {
657 			sheets.append(sheet);
658 		}
659 		// filename of the toc file is "project-name.toc"
660 		filename += get_title() + ".toc";
661 	} else {
662 		Sheet* sheet = get_current_sheet();
663 		if (!sheet) {
664 			return -1;
665 		}
666 		sheets.append(sheet);
667 	}
668 
669 	QString output;
670 
671 	output += "CD_DA\n\n";
672 	output += "CD_TEXT {\n";
673 
674 	output += "  LANGUAGE_MAP {\n    0 : EN\n  }\n\n";
675 
676 	output += "  LANGUAGE 0 {\n";
677 	output += "    TITLE \"" + get_title() +  "\"\n";
678 	output += "    PERFORMER \"" + get_performer() + "\"\n";
679 	output += "    DISC_ID \"" + get_discid() + "\"\n";
680 	output += "    UPC_EAN \"" + get_upc_ean() + "\"\n\n";
681 
682 	output += "    ARRANGER \"" + get_arranger() + "\"\n";
683 	output += "    SONGWRITER \"" + get_songwriter() + "\"\n";
684 	output += "    MESSAGE \"" + get_message() + "\"\n";
685 	output += "    GENRE \"" + QString::number(get_genre()) + "\"\n  }\n}\n\n";
686 
687 
688 	bool pregap = true;
689 	spec->renderpass = ExportSpecification::CREATE_CDRDAO_TOC;
690 
691 	foreach(Sheet* sheet, sheets) {
692 		if (sheet->prepare_export(spec) < 0) {
693 			return -1;
694 		}
695 		output += sheet->get_cdrdao_tracklist(spec, pregap);
696 		pregap = false; // only add the pregap at the first sheet
697 	}
698 
699 
700 	if (spec->writeToc) {
701 		if (!spec->allSheets) {
702 			// filename of the toc file is "sheet-name.toc"
703 			filename += spec->basename + ".toc";
704 		}
705 
706 		spec->tocFileName = filename;
707 
708 		QFile file(filename);
709 
710 		if (file.open(QFile::WriteOnly)) {
711 			printf("Saving cdrdao toc-file to %s\n", QS_C(spec->tocFileName));
712 			QTextStream out(&file);
713 			out << output;
714 			file.close();
715 		}
716 	}
717 
718 	spec->cdrdaoToc = output;
719 
720 	return 1;
721 }
722 
select()723 Command* Project::select()
724 {
725 	int index = ie().collected_number();
726 	if (index <= m_sheets.size() && index > 0) {
727 		set_current_sheet(m_sheets.at(index - 1)->get_id());
728 	}
729 	return (Command*) 0;
730 }
731 
get_rate() const732 int Project::get_rate( ) const
733 {
734 	// FIXME: Projects should eventually just use the universal samplerate
735 	if (m_useResampling) {
736 		return audiodevice().get_sample_rate();
737 	}
738 
739 	return m_rate;
740 }
741 
get_bitdepth() const742 int Project::get_bitdepth( ) const
743 {
744 	return m_bitDepth;
745 }
746 
set_sheet_export_progress(int progress)747 void Project::set_sheet_export_progress(int progress)
748 {
749 	overallExportProgress = (progress / sheetsToRender.count()) +
750 			(renderedSheets * (100 / sheetsToRender.count()) );
751 
752 	emit sheetExportProgressChanged(progress);
753 	emit overallExportProgressChanged(overallExportProgress);
754 }
755 
get_sheets() const756 QList<Sheet* > Project::get_sheets( ) const
757 {
758 	return m_sheets;
759 }
760 
get_sheet_index(qint64 id) const761 int Project::get_sheet_index(qint64 id) const
762 {
763 	for (int i=0; i<m_sheets.size(); ++i) {
764 		if (m_sheets.at(i)->get_id() == id) {
765 			return i + 1;
766 		}
767 	}
768 
769 	return 0;
770 }
771 
772 
get_current_sheet_id() const773 int Project::get_current_sheet_id( ) const
774 {
775 	return m_currentSheetId;
776 }
777 
get_num_sheets() const778 int Project::get_num_sheets( ) const
779 {
780 	return m_sheets.size();
781 }
782 
get_title() const783 QString Project::get_title( ) const
784 {
785 	return m_title;
786 }
787 
get_engineer() const788 QString Project::get_engineer( ) const
789 {
790 	return engineer;
791 }
792 
get_description() const793 QString Project::get_description() const
794 {
795 	return m_description;
796 }
797 
get_discid() const798 QString Project::get_discid() const
799 {
800 	return m_discid;
801 }
802 
get_performer() const803 QString Project::get_performer() const
804 {
805 	return m_performer;
806 }
807 
get_arranger() const808 QString Project::get_arranger() const
809 {
810 	return m_arranger;
811 }
812 
get_songwriter() const813 QString Project::get_songwriter() const
814 {
815 	return m_songwriter;
816 }
817 
get_message() const818 QString Project::get_message() const
819 {
820 	return m_message;
821 }
822 
get_upc_ean() const823 QString Project::get_upc_ean() const
824 {
825 	return m_upcEan;
826 }
827 
get_genre()828 int Project::get_genre()
829 {
830 	return m_genre;
831 }
832 
get_root_dir() const833 QString Project::get_root_dir( ) const
834 {
835 	return m_rootDir;
836 }
837 
get_audiosources_dir() const838 QString Project::get_audiosources_dir() const
839 {
840 	return m_rootDir + "/audiosources/";
841 }
842 
get_audiosource_manager() const843 ResourcesManager * Project::get_audiosource_manager( ) const
844 {
845 	return m_resourcesManager;
846 }
847 
848 
private_add_sheet(Sheet * sheet)849 void Project::private_add_sheet(Sheet * sheet)
850 {
851 	PENTER;
852 	m_sheets.append(sheet);
853 	sheet->connect_to_audiodevice();
854 
855 	set_current_sheet(sheet->get_id());
856 }
857 
private_remove_sheet(Sheet * sheet)858 void Project::private_remove_sheet(Sheet * sheet)
859 {
860 	PENTER;
861 	m_sheets.removeAll(sheet);
862 
863 	if (m_sheets.isEmpty()) {
864 		m_currentSheetId = -1;
865 	}
866 
867 	qint64 newcurrent = 0;
868 
869 	if (m_sheets.size() > 0) {
870 		newcurrent = m_sheets.last()->get_id();
871 	}
872 
873 	set_current_sheet(newcurrent);
874 
875 	sheet->disconnect_from_audiodevice();
876 }
877 
get_import_dir() const878 QString Project::get_import_dir() const
879 {
880 	return m_importDir;
881 }
882 
set_import_dir(const QString & dir)883 void Project::set_import_dir(const QString& dir)
884 {
885 	m_importDir = dir;
886 }
887 
is_save_to_close() const888 bool Project::is_save_to_close() const
889 {
890 	if (is_recording()) {
891 		QMessageBox::information( 0,
892 				tr("Traverso - Information"),
893 				tr("You're still recording, please stop recording first to be able to exit the application!"),
894 				QMessageBox::Ok);
895 		return false;
896 	}
897 	return true;
898 }
899 
is_recording() const900 bool Project::is_recording() const
901 {
902 	foreach(Sheet* sheet, m_sheets) {
903 		if (sheet->is_recording() && sheet->is_transport_rolling()) {
904 			return true;
905 		}
906 	}
907 	return false;
908 }
909 
910