diff --git a/data/hydrogen.default.conf b/data/hydrogen.default.conf index a6dedfba4..c9833a240 100644 --- a/data/hydrogen.default.conf +++ b/data/hydrogen.default.conf @@ -104,9 +104,10 @@ 1.1 8 + 4 + 4 21 3 - false true diff --git a/data/img/gray/patternEditor/background_res-new.png b/data/img/gray/patternEditor/background_res-new.png index 7480eaa3b..165f9dc2c 100644 Binary files a/data/img/gray/patternEditor/background_res-new.png and b/data/img/gray/patternEditor/background_res-new.png differ diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 5a02a2331..86d7d8b6e 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -117,7 +117,7 @@ AudioEngine::AudioEngine() , m_nPatternTickPosition( 0 ) , m_nSongSizeInTicks( 0 ) , m_nRealtimeFrames( 0 ) - , m_nAddRealtimeNoteTickPosition( 0 ) + , m_fAddRealtimeNoteTickPosition( 0. ) , m_nSongPos( -1 ) , m_nSelectedPatternNumber( 0 ) { @@ -932,7 +932,7 @@ inline void AudioEngine::processPlayNotes( unsigned long nframes ) float velocity_adjustment = 1.0f; if ( pSong->getMode() == Song::SONG_MODE ) { - float fPos = m_nSongPos + (pNote->get_position()%192) / 192.f; + float fPos = m_nSongPos + ( ( (int) pNote->get_position() ) %192) / 192.f; //TODO what if pattern length > 192? velocity_adjustment = vp->get_value(fPos); } @@ -1713,10 +1713,14 @@ inline int AudioEngine::updateNoteQueue( unsigned nFrames ) // iterator (notes won't be altered!). After some // humanization was applied to onset of each note, it // will be added to `m_songNoteQueue` for playback. - FOREACH_NOTE_CST_IT_BOUND(notes,it,m_nPatternTickPosition) { + //FOREACH_NOTE_CST_IT_BOUND(notes,it,m_nPatternTickPosition) { //TODO macro + for( Pattern::notes_cst_it_t it=notes->lower_bound( m_nPatternTickPosition ); + it != notes->end() && it->first < m_nPatternTickPosition + 1; + it++ ) { Note *pNote = it->second; if ( pNote ) { pNote->set_just_recorded( false ); + int nOffset = 0; // Swing // @@ -1761,9 +1765,10 @@ inline int AudioEngine::updateNoteQueue( unsigned nFrames ) // it the new offset, and push it to the list // of all notes, which are about to be played // back. - // TODO: Why a copy? + // Why a copy? because it has the new offset (including swing and random timing) in its + // humanized delay, and tick position is expressed referring to start time (and not pattern). Note *pCopiedNote = new Note( pNote ); - pCopiedNote->set_position( tick ); + pCopiedNote->set_position( tick + pNote->get_position() - floor( pNote->get_position() ) ); pCopiedNote->set_humanize_delay( nOffset ); pNote->get_instrument()->enqueue(); m_songNoteQueue.push( pCopiedNote ); diff --git a/src/core/AudioEngine.h b/src/core/AudioEngine.h index b9c6a6e68..d7e080303 100644 --- a/src/core/AudioEngine.h +++ b/src/core/AudioEngine.h @@ -704,8 +704,8 @@ class AudioEngine : public H2Core::Object unsigned long getRealtimeFrames() const; void setRealtimeFrames( unsigned long nFrames ); - unsigned int getAddRealtimeNoteTickPosition() const; - void setAddRealtimeNoteTickPosition( unsigned int tickPosition ); + double getAddRealtimeNoteTickPosition() const; + void setAddRealtimeNoteTickPosition( double fTickPosition ); struct timeval& getCurrentTickTime(); @@ -952,7 +952,7 @@ class AudioEngine : public H2Core::Object * position TransportInfo::m_nFrames. */ unsigned long m_nRealtimeFrames; - unsigned int m_nAddRealtimeNoteTickPosition; + double m_fAddRealtimeNoteTickPosition; /** * Current state of the H2Core::AudioEngine. @@ -1145,12 +1145,12 @@ inline void AudioEngine::setRealtimeFrames( unsigned long nFrames ) { m_nRealtimeFrames = nFrames; } -inline unsigned int AudioEngine::getAddRealtimeNoteTickPosition() const { - return m_nAddRealtimeNoteTickPosition; +inline double AudioEngine::getAddRealtimeNoteTickPosition() const { + return m_fAddRealtimeNoteTickPosition; } -inline void AudioEngine::setAddRealtimeNoteTickPosition( unsigned int tickPosition) { - m_nAddRealtimeNoteTickPosition = tickPosition; +inline void AudioEngine::setAddRealtimeNoteTickPosition( double fTickPosition) { + m_fAddRealtimeNoteTickPosition = fTickPosition; } }; diff --git a/src/core/Basics/Note.cpp b/src/core/Basics/Note.cpp index 3a306f7dc..7e8438d4f 100644 --- a/src/core/Basics/Note.cpp +++ b/src/core/Basics/Note.cpp @@ -37,7 +37,7 @@ namespace H2Core const char* Note::__class_name = "Note"; const char* Note::__key_str[] = { "C", "Cs", "D", "Ef", "E", "F", "Fs", "G", "Af", "A", "Bf", "B" }; -Note::Note( Instrument* instrument, int position, float velocity, float pan_l, float pan_r, int length, float pitch ) +Note::Note( Instrument* instrument, double position, float velocity, float pan_l, float pan_r, int length, float pitch ) : Object( __class_name ), __instrument( instrument ), __instrument_id( 0 ), @@ -212,14 +212,14 @@ void Note::dump() void Note::save_to( XMLNode* node ) { - node->write_int( "position", __position ); + node->write_double( "position", __position ); node->write_float( "leadlag", __lead_lag ); node->write_float( "velocity", __velocity ); node->write_float( "pan_L", __pan_l ); node->write_float( "pan_R", __pan_r ); node->write_float( "pitch", __pitch ); node->write_string( "key", key_to_string() ); - node->write_int( "length", __length ); + node->write_double( "length", __length ); node->write_int( "instrument", get_instrument()->get_id() ); node->write_bool( "note_off", __note_off ); node->write_float( "probability", __probability ); @@ -229,11 +229,11 @@ Note* Note::load_from( XMLNode* node, InstrumentList* instruments ) { Note* note = new Note( nullptr, - node->read_int( "position", 0 ), + node->read_double( "position", 0 ), node->read_float( "velocity", 0.8f ), node->read_float( "pan_L", 0.5f ), node->read_float( "pan_R", 0.5f ), - node->read_int( "length", -1 ), + node->read_double( "length", -1 ), node->read_float( "pitch", 0.0f ) ); note->set_lead_lag( node->read_float( "leadlag", 0, false, false ) ); @@ -253,11 +253,11 @@ QString Note::toQString( const QString& sPrefix, bool bShort ) const { sOutput = QString( "%1[Note]\n" ).arg( sPrefix ) .append( QString( "%1%2instrument_id: %3\n" ).arg( sPrefix ).arg( s ).arg( __instrument_id ) ) .append( QString( "%1%2specific_compo_id: %3\n" ).arg( sPrefix ).arg( s ).arg( __specific_compo_id ) ) - .append( QString( "%1%2position: %3\n" ).arg( sPrefix ).arg( s ).arg( __position ) ) + .append( QString( "%1%2position: %3\n" ).arg( sPrefix ).arg( s ).arg( __position, 0, 'g', 10 ) ) //TODO what precision? .append( QString( "%1%2velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __velocity ) ) .append( QString( "%1%2pan_l: %3\n" ).arg( sPrefix ).arg( s ).arg( __pan_l ) ) .append( QString( "%1%2pan_r: %3\n" ).arg( sPrefix ).arg( s ).arg( __pan_r ) ) - .append( QString( "%1%2length: %3\n" ).arg( sPrefix ).arg( s ).arg( __length ) ) + .append( QString( "%1%2length: %3\n" ).arg( sPrefix ).arg( s ).arg( __length, 0, 'g', 10 ) ) //TODO what precision? .append( QString( "%1%2pitch: %3\n" ).arg( sPrefix ).arg( s ).arg( __pitch ) ) .append( QString( "%1%2key: %3\n" ).arg( sPrefix ).arg( s ).arg( __key ) ) .append( QString( "%1%2octave: %3\n" ).arg( sPrefix ).arg( s ).arg( __octave ) ) @@ -291,11 +291,11 @@ QString Note::toQString( const QString& sPrefix, bool bShort ) const { sOutput = QString( "[Note]" ) .append( QString( ", instrument_id: %1" ).arg( __instrument_id ) ) .append( QString( ", specific_compo_id: %1" ).arg( __specific_compo_id ) ) - .append( QString( ", position: %1" ).arg( __position ) ) + .append( QString( ", position: %1" ).arg( __position, 0, 'g', 10 ) ) //TODO what precision? .append( QString( ", velocity: %1" ).arg( __velocity ) ) .append( QString( ", pan_l: %1" ).arg( __pan_l ) ) .append( QString( ", pan_r: %1" ).arg( __pan_r ) ) - .append( QString( ", length: %1" ).arg( __length ) ) + .append( QString( ", length: %1" ).arg( __length, 0, 'g', 10 ) ) //TODO what precision? .append( QString( ", pitch: %1" ).arg( __pitch ) ) .append( QString( ", key: %1" ).arg( __key ) ) .append( QString( ", octave: %1" ).arg( __octave ) ) diff --git a/src/core/Basics/Note.h b/src/core/Basics/Note.h index 6492cfcb6..b37a849cc 100644 --- a/src/core/Basics/Note.h +++ b/src/core/Basics/Note.h @@ -82,7 +82,7 @@ class Note : public H2Core::Object * \param length it's length * \param pitch it's pitch */ - Note( Instrument* instrument, int position, float velocity, float pan_l, float pan_r, int length, float pitch ); + Note( Instrument* instrument, double position, float velocity, float pan_l, float pan_r, int length, float pitch ); /** * copy constructor with an optional parameter @@ -136,9 +136,9 @@ class Note : public H2Core::Object * #__position setter * \param value the new value */ - void set_position( int value ); + void set_position( double value ); /** #__position accessor */ - int get_position() const; + double get_position() const; /** * #__velocity setter * \param value the new value @@ -171,9 +171,9 @@ class Note : public H2Core::Object * #__length setter * \param value the new value */ - void set_length( int value ); + void set_length( double value ); /** #__length accessor */ - int get_length() const; + double get_length() const; /** * #__pitch setter * \param value the new value @@ -299,6 +299,7 @@ class Note : public H2Core::Object * \param val_r the right channel value */ void compute_lr_values( float* val_l, float* val_r ); + /** Formatted string version for debugging purposes. * \param sPrefix String prefix which will be added in front of * every new line @@ -313,11 +314,11 @@ class Note : public H2Core::Object Instrument* __instrument; ///< the instrument to be played by this note int __instrument_id; ///< the id of the instrument played by this note int __specific_compo_id; ///< play a specific component, -1 if playing all - int __position; ///< note position inside the pattern + double __position; ///< note position inside the pattern, in ticks float __velocity; ///< velocity (intensity) of the note [0;1] float __pan_l; ///< pan of the note (left volume) [0;0.5] float __pan_r; ///< pan of the note (right volume) [0;0.5] - int __length; ///< the length of the note + double __length; ///< the length of the note float __pitch; ///< the frequency of the note Key __key; ///< the key, [0;11]==[C;B] Octave __octave; ///< the octave [-3;3] @@ -376,12 +377,12 @@ inline int Note::get_specific_compo_id() const return __specific_compo_id; } -inline void Note::set_position( int value ) +inline void Note::set_position( double value ) { __position = value; } -inline int Note::get_position() const +inline double Note::get_position() const { return __position; } @@ -406,12 +407,12 @@ inline float Note::get_lead_lag() const return __lead_lag; } -inline void Note::set_length( int value ) +inline void Note::set_length( double value ) { __length = value; } -inline int Note::get_length() const +inline double Note::get_length() const { return __length; } @@ -593,6 +594,7 @@ inline void Note::compute_lr_values( float* val_l, float* val_r ) }; + #endif // H2C_NOTE_H /* vim: set softtabstop=4 noexpandtab: */ diff --git a/src/core/Basics/Pattern.cpp b/src/core/Basics/Pattern.cpp index ac01714a9..21f9c80e2 100644 --- a/src/core/Basics/Pattern.cpp +++ b/src/core/Basics/Pattern.cpp @@ -151,9 +151,9 @@ void Pattern::save_to( XMLNode* node, const Instrument* instrumentOnly ) const } } -Note* Pattern::find_note( int idx_a, int idx_b, Instrument* instrument, Note::Key key, Note::Octave octave, bool strict ) const +Note* Pattern::find_note( double idx_a, int idx_b, Instrument* instrument, Note::Key key, Note::Octave octave, bool strict ) const { - for( notes_cst_it_t it=__notes.lower_bound( idx_a ); it!=__notes.upper_bound( idx_a ); it++ ) { + for( notes_cst_it_t it=__notes.lower_bound( idx_a - POS_EPSILON ) ; it!=__notes.upper_bound( idx_a + POS_EPSILON ); it++ ) { Note* note = it->second; assert( note ); if ( note->match( instrument, key, octave ) ) return note; @@ -176,10 +176,10 @@ Note* Pattern::find_note( int idx_a, int idx_b, Instrument* instrument, Note::Ke return nullptr; } -Note* Pattern::find_note( int idx_a, int idx_b, Instrument* instrument, bool strict ) const +Note* Pattern::find_note( double idx_a, int idx_b, Instrument* instrument, bool strict ) const { notes_cst_it_t it; - for( it=__notes.lower_bound( idx_a ); it!=__notes.upper_bound( idx_a ); it++ ) { + for( it=__notes.lower_bound( idx_a - POS_EPSILON ); it!=__notes.upper_bound( idx_a + POS_EPSILON ); it++ ) { Note* note = it->second; assert( note ); if ( note->get_instrument() == instrument ) return note; @@ -205,8 +205,8 @@ Note* Pattern::find_note( int idx_a, int idx_b, Instrument* instrument, bool str void Pattern::remove_note( Note* note ) { - int pos = note->get_position(); - for( notes_it_t it=__notes.lower_bound( pos ); it!=__notes.end() && it->first == pos; ++it ) { + double fPos = note->get_position(); + FOREACH_NOTE_IT_BOUND( &__notes, it, fPos ) { if( it->second==note ) { __notes.erase( it ); break; diff --git a/src/core/Basics/Pattern.h b/src/core/Basics/Pattern.h index 2fc3ed2dd..798b56bc2 100644 --- a/src/core/Basics/Pattern.h +++ b/src/core/Basics/Pattern.h @@ -43,7 +43,7 @@ class Pattern : public H2Core::Object H2_OBJECT public: ///< multimap note type - typedef std::multimap notes_t; + typedef std::multimap notes_t; ///< multimap note iterator type typedef notes_t::iterator notes_it_t; ///< multimap note const iterator type @@ -125,7 +125,7 @@ class Pattern : public H2Core::Object * \param strict if set to false, will search for a note around the given idx * \return the note if found, 0 otherwise */ - Note* find_note( int idx_a, int idx_b, Instrument* instrument, bool strict=true ) const; + Note* find_note( double idx_a, int idx_b, Instrument* instrument, bool strict=true ) const; // TODO second arg float /** * search for a note at a given index within __notes which correspond to the given arguments * \param idx_a the first __notes index to search in @@ -136,7 +136,7 @@ class Pattern : public H2Core::Object * \param strict if set to false, will search for a note around the given idx * \return the note if found, 0 otherwise */ - Note* find_note( int idx_a, int idx_b, Instrument* instrument, Note::Key key, Note::Octave octave, bool strict=true) const; + Note* find_note( double idx_a, int idx_b, Instrument* instrument, Note::Key key, Note::Octave octave, bool strict=true) const; // TODO second arg float /** * removes a given note from __notes, it's not deleted * \param note the note to be removed @@ -222,15 +222,17 @@ class Pattern : public H2Core::Object #define FOREACH_NOTE_CST_IT_BEGIN_END(_notes,_it) \ for( Pattern::notes_cst_it_t _it=(_notes)->begin(); (_it)!=(_notes)->end(); (_it)++ ) + +#define POS_EPSILON 0.0001 // TODO choose value. use as argument of macro? #define FOREACH_NOTE_CST_IT_BOUND(_notes,_it,_bound) \ - for( Pattern::notes_cst_it_t _it=(_notes)->lower_bound((_bound)); (_it)!=(_notes)->end() && (_it)->first == (_bound); (_it)++ ) + for( Pattern::notes_cst_it_t _it=(_notes)->lower_bound((_bound) - POS_EPSILON ); (_it)!=(_notes)->end() && (_it)->first < (_bound) + POS_EPSILON; (_it)++ ) #define FOREACH_NOTE_IT_BEGIN_END(_notes,_it) \ for( Pattern::notes_it_t _it=(_notes)->begin(); (_it)!=(_notes)->end(); (_it)++ ) #define FOREACH_NOTE_IT_BOUND(_notes,_it,_bound) \ - for( Pattern::notes_it_t _it=(_notes)->lower_bound((_bound)); (_it)!=(_notes)->end() && (_it)->first == (_bound); (_it)++ ) + for( Pattern::notes_it_t _it=(_notes)->lower_bound((_bound) - POS_EPSILON ); (_it)!=(_notes)->end() && (_it)->first < (_bound) + POS_EPSILON; (_it)++ ) // DEFINITIONS diff --git a/src/core/Basics/Song.cpp b/src/core/Basics/Song.cpp index 666f4a356..619a6926e 100644 --- a/src/core/Basics/Song.cpp +++ b/src/core/Basics/Song.cpp @@ -1531,7 +1531,7 @@ Pattern* SongReader::getPattern( QDomNode pattern, InstrumentList* pInstrList ) Note* pNote = nullptr; - unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 ); + double fPosition = LocalFileMng::readXmlDouble( noteNode, "position", 0 ); float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0, false, false ); float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f ); float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 ); @@ -1558,7 +1558,7 @@ Pattern* SongReader::getPattern( QDomNode pattern, InstrumentList* pInstrList ) noteoff = true; } - pNote = new Note( pInstrumentRef, nPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch ); + pNote = new Note( pInstrumentRef, fPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch ); pNote->set_key_octave( sKey ); pNote->set_lead_lag( fLeadLag ); pNote->set_note_off( noteoff ); @@ -1582,7 +1582,7 @@ Pattern* SongReader::getPattern( QDomNode pattern, InstrumentList* pInstrList ) Note* pNote = nullptr; - unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 ); + double fPosition = LocalFileMng::readXmlDouble( noteNode, "position", 0 ); float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0, false, false ); float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f ); float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 ); @@ -1595,7 +1595,7 @@ Pattern* SongReader::getPattern( QDomNode pattern, InstrumentList* pInstrList ) Instrument* instrRef = pInstrList->find( instrId ); assert( instrRef ); - pNote = new Note( instrRef, nPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch ); + pNote = new Note( instrRef, fPosition, fVelocity, fPan_L, fPan_R, nLength, nPitch ); pNote->set_lead_lag( fLeadLag ); //infoLog( "new note!! pos: " + toString( pNote->m_nPosition ) + "\t instr: " + instrId ); diff --git a/src/core/EventQueue.h b/src/core/EventQueue.h index caa653d25..6bb75de03 100644 --- a/src/core/EventQueue.h +++ b/src/core/EventQueue.h @@ -238,7 +238,7 @@ class EventQueue : public H2Core::Object Event pop_event(); struct AddMidiNoteVector { - int m_column; //position + double m_column; //position int m_row; //instrument row int m_pattern; // pattern number int m_length; diff --git a/src/core/Helpers/Xml.cpp b/src/core/Helpers/Xml.cpp index 8b36c815e..ee5432a43 100644 --- a/src/core/Helpers/Xml.cpp +++ b/src/core/Helpers/Xml.cpp @@ -85,6 +85,17 @@ float XMLNode::read_float( const QString& node, float default_value, bool inexis return c_locale.toFloat( ret ); } +double XMLNode::read_double( const QString& node, double default_value, bool inexistent_ok, bool empty_ok ) +{ + QString ret = read_child_node( node, inexistent_ok, empty_ok ); + if( ret.isNull() ) { + DEBUGLOG( QString( "Using default value %1 for %2" ).arg( default_value ).arg( node ) ); + return default_value; + } + QLocale c_locale = QLocale::c(); + return c_locale.toDouble( ret ); +} + int XMLNode::read_int( const QString& node, int default_value, bool inexistent_ok, bool empty_ok ) { QString ret = read_child_node( node, inexistent_ok, empty_ok ); @@ -156,6 +167,10 @@ void XMLNode::write_float( const QString& node, const float value ) { write_child_node( node, QString::number( value ) ); } +void XMLNode::write_double( const QString& node, const double value ) +{ + write_child_node( node, QString( "%1" ).arg( value, 0, 'g', 10 ) ); +} void XMLNode::write_int( const QString& node, const int value ) { write_child_node( node, QString::number( value ) ); diff --git a/src/core/Helpers/Xml.h b/src/core/Helpers/Xml.h index 088ca1fef..afa336467 100644 --- a/src/core/Helpers/Xml.h +++ b/src/core/Helpers/Xml.h @@ -52,6 +52,14 @@ class XMLNode : public H2Core::Object, public QDomNode * \param empty_ok if set to false output a DEBUG log line if the child node is empty */ float read_float( const QString& node, float default_value, bool inexistent_ok=true, bool empty_ok=true ); + /** + * reads a double stored into a child node + * \param node the name of the child node to read into + * \param default_value the value returned if something goes wrong + * \param inexistent_ok if set to false output a DEBUG log line if the node doesn't exists + * \param empty_ok if set to false output a DEBUG log line if the child node is empty + */ + double read_double( const QString& node, double default_value, bool inexistent_ok=true, bool empty_ok=true ); /** * reads a string stored into a child node * \param node the name of the child node to read into @@ -94,6 +102,12 @@ class XMLNode : public H2Core::Object, public QDomNode * \param value the value to write */ void write_float( const QString& node, const float value ); + /** + * write a double into a child node + * \param node the name of the child node to create + * \param value the value to write + */ + void write_double( const QString& node, const double value ); /** * write a string into a child node * \param node the name of the child node to create diff --git a/src/core/Hydrogen.cpp b/src/core/Hydrogen.cpp index 6b9c96994..48928ff2a 100644 --- a/src/core/Hydrogen.cpp +++ b/src/core/Hydrogen.cpp @@ -350,9 +350,6 @@ void Hydrogen::addRealtimeNote( int instrument, AudioEngine* pAudioEngine = m_pAudioEngine; Preferences *pPreferences = Preferences::get_instance(); unsigned int nRealColumn = 0; - unsigned res = pPreferences->getPatternEditorGridResolution(); - int nBase = pPreferences->isPatternEditorUsingTriplets() ? 3 : 4; - int scalar = ( 4 * MAX_NOTES ) / ( res * nBase ); bool hearnote = forcePlay; int currentPatternNumber; @@ -367,7 +364,7 @@ void Hydrogen::addRealtimeNote( int instrument, } } - // Get current partern and column, compensating for "lookahead" if required + // Get current pattern and column, compensating for "lookahead" if required const Pattern* currentPattern = nullptr; unsigned int column = 0; float fTickSize = pAudioEngine->getAudioDriver()->m_transport.m_fTickSize; @@ -459,18 +456,31 @@ void Hydrogen::addRealtimeNote( int instrument, nRealColumn = getRealtimeTickPosition(); + double fTickPosition; if ( currentPattern && pPreferences->getQuantizeEvents() ) { - // quantize it to scale - unsigned qcolumn = ( unsigned )::round( column / ( double )scalar ) * scalar; + //double fScalar = ( 4 * MAX_NOTES ) / ( res * nBase ); + int nResolution = pPreferences->getPatternEditorGridResolution(); + int nTupletNumerator = pPreferences->getPatternEditorGridTupletNumerator(); + int nTupletDenominator = pPreferences->getPatternEditorGridTupletDenominator(); + + // calculate the granularity = grid quantum length in ticks + double fGridQuantumInTicks = (double) MAX_NOTES * nTupletDenominator / ( nTupletNumerator * nResolution ); + + // get position of nearest grid mark + double fQuantizedColumn = round( column / fGridQuantumInTicks ) * fGridQuantumInTicks; //we have to make sure that no beat is added on the last displayed note in a bar //for example: if the pattern has 4 beats, the editor displays 5 beats, so we should avoid adding beats an note 5. - if ( qcolumn == currentPattern->get_length() ) qcolumn = 0; - column = qcolumn; + if ( fQuantizedColumn >= currentPattern->get_length() ) { + fTickPosition = 0.; + } else { + fTickPosition = fQuantizedColumn; + } + } else { + fTickPosition = column; } - unsigned position = column; - pAudioEngine->setAddRealtimeNoteTickPosition( column ); + pAudioEngine->setAddRealtimeNoteTickPosition( fTickPosition ); Instrument *instrRef = nullptr; if ( pSong ) { @@ -482,7 +492,7 @@ void Hydrogen::addRealtimeNote( int instrument, assert( currentPattern ); if ( doRecord ) { EventQueue::AddMidiNoteVector noteAction; - noteAction.m_column = column; + noteAction.m_column = fTickPosition; noteAction.m_pattern = currentPatternNumber; noteAction.f_velocity = velocity; noteAction.f_pan_L = pan_L; @@ -511,7 +521,7 @@ void Hydrogen::addRealtimeNote( int instrument, EventQueue::get_instance()->m_addMidiNoteVector.push_back(noteAction); // hear note if its not in the future - if ( pPreferences->getHearNewNotes() && position <= getTickPosition() ) { + if ( pPreferences->getHearNewNotes() && fTickPosition <= getTickPosition() ) { hearnote = true; } }/* if doRecord */ diff --git a/src/core/LocalFileMgr.cpp b/src/core/LocalFileMgr.cpp index b30862857..4e30e06de 100644 --- a/src/core/LocalFileMgr.cpp +++ b/src/core/LocalFileMgr.cpp @@ -135,6 +135,17 @@ float LocalFileMng::readXmlFloat( QDomNode node , const QString& nodeName, float } } +double LocalFileMng::readXmlDouble( QDomNode node , const QString& nodeName, double defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QString text = processNode( node, nodeName, bCanBeEmpty, bShouldExists ); + if ( text == nullptr ) { + _WARNINGLOG( QString( "\tusing default value : '%1' for node '%2'" ).arg( defaultValue ).arg( nodeName )); + return defaultValue; + } else { + return QLocale::c().toDouble( text ); + } +} + int LocalFileMng::readXmlInt( QDomNode node , const QString& nodeName, int defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) { QString text = processNode( node, nodeName, bCanBeEmpty, bShouldExists ); @@ -600,7 +611,7 @@ int SongWriter::writeSong( Song * pSong, const QString& filename ) assert( pNote ); QDomNode noteNode = doc.createElement( "note" ); - LocalFileMng::writeXmlString( noteNode, "position", QString("%1").arg( pNote->get_position() ) ); + LocalFileMng::writeXmlString( noteNode, "position", QString("%1").arg( pNote->get_position(), 0, 'g', 10 ) ); //TODO what precision? LocalFileMng::writeXmlString( noteNode, "leadlag", QString("%1").arg( pNote->get_lead_lag() ) ); LocalFileMng::writeXmlString( noteNode, "velocity", QString("%1").arg( pNote->get_velocity() ) ); LocalFileMng::writeXmlString( noteNode, "pan_L", QString("%1").arg( pNote->get_pan_l() ) ); @@ -610,7 +621,7 @@ int SongWriter::writeSong( Song * pSong, const QString& filename ) LocalFileMng::writeXmlString( noteNode, "key", pNote->key_to_string() ); - LocalFileMng::writeXmlString( noteNode, "length", QString("%1").arg( pNote->get_length() ) ); + LocalFileMng::writeXmlString( noteNode, "length", QString("%1").arg( pNote->get_length(), 0, 'g', 10 ) ); //TODO what precision? LocalFileMng::writeXmlString( noteNode, "instrument", QString("%1").arg( pNote->get_instrument()->get_id() ) ); QString noteoff = "false"; diff --git a/src/core/LocalFileMng.h b/src/core/LocalFileMng.h index aabfa700a..cd1bf9b52 100644 --- a/src/core/LocalFileMng.h +++ b/src/core/LocalFileMng.h @@ -61,6 +61,8 @@ class LocalFileMng : public H2Core::Object static QString readXmlString( QDomNode , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); static float readXmlFloat( QDomNode , const QString& nodeName, float defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static double readXmlDouble( QDomNode node, const QString& nodeName, double defaultValue, + bool bCanBeEmpty = false, bool bShouldExists = true, bool tinyXmlCompatMode = false); static int readXmlInt( QDomNode , const QString& nodeName, int defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); static bool readXmlBool( QDomNode , const QString& nodeName, bool defaultValue, bool bShouldExists = true , bool tinyXmlCompatMode = false ); static void convertFromTinyXMLString( QByteArray* str ); diff --git a/src/core/Preferences.cpp b/src/core/Preferences.cpp index 015664638..74009dd4e 100644 --- a/src/core/Preferences.cpp +++ b/src/core/Preferences.cpp @@ -198,7 +198,8 @@ Preferences::Preferences() mixerFontPointSize = 11; mixerFalloffSpeed = 1.1; m_nPatternEditorGridResolution = 8; - m_bPatternEditorUsingTriplets = false; + m_nPatternEditorGridTupletNumerator = 4; + m_nPatternEditorGridTupletDenominator = 4; m_bShowInstrumentPeaks = true; m_bIsFXTabVisible = true; m_bShowAutomationArea = false; @@ -557,9 +558,15 @@ void Preferences::loadPreferences( bool bGlobal ) mixerFalloffSpeed = LocalFileMng::readXmlFloat( guiNode, "mixer_falloff_speed", 1.1f ); // pattern editor grid resolution - m_nPatternEditorGridResolution = LocalFileMng::readXmlInt( guiNode, "patternEditorGridResolution", m_nPatternEditorGridResolution ); - m_bPatternEditorUsingTriplets = LocalFileMng::readXmlBool( guiNode, "patternEditorUsingTriplets", m_bPatternEditorUsingTriplets ); - + m_nPatternEditorGridResolution = LocalFileMng::readXmlInt( guiNode, + "patternEditorGridResolution", m_nPatternEditorGridResolution ); + + // pattern editor grid Tuplet ratio + m_nPatternEditorGridTupletNumerator = LocalFileMng::readXmlInt( guiNode, + "patternEditorGridTupletNumerator", m_nPatternEditorGridTupletNumerator ); + m_nPatternEditorGridTupletDenominator = LocalFileMng::readXmlInt( guiNode, + "patternEditorGridTupletDenominator", m_nPatternEditorGridTupletDenominator ); + m_bShowInstrumentPeaks = LocalFileMng::readXmlBool( guiNode, "showInstrumentPeaks", m_bShowInstrumentPeaks ); m_bIsFXTabVisible = LocalFileMng::readXmlBool( guiNode, "isFXTabVisible", m_bIsFXTabVisible ); m_bShowAutomationArea = LocalFileMng::readXmlBool( guiNode, "showAutomationArea", m_bShowAutomationArea ); @@ -997,10 +1004,14 @@ void Preferences::savePreferences() LocalFileMng::writeXmlString( guiNode, "mixer_font_family", mixerFontFamily ); LocalFileMng::writeXmlString( guiNode, "mixer_font_pointsize", QString("%1").arg( mixerFontPointSize ) ); LocalFileMng::writeXmlString( guiNode, "mixer_falloff_speed", QString("%1").arg( mixerFalloffSpeed ) ); - LocalFileMng::writeXmlString( guiNode, "patternEditorGridResolution", QString("%1").arg( m_nPatternEditorGridResolution ) ); + LocalFileMng::writeXmlString( guiNode, "patternEditorGridResolution", + QString("%1").arg( m_nPatternEditorGridResolution ) ); + LocalFileMng::writeXmlString( guiNode, "patternEditorGridTupletNumerator", + QString("%1").arg( m_nPatternEditorGridTupletNumerator ) ); + LocalFileMng::writeXmlString( guiNode, "patternEditorGridTupletDenominator", + QString("%1").arg( m_nPatternEditorGridTupletDenominator ) ); LocalFileMng::writeXmlString( guiNode, "patternEditorGridHeight", QString("%1").arg( m_nPatternEditorGridHeight ) ); LocalFileMng::writeXmlString( guiNode, "patternEditorGridWidth", QString("%1").arg( m_nPatternEditorGridWidth ) ); - LocalFileMng::writeXmlBool( guiNode, "patternEditorUsingTriplets", m_bPatternEditorUsingTriplets ); LocalFileMng::writeXmlString( guiNode, "songEditorGridHeight", QString("%1").arg( m_nSongEditorGridHeight ) ); LocalFileMng::writeXmlString( guiNode, "songEditorGridWidth", QString("%1").arg( m_nSongEditorGridWidth ) ); LocalFileMng::writeXmlBool( guiNode, "showInstrumentPeaks", m_bShowInstrumentPeaks ); diff --git a/src/core/Preferences.h b/src/core/Preferences.h index 176978b81..605728332 100644 --- a/src/core/Preferences.h +++ b/src/core/Preferences.h @@ -522,8 +522,13 @@ class Preferences : public H2Core::Object int getPatternEditorGridResolution(); void setPatternEditorGridResolution( int value ); - bool isPatternEditorUsingTriplets(); - void setPatternEditorUsingTriplets( bool value ); + int getPatternEditorGridTupletNumerator() const; + //void setPatternEditorGridTupletNumerator( int n ); + int getPatternEditorGridTupletDenominator() const; + //void setPatternEditorGridTupletDenominator( int n ); + + // setter for both Tuplet numerator and denominator together + void setPatternEditorGridTupletRatio( int nTupletNumerator, int nTupletDenominator ); bool isFXTabVisible(); void setFXTabVisible( bool value ); @@ -772,7 +777,8 @@ class Preferences : public H2Core::Object int mixerFontPointSize; float mixerFalloffSpeed; int m_nPatternEditorGridResolution; - bool m_bPatternEditorUsingTriplets; + int m_nPatternEditorGridTupletNumerator; + int m_nPatternEditorGridTupletDenominator; bool m_bShowInstrumentPeaks; bool m_bIsFXTabVisible; bool m_bShowAutomationArea; @@ -1115,11 +1121,26 @@ inline void Preferences::setPatternEditorGridResolution( int value ) { m_nPatternEditorGridResolution = value; } -inline bool Preferences::isPatternEditorUsingTriplets() { - return m_bPatternEditorUsingTriplets; +inline int Preferences::getPatternEditorGridTupletNumerator() const { + return m_nPatternEditorGridTupletNumerator; } -inline void Preferences::setPatternEditorUsingTriplets( bool value ) { - m_bPatternEditorUsingTriplets = value; +/* +inline void Preferences::setPatternEditorGridTupletNumerator( int n ) { + m_nPatternEditorGridTupletNumerator = n; +}*/ + + +inline int Preferences::getPatternEditorGridTupletDenominator() const { + return m_nPatternEditorGridTupletDenominator; +} +/* +inline void Preferences::setPatternEditorGridTupletDenominator( int n ) { + m_nPatternEditorGridTupletDenominator = n; +}*/ + +inline void Preferences::setPatternEditorGridTupletRatio( int nTupletNumerator, int nTupletDenominator ) { + m_nPatternEditorGridTupletNumerator = nTupletNumerator; + m_nPatternEditorGridTupletDenominator = nTupletDenominator; } inline bool Preferences::isFXTabVisible() { diff --git a/src/gui/src/HydrogenApp.cpp b/src/gui/src/HydrogenApp.cpp index df3b2103e..664e80bb7 100644 --- a/src/gui/src/HydrogenApp.cpp +++ b/src/gui/src/HydrogenApp.cpp @@ -660,8 +660,7 @@ void HydrogenApp::onEventQueueTimer() false, pQueue->m_addMidiNoteVector[0].b_isMidi, pQueue->m_addMidiNoteVector[0].b_isInstrumentMode, - false ); - + false ); //TODO 0 4 ?! divbase... HydrogenApp::get_instance()->m_pUndoStack->push( action ); } pQueue->m_addMidiNoteVector.erase(pQueue->m_addMidiNoteVector.begin()); diff --git a/src/gui/src/PatternEditor/DrumPatternEditor.cpp b/src/gui/src/PatternEditor/DrumPatternEditor.cpp index bf5bee59a..e76630108 100644 --- a/src/gui/src/PatternEditor/DrumPatternEditor.cpp +++ b/src/gui/src/PatternEditor/DrumPatternEditor.cpp @@ -86,10 +86,10 @@ void DrumPatternEditor::updateEditor( bool bPatternOnly ) updatePatternInfo(); if ( m_pPattern ) { - m_nEditorWidth = m_nMargin + m_nGridWidth * m_pPattern->get_length(); + m_nEditorWidth = m_nMargin + m_fGridWidth * m_pPattern->get_length(); } else { - m_nEditorWidth = m_nMargin + m_nGridWidth * MAX_NOTES; + m_nEditorWidth = m_nMargin + m_fGridWidth * MAX_NOTES; } resize( m_nEditorWidth, height() ); @@ -98,43 +98,49 @@ void DrumPatternEditor::updateEditor( bool bPatternOnly ) } -void DrumPatternEditor::addOrRemoveNote( int nColumn, int nRealColumn, int row, - bool bDoAdd, bool bDoDelete ) { +void DrumPatternEditor::addOrRemoveNote( int nGridIndex, int nRealColumn, int row, bool bDoAdd, bool bDoDelete ) { +//TODO first arg could be easily double fTickPosition + + /* convert gridIndex into the nearest tick */ + double fTickPosition = nGridIndex * granularity(); // TODO make this a macro? + Song *pSong = Hydrogen::get_instance()->getSong(); Instrument *pSelectedInstrument = pSong->getInstrumentList()->get( row ); - H2Core::Note *pOldNote = m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument ); + // why naming "old"?! old or new depends if the note is already present (delete or add) int oldLength = -1; float oldVelocity = 0.8f; float oldPan_L = 0.5f; float oldPan_R = 0.5f; float oldLeadLag = 0.0f; float fProbability = 1.0f; + Note::Key oldNoteKeyVal = Note::C; Note::Octave oldOctaveKeyVal = Note::P8; bool isNoteOff = false; - if ( pOldNote && !bDoDelete ) { - // Found an old note, but we don't want to delete, so just return. - return; + H2Core::Note *pOldNote = m_pPattern->find_note( fTickPosition, nRealColumn, pSelectedInstrument ); + if ( pOldNote ) { + // Found an old note matching the same position + if ( !bDoDelete ) { // we don't want to delete, so just return. + return; + } else { // note will be deleted, so here "old" has sense + oldLength = pOldNote->get_length(); + oldVelocity = pOldNote->get_velocity(); + oldPan_L = pOldNote->get_pan_l(); + oldPan_R = pOldNote->get_pan_r(); + oldLeadLag = pOldNote->get_lead_lag(); + oldNoteKeyVal = pOldNote->get_key(); + oldOctaveKeyVal = pOldNote->get_octave(); + isNoteOff = pOldNote->get_note_off(); // not "old" here?! ;) + fProbability = pOldNote->get_probability(); + } } else if ( !pOldNote && !bDoAdd ) { // No note there, but we don't want to add a new one, so return. return; } - if ( pOldNote ) { - oldLength = pOldNote->get_length(); - oldVelocity = pOldNote->get_velocity(); - oldPan_L = pOldNote->get_pan_l(); - oldPan_R = pOldNote->get_pan_r(); - oldLeadLag = pOldNote->get_lead_lag(); - oldNoteKeyVal = pOldNote->get_key(); - oldOctaveKeyVal = pOldNote->get_octave(); - isNoteOff = pOldNote->get_note_off(); - fProbability = pOldNote->get_probability(); - } - - SE_addOrDeleteNoteAction *action = new SE_addOrDeleteNoteAction( nColumn, + SE_addOrDeleteNoteAction *action = new SE_addOrDeleteNoteAction( fTickPosition, row, m_nSelectedPatternNumber, oldLength, @@ -169,14 +175,14 @@ void DrumPatternEditor::mouseClickEvent( QMouseEvent *ev ) if (row >= nInstruments) { return; } - int nColumn = getColumn( ev->x(), /* bUseFineGrained=*/ true ); - int nRealColumn = 0; + double fTickPosition = getColumn( ev->x(), /* bUseFineGrained=*/ true ); // position of nearest grid mark in ticks + int nGridIndex = getGridIndex( ev->x() ); // index of nearest grid mark //TODO could be avoided + int nRealColumn = 0; // TODO what is the use of this? does it affect tuplets? currently it is not rounded if( ev->x() > m_nMargin ) { - nRealColumn = ( ev->x() - m_nMargin) / static_cast(m_nGridWidth); + nRealColumn = ( ev->x() - m_nMargin ) / m_fGridWidth; } - - if ( nColumn >= (int)m_pPattern->get_length() ) { + if ( fTickPosition >= m_pPattern->get_length() ) { // here was a (int) cast update( 0, 0, width(), height() ); return; } @@ -186,9 +192,9 @@ void DrumPatternEditor::mouseClickEvent( QMouseEvent *ev ) { //shift + leftClick: add noteOff note HydrogenApp *pApp = HydrogenApp::get_instance(); - Note *pNote = m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false ); + Note *pNote = m_pPattern->find_note( fTickPosition, nRealColumn, pSelectedInstrument, false ); if ( pNote != nullptr ) { - SE_addOrDeleteNoteAction *action = new SE_addOrDeleteNoteAction( nColumn, + SE_addOrDeleteNoteAction *action = new SE_addOrDeleteNoteAction( fTickPosition, row, m_nSelectedPatternNumber, pNote->get_length(), @@ -207,7 +213,7 @@ void DrumPatternEditor::mouseClickEvent( QMouseEvent *ev ) pApp->m_pUndoStack->push( action ); } else { // Add stop-note - SE_addNoteOffAction *action = new SE_addNoteOffAction( nColumn, row, m_nSelectedPatternNumber, + SE_addNoteOffAction *action = new SE_addNoteOffAction( fTickPosition, row, m_nSelectedPatternNumber, pNote != nullptr ); pApp->m_pUndoStack->push( action ); } @@ -215,7 +221,7 @@ void DrumPatternEditor::mouseClickEvent( QMouseEvent *ev ) else if ( ev->button() == Qt::LeftButton ) { pHydrogen->setSelectedInstrumentNumber( row ); - addOrRemoveNote( nColumn, nRealColumn, row ); + addOrRemoveNote( nGridIndex, nRealColumn, row ); //TODO first arg could be easily double fTickPosition m_selection.clearSelection(); } else if ( ev->button() == Qt::RightButton ) { @@ -228,7 +234,7 @@ void DrumPatternEditor::mouseClickEvent( QMouseEvent *ev ) pHydrogen->setSelectedInstrumentNumber( row ); } - m_pPatternEditorPanel->setCursorPosition( nColumn ); + m_pPatternEditorPanel->setCursorIndexPosition( nGridIndex ); HydrogenApp::get_instance()->setHideKeyboardCursor( true ); update(); } @@ -238,20 +244,20 @@ void DrumPatternEditor::mouseDragStartEvent( QMouseEvent *ev ) int row = (int)( ev->y() / (float)m_nGridHeight); Hydrogen *pHydrogen = Hydrogen::get_instance(); Song *pSong = pHydrogen->getSong(); - int nColumn = getColumn( ev->x() ); + double fTickPosition = getColumn( ev->x() ); if ( ev->button() == Qt::RightButton ) { // Right button drag: adjust note length int nRealColumn = 0; Instrument *pSelectedInstrument = pSong->getInstrumentList()->get( row ); if( ev->x() > m_nMargin ) { - nRealColumn = ( ev->x() - m_nMargin) / static_cast(m_nGridWidth); + nRealColumn = ( ev->x() - m_nMargin) / m_fGridWidth; } - m_pDraggedNote = m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false ); + m_pDraggedNote = m_pPattern->find_note( fTickPosition, nRealColumn, pSelectedInstrument, false ); // needed for undo note length __nRealColumn = nRealColumn; - __nColumn = nColumn; + m_fTickPosition = fTickPosition; __row = row; if( m_pDraggedNote ){ __oldLength = m_pDraggedNote->get_length(); @@ -260,13 +266,14 @@ void DrumPatternEditor::mouseDragStartEvent( QMouseEvent *ev ) } } else { // Other drag (selection or move) we'll set the cursor input position to the start of the gesture - pHydrogen->setSelectedInstrumentNumber( row ); - m_pPatternEditorPanel->setCursorPosition( nColumn ); + pHydrogen->setSelectedInstrumentNumber( row ); + int nGridIndex = getGridIndex( ev->x() ); // position in grid marks + m_pPatternEditorPanel->setCursorIndexPosition( nGridIndex ); HydrogenApp::get_instance()->setHideKeyboardCursor( true ); } } -void DrumPatternEditor::addOrDeleteNoteAction( int nColumn, +void DrumPatternEditor::addOrDeleteNoteAction( double fTickPosition, int row, int selectedPatternNumber, int oldLength, @@ -303,9 +310,9 @@ void DrumPatternEditor::addOrDeleteNoteAction( int nColumn, if ( isDelete ) { // Find and delete an existing (matching) note. - Pattern::notes_t *notes = (Pattern::notes_t *)pPattern->get_notes(); + Pattern::notes_t *notes = (Pattern::notes_t *) pPattern->get_notes(); bool bFound = false; - FOREACH_NOTE_IT_BOUND( notes, it, nColumn ) { + FOREACH_NOTE_IT_BOUND( notes, it, fTickPosition ) { Note *pNote = it->second; assert( pNote ); if ( ( isNoteOff && pNote->get_note_off() ) @@ -313,7 +320,9 @@ void DrumPatternEditor::addOrDeleteNoteAction( int nColumn, && pNote->get_key() == oldNoteKeyVal && pNote->get_octave() == oldOctaveKeyVal && pNote->get_velocity() == oldVelocity - && pNote->get_probability() == fProbability ) ) { + && pNote->get_probability() == fProbability + ) + ) { delete pNote; notes->erase( it ); bFound = true; @@ -326,7 +335,6 @@ void DrumPatternEditor::addOrDeleteNoteAction( int nColumn, } else { // create the new note - unsigned nPosition = nColumn; float fVelocity = oldVelocity; float fPan_L = oldPan_L ; float fPan_R = oldPan_R; @@ -344,7 +352,7 @@ void DrumPatternEditor::addOrDeleteNoteAction( int nColumn, float fPitch = 0.f; - Note *pNote = new Note( pSelectedInstrument, nPosition, fVelocity, fPan_L, fPan_R, nLength, fPitch ); + Note *pNote = new Note( pSelectedInstrument, fTickPosition, fVelocity, fPan_L, fPan_R, nLength, fPitch ); pNote->set_note_off( isNoteOff ); if ( !isNoteOff ) { pNote->set_lead_lag( oldLeadLag ); @@ -374,14 +382,15 @@ void DrumPatternEditor::addOrDeleteNoteAction( int nColumn, } -// Find a note that matches pNote, and move it from (nColumn, nRow) to (nNewColumn, nNewRow) -void DrumPatternEditor::moveNoteAction( int nColumn, +// Find a note that matches pNote, and move it from (fColumn, nRow) to (fNewColumn, nNewRow) +void DrumPatternEditor::moveNoteAction( double fColumn, int nRow, int nPattern, - int nNewColumn, + double fNewColumn, int nNewRow, Note *pNote) { + Hydrogen *pHydrogen = Hydrogen::get_instance(); Song *pSong = pHydrogen->getSong(); @@ -400,7 +409,7 @@ void DrumPatternEditor::moveNoteAction( int nColumn, Instrument *pFromInstrument = pInstrumentList->get( nRow ), *pToInstrument = pInstrumentList->get( nNewRow ); - FOREACH_NOTE_IT_BOUND((Pattern::notes_t *)pPattern->get_notes(), it, nColumn) { + FOREACH_NOTE_IT_BOUND((Pattern::notes_t *)pPattern->get_notes(), it, fColumn) { Note *pCandidateNote = it->second; if ( pCandidateNote->get_instrument() == pFromInstrument && pCandidateNote->get_key() == pNote->get_key() @@ -424,9 +433,10 @@ void DrumPatternEditor::moveNoteAction( int nColumn, } pPattern->remove_note( pFoundNote ); + if ( pFromInstrument == pToInstrument ) { // Note can simply be moved. - pFoundNote->set_position( nNewColumn ); + pFoundNote->set_position( fNewColumn ); pPattern->insert_note( pFoundNote ); } else { pPattern->remove_note( pFoundNote ); @@ -436,7 +446,7 @@ void DrumPatternEditor::moveNoteAction( int nColumn, m_selection.removeFromSelection( pFoundNote, /* bCheck=*/false ); m_selection.addToSelection( pNewNote ); } - pNewNote->set_position( nNewColumn ); + pNewNote->set_position( fNewColumn ); m_selection.addToSelection( pNewNote ); pPattern->insert_note( pNewNote ); delete pFoundNote; @@ -477,7 +487,7 @@ void DrumPatternEditor::mouseDragEndEvent( QMouseEvent *ev ) void DrumPatternEditor::selectionMoveEndEvent( QInputEvent *ev ) { updateModifiers( ev ); - QPoint offset = movingGridOffset(); + QPointF offset = movingGridOffset(); if ( offset.x() == 0 && offset.y() == 0 ) { // Move with no effect. return; @@ -507,17 +517,17 @@ void DrumPatternEditor::selectionMoveEndEvent( QInputEvent *ev ) for ( auto pNote : selectedNotes ) { int nInstrument = pInstrumentList->index( pNote->get_instrument() ); - int nPosition = pNote->get_position(); + double fPosition = pNote->get_position(); int nNewInstrument = nInstrument + offset.y(); - int nNewPosition = nPosition + offset.x(); + double fNewPosition = fPosition + offset.x(); if ( nNewInstrument < 0 || nNewInstrument >= pInstrumentList->size() - || nNewPosition < 0 || nNewPosition >= m_pPattern->get_length() ) { + || fNewPosition < 0 || fNewPosition >= m_pPattern->get_length() ) { if ( m_bCopyNotMove ) { // Copying a note to an out-of-range location. Nothing to do. } else { // Note is moved out of range. Delete it. - pUndo->push( new SE_addOrDeleteNoteAction( nPosition, + pUndo->push( new SE_addOrDeleteNoteAction( fPosition, nInstrument, m_nSelectedPatternNumber, pNote->get_length(), @@ -538,7 +548,7 @@ void DrumPatternEditor::selectionMoveEndEvent( QInputEvent *ev ) } else { if ( m_bCopyNotMove ) { // Copy note to a new note. - pUndo->push( new SE_addOrDeleteNoteAction( nNewPosition, + pUndo->push( new SE_addOrDeleteNoteAction( fNewPosition, nNewInstrument, m_nSelectedPatternNumber, pNote->get_length(), @@ -556,8 +566,8 @@ void DrumPatternEditor::selectionMoveEndEvent( QInputEvent *ev ) false ) ); } else { // Move note - pUndo->push( new SE_moveNoteAction( nPosition, nInstrument, m_nSelectedPatternNumber, - nNewPosition, nNewInstrument, pNote ) ); + pUndo->push( new SE_moveNoteAction( fPosition, nInstrument, m_nSelectedPatternNumber, + fNewPosition, nNewInstrument, pNote ) ); } } } @@ -566,7 +576,7 @@ void DrumPatternEditor::selectionMoveEndEvent( QInputEvent *ev ) } -void DrumPatternEditor::editNoteLengthAction( int nColumn, int nRealColumn, int row, int length, int selectedPatternNumber ) +void DrumPatternEditor::editNoteLengthAction( double fColumn, int nRealColumn, int row, double length, int selectedPatternNumber ) { Hydrogen *pHydrogen = Hydrogen::get_instance(); PatternList *pPatternList = pHydrogen->getSong()->getPatternList(); @@ -583,7 +593,7 @@ void DrumPatternEditor::editNoteLengthAction( int nColumn, int nRealColumn, int m_pAudioEngine->lock( RIGHT_HERE ); - pDraggedNote = pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false ); + pDraggedNote = pPattern->find_note( fColumn, nRealColumn, pSelectedInstrument, false ); if( pDraggedNote ){ pDraggedNote->set_length( length ); } @@ -668,7 +678,7 @@ void DrumPatternEditor::keyPressEvent( QKeyEvent *ev ) } else if ( ev->matches( QKeySequence::MoveToEndOfLine ) || ev->matches( QKeySequence::SelectEndOfLine ) ) { // -->| - m_pPatternEditorPanel->setCursorPosition( m_pPattern->get_length() ); + m_pPatternEditorPanel->setCursorIndexPosition( (int) m_pPattern->get_length() / granularity() ); } else if ( ev->matches( QKeySequence::MoveToPreviousChar ) || ev->matches( QKeySequence::SelectPreviousChar ) ) { // <- @@ -680,7 +690,7 @@ void DrumPatternEditor::keyPressEvent( QKeyEvent *ev ) } else if ( ev->matches( QKeySequence::MoveToStartOfLine ) || ev->matches( QKeySequence::SelectStartOfLine ) ) { // |<-- - m_pPatternEditorPanel->setCursorPosition( 0 ); + m_pPatternEditorPanel->setCursorIndexPosition( 0 ); } else if ( ev->matches( QKeySequence::MoveToNextLine ) || ev->matches( QKeySequence::SelectNextLine ) ) { if ( nSelectedInstrument + 1 < nMaxInstrument ) { @@ -726,7 +736,7 @@ void DrumPatternEditor::keyPressEvent( QKeyEvent *ev ) } else if ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) { // Key: Enter / Return: add or remove note at current position m_selection.clearSelection(); - addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1, nSelectedInstrument ); + addOrRemoveNote( m_pPatternEditorPanel->getCursorIndexPosition(), -1, nSelectedInstrument ); } else if ( ev->key() == Qt::Key_Delete ) { // Key: Delete / Backspace: delete selected notes, or note under keyboard cursor @@ -736,7 +746,7 @@ void DrumPatternEditor::keyPressEvent( QKeyEvent *ev ) deleteSelection(); } else { // Delete note under the keyboard cursor. - addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1, nSelectedInstrument, + addOrRemoveNote( m_pPatternEditorPanel->getCursorIndexPosition(), -1, nSelectedInstrument, /*bDoAdd=*/false, /*bDoDelete=*/true); } @@ -804,8 +814,8 @@ std::vector DrumPatternEditor::elementsInters // Calculate the first and last position values that this rect will intersect with - int x_min = (r.left() - m_nMargin - 1) / m_nGridWidth; - int x_max = (r.right() - m_nMargin) / m_nGridWidth; + int x_min = (r.left() - m_nMargin - 1) / m_fGridWidth; + int x_max = (r.right() - m_nMargin) / m_fGridWidth; const Pattern::notes_t* notes = m_pPattern->get_notes(); std::vector result; @@ -813,7 +823,7 @@ std::vector DrumPatternEditor::elementsInters for (auto it = notes->lower_bound( x_min ); it != notes->end() && it->first <= x_max; ++it ) { Note *note = it->second; int nInstrument = pInstrList->index( note->get_instrument() ); - uint x_pos = m_nMargin + (it->first * m_nGridWidth); + uint x_pos = m_nMargin + (it->first * m_fGridWidth); uint y_pos = ( nInstrument * m_nGridHeight) + (m_nGridHeight / 2) - 3; if ( r.contains( QPoint( x_pos, y_pos + h/2) ) ) { @@ -829,12 +839,10 @@ std::vector DrumPatternEditor::elementsInters /// QRect DrumPatternEditor::getKeyboardCursorRect() { - - uint x = m_nMargin + m_pPatternEditorPanel->getCursorPosition() * m_nGridWidth; + uint x = round( m_nMargin + m_pPatternEditorPanel->getCursorIndexPosition()* granularity() * m_fGridWidth ); // TODO check int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); uint y = nSelectedInstrument * m_nGridHeight; - return QRect( x-m_nGridWidth*3, y+2, m_nGridWidth*6, m_nGridHeight-3 ); - + return QRect( x-m_fGridWidth*3, y+2, m_fGridWidth*6, m_nGridHeight-3 ); } void DrumPatternEditor::selectAll() @@ -901,7 +909,8 @@ void DrumPatternEditor::paste() QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack; InstrumentList *pInstrList = Hydrogen::get_instance()->getSong()->getInstrumentList(); XMLNode noteList; - int nDeltaPos = 0, nDeltaInstrument = 0; + double fDeltaPos = 0.; + int nDeltaInstrument = 0; XMLDoc doc; if ( ! doc.setContent( clipboard->text() ) ) { @@ -926,10 +935,10 @@ void DrumPatternEditor::paste() // it to adjust the location relative to the current keyboard // input cursor. if ( !positionNode.isNull() ) { - int nCurrentPos = m_pPatternEditorPanel->getCursorPosition(); + double fCurrentPos = m_pPatternEditorPanel->getCursorIndexPosition() * granularity(); int nCurrentInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); - nDeltaPos = nCurrentPos - positionNode.read_int( "position", nCurrentPos ); + fDeltaPos = fCurrentPos - positionNode.read_double( "position", fCurrentPos ); nDeltaInstrument = nCurrentInstrument - positionNode.read_int( "instrument", nCurrentInstrument ); } @@ -970,12 +979,12 @@ void DrumPatternEditor::paste() pUndo->beginMacro( "paste notes" ); for ( XMLNode n = noteList.firstChildElement( "note" ); ! n.isNull(); n = n.nextSiblingElement() ) { Note *pNote = Note::load_from( &n, pInstrList ); - int nPos = pNote->get_position() + nDeltaPos; + double fPos = pNote->get_position() + fDeltaPos; int nInstrument = pInstrList->index( pNote->get_instrument() ) + nDeltaInstrument; - if ( nPos >= 0 && nPos < m_pPattern->get_length() + if ( fPos >= 0 && fPos < m_pPattern->get_length() && nInstrument >= 0 && nInstrument < pInstrList->size() ) { - pUndo->push( new SE_addOrDeleteNoteAction( nPos, + pUndo->push( new SE_addOrDeleteNoteAction( fPos, nInstrument, m_nSelectedPatternNumber, pNote->get_length(), @@ -1032,7 +1041,7 @@ void DrumPatternEditor::__draw_pattern(QPainter& painter) for ( uint nInstr = 0; nInstr < pInstrList->size(); ++nInstr ) { uint y = m_nGridHeight * nInstr; if ( nInstr == (uint)nSelectedInstrument ) { // selected instrument - painter.fillRect( 0, y + 1, ( m_nMargin + nNotes * m_nGridWidth ), m_nGridHeight - 1, selectedRowColor ); + painter.fillRect( 0, y + 1, ( m_nMargin + nNotes * m_fGridWidth ), m_nGridHeight - 1, selectedRowColor ); } } @@ -1043,14 +1052,14 @@ void DrumPatternEditor::__draw_pattern(QPainter& painter) // Draw cursor if ( hasFocus() && !HydrogenApp::get_instance()->hideKeyboardCursor() ) { - uint x = m_nMargin + m_pPatternEditorPanel->getCursorPosition() * m_nGridWidth; + uint x = round( m_nMargin + m_pPatternEditorPanel->getCursorIndexPosition()* granularity() * m_fGridWidth ); int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); uint y = nSelectedInstrument * m_nGridHeight; QPen p( Qt::black ); p.setWidth( 2 ); painter.setPen( p ); painter.setRenderHint( QPainter::Antialiasing ); - painter.drawRoundedRect( QRect( x-m_nGridWidth*3, y+2, m_nGridWidth*6, m_nGridHeight-3 ), 4, 4 ); + painter.drawRoundedRect( QRect( x-m_fGridWidth*3, y+2, m_fGridWidth*6, m_nGridHeight-3 ), 4, 4 ); } @@ -1078,11 +1087,11 @@ void DrumPatternEditor::__draw_pattern(QPainter& painter) // markers for instruments which have more than one note in the same position (a chord or genuine // duplicates) for ( auto posIt = pNotes->begin(); posIt != pNotes->end(); ) { - int nPosition = posIt->second->get_position(); + double fPosition = posIt->second->get_position(); // Process all notes at this position auto noteIt = posIt; - while ( noteIt != pNotes->end() && noteIt->second->get_position() == nPosition ) { + while ( noteIt != pNotes->end() && noteIt->second->get_position() == fPosition ) { Note *pNote = noteIt->second; int nInstrumentID = pNote->get_instrument_id(); @@ -1106,7 +1115,7 @@ void DrumPatternEditor::__draw_pattern(QPainter& painter) if ( noteCount[ nInstrumentID ] > 1 ) { // Draw "2x" text to the left of the note int nInstrument = pInstrList->index( pInstrument ); - int x = m_nMargin + (nPosition * m_nGridWidth); + int x = m_nMargin + round( fPosition * m_fGridWidth ); int y = ( nInstrument * m_nGridHeight); const int boxWidth = 128; QFont font; @@ -1140,8 +1149,7 @@ void DrumPatternEditor::__draw_note( Note *note, QPainter& p ) ERRORLOG( "Instrument not found..skipping note" ); return; } - - QPoint pos ( m_nMargin + note->get_position() * m_nGridWidth, + QPoint pos ( m_nMargin + round( note->get_position() * m_fGridWidth ), ( nInstrument * m_nGridHeight) + (m_nGridHeight / 2) - 3 ); drawNoteSymbol( p, pos, note ); @@ -1170,13 +1178,12 @@ void DrumPatternEditor::__draw_grid( QPainter& p ) for ( uint i = 0; i < (uint)nInstruments; i++ ) { uint y = m_nGridHeight * i + 1; if ( i == (uint)nSelectedInstrument ) { - p.fillRect( 0, y, (m_nMargin + nNotes * m_nGridWidth), (int)( m_nGridHeight * 0.7 ), selectedRowColor ); + p.fillRect( 0, y, (m_nMargin + nNotes * m_fGridWidth), (int)( m_nGridHeight * 0.7 ), selectedRowColor ); } else { - p.fillRect( 0, y, (m_nMargin + nNotes * m_nGridWidth), (int)( m_nGridHeight * 0.7 ), backgroundColor ); + p.fillRect( 0, y, (m_nMargin + nNotes * m_fGridWidth), (int)( m_nGridHeight * 0.7 ), backgroundColor ); } } - } @@ -1201,11 +1208,11 @@ void DrumPatternEditor::__create_background( QPainter& p) resize( width(), m_nEditorHeight ); } - p.fillRect(0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor); + p.fillRect(0, 0, m_nMargin + nNotes * m_fGridWidth, height(), backgroundColor); for ( uint i = 0; i < (uint)nInstruments; i++ ) { uint y = m_nGridHeight * i; if ( ( i % 2) != 0) { - p.fillRect( 0, y, (m_nMargin + nNotes * m_nGridWidth), m_nGridHeight, alternateRowColor ); + p.fillRect( 0, y, (m_nMargin + nNotes * m_fGridWidth), m_nGridHeight, alternateRowColor ); } } @@ -1213,10 +1220,10 @@ void DrumPatternEditor::__create_background( QPainter& p) p.setPen( lineColor ); for ( uint i = 0; i < (uint)nInstruments; i++ ) { uint y = m_nGridHeight * i + m_nGridHeight; - p.drawLine( 0, y, (m_nMargin + nNotes * m_nGridWidth), y); + p.drawLine( 0, y, (m_nMargin + nNotes * m_fGridWidth), y); } - p.drawLine( 0, m_nEditorHeight, (m_nMargin + nNotes * m_nGridWidth), m_nEditorHeight ); + p.drawLine( 0, m_nEditorHeight, (m_nMargin + nNotes * m_fGridWidth), m_nEditorHeight ); } @@ -1287,7 +1294,7 @@ void DrumPatternEditor::selectedPatternChangedEvent() ///NotePropertiesRuler undo redo action -void DrumPatternEditor::undoRedoAction( int column, +void DrumPatternEditor::undoRedoAction( double column, QString mode, int nSelectedPatternNumber, int nSelectedInstrument, @@ -1313,7 +1320,7 @@ void DrumPatternEditor::undoRedoAction( int column, FOREACH_NOTE_CST_IT_BOUND(notes,it,column) { Note *pNote = it->second; assert( pNote ); - assert( (int)pNote->get_position() == column ); + //assert( (int)pNote->get_position() == column ); // TODO tolerance. is this redundant? if ( pNote->get_instrument() != pSong->getInstrumentList()->get( nSelectedInstrument ) ) { continue; } @@ -1502,7 +1509,9 @@ void DrumPatternEditor::functionPasteNotesRedoAction(std::list -void DrumPatternEditor::functionFillNotesUndoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ) +//void DrumPatternEditor::functionFillNotesUndoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ) +void DrumPatternEditor::functionFillNotesUndoAction( std::vector notePositions, + int nSelectedInstrument, int patternNumber ) { Hydrogen * H = Hydrogen::get_instance(); PatternList *pPatternList = Hydrogen::get_instance()->getSong()->getPatternList(); @@ -1511,10 +1520,9 @@ void DrumPatternEditor::functionFillNotesUndoAction( QStringList noteList, int n m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine - for (int i = 0; i < noteList.size(); i++ ) { - int nColumn = noteList.value(i).toInt(); + for ( int i = 0; i < notePositions.size(); i++ ) { Pattern::notes_t* notes = (Pattern::notes_t*)pPattern->get_notes(); - FOREACH_NOTE_IT_BOUND(notes,it,nColumn) { + FOREACH_NOTE_IT_BOUND( notes, it, notePositions[i] ) { Note *pNote = it->second; assert( pNote ); if ( pNote->get_instrument() == pSelectedInstrument ) { @@ -1532,7 +1540,10 @@ void DrumPatternEditor::functionFillNotesUndoAction( QStringList noteList, int n } -void DrumPatternEditor::functionFillNotesRedoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ) +//void DrumPatternEditor::functionFillNotesRedoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ) + +void DrumPatternEditor::functionFillNotesRedoAction( std::vector notePositions, + int nSelectedInstrument, int patternNumber ) { Hydrogen * H = Hydrogen::get_instance(); PatternList *pPatternList = Hydrogen::get_instance()->getSong()->getPatternList(); @@ -1546,11 +1557,10 @@ void DrumPatternEditor::functionFillNotesRedoAction( QStringList noteList, int n const int nLength = -1; m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine - for (int i = 0; i < noteList.size(); i++ ) { + for (int i = 0; i < notePositions.size(); i++ ) { // create the new note - int position = noteList.value(i).toInt(); - Note *pNote = new Note( pSelectedInstrument, position, velocity, pan_L, pan_R, nLength, fPitch ); + Note *pNote = new Note( pSelectedInstrument, notePositions[i], velocity, pan_L, pan_R, nLength, fPitch ); pPattern->insert_note( pNote ); } m_pAudioEngine->unlock(); // unlock the audio engine @@ -1561,7 +1571,7 @@ void DrumPatternEditor::functionFillNotesRedoAction( QStringList noteList, int n void DrumPatternEditor::functionRandomVelocityAction( QStringList noteVeloValue, int nSelectedInstrument, int selectedPatternNumber ) -{ +{ // TODO why QStringList and not std::vector ? Does it save space? Hydrogen * H = Hydrogen::get_instance(); PatternList *pPatternList = Hydrogen::get_instance()->getSong()->getPatternList(); Pattern *pPattern = pPatternList->get( selectedPatternNumber ); @@ -1570,18 +1580,19 @@ void DrumPatternEditor::functionRandomVelocityAction( QStringList noteVeloValue, m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine - int nResolution = granularity(); + double fResolution = granularity(); int positionCount = 0; - for (int i = 0; i < pPattern->get_length(); i += nResolution) { + for (int i = 0; i*fResolution < pPattern->get_length(); i++ ) { //TODO while() loop using variable float fPosition const Pattern::notes_t* notes = pPattern->get_notes(); - FOREACH_NOTE_CST_IT_BOUND(notes,it,i) { + FOREACH_NOTE_CST_IT_BOUND(notes,it,i*fResolution) { Note *pNote = it->second; if ( pNote->get_instrument() == pSelectedInstrument) { - float velocity = noteVeloValue.value( positionCount ).toFloat(); + float velocity = noteVeloValue.value( positionCount ).toFloat(); // TODO why QStringList and not std::vector ? pNote->set_velocity(velocity); positionCount++; } } + } H->getSong()->setIsModified( true ); m_pAudioEngine->unlock(); // unlock the audio engine diff --git a/src/gui/src/PatternEditor/DrumPatternEditor.h b/src/gui/src/PatternEditor/DrumPatternEditor.h index 48a796796..68330085e 100644 --- a/src/gui/src/PatternEditor/DrumPatternEditor.h +++ b/src/gui/src/PatternEditor/DrumPatternEditor.h @@ -33,6 +33,7 @@ #include #include +#include class PatternEditorInstrumentList; @@ -54,7 +55,7 @@ class DrumPatternEditor : public PatternEditor virtual void selectedPatternChangedEvent() override; virtual void selectedInstrumentChangedEvent() override; //~ Implements EventListener interface - void addOrDeleteNoteAction( int nColumn, + void addOrDeleteNoteAction( double fTickPosition, int row, int selectedPatternNumber, int oldLength, @@ -70,16 +71,16 @@ class DrumPatternEditor : public PatternEditor bool isInstrumentMode, bool isNoteOff, bool isDelete ); - void moveNoteAction( int nColumn, + void moveNoteAction( double fColumn, int nRow, int nPattern, - int nNewColumn, + double fNewColumn, int nNewRow, H2Core::Note *note); - void addOrRemoveNote( int nColumn, int nRealColumn, int row, bool bDoAdd = true, bool bDoDelete = true ); - void editNoteLengthAction( int nColumn, int nRealColumn, int row, int length, int selectedPatternNumber ); - void undoRedoAction( int column, + void addOrRemoveNote( int nGridIndex, int nRealColumn, int row, bool bDoAdd = true, bool bDoDelete = true ); //TODO first arg could be easily double fTickPosition + void editNoteLengthAction( double fColumn, int nRealColumn, int row, double length, int selectedPatternNumber ); + void undoRedoAction( double column, QString mode, int nSelectedPatternNumber, int nSelectedInstrument, @@ -92,8 +93,10 @@ class DrumPatternEditor : public PatternEditor int octaveKeyVal ); void functionClearNotesRedoAction( int nSelectedInstrument, int selectedPatternNumber ); void functionClearNotesUndoAction( std::list< H2Core::Note* > noteList, int nSelectedInstrument, int patternNumber ); - void functionFillNotesUndoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ); - void functionFillNotesRedoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ); + //void functionFillNotesUndoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ); + void functionFillNotesUndoAction( std::vector notePositions, int nSelectedInstrument, int patternNumber ); + //void functionFillNotesRedoAction( QStringList noteList, int nSelectedInstrument, int patternNumber ); + void functionFillNotesRedoAction( std::vector notePositions, int nSelectedInstrument, int patternNumber ); void functionRandomVelocityAction( QStringList noteVeloValue, int nSelectedInstrument, int selectedPatternNumber ); void functionMoveInstrumentAction( int nSourceInstrument, int nTargetInstrument ); void functionDropInstrumentUndoAction( int nTargetInstrument, std::vector* AddedComponents ); @@ -148,9 +151,9 @@ class DrumPatternEditor : public PatternEditor QString renameCompo( QString OriginalName ); int __nRealColumn; - int __nColumn; + double m_fTickPosition; int __row; - int __oldLength; + double __oldLength; }; diff --git a/src/gui/src/PatternEditor/NotePropertiesRuler.cpp b/src/gui/src/PatternEditor/NotePropertiesRuler.cpp index 13fe00d7a..ff573f70c 100644 --- a/src/gui/src/PatternEditor/NotePropertiesRuler.cpp +++ b/src/gui/src/PatternEditor/NotePropertiesRuler.cpp @@ -49,8 +49,8 @@ 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_fGridWidth = (Preferences::get_instance())->getPatternEditorGridWidth(); + m_nEditorWidth = m_nMargin + m_fGridWidth * ( MAX_NOTES * 4 ); m_fLastSetValue = 0.0; m_bValueHasBeenSet = false; @@ -126,9 +126,10 @@ void NotePropertiesRuler::wheelEvent(QWheelEvent *ev ) fDelta = fDelta * -1.0; } - int nColumn = getColumn( ev->x() ); + double m_fTickPosition = getColumn( ev->x() ); + int nGridIndex = getGridIndex( ev->x() ); //unused - m_pPatternEditorPanel->setCursorPosition( nColumn ); + m_pPatternEditorPanel->setCursorIndexPosition( nGridIndex ); HydrogenApp::get_instance()->setHideKeyboardCursor( true ); Song *pSong = pHydrogen->getSong(); @@ -141,7 +142,7 @@ void NotePropertiesRuler::wheelEvent(QWheelEvent *ev ) notes.push_back( pNote ); } } else { - FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, nColumn ) { + FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, m_fTickPosition ) { notes.push_back( it->second ); } } @@ -267,9 +268,9 @@ void NotePropertiesRuler::selectionMoveCancelEvent() { void NotePropertiesRuler::mouseMoveEvent( QMouseEvent *ev ) { if ( ev->buttons() == Qt::NoButton ) { - int nColumn = getColumn( ev->x() ); + double fTickPosition = getColumn( ev->x() ); bool bFound = false; - FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, nColumn ) { + FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, fTickPosition ) { bFound = true; break; } @@ -317,8 +318,8 @@ void NotePropertiesRuler::prepareUndoAction( int x ) } else { // No notes are selected. The target notes to adjust are all those at column given by 'x', so we preserve these. - int nColumn = getColumn( x ); - FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, nColumn ) { + double m_fTickPosition = getColumn( x ); + FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, m_fTickPosition ) { Note *pNote = it->second; if ( pNote->get_instrument() == pSelectedInstrument ) { m_oldNotes[ pNote ] = new Note( pNote ); @@ -336,12 +337,13 @@ void NotePropertiesRuler::propertyDragUpdate( QMouseEvent *ev ) return; } - int nColumn = getColumn( ev->x() ); + double m_fTickPosition = getColumn( ev->x() ); + int nGridIndex = getGridIndex( ev->x() ); - m_pPatternEditorPanel->setCursorPosition( nColumn ); + m_pPatternEditorPanel->setCursorIndexPosition( nGridIndex ); HydrogenApp::get_instance()->setHideKeyboardCursor( true ); - if ( m_nDragPreviousColumn != nColumn ) { + if ( m_fDragPreviousColumn != m_fTickPosition ) { // Complete current undo action, and start a new one. addUndoAction(); prepareUndoAction( ev->x() ); @@ -361,7 +363,7 @@ void NotePropertiesRuler::propertyDragUpdate( QMouseEvent *ev ) Song *pSong = pHydrogen->getSong(); Instrument *pSelectedInstrument = pSong->getInstrumentList()->get( nSelectedInstrument ); - FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, nColumn ) { + FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, m_fTickPosition ) { Note *pNote = it->second; if ( pNote->get_instrument() != pSelectedInstrument && !m_selection.isSelected( pNote ) ) { @@ -443,7 +445,7 @@ void NotePropertiesRuler::propertyDragUpdate( QMouseEvent *ev ) } } - m_nDragPreviousColumn = nColumn; + m_fDragPreviousColumn = m_fTickPosition; Hydrogen::get_instance()->getSong()->setIsModified( true ); updateEditor(); @@ -564,7 +566,7 @@ void NotePropertiesRuler::keyPressEvent( QKeyEvent *ev ) } else if ( ev->matches( QKeySequence::MoveToEndOfLine ) || ev->matches( QKeySequence::SelectEndOfLine ) ) { // -->| - m_pPatternEditorPanel->setCursorPosition( m_pPattern->get_length() ); + m_pPatternEditorPanel->setCursorIndexPosition( m_pPattern->get_length() / granularity() ); } else if ( ev->matches( QKeySequence::MoveToPreviousChar ) || ev->matches( QKeySequence::SelectPreviousChar ) ) { // <- @@ -576,7 +578,7 @@ void NotePropertiesRuler::keyPressEvent( QKeyEvent *ev ) } else if ( ev->matches( QKeySequence::MoveToStartOfLine ) || ev->matches( QKeySequence::SelectStartOfLine ) ) { // |<-- - m_pPatternEditorPanel->setCursorPosition(0); + m_pPatternEditorPanel->setCursorIndexPosition(0); } else { // Value adjustments @@ -626,7 +628,7 @@ void NotePropertiesRuler::keyPressEvent( QKeyEvent *ev ) } if ( fDelta != 0.0 || bRepeatLastValue ) { - int column = m_pPatternEditorPanel->getCursorPosition(); + double fColumn = m_pPatternEditorPanel->getCursorIndexPosition() * granularity(); int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); Song *pSong = (Hydrogen::get_instance())->getSong(); int nNotes = 0; @@ -639,10 +641,10 @@ void NotePropertiesRuler::keyPressEvent( QKeyEvent *ev ) notes.push_back( pNote ); } } else { - FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, column ) { + FOREACH_NOTE_CST_IT_BOUND( m_pPattern->get_notes(), it, fColumn ) { Note *pNote = it->second; assert( pNote ); - assert( pNote->get_position() == column ); + //assert( pNote->get_position() == column ); nNotes++; notes.push_back( pNote ); } @@ -657,7 +659,7 @@ void NotePropertiesRuler::keyPressEvent( QKeyEvent *ev ) } } - prepareUndoAction( m_nMargin + column * m_nGridWidth ); + prepareUndoAction( m_nMargin + fColumn * m_fGridWidth ); for ( Note *pNote : notes ) { @@ -831,14 +833,14 @@ void NotePropertiesRuler::createVelocityBackground(QPixmap *pixmap) QPainter p( pixmap ); - p.fillRect( 0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor ); + p.fillRect( 0, 0, m_nMargin + nNotes * m_fGridWidth, height(), backgroundColor ); drawGridLines( p, Qt::DotLine ); // Horizontal lines at 10% intervals p.setPen( horizLinesColor ); for (unsigned y = 0; y < m_nEditorHeight; y = y + (m_nEditorHeight / 10)) { - p.drawLine( m_nMargin, y, 20 + nNotes * m_nGridWidth, y ); + p.drawLine( m_nMargin, y, 20 + nNotes * m_fGridWidth, y ); } // draw velocity lines @@ -853,16 +855,16 @@ void NotePropertiesRuler::createVelocityBackground(QPixmap *pixmap) FOREACH_NOTE_CST_IT_BEGIN_END(notes,it) { Note *pposNote = it->second; assert( pposNote ); - uint pos = pposNote->get_position(); + double fPos = pposNote->get_position(); int xoffset = 0; - FOREACH_NOTE_CST_IT_BOUND(notes,coit,pos) { + FOREACH_NOTE_CST_IT_BOUND(notes,coit, fPos) { Note *pNote = coit->second; assert( pNote ); if ( pNote->get_instrument() != pSong->getInstrumentList()->get( nSelectedInstrument ) && !m_selection.isSelected( pNote ) ) { continue; } - uint x_pos = m_nMargin + pos * m_nGridWidth; + uint x_pos = m_nMargin + round( fPos * m_fGridWidth ); uint line_end = height(); @@ -918,7 +920,7 @@ void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) if (m_pPattern) { nNotes = m_pPattern->get_length(); } - p.fillRect( 0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor ); + p.fillRect( 0, 0, m_nMargin + nNotes * m_fGridWidth, height(), backgroundColor ); // central line p.setPen( horizLinesColor ); @@ -937,9 +939,9 @@ void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) FOREACH_NOTE_CST_IT_BEGIN_END(notes,it) { Note *pposNote = it->second; assert( pposNote ); - uint pos = pposNote->get_position(); + double fPos = pposNote->get_position(); int xoffset = 0; - FOREACH_NOTE_CST_IT_BOUND(notes,coit,pos) { + FOREACH_NOTE_CST_IT_BOUND(notes,coit,fPos) { Note *pNote = coit->second; assert( pNote ); if ( pNote->get_note_off() || (pNote->get_instrument() @@ -947,7 +949,8 @@ void NotePropertiesRuler::createPanBackground(QPixmap *pixmap) && !m_selection.isSelected( pNote ) ) ) { continue; } - uint x_pos = m_nMargin + pNote->get_position() * m_nGridWidth; + uint x_pos = round( m_nMargin + ( fPos * m_fGridWidth ) ); + QColor centerColor = DrumPatternEditor::computeNoteColor( pNote->get_velocity() ); p.setPen( Qt::NoPen ); @@ -1005,7 +1008,7 @@ void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) if (m_pPattern) { nNotes = m_pPattern->get_length(); } - p.fillRect( 0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor ); + p.fillRect( 0, 0, m_nMargin + nNotes * m_fGridWidth, height(), backgroundColor ); // central line p.setPen( horizLinesColor ); @@ -1024,9 +1027,9 @@ void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) FOREACH_NOTE_CST_IT_BEGIN_END(notes,it) { Note *pposNote = it->second; assert( pposNote ); - uint pos = pposNote->get_position(); + double fPos = pposNote->get_position(); int xoffset = 0; - FOREACH_NOTE_CST_IT_BOUND(notes,coit,pos) { + FOREACH_NOTE_CST_IT_BOUND(notes,coit,fPos) { Note *pNote = coit->second; assert( pNote ); if ( pNote->get_instrument() != pSong->getInstrumentList()->get( nSelectedInstrument ) @@ -1034,7 +1037,7 @@ void NotePropertiesRuler::createLeadLagBackground(QPixmap *pixmap) continue; } - uint x_pos = m_nMargin + pNote->get_position() * m_nGridWidth; + uint x_pos = round( m_nMargin + ( fPos * m_fGridWidth ) ); int red1 = (int) (pNote->get_velocity() * 255); int green1; @@ -1114,13 +1117,13 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) } QPainter p( pixmap ); - p.fillRect( 0, 0, m_nMargin + nNotes * m_nGridWidth, height(), backgroundColor ); + p.fillRect( 0, 0, m_nMargin + nNotes * m_fGridWidth, height(), backgroundColor ); p.setPen( horizLinesColor ); for (unsigned y = 10; y < 80; y = y + 10 ) { p.setPen( QPen( res_1, 1, Qt::DashLine ) ); if (y == 40) p.setPen( QPen( QColor(0,0,0), 1, Qt::SolidLine ) ); - p.drawLine( m_nMargin, y, m_nMargin + nNotes * m_nGridWidth, y ); + p.drawLine( m_nMargin, y, m_nMargin + nNotes * m_fGridWidth, y ); } for (unsigned y = 90; y < 210; y = y + 10 ) { @@ -1128,7 +1131,7 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) if ( y == 100 ||y == 120 ||y == 140 ||y == 170 ||y == 190) { p.setPen( QPen( QColor( 128, 128, 128 ), 9, Qt::SolidLine, Qt::FlatCap ) ); } - p.drawLine( m_nMargin, y, m_nMargin + nNotes * m_nGridWidth, y ); + p.drawLine( m_nMargin, y, m_nMargin + nNotes * m_fGridWidth, y ); } // Annotate with note class names @@ -1153,7 +1156,7 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) // Black outline each key for (unsigned y = 90; y <= 210; y = y + 10 ) { p.setPen( QPen( QColor( 0, 0, 0 ), 1, Qt::SolidLine)); - p.drawLine( m_nMargin, y-5, m_nMargin + nNotes * m_nGridWidth, y-5); + p.drawLine( m_nMargin, y-5, m_nMargin + nNotes * m_fGridWidth, y-5); } //paint the octave @@ -1172,7 +1175,8 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) continue; } if ( !pNote->get_note_off() ) { - uint x_pos = 17 + pNote->get_position() * m_nGridWidth; + uint x_pos = round( pNote->get_position() * m_fGridWidth ); + x_pos += 17; uint y_pos = (4-pNote->get_octave())*10-3; p.setBrush(QColor( 99, 160, 233 )); p.drawEllipse( x_pos, y_pos, 6, 6); @@ -1199,7 +1203,8 @@ void NotePropertiesRuler::createNoteKeyBackground(QPixmap *pixmap) if ( !pNote->get_note_off() ) { int d = 8; int k = pNote->get_key(); - uint x_pos = 16 + pNote->get_position() * m_nGridWidth; + uint x_pos = round ( ( pNote->get_position() ) * m_fGridWidth ); + x_pos += 16; uint y_pos = 200-(k*10)-4; x_pos -= 1; @@ -1240,10 +1245,10 @@ void NotePropertiesRuler::updateEditor( bool bPatternOnly ) // update editor width if ( m_pPattern ) { - m_nEditorWidth = m_nMargin + m_pPattern->get_length() * m_nGridWidth; + m_nEditorWidth = m_nMargin + m_pPattern->get_length() * m_fGridWidth; } else { - m_nEditorWidth = m_nMargin + MAX_NOTES * m_nGridWidth; + m_nEditorWidth = m_nMargin + MAX_NOTES * m_fGridWidth; } if ( !m_bNeedsUpdate ) { @@ -1276,13 +1281,13 @@ void NotePropertiesRuler::finishUpdateEditor() if ( hasFocus() && ! HydrogenApp::get_instance()->hideKeyboardCursor() ) { QPainter p( m_pBackground ); - uint x = m_nMargin + m_pPatternEditorPanel->getCursorPosition() * m_nGridWidth; + uint x = round( m_nMargin + m_pPatternEditorPanel->getCursorIndexPosition()* granularity() * m_fGridWidth ); // TODO check QPen pen( Qt::black ); pen.setWidth( 2 ); p.setPen( pen ); p.setRenderHint( QPainter::Antialiasing ); - p.drawRoundedRect( QRect( x-m_nGridWidth*3, 0+1, m_nGridWidth*6, height()-2 ), 4, 4 ); + p.drawRoundedRect( QRect( x-m_fGridWidth*3, 0+1, m_fGridWidth*6, height()-2 ), 4, 4 ); } // redraw all @@ -1327,7 +1332,7 @@ std::vector NotePropertiesRuler::elementsIn } int pos = it->first; - uint x_pos = m_nMargin + pos * m_nGridWidth; + uint x_pos = m_nMargin + pos * m_fGridWidth; if ( r.intersects( QRect( x_pos, 0, 1, height() ) ) ) { result.push_back( it->second ); } @@ -1343,8 +1348,10 @@ std::vector NotePropertiesRuler::elementsIn /// QRect NotePropertiesRuler::getKeyboardCursorRect() { - uint x = m_nMargin + m_pPatternEditorPanel->getCursorPosition() * m_nGridWidth; - return QRect( x-m_nGridWidth*3, 0+1, m_nGridWidth*6, height()-2 ); + uint x = round( m_nMargin + m_pPatternEditorPanel->getCursorIndexPosition()* granularity() * m_fGridWidth ); // TODO check + int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); + uint y = nSelectedInstrument * m_nGridHeight; + return QRect( x-m_fGridWidth*3, 0+1, m_fGridWidth*6, height()-2 ); } void NotePropertiesRuler::selectAll() { diff --git a/src/gui/src/PatternEditor/NotePropertiesRuler.h b/src/gui/src/PatternEditor/NotePropertiesRuler.h index 67b7ac01e..267505754 100644 --- a/src/gui/src/PatternEditor/NotePropertiesRuler.h +++ b/src/gui/src/PatternEditor/NotePropertiesRuler.h @@ -136,7 +136,7 @@ class NotePropertiesRuler : public PatternEditor void adjustNotePropertyDelta( H2Core::Note *pNote, float fDelta, bool bMessage = false ); - int m_nDragPreviousColumn; + double m_fDragPreviousColumn; }; diff --git a/src/gui/src/PatternEditor/PatternEditor.cpp b/src/gui/src/PatternEditor/PatternEditor.cpp index 3de820bdf..913a6c741 100644 --- a/src/gui/src/PatternEditor/PatternEditor.cpp +++ b/src/gui/src/PatternEditor/PatternEditor.cpp @@ -54,8 +54,11 @@ const char* PatternEditor::__class_name = "PatternEditor"; PatternEditor::PatternEditor( QWidget *pParent, const char *sClassName, PatternEditorPanel *panel ) : Object ( sClassName ), QWidget( pParent ), m_selection( this ) { - m_nResolution = 8; - m_bUseTriplets = false; + m_nTupletNumerator = Preferences::get_instance()->getPatternEditorGridTupletNumerator(); + m_nTupletDenominator = Preferences::get_instance()->getPatternEditorGridTupletDenominator(); + m_nResolution = 8; //TODO where is this update to match preferences? + //m_nTupletNumerator = 4; + //m_nTupletDenominator = 4; m_pDraggedNote = nullptr; m_pPatternEditorPanel = panel; m_pPattern = nullptr; @@ -63,8 +66,9 @@ PatternEditor::PatternEditor( QWidget *pParent, const char *sClassName, m_bFineGrained = false; m_bCopyNotMove = false; - m_nGridWidth = Preferences::get_instance()->getPatternEditorGridWidth(); - m_nEditorWidth = m_nMargin + m_nGridWidth * ( MAX_NOTES * 4 ); + m_fGridWidth = Preferences::get_instance()->getPatternEditorGridWidth(); + /* default width is four 4/4 bars, in pixels units */ + m_nEditorWidth = m_nMargin + m_fGridWidth * ( MAX_NOTES * 4 ); setFocusPolicy(Qt::StrongFocus); @@ -85,33 +89,41 @@ PatternEditor::PatternEditor( QWidget *pParent, const char *sClassName, } -void PatternEditor::setResolution(uint res, bool bUseTriplets) +void PatternEditor::setResolution( uint res ) { - this->m_nResolution = res; - this->m_bUseTriplets = bUseTriplets; + m_nResolution = res; // redraw all update( 0, 0, width(), height() ); m_pPatternEditorPanel->updateEditors(); } +void PatternEditor::setTupletRatio( int nTupletNumerator, int nTupletDenominator ) { + m_nTupletNumerator = nTupletNumerator; + m_nTupletDenominator = nTupletDenominator; + + // redraw all + update( 0, 0, width(), height() ); + m_pPatternEditorPanel->updateEditors(); +} + void PatternEditor::zoomIn() { - if (m_nGridWidth >= 3) { - m_nGridWidth *= 2; + if ( m_fGridWidth >= 3. ) { + m_fGridWidth *= 2.; } else { - m_nGridWidth *= 1.5; + m_fGridWidth *= 1.5; } updateEditor(); } void PatternEditor::zoomOut() { - if ( m_nGridWidth > 1.5 ) { - if (m_nGridWidth > 3) { - m_nGridWidth /= 2; + if ( m_fGridWidth > 1.5 ) { + if ( m_fGridWidth > 3. ) { + m_fGridWidth /= 2.; } else { - m_nGridWidth /= 1.5; + m_fGridWidth /= 1.5; } updateEditor(); } @@ -176,8 +188,8 @@ void PatternEditor::drawNoteSymbol( QPainter &p, QPoint pos, H2Core::Note *pNote if ( bMoving ) { movingPen.setStyle( Qt::DotLine ); movingPen.setWidth( 2 ); - QPoint delta = movingGridOffset(); - movingOffset = QPoint( delta.x() * m_nGridWidth, + QPointF delta = movingGridOffset(); + movingOffset = QPoint( delta.x() * m_fGridWidth, delta.y() * m_nGridHeight ); } @@ -192,7 +204,7 @@ void PatternEditor::drawNoteSymbol( QPainter &p, QPoint pos, H2Core::Note *pNote if ( pNote->get_length() != -1 ) { float fNotePitch = pNote->get_octave() * 12 + pNote->get_key(); float fStep = pow( 1.0594630943593, ( double )fNotePitch ); - width = m_nGridWidth * pNote->get_length() / fStep; + width = m_fGridWidth * pNote->get_length() / fStep; width = width - 1; // lascio un piccolo spazio tra una nota ed un altra if ( bSelected ) { @@ -241,22 +253,37 @@ void PatternEditor::drawNoteSymbol( QPainter &p, QPoint pos, H2Core::Note *pNote } -int PatternEditor::getColumn( int x, bool bUseFineGrained ) const -{ - int nGranularity = 1; - if ( !( bUseFineGrained && m_bFineGrained ) ) { - nGranularity = granularity(); +double PatternEditor::getColumn( int x, bool bUseFineGrained ) const +{ // without fineGrain, returns the position of the nearest grid mark, in tick units (rounded value!) + // with fineGrain, returns the position of nearest tick with res = 192/whole note + + if ( x <= m_nMargin ) { + return 0; + } else { + double fGranularity; + if ( bUseFineGrained && m_bFineGrained ) { + fGranularity = 1.; + } else { + fGranularity = granularity(); + } + + double fWidth = m_fGridWidth * fGranularity; // distance between grid marks (or ticks), in pixel units + double fGridIndex = round( ( x - m_nMargin ) / fWidth ); // The index of the nearest grid mark (or tick) + return fGridIndex * fGranularity; // the position of the nearest grid mark (or tick), in tick units } - int nWidth = m_nGridWidth * nGranularity; - int nColumn = ( x - m_nMargin + (nWidth / 2) ) / nWidth; - nColumn = nColumn * nGranularity; - if ( nColumn < 0 ) { +} + +int PatternEditor::getGridIndex( int x ) const { + if ( x <= m_nMargin ) { return 0; } else { - return nColumn; + float fWidth = m_fGridWidth * granularity(); // distance between grid marks, in pixel units + int nGridIndex = round( ( x - m_nMargin ) / fWidth ); // The index of the nearest grid mark + return nGridIndex; } } + void PatternEditor::selectNone() { m_selection.clearSelection(); @@ -276,18 +303,19 @@ void PatternEditor::copy() XMLNode positionNode = selection.createNode( "sourcePosition" ); bool bWroteNote = false; // "Top left" of selection, in the three dimensional time*instrument*pitch space. - int nLowestPos, nLowestInstrument, nHighestPitch; + double fLowestPos; + int nLowestInstrument, nHighestPitch; for ( Note *pNote : m_selection ) { int nPitch = pNote->get_notekey_pitch() + 12*OCTAVE_OFFSET; - int nPos = pNote->get_position(); + double fPos = pNote->get_position(); int nInstrument = pInstrumentList->index( pNote->get_instrument() ); if ( bWroteNote ) { - nLowestPos = std::min( nPos, nLowestPos ); + fLowestPos = std::min( fPos, fLowestPos ); nLowestInstrument = std::min( nInstrument, nLowestInstrument ); nHighestPitch = std::max( nPitch, nHighestPitch ); } else { - nLowestPos = nPos; + fLowestPos = fPos; nLowestInstrument = nInstrument; nHighestPitch = nPitch; bWroteNote = true; @@ -297,11 +325,11 @@ void PatternEditor::copy() } if ( bWroteNote ) { - positionNode.write_int( "position", nLowestPos ); + positionNode.write_double( "position", fLowestPos ); positionNode.write_int( "instrument", nLowestInstrument ); positionNode.write_int( "note", nHighestPitch ); } else { - positionNode.write_int( "position", m_pPatternEditorPanel->getCursorPosition() ); + positionNode.write_int( "index_position", m_pPatternEditorPanel->getCursorIndexPosition() ); positionNode.write_int( "instrument", pHydrogen->getSelectedInstrumentNumber() ); } @@ -392,7 +420,7 @@ void PatternEditor::updateModifiers( QInputEvent *ev ) { bool PatternEditor::notesMatchExactly( Note *pNoteA, Note *pNoteB ) const { return ( pNoteA->match( pNoteB->get_instrument(), pNoteB->get_key(), pNoteB->get_octave() ) - && pNoteA->get_position() == pNoteB->get_position() + && fabs( pNoteA->get_position() - pNoteB->get_position() ) < POS_EPSILON && pNoteA->get_velocity() == pNoteB->get_velocity() && pNoteA->get_pan_r() == pNoteB->get_pan_r() && pNoteA->get_pan_l() == pNoteB->get_pan_l() @@ -464,14 +492,16 @@ void PatternEditor::deselectAndOverwriteNotes( std::vector< H2Core::Note *> &sel for ( auto pSelectedNote : selected ) { m_selection.removeFromSelection( pSelectedNote, /* bCheck=*/false ); bool bFoundExact = false; - int nPosition = pSelectedNote->get_position(); - for ( auto it = pNotes->lower_bound( nPosition ); it != pNotes->end() && it->first == nPosition; ) { + double fPosition = pSelectedNote->get_position(); + for ( auto it = pNotes->lower_bound( fPosition - POS_EPSILON ); + it != pNotes->end() && it->first < fPosition + POS_EPSILON; ) { // NOTE: counter not incremented here! Note *pNote = it->second; if ( !bFoundExact && notesMatchExactly( pNote, pSelectedNote ) ) { // Found an exact match. We keep this. bFoundExact = true; ++it; - } else if ( pSelectedNote->match( pNote ) && pNote->get_position() == pSelectedNote->get_position() ) { + } else if ( pSelectedNote->match( pNote ) // match key, octave & instrument + && fabs( pNote->get_position() - pSelectedNote->get_position() ) < POS_EPSILON ) { // Something else occupying the same position (which may or may not be an exact duplicate) it = pNotes->erase( it ); } else { @@ -526,25 +556,23 @@ void PatternEditor::updatePatternInfo() { } -QPoint PatternEditor::movingGridOffset( ) const { +QPointF PatternEditor::movingGridOffset( ) const { QPoint rawOffset = m_selection.movingOffset(); - // Quantize offset to multiples of m_nGrid{Width,Height} - int nQuantX = m_nGridWidth, nQuantY = m_nGridHeight; - float nFactor = 1; + // Quantize offset to multiples of m_fGrid{Width,Height} + double fQuantX = m_fGridWidth; + int nQuantY = m_nGridHeight; + double fFactor = 1; if ( ! m_bFineGrained ) { - nFactor = granularity(); - nQuantX = m_nGridWidth * nFactor; + fFactor = granularity(); + fQuantX = m_fGridWidth * fFactor; } - int x_bias = nQuantX / 2, y_bias = nQuantY / 2; + int y_bias = nQuantY / 2; if ( rawOffset.y() < 0 ) { y_bias = -y_bias; } - if ( rawOffset.x() < 0 ) { - x_bias = -x_bias; - } - int x_off = (rawOffset.x() + x_bias) / nQuantX; + double x_off = round( rawOffset.x() / fQuantX ); int y_off = (rawOffset.y() + y_bias) / nQuantY; - return QPoint( nFactor * x_off, y_off); + return QPointF( fFactor * x_off, y_off); } @@ -570,14 +598,14 @@ void PatternEditor::drawGridLines( QPainter &p, Qt::PenStyle style ) const pStyle->m_patternEditor_line5Color.getBlue() ), }; - int nGranularity = granularity() * m_nResolution; + int nGranularity = round( granularity() * m_nResolution ); int nNotes = MAX_NOTES; if ( m_pPattern ) { nNotes = m_pPattern->get_length(); } - int nMaxX = m_nGridWidth * nNotes + m_nMargin; + int nMaxX = m_fGridWidth * nNotes + m_nMargin; - if ( !m_bUseTriplets ) { + if ( m_nTupletNumerator == 4 && m_nTupletDenominator == 4 ) { // every other case is drawn in tuplet mode // Draw vertical lines. To minimise pen colour changes (and // avoid unnecessary division operations), we draw them in @@ -591,7 +619,7 @@ void PatternEditor::drawGridLines( QPainter &p, Qt::PenStyle style ) const // | . : . | . : . | . : . | . : . - third pass, odd 1/16th notes uint nRes = 4; - uint nStep = nGranularity / nRes * m_nGridWidth; + uint nStep = nGranularity / nRes * m_fGridWidth; // First, quarter note markers. All the quarter note markers must be drawn. if ( m_nResolution >= nRes ) { @@ -615,21 +643,27 @@ void PatternEditor::drawGridLines( QPainter &p, Qt::PenStyle style ) const nRes *= 2; nStep /= 2; } - + } else { - // Triplet style markers, we only differentiate colours on the - // first of every triplet. - uint nStep = granularity() * m_nGridWidth; + // Tuplets style markers, we only differentiate colours on the + // first of every tuplet. + float fStep = granularity() * m_fGridWidth; p.setPen( QPen( res[ 0 ], 0, style ) ); - for ( uint x = m_nMargin; x < nMaxX; x += nStep * 3 ) { - p.drawLine(x, 1, x, m_nEditorHeight - 1); + for ( float x = m_nMargin; x < nMaxX; x += fStep * m_nTupletNumerator ) { + p.drawLine( x, 1, x, m_nEditorHeight - 1); } - // Second and third marks + // Second, third... n-th marks p.setPen( QPen( res[ 2 ], 0, style ) ); - for ( uint x = m_nMargin + nStep; x < nMaxX; x += nStep * 3 ) { - p.drawLine(x, 1, x, m_nEditorHeight - 1); - p.drawLine(x + nStep, 1, x + nStep, m_nEditorHeight - 1); + for ( float x0 = m_nMargin; x0 < nMaxX; x0 += fStep * m_nTupletNumerator ) { + for ( uint i = 1; i < m_nTupletNumerator; i++ ) { + int x = round( x0 + i * fStep ); + if ( x < nMaxX ) { + p.drawLine( x, 1, x, m_nEditorHeight - 1 ); + } else { + i = m_nTupletNumerator; // trick to break + } + } } } diff --git a/src/gui/src/PatternEditor/PatternEditor.h b/src/gui/src/PatternEditor/PatternEditor.h index 06ee7b917..fbad0476a 100644 --- a/src/gui/src/PatternEditor/PatternEditor.h +++ b/src/gui/src/PatternEditor/PatternEditor.h @@ -67,12 +67,27 @@ class PatternEditor : public QWidget, //! Set the editor grid resolution, dividing a whole note into `res` subdivisions. - void setResolution( uint res, bool bUseTriplets ); + void setResolution( uint res ); uint getResolution() const { return m_nResolution; } - bool isUsingTriplets() const { return m_bUseTriplets; } + + //void setTupletNumerator( int n ) { m_nTupletNumerator = n; } + int getTupletNumerator() const { return m_nTupletNumerator; } + + //void setTupletDenominator( int n ) { m_nTupletDenominator = n; } + int getTupletDenominator() const { return m_nTupletDenominator; } + + // tuplet numerator and denominator should be set together + void setTupletRatio( int nTupletNumerator, int nTupletDenominator ); - float getGridWidth() const { return m_nGridWidth; } + float getGridWidth() const { return m_fGridWidth; } unsigned getGridHeight() const { return m_nGridHeight; } + + void setTupletResolution( int nRes, int nTupletNum, int nTupletDen) { //TODO needed? + m_nResolution = nRes; + m_nTupletNumerator = nTupletNum; + m_nTupletDenominator = nTupletDen; + } + //! Zoom in / out on the time axis void zoomIn(); void zoomOut(); @@ -150,31 +165,63 @@ public slots: protected: - //! Granularity of grid positioning (in ticks) - int granularity() const { - int nBase; - if (m_bUseTriplets) { - nBase = 3; - } - else { - nBase = 4; - } - return 4 * MAX_NOTES / ( nBase * m_nResolution ); + //! Granularity of grid positioning ( = distance between grid marks), in tick units + double granularity() const { // float for tuplets + return (double) MAX_NOTES * m_nTupletDenominator / ( m_nTupletNumerator * m_nResolution ); } uint m_nEditorHeight; uint m_nEditorWidth; - - float m_nGridWidth; + + /* the width of a tick on the screen (whose duration is defined: whole note / MAX_NOTES ) in pixel units. + * it depends on zoom. + */ + float m_fGridWidth; + unsigned m_nGridHeight; int m_nSelectedPatternNumber; H2Core::Pattern *m_pPattern; + /* use it to add a left margin in the editor, before the first tick */ const int m_nMargin = 20; + /** the inverse of grid quantum duration in whole notes + * e.g. quantum = 1/16 of whole note <=> resolution = 16 + * Ideally one could set any (fractional) resolution, but the GUI doesn't allow this: + * possible values are only powers of 2 in the GUI (or MAX_NOTES if resolution is set to off) + * + * comment by oddtime: + * It would be so cool entering resolutions like 12 (8th triplets) or 20 (16ths quintuplets) + * or 3/20 (quarter 5:3 tuplets)...!!! + * However the same result is possible with tuplets, accordingly to music notation style. + */ + //TODO should next 3 members be here or only in preferences? uint m_nResolution; - bool m_bUseTriplets; + + /** Tuplet notation is used to represent ANY rational note value in whole notes, using the std music symbols + * (quarters, 8ths, 16ths...). + * A tuplet is explicitly specified by a rational number: the fraction = m_nTupletNumerator / m_nTupletDenominator, + * in fact this fraction DIVIDES the note value returning its resultant length (in whole note units). + * + * Example: for standard triplets the ratio is 3:2, + * in fact a single 1/8 note under a triplet has length = 1/8 * 2/3 of whole note = 1/12 of whole note. + * Other examples: standard quintuplets: 5:4; + * weird (wrongly written?) quintuplets: 5:2; + * quartuplets in compound meters: 4:3; + * a difficult tuplet: 5:7. + * + * Note: when the TupletDenominator is hidden, a power of 2 is usually assumed for it (the biggest but not bigger + * than TupletNumerator) except for quartuplets or 2-tuplets (in those cases there isn't a more used assumed + * TupletDenominator). + * Note: since the music symbols provides all the (inverse) powers of 2 (quarters, 8ths, 16ths...) + * plus a sum operator (the tie!), the tuplet denominator (which becomes a numerator ;) ) is actually REDUNDANT + * to get any rational note duration in whole notes, + * BUT music notation provides it and user may benefit from it (tuplet is more clear with the explicit ratio). + */ + int m_nTupletNumerator; + int m_nTupletDenominator; + bool m_bFineGrained; bool m_bCopyNotMove; @@ -186,8 +233,13 @@ public slots: PatternEditorPanel *m_pPatternEditorPanel; QMenu *m_pPopupMenu; - int getColumn( int x, bool bUseFineGrained = false ) const; - QPoint movingGridOffset() const; + // Magnetic conversions (quantized by the grid granularity) + /* from the pixel position to the position of the nearest grid mark, in tick units */ + double getColumn( int x, bool bUseFineGrained = false ) const; + /* from the pixel position to the index of the nearest grid mark */ + int getGridIndex( int x ) const; + + QPointF movingGridOffset() const; //! Draw lines for note grid. void drawGridLines( QPainter &p, Qt::PenStyle style = Qt::SolidLine ) const; diff --git a/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp b/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp index 63ed9af59..f6bcbcf7d 100644 --- a/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp +++ b/src/gui/src/PatternEditor/PatternEditorInstrumentList.cpp @@ -114,7 +114,9 @@ InstrumentLine::InstrumentLine(QWidget* pParent) m_pFunctionPopupSub->addAction( tr( "Fill 1/2 notes" ), this, SLOT( functionFillEveryTwoNotes() ) ); m_pFunctionPopupSub->addAction( tr( "Fill 1/3 notes" ), this, SLOT( functionFillEveryThreeNotes() ) ); m_pFunctionPopupSub->addAction( tr( "Fill 1/4 notes" ), this, SLOT( functionFillEveryFourNotes() ) ); + m_pFunctionPopupSub->addAction( tr( "Fill 1/5 notes" ), this, SLOT( functionFillEveryFiveNotes() ) ); m_pFunctionPopupSub->addAction( tr( "Fill 1/6 notes" ), this, SLOT( functionFillEverySixNotes() ) ); + m_pFunctionPopupSub->addAction( tr( "Fill 1/7 notes" ), this, SLOT( functionFillEverySevenNotes() ) ); m_pFunctionPopupSub->addAction( tr( "Fill 1/8 notes" ), this, SLOT( functionFillEveryEightNotes() ) ); m_pFunctionPopupSub->addAction( tr( "Fill 1/12 notes" ), this, SLOT( functionFillEveryTwelveNotes() ) ); m_pFunctionPopupSub->addAction( tr( "Fill 1/16 notes" ), this, SLOT( functionFillEverySixteenNotes() ) ); @@ -373,7 +375,9 @@ void InstrumentLine::functionFillAllNotes(){ functionFillNotes(1); } void InstrumentLine::functionFillEveryTwoNotes(){ functionFillNotes(2); } void InstrumentLine::functionFillEveryThreeNotes(){ functionFillNotes(3); } void InstrumentLine::functionFillEveryFourNotes(){ functionFillNotes(4); } +void InstrumentLine::functionFillEveryFiveNotes(){ functionFillNotes(5); } void InstrumentLine::functionFillEverySixNotes(){ functionFillNotes(6); } +void InstrumentLine::functionFillEverySevenNotes(){ functionFillNotes(7); } void InstrumentLine::functionFillEveryEightNotes(){ functionFillNotes(8); } void InstrumentLine::functionFillEveryTwelveNotes(){ functionFillNotes(12); } void InstrumentLine::functionFillEverySixteenNotes(){ functionFillNotes(16); } @@ -383,33 +387,26 @@ void InstrumentLine::functionFillNotes( int every ) Hydrogen *pHydrogen = Hydrogen::get_instance(); PatternEditorPanel *pPatternEditorPanel = HydrogenApp::get_instance()->getPatternEditorPanel(); - DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor(); - int nBase; - if ( pPatternEditor->isUsingTriplets() ) { - nBase = 3; - } - else { - nBase = 4; - } - int nResolution = 4 * MAX_NOTES * every / ( nBase * pPatternEditor->getResolution() ); - + //DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor(); Song *pSong = pHydrogen->getSong(); - QStringList notePositions; + std::vector notePositions; Pattern* pCurrentPattern = getCurrentPattern(); - if (pCurrentPattern != nullptr) { + if ( pCurrentPattern != nullptr ) { int nPatternSize = pCurrentPattern->get_length(); int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); - if (nSelectedInstrument != -1) { + if ( nSelectedInstrument != -1 ) { Instrument *instrRef = (pSong->getInstrumentList())->get( nSelectedInstrument ); - for (int i = 0; i < nPatternSize; i += nResolution) { + int i = 0; + double fColumn = 0; + while ( fColumn < nPatternSize) { bool noteAlreadyPresent = false; const Pattern::notes_t* notes = pCurrentPattern->get_notes(); - FOREACH_NOTE_CST_IT_BOUND(notes,it,i) { + FOREACH_NOTE_CST_IT_BOUND( notes, it, fColumn ) { Note *pNote = it->second; if ( pNote->get_instrument() == instrRef ) { // note already exists @@ -419,10 +416,16 @@ void InstrumentLine::functionFillNotes( int every ) } if ( noteAlreadyPresent == false ) { - notePositions << QString("%1").arg(i); + //notePositions << QString("%1").arg( nColumn ); + notePositions.push_back( fColumn ); } - } - SE_fillNotesRightClickAction *action = new SE_fillNotesRightClickAction( notePositions, nSelectedInstrument, pHydrogen->getSelectedPatternNumber() ); + + i += every; + fColumn = i * pPatternEditorPanel->granularity(); + } + SE_fillNotesRightClickAction *action = new SE_fillNotesRightClickAction( notePositions, + nSelectedInstrument, + pHydrogen->getSelectedPatternNumber() ); HydrogenApp::get_instance()->m_pUndoStack->push( action ); } } @@ -431,45 +434,37 @@ void InstrumentLine::functionFillNotes( int every ) -void InstrumentLine::functionRandomizeVelocity() +void InstrumentLine::functionRandomizeVelocity() // TODO should it act only on the notes on the grid marks (magnetically)? { Hydrogen *pHydrogen = Hydrogen::get_instance(); PatternEditorPanel *pPatternEditorPanel = HydrogenApp::get_instance()->getPatternEditorPanel(); - DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor(); + //DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor(); - - int nBase; - if ( pPatternEditor->isUsingTriplets() ) { - nBase = 3; - } - else { - nBase = 4; - } - int nResolution = 4 * MAX_NOTES / ( nBase * pPatternEditor->getResolution() ); + float fResolution = pPatternEditorPanel-> granularity(); Song *pSong = pHydrogen->getSong(); - QStringList noteVeloValue; + QStringList noteVeloValue; // TODO why a QSTringList instead of std::vector ? Does it save space? QStringList oldNoteVeloValue; Pattern* pCurrentPattern = getCurrentPattern(); - if (pCurrentPattern != nullptr) { + if ( pCurrentPattern != nullptr ) { int nPatternSize = pCurrentPattern->get_length(); int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber(); - if (nSelectedInstrument != -1) { - Instrument *instrRef = (pSong->getInstrumentList())->get( nSelectedInstrument ); + if ( nSelectedInstrument != -1 ) { + Instrument *instrRef = ( pSong->getInstrumentList() )->get( nSelectedInstrument ); - for (int i = 0; i < nPatternSize; i += nResolution) { + for ( int i = 0; i*fResolution < nPatternSize; i++ ) { //TODO while() loop using variable float fTickPosition const Pattern::notes_t* notes = pCurrentPattern->get_notes(); - FOREACH_NOTE_CST_IT_BOUND(notes,it,i) { + FOREACH_NOTE_CST_IT_BOUND( notes, it, i*fResolution ) { Note *pNote = it->second; if ( pNote->get_instrument() == instrRef ) { - float fVal = ( rand() % 100 ) / 100.0; + float fVal = ( rand() % 100 ) / 100.0; // uses quantized 0.01 steps oldNoteVeloValue << QString("%1").arg( pNote->get_velocity() ); fVal = pNote->get_velocity() + ( ( fVal - 0.50 ) / 2 ); - if ( fVal < 0 ) { + if ( fVal < 0 ) { fVal = 0; } if ( fVal > 1 ) { diff --git a/src/gui/src/PatternEditor/PatternEditorInstrumentList.h b/src/gui/src/PatternEditor/PatternEditorInstrumentList.h index 99668ca69..ab3d62a77 100644 --- a/src/gui/src/PatternEditor/PatternEditorInstrumentList.h +++ b/src/gui/src/PatternEditor/PatternEditorInstrumentList.h @@ -29,6 +29,7 @@ #include #include +#include #include #include "../Widgets/PixmapWidget.h" @@ -61,11 +62,14 @@ class InstrumentLine : public PixmapWidget private slots: void functionClearNotes(); + // TODO use parametric fillNotes(every) with Qvariant in menu item? void functionFillAllNotes(); void functionFillEveryTwoNotes(); void functionFillEveryThreeNotes(); void functionFillEveryFourNotes(); + void functionFillEveryFiveNotes(); void functionFillEverySixNotes(); + void functionFillEverySevenNotes(); void functionFillEveryEightNotes(); void functionFillEveryTwelveNotes(); void functionFillEverySixteenNotes(); diff --git a/src/gui/src/PatternEditor/PatternEditorPanel.cpp b/src/gui/src/PatternEditor/PatternEditorPanel.cpp index cca8a245f..7efc1ef84 100644 --- a/src/gui/src/PatternEditor/PatternEditorPanel.cpp +++ b/src/gui/src/PatternEditor/PatternEditorPanel.cpp @@ -71,10 +71,19 @@ PatternEditorPanel::PatternEditorPanel( QWidget *pParent ) { setAcceptDrops(true); - Preferences *pPref = Preferences::get_instance(); + m_nCursorIndexPosition = 0; - m_nCursorPosition = 0; - m_nCursorIncrement = 0; + + Preferences *pPref = Preferences::get_instance(); + if ( pPref ) { // TODO need this statement? + m_nResolution = pPref->getPatternEditorGridResolution(); + m_nTupletNumerator = pPref->getPatternEditorGridTupletNumerator(); + m_nTupletDenominator = pPref->getPatternEditorGridTupletDenominator(); + } else { // default values + m_nResolution = 8; + m_nTupletNumerator = 4; + m_nTupletDenominator = 4; + } // Editor TOP PixmapWidget *editor_top = new PixmapWidget( nullptr ); @@ -106,7 +115,7 @@ PatternEditorPanel::PatternEditorPanel( QWidget *pParent ) //wolke some background images back_size_res PixmapWidget *pSizeResol = new PixmapWidget( nullptr ); - pSizeResol->setFixedSize( 216, 20 ); + pSizeResol->setFixedSize( 300, 20 ); pSizeResol->setPixmap( "/patternEditor/background_res-new.png" ); pSizeResol->move( 0, 3 ); editor_top_hbox_2->addWidget( pSizeResol ); @@ -156,7 +165,12 @@ PatternEditorPanel::PatternEditorPanel( QWidget *pParent ) __resolution_combo->move( 154, 2 ); // is triggered from inside PatternEditorPanel() connect( __resolution_combo, SIGNAL( valueChanged( int ) ), this, SLOT( gridResolutionChanged( int ) ) ); - + + // TUPLET LCD + m_pTupletLCD = new LCDDisplay( pSizeResol, LCDDigit::SMALL_BLUE, 5 ); + m_pTupletLCD->move( 252, 2 ); + m_pTupletLCD->setToolTip( tr( "Select resolution Tuplet" ) ); + connect( m_pTupletLCD, SIGNAL( displayClicked( LCDDisplay* ) ), this, SLOT( tupletLCDClicked() ) ); PixmapWidget *pRec = new PixmapWidget( nullptr ); pRec->setFixedSize( 300, 20 ); @@ -525,11 +539,11 @@ PatternEditorPanel::PatternEditorPanel( QWidget *pParent ) // restore grid resolution int nIndex; - int nRes = pPref->getPatternEditorGridResolution(); - if ( nRes == MAX_NOTES ) { + + if ( m_nResolution == MAX_NOTES ) { nIndex = 11; - } else if ( pPref->isPatternEditorUsingTriplets() == false ) { - switch ( nRes ) { + } else if ( m_nTupletNumerator != 3 || m_nTupletDenominator != 2 ) { + switch ( m_nResolution ) { case 4: nIndex = 0; break; case 8: nIndex = 1; break; case 16: nIndex = 2; break; @@ -537,20 +551,26 @@ PatternEditorPanel::PatternEditorPanel( QWidget *pParent ) case 64: nIndex = 4; break; default: nIndex = 0; - ERRORLOG( QString( "Wrong grid resolution: %1" ).arg( pPref->getPatternEditorGridResolution() ) ); + ERRORLOG( QString( "Wrong grid resolution: %1" ).arg( m_nResolution ) ); } } else { - switch ( nRes ) { - case 8: nIndex = 6; break; - case 16: nIndex = 7; break; - case 32: nIndex = 8; break; - case 64: nIndex = 9; break; + switch ( m_nResolution ) { + case 4: nIndex = 6; break; + case 8: nIndex = 7; break; + case 16: nIndex = 8; break; + case 32: nIndex = 9; break; default: nIndex = 6; - ERRORLOG( QString( "Wrong grid resolution: %1" ).arg( pPref->getPatternEditorGridResolution() ) ); + ERRORLOG( QString( "Wrong grid resolution: %1" ).arg( m_nResolution ) ); } } __resolution_combo->select( nIndex ); + + if ( m_nTupletNumerator == 4 && m_nTupletDenominator == 4 ) { + m_pTupletLCD->setText( tr( "off" ) ); + } else { + m_pTupletLCD->setText( QString("%1:%2").arg( m_nTupletNumerator ).arg( m_nTupletDenominator ) ); + } // LAYOUT QVBoxLayout *pVBox = new QVBoxLayout(); @@ -627,40 +647,65 @@ void PatternEditorPanel::on_patternEditorHScroll( int nValue ) -void PatternEditorPanel::gridResolutionChanged( int nSelected ) -{ - int nResolution; - bool bUseTriplets = false; - +void PatternEditorPanel::gridResolutionChanged( int nSelected ) { + double fOldCursorTickPosition = getCursorFloatPosition(); if ( nSelected == 11 ) { - nResolution = MAX_NOTES; + m_nResolution = MAX_NOTES; + m_nTupletNumerator = 4; + m_nTupletDenominator = 4; + + setTupletRatioToAllEditors( m_nTupletNumerator, m_nTupletDenominator ); + // TODO why all the editors have the same grid members and there is no a unique variable for them to read? + + m_pTupletLCD->setText( QString( "%1:%2" ).arg( m_nTupletNumerator ).arg( m_nTupletDenominator ) ); + Preferences::get_instance()->setPatternEditorGridTupletRatio( m_nTupletNumerator, m_nTupletDenominator ); } else if ( nSelected > 4 ) { - bUseTriplets = true; - nResolution = 0x1 << ( nSelected - 3 ); + m_nTupletNumerator = 3; + m_nTupletDenominator = 2; + m_nResolution = 0x1 << ( nSelected - 4 ); + + setTupletRatioToAllEditors( m_nTupletNumerator, m_nTupletDenominator ); + // TODO why all the editors have the same grid members and there is no a unique variable for them to read? + + m_pTupletLCD->setText( QString( "%1:%2" ).arg( m_nTupletNumerator ).arg( m_nTupletDenominator ) ); + Preferences::get_instance()->setPatternEditorGridTupletRatio( m_nTupletNumerator, m_nTupletDenominator ); } - else { - nResolution = 0x1 << ( nSelected + 2 ); + else { // plain note value symbols + m_nResolution = 0x1 << ( nSelected + 2 ); } // INFOLOG( QString("idx %1 -> %2 resolution").arg( nSelected ).arg( nResolution ) ); - m_pDrumPatternEditor->setResolution( nResolution, bUseTriplets ); - m_pPianoRollEditor->setResolution( nResolution, bUseTriplets ); - m_pNoteVelocityEditor->setResolution( nResolution, bUseTriplets ); - m_pNoteLeadLagEditor->setResolution( nResolution, bUseTriplets ); - m_pNoteNoteKeyEditor->setResolution( nResolution, bUseTriplets ); - m_pNoteProbabilityEditor->setResolution( nResolution, bUseTriplets ); - m_pNotePanEditor->setResolution( nResolution, bUseTriplets ); - - m_nCursorIncrement = ( bUseTriplets ? 4 : 3 ) * MAX_NOTES / ( nResolution * 3 ); - m_nCursorPosition = m_nCursorIncrement * ( m_nCursorPosition / m_nCursorIncrement ); - - Preferences::get_instance()->setPatternEditorGridResolution( nResolution ); - Preferences::get_instance()->setPatternEditorUsingTriplets( bUseTriplets ); + setResolutionToAllEditors( m_nResolution ); + // TODO why all the editors have the same grid members and there is no a unique variable for them to read? + Preferences::get_instance()->setPatternEditorGridResolution( m_nResolution ); + + // move cursor to the nearest grid mark (since granularity changed) + setCursorPosition( fOldCursorTickPosition ); } +void PatternEditorPanel::setResolutionToAllEditors( int nResolution ) { + // TODO should this be saved only in Preferences or Editor Panel or in all these classes? + m_pDrumPatternEditor->setResolution( nResolution ); + m_pPianoRollEditor->setResolution( nResolution ); + m_pNoteVelocityEditor->setResolution( nResolution ); + m_pNoteLeadLagEditor->setResolution( nResolution ); + m_pNoteNoteKeyEditor->setResolution( nResolution ); + m_pNoteProbabilityEditor->setResolution( nResolution ); + m_pNotePanEditor->setResolution( nResolution ); +} +void PatternEditorPanel::setTupletRatioToAllEditors( int nTupletNum, int nTupletDen ) { + //TODO should these be saved only in Preferences or Editor Panel or in all these classes? + m_pDrumPatternEditor->setTupletRatio( nTupletNum, nTupletDen ); + m_pPianoRollEditor->setTupletRatio( nTupletNum, nTupletDen ); + m_pNoteVelocityEditor->setTupletRatio( nTupletNum, nTupletDen ); + m_pNoteLeadLagEditor->setTupletRatio( nTupletNum, nTupletDen ); + m_pNoteNoteKeyEditor->setTupletRatio( nTupletNum, nTupletDen ); + m_pNoteProbabilityEditor->setTupletRatio( nTupletNum, nTupletDen ); + m_pNotePanEditor->setTupletRatio( nTupletNum, nTupletDen ); +} void PatternEditorPanel::selectedPatternChangedEvent() { @@ -1023,6 +1068,73 @@ void PatternEditorPanel::patternSizeLCDClicked() } } +void PatternEditorPanel::tupletLCDClicked() +{ + Preferences *pPref = Preferences::get_instance(); + bool bIsOkPressed; + // want to show ratio to see the input format, even if tuplet is currently off + QString text = QString( "%1:%2" ).arg( m_nTupletNumerator ).arg( m_nTupletDenominator ); + + /*QString text = m_pTupletLCD->getText(); //TODO cancel + if ( text == "off" ) { + text = "4:4"; + }*/ + + QString qtmp = QInputDialog::getText( this, "Tuplet Resolution", tr( "Enter tuplet ratio (\"4\" to set off)" ), + QLineEdit::Normal, text, &bIsOkPressed ); + qtmp.replace( "/", ":" ); // allow both '/' and ':' separators + + if ( bIsOkPressed ) { + if ( m_pTupletLCD->getText() == qtmp ) { // text unchanged + return; + } + double fOldCursorTickPosition = getCursorFloatPosition(); + + QStringList parts = qtmp.split( ':' ); + if ( parts.size() == 1 || parts.size() == 2 ) { // must reject if parts.size > 2 or null + bool bOk; + m_nTupletNumerator = parts[0].toInt( &bOk ); + if ( bOk && parts.size() == 2 ) { // user entered both numerator and denominator + m_nTupletDenominator = parts[1].toInt( &bOk ); + if ( bOk && ( m_nTupletDenominator <= 0 || m_nTupletDenominator > MAX_NOTES ) ) { //TODO what limit? + QMessageBox::information( this, "Hydrogen", tr( "Denominator value rejected.\nLimits: (0, %1]" + ).arg(MAX_NOTES) ); + return; + } + } + else { // user entered numerator only. compute the standard denominator + m_nTupletDenominator = 1; + while ( ( 2 * m_nTupletDenominator ) <= m_nTupletNumerator ) { + m_nTupletDenominator *= 2; + } + } + if ( bOk && m_nTupletNumerator > 0 ) { + if ( m_nTupletNumerator > 99 ) { //TODO what limit? + QMessageBox::information( this, "Hydrogen", tr( "Tuplet numerator too big.\nMaximum = 99" ) ); + return; + } + else { // set tuplet numerator and denominator + setTupletRatioToAllEditors( m_nTupletNumerator, m_nTupletDenominator ); + + // move cursor to the nearest grid mark. + setCursorPosition( fOldCursorTickPosition ); + + m_pTupletLCD->setText( QString( "%1:%2" ).arg( m_nTupletNumerator ).arg( m_nTupletDenominator ) ); + pPref->setPatternEditorGridTupletRatio( m_nTupletNumerator, m_nTupletDenominator ); + } + } + else { // user entered invalid text + QMessageBox::information( this, "Hydrogen", tr( "Text rejected" ) ); + return; + } + } + else { // last case: user entered more than 2 ':' + QMessageBox::information( this, "Hydrogen", tr( "Text rejected" ) ); + return; + } + } +} + void PatternEditorPanel::moveUpBtnClicked( Button * ) { @@ -1157,45 +1269,71 @@ void PatternEditorPanel::updatePianorollEditor() m_pDrumPatternEditor->updateEditor(); // force an update } +int PatternEditorPanel::getCursorIndexPosition() +{ + return m_nCursorIndexPosition; +} + int PatternEditorPanel::getCursorPosition() { - return m_nCursorPosition; + return round( m_nCursorIndexPosition * granularity() ); +} +double PatternEditorPanel::getCursorFloatPosition() +{ + return m_nCursorIndexPosition * granularity(); } void PatternEditorPanel::ensureCursorVisible() { int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber(); uint y = nSelectedInstrument * Preferences::get_instance()->getPatternEditorGridHeight(); - m_pEditorScrollView->ensureVisible( m_nCursorPosition * m_pPatternEditorRuler->getGridWidth(), y ); + m_pEditorScrollView->ensureVisible( + round( m_nCursorIndexPosition * granularity() * m_pPatternEditorRuler->getGridWidth() ), + y ); +} + +void PatternEditorPanel::setCursorIndexPosition( int nGridIndex ) +{ + if ( nGridIndex < 0 ) { + m_nCursorIndexPosition = 0; + } else if ( round( nGridIndex * granularity() ) >= m_pPattern->get_length() ) { // TODO check boundary + m_nCursorIndexPosition = m_pPattern->get_length() / granularity(); // will be floored by (int) cast + } else { + m_nCursorIndexPosition = nGridIndex; + } } -void PatternEditorPanel::setCursorPosition(int nCursorPosition) +void PatternEditorPanel::setCursorPosition( double fColumn ) { - if ( nCursorPosition < 0 ) { - m_nCursorPosition = 0; - } else if ( nCursorPosition >= m_pPattern->get_length() ) { - m_nCursorPosition = m_pPattern->get_length() - m_nCursorIncrement; + if ( fColumn < 0. ) { + m_nCursorIndexPosition = 0; } else { - m_nCursorPosition = nCursorPosition; + m_nCursorIndexPosition = round( fColumn / granularity() ); //TODO round()? since it is used in resolutionChanged, floor() ensures to not exceed the pattern length... should floor() cause problems? + if ( m_pPattern != nullptr) { // avoid segm fault at start + if ( m_nCursorIndexPosition * granularity() >= m_pPattern->get_length() ) { // THIS CAUSES segmentation fault at start because there's no pPattern) + m_nCursorIndexPosition--; + std::cout << "here\n"; + } + } } } int PatternEditorPanel::moveCursorLeft( int n ) { - m_nCursorPosition = std::max( m_nCursorPosition - m_nCursorIncrement * n, - 0 ); + m_nCursorIndexPosition = std::max( m_nCursorIndexPosition - n, + 0 ); ensureCursorVisible(); - return m_nCursorPosition; + return m_nCursorIndexPosition; } int PatternEditorPanel::moveCursorRight( int n ) { - m_nCursorPosition = std::min( m_nCursorPosition + m_nCursorIncrement * n, - m_pPattern->get_length() - m_nCursorIncrement ); - + m_nCursorIndexPosition = std::min( m_nCursorIndexPosition + n, + (int) ceil( m_pPattern->get_length() / granularity() - 1 ) //TODO check formula + ); ensureCursorVisible(); - return m_nCursorPosition; + return m_nCursorIndexPosition; } diff --git a/src/gui/src/PatternEditor/PatternEditorPanel.h b/src/gui/src/PatternEditor/PatternEditorPanel.h index 8f7a1ede0..c40e80d68 100644 --- a/src/gui/src/PatternEditor/PatternEditorPanel.h +++ b/src/gui/src/PatternEditor/PatternEditorPanel.h @@ -80,14 +80,25 @@ class PatternEditorPanel : public QWidget, public EventListener, public H2Core:: //~ Implements EventListener interface void ensureCursorVisible(); - int getCursorPosition(); - void setCursorPosition(int nCursorPosition); + int getCursorIndexPosition(); + int getCursorPosition(); // TODO use this in many lines rather than the explicit expression? make inline + double getCursorFloatPosition(); // TODO use this in many lines rather than the explicit expression? make inline + void setCursorIndexPosition( int nGridIndex ); + // used to update the cursor when changing resolution + void setCursorPosition( double fColumn ); //TODO rename setCursorFloatTickPosition. int moveCursorLeft( int n = 1 ); int moveCursorRight( int n = 1 ); void selectInstrumentNotes( int nInstrument ); void updateEditors( bool bPatternOnly = false ); + //! Granularity of grid positioning ( = distance between grid marks), in tick units + double granularity() const { // float for tuplets + return (double) MAX_NOTES * m_nTupletDenominator / ( m_nTupletNumerator * m_nResolution ); + } + int getTupletNumerator(){ return m_nTupletNumerator; } + int getTupletDenominator(){ return m_nTupletDenominator; } + int getResolution(){ return m_nResolution; } public slots: void showDrumEditor(); @@ -100,6 +111,9 @@ class PatternEditorPanel : public QWidget, public EventListener, public H2Core:: void updatePatternSizeLCD(); void patternSizeLCDClicked(); void denominatorWarningClicked(); + void tupletLCDClicked(); + void setResolutionToAllEditors( int nResolution ); + void setTupletRatioToAllEditors( int nTupletNum, int nTupletDen ); void hearNotesBtnClick(Button *ref); @@ -125,9 +139,10 @@ class PatternEditorPanel : public QWidget, public EventListener, public H2Core:: QLabel * pSLlabel; // Editor top - LCDDisplay * __pattern_size_LCD; + LCDDisplay * __pattern_size_LCD; Button * m_pDenominatorWarning; LCDCombo * __resolution_combo; + LCDDisplay * m_pTupletLCD; ToggleButton * __show_drum_btn; ToggleButton * __show_piano_btn; // ~Editor top @@ -183,10 +198,17 @@ class PatternEditorPanel : public QWidget, public EventListener, public H2Core:: Button * resDropdownBtn; bool m_bEnablePatternResize; - - // Cursor positioning - int m_nCursorPosition; - int m_nCursorIncrement; + + + //TODO should these 3 members be here or only in preferences? or viceversa? Or maybe pref members should be overwritten + //only when saving preferences, rather than being updated each time the user changes the editor panel + uint m_nResolution; + int m_nTupletNumerator; + int m_nTupletDenominator; + + /* Cursor positioning + * it refers to the current grid granularity (which depends on resolution and tuplet ratio) */ + int m_nCursorIndexPosition; //~ Cursor virtual void dragEnterEvent(QDragEnterEvent *event) override; diff --git a/src/gui/src/PatternEditor/PatternEditorRuler.cpp b/src/gui/src/PatternEditor/PatternEditorRuler.cpp index e433ec4e2..3507a5658 100644 --- a/src/gui/src/PatternEditor/PatternEditorRuler.cpp +++ b/src/gui/src/PatternEditor/PatternEditorRuler.cpp @@ -56,9 +56,9 @@ PatternEditorRuler::PatternEditorRuler( QWidget* parent ) m_pPattern = nullptr; - m_nGridWidth = Preferences::get_instance()->getPatternEditorGridWidth(); + m_fGridWidth = Preferences::get_instance()->getPatternEditorGridWidth(); - m_nRulerWidth = 20 + m_nGridWidth * ( MAX_NOTES * 4 ); + m_nRulerWidth = 20 + m_fGridWidth * ( MAX_NOTES * 4 ); m_nRulerHeight = 25; resize( m_nRulerWidth, m_nRulerHeight ); @@ -189,7 +189,7 @@ void PatternEditorRuler::paintEvent( QPaintEvent *ev) // gray background for unusable section of pattern if (m_pPattern) { - int nXStart = 20 + m_pPattern->get_length() * m_nGridWidth; + int nXStart = 20 + m_pPattern->get_length() * m_fGridWidth; if ( (m_nRulerWidth - nXStart) != 0 ) { painter.fillRect( nXStart, 0, m_nRulerWidth - nXStart, m_nRulerHeight, QColor(170,170,170) ); } @@ -210,7 +210,7 @@ void PatternEditorRuler::paintEvent( QPaintEvent *ev) uint nQuarter = 48; for ( int i = 0; i < 64 ; i++ ) { - int nText_x = 20 + nQuarter / 4 * i * m_nGridWidth; + int nText_x = 20 + nQuarter / 4 * i * m_fGridWidth; if ( ( i % 4 ) == 0 ) { painter.setPen( textColor ); painter.drawText( nText_x - 30, 0, 60, m_nRulerHeight, Qt::AlignCenter, QString("%1").arg(i / 4 + 1) ); @@ -225,7 +225,7 @@ void PatternEditorRuler::paintEvent( QPaintEvent *ev) // draw tickPosition if (m_nTicks != -1) { - uint x = (uint)( 20 + m_nTicks * m_nGridWidth - 5 - 11 / 2.0 ); + uint x = (uint)( 20 + m_nTicks * m_fGridWidth - 5 - 11 / 2.0 ); painter.drawPixmap( QRect( x, height() / 2, 11, 8 ), m_tickPosition, QRect( 0, 0, 11, 8 ) ); } @@ -235,13 +235,13 @@ void PatternEditorRuler::paintEvent( QPaintEvent *ev) void PatternEditorRuler::zoomIn() { - if (m_nGridWidth >= 3){ - m_nGridWidth *= 2; + if (m_fGridWidth >= 3){ + m_fGridWidth *= 2; }else { - m_nGridWidth *= 1.5; + m_fGridWidth *= 1.5; } - m_nRulerWidth = 20 + m_nGridWidth * ( MAX_NOTES * 4 ); + m_nRulerWidth = 20 + m_fGridWidth * ( MAX_NOTES * 4 ); resize( QSize(m_nRulerWidth, m_nRulerHeight )); delete m_pBackground; m_pBackground = new QPixmap( m_nRulerWidth, m_nRulerHeight ); @@ -254,14 +254,14 @@ void PatternEditorRuler::zoomIn() void PatternEditorRuler::zoomOut() { - if ( m_nGridWidth > 1.5 ) { - if (m_nGridWidth > 3){ - m_nGridWidth /= 2; + if ( m_fGridWidth > 1.5 ) { + if (m_fGridWidth > 3){ + m_fGridWidth /= 2; }else { - m_nGridWidth /= 1.5; + m_fGridWidth /= 1.5; } - m_nRulerWidth = 20 + m_nGridWidth * ( MAX_NOTES * 4 ); + m_nRulerWidth = 20 + m_fGridWidth * ( MAX_NOTES * 4 ); resize( QSize(m_nRulerWidth, m_nRulerHeight) ); delete m_pBackground; m_pBackground = new QPixmap( m_nRulerWidth, m_nRulerHeight ); diff --git a/src/gui/src/PatternEditor/PatternEditorRuler.h b/src/gui/src/PatternEditor/PatternEditorRuler.h index 8e01f7ef4..02c7cd3cc 100644 --- a/src/gui/src/PatternEditor/PatternEditorRuler.h +++ b/src/gui/src/PatternEditor/PatternEditorRuler.h @@ -57,7 +57,7 @@ class PatternEditorRuler : public QWidget, public H2Core::Object, public EventLi void zoomIn(); void zoomOut(); float getGridWidth() const { - return m_nGridWidth; + return m_fGridWidth; }; public slots: @@ -66,7 +66,7 @@ class PatternEditorRuler : public QWidget, public H2Core::Object, public EventLi private: uint m_nRulerWidth; uint m_nRulerHeight; - float m_nGridWidth; + float m_fGridWidth; QPixmap *m_pBackground; QPixmap m_tickPosition; diff --git a/src/gui/src/PatternEditor/PianoRollEditor.cpp b/src/gui/src/PatternEditor/PianoRollEditor.cpp index 4c7ca3a50..c0908d99d 100644 --- a/src/gui/src/PatternEditor/PianoRollEditor.cpp +++ b/src/gui/src/PatternEditor/PianoRollEditor.cpp @@ -83,10 +83,10 @@ void PianoRollEditor::updateEditor( bool bPatternOnly ) { // uint nEditorWidth; if ( m_pPattern ) { - m_nEditorWidth = m_nMargin + m_nGridWidth * m_pPattern->get_length(); + m_nEditorWidth = m_nMargin + m_fGridWidth * m_pPattern->get_length(); } else { - m_nEditorWidth = m_nMargin + m_nGridWidth * MAX_NOTES; + m_nEditorWidth = m_nMargin + m_fGridWidth * MAX_NOTES; } if ( !bPatternOnly ) { m_bNeedsBackgroundUpdate = true; @@ -312,8 +312,8 @@ void PianoRollEditor::drawPattern() p.setPen( pen ); p.setBrush( Qt::NoBrush ); p.setRenderHint( QPainter::Antialiasing ); - p.drawRoundedRect( QRect( pos.x() - m_nGridWidth*3, pos.y()-2, - m_nGridWidth*6, m_nGridHeight+3 ), 4, 4 ); + p.drawRoundedRect( QRect( pos.x() - m_fGridWidth*3, pos.y()-2, + m_fGridWidth*6, m_nGridHeight+3 ), 4, 4 ); } } @@ -324,7 +324,7 @@ void PianoRollEditor::drawNote( Note *pNote, QPainter *pPainter ) Hydrogen *pHydrogen = Hydrogen::get_instance(); InstrumentList * pInstrList = pHydrogen->getSong()->getInstrumentList(); if ( pInstrList->index( pNote->get_instrument() ) == pHydrogen->getSelectedInstrumentNumber() ) { - QPoint pos ( m_nMargin + pNote->get_position() * m_nGridWidth, + QPoint pos ( m_nMargin + pNote->get_position() * m_fGridWidth, m_nGridHeight * pitchToLine( pNote->get_notekey_pitch() ) + 1); drawNoteSymbol( *pPainter, pos, pNote ); } @@ -437,7 +437,7 @@ void PianoRollEditor::mouseClickEvent( QMouseEvent *ev ) { unsigned nRealColumn = 0; if( ev->x() > m_nMargin ) { - nRealColumn = (ev->x() - m_nMargin) / static_cast(m_nGridWidth); + nRealColumn = (ev->x() - m_nMargin) / static_cast(m_fGridWidth); } if ( ev->modifiers() & Qt::ShiftModifier ) { @@ -496,7 +496,7 @@ void PianoRollEditor::mouseDragStartEvent( QMouseEvent *ev ) unsigned nRealColumn = 0; if( ev->x() > m_nMargin ) { - nRealColumn = (ev->x() - m_nMargin) / static_cast(m_nGridWidth); + nRealColumn = (ev->x() - m_nMargin) / static_cast(m_fGridWidth); } @@ -859,7 +859,7 @@ void PianoRollEditor::mouseDragEndEvent( QMouseEvent *ev ) QPoint PianoRollEditor::cursorPosition() { - uint x = m_nMargin + m_pPatternEditorPanel->getCursorPosition() * m_nGridWidth; + uint x = m_nMargin + m_pPatternEditorPanel->getCursorPosition() * m_fGridWidth; uint y = m_nGridHeight * pitchToLine( m_nCursorPitch ) + 1; return QPoint(x, y); } @@ -1228,7 +1228,7 @@ void PianoRollEditor::editNotePropertiesAction( int nColumn, void PianoRollEditor::selectionMoveEndEvent( QInputEvent *ev ) { updateModifiers( ev ); - QPoint offset = movingGridOffset(); + QPointF offset = movingGridOffset(); if ( offset.x() == 0 && offset.y() == 0 ) { // Move with no effect. return; @@ -1309,8 +1309,8 @@ std::vector PianoRollEditor::elementsIntersecti } // Calculate the first and last position values that this rect will intersect with - int x_min = (r.left() - w - m_nMargin) / m_nGridWidth; - int x_max = (r.right() + w - m_nMargin) / m_nGridWidth; + int x_min = (r.left() - w - m_nMargin) / m_fGridWidth; + int x_max = (r.right() + w - m_nMargin) / m_fGridWidth; const Pattern::notes_t* pNotes = m_pPattern->get_notes(); std::vector result; @@ -1318,7 +1318,7 @@ std::vector PianoRollEditor::elementsIntersecti for ( auto it = pNotes->lower_bound( x_min ); it != pNotes->end() && it->first <= x_max; ++it ) { Note *pNote = it->second; if ( pNote->get_instrument() == pInstr ) { - uint start_x = m_nMargin + pNote->get_position() * m_nGridWidth; + uint start_x = m_nMargin + pNote->get_position() * m_fGridWidth; uint start_y = m_nGridHeight * pitchToLine( pNote->get_notekey_pitch() ) + 1; if ( r.intersects( QRect( start_x -4 , start_y, w, h ) ) ) { @@ -1335,6 +1335,6 @@ std::vector PianoRollEditor::elementsIntersecti /// QRect PianoRollEditor::getKeyboardCursorRect() { QPoint pos = cursorPosition(); - return QRect( pos.x() - m_nGridWidth*3, pos.y()-2, - m_nGridWidth*6, m_nGridHeight+3 ); + return QRect( pos.x() - m_fGridWidth*3, pos.y()-2, + m_fGridWidth*6, m_nGridHeight+3 ); } diff --git a/src/gui/src/SongEditor/SongEditor.cpp b/src/gui/src/SongEditor/SongEditor.cpp index 20ee3f063..a5e1834ba 100644 --- a/src/gui/src/SongEditor/SongEditor.cpp +++ b/src/gui/src/SongEditor/SongEditor.cpp @@ -1002,7 +1002,7 @@ QPoint SongEditor::movingGridOffset( ) const { if ( rawOffset.x() < 0 ) { x_bias = -x_bias; } - int x_off = (rawOffset.x() + x_bias) / (int)m_nGridWidth; + int x_off = (rawOffset.x() + x_bias) / (int)m_nGridWidth; // in ticks int y_off = (rawOffset.y() + y_bias) / (int)m_nGridHeight; return QPoint( x_off, y_off ); } diff --git a/src/gui/src/UndoActions.h b/src/gui/src/UndoActions.h index 0b1bd3d1b..b89d8bbc3 100644 --- a/src/gui/src/UndoActions.h +++ b/src/gui/src/UndoActions.h @@ -487,7 +487,7 @@ class SE_editTagAction : public QUndoCommand class SE_addOrDeleteNoteAction : public QUndoCommand { public: - SE_addOrDeleteNoteAction( int nColumn, + SE_addOrDeleteNoteAction( double fColumn, int nRow, int selectedPatternNumber, int oldLength, @@ -505,11 +505,11 @@ class SE_addOrDeleteNoteAction : public QUndoCommand bool isNoteOff ){ if( isDelete ){ - setText( QObject::tr( "Delete note ( %1, %2)" ).arg( nColumn ).arg( nRow ) ); + setText( QObject::tr( "Delete note ( %1, %2)" ).arg( fColumn ).arg( nRow ) ); } else { - setText( QObject::tr( "Add note ( %1, %2)" ).arg( nColumn ).arg( nRow ) ); + setText( QObject::tr( "Add note ( %1, %2)" ).arg( fColumn ).arg( nRow ) ); } - __nColumn = nColumn; + __fColumn = fColumn; __nRow = nRow; __selectedPatternNumber = selectedPatternNumber; __oldLength = oldLength; @@ -531,7 +531,7 @@ class SE_addOrDeleteNoteAction : public QUndoCommand //qDebug() << "Add note Undo "; HydrogenApp* h2app = HydrogenApp::get_instance(); __isMidi = false; // undo is never a midi event. - h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __nColumn, + h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __fColumn, __nRow, __selectedPatternNumber, __oldLength, @@ -552,7 +552,7 @@ class SE_addOrDeleteNoteAction : public QUndoCommand { //qDebug() << "Add Note Redo " ; HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __nColumn, + h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __fColumn, __nRow, __selectedPatternNumber, __oldLength, @@ -570,7 +570,7 @@ class SE_addOrDeleteNoteAction : public QUndoCommand __isDelete ); } private: - int __nColumn; + double __fColumn; int __nRow; int __selectedPatternNumber; int __oldLength; @@ -630,9 +630,9 @@ class SE_deselectAndOverwriteNotesAction : public QUndoCommand class SE_addNoteOffAction : public QUndoCommand { public: - SE_addNoteOffAction( int nColumn, int nRow, int selectedPatternNumber, bool isDelete ){ - setText( QObject::tr( "Add NOTE_OFF note ( %1, %2 )" ).arg( nColumn ).arg( nRow ) ); - __nColumn = nColumn; + SE_addNoteOffAction( double fColumn, int nRow, int selectedPatternNumber, bool isDelete ){ + setText( QObject::tr( "Add NOTE_OFF note ( %1, %2 )" ).arg( fColumn ).arg( nRow ) ); + __fColumn = fColumn; __nRow = nRow; __selectedPatternNumber = selectedPatternNumber; __isDelete = isDelete; @@ -640,15 +640,45 @@ class SE_addNoteOffAction : public QUndoCommand virtual void undo() { HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __nColumn, __nRow, __selectedPatternNumber, -1, 0.8f, 0.5f, 0.5f, 0.0, 0, 0, 1.0f, false, false, false, true, !__isDelete ) ; + h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __fColumn, + __nRow, + __selectedPatternNumber, + -1, + 0.8f, + 0.5f, + 0.5f, + 0.0, + 0, //TODO 0 4 ?! divbase... + 4, + 1.0f, + false, + false, + false, + true, + !__isDelete ) ; } virtual void redo() { HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __nColumn, __nRow, __selectedPatternNumber, -1, 0.8f, 0.5f, 0.5f, 0.0, 0, 0, 1.0f, false, false, false, true, __isDelete ); + h2app->getPatternEditorPanel()->getDrumPatternEditor()->addOrDeleteNoteAction( __fColumn, + __nRow, + __selectedPatternNumber, + -1, + 0.8f, + 0.5f, + 0.5f, + 0.0, + 0, //TODO 0 4 ?! divbase... + 4, + 1.0f, + false, + false, + false, + true, + __isDelete ); } private: - int __nColumn; + float __fColumn; int __nRow; int __selectedPatternNumber; bool __isDelete; @@ -657,13 +687,13 @@ class SE_addNoteOffAction : public QUndoCommand class SE_moveNoteAction : public QUndoCommand { public: - SE_moveNoteAction( int nOldPosition, int nOldInstrument, int nPattern, int nNewPosition, int nNewInstrument, - H2Core::Note *pNote ) + SE_moveNoteAction( double fOldPosition, int nOldInstrument, int nPattern, double fNewPosition, + int nNewInstrument, H2Core::Note *pNote ) { - m_nOldPosition = nOldPosition; + m_fOldPosition = fOldPosition; m_nOldInstrument = nOldInstrument; m_nPattern = nPattern; - m_nNewPosition = nNewPosition; + m_fNewPosition = fNewPosition; m_nNewInstrument = nNewInstrument; m_pNote = new H2Core::Note( pNote ); } @@ -676,22 +706,22 @@ class SE_moveNoteAction : public QUndoCommand virtual void undo() { HydrogenApp::get_instance()->getPatternEditorPanel()->getDrumPatternEditor() - ->moveNoteAction( m_nNewPosition, m_nNewInstrument, m_nPattern, - m_nOldPosition, m_nOldInstrument, m_pNote ); + ->moveNoteAction( m_fNewPosition, m_nNewInstrument, m_nPattern, + m_fOldPosition, m_nOldInstrument, m_pNote ); } virtual void redo() { HydrogenApp::get_instance()->getPatternEditorPanel()->getDrumPatternEditor() - ->moveNoteAction( m_nOldPosition, m_nOldInstrument, m_nPattern, - m_nNewPosition, m_nNewInstrument, m_pNote ); + ->moveNoteAction( m_fOldPosition, m_nOldInstrument, m_nPattern, + m_fNewPosition, m_nNewInstrument, m_pNote ); } private: - int m_nOldPosition; + double m_fOldPosition; int m_nOldInstrument; int m_nPattern; - int m_nNewPosition; + double m_fNewPosition; int m_nNewInstrument; H2Core::Note *m_pNote; }; @@ -699,9 +729,9 @@ class SE_moveNoteAction : public QUndoCommand class SE_editNoteLenghtAction : public QUndoCommand { public: - SE_editNoteLenghtAction( int nColumn, int nRealColumn, int row, int length, int oldLength, int selectedPatternNumber ){ + SE_editNoteLenghtAction( double fColumn, int nRealColumn, int row, double length, double oldLength, int selectedPatternNumber ){ setText( QObject::tr( "Change note length" ) ); - __nColumn = nColumn; + __fColumn = fColumn; __nRealColumn = nRealColumn; __row = row; __length = length; @@ -712,20 +742,20 @@ class SE_editNoteLenghtAction : public QUndoCommand { //qDebug() << "Change note length Undo "; HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->editNoteLengthAction( __nColumn, __nRealColumn, __row, __oldLength, __selectedPatternNumber ); + h2app->getPatternEditorPanel()->getDrumPatternEditor()->editNoteLengthAction( __fColumn, __nRealColumn, __row, __oldLength, __selectedPatternNumber ); } virtual void redo() { //qDebug() << "Change note length Redo " ; HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->editNoteLengthAction( __nColumn, __nRealColumn, __row, __length, __selectedPatternNumber ); + h2app->getPatternEditorPanel()->getDrumPatternEditor()->editNoteLengthAction( __fColumn, __nRealColumn, __row, __length, __selectedPatternNumber ); } private: - int __nColumn; + double __fColumn; int __nRealColumn; int __row; - int __oldLength; - int __length; + double __oldLength; + double __length; int __selectedPatternNumber; }; @@ -830,9 +860,11 @@ class SE_pasteNotesPatternEditorAction : public QUndoCommand class SE_fillNotesRightClickAction : public QUndoCommand { public: - SE_fillNotesRightClickAction( QStringList notePositions, int nSelectedInstrument, int selectedPatternNumber ){ + //SE_fillNotesRightClickAction( QStringList notePositions, int nSelectedInstrument, int selectedPatternNumber ){ + SE_fillNotesRightClickAction( std::vector notePositions, int nSelectedInstrument, int selectedPatternNumber ){ + setText( QObject::tr( "Fill notes" ) ); - __notePositions = notePositions; + m_notePositions = notePositions; __nSelectedInstrument= nSelectedInstrument; __selectedPatternNumber = selectedPatternNumber; } @@ -840,16 +872,18 @@ class SE_fillNotesRightClickAction : public QUndoCommand { //qDebug() << "fill notes Undo "; HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->functionFillNotesUndoAction( __notePositions, __nSelectedInstrument, __selectedPatternNumber ); + h2app->getPatternEditorPanel()->getDrumPatternEditor()->functionFillNotesUndoAction( m_notePositions, + __nSelectedInstrument, __selectedPatternNumber ); } virtual void redo() { //qDebug() << "fill notes Redo " ; HydrogenApp* h2app = HydrogenApp::get_instance(); - h2app->getPatternEditorPanel()->getDrumPatternEditor()->functionFillNotesRedoAction( __notePositions, __nSelectedInstrument, __selectedPatternNumber ); + h2app->getPatternEditorPanel()->getDrumPatternEditor()->functionFillNotesRedoAction( m_notePositions, + __nSelectedInstrument, __selectedPatternNumber ); } private: - QStringList __notePositions; + std::vector m_notePositions; int __nSelectedInstrument; int __selectedPatternNumber; }; @@ -1315,7 +1349,7 @@ class SE_editNotePropertiesVolumeAction : public QUndoCommand { public: - SE_editNotePropertiesVolumeAction( int undoColumn, + SE_editNotePropertiesVolumeAction( double undoColumn, QString mode, int nSelectedPatternNumber, int nSelectedInstrument, @@ -1393,7 +1427,7 @@ class SE_editNotePropertiesVolumeAction : public QUndoCommand private: - int __undoColumn; + double __undoColumn; QString __mode; int __nSelectedPatternNumber; int __nSelectedInstrument;