1 /***************************************************************************
2  *                                                                         *
3  *   LinuxSampler - modular, streaming capable sampler                     *
4  *                                                                         *
5  *   Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck    *
6  *   Copyright (C) 2005 - 2020 Christian Schoenebeck                       *
7  *   Copyright (C) 2009 - 2012 Grigor Iliev                                *
8  *   Copyright (C) 2012 - 2016 Andreas Persson                             *
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  *   This program is distributed in the hope that it will be useful,       *
16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
18  *   GNU General Public License for more details.                          *
19  *                                                                         *
20  *   You should have received a copy of the GNU General Public License     *
21  *   along with this program; if not, write to the Free Software           *
22  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
23  *   MA  02111-1307  USA                                                   *
24  ***************************************************************************/
25 
26 #include "EngineChannel.h"
27 #include "Engine.h"
28 
29 namespace LinuxSampler { namespace sfz {
EngineChannel()30     EngineChannel::EngineChannel() {
31         for(int i = 0; i < 128; i++) PressedKeys[i] = false;
32         LastKey = LastKeySwitch = -1;
33         AddMidiKeyboardListener(this);
34     }
35 
~EngineChannel()36     EngineChannel::~EngineChannel() {
37         DisconnectAudioOutputDevice();
38         RemoveMidiKeyboardListener(this);
39         // In case the channel was removed before the instrument was
40         // fully loaded, try to give back instrument again (see bug #113)
41         InstrumentChangeCmd< ::sfz::Region, ::sfz::Instrument>& cmd = ChangeInstrument(NULL);
42         if (cmd.pInstrument) {
43             Engine::instruments.HandBack(cmd.pInstrument, this);
44         }
45         ///////
46     }
47 
GetEngineFormat()48     AbstractEngine::Format EngineChannel::GetEngineFormat() { return AbstractEngine::SFZ; }
49 
50     /** This method is not thread safe! */
ResetInternal(bool bResetEngine)51     void EngineChannel::ResetInternal(bool bResetEngine) {
52         CurrentKeyDimension = 0;
53         EngineChannelBase<Voice, ::sfz::Region, ::sfz::Instrument>::ResetInternal(bResetEngine);
54         for(int i = 0; i < 128; i++) PressedKeys[i] = false;
55     }
56 
57     /**
58      *  Will be called by the MIDIIn Thread to signal that a program
59      *  change should be performed. As a program change isn't
60      *  real-time safe, the actual change is performed by the disk
61      *  thread.
62      *
63      *  @param Program     - MIDI program change number
64      */
SendProgramChange(uint8_t Program)65     void EngineChannel::SendProgramChange(uint8_t Program) {
66         SetMidiProgram(Program);
67         Engine* engine = dynamic_cast<Engine*>(pEngine);
68         if(engine == NULL) return;
69 
70         if(engine->GetDiskThread()) {
71             uint32_t merged = (GetMidiBankMsb() << 16) | (GetMidiBankLsb() << 8) | Program;
72             engine->GetDiskThread()->OrderProgramChange(merged, this);
73         } else {
74             // TODO:
75         }
76     }
77 
78     /**
79      * Load an instrument from a .sfz file. PrepareLoadInstrument() has to
80      * be called first to provide the information which instrument to load.
81      * This method will then actually start to load the instrument and block
82      * the calling thread until loading was completed.
83      *
84      * @see PrepareLoadInstrument()
85      */
LoadInstrument()86     void EngineChannel::LoadInstrument() {
87         InstrumentResourceManager* pInstrumentManager = dynamic_cast<InstrumentResourceManager*>(pEngine->GetInstrumentManager());
88 
89         // make sure we don't trigger any new notes with an old
90         // instrument
91         InstrumentChangeCmd< ::sfz::Region, ::sfz::Instrument>& cmd = ChangeInstrument(0);
92         if (cmd.pInstrument) {
93             // give old instrument back to instrument manager, but
94             // keep the dimension regions and samples that are in use
95             pInstrumentManager->HandBackInstrument(cmd.pInstrument, this, cmd.pRegionsInUse);
96         }
97         if (cmd.pScript) {
98             // give old instrument script back to instrument resource manager
99             cmd.pScript->resetAll();
100         }
101         cmd.pRegionsInUse->clear();
102 
103         // delete all key groups
104         DeleteGroupEventLists();
105 
106         // request sfz instrument from instrument manager
107         ::sfz::Instrument* newInstrument;
108         try {
109             InstrumentManager::instrument_id_t instrid;
110             instrid.FileName  = InstrumentFile;
111             instrid.Index     = InstrumentIdx;
112 
113             newInstrument = pInstrumentManager->Borrow(instrid, this);
114             if (!newInstrument) {
115                 throw InstrumentManagerException("resource was not created");
116             }
117 
118             // if requested by set_ccN opcode in sfz file, set initial CC values
119             for (std::map<uint8_t,uint8_t>::const_iterator itCC = newInstrument->initialCCValues.begin();
120                  itCC != newInstrument->initialCCValues.end(); ++itCC)
121             {
122                 const uint8_t& cc = itCC->first;
123                 uint8_t value = itCC->second;
124                 if (cc >= CTRL_TABLE_SIZE) continue;
125                 if ((cc < 128 || cc == CTRL_TABLE_IDX_AFTERTOUCH) && value > 127) value = 127;
126                 ControllerTable[cc] = value;
127             }
128 
129             if (newInstrument->scripts.size() > 1) {
130                 std::cerr << "WARNING: Executing more than one real-time instrument script slot is not implemented yet!\n";
131             }
132             ::sfz::Script* script = (!newInstrument->scripts.empty()) ? &newInstrument->scripts[0] : NULL;
133             if (script) {
134                 String sourceCode = script->GetSourceCode();
135                 std::map<String,String> patchVars; //TODO: we need to invent some new sfz opcode(s) for 'patch' script variables
136                 LoadInstrumentScript(sourceCode, patchVars);
137             }
138         }
139         catch (InstrumentManagerException e) {
140             InstrumentStat = -3;
141             StatusChanged(true);
142             String msg = "sfz::Engine error: Failed to load instrument, cause: " + e.Message();
143             throw Exception(msg);
144         }
145         catch (::sfz::Exception e) {
146             InstrumentStat = -3;
147             StatusChanged(true);
148             String msg = "sfz::Engine error: Failed to load instrument, cause: " + e.Message();
149             throw Exception(msg);
150         }
151         catch (::std::runtime_error e) {
152             InstrumentStat = -3;
153             StatusChanged(true);
154             String msg = "sfz::Engine error: Failed to load instrument, cause: ";
155             msg += e.what();
156             throw Exception(msg);
157         }
158         catch (...) {
159             InstrumentStat = -4;
160             StatusChanged(true);
161             throw Exception("sfz::Engine error: Failed to load instrument, cause: Unknown exception while trying to parse sfz file.");
162         }
163 
164         // rebuild ActiveKeyGroups map with key groups of current instrument
165         for (std::vector< ::sfz::Region*>::iterator itRegion = newInstrument->regions.begin() ;
166              itRegion != newInstrument->regions.end() ; ++itRegion) {
167             AddGroup((*itRegion)->group);
168             AddGroup((*itRegion)->off_by);
169         }
170 
171         InstrumentIdxName = newInstrument->GetName();
172         InstrumentStat = 100;
173 
174         {
175             InstrumentChangeCmd< ::sfz::Region, ::sfz::Instrument>& cmd =
176                 ChangeInstrument(newInstrument);
177             if (cmd.pScript) {
178                 // give old instrument script back to instrument resource manager
179                 cmd.pScript->resetAll();
180             }
181         }
182 
183         StatusChanged(true);
184     }
185 
ProcessKeySwitchChange(int key)186     void EngineChannel::ProcessKeySwitchChange(int key) { }
187 
PreProcessNoteOn(uint8_t key,uint8_t velocity)188     void EngineChannel::PreProcessNoteOn(uint8_t key, uint8_t velocity) {
189         if(pInstrument != NULL && pInstrument->HasKeySwitchBinding(key)) LastKeySwitch = key;
190         PressedKeys[key] = true;
191     }
192 
PostProcessNoteOn(uint8_t key,uint8_t velocity)193     void EngineChannel::PostProcessNoteOn(uint8_t key, uint8_t velocity) {
194         LastKey = key;
195     }
196 
PreProcessNoteOff(uint8_t key,uint8_t velocity)197     void EngineChannel::PreProcessNoteOff(uint8_t key, uint8_t velocity) {
198         PressedKeys[key] = false;
199     }
200 
201 }} // namespace LinuxSampler::sfz
202