Hi,

Over the last few days I have been cleaning up the code for an added
feature to Hydrogen that I have been using for quite awhile now (over 2
years actually). I call the feature Lead and Lag. It is a per note
property like velocity or pan that allows you to set that the note leads
or lags the actual beat by a small amount The range is +/- 5 ticks which
equals around +/- 10 ms at a tempo of 120bpm. The control widget itself
works just like pan control in the pattern editor, and is found in the
note properties selector below Velocity and Pan.

I have found this to be a really nice feature. You can change the feel
of a beat quite drastically by adjusting key notes by a small amount.
For a basic rock beat I typically set the 2 and 4 ahead or behind the
beat depending on the feel that I am looking for. The real fun starts
when you use the feature in creative ways. You can make some pretty
wacky beats by adjusting the timing of some of the beats with extreme
values. I also used it when simulating a drum roll. I programmed a
series of 32 or 64th notes and created a sin wave pattern in the
Lead /Lag control. The gap between the notes slowly fluctuates back and
forth and adds a bit of that swirling sound. 

A few notes about the implementation:

In order to play notes ahead of the beat, I needed to implement a
"lookahead" which primes the playback note queue with notes in the
future. This does _not_ cause a delay in playback. The song or pattern
starts on time and is in sync with the transport. It is just that the
note queue is filled aggressively once playing starts. This works quite
well, and the only limitation is that the first note can't be played in
the future (ie before the 0 frame).

Since the note queue order is based on the absolute note position and
does not take into account any offsets from the lead and lag or even the
humanize function, I had to change the standard queue to a priority
queue. The queue is set up to pop the notes in order of playback based
on the absolute note position plus any offset. This plus the lookahead
have the added effect of making the humanize time function work both
ways. Before it only could lag notes because by the time notes were
pulled from the queue and sent to the sampler for playback, it was too
late to playing anything ahead of the beat.

The patch also adjusts the getGaussian calculation. It was set to prefer
to add offsets to note timing. I am not sure why - maybe because
humanize time was not working as designed before? I set it up to vary
both ahead and behind the beat equally.

Another small feature of the patch is that I setup the middle mouse
click (or ctrl-right mouse click) to center the Pan or Lead/Lag
controls. I have another patch that I will send later that adds mouse
wheel support for adjusting these note controls.

I guess that's it. I have tested this quite a bit over the months, but I
was using an old svn snapshot. I have forward ported it to fit the
current code base and have done basic testing. Would be great if it can
make it into the 0.9.4 release.

-Scott
Index: gui/src/PatternEditor/NotePropertiesRuler.h
===================================================================
--- gui/src/PatternEditor/NotePropertiesRuler.h	(revision 209)
+++ gui/src/PatternEditor/NotePropertiesRuler.h	(working copy)
@@ -36,7 +36,8 @@
 	public:
 		enum NotePropertiesMode {
 			VELOCITY,
-			PAN
+			PAN,
+			LEADLAG
 		};
 
 		NotePropertiesRuler( QWidget *parent, PatternEditorPanel *pPatternEditorPanel, NotePropertiesMode mode );
@@ -64,6 +65,7 @@
 
 		void createVelocityBackground(QPixmap *pixmap);
 		void createPanBackground(QPixmap *pixmap);
+		void createLeadLagBackground(QPixmap *pixmap);
 		void paintEvent(QPaintEvent *ev);
 		void mousePressEvent(QMouseEvent *ev);
 		void mouseMoveEvent(QMouseEvent *ev);
Index: gui/src/PatternEditor/PatternEditorPanel.cpp
===================================================================
--- gui/src/PatternEditor/PatternEditorPanel.cpp	(revision 209)
+++ gui/src/PatternEditor/PatternEditorPanel.cpp	(working copy)
@@ -326,7 +326,7 @@
 //~ NOTE_VELOCITY EDITOR
 
 
-// NOTE_VELOCITY EDITOR
+// NOTE_PAN EDITOR
 	m_pNotePanScrollView = new QScrollArea( NULL );
 	m_pNotePanScrollView->setFrameShape( QFrame::NoFrame );
 	m_pNotePanScrollView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
@@ -334,9 +334,18 @@
 	m_pNotePanEditor = new NotePropertiesRuler( m_pNotePanScrollView->viewport(), this, NotePropertiesRuler::PAN );
 	m_pNotePanScrollView->setWidget( m_pNotePanEditor );
 	m_pNotePanScrollView->setFixedHeight( 100 );
