//--------------------------------------------------------------------------------------- void ChordEngraver::add_note(ImoStaffObj* pSO, GmoShape* pStaffObjShape) { m_numNotesMissing--; ImoNote* pNote = dynamic_cast<ImoNote*>(pSO); GmoShapeNote* pNoteShape = dynamic_cast<GmoShapeNote*>(pStaffObjShape); int posOnStaff = pNoteShape->get_pos_on_staff(); if (m_notes.size() == 0) { ChordNoteData* pData = LOMSE_NEW ChordNoteData(pNote, pNoteShape, posOnStaff, m_iInstr); m_notes.push_back(pData); m_pBaseNoteData = pData; m_stemWidth = tenths_to_logical(LOMSE_STEM_THICKNESS); } else { //keep notes sorted by pitch DiatonicPitch newPitch(pNote->get_step(), pNote->get_octave()); std::list<ChordNoteData*>::iterator it; for (it = m_notes.begin(); it != m_notes.end(); ++it) { DiatonicPitch curPitch((*it)->pNote->get_step(), (*it)->pNote->get_octave()); if (newPitch < curPitch) { ChordNoteData* pData = LOMSE_NEW ChordNoteData(pNote, pNoteShape, posOnStaff, m_iInstr); m_notes.insert(it, 1, pData); return; } } ChordNoteData* pData = LOMSE_NEW ChordNoteData(pNote, pNoteShape, posOnStaff, m_iInstr); m_notes.push_back(pData); } }
TEST_FIXTURE(GmoShapeTestFixture, Composite_LockRecomputesBounds2) { Document doc(m_libraryScope); ImoNote* pNote = static_cast<ImoNote*>(ImFactory::inject(k_imo_note, &doc)); pNote->set_step(0); pNote->set_octave(4); pNote->set_notated_accidentals(k_flat); pNote->set_note_type(k_whole); ScoreMeter meter(1, 1, 180.0f); ShapesStorage storage; NoteEngraver engraver(m_libraryScope, &meter, &storage, 0, 0); GmoShapeNote* pShape = dynamic_cast<GmoShapeNote*>(engraver.create_shape(pNote, k_clef_F4, UPoint(10.0f, 15.0f)) ); pShape->unlock(); CHECK( pShape->is_locked() == false ); USize size = pShape->get_size(); GmoShapeNotehead* pNH = pShape->get_notehead_shape(); GmoShapeAccidentals* pAcc = pShape->get_accidentals_shape(); USize shift(200.0f, 300.0f); pNH->shift_origin(shift); pShape->lock(); CHECK( pShape->is_locked() == true ); CHECK( pShape->get_size().width == size.width + shift.width ); CHECK( pShape->get_origin().x == min(pAcc->get_origin().x, pNH->get_origin().x) ); CHECK( pShape->get_origin().y == min(pAcc->get_origin().y, pNH->get_origin().y) ); delete pNote; delete pShape; }
TEST_FIXTURE(ScoreAlgorithmsTestFixture, find_and_classify_1) { //inserted note starts and ends at same time than existing note // 1 case: nrT == t (full) // (clef G)(n e4 e v1)(n f4 e v1)(n g4 e v1) // 0--------32 Document doc(m_libraryScope); doc.from_string("(score (vers 2.0)(instrument (musicData " "(clef G)(n e4 e v1)(n f4 e v1)(n g4 e v1)" ")))"); ImoScore* pScore = static_cast<ImoScore*>( doc.get_imodoc()->get_content_item(0) ); list<OverlappedNoteRest*> overlaps = ScoreAlgorithms::find_and_classify_overlapped_noterests_at( pScore, 0, 1, 0.0, 32.0); OverlappedNoteRest* pOV = overlaps.front(); ImoNote* pNote = static_cast<ImoNote*>(pOV->pNR); CHECK( overlaps.size() == 1 ); CHECK( pNote != NULL ); CHECK( pNote->get_fpitch() == FPitch("e4") ); CHECK( pOV->type == k_overlap_full ); CHECK( is_equal_time(pOV->overlap, 32.0) ); delete_overlaps(overlaps); }
TEST_FIXTURE(ScoreAlgorithmsTestFixture, find_and_classify_5) { //inserted note starts before and ends at same time than existing note // (clef G)(n e4 e v1)(n f4 e v1)(n g4 e v1) // 24----------56 Document doc(m_libraryScope); doc.from_string("(score (vers 2.0)(instrument (musicData " "(clef G)(n e4 e v1)(n f4 e v1)(n g4 e v1)" ")))"); ImoScore* pScore = static_cast<ImoScore*>( doc.get_imodoc()->get_content_item(0) ); list<OverlappedNoteRest*> overlaps = ScoreAlgorithms::find_and_classify_overlapped_noterests_at( pScore, 0, 1, 24.0, 32.0); CHECK( overlaps.size() == 2 ); list<OverlappedNoteRest*>::const_iterator it = overlaps.begin(); OverlappedNoteRest* pOV = *it; ImoNote* pNote = static_cast<ImoNote*>(pOV->pNR); CHECK( pNote != NULL ); CHECK( pNote->get_fpitch() == FPitch("e4") ); CHECK( pOV->type == k_overlap_at_end ); CHECK( is_equal_time(pOV->overlap, 8.0) ); ++it; pOV = *it; pNote = static_cast<ImoNote*>(pOV->pNR); CHECK( pNote != NULL ); CHECK( pNote->get_fpitch() == FPitch("f4") ); CHECK( pOV->type == k_overlap_at_start ); CHECK( is_equal_time(pOV->overlap, 24.0) ); delete_overlaps(overlaps); }
TEST_FIXTURE(NoteEngraverTestFixture, NoteEngraver_HeadStemFlagTwoDots) { Document doc(m_libraryScope); ImoNote* pNote = static_cast<ImoNote*>(ImFactory::inject(k_imo_note, &doc)); pNote->set_notated_pitch(k_step_C, k_octave_4, k_no_accidentals); pNote->set_note_type(k_eighth); pNote->set_dots(2); ScoreMeter meter(nullptr, 1, 1, 180.0f); ShapesStorage storage; NoteEngraver engraver(m_libraryScope, &meter, &storage, 0, 0); GmoShapeNote* pShape = dynamic_cast<GmoShapeNote*>(engraver.create_shape(pNote, k_clef_F4, UPoint(10.0f, 15.0f)) ); CHECK( pShape != nullptr ); CHECK( pShape->is_shape_note() == true ); std::list<GmoShape*>& components = pShape->get_components(); std::list<GmoShape*>::iterator it = components.begin(); CHECK( components.size() == 5 ); CHECK( (*it)->is_shape_notehead() ); ++it; CHECK( (*it)->is_shape_dot() ); ++it; CHECK( (*it)->is_shape_dot() ); ++it; CHECK( (*it)->is_shape_stem() ); ++it; CHECK( (*it)->is_shape_flag() ); delete pNote; delete pShape; }
//======================================================================================= // SelectionValidator implementation //======================================================================================= bool SelectionValidator::is_valid_to_add_tie(SelectionSet* pSelection, ImoNote** ppStartNote, ImoNote** ppEndNote) { //Returns TRUE if current selection is valid for adding a tie. //If valid, returns pointers to start and end notes, if not NULL parameters received //Conditions to be valid: // 1. The first note found can be tied to next one // 2. If condition 1 is true, the next note must also be in the selection bool fValid = false; ImoNote* pStart = NULL; ImoNote* pEnd = NULL; ColStaffObjs* pCollection = pSelection->get_staffobjs_collection(); if (pCollection == NULL) return false; ColStaffObjsIterator it; for (it = pCollection->begin(); it != pCollection->end(); ++it) { ImoObj* pImo = (*it)->imo_object(); if (pImo->is_note()) { if (!pStart) { //first note found. Verify if it can be tied to next pStart = static_cast<ImoNote*>(pImo); if (!pStart->is_tied_next()) { ImoScore* pScore = pStart->get_score(); ColStaffObjs* pCol = pScore->get_staffobjs_table(); pEnd = ScoreAlgorithms::find_possible_end_of_tie(pCol, pStart); } } else { //Start note processed. verify if end note is also in the selection if (pEnd && pEnd->get_id() == pImo->get_id()) { fValid = true; //ok. End note is in the selection break; } } } } if (fValid) { if (ppStartNote) *ppStartNote = pStart; if (ppEndNote) *ppEndNote = pEnd; return true; } else return false; }
TEST_FIXTURE(ScoreAlgorithmsTestFixture, find_noterest_1) { //note exists and starts at same timepos Document doc(m_libraryScope); doc.from_string("(score (vers 2.0)(instrument (musicData " "(clef G)(n e4 e v1)(n f4 e v1)(n g4 e v1)" ")))"); ImoScore* pScore = static_cast<ImoScore*>( doc.get_imodoc()->get_content_item(0) ); ImoNote* pNote = static_cast<ImoNote*>( ScoreAlgorithms::find_noterest_at(pScore, 0, 1, 0.0) ); CHECK( pNote != NULL ); CHECK( pNote->get_fpitch() == FPitch("e4") ); }
//--------------------------------------------------------------------------------------- ImoObj* Linker::add_staffobj(ImoStaffObj* pSO) { if (m_pParent) { if (m_pParent->is_music_data()) m_pParent->append_child_imo(pSO); else if (m_pParent->is_chord() && pSO->is_note()) { ImoChord* pChord = static_cast<ImoChord*>(m_pParent); ImoNote* pNote = static_cast<ImoNote*>(pSO); pNote->include_in_relation(m_pDoc, pChord); return NULL; } } return pSO; }
TEST_FIXTURE(GmoShapeTestFixture, Composite_IsLocked) { Document doc(m_libraryScope); ImoNote* pNote = static_cast<ImoNote*>(ImFactory::inject(k_imo_note, &doc)); pNote->set_step(0); pNote->set_octave(4); pNote->set_notated_accidentals(k_no_accidentals); pNote->set_note_type(k_whole); ScoreMeter meter(1, 1, 180.0f); ShapesStorage storage; NoteEngraver engraver(m_libraryScope, &meter, &storage, 0, 0); GmoShapeNote* pShape = dynamic_cast<GmoShapeNote*>(engraver.create_shape(pNote, k_clef_F4, UPoint(10.0f, 15.0f)) ); CHECK( pShape != NULL ); CHECK( pShape->is_locked() == true ); delete pNote; delete pShape; }
//--------------------------------------------------------------------------------------- void ChordEngraver::add_stem_and_flag() { // Rules (taken from ref. [1] Music Publishers' Association) // // p.3, b) ... When there is more than one note head on a stem,as in a chord, the // stem length is calculated from the note closest to the end of the stem. if (!has_stem()) return; //the stem length must be increased with the distance from min note to max note. GmoShapeNote* pMinNoteShape = m_notes.front()->pNoteShape; GmoShapeNote* pMaxNoteShape = m_notes.back()->pNoteShape; GmoShapeNotehead* pMinHeadShape = pMinNoteShape->get_notehead_shape(); GmoShapeNotehead* pMaxHeadShape = pMaxNoteShape->get_notehead_shape(); LUnits uExtraLenght = pMinHeadShape->get_top() - pMaxHeadShape->get_top(); //stem and the flag will computed for max/min note, depending on stem direction ChordNoteData* pData = (is_stem_down() ? m_notes.front() : m_notes.back()); ImoNote* pNote = pData->pNote; GmoShapeNote* pNoteShape = pData->pNoteShape; int instr = pData->iInstr; int staff = pNote->get_staff(); int nPosOnStaff = pData->posOnStaff; //but the stem is added to base note shape GmoShapeNote* pBaseNoteShape = m_pBaseNoteData->pNoteShape; Tenths length = NoteEngraver::get_standard_stem_length(nPosOnStaff, is_stem_down()); if (!is_chord_beamed() && length < 35.0f && m_noteType > k_eighth) length = 35.0f; // 3.5 spaces bool fShortFlag = (length < 35.0f); LUnits stemLength = tenths_to_logical(length) + uExtraLenght; StemFlagEngraver engrv(m_libraryScope, m_pMeter, pNote, instr, staff); pNoteShape = (is_stem_down() ? pMaxNoteShape : pMinNoteShape); engrv.add_stem_flag(pNoteShape, pBaseNoteShape, m_noteType, is_stem_down(), has_flag(), fShortFlag, stemLength, m_color); }
//--------------------------------------------------------------------------------------- bool SelectionValidator::is_valid_for_toggle_stem(SelectionSet* pSelection) { //Returns TRUE if current selection is valid to toggle stems. //It is valid if there is at least a note with stem ColStaffObjs* pCollection = pSelection->get_staffobjs_collection(); if (pCollection == NULL) return false; ColStaffObjsIterator it; for (it = pCollection->begin(); it != pCollection->end(); ++it) { ImoObj* pImo = (*it)->imo_object(); if (pImo->is_note()) { ImoNote* pNote = static_cast<ImoNote*>(pImo); if (pNote->get_note_type() > k_whole && !pNote->is_in_chord() && pNote->get_stem_direction() != k_stem_none) return true; } } return false; }
//--------------------------------------------------------------------------------------- void AutoClef::find_staves_needing_clef() { //An staff needs clef if it has pitched notes before finding a clef. //This method saves data for each staff in vectors m_fNeedsClef, m_pAt, //m_maxPitch, m_minPitch int staves = m_pCursor->get_num_staves(); int stavesWithNotes = 0; m_fNeedsClef.assign(staves, false); m_pAt.assign(staves, (ImoStaffObj*)nullptr); m_maxPitch.assign(staves, k_undefined_fpitch); m_minPitch.assign(staves, k_undefined_fpitch); m_numNotes.assign(staves, 0); vector<bool> fHasNotes; //true if staff i has pitched notes fHasNotes.assign(staves, false); while(!m_pCursor->is_end()) { ImoStaffObj* pSO = m_pCursor->get_staffobj(); int iStaff = m_pCursor->staff_index(); if (m_pAt[iStaff] == nullptr) m_pAt[iStaff] = pSO; if (pSO->is_note()) { ImoNote* pN = static_cast<ImoNote*>(pSO); if (!m_fNeedsClef[iStaff] && !fHasNotes[iStaff]) { if (pN->is_pitch_defined()) { int clefType = m_pCursor->get_applicable_clef_type(); if (clefType == k_clef_undefined) m_fNeedsClef[iStaff] = true; fHasNotes[iStaff] = true; } } if (m_fNeedsClef[iStaff]) { if (pN->is_pitch_defined()) { FPitch fp = pN->get_fpitch(); if (m_maxPitch[iStaff] == k_undefined_fpitch || m_maxPitch[iStaff] < fp) m_maxPitch[iStaff] = fp; if (m_minPitch[iStaff] == k_undefined_fpitch || m_minPitch[iStaff] > fp) m_minPitch[iStaff] = fp; ++m_numNotes[iStaff]; if (m_numNotes[iStaff] == 10) ++stavesWithNotes; if (stavesWithNotes == staves) break; } } } m_pCursor->move_next(); } }
//--------------------------------------------------------------------------------------- void SystemLayouter::engrave_attached_objects(ImoStaffObj* pSO, GmoShape* pMainShape, int iInstr, int iStaff, int iSystem, int iCol, int iLine, ImoInstrument* pInstr) { //rel objs if (pSO->get_num_relations() > 0) { ImoRelations* pRelObjs = pSO->get_relations(); int size = pRelObjs->get_num_items(); for (int i=0; i < size; ++i) { ImoRelObj* pRO = pRelObjs->get_item(i); if (!pRO->is_chord()) { if (pSO == pRO->get_start_object()) m_pShapesCreator->start_engraving_relobj(pRO, pSO, pMainShape, iInstr, iStaff, iSystem, iCol, iLine, pInstr); else if (pSO == pRO->get_end_object()) { SystemLayouter* pSysLyt = m_pScoreLyt->get_system_layouter(iSystem); LUnits prologWidth( pSysLyt->get_prolog_width() ); m_pShapesCreator->finish_engraving_relobj(pRO, pSO, pMainShape, iInstr, iStaff, iSystem, iCol, iLine, prologWidth, pInstr); add_relobjs_shapes_to_model(pRO, GmoShape::k_layer_aux_objs); } else m_pShapesCreator->continue_engraving_relobj(pRO, pSO, pMainShape, iInstr, iStaff, iSystem, iCol, iLine, pInstr); } } } //aux objs if (pSO->get_num_attachments() > 0) { ImoAttachments* pAuxObjs = pSO->get_attachments(); int size = pAuxObjs->get_num_items(); for (int i=0; i < size; ++i) { ImoAuxObj* pAO = static_cast<ImoAuxObj*>( pAuxObjs->get_item(i) ); if (pAO->is_lyric()) { if (pSO->is_note()) { ImoLyric* pLyric = static_cast<ImoLyric*>(pAO); ImoNote* pNote = static_cast<ImoNote*>(pSO); //build hash code from instrument, number & voice. stringstream tag; tag << iInstr << "-" << pLyric->get_number() << "-" << pNote->get_voice(); GmoShapeNote* pNoteShape = static_cast<GmoShapeNote*>(pMainShape); if (pLyric->is_start_of_relation()) m_pShapesCreator->start_engraving_auxrelobj(pLyric, pSO, tag.str(), pNoteShape, iInstr, iStaff, iSystem, iCol, iLine, pInstr); else if (pLyric->is_end_of_relation()) { SystemLayouter* pSysLyt = m_pScoreLyt->get_system_layouter(iSystem); LUnits prologWidth( pSysLyt->get_prolog_width() ); m_pShapesCreator->finish_engraving_auxrelobj(pLyric, pSO, tag.str(), pNoteShape, iInstr, iStaff, iSystem, iCol, iLine, prologWidth, pInstr); add_relauxobjs_shapes_to_model(tag.str(), GmoShape::k_layer_aux_objs); } else m_pShapesCreator->continue_engraving_auxrelobj(pLyric, pSO, tag.str(), pNoteShape, iInstr, iStaff, iSystem, iCol, iLine, pInstr); } } else { GmoShape* pAuxShape = m_pShapesCreator->create_auxobj_shape(pAO, iInstr, iStaff, pMainShape); // pMainShape->accept_link_from(pAuxShape); add_aux_shape_to_model(pAuxShape, GmoShape::k_layer_aux_objs, iSystem, iCol, iInstr); m_yMax = max(m_yMax, pAuxShape->get_bottom()); } } } }
//--------------------------------------------------------------------------------------- void ChordEngraver::decide_on_stem_direction() { // Rules (taken from ref. [2] www.coloradocollege.edu) // // a) Two notes in chord: // a1. If the interval above the middle line is greater than the interval below // the middle line: downward stems. i.e. (a4,d5) (f4,f5) (a4,g5) // ==> (MaxNotePos + MinNotePos)/2 > MiddleLinePos // // a2. If the interval below the middle line is greater than the interval above // the middle line: upward stems. i.e. (e4,c5)(g4,c5)(d4,e5) // // a3. If the two notes are at the same distance from the middle line: stem can // go in either direction, but most engravers prefer downward stems. // i.e. (g4.d5)(a4,c5) // // // b) More than two notes in chord: // // b1. If the interval of the highest note above the middle line is greater than // the interval of the lowest note below the middle line: downward stems. // ==> same than a1 // // b2. If the interval of the lowest note below the middle line is greater than // the interval of the highest note above the middle line: upward stems. // ==> same than a2 // // b3. If the highest and the lowest notes are the same distance from the middle // line use the majority rule to determine stem direction: If the majority of // the notes are above the middle: downward stems. Else: upward stems. // ==> Mean(NotePos) > MiddleLinePos -> downward // // Additional rules (mine): // c) chords without stem (notes longer than half notes): // c1. layout as if stem was up ImoNote* pBaseNote = get_base_note(); m_noteType = pBaseNote->get_note_type(); int stemType = pBaseNote->get_stem_direction(); m_fHasStem = m_noteType >= k_half && stemType != k_stem_none; m_fHasFlag = m_fHasStem && m_noteType > k_quarter && !is_chord_beamed(); if (m_noteType < k_half) m_fStemDown = false; //c1. layout as if stem up else if (stemType == k_stem_up) m_fStemDown = false; //force stem up else if (stemType == k_stem_down) m_fStemDown = true; //force stem down else if (stemType == k_stem_none) m_fStemDown = false; //c1. layout as if stem up else if (stemType == k_stem_default) //as decided by program { //majority rule int weight = 0; std::list<ChordNoteData*>::iterator it; for(it=m_notes.begin(); it != m_notes.end(); ++it) weight += (*it)->posOnStaff; m_fStemDown = ( weight >= 6 * int(m_notes.size()) ); } }