From e88c117497806be414641bf3dbbfef1b47127f96 Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Sat, 28 Apr 2018 13:10:37 +0200 Subject: [PATCH 1/7] tweaking the humanizer I just took a look at the humanizer and there a couple of things I do not understand and which are probably not intended to work the way they are doing right now. First of all both the humanization of the velocity and the pitch in *hydrogen.cpp* feature a negative bias of the random variable. Firstly, a random generated and afterwards a constant value determined by the get_humanize_velocity_value() and the get_random_pitch_factor() is subtracted. Is there a reason for this bias? In randomizing the pitch the random variable, which is drawn using the getGaussian() function, is only scaled using the fMaxPitchDeviation value but not with the random pitch factor of the corresponding instrument. The latter is only introduced as a negative bias. This does not seem right. For a better readbility I renamed the input variable of the getGaussian() function into "variance" since this is what it actually is: the variance of the distribution the random variable is drawn from. Also I would propose to use the get_humanize... values as a input to the getGaussian function so it is obvious that the variance will be scaled using the knots for the humanizer. There are two variables *fMaxPitchDeviation* and *nMaxTimeHumanize* defined in the source and only used for the scaling. I expect these are intended to bound the random variable. Since we are generating Gaussian white noise, the random variable is (in theory) able to take any number between minus and plus infinity. I used the two variables instead to bound the random variable to assure plausible fluctuations. But this might messed up the scaling applied here. I just looked at the humanizer so far and lack the overall knowledge of the internal working of Hydrogen. Could someone take a look at the scaling here? Last but not least I have a question: What exactly is the *swing* intended to accomplish? It is implemented as a constant and positive additive term to the offset of a note. No randomization or whatsoever takes place here. correct scaling of the humanizer inputs Using integration by substitution on the second moment of a distribution one can verify that the scaling of a random variable by a factor `a` multiplies its standard deviation by `a` and its variance by `a^2`. `var(a*x)=a^2var(x)` Therefore, the inputs of the knots of the humanizers have to be squared in order to have the same experience as beforehand. In other words, up to now the behavior was as follows: draw a Gaussian random number with a variance of 0.2 and scale the process by a factor controlled via the rotator buttons, which will scale the variance of the process by the square of their value. I would vote to streamline this behavior by introducing the right range and scaling to the rotator button itself and to display the variance of the white noise in the info region in Hydrogen after moving the button. verbose the maximal humanization variance The factors 0.2 or 0.3 in the calls to `getGaussian` in the humanization parts do act as an upper bound for the variance of the Gaussian white noise. By assigning those value to an approriately names variable their role becomes much more apparent. --- src/core/src/hydrogen.cpp | 56 +++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/core/src/hydrogen.cpp b/src/core/src/hydrogen.cpp index d863146b5..e3b148245 100644 --- a/src/core/src/hydrogen.cpp +++ b/src/core/src/hydrogen.cpp @@ -208,7 +208,7 @@ inline int randomValue( int max ) return rand() % max; } -inline float getGaussian( float z ) +inline float getGaussian( float variance ) { // gaussian distribution -- dimss float x1, x2, w; @@ -219,7 +219,7 @@ inline float getGaussian( float z ) } while ( w >= 1.0 ); w = sqrtf( ( -2.0 * logf( w ) ) / w ); - return x1 * w * z + 0.0; // tunable + return x1 * w * variance + 0.0; // tunable } void audioEngine_raiseError( unsigned nErrorCode ) @@ -505,12 +505,13 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) } if ( pSong->get_humanize_velocity_value() != 0 ) { - float random = pSong->get_humanize_velocity_value() * getGaussian( 0.2 ); - pNote->set_velocity( - pNote->get_velocity() - + ( random - - ( pSong->get_humanize_velocity_value() / 2.0 ) ) - ); + // Ensure the generated Gaussian random variables + // won't have a variance bigger than the value. + const float maximalHumanizationVelocityVariance = 0.2; + pNote->set_velocity( pNote->get_velocity() + + getGaussian( pSong->get_humanize_velocity_value() * + pSong->get_humanize_velocity_value() * + maximalHumanizationVelocityVariance ) ); if ( pNote->get_velocity() > 1.0 ) { pNote->set_velocity( 1.0 ); } else if ( pNote->get_velocity() < 0.0 ) { @@ -520,11 +521,21 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) // Random Pitch ;) const float fMaxPitchDeviation = 2.0; + const float maximalPitchVariance = 0.2; + float randomPitch = getGaussian( pNote->get_instrument()->get_random_pitch_factor() * + pNote->get_instrument()->get_random_pitch_factor() * + maximalPitchVariance ); + // Since a Gaussian white noise is unbound we + // have to verify the random pitch shift does + // not exceed the value of maximal pitch + // deviation. + if ( randomPitch > fMaxPitchDeviation ){ + randomPitch = fMaxPitchDeviation; + } else if ( randomPitch < -1.0 * fMaxPitchDeviation ){ + randomPitch = -1.0 * fMaxPitchDeviation; + } pNote->set_pitch( pNote->get_pitch() - + ( fMaxPitchDeviation * getGaussian( 0.2 ) - - fMaxPitchDeviation / 2.0 ) - * pNote->get_instrument()->get_random_pitch_factor() ); - + + randomPitch ); /* * Check if the current instrument has the property "Stop-Note" set. @@ -1293,11 +1304,22 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) // Humanize - Time parameter if ( pSong->get_humanize_time_value() != 0 ) { - nOffset += ( int )( - getGaussian( 0.3 ) - * pSong->get_humanize_time_value() - * nMaxTimeHumanize - ); + + // Ensure the generated Gaussian random variables + // won't have a variance bigger than the value. + const float maximalHumanizationTimeVariance = 0.3; + float randomHumanizeTime = getGaussian( maximalHumanizationTimeVariance * + pSong->get_humanize_time_value() * + pSong->get_humanize_time_value() ); + // Since a Gaussian white noise is unbound we + // have to verify the random time shift does + // not exceed the maximal value. + if ( ( int ) randomHumanizeTime > nMaxTimeHumanize ){ + randomHumanizeTime = ( float ) nMaxTimeHumanize; + } else if ( ( int ) randomHumanizeTime < -1* nMaxTimeHumanize ){ + randomHumanizeTime = ( float ) -1* nMaxTimeHumanize; + } + nOffset += ( int ) randomHumanizeTime; } //~ // Lead or Lag - timing parameter From 240eefe18f41acc000990c15a3b0016438d5a26c Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Fri, 4 May 2018 12:50:09 +0200 Subject: [PATCH 2/7] adjust mixerline output for small window sizes in small window sizes the phrase "Set humanize velocity parameter" needs some time to run through notification area of the mixer. Thus, one is not able to see the actual value when still rotating the knot. Instead, I moved the displayed value to the front of the display message to visible at all times. --- src/gui/src/Mixer/MixerLine.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/src/Mixer/MixerLine.cpp b/src/gui/src/Mixer/MixerLine.cpp index 28b39edf9..73f2ee704 100644 --- a/src/gui/src/Mixer/MixerLine.cpp +++ b/src/gui/src/Mixer/MixerLine.cpp @@ -841,15 +841,15 @@ void MasterMixerLine::rotaryChanged( Rotary *pRef ) if ( pRef == m_pHumanizeTimeRotary ) { pEngine->getSong()->set_humanize_time_value( fVal ); - sMsg = trUtf8( "Set humanize time parameter [%1]").arg( fVal, 0, 'f', 2 ); + sMsg = trUtf8( "[%1]: set humanize time parameter").arg( fVal, 0, 'f', 2 ); } else if ( pRef == m_pHumanizeVelocityRotary ) { pEngine->getSong()->set_humanize_velocity_value( fVal ); - sMsg = trUtf8( "Set humanize velocity parameter [%1]").arg( fVal, 0, 'f', 2 ); + sMsg = trUtf8( "[%1]: set humanize velocity parameter").arg( fVal, 0, 'f', 2 ); } else if ( pRef == m_pSwingRotary ) { pEngine->getSong()->set_swing_factor( fVal ); - sMsg = trUtf8( "Set swing factor [%1]").arg( fVal, 0, 'f', 2 ); + sMsg = trUtf8( "[%1]: set swing factor").arg( fVal, 0, 'f', 2 ); } else { ERRORLOG( "[knobChanged] Unhandled knob" ); From 2e0feb6606ad61ceac31d8c3e476bb59889f5724 Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Fri, 6 Jul 2018 19:49:41 +0200 Subject: [PATCH 3/7] Choosing noise color in masterMixerLine The Rotary knob for the **swing** was removed (because I still do not see any use of this variable and no one of the Hydrogen team responds to my questions). Instead two `QComboBox`s have been added via `LCDCombo` objects. They will be used as drop down menus to select the color of the corresponding noise. Those colors can be indiviually chosen for both the humanization of the velocity and the onset. The background picture of the master strip of the MixerLine was changed as well to nicely inegrate the changes. --- .../mixerPanel/masterMixerline_background.png | Bin 7023 -> 8466 bytes src/gui/src/Mixer/MixerLine.cpp | 67 ++++++++++++++---- src/gui/src/Mixer/MixerLine.h | 11 ++- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/data/img/gray/mixerPanel/masterMixerline_background.png b/data/img/gray/mixerPanel/masterMixerline_background.png index 57688d3cbff8a3e36629e6169a489205384a7bb4..964895be72d1b2a40776ffcf43617298452f25b7 100644 GIT binary patch literal 8466 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 a6+7h#y?zItx*HfC5nNG2p;GR3@P7cmI?)UO literal 7023 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 Gk^cv|taB{@ diff --git a/src/gui/src/Mixer/MixerLine.cpp b/src/gui/src/Mixer/MixerLine.cpp index 73f2ee704..8c6de6dcf 100644 --- a/src/gui/src/Mixer/MixerLine.cpp +++ b/src/gui/src/Mixer/MixerLine.cpp @@ -31,6 +31,7 @@ #include "../widgets/Rotary.h" #include "../widgets/Button.h" #include "../widgets/LCD.h" +#include "../widgets/LCDCombo.h" #include #include @@ -685,17 +686,40 @@ MasterMixerLine::MasterMixerLine(QWidget* parent) lcdPalette.setColor( QPalette::Background, QColor( 49, 53, 61 ) ); m_pPeakLCD->setPalette( lcdPalette ); - m_pHumanizeVelocityRotary = new Rotary( this, Rotary::TYPE_NORMAL, trUtf8( "Humanize velocity" ), false, false ); + /// Humanizer + // Rotary buttons controlling the scaling factor of the + // distribution the random values are drawn from. + m_pHumanizeVelocityRotary = new Rotary( this, Rotary::TYPE_NORMAL, + trUtf8( "Humanize velocity" ), + false, false ); m_pHumanizeVelocityRotary->move( 74, 88 ); - connect( m_pHumanizeVelocityRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); - - m_pHumanizeTimeRotary = new Rotary( this, Rotary::TYPE_NORMAL, trUtf8( "Humanize time" ), false, false ); - m_pHumanizeTimeRotary->move( 74, 125 ); - connect( m_pHumanizeTimeRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); - - m_pSwingRotary = new Rotary( this, Rotary::TYPE_NORMAL, trUtf8( "Swing" ), false, false ); - m_pSwingRotary->move( 74, 162 ); - connect( m_pSwingRotary, SIGNAL( valueChanged(Rotary*) ), this, SLOT( rotaryChanged(Rotary*) ) ); + connect( m_pHumanizeVelocityRotary, SIGNAL( valueChanged(Rotary*) ), + this, SLOT( rotaryChanged(Rotary*) ) ); + + m_pHumanizeTimeRotary = new Rotary( this, Rotary::TYPE_NORMAL, + trUtf8( "Humanize time" ), false, false ); + m_pHumanizeTimeRotary->move( 74, 153 ); + connect( m_pHumanizeTimeRotary, SIGNAL( valueChanged(Rotary*) ), + this, SLOT( rotaryChanged(Rotary*) ) ); + + // ComboBoxes choosing the color of the corresponding noise. + m_pHumanizeColorTimeLCDCombo = new LCDCombo( this, 5 ); + m_pHumanizeColorTimeLCDCombo->move( 60, 123 ); + m_pHumanizeColorTimeLCDCombo->setToolTip( trUtf8( "Select noise color" ) ); + m_pHumanizeColorTimeLCDCombo->addItem( QString( "white" ) ); + m_pHumanizeColorTimeLCDCombo->addItem( QString( "pink" ) ); + m_pHumanizeColorTimeLCDCombo->select( 1 ); + connect( m_pHumanizeColorTimeLCDCombo, SIGNAL( valueChanged( int ) ), + this, SLOT( humanizeColorChanged( int ) ) ); + + m_pHumanizeColorVelocityLCDCombo = new LCDCombo( this, 5 ); + m_pHumanizeColorVelocityLCDCombo->move( 60, 188 ); + m_pHumanizeColorVelocityLCDCombo->setToolTip( trUtf8( "Select noise color" ) ); + m_pHumanizeColorVelocityLCDCombo->addItem( QString( "white" ) ); + m_pHumanizeColorVelocityLCDCombo->addItem( QString( "pink" ) ); + m_pHumanizeColorVelocityLCDCombo->select( 0 ); + connect( m_pHumanizeColorVelocityLCDCombo, SIGNAL( valueChanged( int ) ), + this, SLOT( humanizeColorChanged( int ) ) ); // Mute btn m_pMuteBtn = new ToggleButton( @@ -823,7 +847,6 @@ void MasterMixerLine::updateMixerLine() if ( pSong ) { m_pHumanizeTimeRotary->setValue( pSong->get_humanize_time_value() ); m_pHumanizeVelocityRotary->setValue( pSong->get_humanize_velocity_value() ); - m_pSwingRotary->setValue( pSong->get_swing_factor() ); m_pMuteBtn->setPressed( pSong->__is_muted ); } else { @@ -847,10 +870,6 @@ void MasterMixerLine::rotaryChanged( Rotary *pRef ) pEngine->getSong()->set_humanize_velocity_value( fVal ); sMsg = trUtf8( "[%1]: set humanize velocity parameter").arg( fVal, 0, 'f', 2 ); } - else if ( pRef == m_pSwingRotary ) { - pEngine->getSong()->set_swing_factor( fVal ); - sMsg = trUtf8( "[%1]: set swing factor").arg( fVal, 0, 'f', 2 ); - } else { ERRORLOG( "[knobChanged] Unhandled knob" ); } @@ -860,6 +879,24 @@ void MasterMixerLine::rotaryChanged( Rotary *pRef ) ( HydrogenApp::get_instance() )->setStatusBarMessage( sMsg, 2000 ); } +void MasterMixerLine::humanizeColorChanged( int pSelection ){ + + QString statusMessage; + + Hydrogen *pEngine = Hydrogen::get_instance(); + AudioEngine::get_instance()->lock( RIGHT_HERE ); + + if ( pSelection == 0 ){ + statusMessage = trUtf8( "White noise selected" ); + } else if ( pSelection == 1 ){ + statusMessage = trUtf8( "Pink noise selected" ); + } else + ERRORLOG( "[humanizeColorChanged] Unknown noise selected. Using white noise instead." ); + + AudioEngine::get_instance()->unlock(); + ( HydrogenApp::get_instance() )->setStatusBarMessage( statusMessage, 2000 ); +} + ///////////////////////////////////////// diff --git a/src/gui/src/Mixer/MixerLine.h b/src/gui/src/Mixer/MixerLine.h index 8d2301bad..d02745aa3 100644 --- a/src/gui/src/Mixer/MixerLine.h +++ b/src/gui/src/Mixer/MixerLine.h @@ -31,6 +31,7 @@ #include #include + class Fader; class MasterFader; class PanFader; @@ -41,6 +42,7 @@ class InstrumentPropertiesDialog; class InstrumentNameWidget; class LCDDisplay; class Rotary; +class LCDCombo; #include "../widgets/PixmapWidget.h" @@ -252,6 +254,7 @@ class MasterMixerLine: public PixmapWidget public slots: void faderChanged(MasterFader * ref); void rotaryChanged( Rotary *pRef ); + void humanizeColorChanged( int ); void muteClicked(Button*); private: @@ -267,9 +270,11 @@ class MasterMixerLine: public PixmapWidget LCDDisplay * m_pPeakLCD; - Rotary * m_pSwingRotary; - Rotary * m_pHumanizeTimeRotary; - Rotary * m_pHumanizeVelocityRotary; + Rotary *m_pHumanizeTimeRotary; + Rotary *m_pHumanizeVelocityRotary; + LCDCombo *m_pHumanizeColorTimeLCDCombo; + LCDCombo *m_pHumanizeColorVelocityLCDCombo; + ToggleButton * m_pMuteBtn; }; From 378274ebe9f9775ba129894bc76ac606c388182f Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Sun, 8 Jul 2018 18:30:27 +0200 Subject: [PATCH 4/7] handling of the humanization color box signals The Hydrogen song does now contain the additional functions `get_humanize_time_color`, `set_humanize_time_color` and the equivalent for the velocity. The internal state is stored in the `__humanize_time_color` and `__humanize_velocity_color` intt variables. Both states are written and loaded to file properly now. Every last remaining instance of the swing factor had been removed. first draft of the noise generating functions fixing bugs in auxiliary impl of humanizer The random seed did have the wrong type causing the random number generator to spit `nan`. The factor `maximalHumanizationTimeVariance` has been enlarged. Since the resulting random number will be floored, there is absolutely no point in having it set to 1. --- src/core/include/hydrogen/basics/song.h | 37 ++++++- src/core/include/hydrogen/hydrogen.h | 21 +++- src/core/src/basics/song.cpp | 24 ++--- src/core/src/hydrogen.cpp | 135 +++++++++++++++++------- src/core/src/local_file_mgr.cpp | 3 +- src/gui/src/Mixer/MixerLine.cpp | 22 +++- src/gui/src/Mixer/MixerLine.h | 2 +- 7 files changed, 179 insertions(+), 65 deletions(-) diff --git a/src/core/include/hydrogen/basics/song.h b/src/core/include/hydrogen/basics/song.h index d450a56b3..b8a63887a 100644 --- a/src/core/include/hydrogen/basics/song.h +++ b/src/core/include/hydrogen/basics/song.h @@ -203,12 +203,38 @@ class Song : public H2Core::Object __humanize_velocity_value = value; } - float get_swing_factor() - { - return __swing_factor; + int get_humanize_time_color() + { + return __humanize_time_color; + } + void set_humanize_time_color( int color ) + { + // Colors: + // 0 - white noise + // 1 - pink noise + if ( color < 0 | color > 1 ){ + ERRORLOG( "Wrong color code in the humanizer. Resetting it to white noise" ); + color = 0; + } + __humanize_time_color = color; } - void set_swing_factor( float factor ); + int get_humanize_velocity_color() + { + return __humanize_velocity_color; + } + void set_humanize_velocity_color( int color ) + { + // Colors: + // 0 - white noise + // 1 - pink noise + if ( color < 0 | color > 1 ){ + ERRORLOG( "Wrong color code in the humanizer. Resetting it to white noise" ); + color = 0; + } + __humanize_velocity_color = color; + } + SongMode get_mode() { return __song_mode; @@ -295,7 +321,8 @@ class Song : public H2Core::Object bool __is_loop_enabled; float __humanize_time_value; float __humanize_velocity_value; - float __swing_factor; + int __humanize_time_color; + int __humanize_velocity_color; bool __is_modified; std::map< float, int> __latest_round_robins; SongMode __song_mode; diff --git a/src/core/include/hydrogen/hydrogen.h b/src/core/include/hydrogen/hydrogen.h index ea3d517f4..75e8e51e2 100644 --- a/src/core/include/hydrogen/hydrogen.h +++ b/src/core/include/hydrogen/hydrogen.h @@ -44,7 +44,25 @@ #define STATE_READY 4 // Ready to process audio #define STATE_PLAYING 5 // Currently playing a sequence. -inline int randomValue( int max ); +// *** Humanizer *** // +#define PI 3.1415926536f +#define PINK_NOISE_MAX_ROWS ( 30 ) +#define PINK_NOISE_BITS ( 24 ) +#define PINK_NOISE_SHIFT ( ( sizeof( long )* 8 ) - PINK_NOISE_BITS ) + +typedef struct { + long pink_Rows[PINK_NOISE_MAX_ROWS]; + long pink_RunningSum; /* Used to optimize summing of generators. */ + int pink_Index; /* Incremented each sample. */ + int pink_IndexMask; /* Index wrapped by ANDing with this mask. */ + float pink_Scalar; /* Used to scale within range of -1.0 to +1.0 */ +} PinkNoise; + +inline float get_random_white_uniform(); +inline float get_random_white_gaussian( float scale ); +inline float get_random_pink( PinkNoise *pink, float scale ); + +// ***************** // namespace H2Core { @@ -285,7 +303,6 @@ class Hydrogen : public H2Core::Object std::list __instrument_death_row; /// Deleting instruments too soon leads to potential crashes. - /// Private constructor Hydrogen(); diff --git a/src/core/src/basics/song.cpp b/src/core/src/basics/song.cpp index 24c0d7df0..4470dade2 100644 --- a/src/core/src/basics/song.cpp +++ b/src/core/src/basics/song.cpp @@ -75,7 +75,9 @@ Song::Song( const QString& name, const QString& author, float bpm, float volume , __is_loop_enabled( false ) , __humanize_time_value( 0.0 ) , __humanize_velocity_value( 0.0 ) - , __swing_factor( 0.0 ) + // Colors: 0 - white noise; 1 - pink noise + , __humanize_time_color( 0 ) + , __humanize_velocity_color( 0 ) , __song_mode( PATTERN_MODE ) , __components( NULL ) , __playback_track_enabled( false ) @@ -157,7 +159,8 @@ Song* Song::get_default_song() song->set_mode( Song::PATTERN_MODE ); song->set_humanize_time_value( 0.0 ); song->set_humanize_velocity_value( 0.0 ); - song->set_swing_factor( 0.0 ); + song->set_humanize_time_color( 0 ); + song->set_humanize_velocity_color( 0 ); InstrumentList* pList = new InstrumentList(); Instrument* pNewInstr = new Instrument( EMPTY_INSTR_ID, "New instrument" ); @@ -213,17 +216,6 @@ DrumkitComponent* Song::get_component( int ID ) } -void Song::set_swing_factor( float factor ) -{ - if ( factor < 0.0 ) { - factor = 0.0; - } else if ( factor > 1.0 ) { - factor = 1.0; - } - - __swing_factor = factor; -} - void Song::set_is_modified(bool is_modified) { bool Notify = false; @@ -446,7 +438,8 @@ Song* SongReader::readSong( const QString& filename ) 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 ); + int iHumanizeTimeColor = LocalFileMng::readXmlInt( songNode, "humanize_time_color", 0 ); + int iHumanizeVelocityColor = LocalFileMng::readXmlInt( songNode, "humanize_velocity_color", 0 ); song = new Song( sName, sAuthor, fBpm, fVolume ); song->set_metronome_volume( fMetronomeVolume ); @@ -456,7 +449,8 @@ Song* SongReader::readSong( const QString& filename ) song->set_mode( nMode ); song->set_humanize_time_value( fHumanizeTimeValue ); song->set_humanize_velocity_value( fHumanizeVelocityValue ); - song->set_swing_factor( fSwingFactor ); + song->set_humanize_time_color( iHumanizeTimeColor ); + song->set_humanize_velocity_color( iHumanizeVelocityColor ); song->set_playback_track_filename( sPlaybackTrack ); song->set_playback_track_enabled( bPlaybackTrackEnabled ); song->set_playback_track_volume( fPlaybackTrackVolume ); diff --git a/src/core/src/hydrogen.cpp b/src/core/src/hydrogen.cpp index e3b148245..0fdbd7fe9 100644 --- a/src/core/src/hydrogen.cpp +++ b/src/core/src/hydrogen.cpp @@ -203,25 +203,87 @@ inline timeval currentTime2() return now; } -inline int randomValue( int max ) +// *** Humanizer *** // +static int seed_random = std::rand(); +inline float get_random_white_uniform() { - return rand() % max; + // fast-float-ries + seed_random *= 16807; + return (float) seed_random* 4.6566129e-010f/2 + 0.5; } -inline float getGaussian( float variance ) -{ +inline float get_random_white_gaussian( float scale ){ // gaussian distribution -- dimss - float x1, x2, w; - do { - x1 = 2.0 * ( ( ( float ) rand() ) / RAND_MAX ) - 1.0; - x2 = 2.0 * ( ( ( float ) rand() ) / RAND_MAX ) - 1.0; - w = x1 * x1 + x2 * x2; - } while ( w >= 1.0 ); - w = sqrtf( ( -2.0 * logf( w ) ) / w ); - return x1 * w * variance + 0.0; // tunable + float randomUniform1 = get_random_white_uniform(); + float randomUniform2 = get_random_white_uniform(); + + return (float) sqrt( -2.0f * log( randomUniform1 ) )* + cos( 2.0f * PI * randomUniform2 ) * scale; } + +/* Setup PinkNoise structure for N rows of generators. */ +void InitializePinkNoise( PinkNoise *pink, int numRows ) +{ + int i; + long pmax; + pink->pink_Index = 0; + pink->pink_IndexMask = (1<pink_Scalar = 1.0f / pmax; +/* Initialize rows. */ + for( i=0; ipink_Rows[i] = 0; + pink->pink_RunningSum = 0; +} +inline float get_random_pink( PinkNoise *pink, float scale ){ + long newRandom; + long sum; + float output; + +/* Increment and mask index. */ + pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask; + +/* If index is zero, don't update any random values. */ + if( pink->pink_Index != 0 ) + { + /* Determine how many trailing zeros in PinkIndex. */ + /* This algorithm will hang if n==0 so test first. */ + // The while loop below shifts the index pink->pink_Index to + // the right until the leftmost bit is a zero. + int numZeros = 0; + int n = pink->pink_Index; + while( (n & 1) == 0 ) + { + n = n >> 1; + numZeros++; + } + + /* Replace the indexed ROWS random value. + * Subtract and add back to RunningSum instead of adding all the random + * values together. Only one changes each time. + */ + pink->pink_RunningSum -= pink->pink_Rows[numZeros]; + newRandom = ((long)get_random_white_uniform()) >> PINK_NOISE_SHIFT; + pink->pink_RunningSum += newRandom; + pink->pink_Rows[numZeros] = newRandom; + } + + /* Add extra white noise value. */ + // Discard the first PINK_RANDOM_SHIFT bits to reduce the + // generated random number of the size of PINK_RANDOM_BITS + // bits. + newRandom = ((long)get_random_white_uniform()) >> PINK_NOISE_SHIFT; + sum = pink->pink_RunningSum + newRandom; + + /* Scale to range of -1.0 to 0.9999. */ + output = pink->pink_Scalar * sum; + + return output * scale; +} +// ***************** // + void audioEngine_raiseError( unsigned nErrorCode ) { EventQueue::get_instance()->push_event( EVENT_ERROR, nErrorCode ); @@ -506,12 +568,12 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) if ( pSong->get_humanize_velocity_value() != 0 ) { // Ensure the generated Gaussian random variables - // won't have a variance bigger than the value. + // won't have a variance bigger than this value. const float maximalHumanizationVelocityVariance = 0.2; pNote->set_velocity( pNote->get_velocity() + - getGaussian( pSong->get_humanize_velocity_value() * - pSong->get_humanize_velocity_value() * - maximalHumanizationVelocityVariance ) ); + get_random_white_gaussian( pSong->get_humanize_velocity_value() * + pSong->get_humanize_velocity_value() * + maximalHumanizationVelocityVariance ) ); if ( pNote->get_velocity() > 1.0 ) { pNote->set_velocity( 1.0 ); } else if ( pNote->get_velocity() < 0.0 ) { @@ -522,9 +584,9 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) // Random Pitch ;) const float fMaxPitchDeviation = 2.0; const float maximalPitchVariance = 0.2; - float randomPitch = getGaussian( pNote->get_instrument()->get_random_pitch_factor() * - pNote->get_instrument()->get_random_pitch_factor() * - maximalPitchVariance ); + float randomPitch = get_random_white_gaussian( pNote->get_instrument()->get_random_pitch_factor() * + pNote->get_instrument()->get_random_pitch_factor() * + maximalPitchVariance ); // Since a Gaussian white noise is unbound we // have to verify the random pitch shift does // not exceed the value of maximal pitch @@ -534,8 +596,9 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) } else if ( randomPitch < -1.0 * fMaxPitchDeviation ){ randomPitch = -1.0 * fMaxPitchDeviation; } - pNote->set_pitch( pNote->get_pitch() - + randomPitch ); + + // pNote->set_pitch( pNote->get_pitch() + // + randomPitch ); /* * Check if the current instrument has the property "Stop-Note" set. @@ -1289,28 +1352,18 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) pNote->set_just_recorded( false ); int nOffset = 0; - // Swing - float fSwingFactor = pSong->get_swing_factor(); - - if ( ( ( m_nPatternTickPosition % 12 ) == 0 ) - && ( ( m_nPatternTickPosition % 24 ) != 0 ) ) { - // da l'accento al tick 4, 12, 20, 36... - nOffset += ( int )( - 6.0 - * m_pAudioDriver->m_transport.m_nTickSize - * fSwingFactor - ); - } - // Humanize - Time parameter if ( pSong->get_humanize_time_value() != 0 ) { // Ensure the generated Gaussian random variables // won't have a variance bigger than the value. - const float maximalHumanizationTimeVariance = 0.3; - float randomHumanizeTime = getGaussian( maximalHumanizationTimeVariance * - pSong->get_humanize_time_value() * - pSong->get_humanize_time_value() ); + // A large factor is needed in here. Since the + // `nOffset' variable is an integer, the + // `randomHumanizeTime` will be floored. Is this right? + const float maximalHumanizationTimeVariance = 3.3; + float randomHumanizeTime = get_random_white_gaussian( maximalHumanizationTimeVariance * + pSong->get_humanize_time_value() * + pSong->get_humanize_time_value() ); // Since a Gaussian white noise is unbound we // have to verify the random time shift does // not exceed the maximal value. @@ -1320,13 +1373,19 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) randomHumanizeTime = ( float ) -1* nMaxTimeHumanize; } nOffset += ( int ) randomHumanizeTime; + ___INFOLOG( QString( "randomHumanizeTime: %1" ).arg( randomHumanizeTime ) ); + ___INFOLOG( QString( "nOffset: %1" ).arg( nOffset ) ); + } + //~ // Lead or Lag - timing parameter nOffset += (int) ( pNote->get_lead_lag() * nLeadLagFactor); //~ + ___INFOLOG( QString( "after nOffset: %1" ).arg( nOffset ) ); + if((tick == 0) && (nOffset < 0)) { nOffset = 0; } diff --git a/src/core/src/local_file_mgr.cpp b/src/core/src/local_file_mgr.cpp index edf94f36b..8fd7d99e5 100644 --- a/src/core/src/local_file_mgr.cpp +++ b/src/core/src/local_file_mgr.cpp @@ -759,7 +759,8 @@ int SongWriter::writeSong( Song *song, const QString& filename ) LocalFileMng::writeXmlString( songNode, "humanize_time", QString("%1").arg( song->get_humanize_time_value() ) ); LocalFileMng::writeXmlString( songNode, "humanize_velocity", QString("%1").arg( song->get_humanize_velocity_value() ) ); - LocalFileMng::writeXmlString( songNode, "swing_factor", QString("%1").arg( song->get_swing_factor() ) ); + LocalFileMng::writeXmlString( songNode, "humanize_time_color", QString( "%1" ).arg( song->get_humanize_time_color() ) ); + LocalFileMng::writeXmlString( songNode, "humanize_velocity_color", QString( "%1" ).arg( song->get_humanize_velocity_color() ) ); // component List QDomNode componentListNode = doc.createElement( "componentList" ); diff --git a/src/gui/src/Mixer/MixerLine.cpp b/src/gui/src/Mixer/MixerLine.cpp index 8c6de6dcf..8a616b66f 100644 --- a/src/gui/src/Mixer/MixerLine.cpp +++ b/src/gui/src/Mixer/MixerLine.cpp @@ -708,7 +708,6 @@ MasterMixerLine::MasterMixerLine(QWidget* parent) m_pHumanizeColorTimeLCDCombo->setToolTip( trUtf8( "Select noise color" ) ); m_pHumanizeColorTimeLCDCombo->addItem( QString( "white" ) ); m_pHumanizeColorTimeLCDCombo->addItem( QString( "pink" ) ); - m_pHumanizeColorTimeLCDCombo->select( 1 ); connect( m_pHumanizeColorTimeLCDCombo, SIGNAL( valueChanged( int ) ), this, SLOT( humanizeColorChanged( int ) ) ); @@ -717,7 +716,6 @@ MasterMixerLine::MasterMixerLine(QWidget* parent) m_pHumanizeColorVelocityLCDCombo->setToolTip( trUtf8( "Select noise color" ) ); m_pHumanizeColorVelocityLCDCombo->addItem( QString( "white" ) ); m_pHumanizeColorVelocityLCDCombo->addItem( QString( "pink" ) ); - m_pHumanizeColorVelocityLCDCombo->select( 0 ); connect( m_pHumanizeColorVelocityLCDCombo, SIGNAL( valueChanged( int ) ), this, SLOT( humanizeColorChanged( int ) ) ); @@ -847,6 +845,8 @@ void MasterMixerLine::updateMixerLine() if ( pSong ) { m_pHumanizeTimeRotary->setValue( pSong->get_humanize_time_value() ); m_pHumanizeVelocityRotary->setValue( pSong->get_humanize_velocity_value() ); + m_pHumanizeColorTimeLCDCombo->select( pSong->get_humanize_time_color() ); + m_pHumanizeColorVelocityLCDCombo->select( pSong->get_humanize_velocity_color() ); m_pMuteBtn->setPressed( pSong->__is_muted ); } else { @@ -883,15 +883,31 @@ void MasterMixerLine::humanizeColorChanged( int pSelection ){ QString statusMessage; + QObject *pSignalSender = sender(); + Hydrogen *pEngine = Hydrogen::get_instance(); AudioEngine::get_instance()->lock( RIGHT_HERE ); + // Printing the color of the selected noise in the status + // message bar. if ( pSelection == 0 ){ statusMessage = trUtf8( "White noise selected" ); } else if ( pSelection == 1 ){ statusMessage = trUtf8( "Pink noise selected" ); - } else + } else { ERRORLOG( "[humanizeColorChanged] Unknown noise selected. Using white noise instead." ); + // The fallback to white noise will be done in the + // set_humanize_*_color function + } + + // Picking the corresponding humanizer. + if ( pSignalSender == m_pHumanizeColorTimeLCDCombo ){ + pEngine->getSong()->set_humanize_time_color( pSelection ); + } else if ( pSignalSender == m_pHumanizeColorVelocityLCDCombo ){ + pEngine->getSong()->set_humanize_velocity_color( pSelection ); + } else { + ERRORLOG( "[humanizeColorChange] Unknown selection box." ); + } AudioEngine::get_instance()->unlock(); ( HydrogenApp::get_instance() )->setStatusBarMessage( statusMessage, 2000 ); diff --git a/src/gui/src/Mixer/MixerLine.h b/src/gui/src/Mixer/MixerLine.h index d02745aa3..8e86c5b4b 100644 --- a/src/gui/src/Mixer/MixerLine.h +++ b/src/gui/src/Mixer/MixerLine.h @@ -254,7 +254,7 @@ class MasterMixerLine: public PixmapWidget public slots: void faderChanged(MasterFader * ref); void rotaryChanged( Rotary *pRef ); - void humanizeColorChanged( int ); + void humanizeColorChanged( int ); void muteClicked(Button*); private: From 33b87ae00dbe3a5e8546a3a9b44110440e983153 Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Sun, 15 Jul 2018 21:46:30 +0200 Subject: [PATCH 5/7] adding Randomizer object for humanization Instead of a bunch of inline functions and dozens of global variables holding the states of the different generators of colored noise I introduced an object class `Randomizer`. It will contain interfaces to draw new random numbers as public methods and all states as private ones. To allow a better maintainance I moved all code concerning the random number generator in dedicated files `randomizer.cpp` and `randomizer.h`. The randomizer is now designed to be a proper part of Hydrogen. Each time the `Hydrogen::create_instance()` function is called upon starting up Hydrogen an instance of the Randomizer is created as well using `Randomizer::create_instance()`. During the runs of the audio engine the generated instance will be retrieved using `Randomizer::get_instance()`. This way only one seeded random number generator does exist and every random number, regardless of its color, is drawn from it. In addition the generators of the Gaussian white and white uniform noise source have been updated. - the **Gaussian white noise source** now uses both Gaussian random variables produced by the Box-Muller transformation and relies on the updated uniform white algorithm - the **uniform white noise source** was completely reworked and now the algorithm *Ranq1* of the book *Numerical recipes* by Press et al. is used. It is of higher quality and does not produce correlated noise like the `std::rand()` function. Some description has been added to the pink noise source. --- src/core/include/hydrogen/hydrogen.h | 18 --- src/core/include/hydrogen/randomizer.h | 108 +++++++++++++ src/core/src/hydrogen.cpp | 134 ++++------------ src/core/src/randomizer.cpp | 210 +++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 121 deletions(-) create mode 100644 src/core/include/hydrogen/randomizer.h create mode 100644 src/core/src/randomizer.cpp diff --git a/src/core/include/hydrogen/hydrogen.h b/src/core/include/hydrogen/hydrogen.h index 75e8e51e2..49f1c6a75 100644 --- a/src/core/include/hydrogen/hydrogen.h +++ b/src/core/include/hydrogen/hydrogen.h @@ -44,23 +44,6 @@ #define STATE_READY 4 // Ready to process audio #define STATE_PLAYING 5 // Currently playing a sequence. -// *** Humanizer *** // -#define PI 3.1415926536f -#define PINK_NOISE_MAX_ROWS ( 30 ) -#define PINK_NOISE_BITS ( 24 ) -#define PINK_NOISE_SHIFT ( ( sizeof( long )* 8 ) - PINK_NOISE_BITS ) - -typedef struct { - long pink_Rows[PINK_NOISE_MAX_ROWS]; - long pink_RunningSum; /* Used to optimize summing of generators. */ - int pink_Index; /* Incremented each sample. */ - int pink_IndexMask; /* Index wrapped by ANDing with this mask. */ - float pink_Scalar; /* Used to scale within range of -1.0 to +1.0 */ -} PinkNoise; - -inline float get_random_white_uniform(); -inline float get_random_white_gaussian( float scale ); -inline float get_random_pink( PinkNoise *pink, float scale ); // ***************** // @@ -293,7 +276,6 @@ class Hydrogen : public H2Core::Object Song::SongMode m_oldEngineMode; bool m_bOldLoopEnabled; bool m_bExportSessionIsActive; - //Timline information Timeline* m_pTimeline; diff --git a/src/core/include/hydrogen/randomizer.h b/src/core/include/hydrogen/randomizer.h new file mode 100644 index 000000000..87a8bc270 --- /dev/null +++ b/src/core/include/hydrogen/randomizer.h @@ -0,0 +1,108 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * Humanizer + * Copyright(c) 2018 by Philipp Müller [thegreatwhiteshark@googlemail.com] + * + * 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 RANDOMIZER_H +#define RANDOMIZER_H + +#include +#include + +#define PI ( 3.1415926536d ) +#define PINK_USED_ROWS ( 12 ) +#define PINK_MAX_ROWS ( 30 ) +#define PINK_BITS ( 24 ) +#define PINK_SHIFT ( ( sizeof( long )* 8 ) - PINK_BITS ) + +namespace H2Core { + +class Randomizer { +public: + // Constructor and destructor + Randomizer(); + // Functions to create and retrieve an instance of the + // Randomizer within Hydrogen. + static Randomizer* create_instance(); + static Randomizer* get_instance(); + // + ////////////// White uniform noise /////////////////////////// + // Composed generator with a multiplicative linear + // congruential generator as the outer method and a 64-bit + // Xorshift method as the inner one. + unsigned long long int white_uniform_int64(); + // Double precision version of the white noise generator. + double white_uniform(); + ////////////////////////////////////////////////////////////// + // + ////////////////////// Gaussian white noise ////////////////// + // Box-Muller transformation. The resulting Gaussian random + // variable will be of mean 0 and of standard deviation + // `standardDeviation'. + double white_gaussian( double standardDeviation ); + ////////////////////////////////////////////////////////////// + // + /////////////////////////// Pink noise /////////////////////// + // Actual algorithm producing the pink noise. + float pink( float scale ); + ////////////////////////////////////////////////////////////// + +private: + static Randomizer* __instance; + ////////////// White uniform noise /////////////////////////// + // Internal state of the white uniform random number + // generator. + unsigned long long int uniformStateInt64; + ////////////////////////////////////////////////////////////// + // + ////////////////////// Gaussian white noise ////////////////// + // This methods generates two Gaussian-distributed variables + // out of two uniformly distributed ones. One will be returned + // and the other stored in this variable to be returned during + // the next call. + double gaussianSecondVariable; + ////////////////////////////////////////////////////////////// + // + /////////////////////////// Pink noise /////////////////////// + // Array containing the `PINK_MAX_ROWS' independent white + // noise sources. + long rowsPink[ PINK_MAX_ROWS ]; + // Variable holding the sum over all independent white noise + // sources (used for optimization purposes). + long runningSumPink; + // Internal time unit. + int indexPink; + // Determines the maximum possible value of `indexPink'. + int indexMaskPink; + // Biggest possible value generated by the pink noise source. + long maxValuePink; + // Normalization constant to transform the result of type + // long into float and in the range of -1.0 to +1.0. + float normalizationPink; + ////////////////////////////////////////////////////////////// +}; + +}; + +#endif /* RANDOMIZER_H */ + + diff --git a/src/core/src/hydrogen.cpp b/src/core/src/hydrogen.cpp index 0fdbd7fe9..03e7079a5 100644 --- a/src/core/src/hydrogen.cpp +++ b/src/core/src/hydrogen.cpp @@ -1,3 +1,4 @@ + /* * Hydrogen * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net] @@ -91,6 +92,7 @@ #include #include #include +#include namespace H2Core { @@ -203,87 +205,6 @@ inline timeval currentTime2() return now; } -// *** Humanizer *** // -static int seed_random = std::rand(); -inline float get_random_white_uniform() -{ - // fast-float-ries - seed_random *= 16807; - return (float) seed_random* 4.6566129e-010f/2 + 0.5; -} - -inline float get_random_white_gaussian( float scale ){ - // gaussian distribution -- dimss - - float randomUniform1 = get_random_white_uniform(); - float randomUniform2 = get_random_white_uniform(); - - return (float) sqrt( -2.0f * log( randomUniform1 ) )* - cos( 2.0f * PI * randomUniform2 ) * scale; -} - - -/* Setup PinkNoise structure for N rows of generators. */ -void InitializePinkNoise( PinkNoise *pink, int numRows ) -{ - int i; - long pmax; - pink->pink_Index = 0; - pink->pink_IndexMask = (1<pink_Scalar = 1.0f / pmax; -/* Initialize rows. */ - for( i=0; ipink_Rows[i] = 0; - pink->pink_RunningSum = 0; -} -inline float get_random_pink( PinkNoise *pink, float scale ){ - long newRandom; - long sum; - float output; - -/* Increment and mask index. */ - pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask; - -/* If index is zero, don't update any random values. */ - if( pink->pink_Index != 0 ) - { - /* Determine how many trailing zeros in PinkIndex. */ - /* This algorithm will hang if n==0 so test first. */ - // The while loop below shifts the index pink->pink_Index to - // the right until the leftmost bit is a zero. - int numZeros = 0; - int n = pink->pink_Index; - while( (n & 1) == 0 ) - { - n = n >> 1; - numZeros++; - } - - /* Replace the indexed ROWS random value. - * Subtract and add back to RunningSum instead of adding all the random - * values together. Only one changes each time. - */ - pink->pink_RunningSum -= pink->pink_Rows[numZeros]; - newRandom = ((long)get_random_white_uniform()) >> PINK_NOISE_SHIFT; - pink->pink_RunningSum += newRandom; - pink->pink_Rows[numZeros] = newRandom; - } - - /* Add extra white noise value. */ - // Discard the first PINK_RANDOM_SHIFT bits to reduce the - // generated random number of the size of PINK_RANDOM_BITS - // bits. - newRandom = ((long)get_random_white_uniform()) >> PINK_NOISE_SHIFT; - sum = pink->pink_RunningSum + newRandom; - - /* Scale to range of -1.0 to 0.9999. */ - output = pink->pink_Scalar * sum; - - return output * scale; -} -// ***************** // - void audioEngine_raiseError( unsigned nErrorCode ) { EventQueue::get_instance()->push_event( EVENT_ERROR, nErrorCode ); @@ -515,6 +436,7 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) { Hydrogen* pHydrogen = Hydrogen::get_instance(); Song* pSong = pHydrogen->getSong(); + Randomizer* pRand = Randomizer::get_instance(); unsigned int framepos; @@ -542,10 +464,11 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) unsigned int noteStartInFrames = (int)( pNote->get_position() * m_pAudioDriver->m_transport.m_nTickSize ); - // if there is a negative Humanize delay, take into account so - // we don't miss the time slice. ignore positive delay, or we - // might end the queue processing prematurely based on NoteQueue - // placement. the sampler handles positive delay. + // if there is a negative Humanize delay, take into + // account so we don't miss the time slice. ignore + // positive delay, or we might end the queue processing + // prematurely based on NoteQueue placement. The + // sampler handles positive delay. if (pNote->get_humanize_delay() < 0) { noteStartInFrames += pNote->get_humanize_delay(); } @@ -566,14 +489,15 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) continue; } - if ( pSong->get_humanize_velocity_value() != 0 ) { - // Ensure the generated Gaussian random variables - // won't have a variance bigger than this value. - const float maximalHumanizationVelocityVariance = 0.2; + if ( pSong->get_humanize_velocity_value() != 0 ){ + // Ensure the generated Gaussian random + // variables won't have a variance + // bigger than this value. + const float maximumHumanizationVelocityVariance = 0.2; pNote->set_velocity( pNote->get_velocity() + - get_random_white_gaussian( pSong->get_humanize_velocity_value() * - pSong->get_humanize_velocity_value() * - maximalHumanizationVelocityVariance ) ); + pRand->white_gaussian( pSong->get_humanize_velocity_value() * + pSong->get_humanize_velocity_value() * + maximumHumanizationVelocityVariance ) ); if ( pNote->get_velocity() > 1.0 ) { pNote->set_velocity( 1.0 ); } else if ( pNote->get_velocity() < 0.0 ) { @@ -583,10 +507,11 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) // Random Pitch ;) const float fMaxPitchDeviation = 2.0; - const float maximalPitchVariance = 0.2; - float randomPitch = get_random_white_gaussian( pNote->get_instrument()->get_random_pitch_factor() * - pNote->get_instrument()->get_random_pitch_factor() * - maximalPitchVariance ); + const float maximumPitchVariance = 0.2; + float randomPitch = + pRand->white_gaussian( pNote->get_instrument()->get_random_pitch_factor() * + pNote->get_instrument()->get_random_pitch_factor() * + maximumPitchVariance ); // Since a Gaussian white noise is unbound we // have to verify the random pitch shift does // not exceed the value of maximal pitch @@ -597,8 +522,8 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) randomPitch = -1.0 * fMaxPitchDeviation; } - // pNote->set_pitch( pNote->get_pitch() - // + randomPitch ); + pNote->set_pitch( pNote->get_pitch() + + randomPitch ); /* * Check if the current instrument has the property "Stop-Note" set. @@ -1110,6 +1035,7 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) { Hydrogen* pHydrogen = Hydrogen::get_instance(); Song* pSong = pHydrogen->getSong(); + Randomizer* pRand = Randomizer::get_instance(); // static int nLastTick = -1; bool bSendPatternChange = false; @@ -1359,11 +1285,12 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) // won't have a variance bigger than the value. // A large factor is needed in here. Since the // `nOffset' variable is an integer, the - // `randomHumanizeTime` will be floored. Is this right? - const float maximalHumanizationTimeVariance = 3.3; - float randomHumanizeTime = get_random_white_gaussian( maximalHumanizationTimeVariance * - pSong->get_humanize_time_value() * - pSong->get_humanize_time_value() ); + // `randomHumanizeTime` will be floored. + const float maximumHumanizationTimeVariance = 3.3; + float randomHumanizeTime = + pRand->white_gaussian( maximumHumanizationTimeVariance * + pSong->get_humanize_time_value() * + pSong->get_humanize_time_value() ); // Since a Gaussian white noise is unbound we // have to verify the random time shift does // not exceed the maximal value. @@ -1875,6 +1802,7 @@ void Hydrogen::create_instance() Preferences::create_instance(); EventQueue::create_instance(); MidiActionManager::create_instance(); + Randomizer::create_instance(); #ifdef H2CORE_HAVE_OSC NsmClient::create_instance(); diff --git a/src/core/src/randomizer.cpp b/src/core/src/randomizer.cpp new file mode 100644 index 000000000..a23c7ef0d --- /dev/null +++ b/src/core/src/randomizer.cpp @@ -0,0 +1,210 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * Humanizer + * Copyright(c) 2018 by Philipp Müller [thegreatwhiteshark@googlemail.com] + * + * 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 + +namespace H2Core { + +Randomizer* Randomizer::__instance = 0; + +// Constructor +Randomizer::Randomizer() : + uniformStateInt64( 4101842887655102017LL ) { + __instance = this; + // Generate a random seed + unsigned long long int initialSeed = std::rand(); + // + // Initialize the white uniform noise source. + uniformStateInt64 ^= initialSeed; + uniformStateInt64 = white_uniform_int64(); + // + // Initialize the Gaussian white noise source. + gaussianSecondVariable = 0.; + // + // Initialize the pink noise source. + indexPink = 0; + // Mask specifying the biggest number allowed for the index. + indexMaskPink = ( 1 << PINK_USED_ROWS ) - 1; + // Biggest possible signed random value generated by the pink + // noise generator. An extra 1 is added for the white noise + // source added to the pink noise. + maxValuePink = ( PINK_USED_ROWS + 1 ) * + ( 1 << ( PINK_BITS - 1 ) ); + // Normalization constant to convert the output into float. + normalizationPink = 1.0f/ maxValuePink; + // Initialize the different rows containing the independent + // white noise sources. + for( int ii = 0; ii < PINK_USED_ROWS; ii++ ){ + rowsPink[ ii ] = 0; + } + // This variable will contain the sum over all independent + // white noises. + runningSumPink = 0; +} + +// Create an instance within Hydrogen +Randomizer* Randomizer::create_instance(){ + if ( __instance == 0 ){ + __instance = new Randomizer; + } + return __instance; +} +// Retrieve a created instance +Randomizer* Randomizer::get_instance(){ + assert( __instance ); + return __instance; +} +/////////////////////// White uniform noise ////////////////////////// +// The uniform white noise generator is the basis for all colors +// produced by this struct. It will use the `Ranq1' code recommended +// by the book "Numerical Recipes" by Press et al. p. 351. It is a +// composed generator capable of producing random numbers (the +// std::rand() function produces slightly correlated output) and has +// a period of "only" 1.8 * 10^19. But for our use at hand it is more +// than sufficient. +// +// Composed generator with a multiplicative linear congruential +// generator as the outer method and a 64-bit Xorshift method as the +// inner one. +unsigned long long int Randomizer::white_uniform_int64(){ + uniformStateInt64 ^= uniformStateInt64 >> 21; + uniformStateInt64 ^= uniformStateInt64 << 35; + uniformStateInt64 ^= uniformStateInt64 >> 4; + return uniformStateInt64 * 2685821657736338717LL; +} +// Double precision version of the algorithm. +double Randomizer::white_uniform(){ + return 5.42101086242752217E-20 * white_uniform_int64(); +} +////////////////////////////////////////////////////////////////////// + +////////////////////////// Gaussian white noise ////////////////////// +// There are several algorithms for generating Gaussian white +// noise. But most of them just try to approximate the Gaussian nature +// of the signal. Instead the Box-Muller transformation will be used +// to transform uniformly distributed white noise into true Gaussian +// white noise. +// +// Box-Muller transformation. The resulting Gaussian random +// variable will be of mean 0 and of standard deviation +// `standardDeviation'. +double Randomizer::white_gaussian( double standardDeviation ){ + double uniformRandomNumber1, uniformRandomNumber2, + distance, factor; + // Check, whether there is still a Gaussian random variable + // left from the previous call. + if ( gaussianSecondVariable == .0 ){ + do { + uniformRandomNumber1 = 2.0 * + white_uniform() - 1.0; + uniformRandomNumber2 = 2.0 * + white_uniform() - 1.0; + // For this transformation to work the + // resulting point (if both numbers are + // assumed to be positions along two + // orthogonal axes) has to be in the unit + // cycle. + distance = uniformRandomNumber1 * + uniformRandomNumber1 + + uniformRandomNumber2 * + uniformRandomNumber2; + } while ( distance >= 1.0 || distance == 0 ); + factor = sqrt( -2.0 * log( distance )/ distance ); + gaussianSecondVariable = + uniformRandomNumber1 * factor; + return standardDeviation * + uniformRandomNumber2 * factor; + } else { + // Use the stored number from the previous call + // instead. + factor = gaussianSecondVariable; + gaussianSecondVariable = 0.; + return factor * standardDeviation; + } +} +////////////////////////////////////////////////////////////////////// + +/////////////////////////////// Pink noise /////////////////////////// +// The one over frequency behavior of a true pink spectrum will be +// approximated by summing a bunch of white noises, which are updated +// on different time scales. Those times are positioned equidistant on +// a logarithmic time scale. +// This algorithm will generate a fair 1/f spectrum with some +// inaccuracy at very low frequencies and is based on code snippet +// posted by Phil Burk on the music-dsp mailing +// list. http://www.firstpr.com.au/dsp/pink-noise/ +// The number of involved independent random white noises can be +// controlled using the `PINK_USED_ROWS' constant in the corresponding +// header file. +float Randomizer::pink( float scale ){ + long uniformRandomNumber, sumUpdate; + float output; + // Increment the index and assure it is compatible with its + // mask. + indexPink = ( indexPink + 1 ) & indexMaskPink; + // If index is zero, don't update any random values. + if( indexPink != 0 ){ + // Determine the number of trailing zeros in + // `indexPink'. This is done by shifting `indexPink' + // to the right until the leftmost bit is a zero. The + // algorithm will hang if `indexPink' is zero. + int numberOfZeros = 0; + int nn = indexPink; + while( ( nn & 1 ) == 0 ){ + nn = nn >> 1; + numberOfZeros++; + } + // The number of trailing zeros will now be used to + // determine the white noise source, which will be + // replaced. This way the first one will be updated + // every 2^1 times, the second one every 2^2 times and + // so on. This they all life of different time scales + // with an equal distance in logarithmic time. + // + // To speed up the algorithm the sum over all white + // noise sources will not be calculated in every + // run. Instead, the chosen one will be subtracted and + // the updated value will be add back to + // `runningSumPink'. + runningSumPink -= rowsPink[ numberOfZeros ]; + uniformRandomNumber = + ( (long)white_uniform_int64() ) >> PINK_SHIFT; + runningSumPink += uniformRandomNumber; + rowsPink[ numberOfZeros ] = uniformRandomNumber; + } + // An extra white noise source will be added in every step. It + // will thus live on a time scale of 2^0. Discard the first + // `PINK_SHIFT' bits to reduce the generated random number to + // the size of `PINK_BITS' bits. + uniformRandomNumber = + ( (long)white_uniform_int64() ) >> PINK_SHIFT; + sumUpdate = runningSumPink + uniformRandomNumber; + // Scale the result to a range from -1.0 to 0.9999. + output = normalizationPink * sumUpdate; + return output * scale; +} +////////////////////////////////////////////////////////////////////// + +}; From 2d83ea456c70ed3e7685de0ef18a170643e4a55d Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Sun, 15 Jul 2018 22:34:22 +0200 Subject: [PATCH 6/7] choosing noise color the Hydrogen core backend has now been updated to ask the GUI what noise color was chosen by the user. This will affect the noise source the random increment will be drawn from. --- src/core/src/hydrogen.cpp | 69 +++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/core/src/hydrogen.cpp b/src/core/src/hydrogen.cpp index 03e7079a5..4e6d1a310 100644 --- a/src/core/src/hydrogen.cpp +++ b/src/core/src/hydrogen.cpp @@ -490,14 +490,27 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) } if ( pSong->get_humanize_velocity_value() != 0 ){ - // Ensure the generated Gaussian random - // variables won't have a variance - // bigger than this value. - const float maximumHumanizationVelocityVariance = 0.2; + double randomNumber; + + if ( pSong->get_humanize_velocity_color() == 0 ){ + // Gaussian white noise selected by the + // user. + // Ensure the generated Gaussian random + // variables won't have a standard + // deviation bigger than this value. + const double maxVelocityStandardDeviation = 0.2; + randomNumber = + pRand->white_gaussian( pSong->get_humanize_velocity_value() * + maxVelocityStandardDeviation ); + } else if ( pSong->get_humanize_velocity_color() == 1 ){ + randomNumber = + pRand->pink( pSong->get_humanize_velocity_value() ); + } else { + ___ERRORLOG( "[audioEngine] Unknown noise color for the velocity" ); + } + pNote->set_velocity( pNote->get_velocity() + - pRand->white_gaussian( pSong->get_humanize_velocity_value() * - pSong->get_humanize_velocity_value() * - maximumHumanizationVelocityVariance ) ); + randomNumber ); if ( pNote->get_velocity() > 1.0 ) { pNote->set_velocity( 1.0 ); } else if ( pNote->get_velocity() < 0.0 ) { @@ -510,7 +523,6 @@ inline void audioEngine_process_playNotes( unsigned long nframes ) const float maximumPitchVariance = 0.2; float randomPitch = pRand->white_gaussian( pNote->get_instrument()->get_random_pitch_factor() * - pNote->get_instrument()->get_random_pitch_factor() * maximumPitchVariance ); // Since a Gaussian white noise is unbound we // have to verify the random pitch shift does @@ -1280,27 +1292,44 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) // Humanize - Time parameter if ( pSong->get_humanize_time_value() != 0 ) { - // Ensure the generated Gaussian random variables // won't have a variance bigger than the value. // A large factor is needed in here. Since the // `nOffset' variable is an integer, the // `randomHumanizeTime` will be floored. - const float maximumHumanizationTimeVariance = 3.3; - float randomHumanizeTime = - pRand->white_gaussian( maximumHumanizationTimeVariance * - pSong->get_humanize_time_value() * - pSong->get_humanize_time_value() ); + double randomNumber; + + if ( pSong->get_humanize_time_color() == 0 ){ + // Gaussian white noise selected by the + // user. + // Ensure the generated Gaussian random + // variables won't have a standard + // deviation bigger than this value. + const double maxTimeStandardDeviation = 3.3; + randomNumber = + pRand->white_gaussian( pSong->get_humanize_time_value() * + maxTimeStandardDeviation ); + } else if ( pSong->get_humanize_time_color() == 1 ){ + // Pink noise selected by the user. + // A large factor of 100 has been + // introduced to scale the resulting + // noise to values affecting the audio + // engine. + randomNumber = + pRand->pink( pSong->get_humanize_time_value() * 15. ); + } else { + ___ERRORLOG( "[audioEngine] Unknown noise color for the time" ); + } // Since a Gaussian white noise is unbound we // have to verify the random time shift does // not exceed the maximal value. - if ( ( int ) randomHumanizeTime > nMaxTimeHumanize ){ - randomHumanizeTime = ( float ) nMaxTimeHumanize; - } else if ( ( int ) randomHumanizeTime < -1* nMaxTimeHumanize ){ - randomHumanizeTime = ( float ) -1* nMaxTimeHumanize; + if ( ( int ) randomNumber > nMaxTimeHumanize ){ + randomNumber = ( float ) nMaxTimeHumanize; + } else if ( ( int ) randomNumber < -1* nMaxTimeHumanize ){ + randomNumber = ( float ) -1* nMaxTimeHumanize; } - nOffset += ( int ) randomHumanizeTime; - ___INFOLOG( QString( "randomHumanizeTime: %1" ).arg( randomHumanizeTime ) ); + nOffset += ( int ) randomNumber; + ___INFOLOG( QString( "randomNumber: %1" ).arg( randomNumber ) ); ___INFOLOG( QString( "nOffset: %1" ).arg( nOffset ) ); } From 7cbc822a6645d828df518079bfb212aea7ecdfe3 Mon Sep 17 00:00:00 2001 From: theGreatWhiteShark Date: Fri, 20 Jul 2018 22:30:13 +0200 Subject: [PATCH 7/7] Doxygen documentation for the randomizer --- src/core/include/hydrogen/randomizer.h | 76 +++++++++++--- src/core/src/randomizer.cpp | 135 ++++++++++++++++++------- 2 files changed, 158 insertions(+), 53 deletions(-) diff --git a/src/core/include/hydrogen/randomizer.h b/src/core/include/hydrogen/randomizer.h index 87a8bc270..68db412d1 100644 --- a/src/core/include/hydrogen/randomizer.h +++ b/src/core/include/hydrogen/randomizer.h @@ -29,13 +29,37 @@ #include #define PI ( 3.1415926536d ) +/** + * Number of white noise sources to approximate a 1/f spectrum in the + * pink() function. + */ #define PINK_USED_ROWS ( 12 ) +/** + * Maximum number of independent white noise sources to be used in + * pink(). + */ #define PINK_MAX_ROWS ( 30 ) +/** + * Number of bits used in the output of the pink() function. + */ #define PINK_BITS ( 24 ) +/** + * How much bits less then the 32 bit long integer does the output of + * the pink() function have. + */ #define PINK_SHIFT ( ( sizeof( long )* 8 ) - PINK_BITS ) namespace H2Core { +/** + * @brief Used to generate all random numbers used within the humanizer. + * + * Since not only Gaussian white noise, with increments perfectly + * independent from each other, but also noise of different colors + * will be use, this object provides a state of the random number + * generator. This state is required to draw the next, correlated + * number and should not be shared between different instruments. + */ class Randomizer { public: // Constructor and destructor @@ -69,34 +93,56 @@ class Randomizer { private: static Randomizer* __instance; ////////////// White uniform noise /////////////////////////// - // Internal state of the white uniform random number - // generator. + /** + * Internal state of the white uniform random number + * generator. + */ unsigned long long int uniformStateInt64; ////////////////////////////////////////////////////////////// // ////////////////////// Gaussian white noise ////////////////// - // This methods generates two Gaussian-distributed variables - // out of two uniformly distributed ones. One will be returned - // and the other stored in this variable to be returned during - // the next call. + /** + * @brief Auxiliary variable storing an addition Gaussian + * white number. + * + * The white_gaussian() function generates two + * Gaussian-distributed variables out of two uniformly + * distributed ones. One will be returned and the other stored + * in this variable to be returned during the next call. + */ double gaussianSecondVariable; ////////////////////////////////////////////////////////////// // /////////////////////////// Pink noise /////////////////////// - // Array containing the `PINK_MAX_ROWS' independent white - // noise sources. + /** + * Array containing the PINK_MAX_ROWS independent white + * noise sources in the pink() function. + */ long rowsPink[ PINK_MAX_ROWS ]; - // Variable holding the sum over all independent white noise - // sources (used for optimization purposes). + /** + * Variable holding the sum over all independent white noise + * sources (used for optimization purposes) in the pink() + * function. + */ long runningSumPink; - // Internal time unit. + /** + * Internal time unit of the pink() function. + */ int indexPink; - // Determines the maximum possible value of `indexPink'. + /** + * Determines the maximum possible value of `indexPink'. + */ int indexMaskPink; - // Biggest possible value generated by the pink noise source. + /** + * Biggest possible value generated by the pink noise source + * pink(). + */ long maxValuePink; - // Normalization constant to transform the result of type - // long into float and in the range of -1.0 to +1.0. + /** + * Normalization constant to transform the result of the + * pink() function of type long into float and in the range of + * -1.0 to +1.0. + */ float normalizationPink; ////////////////////////////////////////////////////////////// }; diff --git a/src/core/src/randomizer.cpp b/src/core/src/randomizer.cpp index a23c7ef0d..86655768b 100644 --- a/src/core/src/randomizer.cpp +++ b/src/core/src/randomizer.cpp @@ -29,11 +29,34 @@ namespace H2Core { Randomizer* Randomizer::__instance = 0; -// Constructor +/** + * @brief Constructor initializing the states of the individual + * colors. + * + * Since the \p uniform_white() function is used within the generation + * of all other colors the state of the uniform white noise generator + * is initialized first. This is done using a random seed drawn by the + * \p std::rand() function. The output of the \p std::rand() function + * will be of type integer and the first bits will be filled with + * zeros in the conversion into a 64 bit integer. But since this only + * affects the initialization step, it won't cause any problems within + * for the algorithms within used within the Randomizer. + * + * @return Object of class Randomizer. + */ Randomizer::Randomizer() : uniformStateInt64( 4101842887655102017LL ) { __instance = this; - // Generate a random seed + // + // Random seed used to initiate the Randomizer object. + // + // The output of the std::rand() function will be of type + // integer and the first bits will be filled with zeros in the + // conversion into a 64 bit integer. But since this only + // affects the initialization step, it won't cause any + // problems within for the algorithms within used within the + // Randomizer. + // unsigned long long int initialSeed = std::rand(); // // Initialize the white uniform noise source. @@ -49,7 +72,7 @@ Randomizer::Randomizer() : indexMaskPink = ( 1 << PINK_USED_ROWS ) - 1; // Biggest possible signed random value generated by the pink // noise generator. An extra 1 is added for the white noise - // source added to the pink noise. + // source added to the pink noise. maxValuePink = ( PINK_USED_ROWS + 1 ) * ( 1 << ( PINK_BITS - 1 ) ); // Normalization constant to convert the output into float. @@ -64,52 +87,79 @@ Randomizer::Randomizer() : runningSumPink = 0; } -// Create an instance within Hydrogen +/** + * Create an instance within Hydrogen + */ Randomizer* Randomizer::create_instance(){ if ( __instance == 0 ){ __instance = new Randomizer; } return __instance; } -// Retrieve a created instance +/** + * Retrieve the created instance + */ Randomizer* Randomizer::get_instance(){ assert( __instance ); return __instance; } /////////////////////// White uniform noise ////////////////////////// -// The uniform white noise generator is the basis for all colors -// produced by this struct. It will use the `Ranq1' code recommended -// by the book "Numerical Recipes" by Press et al. p. 351. It is a -// composed generator capable of producing random numbers (the -// std::rand() function produces slightly correlated output) and has -// a period of "only" 1.8 * 10^19. But for our use at hand it is more -// than sufficient. -// -// Composed generator with a multiplicative linear congruential -// generator as the outer method and a 64-bit Xorshift method as the -// inner one. +/** + * @brief Uniform white noise generator for 64 bit integers. + * + * A Composed generator with a multiplicative linear congruential + * generator as the outer method and a 64-bit Xorshift method as the + * inner one. + * + * The uniform white noise generator is the basis for all colors + * produced by this struct. It will use the *Ranq1* code recommended + * by the book "Numerical Recipes" by Press et al. p. 351. It is a + * composed generator capable of producing random numbers (the + * std::rand() function produces slightly correlated output) and has a + * period of \e only 1.8 * 10^19. But for our use at hand it is more + * than sufficient. + * + * @return A 64 bit integer uniformly and independently distributed + * random number. + */ unsigned long long int Randomizer::white_uniform_int64(){ uniformStateInt64 ^= uniformStateInt64 >> 21; uniformStateInt64 ^= uniformStateInt64 << 35; uniformStateInt64 ^= uniformStateInt64 >> 4; return uniformStateInt64 * 2685821657736338717LL; } -// Double precision version of the algorithm. +/** + * @brief Uniform white noise generator at double precision. + * + * @return A double precision uniformly and independently + * distributed random number. + */ double Randomizer::white_uniform(){ return 5.42101086242752217E-20 * white_uniform_int64(); } ////////////////////////////////////////////////////////////////////// ////////////////////////// Gaussian white noise ////////////////////// -// There are several algorithms for generating Gaussian white -// noise. But most of them just try to approximate the Gaussian nature -// of the signal. Instead the Box-Muller transformation will be used -// to transform uniformly distributed white noise into true Gaussian -// white noise. -// -// Box-Muller transformation. The resulting Gaussian random -// variable will be of mean 0 and of standard deviation -// `standardDeviation'. +/** + * @brief Gaussian white noise generator at double precision. + * + * This function is based on the Box-Muller transformation. The + * resulting Gaussian random variable will be of mean 0 and of + * standard deviation \p standardDeviation. + * + * While other algorithms do approximate the Gaussian nature of the + * generated noise this one will yield the most precise results + * because it is based on more mathematical exact transformation. + * + * @param standardDeviation A double precision number specifying the + * standard deviation, or square root of the variance, of the + * generated Gaussian white noise. + * + * @sa white_uniform + * + * @return A double precision independently and normally distributed + * random number. + */ double Randomizer::white_gaussian( double standardDeviation ){ double uniformRandomNumber1, uniformRandomNumber2, distance, factor; @@ -147,17 +197,26 @@ double Randomizer::white_gaussian( double standardDeviation ){ ////////////////////////////////////////////////////////////////////// /////////////////////////////// Pink noise /////////////////////////// -// The one over frequency behavior of a true pink spectrum will be -// approximated by summing a bunch of white noises, which are updated -// on different time scales. Those times are positioned equidistant on -// a logarithmic time scale. -// This algorithm will generate a fair 1/f spectrum with some -// inaccuracy at very low frequencies and is based on code snippet -// posted by Phil Burk on the music-dsp mailing -// list. http://www.firstpr.com.au/dsp/pink-noise/ -// The number of involved independent random white noises can be -// controlled using the `PINK_USED_ROWS' constant in the corresponding -// header file. +/** + * @brief Pink noise generator at double precision. + * + * The one over frequency behavior of a true pink spectrum will be + * approximated by summing a bunch of white noises, which are updated + * on different time scales. Those times are positioned equidistant on + * a logarithmic time scale. This algorithm will generate a fair 1/f + * spectrum with some inaccuracy at very low frequencies and is based + * on code snippet posted by Phil Burk on the music-dsp mailing + * list. http:www.firstpr.com.au/dsp/pink-noise/ The number of + * involved independent random white noises can be controlled using + * the \p PINK_USED_ROWS constant in the corresponding header file. + * + * @param scale A float number, which will be multiplied with the + * resulting pink random numbers. + * + * @sa white_uniform_int64 + * + * @return A pink random number at float precision + */ float Randomizer::pink( float scale ){ long uniformRandomNumber, sumUpdate; float output; @@ -187,7 +246,7 @@ float Randomizer::pink( float scale ){ // noise sources will not be calculated in every // run. Instead, the chosen one will be subtracted and // the updated value will be add back to - // `runningSumPink'. + // `runningSumPink'. runningSumPink -= rowsPink[ numberOfZeros ]; uniformRandomNumber = ( (long)white_uniform_int64() ) >> PINK_SHIFT;