-//~ NOTE_VELOCITY EDITOR
+//~ NOTE_PAN EDITOR
 
 
+// NOTE_LEADLAG EDITOR
+	m_pNoteLeadLagScrollView = new QScrollArea( NULL );
+	m_pNoteLeadLagScrollView->setFrameShape( QFrame::NoFrame );
+	m_pNoteLeadLagScrollView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
+	m_pNoteLeadLagScrollView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
+	m_pNoteLeadLagEditor = new NotePropertiesRuler( m_pNoteLeadLagScrollView->viewport(), this, NotePropertiesRuler::LEADLAG );
+	m_pNoteLeadLagScrollView->setWidget( m_pNoteLeadLagEditor );
+	m_pNoteLeadLagScrollView->setFixedHeight( 100 );
+//~ NOTE_LEADLAG EDITOR
 
 	// external horizontal scrollbar
 	m_pPatternEditorHScrollBar = new QScrollBar( Qt::Horizontal , NULL  );
@@ -379,6 +388,7 @@
 	pPropertiesCombo->setToolTip(trUtf8("Select note properties"));
 	pPropertiesCombo->addItem( trUtf8("Velocity") );
 	pPropertiesCombo->addItem( trUtf8("Pan") );
+	pPropertiesCombo->addItem( trUtf8("Lead and Lag") );
 	pPropertiesCombo->update();
 	connect( pPropertiesCombo, SIGNAL(valueChanged(QString)), this, SLOT(propertiesComboChanged(QString)));
 
@@ -409,6 +419,7 @@
 	pGrid->addWidget( m_pPatternEditorHScrollBar, 10, 1 );
 	pGrid->addWidget( m_pNoteVelocityScrollView, 4, 1 );
 	pGrid->addWidget( m_pNotePanScrollView, 4, 1 );
+	pGrid->addWidget( m_pNoteLeadLagScrollView, 4, 1 );
 
 	pGrid->addWidget( pPropertiesPanel, 4, 0 );
 	pGrid->setRowStretch( 2, 100 );
@@ -536,6 +547,9 @@
 
 	// pan ruler
 	m_pNotePanScrollView->horizontalScrollBar()->setValue( m_pPatternEditorHScrollBar->value() );
+
+	// leadlag ruler
+	m_pNoteLeadLagScrollView->horizontalScrollBar()->setValue( m_pPatternEditorHScrollBar->value() );
 }
 
 
@@ -719,6 +733,7 @@
 {
   //m_pNoteVelocityEditor->updateEditor();
   //m_pNotePanEditor->updateEditor();
+  //m_pNoteLeadLagEditor->updateEditor();
 
 	resizeEvent(NULL);	// force a scrollbar update
 }
@@ -770,6 +785,7 @@
 	m_pPatternEditorRuler->zoomIn();
 	m_pDrumPatternEditor->zoom_in();
 	m_pNoteVelocityEditor->zoomIn();
+	m_pNoteLeadLagEditor->zoomIn();
 
 	resizeEvent( NULL );
 }
@@ -782,6 +798,7 @@
 	m_pPatternEditorRuler->zoomOut();
 	m_pDrumPatternEditor->zoom_out();
 	m_pNoteVelocityEditor->zoomOut();
+	m_pNoteLeadLagEditor->zoomOut();
 
 	resizeEvent( NULL );
 }
@@ -822,6 +839,7 @@
 	m_pPatternEditorRuler->updateEditor( true );	// redraw all
 	m_pNoteVelocityEditor->updateEditor();
 	m_pNotePanEditor->updateEditor();
+	m_pNoteLeadLagEditor->updateEditor();
 
 	resizeEvent( NULL );
 
