1 /** 2 * @file parstore.cpp 3 * @brief Implements the ParStore class. 4 * 5 * 6 * Copyright 2009 - 2017 <qmidiarp-devel@lists.sourceforge.net> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 * MA 02110-1301, USA. 22 */ 23 24 #include "main.h" 25 #include "parstore.h" 26 #include "storagebutton.h" 27 28 #include "pixmaps/filesave.xpm" 29 30 ParStore::ParStore(GlobStore *p_globStore, const QString &name, 31 QAction *p_muteOutAction, QAction *p_deferChangesAction, 32 QWidget *p_parent): globStore(p_globStore) 33 { 34 setParent(p_parent); 35 // when temp.empty is true, restoring from that set is ignored 36 temp.empty = false; 37 temp.muteOut = false; 38 temp.res = 1; 39 temp.size = 0; 40 temp.loopMode = 0; 41 temp.waveForm = 0; 42 temp.portOut = 0; 43 temp.channelOut = 0; 44 temp.chIn = 0; 45 temp.wave.clear(); 46 temp.muteMask.clear(); 47 /* LFO Modules */ 48 temp.ccnumber = -1; 49 temp.ccnumberIn = 0; 50 temp.freq = 0; 51 temp.ampl = 0; 52 temp.offs = 0; 53 /* Seq Modules */ 54 temp.loopMarker = 0; 55 temp.notelen = 0; 56 temp.vel = 0; 57 temp.transp = 0; 58 temp.dispVertIndex = 0; 59 /* Arp Modules */ 60 temp.indexIn0 = 0; 61 temp.indexIn1 = 127; 62 temp.rangeIn0 = 0; 63 temp.rangeIn1 = 127; 64 temp.attack = 0; 65 temp.release = 0; 66 temp.repeatMode = 0; 67 temp.rndTick = 0; 68 temp.rndLen = 0; 69 temp.rndVel = 0; 70 temp.pattern = ""; 71 list.clear(); 72 73 ndc = new Indicator(14, name.at(0)); 74 75 topButton = new QToolButton; 76 topButton->setText(name); 77 topButton->setMinimumSize(QSize(75, 10)); 78 79 muteOutAction = p_muteOutAction; 80 muteOut = new QToolButton; 81 muteOut->setDefaultAction(muteOutAction); 82 muteOut->setMinimumSize(QSize(10, 10)); 83 84 deferChangesAction = p_deferChangesAction; 85 deferChanges = new QToolButton; 86 deferChanges->setDefaultAction(deferChangesAction); 87 deferChanges->setMinimumSize(QSize(10, 10)); 88 89 QHBoxLayout *muteRowLayout = new QHBoxLayout; 90 muteRowLayout->addStretch(); 91 muteRowLayout->addWidget(muteOut); 92 muteRowLayout->addWidget(deferChanges); 93 muteRowLayout->setMargin(0); 94 muteRowLayout->setSpacing(0); 95 96 QVBoxLayout *controlLayout = new QVBoxLayout; 97 controlLayout->addWidget(topButton); 98 controlLayout->addLayout(muteRowLayout); 99 controlLayout->setMargin(0); 100 controlLayout->setSpacing(0); 101 102 QWidget *indicatorBox = new QWidget; 103 QHBoxLayout *indicatorLayout = new QHBoxLayout; 104 indicatorBox->setMinimumHeight(20); 105 indicatorBox->setMinimumWidth(25); 106 indicatorLayout->addWidget(ndc); 107 indicatorLayout->setMargin(2); 108 indicatorLayout->setSpacing(1); 109 indicatorBox->setLayout(indicatorLayout); 110 111 QFrame *topRow = new QFrame; 112 QHBoxLayout *topRowLayout = new QHBoxLayout; 113 topRowLayout->addWidget(indicatorBox); 114 topRowLayout->addLayout(controlLayout); 115 topRowLayout->setSpacing(0); 116 topRowLayout->setMargin(0); 117 topRow->setMinimumSize(QSize(104,46));; 118 topRow->setFrameStyle(QFrame::StyledPanel); 119 topRow->setLayout(topRowLayout); 120 121 QVBoxLayout *buttonLayout = new QVBoxLayout; 122 buttonLayout->addWidget(topRow); 123 124 locContextMenu = new QMenu(this); 125 126 QAction *storeHereAction = new QAction(tr("&Store here"), this); 127 storeHereAction->setProperty("index", list.count()); 128 storeHereAction->setIcon(QPixmap(filesave_xpm)); 129 locContextMenu->addAction(storeHereAction); 130 connect(storeHereAction, SIGNAL(triggered()), this, SLOT(mapStoreSignal())); 131 132 QAction *onlyPatternAction = new QAction(tr("&Act on pattern only"), this); 133 onlyPatternAction->setProperty("index", list.count()); 134 onlyPatternAction->setCheckable(true); 135 locContextMenu->addAction(onlyPatternAction); 136 connect(onlyPatternAction, SIGNAL(toggled(bool)), this, SLOT(updateOnlyPattern(bool))); 137 138 jumpToIndexMenu = new QMenu(tr("When finished"), this); 139 140 jumpToGroup = new QActionGroup(this); 141 connect(jumpToGroup, SIGNAL(triggered(QAction *)) 142 , this, SLOT(mapJumpToGroup(QAction *))); 143 144 jumpToIndexMenu->addAction(new QAction(tr("Stay here"), this)); 145 jumpToIndexMenu->actions().last()->setProperty("index", -2); 146 jumpToIndexMenu->actions().last()->setActionGroup(jumpToGroup); 147 jumpToIndexMenu->actions().last()->setCheckable(true); 148 jumpToIndexMenu->actions().last()->setChecked(true); 149 150 jumpToIndexMenu->addAction(new QAction(tr("Jump back"), this)); 151 jumpToIndexMenu->actions().last()->setProperty("index", -1); 152 jumpToIndexMenu->actions().last()->setActionGroup(jumpToGroup); 153 jumpToIndexMenu->actions().last()->setCheckable(true); 154 155 jumpToIndexMenu->addSeparator()->setText(tr("Jump to:")); 156 157 locContextMenu->addMenu(jumpToIndexMenu); 158 159 for (int l1 = 0; l1 < list.size() - 1; l1++) addLocation(); 160 161 QVBoxLayout *columnLayout = new QVBoxLayout; 162 columnLayout->addLayout(buttonLayout); 163 columnLayout->addStretch(); 164 columnLayout->setMargin(0); 165 columnLayout->setSpacing(0); 166 setLayout(columnLayout); 167 168 globStore->indivButtonLayout->addWidget(this); 169 170 isRestoreMaster = false; 171 restoreRequest = -1; 172 oldRestoreRequest = 0; 173 restoreRunOnce = false; 174 activeStore = 0; 175 currentRequest = 0; 176 dispReqIx = 0; 177 dispReqSelected = 0; 178 needsGUIUpdate = false; 179 } 180 181 void ParStore::writeData(QXmlStreamWriter& xml) 182 { 183 QByteArray tempArray; 184 185 xml.writeStartElement("globalStores"); 186 187 for (int ix = 0; ix < list.size(); ix++) { 188 xml.writeStartElement("parStore"); 189 xml.writeAttribute("ID", QString::number(ix)); 190 xml.writeTextElement("empty", QString::number(list.at(ix).empty)); 191 xml.writeTextElement("muteOut", QString::number(list.at(ix).muteOut)); 192 xml.writeTextElement("res", QString::number(list.at(ix).res)); 193 xml.writeTextElement("size", QString::number(list.at(ix).size)); 194 xml.writeTextElement("loopMode", QString::number(list.at(ix).loopMode)); 195 xml.writeTextElement("waveForm", QString::number(list.at(ix).waveForm)); 196 xml.writeTextElement("portOut", QString::number(list.at(ix).portOut)); 197 xml.writeTextElement("channelOut", QString::number(list.at(ix).channelOut)); 198 xml.writeTextElement("chIn", QString::number(list.at(ix).chIn)); 199 xml.writeTextElement("ccnumber", QString::number(list.at(ix).ccnumber)); 200 xml.writeTextElement("ccnumberIn", QString::number(list.at(ix).ccnumberIn)); 201 xml.writeTextElement("freq", QString::number(list.at(ix).freq)); 202 xml.writeTextElement("ampl", QString::number(list.at(ix).ampl)); 203 xml.writeTextElement("offs", QString::number(list.at(ix).offs)); 204 xml.writeTextElement("loopMarker", QString::number(list.at(ix).loopMarker)); 205 xml.writeTextElement("notelen", QString::number(list.at(ix).notelen)); 206 xml.writeTextElement("vel", QString::number(list.at(ix).vel)); 207 xml.writeTextElement("dispVertical", QString::number(list.at(ix).dispVertIndex)); 208 xml.writeTextElement("transp", QString::number(list.at(ix).transp)); 209 xml.writeTextElement("indexIn0", QString::number(list.at(ix).indexIn0)); 210 xml.writeTextElement("indexIn1", QString::number(list.at(ix).indexIn1)); 211 xml.writeTextElement("rangeIn0", QString::number(list.at(ix).rangeIn0)); 212 xml.writeTextElement("rangeIn1", QString::number(list.at(ix).rangeIn1)); 213 xml.writeTextElement("attack", QString::number(list.at(ix).attack)); 214 xml.writeTextElement("release", QString::number(list.at(ix).release)); 215 xml.writeTextElement("repeatMode", QString::number(list.at(ix).repeatMode)); 216 xml.writeTextElement("rndTick", QString::number(list.at(ix).rndTick)); 217 xml.writeTextElement("rndLen", QString::number(list.at(ix).rndLen)); 218 xml.writeTextElement("rndVel", QString::number(list.at(ix).rndVel)); 219 xml.writeTextElement("pattern", list.at(ix).pattern); 220 221 xml.writeTextElement("jumpTo", QString::number(jumpToList.at(ix))); 222 xml.writeTextElement("onlyPattern", QString::number((int)onlyPatternList.at(ix))); 223 224 tempArray.clear(); 225 int l1 = 0; 226 while (l1 < list.at(ix).muteMask.count()) { 227 tempArray.append(list.at(ix).muteMask.at(l1)); 228 l1++; 229 } 230 xml.writeStartElement("muteMask"); 231 xml.writeTextElement("data", tempArray.toHex()); 232 xml.writeEndElement(); 233 234 tempArray.clear(); 235 l1 = 0; 236 while (l1 < list.at(ix).wave.count()) { 237 tempArray.append(list.at(ix).wave.at(l1).value); 238 l1++; 239 } 240 xml.writeStartElement("wave"); 241 xml.writeTextElement("data", tempArray.toHex()); 242 xml.writeEndElement(); 243 xml.writeEndElement(); 244 } 245 xml.writeEndElement(); 246 } 247 248 void ParStore::readData(QXmlStreamReader& xml) 249 { 250 int ix = 0; 251 int step = 0; 252 int tmpjumpto = -2; 253 int tmponlypattern = 0; 254 255 while (!xml.atEnd()) { 256 xml.readNext(); 257 if (xml.isEndElement()) 258 break; 259 260 if (xml.isStartElement() && (xml.name() == "parStore")) { 261 while (!xml.atEnd()) { 262 xml.readNext(); 263 if (xml.isEndElement()) 264 break; 265 if (xml.name() == "empty") 266 temp.empty = xml.readElementText().toInt(); 267 else if (xml.name() == "muteOut") 268 temp.muteOut = xml.readElementText().toInt(); 269 else if (xml.name() == "res") 270 temp.res = xml.readElementText().toInt(); 271 else if (xml.name() == "size") 272 temp.size = xml.readElementText().toInt(); 273 else if (xml.name() == "loopMode") 274 temp.loopMode = xml.readElementText().toInt(); 275 else if (xml.name() == "waveForm") 276 temp.waveForm = xml.readElementText().toInt(); 277 else if (xml.name() == "portOut") 278 temp.portOut = xml.readElementText().toInt(); 279 else if (xml.name() == "channelOut") 280 temp.channelOut = xml.readElementText().toInt(); 281 else if (xml.name() == "chIn") 282 temp.chIn = xml.readElementText().toInt(); 283 else if (xml.name() == "ccnumber") 284 temp.ccnumber = xml.readElementText().toInt(); 285 else if (xml.name() == "ccnumberIn") 286 temp.ccnumberIn = xml.readElementText().toInt(); 287 else if (xml.name() == "freq") 288 temp.freq = xml.readElementText().toInt(); 289 else if (xml.name() == "ampl") 290 temp.ampl = xml.readElementText().toInt(); 291 else if (xml.name() == "offs") 292 temp.offs = xml.readElementText().toInt(); 293 else if (xml.name() == "vel") 294 temp.vel = xml.readElementText().toInt(); 295 else if (xml.name() == "dispVertical") 296 temp.dispVertIndex = xml.readElementText().toInt(); 297 else if (xml.name() == "transp") 298 temp.transp = xml.readElementText().toInt(); 299 else if (xml.name() == "notelen") 300 temp.notelen = xml.readElementText().toInt(); 301 else if (xml.name() == "loopMarker") 302 temp.loopMarker = xml.readElementText().toInt(); 303 else if (xml.name() == "indexIn0") 304 temp.indexIn0 = xml.readElementText().toInt(); 305 else if (xml.name() == "indexIn1") 306 temp.indexIn1 = xml.readElementText().toInt(); 307 else if (xml.name() == "rangeIn0") 308 temp.rangeIn0 = xml.readElementText().toInt(); 309 else if (xml.name() == "rangeIn1") 310 temp.rangeIn1 = xml.readElementText().toInt(); 311 else if (xml.name() == "attack") 312 temp.attack = xml.readElementText().toInt(); 313 else if (xml.name() == "release") 314 temp.release = xml.readElementText().toInt(); 315 else if (xml.name() == "repeatMode") 316 temp.repeatMode = xml.readElementText().toInt(); 317 else if (xml.name() == "rndTick") 318 temp.rndTick = xml.readElementText().toInt(); 319 else if (xml.name() == "rndLen") 320 temp.rndLen = xml.readElementText().toInt(); 321 else if (xml.name() == "rndVel") 322 temp.rndVel = xml.readElementText().toInt(); 323 else if (xml.name() == "pattern") 324 temp.pattern = xml.readElementText(); 325 else if (xml.name() == "jumpTo") 326 tmpjumpto = xml.readElementText().toInt(); 327 else if (xml.name() == "onlyPattern") 328 tmponlypattern = xml.readElementText().toInt(); 329 else if (xml.isStartElement() && (xml.name() == "muteMask")) { 330 while (!xml.atEnd()) { 331 xml.readNext(); 332 if (xml.isEndElement()) 333 break; 334 if (xml.isStartElement() && (xml.name() == "data")) { 335 temp.muteMask.clear(); 336 QByteArray tmpArray = 337 QByteArray::fromHex(xml.readElementText().toLatin1()); 338 for (int l1 = 0; l1 < tmpArray.count(); l1++) { 339 temp.muteMask.append(tmpArray.at(l1)); 340 } 341 } 342 else skipXmlElement(xml); 343 } 344 } 345 else if (xml.isStartElement() && (xml.name() == "wave")) { 346 while (!xml.atEnd()) { 347 xml.readNext(); 348 if (xml.isEndElement()) 349 break; 350 if (xml.isStartElement() && (xml.name() == "data")) { 351 temp.wave.clear(); 352 QByteArray tmpArray = 353 QByteArray::fromHex(xml.readElementText().toLatin1()); 354 355 if (temp.ccnumberIn >= 0) 356 step = TPQN / lfoResValues[temp.res]; 357 else 358 step = TPQN / seqResValues[temp.res]; 359 360 int lt = 0; 361 Sample sample; 362 for (int l1 = 0; l1 < tmpArray.count(); l1++) { 363 sample.value = tmpArray.at(l1); 364 sample.tick = lt; 365 sample.muted = temp.muteMask.at(l1); 366 temp.wave.append(sample); 367 lt+=step; 368 } 369 } 370 else skipXmlElement(xml); 371 } 372 } 373 else skipXmlElement(xml); 374 } 375 //For compatibility with files stored before all modules got 376 //Note filters: 377 if (!(temp.indexIn0 + temp.indexIn1)) temp.indexIn1 = 127; 378 if (!(temp.rangeIn0 + temp.rangeIn1)) temp.rangeIn1 = 127; 379 tempToList(ix); 380 updateRunOnce(ix, tmpjumpto); 381 onlyPatternList.replace(ix, tmponlypattern); 382 ix++; 383 } 384 } 385 } 386 387 void ParStore::skipXmlElement(QXmlStreamReader& xml) 388 { 389 if (xml.isStartElement()) { 390 qWarning("Unknown Element in XML File: %s",qPrintable(xml.name().toString())); 391 while (!xml.atEnd()) { 392 xml.readNext(); 393 394 if (xml.isEndElement()) 395 break; 396 397 if (xml.isStartElement()) { 398 skipXmlElement(xml); 399 } 400 } 401 } 402 } 403 404 void ParStore::addLocation() 405 { 406 StorageButton *toolButton = new StorageButton(this); 407 toolButton->setText(QString::number(list.count())); 408 toolButton->setStyleSheet("font: 10pt"); 409 toolButton->setProperty("index", list.count()); 410 connect(toolButton, SIGNAL(pressed()), this, SLOT(mapRestoreSignal())); 411 412 toolButton->setContextMenuPolicy(Qt::ContextMenuPolicy(Qt::CustomContextMenu)); 413 connect(toolButton, SIGNAL(customContextMenuRequested(const QPoint &)) 414 , this, SLOT(showLocContextMenu(const QPoint &))); 415 416 jumpToIndexMenu->addAction(new QAction(QString::number(list.count()), this)); 417 jumpToIndexMenu->actions().last()->setActionGroup(jumpToGroup); 418 jumpToIndexMenu->actions().last()->setProperty("index", list.count() - 1); 419 jumpToIndexMenu->actions().last()->setCheckable(true); 420 421 layout()->itemAt(0)->layout()->addWidget(toolButton); 422 jumpToList.append(-2); 423 onlyPatternList.append(false); 424 } 425 426 void ParStore::removeLocation(int ix) 427 { 428 if (ix == -1) ix = list.count() - 1; 429 430 list.removeAt(ix); 431 QWidget *button = layout()->itemAt(0)->layout()->takeAt(ix + 1)->widget(); 432 QAction *action = jumpToIndexMenu->actions().at(ix + 3); 433 delete button; 434 delete action; 435 jumpToList.removeAt(ix); 436 onlyPatternList.removeAt(ix); 437 438 for (int l1 = 0; l1 < jumpToList.count(); l1++) { 439 if (jumpToList.at(l1) >= jumpToList.count()) updateRunOnce(l1, -2); 440 } 441 } 442 443 444 void ParStore::setRestoreRequest(int ix) 445 { 446 restoreRequest = ix; 447 restoreRunOnce = (jumpToList.at(ix) > -2 ); 448 449 setDispState(ix, 2); 450 } 451 452 void ParStore::mapJumpToGroup(QAction *action) 453 { 454 int choice = action->property("index").toInt(); 455 int location = sender()->property("index").toInt(); 456 457 updateRunOnce(location, choice); 458 } 459 460 void ParStore::updateRunOnce(int location, int choice) 461 { 462 if (choice == -2) { //stay here 463 jumpToList.replace(location, -2); 464 setBGColorAt(location + 1, 0); 465 ((StorageButton *)(layout()->itemAt(0)->layout()->itemAt(location + 1) 466 ->widget()))->setSecondText("", 0); 467 } 468 else if (choice == -1) { //jump back to last 469 jumpToList.replace(location, -1); 470 setBGColorAt(location + 1, 3); 471 ((StorageButton *)(layout()->itemAt(0)->layout()->itemAt(location + 1) 472 ->widget()))->setSecondText("<- ", 1); 473 } 474 else if (choice >= 0) { //jump to location 475 jumpToList.replace(location, choice); 476 ((StorageButton *)(layout()->itemAt(0)->layout()->itemAt(location + 1) 477 ->widget()))->setSecondText("-> "+QString::number(choice + 1), 2); 478 setBGColorAt(location + 1, 3); 479 } 480 } 481 482 void ParStore::setBGColorAt(int row, int color) 483 { 484 QString styleSheet; 485 486 if (color == 1) //green 487 styleSheet = "QToolButton { background-color: rgba(50, 255, 50, 40%); }"; 488 else if (color == 2) //yellow 489 styleSheet = "QToolButton { background-color: rgba(150, 255, 150, 10%); }"; 490 else if (color == 3) //blueish 491 styleSheet = "QToolButton { }"; 492 else //no color 493 styleSheet = "QToolButton { }"; 494 495 layout()->itemAt(0)->layout()->itemAt(row)->widget()->setStyleSheet(styleSheet); 496 } 497 498 void ParStore::tempToList(int ix) 499 { 500 if (ix >= list.size()) { 501 list.append(temp); 502 addLocation(); 503 } 504 else { 505 list.replace(ix, temp); 506 } 507 currentRequest = ix; 508 setDispState(ix, 1); 509 } 510 511 void ParStore::mapRestoreSignal() 512 { 513 int ix = sender()->property("index").toInt(); 514 515 setRestoreRequest(ix - 1); 516 } 517 518 void ParStore::mapStoreSignal() 519 { 520 int ix = sender()->property("index").toInt(); 521 522 emit store(ix - 1, false); 523 } 524 525 void ParStore::setDispState(int ix, int selected) 526 { 527 if (selected == 1) { 528 for (int l2 = 1; l2 <= list.count(); l2++) { 529 setBGColorAt(l2, 3 * (jumpToList.at(l2 - 1) > -2)); 530 } 531 setBGColorAt(ix + 1, 1); 532 activeStore = ix; 533 } 534 else if (selected == 2) { 535 setBGColorAt(ix + 1, 2); 536 if (currentRequest != activeStore) { 537 setBGColorAt(currentRequest + 1, 0); 538 } 539 currentRequest = ix; 540 } 541 } 542 543 void ParStore::requestDispState(int ix, int selected) 544 { 545 dispReqIx = ix; 546 dispReqSelected = selected; 547 needsGUIUpdate = true; 548 } 549 550 void ParStore::updateDisplay(int frame, bool reverse) 551 { 552 ndc->updateDraw(); 553 554 if (needsGUIUpdate) { 555 needsGUIUpdate = false; 556 setDispState(dispReqIx, dispReqSelected); 557 } 558 559 if ((restoreRequest >= 0) && !frame) { 560 int req = restoreRequest; 561 setDispState(req, 1); 562 emit restore(req); 563 restoreRequest = -1; 564 if (!restoreRunOnce) { 565 oldRestoreRequest = req; 566 } 567 } 568 569 if ((frame == 1) 570 && (restoreRequest != oldRestoreRequest) 571 && restoreRunOnce 572 && !reverse) { 573 if (jumpToList.at(activeStore) >= 0) { 574 restoreRequest = jumpToList.at(activeStore); 575 oldRestoreRequest = restoreRequest; 576 } 577 else { 578 restoreRequest = oldRestoreRequest; 579 } 580 restoreRunOnce = (jumpToList.at(restoreRequest) > -2); 581 setDispState(restoreRequest, 2); 582 } 583 584 } 585 586 void ParStore::showLocContextMenu(const QPoint &pos) 587 { 588 int senderlocation = sender()->property("index").toInt() - 1; 589 int l1; 590 591 for (l1 = 0; l1 < locContextMenu->actions().count(); l1++) { 592 locContextMenu->actions().at(l1)->setProperty("index", 593 senderlocation + 1); 594 } 595 for (l1 = 0; l1 < list.count(); l1++) { 596 jumpToGroup->actions().at(l1 + 2) 597 ->setDisabled(l1 == senderlocation); 598 } 599 600 locContextMenu->setProperty("index", senderlocation); 601 jumpToGroup->setProperty("index", senderlocation); 602 603 jumpToGroup->actions().at(jumpToList.at(senderlocation) + 2) 604 ->setChecked(true); 605 locContextMenu->actions().at(1)->setChecked(onlyPatternList.at(senderlocation)); 606 locContextMenu->popup(QWidget::mapToGlobal(pos)); 607 } 608 609 void ParStore::updateOnlyPattern(bool on) 610 { 611 onlyPatternList.replace(sender()->property("index").toInt() - 1, on); 612 } 613