diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 2d4901680b..da5d719c9d 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -13,6 +13,14 @@ StepMania 5.0 alpha 1a | 2012021? * [WheelBase, WheelItemBase] Turned WheelItemDataType into a Lua type, made WheelBase:GetSelectedType() return the enum instead of a number. [AJ] +2012/02/08 +---------- +* [ScreenEdit] Added support for editing keysounds. Use the Area Menu (Enter) + to either modify what sounds play on that row, or even remove a sound from + the list. Any sounds assigned to empty notes will automatically become + an AutoKeysound, and any AutoKeysounds that have their sound removed will + become empty notes. [Wolfman2000] + 2012/02/08 ---------- * [ScreenOptionsMaster] Added StepsRowLayoutType metric. diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index c0466c5fe5..8e39955549 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -971,6 +971,7 @@ Min BPM=Min Specified BPM Mines=Mines Mirror Player 1 to 2=Mirror P1's notes to P2 (routine only) Mirror Player 2 to 1=Mirror P2's notes to P1 (routine only) +Modify Keysounds at current beat=Modify keysounds at current beat MoveRandomToEnd=Random At End More Options=More Options MovieColorDepth=Movie Color @@ -1020,6 +1021,7 @@ RefreshRate=Refresh Rate Reload Songs=Reload Songs/Courses Remove=Remove Remove Change=Remove Change +Remove Keysound=Remove Keysound Rename=Rename Reset To Defaults=Reset Options To Default Values Revert from disk=Revert from disk @@ -1369,6 +1371,11 @@ Help=Help Info=Info Routine Player=Player +Track %d=Track %d +None=None +New Sound=New Sound +Enter New Keysound File=Enter the name of the new keysound file.\nPlease make sure it is spelt right. + [ScreenEditMenu] HeaderText=Edit Song/Steps HeaderSubText=Make your songs perfect diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index b135f3835a..e0a4098c27 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -3896,6 +3896,10 @@ ColorDisabled=color("1,1,1,1") RowInitCommand=halign,0.5;valign,0.5;zoom,0.8;x,75;y,45;shadowlength,1 OptionRowNormalMetricsGroup="OptionRowMiniMenuEditHelp" +[ScreenMiniMenuKeysoundTrack] +Fallback="ScreenMiniMenu" +ShowFooter=false + [OptionRowMiniMenuEditHelp] # Help menu ( Keys & Stuff ) Fallback="OptionRowMiniMenu" diff --git a/src/NoteData.cpp b/src/NoteData.cpp index 96429530a0..89698d79f2 100644 --- a/src/NoteData.cpp +++ b/src/NoteData.cpp @@ -462,7 +462,8 @@ int NoteData::GetLastRow() const bool NoteData::IsTap(const TapNote &tn, const int row) const { return (tn.type != TapNote::empty && tn.type != TapNote::mine - && tn.type != TapNote::lift && tn.type != TapNote::fake + && tn.type != TapNote::lift && tn.type != TapNote::fake + && tn.type != TapNote::autoKeysound && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); } @@ -556,6 +557,7 @@ bool NoteData::RowNeedsAtLeastSimultaneousPresses( int iMinSimultaneousPresses, case TapNote::empty: case TapNote::fake: case TapNote::lift: // you don't "press" on a lift. + case TapNote::autoKeysound: continue; // skip these types - they don't count default: break; } @@ -610,9 +612,10 @@ int NoteData::GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex, int for( int t=0; t= iMinTaps ) diff --git a/src/NoteDataWithScoring.cpp b/src/NoteDataWithScoring.cpp index 3ce551b064..7cf2f2ce84 100644 --- a/src/NoteDataWithScoring.cpp +++ b/src/NoteDataWithScoring.cpp @@ -153,7 +153,10 @@ int LastTapNoteScoreTrack( const NoteData &in, unsigned iRow, PlayerNumber pn ) { /* Skip empty tracks and mines */ const TapNote &tn = in.GetTapNote( t, iRow ); - if( tn.type == TapNote::empty || tn.type == TapNote::mine || tn.type == TapNote::fake ) + if (tn.type == TapNote::empty || + tn.type == TapNote::mine || + tn.type == TapNote::fake || + tn.type == TapNote::autoKeysound) continue; if( tn.pn != PLAYER_INVALID && tn.pn != pn && pn != PLAYER_INVALID ) continue; @@ -187,7 +190,10 @@ int MinTapNoteScoreTrack( const NoteData &in, unsigned iRow, PlayerNumber pn ) { // Skip empty tracks and mines const TapNote &tn = in.GetTapNote( t, iRow ); - if( tn.type == TapNote::empty || tn.type == TapNote::mine || tn.type == TapNote::fake ) + if (tn.type == TapNote::empty || + tn.type == TapNote::mine || + tn.type == TapNote::fake || + tn.type == TapNote::autoKeysound) continue; if( tn.pn != PLAYER_INVALID && tn.pn != pn && pn != PLAYER_INVALID ) continue; @@ -250,7 +256,10 @@ TapNoteScore NoteDataWithScoring::MinTapNoteScore( const NoteData &in, unsigned { // Ignore mines (and fake arrows), or the score will always be TNS_None. const TapNote &tn = in.GetTapNote( t, row ); - if( tn.type == TapNote::empty || tn.type == TapNote::mine || tn.type == TapNote::fake ) + if (tn.type == TapNote::empty || + tn.type == TapNote::mine || + tn.type == TapNote::fake || + tn.type == TapNote::autoKeysound) continue; score = min( score, tn.result.tns ); } diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index b7ac1940f4..7e1ff5516c 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -684,6 +684,7 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing void NoteDisplay::DrawActor( const TapNote& tn, Actor* pActor, NotePart part, int iCol, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fColorScale, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar ) { + if (tn.type == TapNote::autoKeysound && !GAMESTATE->m_bInStepEditor) return; if( fYOffset < fDrawDistanceAfterTargetsPixels || fYOffset > fDrawDistanceBeforeTargetsPixels ) return; const float fY = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYOffset, fReverseOffsetPixels ); diff --git a/src/Player.cpp b/src/Player.cpp index e4f0a1e536..c913e1b46d 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1653,7 +1653,8 @@ int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool const TapNote &tn = begin->second; if (!m_Timing->IsJudgableAtRow( begin->first )) break; - if( tn.type == TapNote::empty ) + // unsure if autoKeysounds should be excluded. -Wolfman2000 + if( tn.type == TapNote::empty || tn.type == TapNote::autoKeysound ) break; if( !bAllowGraded && tn.result.tns != TNS_None ) break; @@ -2238,9 +2239,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b break; case TapNote::attack: if( !bRelease && fSecondsFromExact <= GetWindowSeconds(TW_Attack) && !pTN->result.bHidden ) - score = TNS_W2; // sentinel + score = AllowW1() ? TNS_W1 : TNS_W2; // sentinel break; - case TapNote::hold_head: // oh wow, this was causing the trigger before the hold heads // bug. (It was fNoteOffset > 0.f before) -DaisuMaster @@ -2791,7 +2791,9 @@ void Player::UpdateJudgedRows() for( int iTrack = 0; iTrack < m_NoteData.GetNumTracks(); ++iTrack ) { const TapNote &tn = m_NoteData.GetTapNote( iTrack, iRow ); - if( tn.type == TapNote::empty || tn.type == TapNote::mine ) continue; + if (tn.type == TapNote::empty || + tn.type == TapNote::mine || + tn.type == TapNote::autoKeysound) continue; SetJudgment( tn.result.tns, iTrack, tn.result.fTapNoteOffset ); } } @@ -2973,8 +2975,11 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now ) // check to see if there's a note at the crossed row if( m_pPlayerState->m_PlayerController != PC_HUMAN ) { - if(tn.type != TapNote::empty && tn.type != TapNote::fake && tn.result.tns == TNS_None - && this->m_Timing->IsJudgableAtRow(iRow) ) + if (tn.type != TapNote::empty && + tn.type != TapNote::fake && + tn.type != TapNote::autoKeysound && + tn.result.tns == TNS_None && + this->m_Timing->IsJudgableAtRow(iRow) ) { Step( iTrack, iRow, now, false, false ); if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) @@ -2984,6 +2989,19 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now ) } } } + + // handle autokeysounds here (if not in the editor). + if (!GAMESTATE->m_bInStepEditor) + { + for (int t = 0; t < m_NoteData.GetNumTracks(); ++t) + { + const TapNote &tap = m_NoteData.GetTapNote(t, iRow); + if (tap.type == TapNote::autoKeysound) + { + PlayKeysound(tap, TNS_None); + } + } + } } } @@ -3141,7 +3159,10 @@ void Player::HandleTapRowScore( unsigned row ) { const TapNote &tn = m_NoteData.GetTapNote( track, row ); // Mines cannot be handled here. - if( tn.type == TapNote::empty || tn.type == TapNote::fake || tn.type == TapNote::mine ) + if (tn.type == TapNote::empty || + tn.type == TapNote::fake || + tn.type == TapNote::mine || + tn.type == TapNote::autoKeysound) continue; if( m_pPrimaryScoreKeeper ) m_pPrimaryScoreKeeper->HandleTapScore( tn ); diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 9cf82c20ed..b26c52f721 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -74,6 +74,8 @@ AutoScreenMessage( SM_BackFromInsertStepAttackPlayerOptions ); AutoScreenMessage( SM_BackFromInsertCourseAttack ); AutoScreenMessage( SM_BackFromInsertCourseAttackPlayerOptions ); AutoScreenMessage( SM_BackFromCourseModeMenu ); +AutoScreenMessage( SM_BackFromKeysoundTrack ); +AutoScreenMessage( SM_BackFromNewKeysound ); AutoScreenMessage( SM_DoRevertToLastSave ); AutoScreenMessage( SM_DoRevertFromDisk ); AutoScreenMessage( SM_BackFromTimingDataInformation ); @@ -520,6 +522,10 @@ static MenuDef g_EditHelp( // fill this in dynamically ); +static MenuDef g_KeysoundTrack( + "ScreenMiniMenuKeysoundTrack" + ); // fill this in dynamically + static MenuDef g_MainMenu( "ScreenMiniMenuMainMenu", MenuRowDef( ScreenEdit::play_whole_song, "Play whole song", true, EditMode_Practice, true, true, 0, NULL ), @@ -605,7 +611,9 @@ static MenuDef g_AreaMenu( MenuRowDef(ScreenEdit::last_second_at_beat, "Designate last second at current beat", true, EditMode_Full, true, true, 0, NULL ), MenuRowDef( ScreenEdit::undo, "Undo", true, EditMode_Practice, true, true, 0, NULL ), MenuRowDef(ScreenEdit::clear_clipboard, "Clear clipboard", true, - EditMode_Practice, true, true, 0, NULL ) + EditMode_Practice, true, true, 0, NULL ), + MenuRowDef(ScreenEdit::modify_keysounds_at_row, "Modify Keysounds at current beat", + true, EditMode_Full, true, true, 0, NULL) ); @@ -2927,6 +2935,8 @@ void ScreenEdit::ScrollTo( float fDestinationBeat ) m_soundChangeLine.Play(); } +static LocalizedString NEW_KEYSOUND_FILE("ScreenEdit", "Enter New Keysound File"); + void ScreenEdit::HandleMessage( const Message &msg ) { if( msg == "Judgment" ) @@ -3172,6 +3182,94 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) ASSERT( GAMESTATE->m_pCurCourse ); } } + else if (SM == SM_BackFromKeysoundTrack) + { + const int track = ScreenMiniMenu::s_iLastRowCode; + const int tracks = m_NoteDataEdit.GetNumTracks(); + const int row = this->GetRow(); + unsigned int sound = ScreenMiniMenu::s_viLastAnswers[track]; + vector &kses = m_pSong->m_vsKeysoundFile; + + if (track < tracks) + { + if (sound == kses.size()) + { + // create a new sound (filename), point it. + // if it's empty, make it an auto keysound. + ScreenTextEntry::TextEntry(SM_BackFromNewKeysound, NEW_KEYSOUND_FILE, "", 64); + return; + } + const TapNote &oldNote = m_NoteDataEdit.GetTapNote(track, row); + TapNote newNote = oldNote; // need to lose the const. not feeling like casting. + if (sound < kses.size()) + { + // set note at this row to use this keysound file. + // if it's empty, make it an auto keysound. + newNote.iKeysoundIndex = sound; + if (newNote.type == TapNote::empty) + { + newNote.type = TapNote::autoKeysound; // keysounds need something non empty. + } + } + else // sound > kses.size() + { + // remove the sound. if it's an auto keysound, make it empty. + newNote.iKeysoundIndex = -1; + if (newNote.type == TapNote::autoKeysound) + { + newNote.type = TapNote::empty; // autoKeysound with no sound is pointless. + } + } + m_NoteDataEdit.SetTapNote(track, row, newNote); + } + else if (track == tracks) + { + kses.erase(kses.begin() + sound); + // TODO: Make the following a part of NoteData? + for (int t = 0; t < tracks; ++t) + { + FOREACH_NONEMPTY_ROW_IN_TRACK(m_NoteDataEdit, t, r) + { + const TapNote &oldNote = m_NoteDataEdit.GetTapNote(t, r); + TapNote newNote = oldNote; // need to lose the const. not feeling like casting. + if (newNote.iKeysoundIndex == static_cast(sound)) + { + newNote.iKeysoundIndex = -1; + if (newNote.type == TapNote::autoKeysound) + newNote.type = TapNote::empty; + } + else if (newNote.iKeysoundIndex > static_cast(sound)) + newNote.iKeysoundIndex--; + + m_NoteDataEdit.SetTapNote(t, r, newNote); + } + } + } + SetDirty(true); + } + else if (SM == SM_BackFromNewKeysound && !ScreenTextEntry::s_bCancelledLast) + { + RString answer = ScreenTextEntry::s_sLastAnswer; + const int track = ScreenMiniMenu::s_iLastRowCode; // still keeps the same value. + const int row = this->GetRow(); + const TapNote &oldNote = m_NoteDataEdit.GetTapNote(track, row); + TapNote newNote = oldNote; // need to lose the const. not feeling like casting. + vector &kses = m_pSong->m_vsKeysoundFile; + unsigned pos = find(kses.begin(), kses.end(), answer) - kses.begin(); + if (pos == kses.size()) + { + newNote.iKeysoundIndex = kses.size(); + kses.push_back(answer); + } + else + { + newNote.iKeysoundIndex = pos; + } + if (newNote.type == TapNote::empty) + newNote.type = TapNote::autoKeysound; // keysounds need something non empty. + m_NoteDataEdit.SetTapNote(track, row, newNote); + SetDirty(true); + } else if( SM == SM_BackFromOptions ) { // The options may have changed the note skin. @@ -4463,6 +4561,11 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns m_Clipboard.ClearAll(); break; } + case modify_keysounds_at_row: + { + this->DoKeyboardTrackMenu(); + break; + } }; if( bSaveUndo ) @@ -5215,6 +5318,41 @@ static RString GetDeviceButtonsLocalized( const vector &veb, const M return s; } +static LocalizedString TRACK_NUM("ScreenEdit", "Track %d"); +static LocalizedString NO_KEYSND("ScreenEdit", "None"); +static LocalizedString NEWKEYSND("ScreenEdit", "New Sound"); + +void ScreenEdit::DoKeyboardTrackMenu() +{ + g_KeysoundTrack.rows.clear(); + vector &kses = m_pSong->m_vsKeysoundFile; + + vector choices; + FOREACH(RString, kses, ks) + { + choices.push_back(*ks); + } + choices.push_back(NEWKEYSND); + choices.push_back(NO_KEYSND); + int numKeysounds = kses.size(); + for (int i = 0; i < m_NoteDataEdit.GetNumTracks(); ++i) + { + const TapNote &tn = m_NoteDataEdit.GetTapNote(i, this->GetRow()); + int keyIndex = tn.iKeysoundIndex; + if (keyIndex == -1) + { + keyIndex = numKeysounds; + } + + g_KeysoundTrack.rows.push_back(MenuRowDef(i, ssprintf(TRACK_NUM.GetValue(), i + 1), + true, EditMode_Full, false, false, keyIndex, choices)); + } + g_KeysoundTrack.rows.push_back(MenuRowDef(m_NoteDataEdit.GetNumTracks(), "Remove Keysound", + true, EditMode_Full, false, false, 0, kses)); + + EditMiniMenu(&g_KeysoundTrack, SM_BackFromKeysoundTrack); +} + void ScreenEdit::DoHelp() { g_EditHelp.rows.clear(); diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index ecac3673dc..0de36ea16a 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -248,6 +248,9 @@ protected: float GetMaximumBeatForNewNote() const; // don't allow Down key to go past this beat. float GetMaximumBeatForMoving() const; // don't allow Down key to go past this beat. + /** @brief Display the keyboard track menu for the current row. */ + void DoKeyboardTrackMenu(); + void DoHelp(); /** @brief Display the TimingData menu for editing song and step timing. */ @@ -435,6 +438,7 @@ public: last_second_at_beat, undo, clear_clipboard, /**< Clear the clipboards. */ + modify_keysounds_at_row, /**< Modify the keysounds at this row. */ NUM_AREA_MENU_CHOICES }; void HandleAlterMenuChoice(AlterMenuChoice c, diff --git a/src/ScreenMiniMenu.h b/src/ScreenMiniMenu.h index b0ef6176e1..f7e8167e0a 100644 --- a/src/ScreenMiniMenu.h +++ b/src/ScreenMiniMenu.h @@ -50,6 +50,19 @@ struct MenuRowDef PUSH(c23);PUSH(c23);PUSH(c24);PUSH(c25); #undef PUSH } + + MenuRowDef(int r, RString n, bool e, EditMode s, + bool bTT, bool bTI, int d, vector options): + iRowCode(r), sName(n), bEnabled(e), pfnEnabled(NULL), + emShowIn(s), iDefaultChoice(d), choices(), + bThemeTitle(bTT), bThemeItems(bTI) + { + FOREACH(RString, options, str) + { + if (*str != "") choices.push_back(*str); + } + } + MenuRowDef( int r, RString n, bool e, EditMode s, bool bTT, bool bTI, int d, const char *c0=NULL, const char *c1=NULL, const char *c2=NULL, const char *c3=NULL, diff --git a/src/SongManager.cpp b/src/SongManager.cpp index eb2571deb0..f669c52b68 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -258,6 +258,7 @@ void SongManager::LoadStepManiaSongDir( RString sDir, LoadingWindow *ld ) FOREACH_CONST( RString, arrayGroupDirs, s ) // foreach dir in /Songs/ { RString sGroupDirName = *s; + // TODO: If this check fails, log a warning instead of crashing. SanityCheckGroupDir(sDir+sGroupDirName); // Find all Song folders in this group directory diff --git a/src/TimingData.cpp b/src/TimingData.cpp index 0c14ec400d..71bb51d804 100644 --- a/src/TimingData.cpp +++ b/src/TimingData.cpp @@ -38,9 +38,6 @@ void TimingData::Clear() vector &vSegs = m_avpTimingSegments[tst]; for( unsigned i = 0; i < vSegs.size(); ++i ) { -#if defined(DEBUG) - LOG->Trace( "TimingData::Clear(): deleting %p", vSegs[i] ); -#endif SAFE_DELETE( vSegs[i] ); }