diff --git a/ChangeLog b/ChangeLog index 9a075c4ac..8acb9ece7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ * Deprecating JACK-session * Instrument main pitch shift offset * Custom pattern size support with representation in note values + * Custom pan law support in mixer 2020-08-03 the hydrogen team * Release 1.0 diff --git a/data/img/gray/mixerPanel/openMixerSettings_off.png b/data/img/gray/mixerPanel/openMixerSettings_off.png new file mode 100644 index 000000000..485665ca0 Binary files /dev/null and b/data/img/gray/mixerPanel/openMixerSettings_off.png differ diff --git a/data/img/gray/mixerPanel/openMixerSettings_over.png b/data/img/gray/mixerPanel/openMixerSettings_over.png new file mode 100644 index 000000000..2f54d42cf Binary files /dev/null and b/data/img/gray/mixerPanel/openMixerSettings_over.png differ diff --git a/src/core/Basics/Song.cpp b/src/core/Basics/Song.cpp index bc5078a13..8bd7ff4ee 100644 --- a/src/core/Basics/Song.cpp +++ b/src/core/Basics/Song.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #ifdef H2CORE_HAVE_OSC #include @@ -90,7 +91,8 @@ Song::Song( const QString& sName, const QString& sAuthor, float fBpm, float fVol , m_pVelocityAutomationPath( nullptr ) , m_sLicense( "" ) , m_actionMode( ActionMode::selectMode ) - + , m_nPanLawType ( Sampler::RATIO_STRAIGHT_POLYGONAL ) + , m_fPanLawKNorm ( Sampler::K_NORM_DEFAULT ) { INFOLOG( QString( "INIT '%1'" ).arg( sName ) ); @@ -645,6 +647,15 @@ const QString SongReader::getPath ( const QString& sFilename ) const return nullptr; } +void Song::setPanLawKNorm( float fKNorm ) { + if ( fKNorm >= 0. ) { + m_fPanLawKNorm = fKNorm; + } else { + WARNINGLOG("negative kNorm. Set default" ); + m_fPanLawKNorm = Sampler::K_NORM_DEFAULT; + } +} + /// /// Reads a song. /// return nullptr = error reading song file. @@ -725,6 +736,52 @@ Song* SongReader::readSong( const QString& sFileName ) pSong->setPlaybackTrackEnabled( bPlaybackTrackEnabled ); pSong->setPlaybackTrackVolume( fPlaybackTrackVolume ); pSong->setActionMode( actionMode ); + + // pan law + QString sPanLawType( LocalFileMng::readXmlString( songNode, "pan_law_type", "RATIO_STRAIGHT_POLYGONAL" ) ); + if ( sPanLawType == "RATIO_STRAIGHT_POLYGONAL" ) { + pSong->setPanLawType( Sampler::RATIO_STRAIGHT_POLYGONAL ); + } else if ( sPanLawType == "RATIO_CONST_POWER" ) { + pSong->setPanLawType( Sampler::RATIO_CONST_POWER ); + } else if ( sPanLawType == "RATIO_CONST_SUM" ) { + pSong->setPanLawType( Sampler::RATIO_CONST_SUM ); + } else if ( sPanLawType == "LINEAR_STRAIGHT_POLYGONAL" ) { + pSong->setPanLawType( Sampler::LINEAR_STRAIGHT_POLYGONAL ); + } else if ( sPanLawType == "LINEAR_CONST_POWER" ) { + pSong->setPanLawType( Sampler::LINEAR_CONST_POWER ); + } else if ( sPanLawType == "LINEAR_CONST_SUM" ) { + pSong->setPanLawType( Sampler::LINEAR_CONST_SUM ); + } else if ( sPanLawType == "POLAR_STRAIGHT_POLYGONAL" ) { + pSong->setPanLawType( Sampler::POLAR_STRAIGHT_POLYGONAL ); + } else if ( sPanLawType == "POLAR_CONST_POWER" ) { + pSong->setPanLawType( Sampler::POLAR_CONST_POWER ); + } else if ( sPanLawType == "POLAR_CONST_SUM" ) { + pSong->setPanLawType( Sampler::POLAR_CONST_SUM ); + } else if ( sPanLawType == "QUADRATIC_STRAIGHT_POLYGONAL" ) { + pSong->setPanLawType( Sampler::QUADRATIC_STRAIGHT_POLYGONAL ); + } else if ( sPanLawType == "QUADRATIC_CONST_POWER" ) { + pSong->setPanLawType( Sampler::QUADRATIC_CONST_POWER ); + } else if ( sPanLawType == "QUADRATIC_CONST_SUM" ) { + pSong->setPanLawType( Sampler::QUADRATIC_CONST_SUM ); + } else if ( sPanLawType == "LINEAR_CONST_K_NORM" ) { + pSong->setPanLawType( Sampler::LINEAR_CONST_K_NORM ); + } else if ( sPanLawType == "POLAR_CONST_K_NORM" ) { + pSong->setPanLawType( Sampler::POLAR_CONST_K_NORM ); + } else if ( sPanLawType == "RATIO_CONST_K_NORM" ) { + pSong->setPanLawType( Sampler::RATIO_CONST_K_NORM ); + } else if ( sPanLawType == "QUADRATIC_CONST_K_NORM" ) { + pSong->setPanLawType( Sampler::QUADRATIC_CONST_K_NORM ); + } else { + pSong->setPanLawType( Sampler::RATIO_STRAIGHT_POLYGONAL ); + WARNINGLOG( "Unknown pan law type in import song. Set default." ); + } + + float fPanLawKNorm = LocalFileMng::readXmlFloat( songNode, "pan_law_k_norm", Sampler::K_NORM_DEFAULT ); + if ( fPanLawKNorm <= 0.0 ) { + fPanLawKNorm = Sampler::K_NORM_DEFAULT; + WARNINGLOG( "Invalid pan law k in import song (<= 0). Set default k." ); + } + pSong->setPanLawKNorm( fPanLawKNorm ); QDomNode componentListNode = songNode.firstChildElement( "componentList" ); if ( ( ! componentListNode.isNull() ) ) { diff --git a/src/core/Basics/Song.h b/src/core/Basics/Song.h index fea3ce320..0f41daae1 100644 --- a/src/core/Basics/Song.h +++ b/src/core/Basics/Song.h @@ -197,6 +197,11 @@ class Song : public H2Core::Object */ bool hasMissingSamples() const; void clearMissingSamples(); + + void setPanLawType( int nPanLawType ); + int getPanLawType() const; + void setPanLawKNorm( float fKNorm ); + float getPanLawKNorm() const; private: @@ -273,6 +278,10 @@ class Song : public H2Core::Object /** Stores the type of interaction with the SongEditor. */ ActionMode m_actionMode; + + int m_nPanLawType; + // k such that L^k+R^k = 1. Used in constant k-Norm pan law + float m_fPanLawKNorm; }; @@ -516,6 +525,18 @@ inline Song::ActionMode Song::getActionMode() const { return m_actionMode; } +inline void Song::setPanLawType( int nPanLawType ) { + m_nPanLawType = nPanLawType; +} + +inline int Song::getPanLawType() const { + return m_nPanLawType; +} + +inline float Song::getPanLawKNorm() const { + return m_fPanLawKNorm; +} + /** \ingroup H2CORE \brief Read XML file of a song diff --git a/src/core/LocalFileMgr.cpp b/src/core/LocalFileMgr.cpp index 74f43f5e5..71d383eec 100644 --- a/src/core/LocalFileMgr.cpp +++ b/src/core/LocalFileMgr.cpp @@ -386,6 +386,50 @@ int SongWriter::writeSong( Song * pSong, const QString& filename ) LocalFileMng::writeXmlString( songNode, "mode", QString( "pattern" ) ); } + Sampler* pSampler = AudioEngine::get_instance()->get_sampler(); + + QString sPanLawType; // prepare the pan law string to write + int nPanLawType = pSong->getPanLawType(); + if ( nPanLawType == Sampler::RATIO_STRAIGHT_POLYGONAL ) { + sPanLawType = "RATIO_STRAIGHT_POLYGONAL"; + } else if ( nPanLawType == Sampler::RATIO_CONST_POWER ) { + sPanLawType = "RATIO_CONST_POWER"; + } else if ( nPanLawType == Sampler::RATIO_CONST_SUM ) { + sPanLawType = "RATIO_CONST_SUM"; + } else if ( nPanLawType == Sampler::LINEAR_STRAIGHT_POLYGONAL ) { + sPanLawType = "LINEAR_STRAIGHT_POLYGONAL"; + } else if ( nPanLawType == Sampler::LINEAR_CONST_POWER ) { + sPanLawType = "LINEAR_CONST_POWER"; + } else if ( nPanLawType == Sampler::LINEAR_CONST_SUM ) { + sPanLawType = "LINEAR_CONST_SUM"; + } else if ( nPanLawType == Sampler::POLAR_STRAIGHT_POLYGONAL ) { + sPanLawType = "POLAR_STRAIGHT_POLYGONAL"; + } else if ( nPanLawType == Sampler::POLAR_CONST_POWER ) { + sPanLawType = "POLAR_CONST_POWER"; + } else if ( nPanLawType == Sampler::POLAR_CONST_SUM ) { + sPanLawType = "POLAR_CONST_SUM"; + } else if ( nPanLawType == Sampler::QUADRATIC_STRAIGHT_POLYGONAL ) { + sPanLawType = "QUADRATIC_STRAIGHT_POLYGONAL"; + } else if ( nPanLawType == Sampler::QUADRATIC_CONST_POWER ) { + sPanLawType = "QUADRATIC_CONST_POWER"; + } else if ( nPanLawType == Sampler::QUADRATIC_CONST_SUM ) { + sPanLawType = "QUADRATIC_CONST_SUM"; + } else if ( nPanLawType == Sampler::LINEAR_CONST_K_NORM ) { + sPanLawType = "LINEAR_CONST_K_NORM"; + } else if ( nPanLawType == Sampler::POLAR_CONST_K_NORM ) { + sPanLawType = "POLAR_CONST_K_NORM"; + } else if ( nPanLawType == Sampler::RATIO_CONST_K_NORM ) { + sPanLawType = "RATIO_CONST_K_NORM"; + } else if ( nPanLawType == Sampler::QUADRATIC_CONST_K_NORM ) { + sPanLawType = "QUADRATIC_CONST_K_NORM"; + } else { + WARNINGLOG( "Unknown pan law in saving song. Saved default type." ); + sPanLawType = "RATIO_STRAIGHT_POLYGONAL"; + } + // write the pan law string in file + LocalFileMng::writeXmlString( songNode, "pan_law_type", sPanLawType ); + LocalFileMng::writeXmlString( songNode, "pan_law_k_norm", QString("%1").arg( pSong->getPanLawKNorm() ) ); + 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() ) ); diff --git a/src/core/Sampler/Sampler.cpp b/src/core/Sampler/Sampler.cpp index 0d22d4313..debf84e09 100644 --- a/src/core/Sampler/Sampler.cpp +++ b/src/core/Sampler/Sampler.cpp @@ -108,6 +108,11 @@ Sampler::~Sampler() m_pPlaybackTrackInstrument = nullptr; } +/** set default k for pan law with -4.5dB center compensation, given L^k + R^k = const + * it is the mean compromise between constant sum and constant power + */ +float const Sampler::K_NORM_DEFAULT = 1.33333333333333; + void Sampler::process( uint32_t nFrames, Song* pSong ) { //infoLog( "[process]" ); @@ -233,6 +238,179 @@ void Sampler::noteOff(Note* pNote ) } +// functions for pan parameters and laws----------------- + +float Sampler::getRatioPan( float fPan_L, float fPan_R ) { + if ( fPan_L < 0. || fPan_R < 0. || ( fPan_L == 0. && fPan_R == 0.) ) { // invalid input + WARNINGLOG( "Invalid (panL, panR): both zero or some is negative. Pan set to center." ); + return 0.; // default central value + } else { + if ( fPan_L >= fPan_R ) { + return fPan_R / fPan_L - 1.; + } else { + return 1. - fPan_L / fPan_R; + } + } +} + + +float Sampler::ratioStraightPolygonalPanLaw( float fPan ) { + // the straight polygonal pan law interpreting fPan as the "ratio" parameter + if ( fPan <= 0 ) { + return 1.; + } else { + return ( 1. - fPan ); + } +} + +float Sampler::ratioConstPowerPanLaw( float fPan ) { + // the constant power pan law interpreting fPan as the "ratio" parameter + if ( fPan <= 0 ) { + return 1. / sqrt( 1 + ( 1. + fPan ) * ( 1. + fPan ) ); + } else { + return ( 1. - fPan ) / sqrt( 1 + ( 1. - fPan ) * ( 1. - fPan ) ); + } +} + +float Sampler::ratioConstSumPanLaw( float fPan ) { + // the constant Sum pan law interpreting fPan as the "ratio" parameter + if ( fPan <= 0 ) { + return 1. / ( 2. + fPan ); + } else { + return ( 1. - fPan ) / ( 2. - fPan ); + } +} + +float Sampler::linearStraightPolygonalPanLaw( float fPan ) { + // the constant power pan law interpreting fPan as the "linear" parameter + if ( fPan <= 0 ) { + return 1.; + } else { + return ( 1. - fPan ) / ( 1. + fPan ); + } +} + +float Sampler::linearConstPowerPanLaw( float fPan ) { + // the constant power pan law interpreting fPan as the "linear" parameter + return ( 1. - fPan ) / sqrt( 2. * ( 1 + fPan * fPan ) ); +} + +float Sampler::linearConstSumPanLaw( float fPan ) { + // the constant Sum pan law interpreting fPan as the "linear" parameter + return ( 1. - fPan ) * 0.5; +} + +float Sampler::polarStraightPolygonalPanLaw( float fPan ) { + // the constant power pan law interpreting fPan as the "polar" parameter + float fTheta = 0.25 * M_PI * ( fPan + 1 ); + if ( fPan <= 0 ) { + return 1.; + } else { + return cos( fTheta ) / sin( fTheta ); + } +} + +float Sampler::polarConstPowerPanLaw( float fPan ) { + // the constant power pan law interpreting fPan as the "polar" parameter + float fTheta = 0.25 * M_PI * ( fPan + 1 ); + return cos( fTheta ); +} + +float Sampler::polarConstSumPanLaw( float fPan ) { + // the constant Sum pan law interpreting fPan as the "polar" parameter + float fTheta = 0.25 * M_PI * ( fPan + 1 ); + return cos( fTheta ) / ( cos( fTheta ) + sin( fTheta ) ); +} + +float Sampler::quadraticStraightPolygonalPanLaw( float fPan ) { + // the straight polygonal pan law interpreting fPan as the "quadratic" parameter + if ( fPan <= 0 ) { + return 1.; + } else { + return sqrt( ( 1. - fPan ) / ( 1. + fPan ) ); + } +} + +float Sampler::quadraticConstPowerPanLaw( float fPan ) { + // the constant power pan law interpreting fPan as the "quadratic" parameter + return sqrt( ( 1. - fPan ) * 0.5 ); +} + +float Sampler::quadraticConstSumPanLaw( float fPan ) { + // the constant Sum pan law interpreting fPan as the "quadratic" parameter + return sqrt( 1. - fPan ) / ( sqrt( 1. - fPan ) + sqrt( 1. + fPan ) ); +} + +float Sampler::linearConstKNormPanLaw( float fPan, float k ) { + // the constant k norm pan law interpreting fPan as the "linear" parameter + return ( 1. - fPan ) / pow( ( pow( (1. - fPan), k ) + pow( (1. + fPan), k ) ), 1./k ); +} + +float Sampler::quadraticConstKNormPanLaw( float fPan, float k ) { + // the constant k norm pan law interpreting fPan as the "quadratic" parameter + return sqrt( 1. - fPan ) / pow( ( pow( (1. - fPan), 0.5 * k ) + pow( (1. + fPan), 0.5 * k ) ), 1./k ); +} + +float Sampler::polarConstKNormPanLaw( float fPan, float k ) { + // the constant k norm pan law interpreting fPan as the "polar" parameter + float fTheta = 0.25 * M_PI * ( fPan + 1 ); + float cosTheta = cos( fTheta ); + return cosTheta / pow( ( pow( cosTheta, k ) + pow( sin( fTheta ), k ) ), 1./k ); +} + +float Sampler::ratioConstKNormPanLaw( float fPan, float k) { + // the constant k norm pan law interpreting fPan as the "ratio" parameter + if ( fPan <= 0 ) { + return 1. / pow( ( 1. + pow( (1. + fPan), k ) ), 1./k ); + } else { + return ( 1. - fPan ) / pow( ( 1. + pow( (1. - fPan), k ) ), 1./k ); + } +} + +// function to direct the computation to the selected pan law. +inline float Sampler::panLaw( float fPan, Song* pSong ) { + int nPanLawType = pSong->getPanLawType(); + if ( nPanLawType == RATIO_STRAIGHT_POLYGONAL ) { + return ratioStraightPolygonalPanLaw( fPan ); + } else if ( nPanLawType == RATIO_CONST_POWER ) { + return ratioConstPowerPanLaw( fPan ); + } else if ( nPanLawType == RATIO_CONST_SUM ) { + return ratioConstSumPanLaw( fPan ); + } else if ( nPanLawType == LINEAR_STRAIGHT_POLYGONAL ) { + return linearStraightPolygonalPanLaw( fPan ); + } else if ( nPanLawType == LINEAR_CONST_POWER ) { + return linearConstPowerPanLaw( fPan ); + } else if ( nPanLawType == LINEAR_CONST_SUM ) { + return linearConstSumPanLaw( fPan ); + } else if ( nPanLawType == POLAR_STRAIGHT_POLYGONAL ) { + return polarStraightPolygonalPanLaw( fPan ); + } else if ( nPanLawType == POLAR_CONST_POWER ) { + return polarConstPowerPanLaw( fPan ); + } else if ( nPanLawType == POLAR_CONST_SUM ) { + return polarConstSumPanLaw( fPan ); + } else if ( nPanLawType == QUADRATIC_STRAIGHT_POLYGONAL ) { + return quadraticStraightPolygonalPanLaw( fPan ); + } else if ( nPanLawType == QUADRATIC_CONST_POWER ) { + return quadraticConstPowerPanLaw( fPan ); + } else if ( nPanLawType == QUADRATIC_CONST_SUM ) { + return quadraticConstSumPanLaw( fPan ); + } else if ( nPanLawType == LINEAR_CONST_K_NORM ) { + return linearConstKNormPanLaw( fPan, pSong->getPanLawKNorm() ); + } else if ( nPanLawType == POLAR_CONST_K_NORM ) { + return polarConstKNormPanLaw( fPan, pSong->getPanLawKNorm() ); + } else if ( nPanLawType == RATIO_CONST_K_NORM ) { + return ratioConstKNormPanLaw( fPan, pSong->getPanLawKNorm() ); + } else if ( nPanLawType == QUADRATIC_CONST_K_NORM ) { + return quadraticConstKNormPanLaw( fPan, pSong->getPanLawKNorm() ); + } else { + WARNINGLOG( "Unknown pan law type. Set default." ); + pSong->setPanLawType( RATIO_STRAIGHT_POLYGONAL ); + return ratioStraightPolygonalPanLaw( fPan ); + } +} + +//------------------------------------------------------------------ + /// Render a note /// Return false: the note is not ended /// Return true: the note is ended @@ -257,6 +435,45 @@ bool Sampler::renderNote( Note* pNote, unsigned nBufferSize, Song* pSong ) return 1; } + // new instrument and note pan interaction-------------------------- + // notePan moves the RESULTANT pan in a smaller pan range centered at instrumentPan + + /** reconvert (pan_L,pan_R) to a single pan parameter (as it was input from the GUI) in [-1,1]. + * This redundance avoids to import old files as legacy. + * ALWAYS use getRatioPan(), since H2 always stores pan_L,pan_R with a ratioStraightPolygonalPanLaw, + * up to constant multiplication, even if user chooses another type of pan law. + *-----Historical Note----- + * Originally pan_L,pan_R were actually gains for each channel. + * "instrument" and "note" pans were multiplied as in a gain CHAIN in each separate channel, + * so the chain killed the signal if instrument and note pans were hard-sided to opposites sides! + */ + float fNotePan = getRatioPan( pNote->get_pan_l(), pNote->get_pan_r() ); + float fInstrPan = getRatioPan( pInstr->get_pan_l(), pInstr->get_pan_r() ); + + /** Get the RESULTANT pan, following a "matryoshka" multi panning, like in this graphic: + * + * L--------------instrPan---------C------------------------------>R (instrumentPan = -0.4) + * | + * V + * L-----------------C---notePan-------->R (notePan = +0.3) + * | + * V + * L----------------------resPan---C------------------------------>R (resultantPan = -0.22) + * + * Explanation: + * notePan moves the RESULTANT pan in a smaller pan range centered at instrumentPan value, + * whose extension depends on instrPan value: + * if instrPan is central, notePan moves the signal in the whole pan range (really from left to right); + * if instrPan is sided, notePan moves the signal in a progressively smaller pan range centered at instrPan; + * if instrPan is HARD-sided, notePan doesn't have any effect. + */ + float fPan = fInstrPan + fNotePan * ( 1 - fabs( fInstrPan ) ); + + // Pass fPan to the Pan Law + float fPan_L = panLaw( fPan, pSong ); + float fPan_R = panLaw( -fPan, pSong ); + //--------------------------------------------------------- + bool nReturnValues [pInstr->get_components()->size()]; for(int i = 0; i < pInstr->get_components()->size(); i++){ @@ -604,9 +821,10 @@ bool Sampler::renderNote( Note* pNote, unsigned nBufferSize, Song* pSong ) cost_L = cost_L * pNote->get_velocity(); // note velocity cost_R = cost_R * pNote->get_velocity(); // note velocity } - cost_L = cost_L * pNote->get_pan_l(); // note pan + + + cost_L *= fPan_L; // pan cost_L = cost_L * fLayerGain; // layer gain - cost_L = cost_L * pInstr->get_pan_l(); // instrument pan cost_L = cost_L * pInstr->get_gain(); // instrument gain cost_L = cost_L * pCompo->get_gain(); // Component gain @@ -617,11 +835,9 @@ bool Sampler::renderNote( Note* pNote, unsigned nBufferSize, Song* pSong ) cost_track_L = cost_L * 2; } cost_L = cost_L * pSong->getVolume(); // song volume - cost_L = cost_L * 2; // max pan is 0.5 - cost_R = cost_R * pNote->get_pan_r(); // note pan + cost_R *= fPan_R; // pan cost_R = cost_R * fLayerGain; // layer gain - cost_R = cost_R * pInstr->get_pan_r(); // instrument pan cost_R = cost_R * pInstr->get_gain(); // instrument gain cost_R = cost_R * pCompo->get_gain(); // Component gain @@ -632,7 +848,6 @@ bool Sampler::renderNote( Note* pNote, unsigned nBufferSize, Song* pSong ) cost_track_R = cost_R * 2; } cost_R = cost_R * pSong->getVolume(); // song pan - cost_R = cost_R * 2; // max pan is 0.5 } // direct track outputs only use velocity diff --git a/src/core/Sampler/Sampler.h b/src/core/Sampler/Sampler.h index 28ec0ae2c..c69d0fc19 100644 --- a/src/core/Sampler/Sampler.h +++ b/src/core/Sampler/Sampler.h @@ -32,6 +32,8 @@ #include #include + + namespace H2Core { @@ -51,6 +53,106 @@ class Sampler : public H2Core::Object { H2_OBJECT public: + + /** PAN LAWS + * The following pan law functions return pan_L (==L, which is the gain for Left channel). + * They assume a fPan argument domain in [-1;1], and this always happens + * thanks to the previously called getRatioPan(). + *---------------------------- + * For the right channel use: R(p) == pan_R(p) = pan_L(-p) == L(-p) + * thanks to the Left-Right symmetry. + *-------------------------------------- + * The prefix of the function name tells the interpretation of the fPan argument: + * + * "ratio" parameter: + * fPan = R/L - 1 if panned to the left, + * fPan = 1 - L/R if panned to the right. + * + * "linear" parameter (arithmetic mean with linear weights): + * fPan = ( R - L ) / ( R + L ). + * + * "polar" parameter (polar coordinate in LR plane): + * fPan = 4 / pi * arctan( R/L ) - 1 if L != 0, + * fPan = 1 if L == 0. + * + * "quadratic" parameter (arithmetic mean with squared weights): + * fPan = ( R^2 - L^2 ) / ( R^2 + L^2 ). + * + * Note: using a different fPan interpretation makes the output signal more central or more lateral. + * From more central to more lateral: + * "quadratic" ---> "ratio" ---> "polar" ---> "linear" + *--------------------------------------------- + * After the prefix, the name describes the Image of the pan law in the LR plane. + * (remember that each pan law is a parametrized curve in LR plane. + * E.g.: + * "ConstantSum": + * it's properly used in an anechoic room, where there are no reflections. + * Ensures uniform volumes in MONO export, + * has -6.02 dB center compensation. + * "ConstantPower": + * probably makes uniform volumes in a common room, + * has -3.01 dB center compensation. + * "ConstantKNorm": + * L^k + R^k = const + * generalises constant sum (k = 1) and constant power (k = 2) + * "StraightPolygonal": + * one gain is constant while the other varies. + * It's ideal as BALANCE law of DUAL-channel track, + * has 0 dB center compensation. + */ + enum PAN_LAW_TYPES { + RATIO_STRAIGHT_POLYGONAL = 0, + RATIO_CONST_POWER, + RATIO_CONST_SUM, + LINEAR_STRAIGHT_POLYGONAL, + LINEAR_CONST_POWER, + LINEAR_CONST_SUM, + POLAR_STRAIGHT_POLYGONAL, + POLAR_CONST_POWER, + POLAR_CONST_SUM, + QUADRATIC_STRAIGHT_POLYGONAL, + QUADRATIC_CONST_POWER, + QUADRATIC_CONST_SUM, + LINEAR_CONST_K_NORM, + RATIO_CONST_K_NORM, + POLAR_CONST_K_NORM, + QUADRATIC_CONST_K_NORM + }; + + /** default k for pan law with such that L^k + R^k = const + * must be initialised in Sampler.cpp + */ + static const float K_NORM_DEFAULT; + + + // pan law functions + static float ratioStraightPolygonalPanLaw( float fPan ); + static float ratioConstPowerPanLaw( float fPan ); + static float ratioConstSumPanLaw( float fPan ); + static float linearStraightPolygonalPanLaw( float fPan ); + static float linearConstPowerPanLaw( float fPan ); + static float linearConstSumPanLaw( float fPan ); + static float polarStraightPolygonalPanLaw( float fPan ); + static float polarConstPowerPanLaw( float fPan ); + static float polarConstSumPanLaw( float fPan ); + static float quadraticStraightPolygonalPanLaw( float fPan ); + static float quadraticConstPowerPanLaw( float fPan ); + static float quadraticConstSumPanLaw( float fPan ); + // customly compensated + static float linearConstKNormPanLaw( float fPan, float k ); + static float polarConstKNormPanLaw( float fPan, float k ); + static float ratioConstKNormPanLaw( float fPan, float k ); + static float quadraticConstKNormPanLaw( float fPan, float k ); + + + /** This necessary function + * returns the single pan parameter in [-1,1] from the L,R gains + * as it was input from the GUI (up to scale and translation, which is arbitrary) + */ + static float getRatioPan( float fPan_L, float fPan_R ); + + + float* m_pMainOut_L; ///< sampler main out (left channel) float* m_pMainOut_R; ///< sampler main out (right channel) @@ -108,8 +210,7 @@ class Sampler : public H2Core::Object * layer will be loaded with a nullptr instead. */ void reinitializePlaybackTrack(); - - + private: std::vector m_playingNotesQueue; std::vector m_queuedNoteOffs; @@ -129,6 +230,11 @@ class Sampler : public H2Core::Object int m_nPlayBackSamplePosition; + /** function to direct the computation to the selected pan law function + */ + float panLaw( float fPan, Song* pSong ); + + bool processPlaybackTrack(int nBufferSize); @@ -170,6 +276,7 @@ class Sampler : public H2Core::Object ); }; + } // namespace #endif diff --git a/src/gui/src/Mixer/Mixer.cpp b/src/gui/src/Mixer/Mixer.cpp index 5269060c4..ca36b3d7b 100644 --- a/src/gui/src/Mixer/Mixer.cpp +++ b/src/gui/src/Mixer/Mixer.cpp @@ -29,6 +29,7 @@ #include "../InstrumentEditor/InstrumentEditorPanel.h" #include "../Widgets/Button.h" #include "../Widgets/PixmapWidget.h" +#include "MixerSettingsDialog.h" #include #include @@ -107,6 +108,18 @@ Mixer::Mixer( QWidget* pParent ) m_pMasterLine = new MasterMixerLine( nullptr ); m_pMasterLine->move( 0, 0 ); connect( m_pMasterLine, SIGNAL( volumeChanged(MasterMixerLine*) ), this, SLOT( masterVolumeChanged(MasterMixerLine*) ) ); + + m_pOpenMixerSettingsBtn = new Button( + m_pMasterLine, + "/mixerPanel/openMixerSettings_over.png", + "/mixerPanel/openMixerSettings_off.png", + "/mixerPanel/openMixerSettings_over.png", + QSize(17, 17) + ); + m_pOpenMixerSettingsBtn->move( 96, 6 ); + m_pOpenMixerSettingsBtn->setToolTip( tr( "Mixer Settings" ) ); + connect( m_pOpenMixerSettingsBtn, SIGNAL( clicked( Button* ) ), this, SLOT( openMixerSettingsDialog( Button* ) ) ); + m_pShowFXPanelBtn = new ToggleButton( m_pMasterLine, @@ -819,3 +832,8 @@ void Mixer::getPeaksInMixerLine( uint nMixerLine, float& fPeak_L, float& fPeak_R fPeak_R = 0; } } + +void Mixer::openMixerSettingsDialog( Button* ref ) { + MixerSettingsDialog mixerSettingsDialog( this ); // use this as *parent because button makes smaller fonts + mixerSettingsDialog.exec(); +} diff --git a/src/gui/src/Mixer/Mixer.h b/src/gui/src/Mixer/Mixer.h index cb03ea6f3..193a9e20a 100644 --- a/src/gui/src/Mixer/Mixer.h +++ b/src/gui/src/Mixer/Mixer.h @@ -73,6 +73,7 @@ class Mixer : public QWidget, public EventListener, public H2Core::Object void updateMixer(); void showFXPanelClicked(Button* ref); void showPeaksBtnClicked(Button* ref); + void openMixerSettingsDialog( Button* ref ); void ladspaActiveBtnClicked( LadspaFXMixerLine* ref ); void ladspaEditBtnClicked( LadspaFXMixerLine *ref ); void ladspaVolumeChanged( LadspaFXMixerLine* ref); @@ -85,6 +86,7 @@ class Mixer : public QWidget, public EventListener, public H2Core::Object QScrollArea* m_pFaderScrollArea; ToggleButton * m_pShowFXPanelBtn; ToggleButton * m_pShowPeaksBtn; + Button * m_pOpenMixerSettingsBtn; MasterMixerLine * m_pMasterLine; QWidget * m_pFaderPanel; diff --git a/src/gui/src/Mixer/MixerLine.cpp b/src/gui/src/Mixer/MixerLine.cpp index da23b6fce..ae2dc50d7 100644 --- a/src/gui/src/Mixer/MixerLine.cpp +++ b/src/gui/src/Mixer/MixerLine.cpp @@ -337,22 +337,10 @@ void MixerLine::panChanged(Rotary *ref) Song *pSong = Hydrogen::get_instance()->getSong(); pSong->setIsModified( true ); emit panChanged( this ); - - float panValue = ref->getValue(); - float pan_L, pan_R; - if (panValue > 0.5) { - pan_L = (1.0 - panValue) * 2.0; - pan_R = 1.0; - } else { - pan_L = 1.0; - pan_R = panValue * 2.0; - } - - char m_pFaderPos[100]; - snprintf( m_pFaderPos, 99, "%#.2fL, %#.2fR", pan_L, pan_R); - HydrogenApp::get_instance()->setStatusBarMessage( tr( "Set instr. pan [%1]" ).arg( m_pFaderPos ), 2000 ); - - m_pPanRotary->setToolTip( QString("Pan ") + QString( m_pFaderPos ) ); + /** Do not update tooltip nor print status message in the old fashion panL and panL style + * since inconsistent with new pan implementation. The resultant pan depends also on note pan. + * The rotary widget valuetip is enough to read the value. + */ } float MixerLine::getPan() @@ -364,17 +352,10 @@ void MixerLine::setPan(float fValue) { if ( fValue != m_pPanRotary->getValue() ) { m_pPanRotary->setValue( fValue ); - float pan_L, pan_R; - if (fValue > 0.5) { - pan_L = (1.0 - fValue) * 2.0; - pan_R = 1.0; - } else { - pan_L = 1.0; - pan_R = fValue * 2.0; - } - char m_pFaderPos[100]; - snprintf( m_pFaderPos, 99,"Pan %#.2fL, %#.2fR", pan_L, pan_R); - m_pPanRotary->setToolTip( QString( m_pFaderPos ) ); + /** Do not update tooltip in the old fashion panL and panL style + * since inconsistent with new pan implementation. The resultant pan depends also on note pan. + * The rotary widget valuetip is enough to read the value. + */ } } diff --git a/src/gui/src/Mixer/MixerSettingsDialog.cpp b/src/gui/src/Mixer/MixerSettingsDialog.cpp new file mode 100644 index 000000000..2555821e6 --- /dev/null +++ b/src/gui/src/Mixer/MixerSettingsDialog.cpp @@ -0,0 +1,177 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * + * http://www.hydrogen-music.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include "Skin.h" +#include "MixerSettingsDialog.h" +#include "HydrogenApp.h" +#include "MainForm.h" + +#include "qmessagebox.h" +#include "qstylefactory.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "SongEditor/SongEditor.h" +#include "SongEditor/SongEditorPanel.h" + + +using namespace H2Core; + +const char* MixerSettingsDialog::__class_name = "MixerSettingsDialog"; + +MixerSettingsDialog::MixerSettingsDialog(QWidget* parent) + : QDialog( parent ) + , Object( __class_name ) +{ + setupUi( this ); + + setWindowTitle( tr( "Mixer Settings" ) ); + + setMinimumSize( width(), height() ); + + Song* pSong = Hydrogen::get_instance()->getSong(); + + /* insert the items here so they work consistently no matter of their order in the menu (except the headings) + */ + + // heading + panLawComboBox->addItem( tr("------ Linear pan parameter ------"), QVariant( -10000 ) ); + qobject_cast< QStandardItemModel * >( panLawComboBox->model() )->item( 0 )->setEnabled( false ); + panLawComboBox->addItem( tr("Balance Law (0dB)"), QVariant( Sampler::LINEAR_STRAIGHT_POLYGONAL ) ); + panLawComboBox->addItem( tr("Constant Power (-3dB)"), QVariant( Sampler::LINEAR_CONST_POWER ) ); + panLawComboBox->addItem( tr("Constant Sum (-6dB)"), QVariant( Sampler::LINEAR_CONST_SUM ) ); + panLawComboBox->addItem( tr("Constant k-Norm (Custom dB compensation)"), + QVariant( Sampler::LINEAR_CONST_K_NORM ) ); + panLawComboBox->insertSeparator(100); + + // heading + panLawComboBox->addItem( tr("------ Polar pan parameter ------"), QVariant( -10000 ) ); + qobject_cast< QStandardItemModel * >( panLawComboBox->model() )->item( 6 )->setEnabled( false ); + panLawComboBox->addItem( tr("Balance Law (0dB)"), QVariant( Sampler::POLAR_STRAIGHT_POLYGONAL ) ); + panLawComboBox->addItem( tr("Constant Power (-3dB)"), QVariant( Sampler::POLAR_CONST_POWER ) ); + panLawComboBox->addItem( tr("Constant Sum (-6dB)"), QVariant( Sampler::POLAR_CONST_SUM ) ); + panLawComboBox->addItem( tr("Constant k-Norm (Custom dB compensation)"), + QVariant( Sampler::POLAR_CONST_K_NORM ) ); + panLawComboBox->insertSeparator(100); + + // heading + panLawComboBox->addItem( tr("------ Ratio pan parameter ------"), QVariant( -10000 ) ); + qobject_cast< QStandardItemModel * >( panLawComboBox->model() )->item( 12 )->setEnabled( false ); + panLawComboBox->addItem( tr("Balance Law (0dB)"), QVariant( Sampler::RATIO_STRAIGHT_POLYGONAL ) ); + panLawComboBox->addItem( tr("Constant Power (-3dB)"), QVariant( Sampler::RATIO_CONST_POWER ) ); + panLawComboBox->addItem( tr("Constant Sum (-6dB)"), QVariant( Sampler::RATIO_CONST_SUM ) ); + panLawComboBox->addItem( tr("Constant k-Norm (Custom dB compensation)"), + QVariant( Sampler::RATIO_CONST_K_NORM ) ); + panLawComboBox->insertSeparator(100); + + // heading + panLawComboBox->addItem( tr("------ Quadratic pan parameter ------"), QVariant( -10000 ) ); + qobject_cast< QStandardItemModel * >( panLawComboBox->model() )->item( 18 )->setEnabled( false ); + panLawComboBox->addItem( tr("Balance Law (0dB)"), QVariant( Sampler::QUADRATIC_STRAIGHT_POLYGONAL ) ); + panLawComboBox->addItem( tr("Constant Power (-3dB)"), QVariant( Sampler::QUADRATIC_CONST_POWER ) ); + panLawComboBox->addItem( tr("Constant Sum (-6dB)"), QVariant( Sampler::QUADRATIC_CONST_SUM ) ); + panLawComboBox->addItem( tr("Constant k-Norm (Custom dB compensation)"), + QVariant( Sampler::QUADRATIC_CONST_K_NORM ) ); + + panLawComboBox->setCurrentIndex( panLawComboBox->findData( pSong->getPanLawType() ) ); + panLawChanged(); // to hide custom dB SPL compensation + panLawComboBox->setToolTip( tr("Relationship between the sound's apparent image position and the pan knob control" + ) ); + + connect(panLawComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT( panLawChanged() )); + + /** dB SPL Center Compensation, audio engineers friendly. + * (will be converted to the corresponding k assuming L^k + R^k = 1 ) + */ + QValidator *validator = new QDoubleValidator( -10000., 0., 20, this ); + dBCompensationLineEdit->setValidator( validator ); + dBCompensationLineEdit->setText( QString( "%1" ).arg( -6.0206 / pSong->getPanLawKNorm() ) ); +} + + + + +MixerSettingsDialog::~MixerSettingsDialog() +{ + INFOLOG("~MIXER_SETTINGS_DIALOG"); +} + + + +void MixerSettingsDialog::on_cancelBtn_clicked() +{ + reject(); +} + + +void MixerSettingsDialog::on_okBtn_clicked() { + Song* pSong = Hydrogen::get_instance()->getSong(); + bool bOk; + + // Pan Law settings + pSong->setPanLawType( ( panLawComboBox->currentData() ).toInt( &bOk ) ); + + // allowing both point or comma decimal separator + float fdBCenterCompensation = ( dBCompensationLineEdit->text() ).replace( ",", "." ).toFloat( &bOk ); + if ( !bOk ) { // this should not happen + QMessageBox::information( this, "Hydrogen", tr( "dB Center Compensation rejected" ) ); + return; + } else if ( fdBCenterCompensation > -0.01 ) { + /** reject small absolute values since computer approximation (k tends rapidly to infinity at 0) + * and obviously reject positive values to not boost the center (compensation has the opposite aim). + */ + QMessageBox::information( this, "Hydrogen", tr( "dB Center Compensation must be less than -0.01" ) ); + return; + } + /** convert the dB Compensation to the corresponding exponent k: assuming constraint L^k + R^k = 1 + * For example -6.0206 dB <=> k = 1 <=> L + R = 1 (i.e. constant sum) + */ + pSong->setPanLawKNorm( - 6.0206 / fdBCenterCompensation ); + + accept(); +} + +void MixerSettingsDialog::panLawChanged(){ // hide/show some widgets + bool bOk; + int nPanLawType = ( panLawComboBox->currentData() ).toInt( &bOk); + if ( nPanLawType == Sampler::LINEAR_CONST_K_NORM + || nPanLawType == Sampler::POLAR_CONST_K_NORM + || nPanLawType == Sampler::RATIO_CONST_K_NORM + || nPanLawType == Sampler::QUADRATIC_CONST_K_NORM + ) + { + dBCompensationLineEdit->show(); + dBCompensationLbl->show(); + } else { + dBCompensationLineEdit->hide(); + dBCompensationLbl->hide(); + } +} diff --git a/src/gui/src/Mixer/MixerSettingsDialog.h b/src/gui/src/Mixer/MixerSettingsDialog.h new file mode 100644 index 000000000..6297f434f --- /dev/null +++ b/src/gui/src/Mixer/MixerSettingsDialog.h @@ -0,0 +1,49 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * + * http://www.hydrogen-music.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef MIXER_SETTINGS_DIALOG_H +#define MIXER_SETTINGS_DIALOG_H + + +#include "ui_MixerSettingsDialog_UI.h" + +#include + +/// +/// Mixer Settings Dialog +/// +class MixerSettingsDialog : public QDialog, private Ui_MixerSettingsDialog_UI, public H2Core::Object +{ + H2_OBJECT + Q_OBJECT + public: + explicit MixerSettingsDialog( QWidget* parent ); + ~MixerSettingsDialog(); + + private slots: + void on_okBtn_clicked(); + void on_cancelBtn_clicked(); + void panLawChanged(); +}; + +#endif + diff --git a/src/gui/src/Mixer/MixerSettingsDialog_UI.ui b/src/gui/src/Mixer/MixerSettingsDialog_UI.ui new file mode 100644 index 000000000..ee572c8a5 --- /dev/null +++ b/src/gui/src/Mixer/MixerSettingsDialog_UI.ui @@ -0,0 +1,197 @@ + + + MixerSettingsDialog_UI + + + + 0 + 0 + 490 + 157 + + + + + + 10 + 40 + 471 + 26 + + + + + 0 + 26 + + + + + 500 + 16777215 + + + + + + + 10 + 20 + 121 + 18 + + + + Select Pan Law: + + + + + + 10 + 120 + 471 + 28 + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 184 + 16 + + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + &Cancel + + + Alt+C + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + &OK + + + Alt+O + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 183 + 16 + + + + + + + + + + 10 + 80 + 471 + 28 + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 184 + 16 + + + + + + + + + 246 + 16777215 + + + + dB SPL Center Compensation + + + + + + + + 80 + 16777215 + + + + + + + + + + + diff --git a/src/tests/data/functional/test.ref.flac b/src/tests/data/functional/test.ref.flac index b6ba254d1..77113c71d 100644 Binary files a/src/tests/data/functional/test.ref.flac and b/src/tests/data/functional/test.ref.flac differ