@@ -932,16 +950,25 @@
 {
 	if ( text == trUtf8( "Velocity" ) ) {
 		m_pNotePanScrollView->hide();
+		m_pNoteLeadLagScrollView->hide();
 		m_pNoteVelocityScrollView->show();
 
 		m_pNoteVelocityEditor->updateEditor();
 	}
 	else if ( text == trUtf8( "Pan" ) ) {
 		m_pNoteVelocityScrollView->hide();
+		m_pNoteLeadLagScrollView->hide();
 		m_pNotePanScrollView->show();
 
 		m_pNotePanEditor->updateEditor();
 	}
+	else if ( text == trUtf8( "Lead and Lag" ) ) {
+		m_pNoteVelocityScrollView->hide();
+		m_pNotePanScrollView->hide();
+		m_pNoteLeadLagScrollView->show();
+ 
+		m_pNoteLeadLagEditor->updateEditor();
+	}
 	else if ( text == trUtf8( "Cutoff" ) ) {
 	}
 	else if ( text == trUtf8( "Resonance" ) ) {
Index: gui/src/PatternEditor/PatternEditorPanel.h
===================================================================
--- gui/src/PatternEditor/PatternEditorPanel.h	(revision 209)
+++ gui/src/PatternEditor/PatternEditorPanel.h	(working copy)
@@ -66,6 +66,7 @@
 		DrumPatternEditor* getDrumPatternEditor() {	return m_pDrumPatternEditor;	}
 		NotePropertiesRuler* getVelocityEditor() {	return m_pNoteVelocityEditor;	}
 		NotePropertiesRuler* getPanEditor() {	return m_pNotePanEditor;	}
+		NotePropertiesRuler* getLeadLagEditor() {	return m_pNoteLeadLagEditor;	}
 		PatternEditorInstrumentList* getInstrumentList() {	return m_pInstrumentList;	}
 
 		// Implements EventListener interface
@@ -136,6 +137,9 @@
 		QScrollArea* m_pNotePanScrollView;
 		NotePropertiesRuler *m_pNotePanEditor;
 
+		// note leadlag editor
+		QScrollArea* m_pNoteLeadLagScrollView;
+		NotePropertiesRuler *m_pNoteLeadLagEditor;
 
 
 		QScrollBar *m_pPatternEditorHScrollBar;
Index: gui/src/PatternEditor/NotePropertiesRuler.cpp
===================================================================
--- gui/src/PatternEditor/NotePropertiesRuler.cpp	(revision 209)
+++ gui/src/PatternEditor/NotePropertiesRuler.cpp	(working copy)
@@ -51,6 +51,9 @@
 	else if ( m_mode == PAN ) {
 		m_nEditorHeight = 100;
 	}
+	else if ( m_mode == LEADLAG ) {
+		m_nEditorHeight = 100;
+	}
 
 	resize( m_nEditorWidth, m_nEditorHeight );
 	setMinimumSize( m_nEditorWidth, m_nEditorHeight );
@@ -121,20 +124,40 @@
 		}
 		else if ( m_mode == PAN ){
 			float pan_L, pan_R;
-
-			if ( val > 0.5 ) {
-				pan_L = 0.5;
-				pan_R = 1.0 - val;
+			if ( ev->button() == Qt::MidButton || ev->modifiers() == Qt::ControlModifier && ev->button() == Qt::LeftButton ) {
+					pan_R = pan_L = 0.5;
+			} else {
+				if ( val > 0.5 ) {
+					pan_L = 0.5;
+					pan_R = 1.0 - val;
+				}
+				else {
+					pan_L = val;
+					pan_R = 0.5;
+				}
 			}
-			else {
-				pan_L = val;
-				pan_R = 0.5;
-			}
 
 			pNote->set_pan_l( pan_L );
 			pNote->set_pan_r( pan_R );
 		}
+		else if ( m_mode == LEADLAG ){
+			if ( ev->button() == Qt::MidButton || ev->modifiers() == Qt::ControlModifier && ev->button() == Qt::LeftButton ) {
+				pNote->set_leadlag(0.0);
+			} else {
+				pNote->set_leadlag((val * -2.0) + 1.0);
+				char valueChar[100];
+				if (pNote->get_leadlag() < 0.0) {
+					sprintf( valueChar, "%.2f",  ( pNote->get_leadlag() * -5)); // FIXME: '5' taken from fLeadLagFactor calculation in hydrogen.cpp
+					HydrogenApp::getInstance()->setStatusBarMessage( QString("Leading beat by: %1 ticks").arg( valueChar ), 2000 );
+				} else if (pNote->get_leadlag() > 0.0) {
+					sprintf( valueChar, "%.2f",  ( pNote->get_leadlag() * 5)); // FIXME: '5' taken from fLeadLagFactor calculation in hydrogen.cpp
+					HydrogenApp::getInstance()->setStatusBarMessage( QString("Lagging beat by: %1 ticks").arg( valueChar ), 2000 );
+				} else {
+					HydrogenApp::getInstance()->setStatusBarMessage( QString("Note on beat"), 2000 );
+				}
 
+			}
+		}
 		pSong->__is_modified = true;
 		updateEditor();
 		break;
@@ -473,6 +496,165 @@
 	p.drawLine(0, m_nEditorHeight - 1, m_nEditorWidth, m_nEditorHeight - 1);
 }
 
+void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) 
+{
+	if ( !isVisible() ) {   
+		return;
+	}
+ 
+ 
+	UIStyle *pStyle = Preferences::getInstance()->getDefaultUIStyle();
+	QColor backgroundColor( 255, 255, 255 );
+	QColor blackKeysColor( 240, 240, 240 );
+	QColor horizLinesColor(
+			pStyle->m_patternEditor_backgroundColor.getRed() - 20,
+			pStyle->m_patternEditor_backgroundColor.getGreen() - 20,
+			pStyle->m_patternEditor_backgroundColor.getBlue() - 20
+	);
+	H2RGBColor valueColor(
+			(int)( pStyle->m_patternEditor_backgroundColor.getRed() * ( 1 - 0.3 ) ),
+			(int)( pStyle->m_patternEditor_backgroundColor.getGreen() * ( 1 - 0.3 ) ),
+			(int)( pStyle->m_patternEditor_backgroundColor.getBlue() * ( 1 - 0.3 ) )
+	);
+ 
+	QColor res_1( pStyle->m_patternEditor_line1Color.getRed(), pStyle->m_patternEditor_line1Color.getGreen(), pStyle->m_patternEditor_line1Color.getBlue() );
+	QColor res_2( pStyle->m_patternEditor_line2Color.getRed(), pStyle->m_patternEditor_line2Color.getGreen(), pStyle->m_patternEditor_line2Color.getBlue() );
+	QColor res_3( pStyle->m_patternEditor_line3Color.getRed(), pStyle->m_patternEditor_line3Color.getGreen(), pStyle->m_patternEditor_line3Color.getBlue() );
+	QColor res_4( pStyle->m_patternEditor_line4Color.getRed(), pStyle->m_patternEditor_line4Color.getGreen(), pStyle->m_patternEditor_line4Color.getBlue() );
+	QColor res_5( pStyle->m_patternEditor_line5Color.getRed(), pStyle->m_patternEditor_line5Color.getGreen(), pStyle->m_patternEditor_line5Color.getBlue() );
+ 
+	QPainter p( pixmap );
+ 
+	p.fillRect( 0, 0, width(), height(), QColor(0, 0, 0) );
+ 
+	unsigned nNotes = MAX_NOTES;
+	if (m_pPattern) {
+		nNotes = m_pPattern->get_lenght();
+	}
+	p.fillRect( 0, 0, 20 + nNotes * m_nGridWidth, height(), backgroundColor );
+ 
+ 
+	// central line
+	p.setPen( horizLinesColor );
+	p.drawLine(0, height() / 2.0, m_nEditorWidth, height() / 2.0);
+ 
+ 
+ 
+	// vertical lines
+	DrumPatternEditor *pPatternEditor = m_pPatternEditorPanel->getDrumPatternEditor();
+	int nBase;
+	if (pPatternEditor->isUsingTriplets()) {
+		nBase = 3;
+	}
+	else {
+		nBase = 4;
+	}
+ 
+	int n4th = 4 * MAX_NOTES / (nBase * 4);
+	int n8th = 4 * MAX_NOTES / (nBase * 8);
+	int n16th = 4 * MAX_NOTES / (nBase * 16);
+	int n32th = 4 * MAX_NOTES / (nBase * 32);
+	int n64th = 4 * MAX_NOTES / (nBase * 64);
+ 
+	int nResolution = pPatternEditor->getResolution();
+ 
+	if ( !pPatternEditor->isUsingTriplets() ) {
+ 
+		for (uint i = 0; i < MAX_NOTES + 1; i++) {
+			uint x = 20 + i * m_nGridWidth;
+ 
+			if ( (i % n4th) == 0 ) {
+				if (nResolution >= 4) {
+					p.setPen( QPen( res_1, 0, Qt::DotLine ) );
+					p.drawLine(x, 0, x, m_nEditorHeight);
+				}
+			}
+			else if ( (i % n8th) == 0 ) {
+				if (nResolution >= 8) {
+					p.setPen( QPen( res_2, 0, Qt::DotLine ) );
+					p.drawLine(x, 0, x, m_nEditorHeight);
+				}
+			}
+			else if ( (i % n16th) == 0 ) {
+				if (nResolution >= 16) {
+					p.setPen( QPen( res_3, 0, Qt::DotLine ) );
+					p.drawLine(x, 0, x, m_nEditorHeight);
+				}
+			}
+			else if ( (i % n32th) == 0 ) {
+				if (nResolution >= 32) {
+					p.setPen( QPen( res_4, 0, Qt::DotLine ) );
+					p.drawLine(x, 0, x, m_nEditorHeight);
+				}
+			}
+			else if ( (i % n64th) == 0 ) {
+				if (nResolution >= 64) {
+					p.setPen( QPen( res_5, 0, Qt::DotLine ) );
+					p.drawLine(x, 0, x, m_nEditorHeight);
+				}
+			}
+		}
+	}
+	else {  // Triplets
+		uint nCounter = 0;
+		int nSize = 4 * MAX_NOTES / (nBase * nResolution);
+ 
+		for (uint i = 0; i < MAX_NOTES + 1; i++) {
+			uint x = 20 + i * m_nGridWidth;
+ 
+			if ( (i % nSize) == 0) {
+				if ((nCounter % 3) == 0) {
+					p.setPen( QPen( res_1, 0, Qt::DotLine ) );
+				}
+				else {
+					p.setPen( QPen( res_3, 0, Qt::DotLine ) );
+				}
+				p.drawLine(x, 0, x, m_nEditorHeight);
+				nCounter++;
+			}
+		}
+	}
+ 
+	if ( m_pPattern ) {
+		int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber();
+		Song *pSong = Hydrogen::get_instance()->getSong();
+ 
+		std::multimap <int, Note*>::iterator pos;
+		for ( pos = m_pPattern->note_map.begin(); pos != m_pPattern->note_map.end(); ++pos ) {
+			Note *pNote = pos->second;
+			assert( pNote );
+			if ( pNote->get_instrument() != pSong->get_instrument_list()->get( nSelectedInstrument ) ) {
+				continue;
+			}
+			uint x_pos = 20 + pNote->get_position() * m_nGridWidth;
+ 
+			int y_start = (int)( height() * 0.5 );
+			int y_end = y_start + ((pNote->get_leadlag()/2) * height());
+ 
+ 
+			int nLineWidth = 3;
+			int red;
+			int green;
+			int blue = (int) (pNote->get_leadlag() * 255);
+			if (blue < 0)  {
+				red = blue *-1;
+				blue = (int) red * .33;
+				green = (int) red * .33;
+			} else {
+				red = (int) blue * .33;
+				green = (int) blue * .33;
+			}
+			p.fillRect( x_pos - 1, y_start, nLineWidth, y_end - y_start, QColor( red, green ,blue ) );
+ 
+			p.fillRect( x_pos - 1, ( height() / 2.0 ) - 2 , nLineWidth, 5, QColor( red, green ,blue ) );
+ 
+		}
+	}
+ 
+	p.setPen(res_1);
+	p.drawLine(0, 0, m_nEditorWidth, 0);
+	p.drawLine(0, m_nEditorHeight - 1, m_nEditorWidth, m_nEditorHeight - 1);
+}
 
 
 void NotePropertiesRuler::updateEditor()
