diff --git a/data/img/gray/mixerPanel/masterMixerline_background.png b/data/img/gray/mixerPanel/masterMixerline_background.png index 57688d3cbf..adda8cfe4a 100644 Binary files a/data/img/gray/mixerPanel/masterMixerline_background.png and b/data/img/gray/mixerPanel/masterMixerline_background.png differ diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index a675a58ef3..736758a2c2 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -942,7 +942,8 @@ inline void AudioEngine::processPlayNotes( unsigned long nframes ) */ float fNoteProbability = pNote->get_probability(); if ( fNoteProbability != 1. ) { - if ( fNoteProbability < (float) rand() / (float) RAND_MAX ) { + float fThreshold = pSong->getThreshold(); + if ( fNoteProbability < fThreshold ) { m_songNoteQueue.pop(); pNote->get_instrument()->dequeue(); continue; diff --git a/src/core/Basics/Song.cpp b/src/core/Basics/Song.cpp index 67def762d0..080c1eabfc 100644 --- a/src/core/Basics/Song.cpp +++ b/src/core/Basics/Song.cpp @@ -93,6 +93,8 @@ Song::Song( const QString& sName, const QString& sAuthor, float fBpm, float fVol , m_actionMode( ActionMode::selectMode ) , m_nPanLawType ( Sampler::RATIO_STRAIGHT_POLYGONAL ) , m_fPanLawKNorm ( Sampler::K_NORM_DEFAULT ) + , m_fFillValue( 0.5 ) + , m_fFillRandomize( 0 ) { INFOLOG( QString( "INIT '%1'" ).arg( sName ) ); @@ -842,6 +844,8 @@ std::shared_ptr SongReader::readSong( const QString& sFileName ) float fHumanizeTimeValue = LocalFileMng::readXmlFloat( songNode, "humanize_time", 0.0 ); float fHumanizeVelocityValue = LocalFileMng::readXmlFloat( songNode, "humanize_velocity", 0.0 ); float fSwingFactor = LocalFileMng::readXmlFloat( songNode, "swing_factor", 0.0 ); + float fFillValue = LocalFileMng::readXmlFloat( songNode, "fillValue", 0.5 ); + float fFillRandomize = LocalFileMng::readXmlFloat( songNode, "fillRandomize", 1.0 ); pSong = std::make_shared( sName, sAuthor, fBpm, fVolume ); pSong->setMetronomeVolume( fMetronomeVolume ); @@ -855,6 +859,8 @@ std::shared_ptr SongReader::readSong( const QString& sFileName ) pSong->setPlaybackTrackFilename( sPlaybackTrack ); pSong->setPlaybackTrackEnabled( bPlaybackTrackEnabled ); pSong->setPlaybackTrackVolume( fPlaybackTrackVolume ); + pSong->setFillValue( fFillValue ); + pSong->setFillRandomize( fFillRandomize ); pSong->setActionMode( actionMode ); // pan law @@ -1641,4 +1647,16 @@ Pattern* SongReader::getPattern( QDomNode pattern, InstrumentList* pInstrList ) return pPattern; } + + +float Song::getThreshold() const +{ + float w = getFillRandomize(); + float k = 1.0f - w; + float lower = getFillValue() * k; + float upper = lower + w; + float rnd = (float)rand()/(float)RAND_MAX; + return 1.0f - (lower + rnd * (upper-lower)); +} + }; diff --git a/src/core/Basics/Song.h b/src/core/Basics/Song.h index ec65955d54..717d7f1113 100644 --- a/src/core/Basics/Song.h +++ b/src/core/Basics/Song.h @@ -184,6 +184,13 @@ class Song : public H2Core::Object, public std::enable_shared_from_this /** \param volume Sets #m_fPlaybackTrackVolume. */ void setPlaybackTrackVolume( const float fVolume ); + float getFillValue() const; + void setFillValue( float fFillValue ); + float getFillRandomize() const; + void setFillRandomize( float fFillRandomize ); + + float getThreshold() const; + /** Defines the type of user interaction experienced in the SongEditor.*/ enum class ActionMode { @@ -291,6 +298,9 @@ class Song : public H2Core::Object, public std::enable_shared_from_this /** Stores the type of interaction with the SongEditor. */ ActionMode m_actionMode; + + float m_fFillValue; + float m_fFillRandomize; int m_nPanLawType; // k such that L^k+R^k = 1. Used in constant k-Norm pan law @@ -550,6 +560,26 @@ inline float Song::getPanLawKNorm() const { return m_fPanLawKNorm; } +inline float Song::getFillValue() const +{ + return m_fFillValue; +} + +inline void Song::setFillValue( float fFillValue ) +{ + m_fFillValue = fFillValue; +} + +inline float Song::getFillRandomize() const +{ + return m_fFillRandomize; +} + +inline void Song::setFillRandomize( float fFillRandomize ) +{ + m_fFillRandomize = fFillRandomize; +} + /** \ingroup H2CORE \brief Read XML file of a song diff --git a/src/core/LocalFileMgr.cpp b/src/core/LocalFileMgr.cpp index 8e545e396d..0d7ff00e9b 100644 --- a/src/core/LocalFileMgr.cpp +++ b/src/core/LocalFileMgr.cpp @@ -479,6 +479,8 @@ int SongWriter::writeSong( std::shared_ptr pSong, const QString& filename LocalFileMng::writeXmlString( songNode, "humanize_time", QString("%1").arg( pSong->getHumanizeTimeValue() ) ); LocalFileMng::writeXmlString( songNode, "humanize_velocity", QString("%1").arg( pSong->getHumanizeVelocityValue() ) ); LocalFileMng::writeXmlString( songNode, "swing_factor", QString("%1").arg( pSong->getSwingFactor() ) ); + LocalFileMng::writeXmlString( songNode, "fillValue", QString("%1").arg( pSong->getFillValue() ) ); + LocalFileMng::writeXmlString( songNode, "fillRandomize", QString("%1").arg( pSong->getFillRandomize() ) ); // component List QDomNode componentListNode = doc.createElement( "componentList" ); diff --git a/src/core/MidiAction.cpp b/src/core/MidiAction.cpp index e33521462c..2551fed157 100644 --- a/src/core/MidiAction.cpp +++ b/src/core/MidiAction.cpp @@ -123,7 +123,13 @@ MidiActionManager::MidiActionManager() : Object( __class_name ) { actionMap.insert(std::make_pair("MASTER_VOLUME_ABSOLUTE", std::make_pair(&MidiActionManager::master_volume_absolute, empty))); actionMap.insert(std::make_pair("STRIP_VOLUME_RELATIVE", std::make_pair(&MidiActionManager::strip_volume_relative, empty))); actionMap.insert(std::make_pair("STRIP_VOLUME_ABSOLUTE", std::make_pair(&MidiActionManager::strip_volume_absolute, empty))); - + + actionMap.insert(std::make_pair("HUMANIZE_VELOCITY_ABSOLUTE", std::make_pair(&MidiActionManager::humanize_velocity_absolute, empty))); + actionMap.insert(std::make_pair("HUMANIZE_TIME_ABSOLUTE", std::make_pair(&MidiActionManager::humanize_time_absolute, empty))); + actionMap.insert(std::make_pair("SWING_ABSOLUTE", std::make_pair(&MidiActionManager::swing_absolute, empty))); + actionMap.insert(std::make_pair("FILL_VALUE_ABSOLUTE", std::make_pair(&MidiActionManager::fill_value_absolute, empty))); + actionMap.insert(std::make_pair("FILL_RANDOMIZE_ABSOLUTE", std::make_pair(&MidiActionManager::fill_randomize_absolute, empty))); + for(int i = 0; i < MAX_FX; ++i) { targeted_element effect = {i,0}; std::ostringstream toChar; @@ -809,6 +815,56 @@ bool MidiActionManager::bpm_fine_cc_relative(Action * pAction, Hydrogen* pHydrog return true; } +bool MidiActionManager::humanize_velocity_absolute(Action * pAction, Hydrogen* pHydrogen , targeted_element ) { + bool ok; + int value = pAction->getParameter2().toInt(&ok,10); + + std::shared_ptr pSong = pHydrogen->getSong(); + pSong->setHumanizeVelocityValue( value / 127.0 ); + + return true; +} + +bool MidiActionManager::humanize_time_absolute(Action *pAction , Hydrogen* pHydrogen , targeted_element ) { + bool ok; + int value = pAction->getParameter2().toInt(&ok,10); + + std::shared_ptr pSong = pHydrogen->getSong(); + pSong->setHumanizeTimeValue( value / 127.0 ); + + return true; +} + +bool MidiActionManager::swing_absolute(Action *pAction , Hydrogen* pHydrogen , targeted_element ) { + bool ok; + int value = pAction->getParameter2().toInt(&ok,10); + + std::shared_ptr pSong = pHydrogen->getSong(); + pSong->setSwingFactor( value / 127.0 ); + + return true; +} + +bool MidiActionManager::fill_value_absolute(Action *pAction , Hydrogen* pHydrogen , targeted_element ) { + bool ok; + int value = pAction->getParameter2().toInt(&ok,10); + + std::shared_ptr pSong = pHydrogen->getSong(); + pSong->setFillValue( value / 127.0 ); + + return true; +} + +bool MidiActionManager::fill_randomize_absolute(Action *pAction , Hydrogen* pHydrogen , targeted_element ) { + bool ok; + int value = pAction->getParameter2().toInt(&ok,10); + + std::shared_ptr pSong = pHydrogen->getSong(); + pSong->setFillRandomize( value / 127.0 ); + + return true; +} + bool MidiActionManager::bpm_increase(Action * pAction, Hydrogen* pHydrogen, targeted_element ) { pHydrogen->getAudioEngine()->lock( RIGHT_HERE ); diff --git a/src/core/MidiAction.h b/src/core/MidiAction.h index e5168e362b..2bed4c554e 100644 --- a/src/core/MidiAction.h +++ b/src/core/MidiAction.h @@ -145,6 +145,11 @@ class MidiActionManager : public H2Core::Object bool redo_action(Action * , H2Core::Hydrogen * , targeted_element ); bool gain_level_absolute(Action * , H2Core::Hydrogen * , targeted_element ); bool pitch_level_absolute(Action * , H2Core::Hydrogen * , targeted_element ); + bool humanize_velocity_absolute(Action * , H2Core::Hydrogen * , targeted_element ); + bool humanize_time_absolute(Action * , H2Core::Hydrogen * , targeted_element ); + bool swing_absolute(Action * , H2Core::Hydrogen * , targeted_element ); + bool fill_value_absolute(Action * , H2Core::Hydrogen * , targeted_element ); + bool fill_randomize_absolute(Action * , H2Core::Hydrogen * , targeted_element ); QStringList eventList; diff --git a/src/core/Smf/Smf.cpp b/src/core/Smf/Smf.cpp index 61ad675a37..b1d2a018ea 100644 --- a/src/core/Smf/Smf.cpp +++ b/src/core/Smf/Smf.cpp @@ -269,8 +269,8 @@ void SMFWriter::save( const QString& sFilename, std::shared_ptr pSong ) FOREACH_NOTE_CST_IT_BOUND(notes,it,nNote) { Note *pNote = it->second; if ( pNote ) { - float rnd = (float)rand()/(float)RAND_MAX; - if ( pNote->get_probability() < rnd ) { + float fThreshold = pSong->getThreshold(); + if ( pNote->get_probability() < fThreshold ) { continue; } diff --git a/src/gui/src/Mixer/MixerLine.cpp b/src/gui/src/Mixer/MixerLine.cpp index bb95cb1398..ed91df8f86 100644 --- a/src/gui/src/Mixer/MixerLine.cpp +++ b/src/gui/src/Mixer/MixerLine.cpp @@ -676,16 +676,29 @@ MasterMixerLine::MasterMixerLine(QWidget* parent) m_pPeakLCD->setPalette( lcdPalette ); m_pHumanizeVelocityRotary = new Rotary( this, Rotary::TYPE_NORMAL, tr( "Humanize velocity" ), false, true ); - m_pHumanizeVelocityRotary->move( 74, 88 ); + m_pHumanizeVelocityRotary->move( 74, 43 ); connect( m_pHumanizeVelocityRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); + m_pHumanizeVelocityRotary->setAction( new Action("HUMANIZE_VELOCITY_ABSOLUTE") ); m_pHumanizeTimeRotary = new Rotary( this, Rotary::TYPE_NORMAL, tr( "Humanize time" ), false, true ); - m_pHumanizeTimeRotary->move( 74, 125 ); + m_pHumanizeTimeRotary->move( 74, 80 ); connect( m_pHumanizeTimeRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); + m_pHumanizeTimeRotary->setAction( new Action("HUMANIZE_TIME_ABSOLUTE") ); m_pSwingRotary = new Rotary( this, Rotary::TYPE_NORMAL, tr( "16th-note Swing" ), false, true ); - m_pSwingRotary->move( 74, 162 ); + m_pSwingRotary->move( 74, 117 ); connect( m_pSwingRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); + m_pSwingRotary->setAction( new Action("SWING_ABSOLUTE") ); + + m_pFillValueRotary = new Rotary( this, Rotary::TYPE_NORMAL, tr( "Fill" ), false, false ); + m_pFillValueRotary->move( 74, 172 ); + connect( m_pFillValueRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); + m_pFillValueRotary->setAction( new Action("FILL_VALUE_ABSOLUTE") ); + + m_pFillRandomizeRotary = new Rotary( this, Rotary::TYPE_NORMAL, tr( "Randomize" ), false, false ); + m_pFillRandomizeRotary->move( 74, 209 ); + connect( m_pFillRandomizeRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); + m_pFillRandomizeRotary->setAction( new Action("FILL_RANDOMIZE_ABSOLUTE") ); // Mute btn m_pMuteBtn = new ToggleButton( @@ -816,6 +829,8 @@ void MasterMixerLine::updateMixerLine() m_pHumanizeTimeRotary->setValue( pSong->getHumanizeTimeValue() ); m_pHumanizeVelocityRotary->setValue( pSong->getHumanizeVelocityValue() ); m_pSwingRotary->setValue( pSong->getSwingFactor() ); + m_pFillValueRotary->setValue( pSong->getFillValue() ); + m_pFillRandomizeRotary->setValue( pSong->getFillRandomize() ); m_pMuteBtn->setPressed( pSong->getIsMuted() ); } else { @@ -843,6 +858,14 @@ void MasterMixerLine::rotaryChanged( Rotary *pRef ) pHydrogen->getSong()->setSwingFactor( fVal ); sMsg = tr( "Set swing factor [%1]").arg( fVal, 0, 'f', 2 ); } + else if ( pRef == m_pFillValueRotary ) { + pHydrogen->getSong()->setFillValue( fVal ); + sMsg = trUtf8( "Set fill value [%1]").arg( fVal, 0, 'f', 2 ); + } + else if ( pRef == m_pFillRandomizeRotary ) { + pHydrogen->getSong()->setFillRandomize( fVal ); + sMsg = trUtf8( "Set fill randomize [%1]").arg( fVal, 0, 'f', 2 ); + } else { ERRORLOG( "[knobChanged] Unhandled knob" ); } diff --git a/src/gui/src/Mixer/MixerLine.h b/src/gui/src/Mixer/MixerLine.h index 70ce69081d..a0d8cf06d1 100644 --- a/src/gui/src/Mixer/MixerLine.h +++ b/src/gui/src/Mixer/MixerLine.h @@ -275,6 +275,9 @@ class MasterMixerLine: public PixmapWidget Rotary * m_pHumanizeTimeRotary; Rotary * m_pHumanizeVelocityRotary; + Rotary * m_pFillValueRotary; + Rotary * m_pFillRandomizeRotary; + ToggleButton * m_pMuteBtn; };