//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include #include #include #include "hlfaceposer.h" #include "PhonemeEditor.h" #include "PhonemeEditorColors.h" #include "snd_audio_source.h" #include "snd_wave_source.h" #include "ifaceposersound.h" #include "choreowidgetdrawhelper.h" #include "mxBitmapButton.h" #include "phonemeproperties.h" #include "tier2/riff.h" #include "StudioModel.h" #include "expressions.h" #include "expclass.h" #include "InputProperties.h" #include "phonemeextractor/PhonemeExtractor.h" #include "PhonemeConverter.h" #include "choreoevent.h" #include "choreoscene.h" #include "ChoreoView.h" #include "FileSystem.h" #include "UtlBuffer.h" #include "AudioWaveOutput.h" #include "StudioModel.h" #include "viewerSettings.h" #include "ControlPanel.h" #include "faceposer_models.h" #include "tier1/strtools.h" #include "tabwindow.h" #include "MatSysWin.h" #include "soundflags.h" #include "mdlviewer.h" #include "filesystem_init.h" #include "WaveBrowser.h" #include "tier2/p4helpers.h" #include "vstdlib/random.h" extern IUniformRandomStream *random; float SnapTime( float input, float granularity ); #define MODE_TAB_OFFSET 20 // 10x magnification #define MAX_TIME_ZOOM 1000 // 10% per step #define TIME_ZOOM_STEP 2 #define SCRUBBER_HEIGHT 15 #define TAG_TOP ( 25 + SCRUBBER_HEIGHT ) #define TAG_BOTTOM ( TAG_TOP + 20 ) #define PLENTY_OF_TIME 99999.9 #define MINIMUM_WORD_GAP 0.02f #define MINIMUM_PHONEME_GAP 0.01f #define DEFAULT_WORD_LENGTH 0.25f #define DEFAULT_PHONEME_LENGTH 0.1f #define WORD_DATA_EXTENSION ".txt" // #define ITEM_GAP_EPSILON 0.0025f struct PhonemeEditorColor { int color_number; // For readability int mode_number; // -1 for all Color root_color; Color gray_color; // if mode is wrong... }; static PhonemeEditorColor g_PEColors[ NUM_COLORS ] = { { COLOR_PHONEME_BACKGROUND, -1, Color( 240, 240, 220 ) }, { COLOR_PHONEME_TEXT, -1, Color( 63, 63, 63 ) }, { COLOR_PHONEME_LIGHTTEXT, 0, Color( 180, 180, 120 ) }, { COLOR_PHONEME_PLAYBACKTICK, 0, Color( 255, 0, 0 ) }, { COLOR_PHONEME_WAVDATA, 0, Color( 128, 31, 63 ) }, { COLOR_PHONEME_TIMELINE, 0, Color( 31, 31, 127 ) }, { COLOR_PHONEME_TIMELINE_MAJORTICK, 0, Color( 200, 200, 255 ) }, { COLOR_PHONEME_TIMELINE_MINORTICK, 0, Color( 210, 210, 240 ) }, { COLOR_PHONEME_EXTRACTION_RESULT_FAIL, 0, Color( 180, 180, 0 ) }, { COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS, 0, Color( 100, 180, 100 ) }, { COLOR_PHONEME_EXTRACTION_RESULT_ERROR, 0, Color( 255, 31, 31 ) }, { COLOR_PHONEME_EXTRACTION_RESULT_OTHER, 0, Color( 63, 63, 63 ) }, { COLOR_PHONEME_TAG_BORDER, 0, Color( 160, 100, 100 ) }, { COLOR_PHONEME_TAG_BORDER_SELECTED, 0, Color( 255, 40, 60 ) }, { COLOR_PHONEME_TAG_FILLER_NORMAL, 0, Color( 210, 210, 190 ) }, { COLOR_PHONEME_TAG_SELECTED, 0, Color( 200, 130, 130 ) }, { COLOR_PHONEME_TAG_TEXT, 0, Color( 63, 63, 63 ) }, { COLOR_PHONEME_TAG_TEXT_SELECTED, 0, Color( 250, 250, 250 ) }, { COLOR_PHONEME_WAV_ENDPOINT, 0, Color( 0, 0, 200 ) }, { COLOR_PHONEME_AB, 0, Color( 63, 190, 210 ) }, { COLOR_PHONEME_AB_LINE, 0, Color( 31, 150, 180 ) }, { COLOR_PHONEME_AB_TEXT, 0, Color( 100, 120, 120 ) }, { COLOR_PHONEME_ACTIVE_BORDER, 0, Color( 150, 240, 180 ) }, { COLOR_PHONEME_SELECTED_BORDER, 0, Color( 255, 0, 0 ) }, { COLOR_PHONEME_TIMING_TAG, -1, Color( 0, 100, 200 ) }, { COLOR_PHONEME_EMPHASIS_BG, 1, Color( 230, 230, 200 ) }, { COLOR_PHONEME_EMPHASIS_BG_STRONG, 1, Color( 163, 201, 239 ) }, { COLOR_PHONEME_EMPHASIS_BG_WEAK, 1, Color( 237, 239, 163 ) }, { COLOR_PHONEME_EMPHASIS_BORDER, 1, Color( 200, 200, 200 ) }, { COLOR_PHONEME_EMPHASIS_LINECOLOR, 1, Color( 0, 0, 255 ) }, { COLOR_PHONEME_EMPHASIS_DOTCOLOR, 1, Color( 0, 0, 255 ) }, { COLOR_PHONEME_EMPHASIS_DOTCOLOR_SELECTED, 1, Color( 240, 80, 20 ) }, { COLOR_PHONEME_EMPHASIS_TEXT, 1, Color( 0, 0, 0 ) }, { COLOR_PHONEME_EMPHASIS_MIDLINE, 1, Color( 100, 150, 200 ) }, }; struct Extractor { PE_APITYPE apitype; CSysModule *module; IPhonemeExtractor *extractor; }; CUtlVector< Extractor > g_Extractors; bool DoesExtractorExistFor( PE_APITYPE type ) { for ( int i=0; i < g_Extractors.Count(); i++ ) { if ( g_Extractors[i].apitype == type ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Implements the RIFF i/o interface on stdio //----------------------------------------------------------------------------- class StdIOReadBinary : public IFileReadBinary { public: FileHandle_t open( const char *pFileName ) { return filesystem->Open( pFileName, "rb" ); } int read( void *pOutput, int size, FileHandle_t file ) { if ( !file ) return 0; return filesystem->Read( pOutput, size, file ); } void seek( FileHandle_t file, int pos ) { if ( !file ) return; filesystem->Seek( file, pos, FILESYSTEM_SEEK_HEAD ); } unsigned int tell( FileHandle_t file ) { if ( !file ) return 0; return filesystem->Tell( file ); } unsigned int size( FileHandle_t file ) { if ( !file ) return 0; return filesystem->Size( file ); } void close( FileHandle_t file ) { if ( !file ) return; filesystem->Close( file ); } }; class StdIOWriteBinary : public IFileWriteBinary { public: FileHandle_t create( const char *pFileName ) { MakeFileWriteable( pFileName ); return filesystem->Open( pFileName, "wb" ); } int write( void *pData, int size, FileHandle_t file ) { return filesystem->Write( pData, size, file ); } void close( FileHandle_t file ) { filesystem->Close( file ); } void seek( FileHandle_t file, int pos ) { filesystem->Seek( file, pos, FILESYSTEM_SEEK_HEAD ); } unsigned int tell( FileHandle_t file ) { return filesystem->Tell( file ); } }; // Interface objects static StdIOWriteBinary io_out; static StdIOReadBinary io_in; class CPhonemeModeTab : public CTabWindow { public: typedef CTabWindow BaseClass; CPhonemeModeTab( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) : CTabWindow( parent, x, y, w, h, id, style ) { SetInverted( true ); } virtual void ShowRightClickMenu( int mx, int my ) { // Nothing } void Init( void ) { add( "Phonemes" ); add( "Emphasis" ); select( 0 ); } }; PhonemeEditor * g_pPhonemeEditor = 0; //----------------------------------------------------------------------------- // Purpose: // Input : *parent - //----------------------------------------------------------------------------- PhonemeEditor::PhonemeEditor( mxWindow *parent ) : IFacePoserToolWindow( "PhonemeEditor", "Phoneme Editor" ), mxWindow( parent, 0, 0, 0, 0 ) { SetAutoProcess( false ); m_flPlaybackRate = 1.0f; m_flScrub = 0.0f; m_flScrubTarget = 0.0f; m_CurrentMode = MODE_PHONEMES; Emphasis_Init(); SetupPhonemeEditorColors(); m_bRedoPending = false; m_nUndoLevel = 0; m_bWordsActive = false; m_pWaveFile = NULL; m_pMixer = NULL; m_pEvent = NULL; m_nClickX = 0; m_WorkFile.m_bDirty = false; m_WorkFile.m_szWaveFile[ 0 ] = 0; m_WorkFile.m_szWorkingFile[ 0 ] = 0; m_WorkFile.m_szBasePath[ 0 ] = 0; m_nTickHeight = 20; m_flPixelsPerSecond = 500.0f; m_nTimeZoom = 100; m_nTimeZoomStep = TIME_ZOOM_STEP; m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_PHONEME_SCROLL, mxScrollbar::Horizontal ); m_hPrevCursor = 0; m_nStartX = 0; m_nStartY = 0; m_nLastX = 0; m_nLastY = 0; m_nDragType = DRAGTYPE_NONE; SetClickedPhoneme( -1, -1 ); m_nSelection[ 0 ] = m_nSelection[ 1 ] = 0; m_bSelectionActive = false; m_nSelectedPhonemeCount = 0; m_nSelectedWordCount = 0; m_btnSave = new mxButton( this, 0, 0, 16, 16, "Save (Ctrl+S)", IDC_SAVE_LINGUISTIC ); m_btnRedoPhonemeExtraction = new mxButton( this, 38, 14, 80, 16, "Re-extract (Ctrl+R)", IDC_REDO_PHONEMEEXTRACTION ); m_btnLoad = new mxButton( this, 0, 0, 0, 0, "Load (Ctrl+O)", IDC_LOADWAVEFILE ); m_btnPlay = new mxButton( this, 0, 0, 16, 16, "Play (Spacebar)", IDC_PLAYBUTTON ); m_pPlaybackRate = new mxSlider( this, 0, 0, 16, 16, IDC_PLAYBACKRATE ); m_pPlaybackRate->setRange( 0.0, 2.0, 40 ); m_pPlaybackRate->setValue( m_flPlaybackRate ); m_pModeTab = new CPhonemeModeTab( this, 0, 0, 500, 20, IDC_MODE_TAB ); m_pModeTab->Init(); m_nLastExtractionResult = SR_RESULT_NORESULT; ClearDragLimit(); SetSuffix( " - Normal" ); m_flScrubberTimeOffset = 0.0f; LoadPhonemeConverters(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::OnDelete() { if ( m_pWaveFile ) { char fn[ 512 ]; Q_snprintf( fn, sizeof( fn ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); filesystem->RemoveFile( fn, "GAME" ); } delete m_pWaveFile; m_pWaveFile = NULL; m_Tags.Reset(); m_TagsExt.Reset(); UnloadPhonemeConverters(); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::CanClose() { if ( !GetDirty() ) return true; int retval = mxMessageBox( this, va( "Save current changes to %s", m_WorkFile.m_szWaveFile ), "Phoneme Editor", MX_MB_QUESTION | MX_MB_YESNOCANCEL ); // Cancel if ( retval == 2 ) { return false; } // Yes if ( retval == 0 ) { CommitChanges(); } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- PhonemeEditor::~PhonemeEditor( void ) { } void PhonemeEditor::SetupPhonemeEditorColors( void ) { int i; for ( i = 0; i < NUM_COLORS; i++ ) { PhonemeEditorColor *p = &g_PEColors[ i ]; Assert( p->color_number == i ); if ( p->mode_number == -1 ) { p->gray_color = p->root_color; } else { Color bgColor = g_PEColors[ COLOR_PHONEME_BACKGROUND ].root_color; int bgr, bgg, bgb; bgr = bgColor.r(); bgg = bgColor.g(); bgb = bgColor.b(); int r, g, b; r = p->root_color.r(); g = p->root_color.g(); b = p->root_color.b(); int avg = ( r + g + b ) / 3; int bgavg = ( bgr + bgg + bgb ) / 3; // Bias toward bg color avg += ( bgavg - avg ) / 2.5; p->gray_color = Color( avg, avg, avg ); } } } Color PhonemeEditor::PEColor( int colornum ) { Color clr = Color( 0, 0, 0 ); if ( colornum < 0 || colornum >= NUM_COLORS ) { Assert( 0 ); return clr; } PhonemeEditorColor *p = &g_PEColors[ colornum ]; if ( p->mode_number == -1 ) { return p->root_color; } int modenum = (int)GetMode(); if ( p->mode_number == modenum ) { return p->root_color; } return p->gray_color; } void PhonemeEditor::EditWord( CWordTag *pWord, bool positionDialog /*= false*/ ) { if ( !pWord ) { Con_Printf( "PhonemeEditor::EditWord: pWord == NULL\n" ); return; } CInputParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Edit Word" ); strcpy( params.m_szPrompt, "Current Word:" ); strcpy( params.m_szInputText, pWord->GetWord() ); params.m_nLeft = -1; params.m_nTop = -1; params.m_bPositionDialog = positionDialog; if ( params.m_bPositionDialog ) { RECT rcWord; GetWordRect( pWord, rcWord ); // Convert to screen coords POINT pt; pt.x = rcWord.left; pt.y = rcWord.top; ClientToScreen( (HWND)getHandle(), &pt ); params.m_nLeft = pt.x; params.m_nTop = pt.y; } int iret = InputProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } // Validate parameters if ( CSentence::CountWords( params.m_szInputText ) != 1 ) { Con_ErrorPrintf( "Edit word: %s has multiple words in it!!!\n", params.m_szInputText ); return; } SetFocus( (HWND)getHandle() ); SetDirty( true ); PushUndo(); // Set the word and clear out the phonemes // ->m_nPhonemeCode = TextToPhoneme( params.m_szName ); pWord->SetWord( params.m_szInputText ); PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pPhoneme - // positionDialog - //----------------------------------------------------------------------------- void PhonemeEditor::EditPhoneme( CPhonemeTag *pPhoneme, bool positionDialog /*= false*/ ) { if ( !pPhoneme ) { Con_Printf( "PhonemeEditor::EditPhoneme: pPhoneme == NULL\n" ); return; } CPhonemeParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); strcpy( params.m_szName, ConvertPhoneme( pPhoneme->GetPhonemeCode() ) ); params.m_nLeft = -1; params.m_nTop = -1; params.m_bPositionDialog = positionDialog; if ( params.m_bPositionDialog ) { RECT rcPhoneme; GetPhonemeRect( pPhoneme, rcPhoneme ); // Convert to screen coords POINT pt; pt.x = rcPhoneme.left; pt.y = rcPhoneme.top; ClientToScreen( (HWND)getHandle(), &pt ); params.m_nLeft = pt.x; params.m_nTop = pt.y; } int iret = PhonemeProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } SetDirty( true ); PushUndo(); pPhoneme->SetPhonemeCode( TextToPhoneme( params.m_szName ) ); PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditPhoneme( void ) { if ( GetMode() != MODE_PHONEMES ) return; CPhonemeTag *pPhoneme = GetClickedPhoneme(); if ( !pPhoneme ) return; EditPhoneme( pPhoneme, false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditWord( void ) { if ( GetMode() != MODE_PHONEMES ) return; CWordTag *pWord = GetClickedWord(); if ( !pWord ) return; EditWord( pWord, false ); } //----------------------------------------------------------------------------- // Purpose: // Input : dragtype - // startx - // cursor - //----------------------------------------------------------------------------- void PhonemeEditor::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) { m_nDragType = dragtype; m_nStartX = startx; m_nLastX = startx; m_nStartY = starty; m_nLastY = starty; if ( m_hPrevCursor ) { SetCursor( m_hPrevCursor ); m_hPrevCursor = NULL; } m_hPrevCursor = SetCursor( cursor ); m_FocusRects.Purge(); RECT rc; GetWorkspaceRect( rc ); RECT rcStart; rcStart.left = startx; rcStart.right = startx; bool addrect = true; switch ( dragtype ) { default: case DRAGTYPE_SCRUBBER: { RECT rcScrub; GetScrubHandleRect( rcScrub, true ); rcStart = rcScrub; rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; rcStart.right = rcStart.left; rcStart.bottom = h2() - 18 - MODE_TAB_OFFSET; } break; case DRAGTYPE_EMPHASIS_SELECT: { RECT rcEmphasis; Emphasis_GetRect( rc, rcEmphasis ); rcStart.top = starty; rcStart.bottom = starty; } break; case DRAGTYPE_EMPHASIS_MOVE: { SetDirty( true ); PushUndo(); Emphasis_MouseDrag( startx, starty ); m_Tags.Resort(); addrect = false; } break; case DRAGTYPE_SELECTSAMPLES: case DRAGTYPE_MOVESELECTIONSTART: case DRAGTYPE_MOVESELECTIONEND: rcStart.top = rc.top; rcStart.bottom = rc.bottom; break; case DRAGTYPE_MOVESELECTION: { rcStart.top = rc.top; rcStart.bottom = rc.bottom; // Compute left/right pixels for selection rcStart.left = GetPixelForSample( m_nSelection[ 0 ] ); rcStart.right = GetPixelForSample( m_nSelection[ 1 ] ); } break; case DRAGTYPE_PHONEME: { GetPhonemeTrayTopBottom( rcStart ); m_bWordsActive = false; } break; case DRAGTYPE_WORD: { GetWordTrayTopBottom( rcStart ); m_bWordsActive = true; } break; case DRAGTYPE_MOVEWORD: { TraverseWords( &PhonemeEditor::ITER_AddFocusRectSelectedWords, 0.0f ); addrect = false; m_bWordsActive = true; } break; case DRAGTYPE_MOVEPHONEME: { TraversePhonemes( &PhonemeEditor::ITER_AddFocusRectSelectedPhonemes, 0.0f ); addrect = false; m_bWordsActive = false; } break; case DRAGTYPE_EVENTTAG_MOVE: { rcStart.top = TAG_TOP; rcStart.bottom = TAG_BOTTOM; rcStart.left -= 10; rcStart.right += 10; } break; } if ( addrect ) { AddFocusRect( rcStart ); } DrawFocusRect( "start" ); SetDragLimit( m_nDragType ); } //----------------------------------------------------------------------------- // Purpose: // Input : *event - // Output : int //----------------------------------------------------------------------------- int PhonemeEditor::handleEvent( mxEvent *event ) { MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); int iret = 0; if ( HandleToolEvent( event ) ) { return iret; } switch ( event->event ) { case mxEvent::Action: { iret = 1; switch ( event->action ) { case IDC_EXPORT_SENTENCE: { OnExport(); } break; case IDC_IMPORT_SENTENCE: { OnImport(); } break; case IDC_PLAYBACKRATE: { m_flPlaybackRate = m_pPlaybackRate->getValue(); redraw(); } break; case IDC_MODE_TAB: { // The mode changed, so reset stuff here EditorMode newMode = (EditorMode)m_pModeTab->getSelectedIndex(); bool needpaint = ( m_CurrentMode != newMode ); m_CurrentMode = newMode; if ( needpaint ) { switch ( GetMode() ) { default: case MODE_PHONEMES: SetSuffix( " - Normal" ); break; case MODE_EMPHASIS: SetSuffix( " - Emphasis Track" ); break; } OnModeChanged(); redraw(); } } break; case IDC_EMPHASIS_DELETE: Emphasis_Delete(); break; case IDC_EMPHASIS_DESELECT: Emphasis_DeselectAll(); break; case IDC_EMPHASIS_SELECTALL: Emphasis_SelectAll(); break; case IDC_API_SAPI: OnSAPI(); break; case IDC_API_LIPSINC: OnLipSinc(); break; case IDC_PLAYBUTTON: Play(); break; case IDC_UNDO: Undo(); break; case IDC_REDO: Redo(); break; case IDC_CLEARUNDO: ClearUndo(); break; case IDC_ADDTAG: AddTag(); break; case IDC_DELETETAG: DeleteTag(); break; case IDC_COMMITEXTRACTED: CommitExtracted(); SetFocus( (HWND)getHandle() ); break; case IDC_CLEAREXTRACTED: ClearExtracted(); break; case IDC_SEPARATEPHONEMES: SeparatePhonemes(); break; case IDC_SNAPPHONEMES: SnapPhonemes(); break; case IDC_SEPARATEWORDS: SeparateWords(); break; case IDC_SNAPWORDS: SnapWords(); break; case IDC_EDITWORDLIST: EditWordList(); break; case IDC_EDIT_PHONEME: EditPhoneme(); break; case IDC_EDIT_WORD: EditWord(); break; case IDC_EDIT_INSERTPHONEMEBEFORE: EditInsertPhonemeBefore(); break; case IDC_EDIT_INSERTPHONEMEAFTER: EditInsertPhonemeAfter(); break; case IDC_EDIT_INSERTWORDBEFORE: EditInsertWordBefore(); break; case IDC_EDIT_INSERTWORDAFTER: EditInsertWordAfter(); break; case IDC_EDIT_DELETEPHONEME: EditDeletePhoneme(); break; case IDC_EDIT_DELETEWORD: EditDeleteWord(); break; case IDC_EDIT_INSERTFIRSTPHONEMEOFWORD: EditInsertFirstPhonemeOfWord(); break; case IDC_PHONEME_PLAY_ORIG: { StopPlayback(); if ( m_pWaveFile ) { // Make sure phonemes are loaded FacePoser_EnsurePhonemesLoaded(); sound->PlaySound( m_pWaveFile, VOL_NORM, &m_pMixer ); } } break; case IDC_PHONEME_SCROLL: if (event->modifiers == SB_THUMBTRACK) { MoveTimeSliderToPos( event->height ); } else if ( event->modifiers == SB_PAGEUP ) { int offset = m_pHorzScrollBar->getValue(); offset -= 10; offset = max( offset, m_pHorzScrollBar->getMinValue() ); MoveTimeSliderToPos( offset ); } else if ( event->modifiers == SB_PAGEDOWN ) { int offset = m_pHorzScrollBar->getValue(); offset += 10; offset = min( offset, m_pHorzScrollBar->getMaxValue() ); MoveTimeSliderToPos( offset ); } break; case IDC_REDO_PHONEMEEXTRACTION: if ( m_Tags.m_Words.Count() <= 0 ) { // This calls redo LISET if some words are actually entered EditWordList(); } else { RedoPhonemeExtraction(); } SetFocus( (HWND)getHandle() ); break; case IDC_REDO_PHONEMEEXTRACTION_SELECTION: { RedoPhonemeExtractionSelected(); } SetFocus( (HWND)getHandle() ); break; case IDC_DESELECT: Deselect(); redraw(); break; case IDC_PLAY_EDITED: PlayEditedWave( false ); SetFocus( (HWND)getHandle() ); break; case IDC_PLAY_EDITED_SELECTION: PlayEditedWave( true ); SetFocus( (HWND)getHandle() ); break; case IDC_SAVE_LINGUISTIC: CommitChanges(); SetFocus( (HWND)getHandle() ); break; case IDC_LOADWAVEFILE: LoadWaveFile(); SetFocus( (HWND)getHandle() ); break; case IDC_CANCELPLAYBACK: StopPlayback(); SetFocus( (HWND)getHandle() ); break; case IDC_SELECT_WORDSRIGHT: SelectWords( true ); break; case IDC_SELECT_WORDSLEFT: SelectWords( false ); break; case IDC_SELECT_PHONEMESRIGHT: SelectPhonemes( true ); break; case IDC_SELECT_PHONEMESLEFT: SelectPhonemes( false ); break; case IDC_DESELECT_PHONEMESANDWORDS: DeselectPhonemes(); DeselectWords(); redraw(); break; case IDC_CLEANUP: CleanupWordsAndPhonemes( true ); redraw(); break; case IDC_REALIGNPHONEMES: RealignPhonemesToWords( true ); redraw(); break; case IDC_REALIGNWORDS: RealignWordsToPhonemes( true ); redraw(); break; case IDC_TOGGLE_VOICEDUCK: OnToggleVoiceDuck(); break; } if ( iret == 1 ) { SetActiveTool( this ); SetFocus( (HWND)getHandle() ); } } break; case mxEvent::MouseWheeled: { // Zoom time in / out if ( event->height > 0 ) { m_nTimeZoom = min( m_nTimeZoom + m_nTimeZoomStep, MAX_TIME_ZOOM ); } else { m_nTimeZoom = max( m_nTimeZoom - m_nTimeZoomStep, m_nTimeZoomStep ); } RepositionHSlider(); iret = 1; } break; case mxEvent::Size: { int bw = 100; int x = 5; int by = h2() - 18 - MODE_TAB_OFFSET; m_pModeTab->setBounds( 0, h2() - MODE_TAB_OFFSET, w2(), MODE_TAB_OFFSET ); m_btnRedoPhonemeExtraction->setBounds( x, by, bw, 16 ); x += bw; m_btnSave->setBounds( x, by, bw, 16 ); x += bw; m_btnLoad->setBounds( x, by, bw, 16 ); x += bw; m_btnPlay->setBounds( x, by, bw, 16 ); x += bw; m_pPlaybackRate->setBounds( x, by, 100, 16 ); RepositionHSlider(); iret = 1; } break; case mxEvent::MouseDown: { iret = 1; CPhonemeTag *pt; CWordTag *wt; pt = GetPhonemeTagUnderMouse( (short)event->x, (short)event->y ); wt = GetWordTagUnderMouse( (short)event->x, (short)event->y ); bool ctrldown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; if ( event->buttons & mxEvent::MouseRightButton ) { RECT rc; GetWorkspaceRect( rc ); if ( IsMouseOverWordRow( (short)event->y ) ) { ShowWordMenu( wt, (short)event->x, (short)event->y ); } else if ( IsMouseOverPhonemeRow( (short)event->y ) ) { ShowPhonemeMenu( pt, (short)event->x, (short)event->y ); } else if ( IsMouseOverTagRow( (short)event->y ) ) { ShowTagMenu( (short)event->x, (short)event->y ); } else if ( IsMouseOverScrubArea( event ) ) { float t = GetTimeForPixel( (short)event->x ); ClampTimeToSelectionInterval( t ); SetScrubTime( t ); SetScrubTargetTime( t ); redraw(); } else { ShowContextMenu( (short)event->x, (short)event->y ); } return iret; } if ( m_nDragType == DRAGTYPE_NONE ) { CountSelected(); int type = IsMouseOverBoundary( event ); if ( IsMouseOverScrubArea( event ) ) { if ( IsMouseOverScrubHandle( event ) ) { StartDragging( DRAGTYPE_SCRUBBER, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); float t = GetTimeForPixel( (short)event->x ); m_flScrubberTimeOffset = m_flScrub - t; float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); t += m_flScrubberTimeOffset; ClampTimeToSelectionInterval( t ); SetScrubTime( t ); SetScrubTargetTime( t ); DrawScrubHandle(); iret = true; } else { float t = GetTimeForPixel( (short)event->x ); ClampTimeToSelectionInterval( t ); SetScrubTargetTime( t ); iret = true; } return iret; } else if ( GetMode() == MODE_EMPHASIS ) { CEmphasisSample *sample = Emphasis_GetSampleUnderMouse( event ); if ( sample ) { if ( shiftdown ) { sample->selected = !sample->selected; redraw(); } else if ( sample->selected ) { StartDragging( DRAGTYPE_EMPHASIS_MOVE, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); } else { if ( !shiftdown ) { Emphasis_DeselectAll(); redraw(); } StartDragging( DRAGTYPE_EMPHASIS_SELECT, (short)event->x, (short)event->y, NULL ); } return true; } else if ( ctrldown ) { // Add a sample point float t = GetTimeForPixel( (short)event->x ); RECT rcWork; GetWorkspaceRect( rcWork ); RECT rcEmphasis; Emphasis_GetRect( rcWork, rcEmphasis ); int eh = rcEmphasis.bottom - rcEmphasis.top; int dy = (short)event->y - rcEmphasis.top; CEmphasisSample sample; sample.time = t; Assert( eh >= 0 ); sample.value = (float)( dy ) / ( float ) eh; sample.value = 1.0f - clamp( sample.value, 0.0f, 1.0f ); sample.selected = false; Emphasis_AddSample( sample ); redraw(); return true; } else { if ( !shiftdown ) { Emphasis_DeselectAll(); redraw(); } StartDragging( DRAGTYPE_EMPHASIS_SELECT, (short)event->x, (short)event->y, NULL ); return true; } } else { if ( type == BOUNDARY_PHONEME && m_nSelectedPhonemeCount <= 1 ) { StartDragging( DRAGTYPE_PHONEME, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); return true; } else if ( type == BOUNDARY_WORD && m_nSelectedWordCount <= 1 ) { StartDragging( DRAGTYPE_WORD, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); return true; } else if ( IsMouseOverSamples( (short)event->x, (short)event->y ) ) { if ( !m_bSelectionActive ) { StartDragging( DRAGTYPE_SELECTSAMPLES, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); } else { // Either move, move edge if ctrl key is held, or deselect if ( IsMouseOverSelection( (short)event->x, (short)event->y ) ) { if ( IsMouseOverSelectionStartEdge( event ) ) { StartDragging( DRAGTYPE_MOVESELECTIONSTART, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); } else if ( IsMouseOverSelectionEndEdge( event ) ) { StartDragging( DRAGTYPE_MOVESELECTIONEND, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); } else { if ( shiftdown ) { StartDragging( DRAGTYPE_MOVESELECTION, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); } } } else { Deselect(); redraw(); return iret; } } return true; } } if ( IsMouseOverTag( (short)event->x, (short)event->y ) ) { StartDragging( DRAGTYPE_EVENTTAG_MOVE, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); return true; } else { if ( pt ) { // Can only move when holding down shift key if ( shiftdown ) { pt->m_bSelected = true; StartDragging( DRAGTYPE_MOVEPHONEME, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); } else { // toggle the selection pt->m_bSelected = !pt->m_bSelected; } m_bWordsActive = false; redraw(); return true; } else if ( wt ) { // Can only move when holding down shift key if ( shiftdown ) { wt->m_bSelected = true; StartDragging( DRAGTYPE_MOVEWORD, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); } else { // toggle the selection wt->m_bSelected = !wt->m_bSelected; } m_bWordsActive = true; redraw(); return true; } else if ( type == BOUNDARY_NONE ) { DeselectPhonemes(); DeselectWords(); redraw(); return true; } } } } break; case mxEvent::MouseMove: case mxEvent::MouseDrag: { OnMouseMove( event ); iret = 1; } break; case mxEvent::MouseUp: { if ( m_nDragType != DRAGTYPE_NONE ) { int mx = (short)event->x; LimitDrag( mx ); event->x = (short)mx; DrawFocusRect( "finish" ); if ( m_hPrevCursor ) { SetCursor( m_hPrevCursor ); m_hPrevCursor = 0; } switch ( m_nDragType ) { case DRAGTYPE_WORD: FinishWordMove( m_nStartX, (short)event->x ); break; case DRAGTYPE_PHONEME: FinishPhonemeMove( m_nStartX, (short)event->x ); break; case DRAGTYPE_SELECTSAMPLES: FinishSelect( m_nStartX, (short)event->x ); break; case DRAGTYPE_MOVESELECTION: FinishMoveSelection( m_nStartX, (short)event->x ); break; case DRAGTYPE_MOVESELECTIONSTART: FinishMoveSelectionStart( m_nStartX, (short)event->x ); break; case DRAGTYPE_MOVESELECTIONEND: FinishMoveSelectionEnd( m_nStartX, (short)event->x ); break; case DRAGTYPE_MOVEWORD: FinishWordDrag( m_nStartX, (short)event->x ); break; case DRAGTYPE_MOVEPHONEME: FinishPhonemeDrag( m_nStartX, (short)event->x ); break; case DRAGTYPE_EVENTTAG_MOVE: FinishEventTagDrag( m_nStartX, (short)event->x ); break; case DRAGTYPE_EMPHASIS_MOVE: { Emphasis_MouseDrag( (short)event->x, (short)event->y ); m_Tags.Resort(); PushRedo(); redraw(); } break; case DRAGTYPE_EMPHASIS_SELECT: { Emphasis_SelectPoints(); redraw(); } break; case DRAGTYPE_SCRUBBER: { float t = GetTimeForPixel( (short)event->x ); t += m_flScrubberTimeOffset; m_flScrubberTimeOffset = 0.0f; ClampTimeToSelectionInterval( t ); SetScrubTime( t ); SetScrubTargetTime( t ); sound->Flush(); DrawScrubHandle(); } break; default: break; } m_nDragType = DRAGTYPE_NONE; } iret = 1; } break; case mxEvent::KeyUp: { bool shiftDown = GetAsyncKeyState( VK_SHIFT ) ? true : false; bool ctrlDown = GetAsyncKeyState( VK_CONTROL ) ? true : false; switch( event->key ) { case VK_TAB: { int direction = shiftDown ? -1 : 1; SelectNextWord( direction ); } break; case VK_NEXT: case VK_PRIOR: { m_bWordsActive = event->key == VK_PRIOR ? true : false; redraw(); } break; case VK_UP: case VK_RETURN: if ( m_bWordsActive ) { if ( event->key == VK_UP || ctrlDown ) { CountSelected(); if ( m_nSelectedWordCount == 1 ) { // Find the selected one for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word || !word->m_bSelected ) continue; EditWord( word, true ); } } } } else { if ( event->key == VK_UP || ctrlDown ) { CountSelected(); if ( m_nSelectedPhonemeCount == 1 ) { // Find the selected one for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; if ( !phoneme->m_bSelected ) continue; EditPhoneme( phoneme, true ); } } } } } break; case VK_DELETE: if ( GetMode() == MODE_EMPHASIS ) { Emphasis_Delete(); } else { if ( m_bWordsActive ) { EditDeleteWord(); } else { EditDeletePhoneme(); } } break; case VK_INSERT: if ( m_bWordsActive ) { if ( shiftDown ) { EditInsertWordBefore(); } else { EditInsertWordAfter(); } } else { if ( shiftDown ) { EditInsertPhonemeBefore(); } else { EditInsertPhonemeAfter(); } } break; case VK_SPACE: if ( m_pWaveFile && sound->IsSoundPlaying( m_pMixer ) ) { Con_Printf( "Stopping playback\n" ); m_btnPlay->setLabel( "Play (Spacebar)" ); StopPlayback(); } else { Con_Printf( "Playing .wav\n" ); m_btnPlay->setLabel( "Stop[ (Spacebar)" ); PlayEditedWave( m_bSelectionActive ); } break; case VK_SHIFT: case VK_CONTROL: { // Force mouse move POINT pt; GetCursorPos( &pt ); SetCursorPos( pt.x, pt.y ); return 0; } break; case VK_ESCAPE: { // If playing sound, stop it, otherwise, deselect all if ( !StopPlayback() ) { Deselect(); DeselectPhonemes(); DeselectWords(); Emphasis_DeselectAll(); redraw(); } } break; case 'O': { if ( ctrlDown ) { LoadWaveFile(); } } break; case 'S': { if ( ctrlDown ) { CommitChanges(); } } break; case 'T': { if ( ctrlDown ) { // Edit sentence text EditWordList(); } } break; case 'G': { if ( ctrlDown ) { // Commit extraction CommitExtracted(); } } break; case 'R': { if ( ctrlDown ) { RedoPhonemeExtraction(); } } break; default: break; } SetFocus( (HWND)getHandle() ); iret = 1; } break; case mxEvent::KeyDown: { switch ( event->key ) { case 'Z': if ( GetAsyncKeyState( VK_CONTROL ) ) { Undo(); } break; case 'Y': if ( GetAsyncKeyState( VK_CONTROL ) ) { Redo(); } break; case VK_RIGHT: case VK_LEFT: { int direction = event->key == VK_LEFT ? -1 : 1; if ( !m_bWordsActive ) { if ( GetAsyncKeyState( VK_CONTROL ) ) { ExtendSelectedPhonemeEndTime( direction ); } else if ( GetAsyncKeyState( VK_SHIFT ) ) { ShiftSelectedPhoneme( direction ); } else { SelectNextPhoneme( direction ); } } else { if ( GetAsyncKeyState( VK_CONTROL ) ) { ExtendSelectedWordEndTime( direction ); } else if ( GetAsyncKeyState( VK_SHIFT ) ) { ShiftSelectedWord( direction ); } else { SelectNextWord( direction ); } } } break; case VK_RETURN: { } break; case VK_SHIFT: case VK_CONTROL: { // Force mouse move POINT pt; GetCursorPos( &pt ); //SetCursorPos( pt.x -1, pt.y ); SetCursorPos( pt.x, pt.y ); return 0; } break; default: break; } iret = 1; } break; } return iret; } void PhonemeEditor::DrawWords( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence& sentence, int type, bool showactive /* = true */ ) { float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; int ypos = rcWorkSpace.top + m_nTickHeight + 2; if ( type == 1 ) { ypos += m_nTickHeight + 5; } const char *fontName = "Arial"; bool drawselected; for ( int pass = 0; pass < 2 ; pass++ ) { drawselected = pass == 0 ? false : true; for (int k = 0; k < sentence.m_Words.Count(); k++) { CWordTag *word = sentence.m_Words[ k ]; if ( !word ) continue; if ( word->m_bSelected != drawselected ) continue; bool hasselectedphonemes = false; for ( int p = 0; p < word->m_Phonemes.Count() && !hasselectedphonemes; p++ ) { CPhonemeTag *t = word->m_Phonemes[ p ]; if ( t->m_bSelected ) { hasselectedphonemes = true; } } float t1 = word->m_flStartTime; float t2 = word->m_flEndTime; // Tag it float frac = ( t1 - starttime ) / ( endtime - starttime ); int xpos = ( int )( frac * rcWorkSpace.right ); if ( frac <= 0.0 ) xpos = 0; // Draw duration float frac2 = ( t2 - starttime ) / ( endtime - starttime ); if ( frac2 < 0.0 ) continue; int xpos2 = ( int )( frac2 * rcWorkSpace.right ); // Draw line and vertical ticks RECT rcWord; rcWord.left = xpos; rcWord.right = xpos2; rcWord.top = ypos - m_nTickHeight + 1; rcWord.bottom = ypos; drawHelper.DrawFilledRect( PEColor( word->m_bSelected ? COLOR_PHONEME_TAG_SELECTED : COLOR_PHONEME_TAG_FILLER_NORMAL ), rcWord ); Color border = PEColor( word->m_bSelected ? COLOR_PHONEME_TAG_BORDER_SELECTED : COLOR_PHONEME_TAG_BORDER ); if ( showactive && m_bWordsActive ) { drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_ACTIVE_BORDER ), xpos, ypos - m_nTickHeight, xpos2, ypos - m_nTickHeight + 4 ); } drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos2, ypos ); drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos, ypos - m_nTickHeight ); drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos2, ypos, xpos2, ypos - m_nTickHeight ); drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos - m_nTickHeight, xpos2, ypos - m_nTickHeight ); if ( hasselectedphonemes ) { drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_SELECTED_BORDER ), xpos, ypos - 3, xpos2, ypos ); } //if ( frac >= 0.0 && frac <= 1.0 ) { int fontsize = 9; RECT rcText; rcText.left = xpos; rcText.right = xpos + 500; rcText.top = ypos - m_nTickHeight + 4; rcText.bottom = rcText.top + fontsize + 2; int length = drawHelper.CalcTextWidth( fontName, fontsize, FW_NORMAL, "%s", word->GetWord() ); rcText.right = max( xpos2 - 2, rcText.left + length + 1 ); int w = rcText.right - rcText.left; if ( w > length ) { rcText.left += ( w - length ) / 2; } drawHelper.DrawColoredText( fontName, fontsize, FW_NORMAL, PEColor( word->m_bSelected ? COLOR_PHONEME_TAG_TEXT_SELECTED : COLOR_PHONEME_TAG_TEXT ), rcText, "%s", word->GetWord() ); } } } } void PhonemeEditor::DrawPhonemes( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence& sentence, int type, bool showactive /* = true */ ) { float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; int ypos = rcWorkSpace.bottom - m_nTickHeight - 2; if ( type == 1 ) { ypos -= ( m_nTickHeight + 5 ); } const char *fontName = "Arial"; bool drawselected; for ( int pass = 0; pass < 2 ; pass++ ) { drawselected = pass == 0 ? false : true; for ( int i = 0; i < sentence.m_Words.Count(); i++ ) { CWordTag *w = sentence.m_Words[ i ]; if ( !w ) continue; if ( w->m_bSelected != drawselected ) continue; for ( int k = 0; k < w->m_Phonemes.Count(); k++ ) { CPhonemeTag *pPhoneme = w->m_Phonemes[ k ]; float t1 = pPhoneme->GetStartTime(); float t2 = pPhoneme->GetEndTime(); // Tag it float frac = ( t1 - starttime ) / ( endtime - starttime ); int xpos = ( int )( frac * rcWorkSpace.right ); if ( frac <= 0.0 ) { xpos = 0; } // Draw duration float frac2 = ( t2 - starttime ) / ( endtime - starttime ); if ( frac2 < 0.0 ) { continue; } int xpos2 = ( int )( frac2 * rcWorkSpace.right ); RECT rcFrame; rcFrame.left = xpos; rcFrame.right = xpos2; rcFrame.top = ypos - m_nTickHeight + 1; rcFrame.bottom = ypos; drawHelper.DrawFilledRect( PEColor( pPhoneme->m_bSelected ? COLOR_PHONEME_TAG_SELECTED : COLOR_PHONEME_TAG_FILLER_NORMAL ), rcFrame ); Color border = PEColor( pPhoneme->m_bSelected ? COLOR_PHONEME_TAG_BORDER_SELECTED : COLOR_PHONEME_TAG_BORDER ); if ( showactive && !m_bWordsActive ) { drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_ACTIVE_BORDER ), xpos, ypos - 3, xpos2, ypos ); } drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos - m_nTickHeight, xpos2, ypos - m_nTickHeight ); drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos, ypos - m_nTickHeight ); drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos2, ypos, xpos2, ypos - m_nTickHeight ); drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos2, ypos ); if ( w->m_bSelected ) { drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_SELECTED_BORDER ), xpos, ypos - m_nTickHeight + 1, xpos2, ypos - m_nTickHeight + 4 ); } //if ( frac >= 0.0 && frac <= 1.0 ) { int fontsize = 9; RECT rcText; rcText.left = xpos; rcText.right = xpos + 500; rcText.top = ypos - m_nTickHeight + 4; rcText.bottom = rcText.top + fontsize + 2; int length = drawHelper.CalcTextWidth( fontName, fontsize, FW_NORMAL, "%s", ConvertPhoneme( pPhoneme->GetPhonemeCode() ) ); rcText.right = max( xpos2 - 2, rcText.left + length + 1 ); int w = rcText.right - rcText.left; if ( w > length ) { rcText.left += ( w - length ) / 2; } drawHelper.DrawColoredText( fontName, fontsize, FW_NORMAL, PEColor( pPhoneme->m_bSelected ? COLOR_PHONEME_TAG_TEXT_SELECTED : COLOR_PHONEME_TAG_TEXT ), rcText, "%s", ConvertPhoneme( pPhoneme->GetPhonemeCode() ) ); } } } } } //----------------------------------------------------------------------------- // Purpose: // Input : drawHelper - // rc - //----------------------------------------------------------------------------- void PhonemeEditor::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) { if ( !m_pEvent || !m_pWaveFile ) return; drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rc, "Timing Tags:" ); float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; for ( int i = 0; i < m_pEvent->GetNumRelativeTags(); i++ ) { CEventRelativeTag *tag = m_pEvent->GetRelativeTag( i ); if ( !tag ) continue; // float tagtime = tag->GetPercentage() * m_pWaveFile->GetRunningLength(); if ( tagtime < starttime || tagtime > endtime ) continue; float frac = ( tagtime - starttime ) / ( endtime - starttime ); int left = rc.left + (int)( frac * ( float )( rc.right - rc.left ) + 0.5f ); RECT rcMark; rcMark = rc; rcMark.top = rc.bottom - 8; rcMark.bottom = rc.bottom; rcMark.left = left - 4; rcMark.right = left + 4; drawHelper.DrawTriangleMarker( rcMark, PEColor( COLOR_PHONEME_TIMING_TAG ) ); RECT rcText; rcText = rc; rcText.bottom = rc.bottom - 10; rcText.top = rcText.bottom - 10; int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); rcText.left = left - len / 2; rcText.right = rcText.left + len + 2; drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rcText, tag->GetName() ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::redraw( void ) { if ( !ToolCanDraw() ) return; CChoreoWidgetDrawHelper drawHelper( this ); HandleToolRedraw( drawHelper ); if ( !m_pWaveFile ) return; HDC dc = drawHelper.GrabDC(); RECT rc; GetWorkspaceRect( rc ); float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; // Now draw the time legend RECT rcLabel; float granularity = 0.5f; drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_TIMELINE ), PS_SOLID, 1, rc.left, rc.bottom - m_nTickHeight, rc.right, rc.bottom - m_nTickHeight ); if ( GetMode() != MODE_EMPHASIS ) { Emphasis_Redraw( drawHelper, rc ); } sound->RenderWavToDC( dc, rc, PEColor( COLOR_PHONEME_WAVDATA ), starttime, endtime, m_pWaveFile, m_bSelectionActive, m_nSelection[ 0 ], m_nSelection[ 1 ] ); float f = SnapTime( starttime, granularity ); while ( f <= endtime ) { float frac = ( f - starttime ) / ( endtime - starttime ); if ( frac >= 0.0f && frac <= 1.0f ) { drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_TIMELINE_MAJORTICK ), PS_SOLID, 1, (int)( frac * rc.right ), rc.top, (int)( frac * rc.right ), rc.bottom - m_nTickHeight ); rcLabel.left = (int)( frac * rc.right ); rcLabel.bottom = rc.bottom; rcLabel.top = rcLabel.bottom - 10; char sz[ 32 ]; sprintf( sz, "%.2f", f ); int textWidth = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); rcLabel.right = rcLabel.left + textWidth; OffsetRect( &rcLabel, -textWidth / 2, 0 ); drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TEXT ), rcLabel, sz ); } f += granularity; } HBRUSH br = CreateSolidBrush( ColorToRGB( PEColor( COLOR_PHONEME_TEXT ) ) ); FrameRect( dc, &rc, br ); DeleteObject( br ); RECT rcTags = rc; rcTags.top = TAG_TOP; rcTags.bottom = TAG_BOTTOM; DrawRelativeTags( drawHelper, rcTags ); int fontsize = 9; RECT rcText = rc; rcText.top = rcText.bottom + 5; rcText.left += 5; rcText.bottom = rcText.top + fontsize + 1; rcText.right -= 5; int fontweight = FW_NORMAL; const char *font = "Arial"; if ( m_nLastExtractionResult != SR_RESULT_NORESULT ) { Color clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_OTHER ); switch ( m_nLastExtractionResult ) { case SR_RESULT_ERROR: clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_ERROR ); break; case SR_RESULT_SUCCESS: clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS ); break; case SR_RESULT_FAILED: clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_FAIL ); break; default: break; } drawHelper.DrawColoredText( font, fontsize, fontweight, clr, rcText, "Last Extraction Result: %s", GetExtractionResultString( m_nLastExtractionResult ) ); OffsetRect( &rcText, 0, fontsize + 1 ); } if ( m_pEvent && !Q_stristr( m_pEvent->GetParameters(), ".wav" ) ) { drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, "Sound: '%s', file: %s, length %.2f seconds", m_pEvent->GetParameters(), m_WorkFile.m_szWaveFile, m_pWaveFile->GetRunningLength() ); } else { drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, "File: %s, length %.2f seconds", m_WorkFile.m_szWaveFile, m_pWaveFile->GetRunningLength() ); } OffsetRect( &rcText, 0, fontsize + 1 ); drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, "Number of samples %i at %ikhz (%i bits/sample) %s", (int) (m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() ), m_pWaveFile->SampleRate(), (m_pWaveFile->SampleSize()<<3), m_Tags.GetVoiceDuck() ? "duck other audio" : "no ducking" ); OffsetRect( &rcText, 0, fontsize + 1 ); drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, "[ %i ] Words [ %i ] Phonemes / Zoom %i %%", m_Tags.m_Words.Count(), m_Tags.CountPhonemes(), m_nTimeZoom ); if ( m_pEvent ) { OffsetRect( &rcText, 0, fontsize + 1 ); drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, "Event %s", m_pEvent->GetName() ); } OffsetRect( &rcText, 0, fontsize + 1 ); drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, "Using: %s", GetSpeechAPIName() ); char text[ 4096 ]; sprintf( text, "Sentence Text: %s", m_Tags.GetText() ); int halfwidth = ( rc.right - rc.left ) / 2; rcText = rc; rcText.left = halfwidth; rcText.top = rcText.bottom + 5; rcText.right = rcText.left + halfwidth * 0.6; drawHelper.CalcTextRect( font, fontsize, fontweight, halfwidth, rcText, text ); drawHelper.DrawColoredTextMultiline( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, text ); CWordTag *cw = GetSelectedWord(); if ( cw ) { char wordInfo[ 512 ]; sprintf( wordInfo, "Word: %s, start %.2f end %.2f, duration %.2f ms phonemes %i", cw->GetWord(), cw->m_flStartTime, cw->m_flEndTime, 1000.0f * ( cw->m_flEndTime - cw->m_flStartTime ), cw->m_Phonemes.Count() ); int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, wordInfo ); OffsetRect( &rcText, 0, ( rcText.bottom - rcText.top ) + 2 ); rcText.left = rcText.right - length - 10; rcText.bottom = rcText.top + fontsize + 1; drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, wordInfo ); } CPhonemeTag *cp = GetSelectedPhoneme(); if ( cp ) { char phonemeInfo[ 512 ]; sprintf( phonemeInfo, "Phoneme: %s, start %.2f end %.2f, duration %.2f ms", ConvertPhoneme( cp->GetPhonemeCode() ), cp->GetStartTime(), cp->GetEndTime(), 1000.0f * ( cp->GetEndTime() - cp->GetStartTime() ) ); int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, phonemeInfo ); OffsetRect( &rcText, 0, ( rcText.bottom - rcText.top ) + 2 ); rcText.left = rcText.right - length - 10; rcText.bottom = rcText.top + fontsize + 1; drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, phonemeInfo ); } // Draw playback rate { char sz[ 48 ]; sprintf( sz, "Speed: %.2fx", m_flPlaybackRate ); int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, sz); rcText = rc; rcText.top = rc.bottom + 60; rcText.bottom = rcText.top + fontsize + 1; rcText.left = m_pPlaybackRate->x() + m_pPlaybackRate->w() - x(); rcText.right = rcText.left + length + 2; drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, sz ); } if ( m_UndoStack.Count() > 0 ) { int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, "Undo levels: %i/%i", m_nUndoLevel, m_UndoStack.Count() ); rcText = rc; rcText.top = rc.bottom + 60; rcText.bottom = rcText.top + fontsize + 1; rcText.right -= 5; rcText.left = rcText.right - length - 10; drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS ), rcText, "Undo levels: %i/%i", m_nUndoLevel, m_UndoStack.Count() ); } float endfrac = ( m_pWaveFile->GetRunningLength() - starttime ) / ( endtime - starttime ); if ( endfrac >= 0.0f && endfrac <= 1.0f ) { int endpos = ( int ) ( rc.right * endfrac ); drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_WAV_ENDPOINT ), PS_DOT, 2, endpos, rc.top, endpos, rc.bottom - m_nTickHeight ); } DrawPhonemes( drawHelper, rc, m_Tags, 0 ); DrawPhonemes( drawHelper, rc, m_TagsExt, 1, false ); DrawWords( drawHelper, rc, m_Tags, 0 ); DrawWords( drawHelper, rc, m_TagsExt, 1, false ); if ( GetMode() == MODE_EMPHASIS ) { Emphasis_Redraw( drawHelper, rc ); } DrawScrubHandle( drawHelper ); } #define MOTION_RANGE 3000 #define MOTION_MAXSTEP 500 //----------------------------------------------------------------------------- // Purpose: Brown noise simulates brownian motion centered around 127.5 but we cap the walking // to just a couple of units // Input : *buffer - // count - // Output : static void //----------------------------------------------------------------------------- static void WriteBrownNoise( void *buffer, int count ) { int currentValue = 127500; int maxValue = currentValue + ( MOTION_RANGE / 2 ); int minValue = currentValue - ( MOTION_RANGE / 2 ); unsigned char *pos = ( unsigned char *)buffer; while ( --count >= 0 ) { currentValue += random->RandomInt( -MOTION_MAXSTEP, MOTION_MAXSTEP ); currentValue = min( maxValue, currentValue ); currentValue = max( minValue, currentValue ); // Downsample to 0-255 range *pos++ = (unsigned char)( ( (float)currentValue / 1000.0f ) + 0.5f ); } } //----------------------------------------------------------------------------- // Purpose: Replace with brownian noice parts of the wav file that we dont' want processed by the // speech recognizer // Input : store - // *format - // chunkname - // *buffer - // buffersize - //----------------------------------------------------------------------------- void PhonemeEditor::ResampleChunk( IterateOutputRIFF& store, void *format, int chunkname, char *buffer, int buffersize, int start_silence /*=0*/, int end_silence /*=0*/ ) { WAVEFORMATEX *pFormat = ( WAVEFORMATEX * )format; Assert( pFormat ); if ( pFormat->wFormatTag == WAVE_FORMAT_PCM ) { int silience_time = start_silence + end_silence; // Leave room for silence at start + end int resamplesize = buffersize + silience_time * pFormat->nSamplesPerSec; char *resamplebuffer = new char[ resamplesize + 4 ]; memset( resamplebuffer, (unsigned char)128, resamplesize + 4 ); int startpos = (int)( start_silence * pFormat->nSamplesPerSec ); if ( startpos > 0 ) { WriteBrownNoise( resamplebuffer, startpos ); } if ( startpos + buffersize < resamplesize ) { WriteBrownNoise( &resamplebuffer[ startpos + buffersize ], resamplesize - ( startpos + buffersize ) ); } memcpy( &resamplebuffer[ startpos ], buffer, buffersize ); store.ChunkWriteData( resamplebuffer, resamplesize ); return; } store.ChunkWriteData( buffer, buffersize ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::ReadLinguisticTags( void ) { if ( !m_pWaveFile ) return; CAudioSource *wave = sound->LoadSound( m_WorkFile.m_szWorkingFile ); if ( !wave ) return; m_Tags.Reset(); CSentence *sentence = wave->GetSentence(); if ( sentence ) { // Copy data from sentence to m_Tags m_Tags.Reset(); m_Tags = *sentence; } delete wave; } //----------------------------------------------------------------------------- // Purpose: Switch wave files // Input : *wavefile - // force - //----------------------------------------------------------------------------- void PhonemeEditor::SetCurrentWaveFile( const char *wavefile, bool force /*=false*/, CChoreoEvent *event /*=NULL*/ ) { // No change? if ( !force && !stricmp( m_WorkFile.m_szWaveFile, wavefile ) ) return; StopPlayback(); if ( GetDirty() ) { int retval = mxMessageBox( this, va( "Save current changes to %s", m_WorkFile.m_szWaveFile ), "Phoneme Editor", MX_MB_QUESTION | MX_MB_YESNOCANCEL ); // Cancel if ( retval == 2 ) return; // Yes if ( retval == 0 ) { CommitChanges(); } } ClearExtracted(); m_Tags.Reset(); m_TagsExt.Reset(); Deselect(); if ( m_pWaveFile ) { char fn[ 512 ]; Q_snprintf( fn, sizeof( fn ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); filesystem->RemoveFile( fn, "GAME" ); } delete m_pWaveFile; m_pWaveFile = NULL; SetDirty( false ); // Set up event and scene m_pEvent = event; // Try an dload new sound m_pWaveFile = sound->LoadSound( wavefile ); Q_strncpy( m_WorkFile.m_szWaveFile, wavefile, sizeof( m_WorkFile.m_szWaveFile ) ); char fullpath[ 512 ]; filesystem->RelativePathToFullPath( wavefile, "GAME", fullpath, sizeof( fullpath ) ); int len = Q_strlen( fullpath ); int charstocopy = len - Q_strlen( wavefile ) + 1; m_WorkFile.m_szBasePath[ 0 ] = 0; if ( charstocopy >= 0 ) { Q_strncpy( m_WorkFile.m_szBasePath, fullpath, charstocopy ); m_WorkFile.m_szBasePath[ charstocopy ] = 0; } Q_StripExtension( wavefile, m_WorkFile.m_szWorkingFile, sizeof( m_WorkFile.m_szWorkingFile ) ); Q_strncat( m_WorkFile.m_szWorkingFile, "_work.wav", sizeof( m_WorkFile.m_szWorkingFile ), COPY_ALL_CHARACTERS ); Q_FixSlashes( m_WorkFile.m_szWaveFile ); Q_FixSlashes( m_WorkFile.m_szWorkingFile ); Q_FixSlashes( m_WorkFile.m_szBasePath ); if ( !m_pWaveFile ) { Con_ErrorPrintf( "Couldn't set current .wav file to %s\n", m_WorkFile.m_szWaveFile ); return; } Con_Printf( "Current .wav file set to %s\n", m_WorkFile.m_szWaveFile ); g_pWaveBrowser->SetCurrent( m_WorkFile.m_szWaveFile ); // Copy over and overwrite file FPCopyFile( m_WorkFile.m_szWaveFile, m_WorkFile.m_szWorkingFile, false ); // Make it writable MakeFileWriteable( m_WorkFile.m_szWorkingFile ); ReadLinguisticTags(); Deselect(); RepositionHSlider(); } //----------------------------------------------------------------------------- // Purpose: // Input : x - //----------------------------------------------------------------------------- void PhonemeEditor::MoveTimeSliderToPos( int x ) { m_nLeftOffset = x; m_pHorzScrollBar->setValue( m_nLeftOffset ); InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int PhonemeEditor::ComputeHPixelsNeeded( void ) { int pixels = 0; if ( m_pWaveFile ) { float maxtime = m_pWaveFile->GetRunningLength(); maxtime += 1.0f; pixels = (int)( maxtime * GetPixelsPerSecond() ); } return pixels; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::RepositionHSlider( void ) { int pixelsneeded = ComputeHPixelsNeeded(); if ( pixelsneeded <= w2() ) { m_pHorzScrollBar->setVisible( false ); } else { m_pHorzScrollBar->setVisible( true ); } m_pHorzScrollBar->setBounds( 0, GetCaptionHeight(), w2(), 12 ); m_pHorzScrollBar->setRange( 0, pixelsneeded ); m_pHorzScrollBar->setValue( 0 ); m_nLeftOffset = 0; m_pHorzScrollBar->setPagesize( w2() ); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float PhonemeEditor::GetPixelsPerSecond( void ) { return m_flPixelsPerSecond * GetTimeZoomScale(); } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float PhonemeEditor::GetTimeZoomScale( void ) { return ( float )m_nTimeZoom / 100.0f; } //----------------------------------------------------------------------------- // Purpose: // Input : scale - //----------------------------------------------------------------------------- void PhonemeEditor::SetTimeZoomScale( int scale ) { m_nTimeZoom = scale; } //----------------------------------------------------------------------------- // Purpose: // Input : dt - //----------------------------------------------------------------------------- void PhonemeEditor::Think( float dt ) { if ( !m_pWaveFile ) return; bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; ScrubThink( dt, scrubbing ); if ( m_pMixer && !sound->IsSoundPlaying( m_pMixer ) ) { m_pMixer = NULL; } } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- int PhonemeEditor::IsMouseOverBoundary( mxEvent *event ) { int mx, my; mx = (short)event->x; my = (short)event->y; // Deterime if phoneme boundary is under the cursor // if ( !m_pWaveFile ) return BOUNDARY_NONE; if ( !(event->modifiers & mxEvent::KeyCtrl ) ) { return BOUNDARY_NONE; } RECT rc; GetWorkspaceRect( rc ); if ( IsMouseOverPhonemeRow( my ) ) { float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; int mouse_tolerance = 3; for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; for ( int k = 0; k < word->m_Phonemes.Count(); k++ ) { CPhonemeTag *pPhoneme = word->m_Phonemes[ k ]; float t1 = pPhoneme->GetStartTime(); float t2 = pPhoneme->GetEndTime(); // Tag it float frac1 = ( t1 - starttime ) / ( endtime - starttime ); float frac2 = ( t2 - starttime ) / ( endtime - starttime ); int xpos1 = ( int )( frac1 * w2() ); int xpos2 = ( int )( frac2 * w2() ); if ( abs( xpos1 - mx ) <= mouse_tolerance || abs( xpos2 - mx ) <= mouse_tolerance ) { return BOUNDARY_PHONEME; } } } } if ( IsMouseOverWordRow( my ) ) { float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; int mouse_tolerance = 3; for ( int k = 0; k < m_Tags.m_Words.Count(); k++ ) { CWordTag *word = m_Tags.m_Words[ k ]; float t1 = word->m_flStartTime; float t2 = word->m_flEndTime; // Tag it float frac1 = ( t1 - starttime ) / ( endtime - starttime ); float frac2 = ( t2 - starttime ) / ( endtime - starttime ); int xpos1 = ( int )( frac1 * w2() ); int xpos2 = ( int )( frac2 * w2() ); if ( ( abs( xpos1 - mx ) <= mouse_tolerance ) || ( abs( xpos2 - mx ) <= mouse_tolerance ) ) { return BOUNDARY_WORD; } } } return BOUNDARY_NONE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::DrawFocusRect( char *reason ) { HDC dc = GetDC( NULL ); for ( int i = 0; i < m_FocusRects.Count(); i++ ) { RECT rc = m_FocusRects[ i ].m_rcFocus; ::DrawFocusRect( dc, &rc ); } ReleaseDC( NULL, dc ); } //----------------------------------------------------------------------------- // Purpose: // Input : &rc - //----------------------------------------------------------------------------- void PhonemeEditor::GetWorkspaceRect( RECT &rc ) { GetClientRect( (HWND)getHandle(), &rc ); rc.top += TAG_BOTTOM; rc.bottom = rc.bottom - 75 - MODE_TAB_OFFSET; InflateRect( &rc, -1, -1 ); } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - //----------------------------------------------------------------------------- void PhonemeEditor::ShowWordMenu( CWordTag *word, int mx, int my ) { CountSelected(); mxPopupMenu *pop = new mxPopupMenu(); Assert( pop ); pop->add( va( "Edit sentence text..." ), IDC_EDITWORDLIST ); if ( m_nSelectedWordCount > 0 && word ) { pop->addSeparator(); pop->add( va( "Delete %s", m_nSelectedWordCount > 1 ? "words" : va( "'%s'", word->GetWord() ) ), IDC_EDIT_DELETEWORD ); if ( m_nSelectedWordCount == 1 ) { int index = IndexOfWord( word ); bool valid = false; if ( index != -1 ) { SetClickedPhoneme( index, -1 ); valid = true; } if ( valid ) { pop->add( va( "Edit word '%s'...", word->GetWord() ), IDC_EDIT_WORD ); float nextGap = GetTimeGapToNextWord( true, word ); float prevGap = GetTimeGapToNextWord( false, word ); if ( nextGap > MINIMUM_WORD_GAP || prevGap > MINIMUM_WORD_GAP ) { pop->addSeparator(); if ( prevGap > MINIMUM_WORD_GAP ) { pop->add( va( "Insert word before '%s'...", word->GetWord() ), IDC_EDIT_INSERTWORDBEFORE ); } if ( nextGap > MINIMUM_WORD_GAP ) { pop->add( va( "Insert word after '%s'...", word->GetWord() ), IDC_EDIT_INSERTWORDAFTER ); } } if ( word->m_Phonemes.Count() == 0 ) { pop->addSeparator(); pop->add( va( "Add phoneme to '%s'...", word->GetWord() ), IDC_EDIT_INSERTFIRSTPHONEMEOFWORD ); } pop->addSeparator(); pop->add( va( "Select all words after '%s'", word->GetWord() ), IDC_SELECT_WORDSRIGHT ); pop->add( va( "Select all words before '%s'", word->GetWord() ), IDC_SELECT_WORDSLEFT ); } } } if ( AreSelectedWordsContiguous() && m_nSelectedWordCount > 1 ) { pop->addSeparator(); pop->add( va( "Merge words" ), IDC_SNAPWORDS ); if ( m_nSelectedWordCount == 2 ) { pop->add( va( "Separate words" ), IDC_SEPARATEWORDS ); } } if ( m_nSelectedWordCount > 0 ) { pop->addSeparator(); pop->add( va( "Deselect all" ), IDC_DESELECT_PHONEMESANDWORDS ); } if ( m_Tags.m_Words.Count() > 0 ) { pop->addSeparator(); pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP ); } if ( m_Tags.m_Words.Count() > 0 ) { pop->addSeparator(); pop->add( va( "Realign phonemes to words" ), IDC_REALIGNPHONEMES ); } pop->popup( this, mx, my ); } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - //----------------------------------------------------------------------------- void PhonemeEditor::ShowPhonemeMenu( CPhonemeTag *pho, int mx, int my ) { CountSelected(); SetClickedPhoneme( -1, -1 ); if ( !pho ) return; if ( m_Tags.CountPhonemes() == 0 ) { Con_Printf( "No phonemes, try extracting from .wav first\n" ); return; } mxPopupMenu *pop = new mxPopupMenu(); bool valid = false; CWordTag *tag = m_Tags.GetWordForPhoneme( pho ); if ( tag ) { int wordNum = IndexOfWord( tag ); int pi = tag->IndexOfPhoneme( pho ); SetClickedPhoneme( wordNum, pi ); valid = true; } if ( valid ) { if ( m_nSelectedPhonemeCount == 1 ) { pop->add( va( "Edit '%s'...", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_EDIT_PHONEME ); float nextGap = GetTimeGapToNextPhoneme( true, pho ); float prevGap = GetTimeGapToNextPhoneme( false, pho ); if ( nextGap > MINIMUM_PHONEME_GAP || prevGap > MINIMUM_PHONEME_GAP ) { pop->addSeparator(); if ( prevGap > MINIMUM_PHONEME_GAP ) { pop->add( va( "Insert phoneme before '%s'...", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_EDIT_INSERTPHONEMEBEFORE ); } if ( nextGap > MINIMUM_PHONEME_GAP ) { pop->add( va( "Insert phoneme after '%s'...", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_EDIT_INSERTPHONEMEAFTER ); } } pop->addSeparator(); pop->add( va( "Select all phonemes after '%s'", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_SELECT_PHONEMESRIGHT ); pop->add( va( "Select all phonemes before '%s'",ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_SELECT_PHONEMESLEFT ); pop->addSeparator(); } if ( AreSelectedPhonemesContiguous() && m_nSelectedPhonemeCount > 1 ) { pop->add( va( "Merge phonemes" ), IDC_SNAPPHONEMES ); if ( m_nSelectedPhonemeCount == 2 ) { pop->add( va( "Separate phonemes" ), IDC_SEPARATEPHONEMES ); } pop->addSeparator(); } if ( m_nSelectedPhonemeCount >= 1 ) { pop->add( va( "Delete %s", m_nSelectedPhonemeCount == 1 ? va( "'%s'", ConvertPhoneme( pho->GetPhonemeCode() ) ) : "phonemes" ), IDC_EDIT_DELETEPHONEME ); pop->addSeparator(); pop->add( va( "Deselect all" ), IDC_DESELECT_PHONEMESANDWORDS ); } } if ( m_Tags.m_Words.Count() > 0 ) { pop->addSeparator(); pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP ); } if ( m_Tags.m_Words.Count() > 0 ) { pop->addSeparator(); pop->add( va( "Realign words to phonemes" ), IDC_REALIGNWORDS ); } pop->popup( this, mx, my ); } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // Output : float //----------------------------------------------------------------------------- float PhonemeEditor::GetTimeForPixel( int mx ) { RECT rc; GetWorkspaceRect( rc ); float starttime = m_nLeftOffset / GetPixelsPerSecond(); float time = (float)mx / GetPixelsPerSecond() + starttime; return time; } //----------------------------------------------------------------------------- // Purpose: // Input : time - // **pp1 - // **pp2 - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::FindSpanningPhonemes( float time, CPhonemeTag **pp1, CPhonemeTag **pp2 ) { Assert( pp1 && pp2 ); *pp1 = NULL; *pp2 = NULL; // Three pixels double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 3; CPhonemeTag *previous = NULL; for ( int w = 0; w < m_Tags.m_Words.Count(); w++ ) { CWordTag *word = m_Tags.m_Words[ w ]; for ( int i = 0; i < word->m_Phonemes.Count(); i++ ) { CPhonemeTag *current = word->m_Phonemes[ i ]; double dt; if ( !previous ) { dt = fabs( current->GetStartTime() - time ); if ( dt < time_epsilon ) { *pp2 = current; return true; } } else { int found = 0; dt = fabs( previous->GetEndTime() - time ); if ( dt < time_epsilon ) { *pp1 = previous; found++; } dt = fabs( current->GetStartTime() - time ); if ( dt < time_epsilon ) { *pp2 = current; found++; } if ( found != 0 ) { return true; } } previous = current; } } if ( m_Tags.m_Words.Count() > 0 ) { // Check last word, but only if it has some phonemes CWordTag *lastWord = m_Tags.m_Words[ m_Tags.m_Words.Count() - 1 ]; if ( lastWord && ( lastWord->m_Phonemes.Count() > 0 ) ) { CPhonemeTag *last = lastWord->m_Phonemes[ lastWord->m_Phonemes.Count() - 1 ]; float dt; dt = fabs( last->GetEndTime() - time ); if ( dt < time_epsilon ) { *pp1 = last; return true; } } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : time - // **pp1 - // **pp2 - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::FindSpanningWords( float time, CWordTag **pp1, CWordTag **pp2 ) { Assert( pp1 && pp2 ); *pp1 = NULL; *pp2 = NULL; // Three pixels double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 3; CWordTag *previous = NULL; for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *current = m_Tags.m_Words[ i ]; double dt; if ( !previous ) { dt = fabs( current->m_flStartTime - time ); if ( dt < time_epsilon ) { *pp2 = current; return true; } } else { int found = 0; dt = fabs( previous->m_flEndTime - time ); if ( dt < time_epsilon ) { *pp1 = previous; found++; } dt = fabs( current->m_flStartTime - time ); if ( dt < time_epsilon ) { *pp2 = current; found++; } if ( found != 0 ) { return true; } } previous = current; } if ( m_Tags.m_Words.Count() > 0 ) { CWordTag *last = m_Tags.m_Words[ m_Tags.m_Words.Count() - 1 ]; float dt; dt = fabs( last->m_flEndTime - time ); if ( dt < time_epsilon ) { *pp1 = last; return true; } } return false; } int PhonemeEditor::FindWordForTime( float time ) { for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *pCurrent = m_Tags.m_Words[ i ]; if ( time < pCurrent->m_flStartTime ) continue; if ( time > pCurrent->m_flEndTime ) continue; return i; } return -1; } void PhonemeEditor::FinishWordDrag( int startx, int endx ) { float clicktime = GetTimeForPixel( startx ); float endtime = GetTimeForPixel( endx ); float dt = endtime - clicktime; SetDirty( true ); PushUndo(); TraverseWords( &PhonemeEditor::ITER_MoveSelectedWords, dt ); RealignPhonemesToWords( false ); CleanupWordsAndPhonemes( false ); PushRedo(); redraw(); } void PhonemeEditor::FinishWordMove( int startx, int endx ) { float clicktime = GetTimeForPixel( startx ); float endtime = GetTimeForPixel( endx ); // Find the phonemes who have the closest start/endtime to the starting click time CWordTag *current, *next; if ( !FindSpanningWords( clicktime, ¤t, &next ) ) { return; } SetDirty( true ); PushUndo(); if ( current && !next ) { // cap movement current->m_flEndTime += ( endtime - clicktime ); } else if ( !current && next ) { // cap movement next->m_flStartTime += ( endtime - clicktime ); } else { // cap movement endtime = min( endtime, next->m_flEndTime - 1.0f / GetPixelsPerSecond() ); endtime = max( endtime, current->m_flStartTime + 1.0f / GetPixelsPerSecond() ); current->m_flEndTime = endtime; next->m_flStartTime = endtime; } RealignPhonemesToWords( false ); CleanupWordsAndPhonemes( false ); PushRedo(); redraw(); } CPhonemeTag *PhonemeEditor::FindPhonemeForTime( float time ) { for ( int w = 0 ; w < m_Tags.m_Words.Count(); w++ ) { CWordTag *word = m_Tags.m_Words[ w ]; for ( int i = 0; i < word->m_Phonemes.Count(); i++ ) { CPhonemeTag *pCurrent = word->m_Phonemes[ i ]; if ( time < pCurrent->GetStartTime() ) continue; if ( time > pCurrent->GetEndTime() ) continue; return pCurrent; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : phoneme - // startx - // endx - //----------------------------------------------------------------------------- void PhonemeEditor::FinishPhonemeDrag( int startx, int endx ) { float clicktime = GetTimeForPixel( startx ); float endtime = GetTimeForPixel( endx ); float dt = endtime - clicktime; SetDirty( true ); PushUndo(); TraversePhonemes( &PhonemeEditor::ITER_MoveSelectedPhonemes, dt ); RealignWordsToPhonemes( false ); CleanupWordsAndPhonemes( false ); PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : phoneme - // startx - // endx - //----------------------------------------------------------------------------- void PhonemeEditor::FinishPhonemeMove( int startx, int endx ) { float clicktime = GetTimeForPixel( startx ); float endtime = GetTimeForPixel( endx ); // Find the phonemes who have the closest start/endtime to the starting click time CPhonemeTag *current, *next; if ( !FindSpanningPhonemes( clicktime, ¤t, &next ) ) { return; } SetDirty( true ); PushUndo(); if ( current && !next ) { // cap movement current->AddEndTime( endtime - clicktime ); } else if ( !current && next ) { // cap movement next->AddStartTime( endtime - clicktime ); } else { // cap movement endtime = min( endtime, next->GetEndTime() - 1.0f / GetPixelsPerSecond() ); endtime = max( endtime, current->GetStartTime() + 1.0f / GetPixelsPerSecond() ); current->SetEndTime( endtime ); next->SetStartTime( endtime ); } RealignWordsToPhonemes( false ); CleanupWordsAndPhonemes( false ); PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : dirty - //----------------------------------------------------------------------------- void PhonemeEditor::SetDirty( bool dirty, bool clearundo /*=true*/ ) { m_WorkFile.m_bDirty = dirty; if ( !dirty && clearundo ) { WipeUndo(); redraw(); } SetPrefix( dirty ? "* " : "" ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::GetDirty( void ) { return m_WorkFile.m_bDirty; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditInsertPhonemeBefore( void ) { if ( GetMode() != MODE_PHONEMES ) return; CPhonemeTag *cp = GetSelectedPhoneme(); if ( !cp ) return; float gap = GetTimeGapToNextPhoneme( false, cp ); if ( gap < MINIMUM_PHONEME_GAP ) { Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap ); return; } // Don't have really long phonemes gap = min( gap, DEFAULT_PHONEME_LENGTH ); CWordTag *word = m_Tags.GetWordForPhoneme( cp ); if ( !word ) { Con_Printf( "EditInsertPhonemeBefore: phoneme not a member of any known word!!!\n" ); return; } int clicked = word->IndexOfPhoneme( cp ); if ( clicked < 0 ) { Con_Printf( "EditInsertPhonemeBefore: phoneme not a member of any specified word!!!\n" ); Assert( 0 ); return; } CPhonemeTag phoneme; CPhonemeParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); strcpy( params.m_szName, "" ); int iret = PhonemeProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } SetDirty( true ); PushUndo(); phoneme.SetPhonemeCode( TextToPhoneme( params.m_szName ) ); phoneme.SetTag( params.m_szName ); phoneme.SetEndTime( cp->GetStartTime() ); phoneme.SetStartTime( cp->GetStartTime() - gap ); phoneme.m_bSelected = true; cp->m_bSelected = false; word->m_Phonemes.InsertBefore( clicked, new CPhonemeTag( phoneme ) ); PushRedo(); // Add it redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditInsertPhonemeAfter( void ) { if ( GetMode() != MODE_PHONEMES ) return; CPhonemeTag *cp = GetSelectedPhoneme(); if ( !cp ) return; float gap = GetTimeGapToNextPhoneme( true, cp ); if ( gap < MINIMUM_PHONEME_GAP ) { Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap ); return; } // Don't have really long phonemes gap = min( gap, DEFAULT_PHONEME_LENGTH ); CWordTag *word = m_Tags.GetWordForPhoneme( cp ); if ( !word ) { Con_Printf( "EditInsertPhonemeAfter: phoneme not a member of any known word!!!\n" ); return; } int clicked = word->IndexOfPhoneme( cp ); if ( clicked < 0 ) { Con_Printf( "EditInsertPhonemeAfter: phoneme not a member of any specified word!!!\n" ); Assert( 0 ); return; } CPhonemeTag phoneme; CPhonemeParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); strcpy( params.m_szName, "" ); int iret = PhonemeProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } SetDirty( true ); PushUndo(); phoneme.SetPhonemeCode( TextToPhoneme( params.m_szName ) ); phoneme.SetTag( params.m_szName ); phoneme.SetEndTime( cp->GetEndTime() + gap ); phoneme.SetStartTime( cp->GetEndTime() ); phoneme.m_bSelected = true; cp->m_bSelected = false; word->m_Phonemes.InsertAfter( clicked, new CPhonemeTag( phoneme ) ); PushRedo(); // Add it redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditInsertWordBefore( void ) { if ( GetMode() != MODE_PHONEMES ) return; CWordTag *cw = GetSelectedWord(); if ( !cw ) return; float gap = GetTimeGapToNextWord( false, cw ); if ( gap < MINIMUM_WORD_GAP ) { Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap ); return; } // Don't have really long words gap = min( gap, DEFAULT_WORD_LENGTH ); int clicked = IndexOfWord( cw ); if ( clicked < 0 ) { Con_Printf( "EditInsertWordBefore: word not in sentence!!!\n" ); Assert( 0 ); return; } CInputParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Insert Word" ); strcpy( params.m_szPrompt, "Word:" ); strcpy( params.m_szInputText, "" ); params.m_nLeft = -1; params.m_nTop = -1; params.m_bPositionDialog = true; if ( params.m_bPositionDialog ) { RECT rcWord; GetWordRect( cw, rcWord ); // Convert to screen coords POINT pt; pt.x = rcWord.left; pt.y = rcWord.top; ClientToScreen( (HWND)getHandle(), &pt ); params.m_nLeft = pt.x; params.m_nTop = pt.y; } int iret = InputProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } if ( strlen( params.m_szInputText ) <= 0 ) { return; } int wordCount = CSentence::CountWords( params.m_szInputText ); if ( wordCount > 1 ) { Con_Printf( "Can only insert one word at a time, %s has %i words in it!\n", params.m_szInputText, wordCount ); return; } SetDirty( true ); PushUndo(); CWordTag newword; newword.SetWord( params.m_szInputText ); newword.m_flEndTime = cw->m_flStartTime; newword.m_flStartTime = cw->m_flStartTime - gap; newword.m_bSelected = true; cw->m_bSelected = false; m_Tags.m_Words.InsertBefore( clicked, new CWordTag( newword ) ); PushRedo(); // Add it redraw(); // Jump to phoneme insertion UI EditInsertFirstPhonemeOfWord(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditInsertWordAfter( void ) { if ( GetMode() != MODE_PHONEMES ) return; CWordTag *cw = GetSelectedWord(); if ( !cw ) return; float gap = GetTimeGapToNextWord( true, cw ); if ( gap < MINIMUM_WORD_GAP ) { Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap ); return; } // Don't have really long words gap = min( gap, DEFAULT_WORD_LENGTH ); int clicked = IndexOfWord( cw ); if ( clicked < 0 ) { Con_Printf( "EditInsertWordBefore: word not in sentence!!!\n" ); Assert( 0 ); return; } CInputParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Insert Word" ); strcpy( params.m_szPrompt, "Word:" ); strcpy( params.m_szInputText, "" ); params.m_nLeft = -1; params.m_nTop = -1; params.m_bPositionDialog = true; if ( params.m_bPositionDialog ) { RECT rcWord; GetWordRect( cw, rcWord ); // Convert to screen coords POINT pt; pt.x = rcWord.left; pt.y = rcWord.top; ClientToScreen( (HWND)getHandle(), &pt ); params.m_nLeft = pt.x; params.m_nTop = pt.y; } int iret = InputProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } if ( strlen( params.m_szInputText ) <= 0 ) { return; } int wordCount = CSentence::CountWords( params.m_szInputText ); if ( wordCount > 1 ) { Con_Printf( "Can only insert one word at a time, %s has %i words in it!\n", params.m_szInputText, wordCount ); return; } SetDirty( true ); PushUndo(); CWordTag newword; newword.SetWord( params.m_szInputText ); newword.m_flEndTime = cw->m_flEndTime + gap; newword.m_flStartTime = cw->m_flEndTime; newword.m_bSelected = true; cw->m_bSelected = false; CWordTag *w = new CWordTag( newword ); Assert( w ); if ( w ) { m_Tags.m_Words.InsertAfter( clicked, w ); } PushRedo(); // Add it redraw(); EditInsertFirstPhonemeOfWord(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditDeletePhoneme( void ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedPhonemeCount < 1 ) { return; } SetDirty( true ); PushUndo(); for ( int i = m_Tags.m_Words.Count() - 1; i >= 0; i-- ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = word->m_Phonemes.Count() - 1; j >= 0; j-- ) { CPhonemeTag *p = word->m_Phonemes[ j ]; if ( !p || !p->m_bSelected ) continue; // Delete it word->m_Phonemes.Remove( j ); } } PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditDeleteWord( void ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedWordCount < 1 ) { return; } SetDirty( true ); PushUndo(); for ( int i = m_Tags.m_Words.Count() - 1; i >= 0; i-- ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word || !word->m_bSelected ) continue; m_Tags.m_Words.Remove( i ); } PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::PlayEditedWave( bool selection /* = false */ ) { StopPlayback(); if ( !m_pWaveFile ) return; // Make sure phonemes are loaded FacePoser_EnsurePhonemesLoaded(); SaveLinguisticData(); SetScrubTime( 0.0f ); SetScrubTargetTime( m_pWaveFile->GetRunningLength() ); } typedef struct channel_s { int leftvol; int rightvol; int rleftvol; int rrightvol; float pitch; } channel_t; bool PhonemeEditor::CreateCroppedWave( char const *filename, int startsample, int endsample ) { Assert( sound ); CAudioWaveOutput *pWaveOutput = ( CAudioWaveOutput * )sound->GetAudioOutput(); if ( !pWaveOutput ) return false; CAudioSource *wave = sound->LoadSound( m_WorkFile.m_szWaveFile ); if ( !wave ) return false; CAudioMixer *pMixer = wave->CreateMixer(); if ( !pMixer ) return false; // Create out put file OutFileRIFF riffout( filename, io_out ); // Create output iterator IterateOutputRIFF store( riffout ); WAVEFORMATEX format; format.cbSize = sizeof( format ); format.wFormatTag = WAVE_FORMAT_PCM; format.nAvgBytesPerSec = (int)wave->SampleRate(); format.nChannels = 1; format.wBitsPerSample = 8; format.nSamplesPerSec = (int)wave->SampleRate(); format.nBlockAlign = 1; // (int)wave->SampleSize(); store.ChunkWrite( WAVE_FMT, &format, sizeof( format ) ); // Pull in data and write it out int currentsample = 0; store.ChunkStart( WAVE_DATA ); // need a bit of space short samples[ 2 ]; channel_t channel; channel.leftvol = 255; channel.rightvol = 255; channel.pitch = 1.0; while ( 1 ) { pWaveOutput->m_audioDevice.MixBegin(); if ( !pMixer->MixDataToDevice( &pWaveOutput->m_audioDevice, &channel, currentsample, 1, wave->SampleRate(), true ) ) break; pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, 1 ); currentsample = pMixer->GetSamplePosition(); if ( currentsample >= startsample && currentsample <= endsample ) { // left + right (2 channels ) * 16 bits float s1 = (float)( samples[ 0 ] >> 8 ); float s2 = (float)( samples[ 1 ] >> 8 ); float avg = ( s1 + s2 ) / 2.0f; unsigned char chopped = (unsigned char)( avg + 127.0f ); store.ChunkWriteData( &chopped, sizeof( byte ) ); } } store.ChunkFinish(); delete pMixer; delete wave; return true; } void PhonemeEditor::SentenceFromString( CSentence& sentence, char const *str ) { sentence.Reset(); if ( !str || !str[0] || CSentence::CountWords( str ) == 0 ) { return; } char word[ 256 ]; unsigned char const *in = (unsigned char *)str; char *out = word; while ( *in ) { if ( *in > 32 ) { *out++ = *in++; } else { *out = 0; while ( *in && *in <= 32 ) { in++; } if ( strlen( word ) > 0 ) { CWordTag *w = new CWordTag( (char *)word ); Assert( w ); if ( w ) { sentence.m_Words.AddToTail( w ); } } out = word; } } *out = 0; if ( strlen( word ) > 0 ) { CWordTag *w = new CWordTag( (char *)word ); Assert( w ); if ( w ) { sentence.m_Words.AddToTail( w ); } } sentence.SetText( str ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::RedoPhonemeExtractionSelected( void ) { if ( GetMode() != MODE_PHONEMES ) return; if ( !CheckSpeechAPI() ) return; if ( !m_pWaveFile ) { Con_Printf( "Can't redo extraction, no wavefile loaded!\n" ); Assert( 0 ); return; } if ( !m_bSelectionActive ) { Con_Printf( "Please select a portion of the .wav from which to re-extract phonemes\n" ); return; } // Now copy data back into original list, offsetting by samplestart time float numsamples = m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate(); float selectionstarttime = 0.0f; if ( numsamples > 0.0f ) { // Convert sample #'s to time selectionstarttime = ( m_nSelection[ 0 ] / numsamples ) * m_pWaveFile->GetRunningLength(); selectionstarttime = max( 0.0f, selectionstarttime ); } else { Con_Printf( "Original .wav file %s has no samples!!!\n", m_WorkFile.m_szWaveFile ); return; } int i; // Create input array of just selected words CSentence m_InputWords; CSentence m_Results; CountSelected(); bool usingselection = true; if ( m_nSelectedWordCount == 0 ) { // Allow user to type in text // Build word string char wordstring[ 1024 ]; strcpy( wordstring, "" ); CInputParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Phrase Word List" ); strcpy( params.m_szPrompt, "Phrase" ); strcpy( params.m_szInputText, wordstring ); if ( !InputProperties( ¶ms ) ) return; if ( strlen( params.m_szInputText ) <= 0 ) { Con_ErrorPrintf( "Edit word list: No words entered!\n" ); return; } SentenceFromString( m_InputWords, params.m_szInputText ); if ( m_InputWords.m_Words.Count() == 0 ) { Con_Printf( "You must either select words, or type in a set of words in order to extract phonemes!\n" ); return; } usingselection = false; } else { if ( !AreSelectedWordsContiguous() ) { Con_Printf( "Can only redo extraction on a contiguous subset of words\n" ); return; } char temp[ 4096 ]; bool killspace = false; Q_strncpy( temp, m_InputWords.GetText(), sizeof( temp ) ); // Iterate existing words, looking for contiguous selected words for ( i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word || !word->m_bSelected ) continue; // Now add "clean slate" to input list m_InputWords.m_Words.AddToTail( new CWordTag( *word ) ); Q_strncat( temp, word->GetWord(), sizeof( temp ), COPY_ALL_CHARACTERS ); Q_strncat( temp, " ", sizeof( temp ), COPY_ALL_CHARACTERS ); killspace = true; } // Kill terminal space character int len = Q_strlen( temp ); if ( killspace && ( len >= 1 ) ) { Assert( temp[ len -1 ] == ' ' ); temp[ len - 1 ] = 0; } m_InputWords.SetText( temp ); } m_nLastExtractionResult = SR_RESULT_NORESULT; char szCroppedFile[ 512 ]; char szBaseFile[ 512 ]; Q_StripExtension( m_WorkFile.m_szWaveFile, szBaseFile, sizeof( szBaseFile ) ); Q_snprintf( szCroppedFile, sizeof( szCroppedFile ), "%s%s_work1.wav", m_WorkFile.m_szBasePath, szBaseFile ); filesystem->RemoveFile( szCroppedFile, "GAME" ); if ( !CreateCroppedWave( szCroppedFile, m_nSelection[ 0 ], m_nSelection[ 1 ] ) ) { Con_Printf( "Unable to create cropped wave file %s from samples %i to %i\n", szCroppedFile, m_nSelection[ 0 ], m_nSelection[ 1 ] ); return; } CAudioSource *m_pCroppedWave = sound->LoadSound( szCroppedFile ); if ( !m_pCroppedWave ) { Con_Printf( "Unable to load cropped wave file %s from samples %i to %i\n" ); return; } // Save any pending stuff SaveLinguisticData(); // Store off copy of complete sentence m_TagsExt = m_Tags; char filename[ 512 ]; Q_snprintf( filename, sizeof( filename ), "%s%s", m_WorkFile.m_szBasePath, szCroppedFile ); m_nLastExtractionResult = m_pPhonemeExtractor->Extract( filename, (int)( m_pCroppedWave->GetRunningLength() * m_pCroppedWave->SampleRate() * m_pCroppedWave->TrueSampleSize() ), Con_Printf, m_InputWords, m_Results ); if ( m_InputWords.m_Words.Count() != m_Results.m_Words.Count() ) { Con_Printf( "Extraction returned %i words, source had %i, try adjusting selection\n", m_Results.m_Words.Count(), m_InputWords.m_Words.Count() ); filesystem->RemoveFile( filename, "GAME" ); redraw(); return; } float bytespersecond = m_pCroppedWave->SampleRate() * m_pCroppedWave->TrueSampleSize(); // Tracker 57389: // Total hack to fix a bug where the Lipsinc extractor is messing up the # channels on 16 bit stereo waves if ( m_pPhonemeExtractor->GetAPIType() == SPEECH_API_LIPSINC && m_pCroppedWave->IsStereoWav() && m_pCroppedWave->SampleSize() == 16 ) { bytespersecond *= 2.0f; } // Now convert byte offsets to times for ( i = 0; i < m_Results.m_Words.Count(); i++ ) { CWordTag *tag = m_Results.m_Words[ i ]; Assert( tag ); if ( !tag ) continue; tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond; tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond; for ( int j = 0; j < tag->m_Phonemes.Count(); j++ ) { CPhonemeTag *ptag = tag->m_Phonemes[ j ]; Assert( ptag ); if ( !ptag ) continue; ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond ); ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond ); } } if ( usingselection ) { // Copy data into m_TagsExt, offseting times by selectionstarttime CWordTag *from; CWordTag *to; int fromWord = 0; for ( i = 0; i < m_TagsExt.m_Words.Count() ; i++ ) { to = m_TagsExt.m_Words[ i ]; if ( !to || !to->m_bSelected ) continue; // Found start of contiguous run if ( fromWord >= m_Results.m_Words.Count() ) break; from = m_Results.m_Words[ fromWord++ ]; Assert( from ); if ( !from ) continue; // Remove all phonemes from destination while ( to->m_Phonemes.Count() > 0 ) { CPhonemeTag *p = to->m_Phonemes[ 0 ]; Assert( p ); to->m_Phonemes.Remove( 0 ); delete p; } // Now copy phonemes from source for ( int j = 0; j < from->m_Phonemes.Count(); j++ ) { CPhonemeTag *fromPhoneme = from->m_Phonemes[ j ]; Assert( fromPhoneme ); if ( !fromPhoneme ) continue; CPhonemeTag newPhoneme( *fromPhoneme ); // Offset start time newPhoneme.AddStartTime( selectionstarttime ); newPhoneme.AddEndTime( selectionstarttime ); // Add it back in with corrected timing data CPhonemeTag *p = new CPhonemeTag( newPhoneme ); Assert( p ); if ( p ) { to->m_Phonemes.AddToTail( p ); } } // Done if ( fromWord >= m_Results.m_Words.Count() ) break; } } else { // Find word just before starting point of selection and // place input words into list starting that that point int startWord = 0; CWordTag *firstWordOfPhrase = m_Results.m_Words[ 0 ]; Assert( firstWordOfPhrase ); for ( ; startWord < m_TagsExt.m_Words.Count(); startWord++ ) { CWordTag *w = m_TagsExt.m_Words[ startWord ]; Assert( w ); if ( !w ) continue; if ( w->m_flStartTime > firstWordOfPhrase->m_flStartTime + selectionstarttime ) break; } for ( i = 0; i < m_Results.m_Words.Count(); i++ ) { CWordTag *from = m_Results.m_Words[ i ]; Assert( from ); if ( !from ) continue; CWordTag *to = new CWordTag( *from ); Assert( to ); to->m_flStartTime += selectionstarttime; to->m_flEndTime += selectionstarttime; // Now adjust phoneme times for ( int j = 0; j < to->m_Phonemes.Count(); j++ ) { CPhonemeTag *toPhoneme = to->m_Phonemes[ j ]; Assert( toPhoneme ); if ( !toPhoneme ) continue; // Offset start time toPhoneme->AddStartTime( selectionstarttime ); toPhoneme->AddEndTime( selectionstarttime ); } m_TagsExt.m_Words.InsertBefore( startWord++, to ); } } Con_Printf( "Cleaning up...\n" ); filesystem->RemoveFile( filename, "GAME" ); SetFocus( (HWND)getHandle() ); redraw(); } void PhonemeEditor::RedoPhonemeExtraction( void ) { if ( GetMode() != MODE_PHONEMES ) return; if ( !CheckSpeechAPI() ) return; m_nLastExtractionResult = SR_RESULT_NORESULT; if ( !m_pWaveFile ) return; SaveLinguisticData(); // Send m_WorkFile.m_szWorkingFile to extractor and retrieve resulting data // m_TagsExt.Reset(); Assert( m_pPhonemeExtractor ); char filename[ 512 ]; Q_snprintf( filename, sizeof( filename ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); m_nLastExtractionResult = m_pPhonemeExtractor->Extract( filename, (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize() ), Con_Printf, m_Tags, m_TagsExt ); float bytespersecond = m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize(); // Now convert byte offsets to times int i; for ( i = 0; i < m_TagsExt.m_Words.Count(); i++ ) { CWordTag *tag = m_TagsExt.m_Words[ i ]; Assert( tag ); if ( !tag ) continue; tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond; tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond; for ( int j = 0; j < tag->m_Phonemes.Count(); j++ ) { CPhonemeTag *ptag = tag->m_Phonemes[ j ]; Assert( ptag ); if ( !ptag ) continue; ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond ); ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond ); } } SetFocus( (HWND)getHandle() ); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::Deselect( void ) { m_nSelection[ 0 ] = m_nSelection[ 1 ] = 0; m_bSelectionActive = false; } void PhonemeEditor::ITER_SelectSpanningWords( CWordTag *word, float amount ) { Assert( word ); word->m_bSelected = false; if ( !m_bSelectionActive ) return; if ( !m_pWaveFile ) return; float numsamples = m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate(); if ( numsamples > 0.0f ) { // Convert sample #'s to time float starttime = ( m_nSelection[ 0 ] / numsamples ) * m_pWaveFile->GetRunningLength(); float endtime = ( m_nSelection[ 1 ] / numsamples ) * m_pWaveFile->GetRunningLength(); if ( word->m_flEndTime >= starttime && word->m_flStartTime <= endtime ) { word->m_bSelected = true; m_bWordsActive = true; } } } //----------------------------------------------------------------------------- // Purpose: // Input : start - // end - //----------------------------------------------------------------------------- void PhonemeEditor::SelectSamples( int start, int end ) { if ( !m_pWaveFile ) return; // Make sure order is correct if ( end < start ) { int temp = end; end = start; start = temp; } Deselect(); m_nSelection[ 0 ] = start; m_nSelection[ 1 ] = end; m_bSelectionActive = true; // Select any words that span the selection // TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); redraw(); } void PhonemeEditor::FinishMoveSelection( int startx, int mx ) { if ( !m_pWaveFile ) return; int sampleStart = GetSampleForMouse( startx ); int sampleEnd = GetSampleForMouse( mx ); int delta = sampleEnd - sampleStart; for ( int i = 0; i < 2; i++ ) { m_nSelection[ i ] += delta; } // Select any words that span the selection // TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); redraw(); } void PhonemeEditor::FinishMoveSelectionStart( int startx, int mx ) { if ( !m_pWaveFile ) return; int sampleStart = GetSampleForMouse( startx ); int sampleEnd = GetSampleForMouse( mx ); int delta = sampleEnd - sampleStart; m_nSelection[ 0 ] += delta; if ( m_nSelection[ 0 ] >= m_nSelection[ 1 ] ) { Deselect(); } // Select any words that span the selection // TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); redraw(); } void PhonemeEditor::FinishMoveSelectionEnd( int startx, int mx ) { if ( !m_pWaveFile ) return; int sampleStart = GetSampleForMouse( startx ); int sampleEnd = GetSampleForMouse( mx ); int delta = sampleEnd - sampleStart; m_nSelection[ 1 ] += delta; if ( m_nSelection[ 1 ] <= m_nSelection[ 0 ] ) { Deselect(); } // Select any words that span the selection // TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : startx - // mx - //----------------------------------------------------------------------------- void PhonemeEditor::FinishSelect( int startx, int mx ) { if ( !m_pWaveFile ) return; // Don't select really small areas if ( abs( startx - mx ) < 2 ) return; int sampleStart = GetSampleForMouse( startx ); int sampleEnd = GetSampleForMouse( mx ); SelectSamples( sampleStart, sampleEnd ); } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::IsMouseOverSamples( int mx, int my ) { if ( GetMode() != MODE_PHONEMES ) return false; // Deterime if phoneme boundary is under the cursor // if ( !m_pWaveFile ) return false; RECT rc; GetWorkspaceRect( rc ); // Over tag if ( my >= TAG_TOP && my <= TAG_BOTTOM ) return false; if ( IsMouseOverPhonemeRow( my ) ) return false; if ( IsMouseOverWordRow( my ) ) return false; RECT rcWord; GetWordTrayTopBottom( rcWord ); RECT rcPhoneme; GetPhonemeTrayTopBottom( rcPhoneme ); if ( my < rcWord.bottom ) return false; if ( my > rcPhoneme.top ) return false; return true; } void PhonemeEditor::GetScreenStartAndEndTime( float &starttime, float& endtime ) { starttime = m_nLeftOffset / GetPixelsPerSecond(); endtime = w2() / GetPixelsPerSecond() + starttime; } float PhonemeEditor::GetTimePerPixel( void ) { RECT rc; GetWorkspaceRect( rc ); float starttime, endtime; GetScreenStartAndEndTime( starttime, endtime ); if ( rc.right - rc.left <= 0 ) { return ( endtime - starttime ); } float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); return timeperpixel; } int PhonemeEditor::GetPixelForSample( int sample ) { RECT rc; GetWorkspaceRect( rc ); if ( !m_pWaveFile ) return rc.left; // Determine start/stop positions int totalsamples = (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() ); if ( totalsamples <= 0 ) { return rc.left; } float starttime, endtime; GetScreenStartAndEndTime( starttime, endtime ); float sampleFrac = (float)sample / (float)totalsamples; float sampleTime = sampleFrac * (float)m_pWaveFile->GetRunningLength(); if ( endtime - starttime < 0.0f ) { return rc.left; } float windowFrac = ( sampleTime - starttime ) / ( endtime - starttime ); return rc.left + (int)( windowFrac * ( rc.right - rc.left ) ); } int PhonemeEditor::GetSampleForMouse( int mx ) { if ( !m_pWaveFile ) return 0; RECT rc; GetWorkspaceRect( rc ); // Determine start/stop positions int totalsamples = (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() ); float starttime, endtime; GetScreenStartAndEndTime( starttime, endtime ); if ( GetPixelsPerSecond() <= 0 ) return 0; // Start and end times float clickTime = (float)mx / GetPixelsPerSecond() + starttime; // What sample do these correspond to if ( (float)m_pWaveFile->GetRunningLength() <= 0.0f ) return 0; int sampleNumber = (int) ( (float)totalsamples * clickTime / (float)m_pWaveFile->GetRunningLength() ); return sampleNumber; } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::IsMouseOverSelection( int mx, int my ) { if ( GetMode() != MODE_PHONEMES ) return false; if ( !m_pWaveFile ) return false; if ( !m_bSelectionActive ) return false; if ( !IsMouseOverSamples( mx, my ) ) return false; int sampleNumber = GetSampleForMouse( mx ); if ( sampleNumber >= m_nSelection[ 0 ] - 3 && sampleNumber <= m_nSelection[ 1 ] + 3 ) { return true; } return false; } bool PhonemeEditor::IsMouseOverSelectionStartEdge( mxEvent *event ) { if ( GetMode() != MODE_PHONEMES ) return false; if ( !m_pWaveFile ) return false; int mx, my; mx = (short)event->x; my = (short)event->y; if ( !(event->modifiers & mxEvent::KeyCtrl ) ) return false; if ( !IsMouseOverSelection( mx, my ) ) return false; int sample = GetSampleForMouse( mx ); int mouse_tolerance = 5; RECT rc; GetWorkspaceRect( rc ); // Determine start/stop positions float timeperpixel = GetTimePerPixel(); int samplesperpixel = (int)( timeperpixel * m_pWaveFile->SampleRate() ); if ( abs( sample - m_nSelection[ 0 ] ) < mouse_tolerance * samplesperpixel ) { return true; } return false; } bool PhonemeEditor::IsMouseOverSelectionEndEdge( mxEvent *event ) { if ( GetMode() != MODE_PHONEMES ) return false; if ( !m_pWaveFile ) return false; int mx, my; mx = (short)event->x; my = (short)event->y; if ( !(event->modifiers & mxEvent::KeyCtrl ) ) return false; if ( !IsMouseOverSelection( mx, my ) ) return false; int sample = GetSampleForMouse( mx ); int mouse_tolerance = 5; RECT rc; GetWorkspaceRect( rc ); if ( GetPixelsPerSecond() <= 0.0f ) return false; if ( ( rc.right - rc.left ) <= 0 ) return false; // Determine start/stop positions float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); int samplesperpixel = (int)( timeperpixel * m_pWaveFile->SampleRate() ); if ( abs( sample - m_nSelection[ 1 ] ) < mouse_tolerance * samplesperpixel ) { return true; } return false; } void PhonemeEditor::OnImport() { char filename[ 512 ]; if ( !FacePoser_ShowOpenFileNameDialog( filename, sizeof( filename ), "sound", "*" WORD_DATA_EXTENSION ) ) { return; } ImportValveDataChunk( filename ); } void PhonemeEditor::OnExport() { if ( !m_pWaveFile ) return; char filename[ 512 ]; if ( !FacePoser_ShowSaveFileNameDialog( filename, sizeof( filename ), "sound", "*" WORD_DATA_EXTENSION ) ) { return; } Q_SetExtension( filename, WORD_DATA_EXTENSION, sizeof( filename ) ); ExportValveDataChunk( filename ); } //----------------------------------------------------------------------------- // Purpose: // Input : store - //----------------------------------------------------------------------------- void PhonemeEditor::StoreValveDataChunk( IterateOutputRIFF& store ) { // Buffer and dump data CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); m_Tags.SaveToBuffer( buf ); // Copy into store store.ChunkWriteData( buf.Base(), buf.TellPut() ); } //----------------------------------------------------------------------------- // Purpose: // Input : *tempfile - //----------------------------------------------------------------------------- void PhonemeEditor::ExportValveDataChunk( char const *tempfile ) { if ( m_Tags.m_Words.Count() <= 0 ) { Con_ErrorPrintf( "PhonemeEditor::ExportValveDataChunk: Sentence has no word data\n" ); return; } FileHandle_t fh = filesystem->Open( tempfile, "wb" ); if ( !fh ) { Con_ErrorPrintf( "PhonemeEditor::ExportValveDataChunk: Unable to write to %s (read-only?)\n", tempfile ); return; } else { // Buffer and dump data CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); m_Tags.SaveToBuffer( buf ); filesystem->Write( buf.Base(), buf.TellPut(), fh ); filesystem->Close(fh); Con_Printf( "Exported %i words to %s\n", m_Tags.m_Words.Count(), tempfile ); } } //----------------------------------------------------------------------------- // Purpose: // Input : *tempfile - //----------------------------------------------------------------------------- void PhonemeEditor::ImportValveDataChunk( char const *tempfile ) { FileHandle_t fh = filesystem->Open( tempfile, "rb" ); if ( !fh ) { Con_ErrorPrintf( "PhonemeEditor::ImportValveDataChunk: Unable to read from %s\n", tempfile ); return; } int len = filesystem->Size( fh ); if ( len <= 4 ) { Con_ErrorPrintf( "PhonemeEditor::ImportValveDataChunk: File %s has length 0\n", tempfile ); return; } ClearExtracted(); unsigned char *buf = new unsigned char[ len + 1 ]; filesystem->Read( buf, len, fh ); filesystem->Close( fh ); m_TagsExt.InitFromDataChunk( (void *)( buf ), len ); delete[] buf; Con_Printf( "Imported %i words from %s\n", m_TagsExt.m_Words.Count(), tempfile ); redraw(); } //----------------------------------------------------------------------------- // Purpose: Copy file over, but update phoneme lump with new data //----------------------------------------------------------------------------- void PhonemeEditor::SaveLinguisticData( void ) { if ( !m_pWaveFile ) return; InFileRIFF riff( m_WorkFile.m_szWaveFile, io_in ); Assert( riff.RIFFName() == RIFF_WAVE ); // set up the iterator for the whole file (root RIFF is a chunk) IterateRIFF walk( riff, riff.RIFFSize() ); char fullout[ 512 ]; Q_snprintf( fullout, sizeof( fullout ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); OutFileRIFF riffout( fullout, io_out ); IterateOutputRIFF store( riffout ); bool formatset = false; WAVEFORMATEX format; bool wordtrackwritten = false; // Walk input chunks and copy to output while ( walk.ChunkAvailable() ) { unsigned int originalPos = store.ChunkGetPosition(); store.ChunkStart( walk.ChunkName() ); bool skipchunk = false; switch ( walk.ChunkName() ) { case WAVE_VALVEDATA: // Overwrite data StoreValveDataChunk( store ); wordtrackwritten = true; break; case WAVE_FMT: { formatset = true; char *buffer = new char[ walk.ChunkSize() ]; Assert( buffer ); walk.ChunkRead( buffer ); format = *(WAVEFORMATEX *)buffer; store.ChunkWriteData( buffer, walk.ChunkSize() ); delete[] buffer; } break; case WAVE_DATA: { Assert( formatset ); char *buffer = new char[ walk.ChunkSize() ]; Assert( buffer ); walk.ChunkRead( buffer ); // Resample it ResampleChunk( store, (void *)&format, walk.ChunkName(), buffer, walk.ChunkSize() ); delete[] buffer; } break; default: store.CopyChunkData( walk ); break; } store.ChunkFinish(); if ( skipchunk ) { store.ChunkSetPosition( originalPos ); } walk.ChunkNext(); } if ( !wordtrackwritten ) { store.ChunkStart( WAVE_VALVEDATA ); StoreValveDataChunk( store ); store.ChunkFinish(); } } //----------------------------------------------------------------------------- // Purpose: Copy phoneme data in from wave file we sent for resprocessing //----------------------------------------------------------------------------- void PhonemeEditor::RetrieveLinguisticData( void ) { if ( !m_pWaveFile ) return; m_Tags.Reset(); ReadLinguisticTags(); redraw(); } bool PhonemeEditor::StopPlayback( void ) { bool bret = false; if ( m_pWaveFile ) { SetScrubTargetTime( m_flScrub ); if ( sound->IsSoundPlaying( m_pMixer ) ) { sound->StopAll(); bret = true; } } sound->Flush(); return bret; } CPhonemeTag *PhonemeEditor::GetPhonemeTagUnderMouse( int mx, int my ) { if ( GetMode() != MODE_PHONEMES ) return NULL; if ( !m_pWaveFile ) return NULL; // FIXME: Don't read from file, read from arrays after LISET finishes // Deterime if phoneme boundary is under the cursor // RECT rc; GetWorkspaceRect( rc ); if ( !IsMouseOverPhonemeRow( my ) ) return NULL; if ( GetPixelsPerSecond() <= 0 ) return NULL; float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; if ( endtime - starttime <= 0.0f ) return NULL; for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; Assert( word ); if ( !word ) continue; for ( int k = 0; k < word->m_Phonemes.Count(); k++ ) { CPhonemeTag *pPhoneme = word->m_Phonemes[ k ]; Assert( pPhoneme ); if ( !pPhoneme ) continue; float t1 = pPhoneme->GetStartTime(); float t2 = pPhoneme->GetEndTime(); float frac1 = ( t1 - starttime ) / ( endtime - starttime ); float frac2 = ( t2 - starttime ) / ( endtime - starttime ); frac1 = min( 1.0f, frac1 ); frac1 = max( 0.0f, frac1 ); frac2 = min( 1.0f, frac2 ); frac2 = max( 0.0f, frac2 ); if ( frac1 == frac2 ) continue; int x1 = ( int )( frac1 * w2() ); int x2 = ( int )( frac2 * w2() ); if ( mx >= x1 && mx <= x2 ) { return pPhoneme; } } } return NULL; } CWordTag *PhonemeEditor::GetWordTagUnderMouse( int mx, int my ) { if ( GetMode() != MODE_PHONEMES ) return NULL; // Deterime if phoneme boundary is under the cursor // if ( !m_pWaveFile ) return NULL; RECT rc; GetWorkspaceRect( rc ); if ( !IsMouseOverWordRow( my ) ) return NULL; if ( GetPixelsPerSecond() <= 0 ) return NULL; float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; if ( endtime - starttime <= 0.0f ) return NULL; for ( int k = 0; k < m_Tags.m_Words.Count(); k++ ) { CWordTag *word = m_Tags.m_Words[ k ]; Assert( word ); if ( !word ) continue; float t1 = word->m_flStartTime; float t2 = word->m_flEndTime; float frac1 = ( t1 - starttime ) / ( endtime - starttime ); float frac2 = ( t2 - starttime ) / ( endtime - starttime ); frac1 = min( 1.0f, frac1 ); frac1 = max( 0.0f, frac1 ); frac2 = min( 1.0f, frac2 ); frac2 = max( 0.0f, frac2 ); if ( frac1 == frac2 ) continue; int x1 = ( int )( frac1 * w2() ); int x2 = ( int )( frac2 * w2() ); if ( mx >= x1 && mx <= x2 ) { return word; } } return NULL; } void PhonemeEditor::DeselectWords( void ) { if ( GetMode() != MODE_PHONEMES ) return; for ( int i = 0 ; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *w = m_Tags.m_Words[ i ]; Assert( w ); if ( !w ) continue; w->m_bSelected = false; } } void PhonemeEditor::DeselectPhonemes( void ) { if ( GetMode() != MODE_PHONEMES ) return; for ( int w = 0 ; w < m_Tags.m_Words.Count(); w++ ) { CWordTag *word = m_Tags.m_Words[ w ]; Assert( word ); if ( !word ) continue; for ( int i = 0 ; i < word->m_Phonemes.Count(); i++ ) { CPhonemeTag *pt = word->m_Phonemes[ i ]; Assert( pt ); if ( !pt ) continue; pt->m_bSelected = false; } } } void PhonemeEditor::SnapWords( void ) { if ( GetMode() != MODE_PHONEMES ) return; if ( m_Tags.m_Words.Count() < 2 ) { Con_Printf( "Can't snap, need at least two contiguous selected words\n" ); return; } SetDirty( true ); PushUndo(); for ( int i = 0; i < m_Tags.m_Words.Count() - 1; i++ ) { CWordTag *current = m_Tags.m_Words[ i ]; CWordTag *next = m_Tags.m_Words[ i + 1 ]; Assert( current && next ); if ( !current->m_bSelected || !next->m_bSelected ) continue; // Move next word to end of current next->m_flStartTime = current->m_flEndTime; } PushRedo(); redraw(); } void PhonemeEditor::SeparateWords( void ) { if ( GetMode() != MODE_PHONEMES ) return; if ( GetPixelsPerSecond() <= 0.0f ) return; if ( m_Tags.m_Words.Count() < 2 ) { Con_Printf( "Can't separate, need at least two contiguous selected words\n" ); return; } // Three pixels double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 6; SetDirty( true ); PushUndo(); for ( int i = 0; i < m_Tags.m_Words.Count() - 1; i++ ) { CWordTag *current = m_Tags.m_Words[ i ]; CWordTag *next = m_Tags.m_Words[ i + 1 ]; Assert( current && next ); if ( !current->m_bSelected || !next->m_bSelected ) continue; // Close enough? if ( fabs( current->m_flEndTime - next->m_flStartTime ) > time_epsilon ) { Con_Printf( "Can't split %s and %s, already split apart\n", current->GetWord(), next->GetWord() ); continue; } // Offset next word start time a bit next->m_flStartTime += time_epsilon; break; } PushRedo(); redraw(); } void PhonemeEditor::CreateEvenWordDistribution( const char *wordlist ) { if ( GetMode() != MODE_PHONEMES ) return; if( !m_pWaveFile ) return; Assert( wordlist ); if ( !wordlist ) return; m_Tags.CreateEventWordDistribution( wordlist, m_pWaveFile->GetRunningLength() ); redraw(); } void PhonemeEditor::EditWordList( void ) { if ( GetMode() != MODE_PHONEMES ) return; if ( !m_pWaveFile ) return; // Build word string char wordstring[ 1024 ]; strcpy( wordstring, m_Tags.GetText() ); CInputParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Word List" ); strcpy( params.m_szPrompt, "Sentence:" ); strcpy( params.m_szInputText, wordstring ); if ( !InputProperties( ¶ms ) ) return; if ( strlen( params.m_szInputText ) <= 0 ) { // Could be foreign language... Warning( "Edit word list: No words entered!\n" ); } SetDirty( true ); PushUndo(); // Clear any current LISET results ClearExtracted(); // Force text m_Tags.SetText( params.m_szInputText ); if ( m_Tags.m_Words.Count() == 0 ) { // First text we've seen, just distribute words evenly CreateEvenWordDistribution( params.m_szInputText ); // Redo liset RedoPhonemeExtraction(); } PushRedo(); SetFocus( (HWND)getHandle() ); redraw(); } //----------------------------------------------------------------------------- // Purpose: Overwrite original wave with changes //----------------------------------------------------------------------------- void PhonemeEditor::CommitChanges( void ) { SaveLinguisticData(); // Make it writable - if possible MakeFileWriteable( m_WorkFile.m_szWaveFile ); //Open a message box to warn the user if the file was unable to be made non-read only if ( !IsFileWriteable( m_WorkFile.m_szWaveFile ) ) { mxMessageBox( NULL, va( "Unable to save file '%s'. File is read-only or in use.", m_WorkFile.m_szWaveFile ), g_appTitle, MX_MB_OK ); } else { // Copy over and overwrite file FPCopyFile( m_WorkFile.m_szWorkingFile, m_WorkFile.m_szWaveFile, true ); Msg( "Changes saved to '%s'\n", m_WorkFile.m_szWaveFile ); SetDirty( false, false ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::LoadWaveFile( void ) { char filename[ 512 ]; if ( !FacePoser_ShowOpenFileNameDialog( filename, sizeof( filename ), "sound", "*.wav" ) ) { return; } StopPlayback(); // Strip out the game directory SetCurrentWaveFile( filename ); } void PhonemeEditor::SnapPhonemes( void ) { if ( GetMode() != MODE_PHONEMES ) return; SetDirty( true ); PushUndo(); CPhonemeTag *prev = NULL; for ( int w = 0; w < m_Tags.m_Words.Count(); w++ ) { CWordTag *word = m_Tags.m_Words[ w ]; Assert( word ); if ( !word ) continue; for ( int i = 0; i < word->m_Phonemes.Count(); i++ ) { CPhonemeTag *current = word->m_Phonemes[ i ]; Assert( current ); if ( current->m_bSelected ) { if (prev) { // More start of next to end of previous prev->SetEndTime( current->GetStartTime() ); } prev = current; } else { prev = NULL; } } } PushRedo(); redraw(); } void PhonemeEditor::SeparatePhonemes( void ) { if ( GetMode() != MODE_PHONEMES ) return; SetDirty( true ); PushUndo(); // Three pixels double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 6; CPhonemeTag *prev = NULL; for ( int w = 0; w < m_Tags.m_Words.Count(); w++ ) { CWordTag *word = m_Tags.m_Words[ w ]; Assert( word ); if ( !word ) continue; for ( int i = 0; i < word->m_Phonemes.Count(); i++ ) { CPhonemeTag *current = word->m_Phonemes[ i ]; Assert( current ); if ( current->m_bSelected ) { if ( prev ) { // Close enough? if ( fabs( prev->GetEndTime() - current->GetStartTime() ) > time_epsilon ) { Con_Printf( "Can't split already split apart\n" ); continue; } current->AddStartTime( time_epsilon ); } prev = current; } else { prev = NULL; } } } PushRedo(); redraw(); } bool PhonemeEditor::IsMouseOverWordRow( int my ) { if ( GetMode() != MODE_PHONEMES ) return false; RECT rc; GetWordTrayTopBottom( rc ); if ( my < rc.top ) return false; if ( my > rc.bottom ) return false; return true; } bool PhonemeEditor::IsMouseOverPhonemeRow( int my ) { if ( GetMode() != MODE_PHONEMES ) return false; RECT rc; GetPhonemeTrayTopBottom( rc ); if ( my < rc.top ) return false; if ( my > rc.bottom ) return false; return true; } void PhonemeEditor::GetPhonemeTrayTopBottom( RECT& rc ) { RECT wkrc; GetWorkspaceRect( wkrc ); rc.top = wkrc.bottom - 2 * m_nTickHeight; rc.bottom = wkrc.bottom - m_nTickHeight; } void PhonemeEditor::GetWordTrayTopBottom( RECT& rc ) { RECT wkrc; GetWorkspaceRect( wkrc ); rc.top = wkrc.top; rc.bottom = wkrc.top + m_nTickHeight; } int PhonemeEditor::GetMouseForTime( float time ) { RECT rc; GetWorkspaceRect( rc ); if ( GetPixelsPerSecond() < 0.0f ) return rc.left; float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; if ( endtime - starttime <= 0.0f ) return rc.left; float frac; frac = ( time - starttime ) / ( endtime - starttime ); return rc.left + ( int )( rc.right * frac ); } void PhonemeEditor::GetWordRect( const CWordTag *tag, RECT& rc ) { Assert( tag ); GetWordTrayTopBottom( rc ); rc.left = GetMouseForTime( tag->m_flStartTime ); rc.right = GetMouseForTime( tag->m_flEndTime ); } void PhonemeEditor::GetPhonemeRect( const CPhonemeTag *tag, RECT& rc ) { Assert( tag ); GetPhonemeTrayTopBottom( rc ); rc.left = GetMouseForTime( tag->GetStartTime() ); rc.right = GetMouseForTime( tag->GetEndTime() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::CommitExtracted( void ) { if ( GetMode() != MODE_PHONEMES ) return; m_nLastExtractionResult = SR_RESULT_NORESULT; if ( !m_TagsExt.m_Words.Count() ) return; SetDirty( true ); PushUndo(); m_Tags.Reset(); m_Tags = m_TagsExt; PushRedo(); ClearExtracted(); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::ClearExtracted( void ) { if ( GetMode() != MODE_PHONEMES ) return; m_nLastExtractionResult = SR_RESULT_NORESULT; m_TagsExt.Reset(); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : resultCode - // Output : const char //----------------------------------------------------------------------------- const char *PhonemeEditor::GetExtractionResultString( int resultCode ) { switch ( resultCode ) { case SR_RESULT_NORESULT: return "no extraction info."; case SR_RESULT_ERROR: return "an error occurred during extraction."; case SR_RESULT_SUCCESS: return "successful."; case SR_RESULT_FAILED: return "results retrieved, but full recognition failed."; default: break; } return "unknown result code."; } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // Output : CEventRelativeTag //----------------------------------------------------------------------------- CEventRelativeTag *PhonemeEditor::GetTagUnderMouse( int mx ) { if ( GetMode() != MODE_PHONEMES ) return NULL; // Figure out tag positions if ( !m_pEvent || !m_pWaveFile ) return NULL; RECT rc; GetWorkspaceRect( rc ); RECT rcTags = rc; if ( GetPixelsPerSecond() <= 0.0f ) return NULL; float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; if ( endtime - starttime < 0 ) return NULL; for ( int i = 0; i < m_pEvent->GetNumRelativeTags(); i++ ) { CEventRelativeTag *tag = m_pEvent->GetRelativeTag( i ); if ( !tag ) continue; // float tagtime = tag->GetPercentage() * m_pWaveFile->GetRunningLength(); if ( tagtime < starttime || tagtime > endtime ) continue; float frac = ( tagtime - starttime ) / ( endtime - starttime ); int left = rcTags.left + (int)( frac * ( float )( rcTags.right - rcTags.left ) + 0.5f ); if ( abs( mx - left ) < 10 ) return tag; } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::IsMouseOverTag( int mx, int my ) { if ( GetMode() != MODE_PHONEMES ) return false; if ( !IsMouseOverTagRow( my ) ) return false; CEventRelativeTag *tag = GetTagUnderMouse( mx ); if ( !tag ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: // Input : startx - // endx - //----------------------------------------------------------------------------- void PhonemeEditor::FinishEventTagDrag( int startx, int endx ) { if ( !m_pWaveFile ) return; if ( !m_pWaveFile->GetRunningLength() ) return; // Find starting tag CEventRelativeTag *tag = GetTagUnderMouse( startx ); if ( !tag ) return; if ( GetPixelsPerSecond() <= 0 ) return; // Convert mouse position to time float starttime = m_nLeftOffset / GetPixelsPerSecond(); float clicktime = (float)endx / GetPixelsPerSecond() + starttime; float percent = clicktime / m_pWaveFile->GetRunningLength(); percent = clamp( percent, 0.0f, 1.0f ); tag->SetPercentage( percent ); redraw(); if ( g_pChoreoView ) { g_pChoreoView->InvalidateLayout(); } } //----------------------------------------------------------------------------- // Purpose: // Input : my - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::IsMouseOverTagRow( int my ) { if ( GetMode() != MODE_PHONEMES ) return false; if ( my < TAG_TOP || my > TAG_BOTTOM ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // my - //----------------------------------------------------------------------------- void PhonemeEditor::ShowTagMenu( int mx, int my ) { if ( GetMode() != MODE_PHONEMES ) return; // Figure out tag positions if ( !m_pEvent || !m_pWaveFile ) return; if ( !IsMouseOverTagRow( my ) ) return; CEventRelativeTag *tag = GetTagUnderMouse( mx ); mxPopupMenu *pop = new mxPopupMenu(); if ( tag ) { pop->add( va( "Delete tag '%s'", tag->GetName() ), IDC_DELETETAG ); } else { pop->add( va( "Add tag..." ), IDC_ADDTAG ); } m_nClickX = mx; pop->popup( this, mx, my ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::DeleteTag( void ) { if ( GetMode() != MODE_PHONEMES ) return; // Figure out tag positions if ( !m_pEvent ) return; CEventRelativeTag *tag = GetTagUnderMouse( m_nClickX ); if ( !tag ) return; // Remove it m_pEvent->RemoveRelativeTag( tag->GetName() ); g_pChoreoView->InvalidateLayout(); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::AddTag( void ) { if ( GetMode() != MODE_PHONEMES ) return; // Figure out tag positions if ( !m_pEvent || !m_pWaveFile ) return; CInputParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Event Tag Name" ); strcpy( params.m_szPrompt, "Name:" ); strcpy( params.m_szInputText, "" ); if ( !InputProperties( ¶ms ) ) return; if ( strlen( params.m_szInputText ) <= 0 ) { Con_ErrorPrintf( "Event Tag Name: No name entered!\n" ); return; } // Convert mouse position to time float starttime = m_nLeftOffset / GetPixelsPerSecond(); float clicktime = (float)m_nClickX / GetPixelsPerSecond() + starttime; float percent = clicktime / m_pWaveFile->GetRunningLength(); percent = min( 1.0f, percent ); percent = max( 0.0f, percent ); m_pEvent->AddRelativeTag( params.m_szInputText, percent ); g_pChoreoView->InvalidateLayout(); SetFocus( (HWND)getHandle() ); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::ClearEvent( void ) { m_pEvent = NULL; redraw(); } void PhonemeEditor::TraverseWords( PEWORDITERFUNC pfn, float fparam ) { for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; (this->*pfn)( word, fparam ); } } void PhonemeEditor::TraversePhonemes( PEPHONEMEITERFUNC pfn, float fparam ) { for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; (this->*pfn)( phoneme, word, fparam ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : amount - //----------------------------------------------------------------------------- void PhonemeEditor::ITER_MoveSelectedWords( CWordTag *word, float amount ) { if ( !word->m_bSelected ) return; word->m_flStartTime += amount; word->m_flEndTime += amount; } void PhonemeEditor::ITER_MoveSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ) { if ( !phoneme->m_bSelected ) return; phoneme->AddStartTime( amount ); phoneme->AddEndTime( amount ); } void PhonemeEditor::ITER_ExtendSelectedPhonemeEndTimes( CPhonemeTag *phoneme, CWordTag *word, float amount ) { if ( !phoneme->m_bSelected ) return; if ( phoneme->GetEndTime() + amount <= phoneme->GetStartTime() ) return; phoneme->AddEndTime( amount ); // Fixme, check for extending into next phoneme } void PhonemeEditor::ITER_ExtendSelectedWordEndTimes( CWordTag *word, float amount ) { if ( !word->m_bSelected ) return; if ( word->m_flEndTime + amount <= word->m_flStartTime ) return; word->m_flEndTime += amount; // Fixme, check for extending into next word } void PhonemeEditor::ITER_AddFocusRectSelectedWords( CWordTag *word, float amount ) { if ( !word->m_bSelected ) return; RECT wordRect; GetWordRect( word, wordRect ); AddFocusRect( wordRect ); } void PhonemeEditor::ITER_AddFocusRectSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ) { if ( !phoneme->m_bSelected ) return; RECT phonemeRect; GetPhonemeRect( phoneme, phonemeRect ); AddFocusRect( phonemeRect ); } void PhonemeEditor::AddFocusRect( RECT& rc ) { RECT rcFocus = rc; POINT offset; offset.x = 0; offset.y = 0; ClientToScreen( (HWND)getHandle(), &offset ); OffsetRect( &rcFocus, offset.x, offset.y ); // Convert to screen space? CFocusRect fr; fr.m_rcFocus = rcFocus; fr.m_rcOrig = rcFocus; m_FocusRects.AddToTail( fr ); } void PhonemeEditor::CountSelected( void ) { m_nSelectedPhonemeCount = 0; m_nSelectedWordCount = 0; TraverseWords( &PhonemeEditor::ITER_CountSelectedWords, 0.0f ); TraversePhonemes( &PhonemeEditor::ITER_CountSelectedPhonemes, 0.0f ); } void PhonemeEditor::ITER_CountSelectedWords( CWordTag *word, float amount ) { if ( !word->m_bSelected ) return; m_nSelectedWordCount++; } void PhonemeEditor::ITER_CountSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ) { if ( !phoneme->m_bSelected ) return; m_nSelectedPhonemeCount++; } // Undo/Redo void PhonemeEditor::Undo( void ) { if ( m_UndoStack.Count() > 0 && m_nUndoLevel > 0 ) { m_nUndoLevel--; PEUndo *u = m_UndoStack[ m_nUndoLevel ]; Assert( u->undo ); m_Tags = *(u->undo); SetClickedPhoneme( -1, -1 ); } redraw(); } void PhonemeEditor::Redo( void ) { if ( m_UndoStack.Count() > 0 && m_nUndoLevel <= m_UndoStack.Count() - 1 ) { PEUndo *u = m_UndoStack[ m_nUndoLevel ]; Assert( u->redo ); m_Tags = *(u->redo); m_nUndoLevel++; SetClickedPhoneme( -1, -1 ); } redraw(); } void PhonemeEditor::PushUndo( void ) { Assert( !m_bRedoPending ); m_bRedoPending = true; WipeRedo(); // Copy current data CSentence *u = new CSentence(); *u = m_Tags; PEUndo *undo = new PEUndo; undo->undo = u; undo->redo = NULL; m_UndoStack.AddToTail( undo ); m_nUndoLevel++; } void PhonemeEditor::PushRedo( void ) { Assert( m_bRedoPending ); m_bRedoPending = false; // Copy current data CSentence *r = new CSentence(); *r = m_Tags; PEUndo *undo = m_UndoStack[ m_nUndoLevel - 1 ]; undo->redo = r; } void PhonemeEditor::WipeUndo( void ) { while ( m_UndoStack.Count() > 0 ) { PEUndo *u = m_UndoStack[ 0 ]; delete u->undo; delete u->redo; delete u; m_UndoStack.Remove( 0 ); } m_nUndoLevel = 0; } void PhonemeEditor::WipeRedo( void ) { // Wipe everything above level while ( m_UndoStack.Count() > m_nUndoLevel ) { PEUndo *u = m_UndoStack[ m_nUndoLevel ]; delete u->undo; delete u->redo; delete u; m_UndoStack.Remove( m_nUndoLevel ); } } //----------------------------------------------------------------------------- // Purpose: // Input : word - // phoneme - //----------------------------------------------------------------------------- void PhonemeEditor::SetClickedPhoneme( int word, int phoneme ) { m_nClickedPhoneme = phoneme; m_nClickedWord = word; } //----------------------------------------------------------------------------- // Purpose: // Output : CPhonemeTag //----------------------------------------------------------------------------- CPhonemeTag *PhonemeEditor::GetClickedPhoneme( void ) { if ( m_nClickedPhoneme < 0 || m_nClickedWord < 0 ) return NULL; if ( m_nClickedWord >= m_Tags.m_Words.Count() ) return NULL; CWordTag *word = m_Tags.m_Words[ m_nClickedWord ]; if ( !word ) return NULL; if ( m_nClickedPhoneme >= word->m_Phonemes.Count() ) return NULL; CPhonemeTag *phoneme = word->m_Phonemes[ m_nClickedPhoneme ]; return phoneme; } //----------------------------------------------------------------------------- // Purpose: // Output : CWordTag //----------------------------------------------------------------------------- CWordTag *PhonemeEditor::GetClickedWord( void ) { if ( m_nClickedWord < 0 ) return NULL; if ( m_nClickedWord >= m_Tags.m_Words.Count() ) return NULL; CWordTag *word = m_Tags.m_Words[ m_nClickedWord ]; return word; } void PhonemeEditor::ShowContextMenu_Phonemes( int mx, int my ) { CountSelected(); // Construct main mxPopupMenu *pop = new mxPopupMenu(); if ( m_pWaveFile ) { mxPopupMenu *play = new mxPopupMenu; play->add( va( "Original" ), IDC_PHONEME_PLAY_ORIG ); play->add( va( "Edited" ), IDC_PLAY_EDITED ); if ( m_bSelectionActive ) { play->add( va( "Selection" ), IDC_PLAY_EDITED_SELECTION ); } pop->addMenu( "Play", play ); if ( sound->IsSoundPlaying( m_pMixer ) ) { pop->add( va( "Cancel playback" ), IDC_CANCELPLAYBACK ); } pop->addSeparator(); } pop->add( va( "Load..." ), IDC_LOADWAVEFILE ); if ( m_pWaveFile ) { pop->add( va( "Save" ), IDC_SAVE_LINGUISTIC ); } if ( m_bSelectionActive ) { pop->addSeparator(); pop->add( va( "Deselect" ), IDC_DESELECT ); } if ( m_pWaveFile ) { pop->addSeparator(); pop->add( va( "Redo Extraction" ), IDC_REDO_PHONEMEEXTRACTION ); if ( m_nSelectedWordCount < 1 || AreSelectedWordsContiguous() ) { pop->add( va( "Redo Extraction of selected words" ), IDC_REDO_PHONEMEEXTRACTION_SELECTION ); } } if ( m_pWaveFile && m_TagsExt.m_Words.Count() ) { pop->addSeparator(); pop->add( va( "Commit extraction" ) , IDC_COMMITEXTRACTED ); pop->add( va( "Clear extraction" ), IDC_CLEAREXTRACTED ); } if ( m_nUndoLevel != 0 || m_nUndoLevel != m_UndoStack.Count() ) { pop->addSeparator(); if ( m_nUndoLevel != 0 ) { pop->add( va( "Undo" ), IDC_UNDO ); } if ( m_nUndoLevel != m_UndoStack.Count() ) { pop->add( va( "Redo" ), IDC_REDO ); } pop->add( va( "Clear Undo Info" ), IDC_CLEARUNDO ); } if ( m_Tags.m_Words.Count() > 0 ) { pop->addSeparator(); pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP ); } // Show hierarchical options menu { mxPopupMenu *api = 0; if ( DoesExtractorExistFor( SPEECH_API_SAPI ) ) { api = new mxPopupMenu(); api->add( "Microsoft Speech API", IDC_API_SAPI ); if ( g_viewerSettings.speechapiindex == SPEECH_API_SAPI ) { api->setChecked( IDC_API_SAPI, true ); } } if ( DoesExtractorExistFor( SPEECH_API_LIPSINC ) ) { if ( !api ) api = new mxPopupMenu(); api->add( "Lipsinc Speech API", IDC_API_LIPSINC ); if ( g_viewerSettings.speechapiindex == SPEECH_API_LIPSINC ) { api->setChecked( IDC_API_LIPSINC, true ); } } pop->addSeparator(); pop->addMenu( "Change Speech API", api ); } // Import export menu if ( m_pWaveFile ) { pop->addSeparator(); if ( m_Tags.m_Words.Count() > 0 ) { pop->add( "Export word data to " WORD_DATA_EXTENSION "...", IDC_EXPORT_SENTENCE ); } pop->add( "Import word data from " WORD_DATA_EXTENSION "...", IDC_IMPORT_SENTENCE ); pop->add( va("%s Voice Duck", m_Tags.GetVoiceDuck() ? "Disable" : "Enable" ), IDC_TOGGLE_VOICEDUCK ); } pop->popup( this, mx, my ); } void PhonemeEditor::ShowContextMenu_Emphasis( int mx, int my ) { Emphasis_CountSelected(); // Construct main mxPopupMenu *pop = new mxPopupMenu(); pop->add( va( "Select All" ), IDC_EMPHASIS_SELECTALL ); if ( m_nNumSelected > 0 ) { pop->add( va( "Deselect All" ), IDC_EMPHASIS_DESELECT ); } if ( m_nUndoLevel != 0 || m_nUndoLevel != m_UndoStack.Count() ) { pop->addSeparator(); if ( m_nUndoLevel != 0 ) { pop->add( va( "Undo" ), IDC_UNDO ); } if ( m_nUndoLevel != m_UndoStack.Count() ) { pop->add( va( "Redo" ), IDC_REDO ); } pop->add( va( "Clear Undo Info" ), IDC_CLEARUNDO ); } pop->popup( this, mx, my ); } void PhonemeEditor::ShowContextMenu( int mx, int my ) { switch ( GetMode() ) { default: case MODE_PHONEMES: ShowContextMenu_Phonemes( mx, my ); break; case MODE_EMPHASIS: ShowContextMenu_Emphasis( mx, my ); break; } } void PhonemeEditor::ShiftSelectedPhoneme( int direction ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); switch ( m_nSelectedPhonemeCount ) { case 1: break; case 0: Con_Printf( "Can't shift phonemes, none selected\n" ); return; default: Con_Printf( "Can only shift one phoneme at a time via keyboard\n" ); return; } RECT rc; GetWorkspaceRect( rc ); // Determine start/stop positions float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); float movetime = timeperpixel * (float)direction; float maxmove = ComputeMaxPhonemeShift( direction > 0 ? true : false, false ); if ( direction > 0 ) { if ( movetime > maxmove ) { movetime = maxmove; Con_Printf( "Further shift is blocked on right\n" ); } } else { if ( movetime < -maxmove ) { movetime = -maxmove; Con_Printf( "Further shift is blocked on left\n" ); } } if ( fabs( movetime ) < 0.0001f ) return; SetDirty( true ); PushUndo(); TraversePhonemes( &PhonemeEditor::ITER_MoveSelectedPhonemes, movetime ); PushRedo(); m_bWordsActive = false; redraw(); Con_Printf( "Shift phoneme %s\n", direction == -1 ? "left" : "right" ); } void PhonemeEditor::ExtendSelectedPhonemeEndTime( int direction ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedPhonemeCount != 1 ) return; RECT rc; GetWorkspaceRect( rc ); // Determine start/stop positions float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); float movetime = timeperpixel * (float)direction; SetDirty( true ); PushUndo(); TraversePhonemes( &PhonemeEditor::ITER_ExtendSelectedPhonemeEndTimes, movetime ); PushRedo(); m_bWordsActive = false; redraw(); Con_Printf( "Extend phoneme end %s\n", direction == -1 ? "left" : "right" ); } void PhonemeEditor::SelectNextPhoneme( int direction ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedPhonemeCount != 1 ) { if ( m_nSelectedWordCount == 1 ) { CWordTag *word = GetSelectedWord(); if ( word && word->m_Phonemes.Count() > 0 ) { m_nSelectedPhonemeCount = 1; CPhonemeTag *p = word->m_Phonemes[ direction ? word->m_Phonemes.Count() - 1 : 0 ]; p->m_bSelected = true; } else { return; } } else { return; } } Con_Printf( "Move to next phoneme %s\n", direction == -1 ? "left" : "right" ); for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; if ( !phoneme->m_bSelected ) continue; // Deselect this one and move int nextindex = j + direction; if ( nextindex < 0 ) { nextindex = word->m_Phonemes.Count() - 1; } else if ( nextindex >= word->m_Phonemes.Count() ) { nextindex = 0; } phoneme->m_bSelected = false; phoneme = word->m_Phonemes[ nextindex ]; phoneme->m_bSelected = true; m_bWordsActive = false; redraw(); return; } } } bool PhonemeEditor::IsPhonemeSelected( CWordTag *word ) { for ( int i = 0 ; i < word->m_Phonemes.Count(); i++ ) { CPhonemeTag *p = word->m_Phonemes[ i ]; if ( !p || !p->m_bSelected ) continue; return true; } return false; } void PhonemeEditor::SelectNextWord( int direction ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedWordCount != 1 && m_nSelectedPhonemeCount != 1 ) { // Selected first word then if ( m_nSelectedWordCount == 0 && m_Tags.m_Words.Count() > 0 ) { CWordTag *word = m_Tags.m_Words[ direction ? m_Tags.m_Words.Count() - 1 : 0 ]; word->m_bSelected = true; m_nSelectedWordCount = 1; } else { return; } } Con_Printf( "Move to next word %s\n", direction == -1 ? "left" : "right" ); for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; if ( m_nSelectedWordCount == 1 ) { if ( !word->m_bSelected ) continue; } else { if ( !IsPhonemeSelected( word ) ) continue; } // Deselect word word->m_bSelected = false; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; if ( !phoneme->m_bSelected ) continue; phoneme->m_bSelected = false; } // Deselect this one and move int nextword = i + direction; if ( nextword < 0 ) { nextword = m_Tags.m_Words.Count() - 1; } else if ( nextword >= m_Tags.m_Words.Count() ) { nextword = 0; } word = m_Tags.m_Words[ nextword ]; word->m_bSelected = true; if ( word->m_Phonemes.Count() > 0 ) { CPhonemeTag *phoneme = NULL; if ( direction > 0 ) { phoneme = word->m_Phonemes[ 0 ]; } else { phoneme = word->m_Phonemes[ word->m_Phonemes.Count() - 1 ]; } phoneme->m_bSelected = true; } m_bWordsActive = true; redraw(); return; } } void PhonemeEditor::ShiftSelectedWord( int direction ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); switch ( m_nSelectedWordCount ) { case 1: break; case 0: Con_Printf( "Can't shift words, none selected\n" ); return; default: Con_Printf( "Can only shift one word at a time via keyboard\n" ); return; } RECT rc; GetWorkspaceRect( rc ); // Determine start/stop positions float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); float movetime = timeperpixel * (float)direction; float maxmove = ComputeMaxWordShift( direction > 0 ? true : false, false ); if ( direction > 0 ) { if ( movetime > maxmove ) { movetime = maxmove; Con_Printf( "Further shift is blocked on right\n" ); } } else { if ( movetime < -maxmove ) { movetime = -maxmove; Con_Printf( "Further shift is blocked on left\n" ); } } if ( fabs( movetime ) < 0.0001f ) return; SetDirty( true ); PushUndo(); TraverseWords( &PhonemeEditor::ITER_MoveSelectedWords, movetime ); PushRedo(); m_bWordsActive = true; redraw(); Con_Printf( "Shift word %s\n", direction == -1 ? "left" : "right" ); } void PhonemeEditor::ExtendSelectedWordEndTime( int direction ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedWordCount != 1 ) return; RECT rc; GetWorkspaceRect( rc ); // Determine start/stop positions float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); float movetime = timeperpixel * (float)direction; SetDirty( true ); PushUndo(); TraverseWords( &PhonemeEditor::ITER_ExtendSelectedWordEndTimes, movetime ); PushRedo(); m_bWordsActive = true; redraw(); Con_Printf( "Extend word end %s\n", direction == -1 ? "left" : "right" ); } //----------------------------------------------------------------------------- // Purpose: // Input : *word - // Output : int //----------------------------------------------------------------------------- int PhonemeEditor::IndexOfWord( CWordTag *word ) { for ( int i = 0 ; i < m_Tags.m_Words.Count(); i++ ) { if ( m_Tags.m_Words[ i ] == word ) return i; } return -1; } //----------------------------------------------------------------------------- // Purpose: // Input : forward - // *currentWord - // **nextWord - // Output : float //----------------------------------------------------------------------------- float PhonemeEditor::GetTimeGapToNextWord( bool forward, CWordTag *currentWord, CWordTag **ppNextWord /* = NULL */ ) { if ( ppNextWord ) { *ppNextWord = NULL; } if ( !currentWord ) return 0.0f; int wordnum = IndexOfWord( currentWord ); if ( wordnum == -1 ) return 0.0f; // Go in correct direction int newwordnum = wordnum + ( forward ? 1 : -1 ); // There is no next word if ( newwordnum >= m_Tags.m_Words.Count() ) { return PLENTY_OF_TIME; } // There is no previous word if ( newwordnum < 0 ) { return PLENTY_OF_TIME; } if ( ppNextWord ) { *ppNextWord = m_Tags.m_Words[ newwordnum ]; } // Otherwise, figure out time gap if ( forward ) { float currentEnd = currentWord->m_flEndTime; float nextStart = m_Tags.m_Words[ newwordnum ]->m_flStartTime; return ( nextStart - currentEnd ); } else { float previousEnd = m_Tags.m_Words[ newwordnum ]->m_flEndTime; float currentStart = currentWord->m_flStartTime; return ( currentStart - previousEnd ); } Assert( 0 ); return 0.0f; } //----------------------------------------------------------------------------- // Purpose: // Input : forward - // *currentPhoneme - // **word - // **phoneme - // Output : float //----------------------------------------------------------------------------- float PhonemeEditor::GetTimeGapToNextPhoneme( bool forward, CPhonemeTag *currentPhoneme, CWordTag **ppword /* = NULL */, CPhonemeTag **ppphoneme /* = NULL */ ) { if ( ppword ) { *ppword = NULL; } if ( ppphoneme ) { *ppphoneme = NULL; } if ( !currentPhoneme ) return 0.0f; CWordTag *word = m_Tags.GetWordForPhoneme( currentPhoneme ); if ( !word ) return 0.0f; int wordnum = IndexOfWord( word ); Assert( wordnum != -1 ); int phonemenum = word->IndexOfPhoneme( currentPhoneme ); if ( phonemenum < 0 ) return 0.0f; CPhonemeTag *nextPhoneme = NULL; int nextphoneme = phonemenum + ( forward ? 1 : -1 ); // Try last phoneme of previous word if ( nextphoneme < 0 ) { wordnum--; while ( wordnum >= 0 ) { if ( ppword ) { *ppword = m_Tags.m_Words[ wordnum ]; } if ( m_Tags.m_Words.Count() > 0 ) { if ( m_Tags.m_Words[ wordnum ]->m_Phonemes.Count() > 0 ) { nextPhoneme = m_Tags.m_Words[ wordnum ]->m_Phonemes[ m_Tags.m_Words[ wordnum ]->m_Phonemes.Count() - 1 ]; break; } } wordnum--; } } // Try first phoneme of next word, if there is one else if ( nextphoneme >= word->m_Phonemes.Count() ) { wordnum++; while ( wordnum < m_Tags.m_Words.Count() ) { if ( ppword ) { *ppword = m_Tags.m_Words[ wordnum ]; } // Really it can't be zero, but check anyway if ( m_Tags.m_Words.Count() > 0 ) { if ( m_Tags.m_Words[ wordnum ]->m_Phonemes.Count() > 0 ) { nextPhoneme = m_Tags.m_Words[ wordnum ]->m_Phonemes[ 0 ]; break; } } wordnum++; } } else { nextPhoneme = word->m_Phonemes[ nextphoneme ]; } if ( !nextPhoneme ) return PLENTY_OF_TIME; if ( ppphoneme ) { *ppphoneme = nextPhoneme; } // Now compute time delta float dt = 0.0f; if ( forward ) { dt = nextPhoneme->GetStartTime() - currentPhoneme->GetEndTime(); } else { dt = currentPhoneme->GetStartTime() - nextPhoneme->GetEndTime(); } return dt; } CPhonemeTag *PhonemeEditor::GetSelectedPhoneme( void ) { CountSelected(); if ( m_nSelectedPhonemeCount != 1 ) return NULL; for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *w = m_Tags.m_Words[ i ]; if ( !w ) continue; for ( int j = 0; j < w->m_Phonemes.Count() ; j++ ) { CPhonemeTag *p = w->m_Phonemes[ j ]; if ( !p || !p->m_bSelected ) continue; return p; } } return NULL; } CWordTag *PhonemeEditor::GetSelectedWord( void ) { CountSelected(); if ( m_nSelectedWordCount != 1 ) return NULL; for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *w = m_Tags.m_Words[ i ]; if ( !w || !w->m_bSelected ) continue; return w; } return NULL; } void PhonemeEditor::OnMouseMove( mxEvent *event ) { int mx = (short)event->x; LimitDrag( mx ); event->x = (short)mx; if ( m_nDragType != DRAGTYPE_NONE ) { DrawFocusRect( "moving old" ); for ( int i = 0; i < m_FocusRects.Count(); i++ ) { CFocusRect *f = &m_FocusRects[ i ]; f->m_rcFocus = f->m_rcOrig; switch ( m_nDragType ) { default: { // Only X Shifts supported OffsetRect( &f->m_rcFocus, ( (short)event->x - m_nStartX ), 0 ); } break; case DRAGTYPE_EMPHASIS_SELECT: { RECT rcWork; GetWorkspaceRect( rcWork ); RECT rcEmphasis; Emphasis_GetRect( rcWork, rcEmphasis ); RECT rcFocus; rcFocus = f->m_rcOrig; rcFocus.left = m_nStartX < (short)event->x ? m_nStartX : (short)event->x; rcFocus.right = m_nStartX < (short)event->x ? (short)event->x : m_nStartX; rcFocus.top = m_nStartY < (short)event->y ? m_nStartY : (short)event->y; rcFocus.bottom = m_nStartY < (short)event->y ? (short)event->y : m_nStartY; rcFocus.top = clamp( rcFocus.top, rcEmphasis.top, rcEmphasis.bottom ); rcFocus.bottom = clamp( rcFocus.bottom, rcEmphasis.top, rcEmphasis.bottom ); //OffsetRect( &rcFocus, 0, -rcEmphasis.top ); POINT offset; offset.x = 0; offset.y = 0; ClientToScreen( (HWND)getHandle(), &offset ); OffsetRect( &rcFocus, offset.x, offset.y ); f->m_rcFocus = rcFocus; } break; } } if ( m_nDragType == DRAGTYPE_EMPHASIS_MOVE ) { redraw(); } DrawFocusRect( "moving new" ); } else { if ( m_hPrevCursor ) { SetCursor( m_hPrevCursor ); m_hPrevCursor = NULL; } CountSelected(); int overhandle = IsMouseOverBoundary( event ); if ( overhandle == BOUNDARY_PHONEME && m_nSelectedPhonemeCount <= 1 ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); } else if ( overhandle == BOUNDARY_WORD && m_nSelectedWordCount <= 1 ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); } else if ( IsMouseOverSelection( (short)event->x, (short)event->y ) ) { if ( IsMouseOverSelectionStartEdge( event ) ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); } else if ( IsMouseOverSelectionEndEdge( event ) ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); } else { if ( event->modifiers & mxEvent::KeyShift ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); } } } else { if ( IsMouseOverTag( (short)event->x, (short)event->y ) ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); } else { CPhonemeTag *pt = GetPhonemeTagUnderMouse( (short)event->x, (short)event->y ); CWordTag *wt = GetWordTagUnderMouse( (short)event->x, (short)event->y ); if ( wt || pt ) { if ( pt ) { // Select it SelectExpression( pt ); } if ( event->modifiers & mxEvent::KeyShift ) { m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); } } } } } switch ( m_nDragType ) { default: break; case DRAGTYPE_EMPHASIS_MOVE: { Emphasis_MouseDrag( (short)event->x, (short)event->y ); m_Tags.Resort(); } break; case DRAGTYPE_SCRUBBER: { float t = GetTimeForPixel( (short)event->x ); t += m_flScrubberTimeOffset; ClampTimeToSelectionInterval( t ); float dt = t - m_flScrub; SetScrubTargetTime( t ); ScrubThink( dt, true ); SetScrubTime( t ); DrawScrubHandle(); } break; } m_nLastX = (short)event->x; m_nLastY = (short)event->y; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::EditInsertFirstPhonemeOfWord( void ) { if ( GetMode() != MODE_PHONEMES ) return; CWordTag *cw = GetSelectedWord(); if ( !cw ) return; if ( cw->m_Phonemes.Count() != 0 ) { Con_Printf( "Can't insert first phoneme into %s, already has phonemes\n", cw->GetWord() ); return; } CPhonemeParams params; memset( ¶ms, 0, sizeof( params ) ); strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); strcpy( params.m_szName, "" ); params.m_nLeft = -1; params.m_nTop = -1; params.m_bPositionDialog = true; params.m_bMultiplePhoneme = true; if ( params.m_bPositionDialog ) { RECT rcWord; GetWordRect( cw, rcWord ); // Convert to screen coords POINT pt; pt.x = rcWord.left; pt.y = rcWord.top; ClientToScreen( (HWND)getHandle(), &pt ); params.m_nLeft = pt.x; params.m_nTop = pt.y; } int iret = PhonemeProperties( ¶ms ); SetFocus( (HWND)getHandle() ); if ( !iret ) { return; } int phonemeCount = CSentence::CountWords( params.m_szName ); if ( phonemeCount <= 0 ) { return; } float wordLength = cw->m_flEndTime - cw->m_flStartTime; float timePerPhoneme = wordLength / (float)phonemeCount; float currentTime = cw->m_flStartTime; SetDirty( true ); PushUndo(); unsigned char *in; char *out; char phonemeName[ 128 ]; in = (unsigned char *)params.m_szName; do { out = phonemeName; while ( *in > 32 ) { *out++ = *in++; } *out = 0; CPhonemeTag phoneme; phoneme.SetPhonemeCode( TextToPhoneme( phonemeName ) ); phoneme.SetTag( phonemeName ); phoneme.SetStartTime( currentTime ); phoneme.SetEndTime( currentTime + timePerPhoneme ); phoneme.m_bSelected = false; cw->m_Phonemes.AddToTail( new CPhonemeTag( phoneme ) ); currentTime += timePerPhoneme; if ( !*in ) break; // Skip whitespace in++; } while ( 1 ); cw->m_Phonemes[ 0 ]->m_bSelected = true; PushRedo(); // Add it redraw(); } void PhonemeEditor::SelectPhonemes( bool forward ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedPhonemeCount != 1 ) return; CPhonemeTag *phoneme = GetSelectedPhoneme(); if ( !phoneme ) return; // Figure out it's word and index CWordTag *word = m_Tags.GetWordForPhoneme( phoneme ); if ( !word ) return; int wordNum = IndexOfWord( word ); if ( wordNum == -1 ) return; // Select remaining phonemes in current word int i; i = word->IndexOfPhoneme( phoneme ); if ( i == -1 ) return; if ( forward ) { // Start at next one i++; for ( ; i < word->m_Phonemes.Count(); i++ ) { phoneme = word->m_Phonemes[ i ]; phoneme->m_bSelected = true; } // Now start at next word wordNum++; for ( ; wordNum < m_Tags.m_Words.Count(); wordNum++ ) { word = m_Tags.m_Words[ wordNum ]; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { phoneme = word->m_Phonemes[ j ]; phoneme->m_bSelected = true; } } } else { // Start at previous i--; for ( ; i >= 0; i-- ) { phoneme = word->m_Phonemes[ i ]; phoneme->m_bSelected = true; } // Now start at previous word wordNum--; for ( ; wordNum >= 0 ; wordNum-- ) { word = m_Tags.m_Words[ wordNum ]; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { phoneme = word->m_Phonemes[ j ]; phoneme->m_bSelected = true; } } } redraw(); } void PhonemeEditor::SelectWords( bool forward ) { if ( GetMode() != MODE_PHONEMES ) return; CountSelected(); if ( m_nSelectedWordCount != 1 ) return; // Figure out it's word and index CWordTag *word = GetSelectedWord(); if ( !word ) return; int wordNum = IndexOfWord( word ); if ( wordNum == -1 ) return; if ( forward ) { wordNum++; for ( ; wordNum < m_Tags.m_Words.Count(); wordNum++ ) { word = m_Tags.m_Words[ wordNum ]; word->m_bSelected = true; } } else { wordNum--; for ( ; wordNum >= 0; wordNum-- ) { word = m_Tags.m_Words[ wordNum ]; word->m_bSelected = true; } } redraw(); } bool PhonemeEditor::AreSelectedWordsContiguous( void ) { CountSelected(); if ( m_nSelectedWordCount < 1 ) return false; if ( m_nSelectedWordCount == 1 ) return true; // Find first selected word int runcount = 0; bool parity = false; for ( int i = 0 ; i < m_Tags.m_Words.Count() ; i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; if ( word->m_bSelected ) { if ( !parity ) { parity = true; runcount++; } } else { if ( parity ) { parity = false; } } } if ( runcount == 1 ) return true; return false; } bool PhonemeEditor::AreSelectedPhonemesContiguous( void ) { CountSelected(); if ( m_nSelectedPhonemeCount < 1 ) return false; if ( m_nSelectedPhonemeCount == 1 ) return true; // Find first selected word int runcount = 0; bool parity = false; for ( int i = 0 ; i < m_Tags.m_Words.Count() ; i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = 0 ; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; if ( phoneme->m_bSelected ) { if ( !parity ) { parity = true; runcount++; } } else { if ( parity ) { parity = false; } } } } if ( runcount == 1 ) return true; return false; } void PhonemeEditor::SortWords( bool prepareundo ) { if ( prepareundo ) { SetDirty( true ); PushUndo(); } // Just bubble sort by start time int c = m_Tags.m_Words.Count(); int i; // check for start > end for ( i = 0; i < c; i++ ) { CWordTag *p1 = m_Tags.m_Words[ i ]; if (p1->m_flStartTime > p1->m_flEndTime ) { float swap = p1->m_flStartTime; p1->m_flStartTime = p1->m_flEndTime; p1->m_flEndTime = swap; } } for ( i = 0; i < c; i++ ) { for ( int j = i + 1; j < c; j++ ) { CWordTag *p1 = m_Tags.m_Words[ i ]; CWordTag *p2 = m_Tags.m_Words[ j ]; if ( p1->m_flStartTime < p2->m_flStartTime ) continue; // Swap them m_Tags.m_Words[ i ] = p2; m_Tags.m_Words[ j ] = p1; } } if ( prepareundo ) { PushRedo(); } } void PhonemeEditor::SortPhonemes( bool prepareundo ) { if ( prepareundo ) { SetDirty( true ); PushUndo(); } // Just bubble sort by start time int wc = m_Tags.m_Words.Count(); for ( int w = 0; w < wc; w++ ) { CWordTag *word = m_Tags.m_Words[ w ]; Assert( word ); int c = word->m_Phonemes.Count(); int i; // check for start > end for ( i = 0; i < c; i++ ) { CPhonemeTag *p1 = word->m_Phonemes[ i ]; if (p1->GetStartTime() > p1->GetEndTime() ) { float swap = p1->GetStartTime(); p1->SetStartTime( p1->GetEndTime() ); p1->SetEndTime( swap ); } } for ( i = 0; i < c; i++ ) { for ( int j = i + 1; j < c; j++ ) { CPhonemeTag *p1 = word->m_Phonemes[ i ]; CPhonemeTag *p2 = word->m_Phonemes[ j ]; if ( p1->GetStartTime() < p2->GetStartTime() ) continue; // Swap them word->m_Phonemes[ i ] = p2; word->m_Phonemes[ j ] = p1; } } } if ( prepareundo ) { PushRedo(); } } void PhonemeEditor::CleanupWordsAndPhonemes( bool prepareundo ) { if ( GetMode() != MODE_PHONEMES ) return; // 2 pixel gap float snap_epsilon = 2.49f / GetPixelsPerSecond(); if ( prepareundo ) { SetDirty( true ); PushUndo(); } SortWords( false ); SortPhonemes( false ); for ( int i = 0 ; i < m_Tags.m_Words.Count() ; i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; CWordTag *next = NULL; if ( i < m_Tags.m_Words.Count() - 1 ) { next = m_Tags.m_Words[ i + 1 ]; } if ( word && next ) { // Check for words close enough float eps = next->m_flStartTime - word->m_flEndTime; if ( eps && eps <= snap_epsilon ) { float t = (word->m_flEndTime + next->m_flStartTime) * 0.5; word->m_flEndTime = t; next->m_flStartTime = t; } } for ( int j = 0 ; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; CPhonemeTag *next = NULL; if ( j < word->m_Phonemes.Count() - 1 ) { next = word->m_Phonemes[ j + 1 ]; } if ( phoneme && next ) { float eps = next->GetStartTime() - phoneme->GetEndTime(); if ( eps && eps <= snap_epsilon ) { float t = (phoneme->GetEndTime() + next->GetStartTime() ) * 0.5; phoneme->SetEndTime( t ); next->SetStartTime( t ); } } } } if ( prepareundo ) { PushRedo(); } // NOTE: Caller must call "redraw()" to get screen to update } void PhonemeEditor::RealignPhonemesToWords( bool prepareundo ) { if ( GetMode() != MODE_PHONEMES ) return; if ( prepareundo ) { SetDirty( true ); PushUndo(); } SortWords( false ); SortPhonemes( false ); for ( int i = 0 ; i < m_Tags.m_Words.Count() ; i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; CWordTag *next = NULL; if ( i < m_Tags.m_Words.Count() - 1 ) { next = m_Tags.m_Words[ i + 1 ]; } float word_dt = word->m_flEndTime - word->m_flStartTime; CPhonemeTag *FirstPhoneme = word->m_Phonemes[ 0 ]; if ( !FirstPhoneme ) continue; CPhonemeTag *LastPhoneme = word->m_Phonemes[ word->m_Phonemes.Count() - 1 ]; if ( !LastPhoneme ) continue; float phoneme_dt = LastPhoneme->GetEndTime() - FirstPhoneme->GetStartTime(); float phoneme_shift = FirstPhoneme->GetStartTime(); for ( int j = 0 ; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *phoneme = word->m_Phonemes[ j ]; if ( !phoneme ) continue; CPhonemeTag *next = NULL; if ( j < word->m_Phonemes.Count() - 1 ) { next = word->m_Phonemes[ j + 1 ]; } if (j == 0) { float t = (phoneme->GetStartTime() - phoneme_shift ) * (word_dt / phoneme_dt) + word->m_flStartTime; phoneme->SetStartTime( t ); } float t = (phoneme->GetEndTime() - phoneme_shift ) * (word_dt / phoneme_dt) + word->m_flStartTime; phoneme->SetEndTime( t ); if (next) { next->SetStartTime( t ); } } } if ( prepareundo ) { PushRedo(); } // NOTE: Caller must call "redraw()" to get screen to update } void PhonemeEditor::RealignWordsToPhonemes( bool prepareundo ) { if ( GetMode() != MODE_PHONEMES ) return; if ( prepareundo ) { SetDirty( true ); PushUndo(); } SortWords( false ); SortPhonemes( false ); for ( int i = 0 ; i < m_Tags.m_Words.Count() ; i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; CPhonemeTag *FirstPhoneme = word->m_Phonemes[ 0 ]; if ( !FirstPhoneme ) continue; CPhonemeTag *LastPhoneme = word->m_Phonemes[ word->m_Phonemes.Count() - 1 ]; if ( !LastPhoneme ) continue; word->m_flStartTime = FirstPhoneme->GetStartTime(); word->m_flEndTime = LastPhoneme->GetEndTime(); } if ( prepareundo ) { PushRedo(); } // NOTE: Caller must call "redraw()" to get screen to update } float PhonemeEditor::ComputeMaxWordShift( bool forward, bool allowcrop ) { // skipping selected words, figure out max time shift of words before they selection touches any // unselected words // if allowcrop is true, then the maximum extends up to end of next word float maxshift = PLENTY_OF_TIME; if ( forward ) { for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *w1 = m_Tags.m_Words[ i ]; if ( !w1 || !w1->m_bSelected ) continue; CWordTag *w2 = NULL; for ( int search = i + 1; search < m_Tags.m_Words.Count() ; search++ ) { CWordTag *check = m_Tags.m_Words[ search ]; if ( !check || check->m_bSelected ) continue; w2 = check; break; } if ( w2 ) { float shift; if ( allowcrop ) { shift = w2->m_flEndTime - w1->m_flEndTime; } else { shift = w2->m_flStartTime - w1->m_flEndTime; } if ( shift < maxshift ) { maxshift = shift; } } } } else { for ( int i = m_Tags.m_Words.Count() -1; i >= 0; i-- ) { CWordTag *w1 = m_Tags.m_Words[ i ]; if ( !w1 || !w1->m_bSelected ) continue; CWordTag *w2 = NULL; for ( int search = i - 1; search >= 0 ; search-- ) { CWordTag *check = m_Tags.m_Words[ search ]; if ( !check || check->m_bSelected ) continue; w2 = check; break; } if ( w2 ) { float shift; if ( allowcrop ) { shift = w1->m_flStartTime - w2->m_flStartTime; } else { shift = w1->m_flStartTime - w2->m_flEndTime; } if ( shift < maxshift ) { maxshift = shift; } } } } return maxshift; } float PhonemeEditor::ComputeMaxPhonemeShift( bool forward, bool allowcrop ) { // skipping selected phonemes, figure out max time shift of phonemes before they selection touches any // unselected words // if allowcrop is true, then the maximum extends up to end of next word float maxshift = PLENTY_OF_TIME; if ( forward ) { for ( int i = 0; i < m_Tags.m_Words.Count(); i++ ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = 0; j < word->m_Phonemes.Count(); j++ ) { CPhonemeTag *p1 = word->m_Phonemes[ j ]; if ( !p1 || !p1->m_bSelected ) continue; // Find next unselected phoneme CPhonemeTag *p2 = NULL; CPhonemeTag *start = p1; do { CPhonemeTag *test = NULL; GetTimeGapToNextPhoneme( forward, start, NULL, &test ); if ( !test ) break; if ( test->m_bSelected ) { start = test; continue; } p2 = test; break; } while ( 1 ); if ( p2 ) { float shift; if ( allowcrop ) { shift = p2->GetEndTime() - p1->GetEndTime(); } else { shift = p2->GetStartTime() - p1->GetEndTime(); } if ( shift < maxshift ) { maxshift = shift; } } } } } else { for ( int i = m_Tags.m_Words.Count() -1; i >= 0; i-- ) { CWordTag *word = m_Tags.m_Words[ i ]; if ( !word ) continue; for ( int j = word->m_Phonemes.Count() - 1; j >= 0; j-- ) { CPhonemeTag *p1 = word->m_Phonemes[ j ]; if ( !p1 || !p1->m_bSelected ) continue; // Find previous unselected phoneme CPhonemeTag *p2 = NULL; CPhonemeTag *start = p1; do { CPhonemeTag *test = NULL; GetTimeGapToNextPhoneme( forward, start, NULL, &test ); if ( !test ) break; if ( test->m_bSelected ) { start = test; continue; } p2 = test; break; } while ( 1 ); if ( p2 ) { float shift; if ( allowcrop ) { shift = p1->GetStartTime() - p2->GetStartTime(); } else { shift = p1->GetStartTime() - p2->GetEndTime(); } if ( shift < maxshift ) { maxshift = shift; } } } } } return maxshift; } int PhonemeEditor::PixelsForDeltaTime( float dt ) { if ( !dt ) return 0; RECT rc; GetWorkspaceRect( rc ); float starttime = m_nLeftOffset / GetPixelsPerSecond(); float endtime = w2() / GetPixelsPerSecond() + starttime; float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); float pixels = dt / timeperpixel; return abs( (int)pixels ); } void PhonemeEditor::ClearDragLimit( void ) { m_bLimitDrag = false; m_nLeftLimit = -1; m_nRightLimit = -1; } void PhonemeEditor::SetDragLimit( int dragtype ) { ClearDragLimit(); float nextW, nextP; float prevW, prevP; nextW = ComputeMaxWordShift( true, false ); prevW = ComputeMaxWordShift( false, false ); nextP = ComputeMaxPhonemeShift( true, false ); prevP = ComputeMaxPhonemeShift( false, false ); /* Con_Printf( "+w %f -w %f +p %f -p %f\n", 1000.0f * nextW, 1000.0f * prevW, 1000.0f * nextP, 1000.0f * prevP ); */ switch ( dragtype ) { case DRAGTYPE_MOVEWORD: m_bLimitDrag = true; m_nLeftLimit = PixelsForDeltaTime( prevW ); m_nRightLimit = PixelsForDeltaTime( nextW ); break; case DRAGTYPE_MOVEPHONEME: m_bLimitDrag = true; m_nLeftLimit = PixelsForDeltaTime( prevP ); m_nRightLimit = PixelsForDeltaTime( nextP ); break; default: ClearDragLimit(); break; } } void PhonemeEditor::LimitDrag( int& mousex ) { if ( m_nDragType == DRAGTYPE_NONE ) return; if ( !m_bLimitDrag ) return; int delta = mousex - m_nStartX; if ( delta > 0 ) { if ( m_nRightLimit >= 0 ) { if ( delta > m_nRightLimit ) { mousex = m_nStartX + m_nRightLimit; } } } else if ( delta < 0 ) { if ( m_nLeftLimit >= 0 ) { if ( abs( delta ) > abs( m_nLeftLimit ) ) { mousex = m_nStartX - m_nLeftLimit; } } } } //----------------------------------------------------------------------------- // Purpose: Wipe undo/redo data //----------------------------------------------------------------------------- void PhonemeEditor::ClearUndo( void ) { WipeUndo(); WipeRedo(); SetDirty( false ); } //----------------------------------------------------------------------------- // Purpose: // Input : *tag - //----------------------------------------------------------------------------- void PhonemeEditor::SelectExpression( CPhonemeTag *tag ) { if ( !models->GetActiveStudioModel() ) return; CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); if ( !hdr ) return; // Make sure phonemes are loaded FacePoser_EnsurePhonemesLoaded(); CExpClass *cl = expressions->FindClass( "phonemes", true ); if ( !cl ) { Con_Printf( "Couldn't load expressions/phonemes.txt!\n" ); return; } if ( expressions->GetActiveClass() != cl ) { expressions->ActivateExpressionClass( cl ); } CExpression *exp = cl->FindExpression( ConvertPhoneme( tag->GetPhonemeCode() ) ); if ( !exp ) { Con_Printf( "Couldn't find phoneme '%s'\n", ConvertPhoneme( tag->GetPhonemeCode() ) ); return; } float *settings = exp->GetSettings(); for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) { int j = hdr->pFlexcontroller( i )->localToGlobal; models->GetActiveStudioModel()->SetFlexController( i, settings[j] ); } } void PhonemeEditor::OnSAPI( void ) { if ( GetMode() != MODE_PHONEMES ) return; g_viewerSettings.speechapiindex = SPEECH_API_SAPI; m_pPhonemeExtractor = NULL; CheckSpeechAPI(); redraw(); } void PhonemeEditor::OnLipSinc( void ) { if ( GetMode() != MODE_PHONEMES ) return; g_viewerSettings.speechapiindex = SPEECH_API_LIPSINC; m_pPhonemeExtractor = NULL; CheckSpeechAPI(); redraw(); } void PhonemeEditor::LoadPhonemeConverters() { m_pPhonemeExtractor = NULL; // Enumerate modules under bin folder of exe FileFindHandle_t findHandle; const char *pFilename = filesystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle ); while( pFilename ) { char fullpath[ 512 ]; Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename ); Con_Printf( "Loading extractor from %s\n", fullpath ); Extractor e; e.module = Sys_LoadModule( fullpath ); if ( !e.module ) { pFilename = filesystem->FindNext( findHandle ); continue; } CreateInterfaceFn factory = Sys_GetFactory( e.module ); if ( !factory ) { pFilename = filesystem->FindNext( findHandle ); continue; } e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL ); if ( !e.extractor ) { Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath ); pFilename = filesystem->FindNext( findHandle ); continue; } e.apitype = e.extractor->GetAPIType(); g_Extractors.AddToTail( e ); pFilename = filesystem->FindNext( findHandle ); } filesystem->FindClose( findHandle ); } void PhonemeEditor::ValidateSpeechAPIIndex() { if ( !DoesExtractorExistFor( (PE_APITYPE)g_viewerSettings.speechapiindex ) ) { if ( g_Extractors.Count() > 0 ) g_viewerSettings.speechapiindex = g_Extractors[0].apitype; } } void PhonemeEditor::UnloadPhonemeConverters() { int c = g_Extractors.Count(); for ( int i = c - 1; i >= 0; i-- ) { Extractor *e = &g_Extractors[ i ]; Sys_UnloadModule( e->module ); } g_Extractors.RemoveAll(); m_pPhonemeExtractor = NULL; } bool PhonemeEditor::CheckSpeechAPI( void ) { if ( GetMode() != MODE_PHONEMES ) { return false; } if ( !m_pPhonemeExtractor ) { int c = g_Extractors.Count(); for ( int i = 0; i < c; i++ ) { Extractor *e = &g_Extractors[ i ]; if ( e->apitype == (PE_APITYPE)g_viewerSettings.speechapiindex ) { m_pPhonemeExtractor = e->extractor; break; } } if ( !m_pPhonemeExtractor ) { Con_ErrorPrintf( "Couldn't find phoneme extractor %i\n", g_viewerSettings.speechapiindex ); } } return m_pPhonemeExtractor != NULL; } //----------------------------------------------------------------------------- // Purpose: // Output : char const //----------------------------------------------------------------------------- char const *PhonemeEditor::GetSpeechAPIName( void ) { CheckSpeechAPI(); if ( m_pPhonemeExtractor ) { return m_pPhonemeExtractor->GetName(); } return "Unknown Speech API"; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::PaintBackground( void ) { redraw(); return false; } //----------------------------------------------------------------------------- // Purpose: // Output : PhonemeEditor::EditorMode //----------------------------------------------------------------------------- PhonemeEditor::EditorMode PhonemeEditor::GetMode( void ) const { return m_CurrentMode; } //----------------------------------------------------------------------------- // Purpose: // Input : rcWorkSpace - // rcEmphasis - //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_GetRect( RECT const & rcWorkSpace, RECT& rcEmphasis ) { rcEmphasis = rcWorkSpace; int ybottom = rcWorkSpace.bottom - 2 * m_nTickHeight - 2; int workspaceheight = rcWorkSpace.bottom - rcWorkSpace.top; // Just past midpoint rcEmphasis.top = rcWorkSpace.top + workspaceheight / 2 + 2; // 60 units or rcEmphasis.bottom = clamp( rcEmphasis.top + 60, rcEmphasis.top + 20, ybottom ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::OnModeChanged( void ) { // Show/hide controls as necessary } //----------------------------------------------------------------------------- // Purpose: // Input : *parent - //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_Init( void ) { m_nNumSelected = 0; } CEmphasisSample *PhonemeEditor::Emphasis_GetSampleUnderMouse( mxEvent *event ) { if ( GetMode() != MODE_EMPHASIS ) return NULL; if ( !m_pWaveFile ) return NULL; if ( w2() <= 0 ) return NULL; if ( GetPixelsPerSecond() <= 0 ) return NULL; float timeperpixel = 1.0f / GetPixelsPerSecond(); float closest_dist = 999999.0f; CEmphasisSample *bestsample = NULL; int samples = m_Tags.GetNumSamples(); float clickTime = GetTimeForPixel( (short)event->x ); for ( int i = 0; i < samples; i++ ) { CEmphasisSample *sample = m_Tags.GetSample( i ); float dist = fabs( sample->time - clickTime ); if ( dist < closest_dist ) { bestsample = sample; closest_dist = dist; } } // Not close to any of them!!! if ( closest_dist > ( 5.0f * timeperpixel ) ) { return NULL; } return bestsample; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_DeselectAll( void ) { if ( GetMode() != MODE_EMPHASIS ) return; for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) { CEmphasisSample *sample = m_Tags.GetSample( i ); sample->selected = false; } redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_SelectAll( void ) { if ( GetMode() != MODE_EMPHASIS ) return; for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) { CEmphasisSample *sample = m_Tags.GetSample( i ); sample->selected = true; } redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_Delete( void ) { if ( GetMode() != MODE_EMPHASIS ) return; SetDirty( true ); PushUndo(); for ( int i = m_Tags.GetNumSamples() - 1; i >= 0 ; i-- ) { CEmphasisSample *sample = m_Tags.GetSample( i ); if ( !sample->selected ) continue; m_Tags.m_EmphasisSamples.Remove( i ); SetDirty( true ); } PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : sample - //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_AddSample( CEmphasisSample const& sample ) { if ( GetMode() != MODE_EMPHASIS ) return; SetDirty( true ); PushUndo(); m_Tags.m_EmphasisSamples.AddToTail( sample ); m_Tags.Resort(); PushRedo(); redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_CountSelected( void ) { m_nNumSelected = 0; for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) { CEmphasisSample *sample = m_Tags.GetSample( i ); if ( !sample || !sample->selected ) continue; m_nNumSelected++; } } void PhonemeEditor::Emphasis_ShowContextMenu( mxEvent *event ) { if ( GetMode() != MODE_EMPHASIS ) return; CountSelected(); // Construct main menu mxPopupMenu *pop = new mxPopupMenu(); if ( m_nNumSelected > 0 ) { pop->add( va( "Delete" ), IDC_EMPHASIS_DELETE ); pop->add( "Deselect all", IDC_EMPHASIS_DESELECT ); } pop->add( "Select all", IDC_EMPHASIS_SELECTALL ); pop->popup( this, (short)event->x, (short)event->y ); } void PhonemeEditor::Emphasis_MouseDrag( int x, int y ) { if ( m_nDragType != DRAGTYPE_EMPHASIS_MOVE ) return; RECT rcWork; GetWorkspaceRect( rcWork ); RECT rc; Emphasis_GetRect( rcWork, rc ); int height = rc.bottom - rc.top; int dx = x - m_nLastX; int dy = y - m_nLastY; float dfdx = (float)dx * GetTimePerPixel(); float dfdy = (float)dy / (float)height; for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) { CEmphasisSample *sample = m_Tags.GetSample( i ); if ( !sample || !sample->selected ) continue; sample->time += dfdx; //sample->time = clamp( sample->time, 0.0f, 1.0f ); sample->value -= dfdy; sample->value = clamp( sample->value, 0.0f, 1.0f ); } } void PhonemeEditor::Emphasis_Redraw( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace ) { if ( GetMode() != MODE_EMPHASIS && GetMode() != MODE_PHONEMES ) return; bool fullmode = GetMode() == MODE_EMPHASIS; RECT rcClient; Emphasis_GetRect( rcWorkSpace, rcClient ); RECT rcText; rcText = rcClient; InflateRect( &rcText, -15, 0 ); OffsetRect( &rcText, 0, -20 ); rcText.bottom = rcText.top + 20; if ( fullmode ) { drawHelper.DrawColoredText( "Arial", 15, FW_BOLD, PEColor( COLOR_PHONEME_EMPHASIS_TEXT ), rcText, "Emphasis..." ); } { int h = rcClient.bottom - rcClient.top; int offset = h/3; RECT rcSpot = rcClient; rcSpot.bottom = rcSpot.top + offset; drawHelper.DrawGradientFilledRect( rcSpot, PEColor( COLOR_PHONEME_EMPHASIS_BG_STRONG ), PEColor( COLOR_PHONEME_EMPHASIS_BG ), true ); OffsetRect( &rcSpot, 0, offset ); drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_EMPHASIS_BG ), rcSpot ); OffsetRect( &rcSpot, 0, offset ); drawHelper.DrawGradientFilledRect( rcSpot, PEColor( COLOR_PHONEME_EMPHASIS_BG ), PEColor( COLOR_PHONEME_EMPHASIS_BG_WEAK ), true ); } Color gray = PEColor( COLOR_PHONEME_EMPHASIS_MIDLINE ); drawHelper.DrawOutlinedRect( PEColor( COLOR_PHONEME_EMPHASIS_BORDER ), PS_SOLID, 1, rcClient ); Color lineColor = PEColor( COLOR_PHONEME_EMPHASIS_LINECOLOR ); Color dotColor = PEColor( COLOR_PHONEME_EMPHASIS_DOTCOLOR ); Color dotColorSelected = PEColor( COLOR_PHONEME_EMPHASIS_DOTCOLOR_SELECTED ); int midy = ( rcClient.bottom + rcClient.top ) / 2; drawHelper.DrawColoredLine( gray, PS_SOLID, 1, rcClient.left, midy, rcClient.right, midy ); int height = rcClient.bottom - rcClient.top; int bottom = rcClient.bottom - 1; if ( !m_pWaveFile ) return; float running_length = m_pWaveFile->GetRunningLength(); // FIXME: adjust this based on framerate.... float timeperpixel = GetTimePerPixel(); float starttime, endtime; GetScreenStartAndEndTime( starttime, endtime ); int prevx = 0; float prev_t = starttime; float prev_value = m_Tags.GetIntensity( prev_t, running_length ); int dx = 5; for ( int x = 0; x < ( w2() + dx ); x += dx ) { float t = GetTimeForPixel( x ); float value = m_Tags.GetIntensity( t, running_length ); // Draw segment drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, prevx, bottom - prev_value * height, x, bottom - value * height ); prev_t = t; prev_value = value; prevx = x; } int numsamples = m_Tags.GetNumSamples(); for ( int sample = 0; sample < numsamples; sample++ ) { CEmphasisSample *start = m_Tags.GetSample( sample ); int x = ( start->time - starttime ) / timeperpixel; float value = m_Tags.GetIntensity( start->time, running_length ); int y = bottom - value * height; int dotsize = 4; int dotSizeSelected = 5; Color clr = dotColor; Color clrSelected = dotColorSelected; drawHelper.DrawCircle( start->selected ? clrSelected : clr, x, y, start->selected ? dotSizeSelected : dotsize, true ); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::Emphasis_IsValid( void ) { if ( m_Tags.GetNumSamples() > 0 ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void PhonemeEditor::Emphasis_SelectPoints( void ) { if ( GetMode() != MODE_EMPHASIS ) return; RECT rcWork, rcEmphasis; GetWorkspaceRect( rcWork ); Emphasis_GetRect( rcWork, rcEmphasis ); RECT rcSelection; rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; rcSelection.top = max( rcSelection.top, rcEmphasis.top ); rcSelection.bottom = min( rcSelection.bottom, rcEmphasis.bottom ); int eh, ew; eh = rcEmphasis.bottom - rcEmphasis.top; ew = rcEmphasis.right - rcEmphasis.left; InflateRect( &rcSelection, 5, 5 ); if ( !w2() || !h2() ) return; float fleft = GetTimeForPixel( rcSelection.left ); float fright = GetTimeForPixel( rcSelection.right ); float ftop = (float)( rcSelection.top - rcEmphasis.top ) / (float)eh; float fbottom = (float)( rcSelection.bottom- rcEmphasis.top ) / (float)eh; //fleft = clamp( fleft, 0.0f, 1.0f ); //fright = clamp( fright, 0.0f, 1.0f ); ftop = clamp( ftop, 0.0f, 1.0f ); fbottom = clamp( fbottom, 0.0f, 1.0f ); float eps = 0.005; for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) { CEmphasisSample *sample = m_Tags.GetSample( i ); if ( sample->time + eps < fleft ) continue; if ( sample->time - eps > fright ) continue; if ( (1.0f - sample->value ) + eps < ftop ) continue; if ( (1.0f - sample->value ) - eps > fbottom ) continue; sample->selected = true; } redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : rcHandle - //----------------------------------------------------------------------------- void PhonemeEditor::GetScrubHandleRect( RECT& rcHandle, bool clipped ) { float pixel = 0.0f; if ( m_pWaveFile ) { float currenttime = m_flScrub; float starttime, endtime; GetScreenStartAndEndTime( starttime, endtime ); float screenfrac = ( currenttime - starttime ) / ( endtime - starttime ); pixel = screenfrac * w2(); if ( clipped ) { pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH/2, w2() - SCRUBBER_HANDLE_WIDTH/2 ); } } rcHandle.left = pixel-SCRUBBER_HANDLE_WIDTH/2; rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH/2; rcHandle.top = 2 + GetCaptionHeight() + 12; rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; } //----------------------------------------------------------------------------- // Purpose: // Input : rcArea - //----------------------------------------------------------------------------- void PhonemeEditor::GetScrubAreaRect( RECT& rcArea ) { rcArea.left = 0; rcArea.right = w2(); rcArea.top = 2 + GetCaptionHeight() + 12; rcArea.bottom = rcArea.top + SCRUBBER_HEIGHT - 4; } void PhonemeEditor::DrawScrubHandle() { RECT rcHandle; GetScrubHandleRect( rcHandle, true ); rcHandle.left = 0; rcHandle.right = w2(); CChoreoWidgetDrawHelper drawHelper( this, rcHandle ); DrawScrubHandle( drawHelper ); } //----------------------------------------------------------------------------- // Purpose: // Input : drawHelper - // rcHandle - //----------------------------------------------------------------------------- void PhonemeEditor::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper ) { RECT rcHandle; GetScrubHandleRect( rcHandle, true ); HBRUSH br = CreateSolidBrush( ColorToRGB( Color( 0, 150, 100 ) ) ); Color areaBorder = Color( 230, 230, 220 ); drawHelper.DrawColoredLine( areaBorder, PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); drawHelper.DrawColoredLine( areaBorder, PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); drawHelper.DrawFilledRect( br, rcHandle ); // char sz[ 32 ]; sprintf( sz, "%.3f", m_flScrub ); int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); RECT rcText = rcHandle; int textw = rcText.right - rcText.left; rcText.left += ( textw - len ) / 2; drawHelper.DrawColoredText( "Arial", 9, 500, Color( 255, 255, 255 ), rcText, sz ); DeleteObject( br ); } //----------------------------------------------------------------------------- // Purpose: // Input : *event - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool PhonemeEditor::IsMouseOverScrubHandle( mxEvent *event ) { RECT rcHandle; GetScrubHandleRect( rcHandle, true ); InflateRect( &rcHandle, 2, 2 ); POINT pt; pt.x = (short)event->x; pt.y = (short)event->y; if ( PtInRect( &rcHandle, pt ) ) { return true; } return false; } bool PhonemeEditor::IsMouseOverScrubArea( mxEvent *event ) { RECT rcArea; rcArea.left = 0; rcArea.right = w2(); rcArea.top = 2 + GetCaptionHeight() + 12; rcArea.bottom = rcArea.top + 10; InflateRect( &rcArea, 2, 2 ); POINT pt; pt.x = (short)event->x; pt.y = (short)event->y; if ( PtInRect( &rcArea, pt ) ) { return true; } return false; } float PhonemeEditor::GetTimeForSample( int sample ) { if ( !m_pWaveFile ) { return 0.0f; } float duration = m_pWaveFile->GetRunningLength(); int sampleCount = m_pWaveFile->SampleCount(); if ( sampleCount <= 0 ) return 0.0f; float frac = (float)sample / (float)sampleCount; return frac * duration; } void PhonemeEditor::ClampTimeToSelectionInterval( float& timeval ) { if ( !m_pWaveFile ) { return; } if ( !m_pMixer || !sound->IsSoundPlaying( m_pMixer ) ) { return; } if ( !m_bSelectionActive ) return; float starttime = GetTimeForSample( m_nSelection[ 0 ] ); float endtime = GetTimeForSample( m_nSelection[ 1 ] ); Assert( starttime <= endtime ); timeval = clamp( timeval, starttime, endtime ); } void PhonemeEditor::ScrubThink( float dt, bool scrubbing ) { ClampTimeToSelectionInterval( m_flScrub ); ClampTimeToSelectionInterval( m_flScrubTarget ); if ( m_flScrubTarget == m_flScrub && !scrubbing ) { if ( sound->IsSoundPlaying( m_pMixer ) ) { sound->StopSound( m_pMixer ); } return; } if ( !m_pWaveFile ) return; bool newmixer = false; if ( !m_pMixer || !sound->IsSoundPlaying( m_pMixer ) ) { m_pMixer = NULL; SaveLinguisticData(); StudioModel *model = NULL;//models->GetActiveStudioModel(); sound->PlaySound( model, VOL_NORM, m_WorkFile.m_szWorkingFile, &m_pMixer ); newmixer = true; } if ( !m_pMixer ) { return; } if ( m_flScrub > m_flScrubTarget ) { m_pMixer->SetDirection( false ); } else { m_pMixer->SetDirection( true ); } float duration = m_pWaveFile->GetRunningLength(); if ( !duration ) return; float d = m_flScrubTarget - m_flScrub; int sign = d > 0.0f ? 1 : -1; float maxmove = dt * m_flPlaybackRate; if ( sign > 0 ) { if ( d < maxmove ) { m_flScrub = m_flScrubTarget; } else { m_flScrub += maxmove; } } else { if ( -d < maxmove ) { m_flScrub = m_flScrubTarget; } else { m_flScrub -= maxmove; } } int sampleCount = m_pMixer->GetSource()->SampleCount(); int cursample = sampleCount * ( m_flScrub / duration ); int realsample = m_pMixer->GetSamplePosition(); int dsample = cursample - realsample; int onehundredth = m_pMixer->GetSource()->SampleRate() * 0.01f; if ( abs( dsample ) > onehundredth ) { m_pMixer->SetSamplePosition( cursample, true ); } m_pMixer->SetActive( true ); RECT rcArea; GetScrubAreaRect( rcArea ); CChoreoWidgetDrawHelper drawHelper( this, rcArea ); DrawScrubHandle( drawHelper ); if ( scrubbing ) { g_pMatSysWindow->Frame(); } } void PhonemeEditor::SetScrubTime( float t ) { m_flScrub = t; ClampTimeToSelectionInterval( m_flScrub ); } void PhonemeEditor::SetScrubTargetTime( float t ) { m_flScrubTarget = t; ClampTimeToSelectionInterval( m_flScrubTarget ); } void PhonemeEditor::OnToggleVoiceDuck() { SetDirty( true ); PushUndo(); m_Tags.SetVoiceDuck( !m_Tags.GetVoiceDuck() ); PushRedo(); redraw(); } void PhonemeEditor::Play() { PlayEditedWave( m_bSelectionActive ); }