@@ -505,6 +687,9 @@
 	else if ( m_mode == PAN ) {
 		createPanBackground( m_pBackground );
 	}
+	else if ( m_mode == LEADLAG ) {
+		createLeadLagBackground( m_pBackground );
+	}
 
 	// redraw all
 	update();
Index: gui/src/SoundLibrary/SoundLibraryImportDialog.cpp
===================================================================
--- gui/src/SoundLibrary/SoundLibraryImportDialog.cpp	(revision 209)
+++ gui/src/SoundLibrary/SoundLibraryImportDialog.cpp	(working copy)
@@ -53,7 +53,7 @@
 
 	QStringList headers;
 	headers << trUtf8( "Sound library" ) << trUtf8( "Status" );
-	QTreeWidgetItem* header = new QTreeWidgetItem( headers );
+	QTreeWidgetItem* header = new QTreeWidgetItem( 1 );
 	m_pDrumkitTree->setHeaderItem( header );
 	m_pDrumkitTree->header()->resizeSection( 0, 200 );
 
Index: libs/hydrogen/include/hydrogen/note.h
===================================================================
--- libs/hydrogen/include/hydrogen/note.h	(revision 209)
+++ libs/hydrogen/include/hydrogen/note.h	(working copy)
@@ -78,7 +78,7 @@
 public:
 
 	float m_fSamplePosition;
