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