From 6a5a07e739b68ff3aafdfabd35fe95aed0879b59 Mon Sep 17 00:00:00 2001 From: Colin McEwan Date: Thu, 11 Feb 2021 22:15:14 +0000 Subject: [PATCH 1/3] Some support for variable-resolution operation. - store resolution in h2song and h2pattern files - correct most uses "MAX_NOTES" to be in terms of song or pattern resolution - MIDI export works --- src/core/Basics/Pattern.cpp | 12 +++- src/core/Basics/Pattern.h | 26 ++++++++- src/core/Basics/Song.cpp | 4 +- src/core/Basics/Song.h | 9 +++ src/core/Hydrogen.cpp | 24 ++++---- src/core/Hydrogen.h | 4 +- src/core/IO/DiskWriterDriver.cpp | 2 +- src/core/LocalFileMgr.cpp | 4 +- src/core/Sampler/Sampler.cpp | 2 +- src/core/Smf/Smf.cpp | 6 +- .../src/PatternEditor/DrumPatternEditor.cpp | 18 +++--- .../src/PatternEditor/NotePropertiesRuler.cpp | 56 +++++++++++-------- src/gui/src/PatternEditor/PatternEditor.cpp | 6 +- src/gui/src/PatternEditor/PatternEditor.h | 4 +- .../PatternEditorInstrumentList.cpp | 5 +- .../src/PatternEditor/PatternEditorPanel.cpp | 14 ++++- .../src/PatternEditor/PatternEditorRuler.cpp | 41 +++++++++----- src/gui/src/PatternEditor/PianoRollEditor.cpp | 4 +- src/gui/src/SongEditor/SongEditorPanel.cpp | 4 +- 19 files changed, 166 insertions(+), 79 deletions(-) diff --git a/src/core/Basics/Pattern.cpp b/src/core/Basics/Pattern.cpp index c99d98770..7c62e12aa 100644 --- a/src/core/Basics/Pattern.cpp +++ b/src/core/Basics/Pattern.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -39,12 +41,18 @@ const char* Pattern::__class_name = "Pattern"; Pattern::Pattern( const QString& name, const QString& info, const QString& category, int length, int denominator ) : Object( __class_name ) - , __length( length ) , __denominator( denominator) , __name( name ) , __info( info ) , __category( category ) { + m_nResolution = H2Core::Song::nDefaultResolutionTPQN; + // Default to a pattern length of a whole note. + if ( length <= 0 ) { + __length = 4 * m_nResolution; + } else { + __length = length; + } } Pattern::Pattern( Pattern* other ) @@ -101,6 +109,7 @@ Pattern* Pattern::load_from( XMLNode* node, InstrumentList* instruments ) if ( pattern->get_name().isEmpty() ) { pattern->set_name( node->read_string( "pattern_name", "unknown", false, false ) ); } + pattern->set_resolution( node->read_int( "resolution", Song::nDefaultResolutionTPQN ) ); XMLNode note_list_node = node->firstChildElement( "noteList" ); if ( !note_list_node.isNull() ) { XMLNode note_node = note_list_node.firstChildElement( "note" ); @@ -139,6 +148,7 @@ void Pattern::save_to( XMLNode* node, const Instrument* instrumentOnly ) const pattern_node.write_string( "category", __category ); pattern_node.write_int( "size", __length ); pattern_node.write_int( "denominator", __denominator ); + pattern_node.write_int( "resolution", m_nResolution ); XMLNode note_list_node = pattern_node.createNode( "noteList" ); int id = ( instrumentOnly == nullptr ? -1 : instrumentOnly->get_id() ); for( auto it=__notes.cbegin(); it!=__notes.cend(); ++it ) { diff --git a/src/core/Basics/Pattern.h b/src/core/Basics/Pattern.h index 5f20b80ef..aa6e8588c 100644 --- a/src/core/Basics/Pattern.h +++ b/src/core/Basics/Pattern.h @@ -63,7 +63,8 @@ class Pattern : public H2Core::Object * \param length the length of the pattern * \param denominator the denominator for meter representation (eg 4/4) */ - Pattern( const QString& name="Pattern", const QString& info="", const QString& category="not_categorized", int length=MAX_NOTES, int denominator=4 ); + Pattern( const QString& name="Pattern", const QString& info="", const QString& category="not_categorized", + int length=-1, int denominator=4 ); /** copy constructor */ Pattern( Pattern* other ); /** destructor */ @@ -194,9 +195,16 @@ class Pattern : public H2Core::Object */ void save_to( XMLNode* node, const Instrument* instrumentOnly = nullptr ) const; + /// Get resolution of the pattern, in ticks per quarter-note + int get_resolution() const; + + /// Set resolution, in ticks per quarter-note. + /// This does not alter the length of the pattern or the position of notes + void set_resolution( int nResolution ); + private: int __length; ///< the length of the pattern - int __denominator; ///< the meter denominator of the pattern used in meter (eg 4/4) + int __denominator; ///< the meter denominator of the pattern used in meter (eg 4/4) QString __name; ///< the name of thepattern QString __category; ///< the category of the pattern QString __info; ///< a description of the pattern @@ -210,6 +218,9 @@ class Pattern : public H2Core::Object * \return a new Pattern instance */ static Pattern* load_from( XMLNode* node, InstrumentList* instruments ); + + int m_nResolution; ///< Resolution in ticks per quarter-note + }; #define FOREACH_NOTE_CST_IT_BEGIN_END(_notes,_it) \ @@ -322,8 +333,19 @@ inline void Pattern::flattened_virtual_patterns_clear() __flattened_virtual_patterns.clear(); } +inline int Pattern::get_resolution() const +{ + return m_nResolution; +} + +inline void Pattern::set_resolution( int nResolution ) +{ + m_nResolution = nResolution; +} + }; + #endif // H2C_PATTERN_H /* vim: set softtabstop=4 noexpandtab: */ diff --git a/src/core/Basics/Song.cpp b/src/core/Basics/Song.cpp index 8bd7ff4ee..edcbe41aa 100644 --- a/src/core/Basics/Song.cpp +++ b/src/core/Basics/Song.cpp @@ -67,7 +67,7 @@ const char* Song::__class_name = "Song"; Song::Song( const QString& sName, const QString& sAuthor, float fBpm, float fVolume ) : Object( __class_name ) , m_bIsMuted( false ) - , m_resolution( 48 ) + , m_resolution( nDefaultResolutionTPQN ) , m_fBpm( fBpm ) , m_sName( sName ) , m_sAuthor( sAuthor ) @@ -689,6 +689,7 @@ Song* SongReader::readSong( const QString& sFileName ) float fBpm = LocalFileMng::readXmlFloat( songNode, "bpm", 120 ); Hydrogen::get_instance()->setNewBpmJTM( fBpm ); + int nResolution = LocalFileMng::readXmlFloat( songNode, "resolution", Song::nDefaultResolutionTPQN ); float fVolume = LocalFileMng::readXmlFloat( songNode, "volume", 0.5 ); float fMetronomeVolume = LocalFileMng::readXmlFloat( songNode, "metronomeVolume", 0.5 ); QString sName( LocalFileMng::readXmlString( songNode, "name", "Untitled Song" ) ); @@ -724,6 +725,7 @@ Song* SongReader::readSong( const QString& sFileName ) float fSwingFactor = LocalFileMng::readXmlFloat( songNode, "swing_factor", 0.0 ); pSong = new Song( sName, sAuthor, fBpm, fVolume ); + pSong->setResolution( nResolution ); pSong->setMetronomeVolume( fMetronomeVolume ); pSong->setNotes( sNotes ); pSong->setLicense( sLicense ); diff --git a/src/core/Basics/Song.h b/src/core/Basics/Song.h index 0f41daae1..da34eb3de 100644 --- a/src/core/Basics/Song.h +++ b/src/core/Basics/Song.h @@ -60,12 +60,16 @@ class Song : public H2Core::Object SONG_MODE }; + static constexpr int nDefaultResolutionTPQN = 48; + Song( const QString& sName, const QString& sAuthor, float fBpm, float fVolume ); ~Song(); static Song* getEmptySong(); static Song* getDefaultSong(); + int getDefaultPatternSize() const; + bool getIsMuted() const; void setIsMuted( bool bIsMuted ); @@ -285,6 +289,11 @@ class Song : public H2Core::Object }; +inline int Song::getDefaultPatternSize() const +{ + return getResolution() * 4; +} + inline bool Song::getIsMuted() const { return m_bIsMuted; diff --git a/src/core/Hydrogen.cpp b/src/core/Hydrogen.cpp index 076ca5439..1f0920dc5 100644 --- a/src/core/Hydrogen.cpp +++ b/src/core/Hydrogen.cpp @@ -1738,7 +1738,7 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) // PATTERN MODE else if ( pSong->getMode() == Song::PATTERN_MODE ) { - int nPatternSize = MAX_NOTES; + int nPatternSize = pSong->getDefaultPatternSize(); // If the user chose to playback the pattern she focuses, // use it to overwrite `m_pPlayingPatterns`. @@ -1802,7 +1802,7 @@ inline int audioEngine_updateNoteQueue( unsigned nFrames ) ////////////////////////////////////////////////////////////// // Metronome // Only trigger the metronome at a predefined rate. - if ( m_nPatternTickPosition % 48 == 0 ) { + if ( m_nPatternTickPosition % pSong->getResolution() == 0 ) { float fPitch; float fVelocity; @@ -1934,8 +1934,8 @@ inline int findPatternInTick( int nTick, bool bLoopMode, int* pPatternStartTick std::vector *pPatternColumns = pSong->getPatternGroupVector(); int nColumns = pPatternColumns->size(); - // Sum the lengths of all pattern columns and use the macro - // MAX_NOTES in case some of them are of size zero. If the + // Sum the lengths of all pattern columns and use the default length + // in case some of them are of size zero. If the // supplied value nTick is bigger than this and doesn't belong to // the next pattern column, we just found the pattern list we were // searching for. @@ -1945,7 +1945,7 @@ inline int findPatternInTick( int nTick, bool bLoopMode, int* pPatternStartTick if ( pColumn->size() != 0 ) { nPatternSize = pColumn->longest_pattern_length(); } else { - nPatternSize = MAX_NOTES; + nPatternSize = pSong->getDefaultPatternSize(); } if ( ( nTick >= nTotalTick ) && ( nTick < nTotalTick + nPatternSize ) ) { @@ -1971,7 +1971,7 @@ inline int findPatternInTick( int nTick, bool bLoopMode, int* pPatternStartTick if ( pColumn->size() != 0 ) { nPatternSize = pColumn->longest_pattern_length(); } else { - nPatternSize = MAX_NOTES; + nPatternSize = pSong->getDefaultPatternSize(); } if ( ( nLoopTick >= nTotalTick ) @@ -2564,16 +2564,16 @@ void Hydrogen::addRealtimeNote( int instrument, UNUSED( pitch ); Preferences *pPreferences = Preferences::get_instance(); + Song *pSong = getSong(); unsigned int nRealColumn = 0; unsigned res = pPreferences->getPatternEditorGridResolution(); int nBase = pPreferences->isPatternEditorUsingTriplets() ? 3 : 4; - int scalar = ( 4 * MAX_NOTES ) / ( res * nBase ); + int scalar = ( 4 * pSong->getResolution() * 4 ) / ( res * nBase ); bool hearnote = forcePlay; int currentPatternNumber; AudioEngine::get_instance()->lock( RIGHT_HERE ); - Song *pSong = getSong(); if ( !pPreferences->__playselectedinstrument ) { if ( instrument >= ( int ) pSong->getInstrumentList()->size() ) { // unused instrument @@ -3338,7 +3338,7 @@ long Hydrogen::getTickForPosition( int pos ) { nPatternSize = pColumn->longest_pattern_length(); } else { - nPatternSize = MAX_NOTES; + nPatternSize = pSong->getDefaultPatternSize(); } totalTick += nPatternSize; } @@ -3755,19 +3755,19 @@ long Hydrogen::getPatternLength( int nPattern ) if ( pSong->getIsLoopEnabled() ) { nPattern = nPattern % nPatternGroups; } else { - return MAX_NOTES; + return pSong->getDefaultPatternSize(); } } if ( nPattern < 1 ){ - return MAX_NOTES; + return pSong->getDefaultPatternSize(); } PatternList* pPatternList = pColumns->at( nPattern - 1 ); if ( pPatternList->size() > 0 ) { return pPatternList->longest_pattern_length(); } else { - return MAX_NOTES; + return pSong->getDefaultPatternSize(); } } diff --git a/src/core/Hydrogen.h b/src/core/Hydrogen.h index c652553ae..8541744fa 100644 --- a/src/core/Hydrogen.h +++ b/src/core/Hydrogen.h @@ -309,8 +309,8 @@ class Hydrogen : public H2Core::Object * * The function will loop over all and sums up their * Pattern::__length. If one of the Pattern is NULL or no - * Pattern is present one of the PatternList, #MAX_NOTES will - * be added instead. + * Pattern is present one of the PatternList, the default + * pattern length will be added instead. * * The driver should be LOCKED when calling this! * diff --git a/src/core/IO/DiskWriterDriver.cpp b/src/core/IO/DiskWriterDriver.cpp index a0466bca2..2d5ba2aae 100644 --- a/src/core/IO/DiskWriterDriver.cpp +++ b/src/core/IO/DiskWriterDriver.cpp @@ -169,7 +169,7 @@ void* diskWriterDriver_thread( void* param ) if ( pColumn->size() != 0 ) { nPatternSize = pColumn->longest_pattern_length(); } else { - nPatternSize = MAX_NOTES; + nPatternSize = 4 * pSong->getResolution(); } // check pattern bpm if timeline bpm is in use diff --git a/src/core/LocalFileMgr.cpp b/src/core/LocalFileMgr.cpp index 71d383eec..35cfe2c8a 100644 --- a/src/core/LocalFileMgr.cpp +++ b/src/core/LocalFileMgr.cpp @@ -357,6 +357,8 @@ int SongWriter::writeSong( Song * pSong, const QString& filename ) QDomNode songNode = doc.createElement( "song" ); LocalFileMng::writeXmlString( songNode, "version", QString( get_version().c_str() ) ); + LocalFileMng::writeXmlString( songNode, "resolution", QString("%1").arg( pSong->getResolution() ) ); + LocalFileMng::writeXmlString( songNode, "bpm", QString("%1").arg( pSong->getBpm() ) ); LocalFileMng::writeXmlString( songNode, "volume", QString("%1").arg( pSong->getVolume() ) ); LocalFileMng::writeXmlString( songNode, "metronomeVolume", QString("%1").arg( pSong->getMetronomeVolume() ) ); @@ -366,7 +368,7 @@ int SongWriter::writeSong( Song * pSong, const QString& filename ) LocalFileMng::writeXmlString( songNode, "license", pSong->getLicense() ); LocalFileMng::writeXmlBool( songNode, "loopEnabled", pSong->getIsLoopEnabled() ); LocalFileMng::writeXmlBool( songNode, "patternModeMode", Preferences::get_instance()->patternModePlaysSelected()); - + LocalFileMng::writeXmlString( songNode, "playbackTrackFilename", QString("%1").arg( pSong->getPlaybackTrackFilename() ) ); LocalFileMng::writeXmlBool( songNode, "playbackTrackEnabled", pSong->getPlaybackTrackEnabled() ); LocalFileMng::writeXmlString( songNode, "playbackTrackVolume", QString("%1").arg( pSong->getPlaybackTrackVolume() ) ); diff --git a/src/core/Sampler/Sampler.cpp b/src/core/Sampler/Sampler.cpp index debf84e09..3b8660356 100644 --- a/src/core/Sampler/Sampler.cpp +++ b/src/core/Sampler/Sampler.cpp @@ -1510,7 +1510,7 @@ void Sampler::preview_instrument(Instrument* pInstr ) m_pPreviewInstrument = pInstr; pInstr->set_is_preview_instrument(true); - Note *pPreviewNote = new Note( m_pPreviewInstrument, 0, 1.0, 0.5, 0.5, MAX_NOTES, 0 ); + Note *pPreviewNote = new Note( m_pPreviewInstrument, 0, 1.0, 0.5, 0.5, -1, 0 ); noteOn( pPreviewNote ); // exclusive note AudioEngine::get_instance()->unlock(); diff --git a/src/core/Smf/Smf.cpp b/src/core/Smf/Smf.cpp index 337a5ef61..2ddeac71f 100644 --- a/src/core/Smf/Smf.cpp +++ b/src/core/Smf/Smf.cpp @@ -204,7 +204,6 @@ std::vector SMF::getBuffer() // :::::::::::::::::::... -constexpr unsigned int TPQN = 192; constexpr unsigned int DRUM_CHANNEL = 9; constexpr unsigned int NOTE_LENGTH = 12; @@ -379,7 +378,8 @@ SMF1Writer::~SMF1Writer() SMF* SMF1Writer::createSMF( Song* pSong ){ - SMF* pSmf = new SMF( 1, TPQN ); + // TODO: MIDI export uses a TPQN 4 times as high as it should be. We should fix that. + SMF* pSmf = new SMF( 1, pSong->getResolution() * 4 ); // Standard MIDI format 1 files should have the first track being the tempo map // which is a track that contains global meta events only. @@ -534,7 +534,7 @@ SMF0Writer::~SMF0Writer() SMF* SMF0Writer::createSMF( Song* pSong ){ // MIDI files format 0 have all their events in one track - SMF* pSmf = new SMF( 0, TPQN ); + SMF* pSmf = new SMF( 0, pSong->getResolution() * 4 ); m_pTrack = createTrack0( pSong ); pSmf->addTrack( m_pTrack ); return pSmf; diff --git a/src/gui/src/PatternEditor/DrumPatternEditor.cpp b/src/gui/src/PatternEditor/DrumPatternEditor.cpp index 7b71744ae..62f572abc 100644 --- a/src/gui/src/PatternEditor/DrumPatternEditor.cpp +++ b/src/gui/src/PatternEditor/DrumPatternEditor.cpp @@ -73,10 +73,10 @@ DrumPatternEditor::~DrumPatternEditor() void DrumPatternEditor::updateEditor( bool bPatternOnly ) { - Hydrogen* engine = Hydrogen::get_instance(); + Hydrogen* pHydrogen = Hydrogen::get_instance(); // check engine state - int state = engine->getState(); + int state = pHydrogen->getState(); if ( (state != STATE_READY) && (state != STATE_PLAYING) ) { ERRORLOG( "FIXME: skipping pattern editor update (state should be READY or PLAYING)" ); return; @@ -88,7 +88,7 @@ void DrumPatternEditor::updateEditor( bool bPatternOnly ) m_nEditorWidth = m_nMargin + m_nGridWidth * m_pPattern->get_length(); } else { - m_nEditorWidth = m_nMargin + m_nGridWidth * MAX_NOTES; + m_nEditorWidth = m_nMargin + m_nGridWidth * pHydrogen->getSong()->getDefaultPatternSize(); } resize( m_nEditorWidth, height() ); @@ -1159,16 +1159,18 @@ void DrumPatternEditor::__draw_grid( QPainter& p ) drawGridLines( p ); static const UIStyle *pStyle = Preferences::get_instance()->getDefaultUIStyle(); - int nNotes = MAX_NOTES; + Song *pSong = Hydrogen::get_instance()->getSong(); + int nNotes; if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = pSong->getDefaultPatternSize(); } // fill the first half of the rect with a solid color static const QColor backgroundColor( pStyle->m_patternEditor_backgroundColor.getRed(), pStyle->m_patternEditor_backgroundColor.getGreen(), pStyle->m_patternEditor_backgroundColor.getBlue() ); static const QColor selectedRowColor( pStyle->m_patternEditor_selectedRowColor.getRed(), pStyle->m_patternEditor_selectedRowColor.getGreen(), pStyle->m_patternEditor_selectedRowColor.getBlue() ); int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - Song *pSong = Hydrogen::get_instance()->getSong(); int nInstruments = pSong->getInstrumentList()->size(); for ( uint i = 0; i < (uint)nInstruments; i++ ) { uint y = m_nGridHeight * i + 1; @@ -1190,12 +1192,14 @@ void DrumPatternEditor::__create_background( QPainter& p) static const QColor alternateRowColor( pStyle->m_patternEditor_alternateRowColor.getRed(), pStyle->m_patternEditor_alternateRowColor.getGreen(), pStyle->m_patternEditor_alternateRowColor.getBlue() ); static const QColor lineColor( pStyle->m_patternEditor_lineColor.getRed(), pStyle->m_patternEditor_lineColor.getGreen(), pStyle->m_patternEditor_lineColor.getBlue() ); - int nNotes = MAX_NOTES; + Song *pSong = Hydrogen::get_instance()->getSong(); + int nNotes; if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = pSong->getDefaultPatternSize(); } - Song *pSong = Hydrogen::get_instance()->getSong(); int nInstruments = pSong->getInstrumentList()->size(); if ( m_nEditorHeight != (int)( m_nGridHeight * nInstruments ) ) { diff --git a/src/gui/src/PatternEditor/NotePropertiesRuler.cpp b/src/gui/src/PatternEditor/NotePropertiesRuler.cpp index 85c6b6018..9615500d0 100644 --- a/src/gui/src/PatternEditor/NotePropertiesRuler.cpp +++ b/src/gui/src/PatternEditor/NotePropertiesRuler.cpp @@ -50,7 +50,7 @@ NotePropertiesRuler::NotePropertiesRuler( QWidget *parent, PatternEditorPanel *p m_Mode = mode; m_nGridWidth = (Preferences::get_instance())->getPatternEditorGridWidth(); - m_nEditorWidth = m_nMargin + m_nGridWidth * ( MAX_NOTES * 4 ); + m_nEditorWidth = m_nMargin + m_nGridWidth * ( Hydrogen::get_instance()->getSong()->getDefaultPatternSize() ); m_fLastSetValue = 0.0; m_bValueHasBeenSet = false; @@ -797,6 +797,7 @@ void NotePropertiesRuler::paintEvent( QPaintEvent *ev) void NotePropertiesRuler::createVelocityBackground(QPixmap *pixmap) { + Hydrogen *pHydrogen = Hydrogen::get_instance(); if ( !isVisible() ) { return; } @@ -815,9 +816,11 @@ void NotePropertiesRuler::createVelocityBackground(QPixmap *pixmap) pStyle->m_patternEditor_backgroundColor.getGreen() - 20, pStyle->m_patternEditor_backgroundColor.getBlue() - 20 ); - unsigned nNotes = MAX_NOTES; + unsigned nNotes; if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = pHydrogen->getSong()->getDefaultPatternSize(); } QPainter p( pixmap ); @@ -834,8 +837,8 @@ void NotePropertiesRuler::createVelocityBackground(QPixmap *pixmap) // draw velocity lines if (m_pPattern != nullptr) { - int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - Song *pSong = Hydrogen::get_instance()->getSong(); + int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); + Song *pSong = pHydrogen->getSong(); QPen selectedPen( selectedNoteColor( pStyle ) ); selectedPen.setWidth( 2 ); @@ -888,6 +891,7 @@ void NotePropertiesRuler::createVelocityBackground(QPixmap *pixmap) void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) { + Hydrogen *pHydrogen = Hydrogen::get_instance(); if ( !isVisible() ) { return; } @@ -908,9 +912,11 @@ void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) QPainter p( pixmap ); - unsigned nNotes = MAX_NOTES; - if (m_pPattern) { + unsigned nNotes; + if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = pHydrogen->getSong()->getDefaultPatternSize(); } p.fillRect( 0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor ); @@ -922,8 +928,8 @@ void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) drawGridLines( p, Qt::DotLine ); if ( m_pPattern ) { - int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - Song *pSong = Hydrogen::get_instance()->getSong(); + int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); + Song *pSong = pHydrogen->getSong(); QPen selectedPen( selectedNoteColor( pStyle ) ); selectedPen.setWidth( 2 ); @@ -978,6 +984,7 @@ void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) { + Hydrogen *pHydrogen = Hydrogen::get_instance(); if ( !isVisible() ) { return; } @@ -998,9 +1005,11 @@ void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) QPainter p( pixmap ); - unsigned nNotes = MAX_NOTES; - if (m_pPattern) { + unsigned nNotes; + if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = pHydrogen->getSong()->getDefaultPatternSize(); } p.fillRect( 0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor ); @@ -1012,8 +1021,8 @@ void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) drawGridLines( p, Qt::DotLine ); if ( m_pPattern ) { - int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - Song *pSong = Hydrogen::get_instance()->getSong(); + int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); + Song *pSong = pHydrogen->getSong(); QPen selectedPen( selectedNoteColor( pStyle ) ); selectedPen.setWidth( 2 ); @@ -1090,6 +1099,7 @@ void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) { + Hydrogen *pHydrogen = Hydrogen::get_instance(); if ( !isVisible() ) { return; } @@ -1108,9 +1118,11 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) pStyle->m_patternEditor_backgroundColor.getGreen() - 100, pStyle->m_patternEditor_backgroundColor.getBlue() - 100 ); - unsigned nNotes = MAX_NOTES; - if (m_pPattern) { + unsigned nNotes; + if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = pHydrogen->getSong()->getDefaultPatternSize(); } QPainter p( pixmap ); @@ -1158,8 +1170,8 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) //paint the octave if ( m_pPattern ) { - int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - Song *pSong = Hydrogen::get_instance()->getSong(); + int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); + Song *pSong = pHydrogen->getSong(); QPen selectedPen( selectedNoteColor( pStyle ) ); selectedPen.setWidth( 2 ); @@ -1181,8 +1193,8 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) //paint the note if ( m_pPattern ) { - int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - Song *pSong = Hydrogen::get_instance()->getSong(); + int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); + Song *pSong = pHydrogen->getSong(); QPen selectedPen( selectedNoteColor( pStyle ) ); selectedPen.setWidth( 2 ); @@ -1225,9 +1237,9 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) void NotePropertiesRuler::updateEditor( bool bPatternOnly ) { - Hydrogen *pEngine = Hydrogen::get_instance(); - PatternList *pPatternList = pEngine->getSong()->getPatternList(); - int nSelectedPatternNumber = pEngine->getSelectedPatternNumber(); + Hydrogen *pHydrogen= Hydrogen::get_instance(); + PatternList *pPatternList = pHydrogen->getSong()->getPatternList(); + int nSelectedPatternNumber = pHydrogen->getSelectedPatternNumber(); if ( (nSelectedPatternNumber != -1) && ( (uint)nSelectedPatternNumber < pPatternList->size() ) ) { m_pPattern = pPatternList->get( nSelectedPatternNumber ); } @@ -1241,7 +1253,7 @@ void NotePropertiesRuler::updateEditor( bool bPatternOnly ) m_nEditorWidth = m_nMargin + m_pPattern->get_length() * m_nGridWidth; } else { - m_nEditorWidth = m_nMargin + MAX_NOTES * m_nGridWidth; + m_nEditorWidth = m_nMargin + pHydrogen->getSong()->getDefaultPatternSize() * m_nGridWidth; } if ( !m_bNeedsUpdate ) { diff --git a/src/gui/src/PatternEditor/PatternEditor.cpp b/src/gui/src/PatternEditor/PatternEditor.cpp index 74b127be5..e3af56969 100644 --- a/src/gui/src/PatternEditor/PatternEditor.cpp +++ b/src/gui/src/PatternEditor/PatternEditor.cpp @@ -63,7 +63,7 @@ PatternEditor::PatternEditor( QWidget *pParent, const char *sClassName, m_bCopyNotMove = false; m_nGridWidth = Preferences::get_instance()->getPatternEditorGridWidth(); - m_nEditorWidth = m_nMargin + m_nGridWidth * ( MAX_NOTES * 4 ); + m_nEditorWidth = m_nMargin + m_nGridWidth * ( 4 * Hydrogen::get_instance()->getSong()->getDefaultPatternSize() ); setFocusPolicy(Qt::StrongFocus); @@ -333,9 +333,11 @@ void PatternEditor::drawGridLines( QPainter &p, Qt::PenStyle style ) const }; int nGranularity = granularity() * m_nResolution; - int nNotes = MAX_NOTES; + int nNotes; if ( m_pPattern ) { nNotes = m_pPattern->get_length(); + } else { + nNotes = Hydrogen::get_instance()->getSong()->getDefaultPatternSize(); } int nMaxX = m_nGridWidth * nNotes + m_nMargin; diff --git a/src/gui/src/PatternEditor/PatternEditor.h b/src/gui/src/PatternEditor/PatternEditor.h index 8526a76bc..519fb53b5 100644 --- a/src/gui/src/PatternEditor/PatternEditor.h +++ b/src/gui/src/PatternEditor/PatternEditor.h @@ -27,6 +27,8 @@ #include "../Selection.h" #include +#include +#include #include #if QT_VERSION >= 0x050000 @@ -141,7 +143,7 @@ public slots: else { nBase = 4; } - return 4 * MAX_NOTES / ( nBase * m_nResolution ); + return 4 * H2Core::Hydrogen::get_instance()->getSong()->getResolution() * 4 / ( nBase * m_nResolution ); } uint m_nEditorHeight; diff --git a/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp b/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp index f5feb0ab3..411d19ec3 100644 --- a/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp +++ b/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp @@ -374,6 +374,7 @@ void InstrumentLine::functionFillNotes( int every ) PatternEditorPanel *pPatternEditorPanel = HydrogenApp::get_instance()->getPatternEditorPanel(); DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor(); + Pattern* pCurrentPattern = getCurrentPattern(); int nBase; if ( pPatternEditor->isUsingTriplets() ) { nBase = 3; @@ -381,14 +382,12 @@ void InstrumentLine::functionFillNotes( int every ) else { nBase = 4; } - int nResolution = 4 * MAX_NOTES * every / ( nBase * pPatternEditor->getResolution() ); - + int nResolution = 4 * pCurrentPattern->get_resolution() * 4 * every / ( nBase * pPatternEditor->getResolution() ); Song *pSong = pEngine->getSong(); QStringList notePositions; - Pattern* pCurrentPattern = getCurrentPattern(); if (pCurrentPattern != nullptr) { int nPatternSize = pCurrentPattern->get_length(); int nSelectedInstrument = pEngine->getSelectedInstrumentNumber(); diff --git a/src/gui/src/PatternEditor/PatternEditorPanel.cpp b/src/gui/src/PatternEditor/PatternEditorPanel.cpp index be7a0c9c0..71ed877dc 100644 --- a/src/gui/src/PatternEditor/PatternEditorPanel.cpp +++ b/src/gui/src/PatternEditor/PatternEditorPanel.cpp @@ -522,7 +522,7 @@ PatternEditorPanel::PatternEditorPanel( QWidget *pParent ) // restore grid resolution int nIndex; int nRes = pPref->getPatternEditorGridResolution(); - if ( nRes == MAX_NOTES ) { + if ( nRes > 64 ) { nIndex = 11; } else if ( pPref->isPatternEditorUsingTriplets() == false ) { switch ( nRes ) { @@ -629,7 +629,9 @@ void PatternEditorPanel::gridResolutionChanged( int nSelected ) bool bUseTriplets = false; if ( nSelected == 11 ) { - nResolution = MAX_NOTES; + // Highest possible grid resolution: use the full resolution of the pattern + int nRes = m_pPattern? m_pPattern->get_resolution() : Song::nDefaultResolutionTPQN; + nResolution = nRes * 4; } else if ( nSelected > 4 ) { bUseTriplets = true; @@ -649,7 +651,13 @@ void PatternEditorPanel::gridResolutionChanged( int nSelected ) m_pNoteProbabilityEditor->setResolution( nResolution, bUseTriplets ); m_pNotePanEditor->setResolution( nResolution, bUseTriplets ); - m_nCursorIncrement = ( bUseTriplets ? 4 : 3 ) * MAX_NOTES / ( nResolution * 3 ); + int nPatternRes; + if ( m_pPattern ) { + nPatternRes = m_pPattern->get_resolution(); + } else { + nPatternRes = Hydrogen::get_instance()->getSong()->getResolution(); + } + m_nCursorIncrement = ( bUseTriplets ? 4 : 3 ) * nPatternRes * 4 / ( nResolution * 3 ); m_nCursorPosition = m_nCursorIncrement * ( m_nCursorPosition / m_nCursorIncrement ); Preferences::get_instance()->setPatternEditorGridResolution( nResolution ); diff --git a/src/gui/src/PatternEditor/PatternEditorRuler.cpp b/src/gui/src/PatternEditor/PatternEditorRuler.cpp index cfc2c2709..c7193596c 100644 --- a/src/gui/src/PatternEditor/PatternEditorRuler.cpp +++ b/src/gui/src/PatternEditor/PatternEditorRuler.cpp @@ -58,7 +58,8 @@ PatternEditorRuler::PatternEditorRuler( QWidget* parent ) m_pPattern = nullptr; m_nGridWidth = Preferences::get_instance()->getPatternEditorGridWidth(); - m_nRulerWidth = 20 + m_nGridWidth * ( MAX_NOTES * 4 ); + // The ruler is always set to be 4 whole notes in size, regardless of the actual pattern length. Should it be variable? + m_nRulerWidth = 20 + m_nGridWidth * ( Song::nDefaultResolutionTPQN * 4 * 4 ); m_nRulerHeight = 25; resize( m_nRulerWidth, m_nRulerHeight ); @@ -207,7 +208,7 @@ void PatternEditorRuler::paintEvent( QPaintEvent *ev) painter.drawLine( 0, 0, m_nRulerWidth, 0 ); painter.drawLine( 0, m_nRulerHeight - 1, m_nRulerWidth - 1, m_nRulerHeight - 1); - uint nQuarter = 48; + uint nQuarter = Hydrogen::get_instance()->getSong()->getResolution(); for ( int i = 0; i < 64 ; i++ ) { int nText_x = 20 + nQuarter / 4 * i * m_nGridWidth; @@ -241,7 +242,13 @@ void PatternEditorRuler::zoomIn() { m_nGridWidth *= 1.5; } - m_nRulerWidth = 20 + m_nGridWidth * ( MAX_NOTES * 4 ); + int nRes; + if ( m_pPattern ) { + nRes = m_pPattern->get_resolution(); + } else { + nRes = Hydrogen::get_instance()->getSong()->getResolution(); + } + m_nRulerWidth = 20 + m_nGridWidth * ( nRes * 4 * 4 ); resize( QSize(m_nRulerWidth, m_nRulerHeight )); delete m_pBackground; m_pBackground = new QPixmap( m_nRulerWidth, m_nRulerHeight ); @@ -255,20 +262,26 @@ void PatternEditorRuler::zoomIn() void PatternEditorRuler::zoomOut() { if ( m_nGridWidth > 1.5 ) { - if (m_nGridWidth > 3){ + if (m_nGridWidth > 3) { m_nGridWidth /= 2; - }else - { + } else { m_nGridWidth /= 1.5; } - m_nRulerWidth = 20 + m_nGridWidth * ( MAX_NOTES * 4 ); - resize( QSize(m_nRulerWidth, m_nRulerHeight) ); - delete m_pBackground; - m_pBackground = new QPixmap( m_nRulerWidth, m_nRulerHeight ); - UIStyle *pStyle = Preferences::get_instance()->getDefaultUIStyle(); - QColor backgroundColor( pStyle->m_patternEditor_backgroundColor.getRed(), pStyle->m_patternEditor_backgroundColor.getGreen(), pStyle->m_patternEditor_backgroundColor.getBlue() ); - m_pBackground->fill( backgroundColor ); - update(); + int nRes; + if ( m_pPattern ) { + nRes = m_pPattern->get_resolution(); + } else { + nRes = Hydrogen::get_instance()->getSong()->getResolution(); + } + m_nRulerWidth = 20 + m_nGridWidth * ( nRes * 4 * 4 ); + + resize( QSize(m_nRulerWidth, m_nRulerHeight) ); + delete m_pBackground; + m_pBackground = new QPixmap( m_nRulerWidth, m_nRulerHeight ); + UIStyle *pStyle = Preferences::get_instance()->getDefaultUIStyle(); + QColor backgroundColor( pStyle->m_patternEditor_backgroundColor.getRed(), pStyle->m_patternEditor_backgroundColor.getGreen(), pStyle->m_patternEditor_backgroundColor.getBlue() ); + m_pBackground->fill( backgroundColor ); + update(); } } diff --git a/src/gui/src/PatternEditor/PianoRollEditor.cpp b/src/gui/src/PatternEditor/PianoRollEditor.cpp index e54571830..6f1dcd008 100644 --- a/src/gui/src/PatternEditor/PianoRollEditor.cpp +++ b/src/gui/src/PatternEditor/PianoRollEditor.cpp @@ -81,12 +81,12 @@ PianoRollEditor::~PianoRollEditor() void PianoRollEditor::updateEditor( bool bPatternOnly ) { - // uint nEditorWidth; if ( m_pPattern ) { m_nEditorWidth = m_nMargin + m_nGridWidth * m_pPattern->get_length(); } else { - m_nEditorWidth = m_nMargin + m_nGridWidth * MAX_NOTES; + Song *pSong = Hydrogen::get_instance()->getSong(); + m_nEditorWidth = m_nMargin + m_nGridWidth * pSong->getDefaultPatternSize(); } if ( !bPatternOnly ) { m_bNeedsBackgroundUpdate = true; diff --git a/src/gui/src/SongEditor/SongEditorPanel.cpp b/src/gui/src/SongEditor/SongEditorPanel.cpp index 5575ddf9f..05a60259c 100644 --- a/src/gui/src/SongEditor/SongEditorPanel.cpp +++ b/src/gui/src/SongEditor/SongEditorPanel.cpp @@ -573,7 +573,9 @@ void SongEditorPanel::newPatBtnClicked( Button* btn ) if ( pDialog->exec() == QDialog::Accepted ) { SE_insertPatternAction* pAction = - new SE_insertPatternAction( pEngine->getSelectedPatternNumber() + 1, new Pattern( pNewPattern->get_name() , pNewPattern->get_info(), pNewPattern->get_category() ) ); + new SE_insertPatternAction( pEngine->getSelectedPatternNumber() + 1, + new Pattern( pNewPattern->get_name() , pNewPattern->get_info(), + pNewPattern->get_category(), 4 * pSong->getResolution()) ); HydrogenApp::get_instance()->m_pUndoStack->push( pAction ); } From 58548b62d7d2bd777c864e8a9b50996eb5d02dc6 Mon Sep 17 00:00:00 2001 From: Colin McEwan Date: Thu, 11 Feb 2021 23:32:25 +0000 Subject: [PATCH 2/3] Add retiming of patterns, and minimum resolution calculation. Not used yet. --- src/core/Basics/Pattern.cpp | 33 +++++++++++++++++++ src/core/Basics/Pattern.h | 6 ++++ .../src/PatternEditor/PatternEditorRuler.cpp | 7 +++- src/gui/src/SongEditor/SongEditor.cpp | 2 +- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/core/Basics/Pattern.cpp b/src/core/Basics/Pattern.cpp index 7c62e12aa..5b9d8f1b0 100644 --- a/src/core/Basics/Pattern.cpp +++ b/src/core/Basics/Pattern.cpp @@ -34,6 +34,8 @@ #include #include +#include + namespace H2Core { @@ -294,6 +296,37 @@ void Pattern::extand_with_flattened_virtual_patterns( PatternList* patterns ) } } +/// Calculate the minimum resolution that can be used to accurately represent the pattern. +int Pattern::get_minimum_resolution() const +{ + int nDenominator = 0; + for ( auto it : __notes ) { + int nPos = it.first; + nDenominator = std::gcd( nDenominator, nPos ); + } + return m_nResolution / nDenominator; +} + +/// Retime a pattern to a given resolution. This adjusts the positions of all notes to fit. +int Pattern::retime_to_resolution( int nResolution ) +{ + std::list< Note *> notes; + for ( auto it : __notes ) { + notes.push_back( it.second ); + } + + __notes.clear(); + + for ( auto pNote : notes ) { + int nPos = pNote->get_position() * nResolution / m_nResolution; + pNote->set_position( nPos ); + __notes.insert( std::make_pair( nPos, pNote ) ); + } + __length = __length * nResolution / m_nResolution; + m_nResolution = nResolution; +} + + }; /* vim: set softtabstop=4 noexpandtab: */ diff --git a/src/core/Basics/Pattern.h b/src/core/Basics/Pattern.h index aa6e8588c..546cab8de 100644 --- a/src/core/Basics/Pattern.h +++ b/src/core/Basics/Pattern.h @@ -202,6 +202,12 @@ class Pattern : public H2Core::Object /// This does not alter the length of the pattern or the position of notes void set_resolution( int nResolution ); + /// Calculate the minimum resolution that can be used to accurately represent the pattern. + int get_minimum_resolution() const; + + /// Retime a pattern to a given resolution. This adjusts the positions of all notes to fit. + int retime_to_resolution( int nResolution ); + private: int __length; ///< the length of the pattern int __denominator; ///< the meter denominator of the pattern used in meter (eg 4/4) diff --git a/src/gui/src/PatternEditor/PatternEditorRuler.cpp b/src/gui/src/PatternEditor/PatternEditorRuler.cpp index c7193596c..87c2e0e4f 100644 --- a/src/gui/src/PatternEditor/PatternEditorRuler.cpp +++ b/src/gui/src/PatternEditor/PatternEditorRuler.cpp @@ -208,7 +208,12 @@ void PatternEditorRuler::paintEvent( QPaintEvent *ev) painter.drawLine( 0, 0, m_nRulerWidth, 0 ); painter.drawLine( 0, m_nRulerHeight - 1, m_nRulerWidth - 1, m_nRulerHeight - 1); - uint nQuarter = Hydrogen::get_instance()->getSong()->getResolution(); + uint nQuarter; + if ( m_pPattern ) { + nQuarter = m_pPattern->get_resolution(); + } else { + nQuarter = Hydrogen::get_instance()->getSong()->getResolution(); + } for ( int i = 0; i < 64 ; i++ ) { int nText_x = 20 + nQuarter / 4 * i * m_nGridWidth; diff --git a/src/gui/src/SongEditor/SongEditor.cpp b/src/gui/src/SongEditor/SongEditor.cpp index 2e44f0faa..50117d61c 100644 --- a/src/gui/src/SongEditor/SongEditor.cpp +++ b/src/gui/src/SongEditor/SongEditor.cpp @@ -2318,7 +2318,7 @@ void SongEditorPositionRuler::paintEvent( QPaintEvent *ev ) } else { // nessun pattern, uso la grandezza di default - fPos += (float)pHydrogen->getTickPosition() / (float)MAX_NOTES; + fPos += (float)pHydrogen->getTickPosition() / (float)pHydrogen->getSong()->getDefaultPatternSize(); } if ( pHydrogen->getSong()->getMode() == Song::PATTERN_MODE ) { From 65d6286f8f0ea0fcbf378cd1d0460411e46cec93 Mon Sep 17 00:00:00 2001 From: Colin McEwan Date: Fri, 12 Feb 2021 21:09:03 +0000 Subject: [PATCH 3/3] Bug fix: default common denominator should be 1 --- src/core/Basics/Pattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Basics/Pattern.cpp b/src/core/Basics/Pattern.cpp index 5b9d8f1b0..e236d90dc 100644 --- a/src/core/Basics/Pattern.cpp +++ b/src/core/Basics/Pattern.cpp @@ -299,7 +299,7 @@ void Pattern::extand_with_flattened_virtual_patterns( PatternList* patterns ) /// Calculate the minimum resolution that can be used to accurately represent the pattern. int Pattern::get_minimum_resolution() const { - int nDenominator = 0; + int nDenominator = 1; for ( auto it : __notes ) { int nPos = it.first; nDenominator = std::gcd( nDenominator, nPos );