-	unsigned m_nHumanizeDelay;	///< Used in "humanize" function
+	int m_nHumanizeDelay;	///< Used in "humanize" function
 	NoteKey m_noteKey;
 
 	ADSR m_adsr;
@@ -169,6 +169,13 @@
 	}
 
 
+	void set_leadlag( float leadlag ) {
+		__leadlag = leadlag;
+	}
+	float get_leadlag() const {
+		return __leadlag;
+	}
+
 	void set_lenght( int lenght ) {
 		__lenght = lenght;
 	}
@@ -190,6 +197,7 @@
 	float __velocity;		///< Velocity (intensity) of the note [0..1]
 	float __pan_l;			///< Pan of the note (left volume) [0..1]
 	float __pan_r;			///< Pan of the note (right volume) [0..1]
+	float __leadlag;		///< Lead or lag offset of the note
 
 	int __lenght;
 	float __pitch;
Index: libs/hydrogen/src/local_file_mgr.cpp
===================================================================
--- libs/hydrogen/src/local_file_mgr.cpp	(revision 209)
+++ libs/hydrogen/src/local_file_mgr.cpp	(working copy)
@@ -107,6 +107,7 @@
 		{
 			Note* pNote = NULL;
 			unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 );
+			float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0 );
 			float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f );
 			float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 );
 			float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 );
@@ -131,6 +132,7 @@
 			//assert( instrRef );
 
 			pNote = new Note( instrRef, nPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch, Note::stringToKey( sKey ) );
+			pNote->set_leadlag(fLeadLag);
 			pPattern->note_map.insert( std::make_pair( pNote->get_position(),pNote ) );
 		}
 	}
@@ -206,6 +208,7 @@
 
 			TiXmlElement noteNode( "note" );
 			writeXmlString( &noteNode, "position", to_string( pNote->get_position() ) );
+			writeXmlString( &noteNode, "leadlag", to_string( pNote->get_leadlag() ) );
 			writeXmlString( &noteNode, "velocity", to_string( pNote->get_velocity() ) );
 			writeXmlString( &noteNode, "pan_L", to_string( pNote->get_pan_l() ) );
 			writeXmlString( &noteNode, "pan_R", to_string( pNote->get_pan_r() ) );
