1 /*
2 * Hydrogen
3 * Copyright(c) 2017 by Sebastian Moors
4 *
5 * http://www.hydrogen-music.org
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY, without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23 #include <hydrogen/core_action_controller.h>
24 #include <hydrogen/event_queue.h>
25 #include <hydrogen/hydrogen.h>
26 #include <hydrogen/Preferences.h>
27 #include <hydrogen/basics/instrument_list.h>
28 #include <hydrogen/basics/instrument.h>
29 #include <hydrogen/osc_server.h>
30 #include <hydrogen/midi_action.h>
31 #include <hydrogen/midi_map.h>
32
33 #include <hydrogen/IO/AlsaMidiDriver.h>
34 #include <hydrogen/IO/MidiOutput.h>
35
36 namespace H2Core
37 {
38
39 const char* CoreActionController::__class_name = "CoreActionController";
40
41
CoreActionController()42 CoreActionController::CoreActionController() : Object( __class_name ),
43 m_nDefaultMidiFeedbackChannel(0)
44 {
45 //nothing
46 }
47
~CoreActionController()48 CoreActionController::~CoreActionController() {
49 //nothing
50 }
51
setMasterVolume(float masterVolumeValue)52 void CoreActionController::setMasterVolume( float masterVolumeValue )
53 {
54 Hydrogen* pEngine = Hydrogen::get_instance();
55 pEngine->getSong()->set_volume( masterVolumeValue );
56
57 #ifdef H2CORE_HAVE_OSC
58 Action FeedbackAction( "MASTER_VOLUME_ABSOLUTE" );
59 FeedbackAction.setParameter2( QString("%1").arg( masterVolumeValue ) );
60 OscServer::handleAction( &FeedbackAction );
61 #endif
62
63 MidiMap* pMidiMap = MidiMap::get_instance();
64
65 int ccParamValue = pMidiMap->findCCValueByActionType( QString("MASTER_VOLUME_ABSOLUTE"));
66
67 handleOutgoingControlChange( ccParamValue, (masterVolumeValue / 1.5) * 127 );
68 }
69
setStripVolume(int nStrip,float masterVolumeValue)70 void CoreActionController::setStripVolume( int nStrip, float masterVolumeValue )
71 {
72 Hydrogen *pEngine = Hydrogen::get_instance();
73 pEngine->setSelectedInstrumentNumber( nStrip );
74
75 Song *pSong = pEngine->getSong();
76 InstrumentList *instrList = pSong->get_instrument_list();
77
78 Instrument *pInstr = instrList->get( nStrip );
79 pInstr->set_volume( masterVolumeValue );
80
81 #ifdef H2CORE_HAVE_OSC
82 Action FeedbackAction( "STRIP_VOLUME_ABSOLUTE" );
83
84 FeedbackAction.setParameter1( QString("%1").arg( nStrip + 1 ) );
85 FeedbackAction.setParameter2( QString("%1").arg( masterVolumeValue ) );
86 OscServer::handleAction( &FeedbackAction );
87 #endif
88
89 MidiMap* pMidiMap = MidiMap::get_instance();
90
91 int ccParamValue = pMidiMap->findCCValueByActionParam1( QString("STRIP_VOLUME_ABSOLUTE"), QString("%1").arg( nStrip ) );
92
93
94 handleOutgoingControlChange( ccParamValue, (masterVolumeValue / 1.5) * 127 );
95
96 }
97
setMetronomeIsActive(bool isActive)98 void CoreActionController::setMetronomeIsActive( bool isActive )
99 {
100 Preferences::get_instance()->m_bUseMetronome = isActive;
101
102 #ifdef H2CORE_HAVE_OSC
103 Action FeedbackAction( "TOGGLE_METRONOME" );
104
105 FeedbackAction.setParameter1( QString("%1").arg( (int) isActive ) );
106 OscServer::handleAction( &FeedbackAction );
107 #endif
108
109 MidiMap* pMidiMap = MidiMap::get_instance();
110
111 int ccParamValue = pMidiMap->findCCValueByActionType( QString("TOGGLE_METRONOME"));
112
113 handleOutgoingControlChange( ccParamValue, (int) isActive * 127 );
114 }
115
setMasterIsMuted(bool isMuted)116 void CoreActionController::setMasterIsMuted( bool isMuted )
117 {
118 Hydrogen *pEngine = Hydrogen::get_instance();
119 pEngine->getSong()->__is_muted = isMuted;
120
121 #ifdef H2CORE_HAVE_OSC
122 Action FeedbackAction( "MUTE_TOGGLE" );
123
124 FeedbackAction.setParameter1( QString("%1").arg( (int) isMuted ) );
125 OscServer::handleAction( &FeedbackAction );
126 #endif
127
128 MidiMap* pMidiMap = MidiMap::get_instance();
129
130 int ccParamValue = pMidiMap->findCCValueByActionType( QString("MUTE_TOGGLE") );
131
132 handleOutgoingControlChange( ccParamValue, (int) isMuted * 127 );
133 }
134
setStripIsMuted(int nStrip,bool isMuted)135 void CoreActionController::setStripIsMuted( int nStrip, bool isMuted )
136 {
137 Hydrogen *pEngine = Hydrogen::get_instance();
138 Song *pSong = pEngine->getSong();
139 InstrumentList *pInstrList = pSong->get_instrument_list();
140
141 Instrument *pInstr = pInstrList->get( nStrip );
142 pInstr->set_muted( isMuted );
143
144 #ifdef H2CORE_HAVE_OSC
145 Action FeedbackAction( "STRIP_MUTE_TOGGLE" );
146
147 FeedbackAction.setParameter1( QString("%1").arg( nStrip + 1 ) );
148 FeedbackAction.setParameter2( QString("%1").arg( (int) isMuted ) );
149 OscServer::handleAction( &FeedbackAction );
150 #endif
151
152 MidiMap* pMidiMap = MidiMap::get_instance();
153
154 int ccParamValue = pMidiMap->findCCValueByActionParam1( QString("STRIP_MUTE_TOGGLE"), QString("%1").arg( nStrip ) );
155
156 handleOutgoingControlChange( ccParamValue, ((int) isMuted) * 127 );
157 }
158
setStripIsSoloed(int nStrip,bool isSoloed)159 void CoreActionController::setStripIsSoloed( int nStrip, bool isSoloed )
160 {
161 Hydrogen *pEngine = Hydrogen::get_instance();
162 Song *pSong = pEngine->getSong();
163 InstrumentList *pInstrList = pSong->get_instrument_list();
164
165 if ( isSoloed ) {
166 for ( int i = 0; i < pInstrList->size(); ++i ) {
167 setStripIsMuted( i, true );
168 }
169
170 setStripIsMuted( nStrip, false );
171 } else {
172 for ( int i = 0; i < pInstrList->size(); ++i ) {
173 setStripIsMuted( i, false );
174 }
175 }
176
177 #ifdef H2CORE_HAVE_OSC
178 Action FeedbackAction( "STRIP_SOLO_TOGGLE" );
179
180 FeedbackAction.setParameter1( QString("%1").arg( nStrip + 1 ) );
181 FeedbackAction.setParameter2( QString("%1").arg( (int) isSoloed ) );
182 OscServer::handleAction( &FeedbackAction );
183 #endif
184
185 MidiMap* pMidiMap = MidiMap::get_instance();
186
187 int ccParamValue = pMidiMap->findCCValueByActionParam1( QString("STRIP_SOLO_TOGGLE"), QString("%1").arg( nStrip ) );
188
189 handleOutgoingControlChange( ccParamValue, ((int) isSoloed) * 127 );
190 }
191
192
193
setStripPan(int nStrip,float panValue)194 void CoreActionController::setStripPan( int nStrip, float panValue )
195 {
196 float pan_L;
197 float pan_R;
198
199 if (panValue >= 0.5) {
200 pan_L = (1.0 - panValue) * 2;
201 pan_R = 1.0;
202 }
203 else {
204 pan_L = 1.0;
205 pan_R = panValue * 2;
206 }
207
208 Hydrogen *pEngine = Hydrogen::get_instance();
209 pEngine->setSelectedInstrumentNumber( nStrip );
210
211 Song *pSong = pEngine->getSong();
212 InstrumentList *pInstrList = pSong->get_instrument_list();
213
214 Instrument *pInstr = pInstrList->get( nStrip );
215 pInstr->set_pan_l( pan_L );
216 pInstr->set_pan_r( pan_R );
217
218 pEngine->setSelectedInstrumentNumber( nStrip );
219
220 #ifdef H2CORE_HAVE_OSC
221 Action FeedbackAction( "PAN_ABSOLUTE" );
222
223 FeedbackAction.setParameter1( QString("%1").arg( nStrip + 1 ) );
224 FeedbackAction.setParameter2( QString("%1").arg( panValue ) );
225 OscServer::handleAction( &FeedbackAction );
226 #endif
227
228 MidiMap* pMidiMap = MidiMap::get_instance();
229
230 int ccParamValue = pMidiMap->findCCValueByActionParam1( QString("PAN_ABSOLUTE"), QString("%1").arg( nStrip ) );
231
232
233 handleOutgoingControlChange( ccParamValue, panValue * 127 );
234 }
235
handleOutgoingControlChange(int param,int value)236 void CoreActionController::handleOutgoingControlChange(int param, int value)
237 {
238 Preferences *pPref = Preferences::get_instance();
239 Hydrogen *pEngine = Hydrogen::get_instance();
240 MidiOutput *pMidiDriver = pEngine->getMidiOutput();
241
242 if( pMidiDriver
243 && pPref->m_bEnableMidiFeedback
244 && param >= 0 ){
245 pMidiDriver->handleOutgoingControlChange( param, value, m_nDefaultMidiFeedbackChannel );
246 }
247 }
248
initExternalControlInterfaces()249 void CoreActionController::initExternalControlInterfaces()
250 {
251 /*
252 * Push the current state of Hydrogen to the attached control interfaces (e.g. OSC clients)
253 */
254
255 //MASTER_VOLUME_ABSOLUTE
256 Hydrogen* pEngine = Hydrogen::get_instance();
257 Song *pSong = pEngine->getSong();
258 setMasterVolume( pSong->get_volume() );
259
260 //PER-INSTRUMENT/STRIP STATES
261 InstrumentList *pInstrList = pSong->get_instrument_list();
262 for(int i=0; i < pInstrList->size(); i++){
263
264 //STRIP_VOLUME_ABSOLUTE
265 Instrument *pInstr = pInstrList->get( i );
266 setStripVolume( i, pInstr->get_volume() );
267
268 float fPan_L = pInstr->get_pan_l();
269 float fPan_R = pInstr->get_pan_r();
270
271 //PAN_ABSOLUTE
272 float fPanValue = 0.0;
273 if (fPan_R == 1.0) {
274 fPanValue = 1.0 - (fPan_L / 2.0);
275 }
276 else {
277 fPanValue = fPan_R / 2.0;
278 }
279
280 setStripPan( i, fPanValue );
281
282 //STRIP_MUTE_TOGGLE
283 setStripIsMuted( i, pInstr->is_muted() );
284
285 //SOLO
286 setStripIsSoloed( i, pInstr->is_soloed() );
287 }
288
289 //TOGGLE_METRONOME
290 setMetronomeIsActive( Preferences::get_instance()->m_bUseMetronome );
291
292 //MUTE_TOGGLE
293 setMasterIsMuted( Hydrogen::get_instance()->getSong()->__is_muted );
294 }
295
newSong(const QString & songPath)296 bool CoreActionController::newSong( const QString& songPath ) {
297
298 auto pHydrogen = Hydrogen::get_instance();
299
300 if ( pHydrogen->getState() == STATE_PLAYING ) {
301 // Stops recording, all queued MIDI notes, and the playback of
302 // the audio driver.
303 pHydrogen->sequencer_stop();
304 }
305
306 // Remove all BPM tags on the Timeline.
307 pHydrogen->getTimeline()->m_timelinevector.clear();
308
309 // Create an empty Song.
310 auto pSong = Song::get_empty_song();
311
312 // Check whether the provided path is valid.
313 if ( !isSongPathValid( songPath ) ) {
314 // isSongPathValid takes care of the error log message.
315
316 return false;
317 }
318
319 pSong->set_filename( songPath );
320
321 if ( pHydrogen->getActiveGUI() ) {
322
323 // Store the prepared Song for the GUI to access after the
324 // EVENT_UPDATE_SONG event was triggered.
325 pHydrogen->setNextSong( pSong );
326
327 // If the GUI is active, the Song *must not* be set by the
328 // core part itself.
329 // Triggers an update of the Qt5 GUI and tells it to update
330 // the song itself.
331 EventQueue::get_instance()->push_event( EVENT_UPDATE_SONG, 0 );
332
333 } else {
334
335 // Update the Song.
336 pHydrogen->setSong( pSong );
337
338 }
339
340 return true;
341 }
342
openSong(const QString & songPath)343 bool CoreActionController::openSong (const QString& songPath ) {
344
345 auto pHydrogen = Hydrogen::get_instance();
346
347 if ( pHydrogen->getState() == STATE_PLAYING ) {
348 // Stops recording, all queued MIDI notes, and the playback of
349 // the audio driver.
350 pHydrogen->sequencer_stop();
351 }
352
353 // Remove all BPM tags on the Timeline.
354 pHydrogen->getTimeline()->m_timelinevector.clear();
355
356 // Check whether the provided path is valid.
357 if ( !isSongPathValid( songPath ) ) {
358 // isSongPathValid takes care of the error log message.
359 return false;
360 }
361
362 QFileInfo songFileInfo = QFileInfo( songPath );
363 if ( !songFileInfo.exists() ) {
364 ERRORLOG( QString( "Selected song [%1] does not exist." )
365 .arg( songPath ) );
366 return false;
367 }
368
369 // Create an empty Song.
370 auto pSong = Song::load( songPath );
371
372 if ( pSong == nullptr ) {
373 ERRORLOG( QString( "Unable to open song [%1]." )
374 .arg( songPath ) );
375
376 return false;
377 }
378
379 if ( pHydrogen->getActiveGUI() ) {
380
381 // Store the prepared Song for the GUI to access after the
382 // EVENT_UPDATE_SONG event was triggered.
383 pHydrogen->setNextSong( pSong );
384
385 // If the GUI is active, the Song *must not* be set by the
386 // core part itself.
387 // Triggers an update of the Qt5 GUI and tells it to update
388 // the song itself.
389 EventQueue::get_instance()->push_event( EVENT_UPDATE_SONG, 0 );
390
391 } else {
392
393 // Update the Song.
394 pHydrogen->setSong( pSong );
395
396 }
397
398 return true;
399 }
400
saveSong()401 bool CoreActionController::saveSong() {
402
403 auto pHydrogen = Hydrogen::get_instance();
404
405 // Get the current Song which is about to be saved.
406 auto pSong = pHydrogen->getSong();
407
408 // Extract the path to the associate .h2song file.
409 QString songPath = pSong->get_filename();
410
411 if ( songPath.isEmpty() ) {
412 ERRORLOG( "Unable to save song. Empty filename!" );
413 return false;
414 }
415
416 // Actual saving
417 bool saved = pSong->save( songPath );
418 if ( !saved ) {
419 ERRORLOG( QString( "Current song [%1] could not be saved!" )
420 .arg( songPath ) );
421 return false;
422 }
423
424 // Update the status bar.
425 if ( pHydrogen->getActiveGUI() ) {
426 EventQueue::get_instance()->push_event( EVENT_UPDATE_SONG, 1 );
427 }
428
429 return true;
430 }
431
saveSongAs(const QString & songPath)432 bool CoreActionController::saveSongAs( const QString& songPath ) {
433
434 auto pHydrogen = Hydrogen::get_instance();
435
436 // Get the current Song which is about to be saved.
437 auto pSong = pHydrogen->getSong();
438
439 // Check whether the provided path is valid.
440 if ( !isSongPathValid( songPath ) ) {
441 // isSongPathValid takes care of the error log message.
442 return false;
443 }
444
445 if ( songPath.isEmpty() ) {
446 ERRORLOG( "Unable to save song. Empty filename!" );
447 return false;
448 }
449
450 // Actual saving
451 bool saved = pSong->save( songPath );
452 if ( !saved ) {
453 ERRORLOG( QString( "Current song [%1] could not be saved!" )
454 .arg( songPath ) );
455 return false;
456 }
457
458 // Update the status bar.
459 if ( pHydrogen->getActiveGUI() ) {
460 EventQueue::get_instance()->push_event( EVENT_UPDATE_SONG, 1 );
461 }
462
463 return true;
464 }
465
quit()466 bool CoreActionController::quit() {
467 EventQueue::get_instance()->push_event( EVENT_QUIT, 0 );
468
469 return true;
470 }
471
472
isSongPathValid(const QString & songPath)473 bool CoreActionController::isSongPathValid( const QString& songPath ) {
474
475 QFileInfo songFileInfo = QFileInfo( songPath );
476
477 if ( !songFileInfo.isAbsolute() ) {
478 ERRORLOG( QString( "Error: Unable to handle path [%1]. Please provide an absolute file path!" )
479 .arg( songPath.toLocal8Bit().data() ));
480 return false;
481 }
482
483 if ( songFileInfo.exists() ) {
484 if ( !songFileInfo.isWritable() ) {
485 ERRORLOG( QString( "Error: Unable to handle path [%1]. You must have permissions to write the file!" )
486 .arg( songPath.toLocal8Bit().data() ));
487 return false;
488 }
489 }
490
491 if ( songFileInfo.suffix() != "h2song" ) {
492 ERRORLOG( QString( "Error: Unable to handle path [%1]. The provided file must have the suffix '.h2song'!" )
493 .arg( songPath.toLocal8Bit().data() ));
494 return false;
495 }
496
497 return true;
498 }
499
500
501 }
502