void MIDIInstrumentParameterPanel::slotSelectProgram(int index) { RG_DEBUG << "slotSelectProgram()"; if (!getSelectedInstrument()) return; MidiDevice *md = dynamic_cast<MidiDevice *>(getSelectedInstrument()->getDevice()); if (!md) return; const MidiProgram *prg = &m_programs[index]; // If there has been no change, bail. if (getSelectedInstrument()->getProgramChange() == prg->getProgram()) return; getSelectedInstrument()->setProgramChange(prg->getProgram()); // In Variations mode, select the 0th variation. // In Variations mode, it's very easy to select an "invalid" // program change. I.e. one for which the bank is not valid. Go // from one program/variation to a program that doesn't have that // variation. We need to handle that here by selecting the 0th // variation. That's what the user expects. if (md->getVariationType() == MidiDevice::VariationFromMSB) { MidiBank bank = getSelectedInstrument()->getProgram().getBank(); // Get the list of MSB variations. BankList bankList = md->getBanksByLSB( getSelectedInstrument()->isPercussion(), bank.getLSB()); if (!bankList.empty()) { // Pick the first MSB variation getSelectedInstrument()->setMSB(bankList.front().getMSB()); } } if (md->getVariationType() == MidiDevice::VariationFromLSB) { MidiBank bank = getSelectedInstrument()->getProgram().getBank(); // Get the list of LSB variations. BankList bankList = md->getBanksByMSB( getSelectedInstrument()->isPercussion(), bank.getMSB()); if (!bankList.empty()) { // Pick the first LSB variation getSelectedInstrument()->setLSB(bankList.front().getLSB()); } } getSelectedInstrument()->sendChannelSetup(); // Just one change notification for the two potential changes. // See comments in slotSelectBank() for further discussion. getSelectedInstrument()->changed(); }
MidiDevice::MidiDevice(DeviceId id, InstrumentId ibase, const MidiDevice &dev) : Device(id, dev.getName(), Device::Midi), m_programList(dev.m_programList), m_bankList(dev.m_bankList), m_controlList(0), m_keyMappingList(dev.m_keyMappingList), m_metronome(0), m_direction(dev.getDirection()), m_variationType(dev.getVariationType()), m_librarian(dev.getLibrarian()), m_allocator(new AllocateChannels(ChannelSetup::MIDI)) { createInstruments(ibase); // Populate device and Instrument with Controllers. ControlList::const_iterator cIt = dev.m_controlList.begin(); for(; cIt != dev.m_controlList.end(); ++cIt) { addControlParameter(*cIt, true); } // Create and assign a metronome if required // if (dev.getMetronome()) { m_metronome = new MidiMetronome(*dev.getMetronome()); } generatePresentationList(); }
MidiDevice::MidiDevice(const MidiDevice &dev) : Device(dev.getId(), dev.getName(), dev.getType()), Controllable(), m_programList(dev.m_programList), m_bankList(dev.m_bankList), m_controlList(dev.m_controlList), m_keyMappingList(dev.m_keyMappingList), m_metronome(0), m_direction(dev.getDirection()), m_variationType(dev.getVariationType()), m_librarian(dev.getLibrarian()), m_allocator(new AllocateChannels(ChannelSetup::MIDI)) { // Create and assign a metronome if required // if (dev.getMetronome()) { m_metronome = new MidiMetronome(*dev.getMetronome()); } // Copy the instruments // InstrumentList insList = dev.getAllInstruments(); InstrumentList::iterator iIt = insList.begin(); for (; iIt != insList.end(); ++iIt) { Instrument *newInst = new Instrument(**iIt); newInst->setDevice(this); m_instruments.push_back(newInst); } // generate presentation instruments generatePresentationList(); }
void ModifyDeviceCommand::execute() { Device *device = m_studio->getDevice(m_device); if (!device) { std::cerr << "ERROR: ModifyDeviceCommand::execute(): no such device as " << m_device << std::endl; return; } MidiDevice *midiDevice = dynamic_cast<MidiDevice *>(device); if (!midiDevice) { std::cerr << "ERROR: ModifyDeviceCommand::execute(): device " << m_device << " is not a MIDI device" << std::endl; return; } // Save Original Values for Undo // ??? Really wish we could just m_oldDevice = *(midiDevice). See below. m_oldName = midiDevice->getName(); m_oldBankList = midiDevice->getBanks(); m_oldProgramList = midiDevice->getPrograms(); m_oldControlList = midiDevice->getControlParameters(); m_oldKeyMappingList = midiDevice->getKeyMappings(); m_oldLibrarianName = midiDevice->getLibrarianName(); m_oldLibrarianEmail = midiDevice->getLibrarianEmail(); m_oldVariationType = midiDevice->getVariationType(); InstrumentList instruments = midiDevice->getAllInstruments(); for (size_t i = 0; i < instruments.size(); ++i) { // ??? Preserving just the programs isn't enough. We need // to preserve the rest of the Instrument as well. However, // the auto/fixed channel feature has made it impossible // to safely make copies of Instrument objects. Also, Instrument // has an ID. How should that be handled for undo? ISTM // that we either need to introduce some sort of copyForUndo() // hack to each object, or develop a set of standards for coding // objects that are undo-safe. Sounds like a pretty big project. m_oldInstrumentPrograms.push_back(instruments[i]->getProgram()); } // Make the Changes if (m_changeVariation) midiDevice->setVariationType(m_variationType); if (m_overwrite) { if (m_clearBankAndProgramList) { midiDevice->clearBankList(); midiDevice->clearProgramList(); midiDevice->clearKeyMappingList(); } else { if (m_changeBanks) midiDevice->replaceBankList(m_bankList); if (m_changePrograms) midiDevice->replaceProgramList(m_programList); if (m_changeBanks || m_changePrograms) { // Make sure the instruments make sense. for (size_t i = 0; i < instruments.size(); ++i) { instruments[i]->pickFirstProgram( midiDevice->isPercussionNumber(i)); } } } if (m_changeKeyMappings) { midiDevice->replaceKeyMappingList(m_keyMappingList); } if (m_rename) midiDevice->setName(m_name); midiDevice->setLibrarian(m_librarianName, m_librarianEmail); } else { if (m_clearBankAndProgramList) { midiDevice->clearBankList(); midiDevice->clearProgramList(); } else { if (m_changeBanks) midiDevice->mergeBankList(m_bankList); if (m_changePrograms) midiDevice->mergeProgramList(m_programList); } if (m_changeKeyMappings) { midiDevice->mergeKeyMappingList(m_keyMappingList); } if (m_rename) { std::string mergeName = midiDevice->getName() + std::string("/") + m_name; midiDevice->setName(mergeName); } } //!!! merge option? if (m_changeControls) { midiDevice->replaceControlParameters(m_controlList); } // ??? Instead of this kludge, we should be calling a Studio::hasChanged() // which would then notify all observers (e.g. MIPP) who, in turn, // would update themselves. RosegardenMainWindow::self()->uiUpdateKludge(); }
void MIDIInstrumentParameterPanel::slotSelectBank(int index) { RG_DEBUG << "slotSelectBank() begin..."; if (!getSelectedInstrument()) return; MidiDevice *md = dynamic_cast<MidiDevice *>(getSelectedInstrument()->getDevice()); if (!md) { std::cerr << "WARNING: MIDIInstrumentParameterPanel::slotSelectBank(): No MidiDevice for Instrument " << getSelectedInstrument()->getId() << '\n'; return; } const MidiBank &bank = m_banks[index]; bool change = false; if (md->getVariationType() != MidiDevice::VariationFromLSB) { if (getSelectedInstrument()->getLSB() != bank.getLSB()) { getSelectedInstrument()->setLSB(bank.getLSB()); change = true; } } if (md->getVariationType() != MidiDevice::VariationFromMSB) { if (getSelectedInstrument()->getMSB() != bank.getMSB()) { getSelectedInstrument()->setMSB(bank.getMSB()); change = true; } } // If no change, bail. if (!change) return; // Make sure the Instrument is valid WRT the Device. // If the current bank/program is not valid for this device, fix it. if (!getSelectedInstrument()->isProgramValid()) { // If we're not in variations mode... if (md->getVariationType() == MidiDevice::NoVariations) { // ...go with the first program ProgramList programList = md->getPrograms(bank); if (!programList.empty()) { // Switch to the first program in this bank. getSelectedInstrument()->setProgram(programList.front()); } else { // No programs for this bank. Just go with 0. getSelectedInstrument()->setProgramChange(0); } } else { // We're in variations mode... // This is the three-comboboxes (bank/program/variation) case. // It's an extremely difficult case to handle, so we're just // going to punt and give them the first program/variation in // the bank they just selected. // Get the variation bank list for this bank BankList bankList; if (md->getVariationType() == MidiDevice::VariationFromMSB) { bankList = md->getBanksByLSB( getSelectedInstrument()->isPercussion(), bank.getLSB()); } else { bankList = md->getBanksByMSB( getSelectedInstrument()->isPercussion(), bank.getMSB()); } if (!bankList.empty()) { // Pick the first bank MidiBank firstBank = bankList.front(); // Get the program list ProgramList programList = md->getPrograms(firstBank); if (!programList.empty()) { // Pick the first program getSelectedInstrument()->setProgram(programList.front()); } } // To make the above more complex, we could consider the // case where the Program Change happens to be valid for // some variation bank in the newly selected bank. Then // go with the 0th variation bank that has that program // change. But I think this is complicated enough. } } getSelectedInstrument()->sendChannelSetup(); // This is why changed() isn't called within // the setters. If it were, then each of the above changes would // result in a change notification going out. Worst case, that // would be three change notifications and the first two would be // sent when the Instrument was in an inconsistent state. // Rule: Avoid sending change notifications from setters. // Why? It reduces the number of notifications which improves // performance. It avoids sending notifications when an object's // state is inconsistent. It avoids endless loops. getSelectedInstrument()->changed(); }
void MIDIInstrumentParameterPanel::updateVariationComboBox() { RG_DEBUG << "updateVariationComboBox() begin..."; if (!getSelectedInstrument()) return; MidiDevice *md = dynamic_cast<MidiDevice *>(getSelectedInstrument()->getDevice()); if (!md) { std::cerr << "WARNING: MIDIInstrumentParameterPanel::updateVariationComboBox(): No MidiDevice for Instrument " << getSelectedInstrument()->getId() << '\n'; return; } RG_DEBUG << "updateVariationComboBox(): Variation type is " << md->getVariationType(); if (md->getVariationType() == MidiDevice::NoVariations) { showVariation(false); return; } // Get the variations. bool useMSB = (md->getVariationType() == MidiDevice::VariationFromMSB); MidiByteList variationBanks; if (useMSB) { MidiByte lsb = getSelectedInstrument()->getLSB(); variationBanks = md->getDistinctMSBs(getSelectedInstrument()->isPercussion(), lsb); RG_DEBUG << "updateVariationComboBox(): Have " << variationBanks.size() << " variations for LSB " << lsb; } else { MidiByte msb = getSelectedInstrument()->getMSB(); variationBanks = md->getDistinctLSBs(getSelectedInstrument()->isPercussion(), msb); RG_DEBUG << "updateVariationComboBox(): Have " << variationBanks.size() << " variations for MSB " << msb; } // Convert variationBanks to a ProgramList. ProgramList variations; // For each variation for (size_t i = 0; i < variationBanks.size(); ++i) { // Assemble the program for the variation. MidiBank bank; if (useMSB) { bank = MidiBank(getSelectedInstrument()->isPercussion(), variationBanks[i], getSelectedInstrument()->getLSB()); } else { bank = MidiBank(getSelectedInstrument()->isPercussion(), getSelectedInstrument()->getMSB(), variationBanks[i]); } MidiProgram program(bank, getSelectedInstrument()->getProgramChange()); // Skip any programs without names. if (md->getProgramName(program) == "") continue; variations.push_back(program); } // Compute the current variation. // ??? This might be combined into the previous for loop. int currentVariation = -1; // For each variation for (size_t i = 0; i < variations.size(); ++i) { if (getSelectedInstrument()->getProgram().partialCompare(variations[i])) { currentVariation = i; break; } } // If the variations have changed, repopulate the combobox. if (!partialCompareWithName(variations, m_variations)) { RG_DEBUG << "updateVariationComboBox(): Repopulating the combobox"; // Update the cache. m_variations = variations; // Copy from m_variations to m_variationComboBox. m_variationComboBox->clear(); for (size_t i = 0; i < m_variations.size(); ++i) { std::string programName = md->getProgramName(m_variations[i]); // Pick the correct bank number. MidiBank bank = m_variations[i].getBank(); MidiByte variationBank = useMSB ? bank.getMSB() : bank.getLSB(); m_variationComboBox->addItem(QObject::tr("%1. %2") .arg(variationBank) .arg(QObject::tr(programName.c_str()))); } } // Display the current variation. m_variationComboBox->setCurrentIndex(currentVariation); // Show the variation widgets in either of two cases: // 1. More than one variation is available for this program. // 2. The variation was not in the Device and there is a variation // to choose from. showVariation(m_variations.size() > 1 || (currentVariation == -1 && !m_variations.empty())); m_variationComboBox->setEnabled(getSelectedInstrument()->sendsBankSelect()); }
void MIDIInstrumentParameterPanel::updateProgramComboBox() { RG_DEBUG << "updateProgramComboBox()"; if (!getSelectedInstrument()) return; MidiDevice *md = dynamic_cast<MidiDevice *>(getSelectedInstrument()->getDevice()); if (!md) { std::cerr << "WARNING: MIDIInstrumentParameterPanel::updateProgramComboBox(): No MidiDevice for Instrument " << getSelectedInstrument()->getId() << '\n'; return; } RG_DEBUG << "updateProgramComboBox(): variation type is " << md->getVariationType(); MidiBank bank = getSelectedInstrument()->getProgram().getBank(); ProgramList programs = md->getPrograms0thVariation(getSelectedInstrument()->isPercussion(), bank); // Remove the programs that have no name. programs.erase(std::remove_if(programs.begin(), programs.end(), MIDIInstrumentParameterPanel::hasNoName), programs.end()); // If we've got programs, show the Program widgets. // Why not "show = (programs.size()>1)"? Because that would hide the // program checkbox which would take away the user's ability to // enable/disable program changes. If we do away with the checkbox // in the future, we should re-evaluate this decision. bool show = !programs.empty(); m_programLabel->setVisible(show); m_programCheckBox->setVisible(show); m_programComboBox->setVisible(show); int currentProgram = -1; // Compute the current program. for (unsigned i = 0; i < programs.size(); ++i) { // If the program change is the same... if (getSelectedInstrument()->getProgram().getProgram() == programs[i].getProgram()) { currentProgram = i; break; } } // If the programs have changed, we need to repopulate the combobox. if (!partialCompareWithName(programs, m_programs)) { // Update the cache. m_programs = programs; // Copy from m_programs to m_programComboBox. m_programComboBox->clear(); for (unsigned i = 0; i < m_programs.size(); ++i) { m_programComboBox->addItem(QObject::tr("%1. %2") .arg(m_programs[i].getProgram() + 1) .arg(QObject::tr(m_programs[i].getName().c_str()))); } } m_programComboBox->setEnabled(getSelectedInstrument()->sendsProgramChange()); #if 0 // ??? This is a pretty nifty idea, but unfortunately, it requires // that we maintain a bogus combobox entry. For now, we'll go // with the simpler "unselected" approach. // If the current program was not found... if (currentProgram < 0 && !m_programs.empty()) { // Format program change and add to combobox. MidiByte programChange = getSelectedInstrument()->getProgram().getProgram(); m_programComboBox.addItem(QString::number(programChange + 1)); currentProgram = programs.size(); } #endif // Display the current program. m_programComboBox->setCurrentIndex(currentProgram); }
void MIDIInstrumentParameterPanel::updateBankComboBox() { RG_DEBUG << "updateBankComboBox()"; if (!getSelectedInstrument()) return; MidiDevice *md = dynamic_cast<MidiDevice *>(getSelectedInstrument()->getDevice()); if (!md) { std::cerr << "WARNING: MIDIInstrumentParameterPanel::updateBankComboBox(): No MidiDevice for Instrument " << getSelectedInstrument()->getId() << '\n'; return; } int currentBank = -1; BankList banks; RG_DEBUG << "updateBankComboBox(): Variation type is " << md->getVariationType(); if (md->getVariationType() == MidiDevice::NoVariations) { banks = md->getBanks(getSelectedInstrument()->isPercussion()); // If there are banks to display, show the bank widgets. // Why not showBank(banks.size()>1)? Because that would hide the // bank checkbox which would take away the user's ability to // enable/disable bank selects. If we do away with the checkbox // in the future, we should re-evaluate this decision. showBank(!banks.empty()); // Find the selected bank in the MIDI Device's bank list. for (unsigned int i = 0; i < banks.size(); ++i) { if (getSelectedInstrument()->getProgram().getBank().partialCompare(banks[i])) { currentBank = i; break; } } } else { // Usually in variation mode, the bank widgets will be hidden. // E.g. in GM2, the MSB for all banks is 121 with the variations // in the LSB numbered 0-9. If, however, there were another // MSB, say 122, with some variations in the LSB, this code would // display the Bank combobox to allow selection of the MSB. // If the variations are in the LSB, then the banks are in the MSB // and vice versa. bool useMSB = (md->getVariationType() == MidiDevice::VariationFromLSB); MidiByteList bytes; if (useMSB) { bytes = md->getDistinctMSBs(getSelectedInstrument()->isPercussion()); } else { bytes = md->getDistinctLSBs(getSelectedInstrument()->isPercussion()); } // If more than one bank value is found, show the bank widgets. showBank(bytes.size() > 1); // Load "banks" with the banks and figure out currentBank. if (useMSB) { for (unsigned int i = 0; i < bytes.size(); ++i) { BankList bl = md->getBanksByMSB (getSelectedInstrument()->isPercussion(), bytes[i]); RG_DEBUG << "updateBankComboBox(): Have " << bl.size() << " variations for MSB " << bytes[i]; if (bl.size() == 0) continue; if (getSelectedInstrument()->getMSB() == bytes[i]) { currentBank = banks.size(); } banks.push_back(bl[0]); } } else { for (unsigned int i = 0; i < bytes.size(); ++i) { BankList bl = md->getBanksByLSB (getSelectedInstrument()->isPercussion(), bytes[i]); RG_DEBUG << "updateBankComboBox(): Have " << bl.size() << " variations for LSB " << bytes[i]; if (bl.size() == 0) continue; if (getSelectedInstrument()->getLSB() == bytes[i]) { currentBank = banks.size(); } banks.push_back(bl[0]); } } } // Populate the combobox with bank names. // If we need to repopulate m_bankComboBox if (banks != m_banks) { // Update the cache. m_banks = banks; // Copy from m_banks to m_bankComboBox. m_bankComboBox->clear(); for (BankList::const_iterator i = m_banks.begin(); i != m_banks.end(); ++i) { m_bankComboBox->addItem(QObject::tr(i->getName().c_str())); } } m_bankComboBox->setEnabled(getSelectedInstrument()->sendsBankSelect()); #if 0 // ??? This is a pretty nifty idea, but unfortunately, it requires // that we maintain a bogus combobox entry. For now, we'll go // with the simpler "unselected" approach. // If the current bank was not found... if (currentBank < 0 && !banks.empty()) { // Format bank MSB:LSB and add to combobox. MidiBank bank = getSelectedInstrument()->getProgram().getBank(); QString bankString = QString("%1:%2").arg(bank.getMSB()).arg(bank.getLSB()); m_bankComboBox.addItem(bankString); currentBank = banks.size(); } #endif // If the bank wasn't in the Device, show the bank widgets so // the user can fix it if they want. if (currentBank == -1 && !banks.empty()) showBank(true); // Display the current bank. m_bankComboBox->setCurrentIndex(currentBank); }