@@ -1006,6 +1009,7 @@
 
 			TiXmlElement noteNode( "note" );
 			LocalFileMng::writeXmlString( &noteNode, "position", to_string( pNote->get_position() ) );
+			LocalFileMng::writeXmlString( &noteNode, "leadlag", to_string( pNote->get_leadlag() ) );
 			LocalFileMng::writeXmlString( &noteNode, "velocity", to_string( pNote->get_velocity() ) );
 			LocalFileMng::writeXmlString( &noteNode, "pan_L", to_string( pNote->get_pan_l() ) );
 			LocalFileMng::writeXmlString( &noteNode, "pan_R", to_string( pNote->get_pan_r() ) );
Index: libs/hydrogen/src/note.cpp
===================================================================
--- libs/hydrogen/src/note.cpp	(revision 209)
+++ libs/hydrogen/src/note.cpp	(working copy)
@@ -50,6 +50,7 @@
 		, m_fBandPassFilterBuffer_R( 0.0 )
 		, m_fLowPassFilterBuffer_L( 0.0 )
 		, m_fLowPassFilterBuffer_R( 0.0 )
+		, __leadlag( 0.0 )
 		, __position( position )
 		, __velocity( velocity )
 {
@@ -71,6 +72,7 @@
 	__velocity	=	pNote->get_velocity();
 	set_pan_l(	pNote->get_pan_l()	);
 	set_pan_r(	pNote->get_pan_r()	);
+	set_leadlag(    pNote->get_leadlag()    );
 	set_lenght(	pNote->get_lenght()	);
 	set_pitch(	pNote->get_pitch()	);
 	m_noteKey	=	pNote->m_noteKey;
@@ -125,7 +127,7 @@
 
 void Note::dumpInfo()
 {
-	INFOLOG( "pos: " + to_string( get_position() ) + "\t instr: " + __instrument->get_name()+ "\t key: " + keyToString( m_noteKey ) + "\t pitch: " + to_string( get_pitch() ) );
+	INFOLOG( "pos: " + to_string( get_position() ) + "\t humanize offset" + to_string(m_nHumanizeDelay) + "\t instr: " + __instrument->get_name()+ "\t key: " + keyToString( m_noteKey ) + "\t pitch: " + to_string( get_pitch() ) );
 }
 
 
@@ -239,6 +241,7 @@
 	    m_noteKey
 	);
 
+	note->set_leadlag(get_leadlag());
 
 
 	return note;
Index: libs/hydrogen/src/song.cpp
===================================================================
--- libs/hydrogen/src/song.cpp	(revision 209)
+++ libs/hydrogen/src/song.cpp	(working copy)
@@ -543,6 +543,7 @@
 			Note* pNote = NULL;
 
 			unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 );
+			float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0 );
 			float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f );
 			float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 );
 			float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 );
@@ -568,6 +569,7 @@
 			//assert( instrRef );
 
 			pNote = new Note( instrRef, nPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch, Note::stringToKey( sKey ) );
+			pNote->set_leadlag(fLeadLag);
 			pPattern->note_map.insert( std::make_pair( pNote->get_position(), pNote ) );
 		}
 	} else {
@@ -585,6 +587,7 @@
 				Note* pNote = NULL;
 
 				unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 );
+				float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0 );
 				float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f );
 				float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 );
 				float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 );
@@ -605,6 +608,7 @@
 				assert( instrRef );
 
 				pNote = new Note( instrRef, nPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch );
+				pNote->set_leadlag(fLeadLag);
 
 				//infoLog( "new note!! pos: " + toString( pNote->m_nPosition ) + "\t instr: " + instrId );
 				pPattern->note_map.insert( std::make_pair( pNote->get_position(), pNote ) );
Index: libs/hydrogen/src/hydrogen.cpp
===================================================================
--- libs/hydrogen/src/hydrogen.cpp	(revision 209)
+++ libs/hydrogen/src/hydrogen.cpp	(working copy)
@@ -32,6 +32,7 @@
 #include <cassert>
 #include <cstdio>
 #include <deque>
+#include <queue>
 #include <iostream>
 #include <ctime>
 #include <cmath>
@@ -105,7 +106,13 @@
 AudioOutput *m_pAudioDriver = NULL;	///< Audio output
 MidiInput *m_pMidiDriver = NULL;	///< MIDI input
 
-std::deque<Note*> m_songNoteQueue;	///< Song Note FIFO
+// overload the the > operator of Note objects for priority_queue
+bool operator> (const Note& pNote1, const Note &pNote2) {
+	return (pNote1.m_nHumanizeDelay + pNote1.get_position() * m_pAudioDriver->m_transport.m_nTickSize) > \
+		(pNote2.m_nHumanizeDelay + pNote2.get_position() * m_pAudioDriver->m_transport.m_nTickSize);
+}
+
+std::priority_queue<Note*, std::deque<Note*>, std::greater<Note> > m_songNoteQueue;	/// Song Note FIFO
 std::deque<Note*> m_midiNoteQueue;	///< Midi Note FIFO
 
 
@@ -174,6 +181,7 @@
 
 inline unsigned audioEngine_renderNote( Note* pNote, const unsigned& nBufferSize );
 inline int audioEngine_updateNoteQueue( unsigned nFrames );
+inline void audioEngine_prepNoteQueue();
 
 inline int findPatternInTick( int tick, bool loopMode, int *patternStartTick );
 
@@ -218,7 +226,7 @@
 	} while ( w >= 1.0 );
 
 	w = sqrtf( ( -2.0 * logf( w ) ) / w );
-	return x1 * w * z + 0.5; // tunable
+	return x1 * w * z + 0.0; // tunable
 }
 
 
@@ -300,13 +308,10 @@
 	AudioEngine::get_instance()->get_sampler()->stop_playing_notes();
 
 	// delete all copied notes in the song notes queue
-	for ( unsigned i = 0; i < m_songNoteQueue.size(); ++i ) {
-		Note *note = m_songNoteQueue[i];
-		delete note;
-		note = NULL;
+	while(!m_songNoteQueue.empty()){
+		delete m_songNoteQueue.top();
+		m_songNoteQueue.pop();
 	}
-	m_songNoteQueue.clear();
-
 	// delete all copied notes in the midi notes queue
 	for ( unsigned i = 0; i < m_midiNoteQueue.size(); ++i ) {
 		Note *note = m_midiNoteQueue[i];
@@ -408,11 +413,10 @@
 	m_nPatternStartTick = -1;
 
 	// delete all copied notes in the song notes queue
-	for ( unsigned i = 0; i < m_songNoteQueue.size(); ++i ) {
-		Note *note = m_songNoteQueue[i];
-		delete note;
+	while(!m_songNoteQueue.empty()){
+		delete m_songNoteQueue.top();
+		m_songNoteQueue.pop();
 	}
-	m_songNoteQueue.clear();
 
 	/*	// delete all copied notes in the playing notes queue
 		for (unsigned i = 0; i < m_playingNotesQueue.size(); ++i) {
@@ -468,10 +472,10 @@
 			/*
 
 						// delete all copied notes in the song notes queue
-						for (unsigned i = 0; i < m_songNoteQueue.size(); ++i) {
-							delete m_songNoteQueue[i];
+						while(!m_songNoteQueue.empty()){
+							delete m_songNoteQueue.top();
+							m_songNoteQueue.pop();
 						}
-						m_songNoteQueue.clear();
 
 						// send a note-off event to all notes present in the playing note queue
 						for ( int i = 0; i < m_playingNotesQueue.size(); ++i ) {
@@ -503,12 +507,20 @@
 	}
 
 	// leggo da m_songNoteQueue
-	while ( m_songNoteQueue.size() > 0 ) {
-		Note *pNote = m_songNoteQueue[0];
+	while (!m_songNoteQueue.empty()) {
+		Note *pNote = m_songNoteQueue.top();
 
 		// verifico se la nota rientra in questo ciclo
-		unsigned noteStartInFrames = ( unsigned )( pNote->get_position() * m_pAudioDriver->m_transport.m_nTickSize );
+		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 (pNote->m_nHumanizeDelay < 0) {
+			noteStartInFrames += pNote->m_nHumanizeDelay;
+		}
+
 		// m_nTotalFrames <= NotePos < m_nTotalFrames + bufferSize
 		bool isNoteStart = ( ( noteStartInFrames >= framepos ) && ( noteStartInFrames < ( framepos + nframes ) ) );
 		bool isOldNote = noteStartInFrames < framepos;
@@ -530,7 +542,7 @@
 			pNote->set_pitch( pNote->get_pitch() + ( fMaxPitchDeviation * getGaussian( 0.2 ) - fMaxPitchDeviation / 2.0 ) * pNote->get_instrument()->get_random_pitch_factor() );
 
 			AudioEngine::get_instance()->get_sampler()->note_on( pNote );	// aggiungo la nota alla lista di note da eseguire
-			m_songNoteQueue.pop_front();			// rimuovo la nota dalla lista di note
+			m_songNoteQueue.pop();			// rimuovo la nota dalla lista di note
 
 			// raise noteOn event
 			int nInstrument = m_pSong->get_instrument_list()->get_pos( pNote->get_instrument() );
@@ -633,10 +645,10 @@
 {
 	//_INFOLOG( "clear notes...");
 
-	for ( unsigned i = 0; i < m_songNoteQueue.size(); ++i ) {	// delete all copied notes in the song notes queue
-		delete m_songNoteQueue[i];
+	while (!m_songNoteQueue.empty()) {       // delete all copied notes in the song notes queue
+		delete m_songNoteQueue.top();
+		m_songNoteQueue.pop();
 	}
-	m_songNoteQueue.clear();
 
 	/*	for (unsigned i = 0; i < m_playingNotesQueue.size(); ++i) {	// delete all copied notes in the playing notes queue
 			delete m_playingNotesQueue[i];
@@ -993,6 +1005,8 @@
 {
 	static int nLastTick = -1;
 	bool bSendPatternChange = false;
+	int nMaxTimeHumanize = 2000;
+	int nLeadLagFactor = m_pAudioDriver->m_transport.m_nTickSize * 5;  // 5 ticks
 
 	unsigned int framepos;
 	if (  m_audioEngineState == STATE_PLAYING ) {
@@ -1002,9 +1016,20 @@
 		framepos = m_nRealtimeFrames;
 	}
 
-	int tickNumber_start = ( int )( framepos / m_pAudioDriver->m_transport.m_nTickSize );
-	int tickNumber_end = ( int )( ( framepos + nFrames ) / m_pAudioDriver->m_transport.m_nTickSize );
+	int tickNumber_start = 0;
 
+	// We need to look ahead in the song for notes with negative offsets from LeadLag or Humanize.
+	// When starting from the beginning, we prime the note queue with notes between 0 and nFrames
+	// plus lookahead. lookahead should be equal or greater than the nLeadLagFactor + nMaxTimeHumanize.
+	int lookahead = nLeadLagFactor + nMaxTimeHumanize + 1;
+	if ( framepos == 0 ) {
+		tickNumber_start = (int)( framepos / m_pAudioDriver->m_transport.m_nTickSize );
+	}
+	else {
+		tickNumber_start = (int)( (framepos + lookahead) / m_pAudioDriver->m_transport.m_nTickSize );
+	}
+	int tickNumber_end = (int)( (framepos + nFrames + lookahead) / m_pAudioDriver->m_transport.m_nTickSize );
+
 	int tick = tickNumber_start;
 
 	// get initial timestamp for first tick
@@ -1026,7 +1051,7 @@
 			if ( ( int )note->get_position() <= tick ) {
 				// printf ("tick=%d  pos=%d\n", tick, note->getPosition());
 				m_midiNoteQueue.pop_front();
-				m_songNoteQueue.push_back( note );
+				m_songNoteQueue.push( note );
 			} else {
 				break;
 			}
@@ -1153,7 +1178,7 @@
 				m_pMetronomeInstrument->set_volume( Preferences::getInstance()->m_fMetronomeVolume );
 
 				Note *pMetronomeNote = new Note( m_pMetronomeInstrument, tick, fVelocity, 0.5, 0.5, -1, fPitch );
-				m_songNoteQueue.push_back( pMetronomeNote );
+				m_songNoteQueue.push( pMetronomeNote );
 			}
 		}
 
@@ -1169,7 +1194,7 @@
 				for ( pos = pPattern->note_map.lower_bound( m_nPatternTickPosition ); pos != pPattern->note_map.upper_bound( m_nPatternTickPosition ); ++pos ) {
 					Note *pNote = pos->second;
 					if ( pNote ) {
-						unsigned nOffset = 0;
+						int nOffset = 0;
 
 						// Swing
 						float fSwingFactor = m_pSong->get_swing_factor();
@@ -1178,17 +1203,24 @@
 						}
 
 						// Humanize - Time parameter
-						int nMaxTimeHumanize = 2000;
 						if ( m_pSong->get_humanize_time_value() != 0 ) {
-							nOffset += ( int )( getGaussian( 0.3 ) * m_pSong->get_humanize_time_value() * nMaxTimeHumanize );
+							nOffset += ( int )( (getGaussian( 0.3 ) * m_pSong->get_humanize_time_value()  * nMaxTimeHumanize ) );
 						}
 						//~
+						// Lead or Lag - timing parameter
+						nOffset += (int) (pNote->get_leadlag() * nLeadLagFactor);
+						//~
 
+						// cannot play note before 0 frame
+						if (tick + nOffset / m_pAudioDriver->m_transport.m_nTickSize < 0) {
+							_INFOLOG(" offset before 0 frame ");
+							nOffset = 0 - (int) (tick * m_pAudioDriver->m_transport.m_nTickSize);
+						}
 						Note *pCopiedNote = new Note( pNote );
 						pCopiedNote->set_position( tick );
 
 						pCopiedNote->m_nHumanizeDelay = nOffset;	// humanize time
-						m_songNoteQueue.push_back( pCopiedNote );
+						m_songNoteQueue.push( pCopiedNote );
 						//pCopiedNote->dumpInfo();
 					}
 				}
-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php
_______________________________________________
Hydrogen-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/hydrogen-devel

Reply via email to