summaryrefslogtreecommitdiff
path: root/utils/hlfaceposer/phonemeeditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/hlfaceposer/phonemeeditor.cpp')
-rw-r--r--utils/hlfaceposer/phonemeeditor.cpp8799
1 files changed, 8799 insertions, 0 deletions
diff --git a/utils/hlfaceposer/phonemeeditor.cpp b/utils/hlfaceposer/phonemeeditor.cpp
new file mode 100644
index 0000000..e33f6c1
--- /dev/null
+++ b/utils/hlfaceposer/phonemeeditor.cpp
@@ -0,0 +1,8799 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//===========================================================================//
+#include <Assert.h>
+#include <stdio.h>
+#include <math.h>
+#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
+ COLORREF root_color;
+ COLORREF gray_color; // if mode is wrong...
+};
+
+static PhonemeEditorColor g_PEColors[ NUM_COLORS ] =
+{
+ { COLOR_PHONEME_BACKGROUND, -1, RGB( 240, 240, 220 ) },
+ { COLOR_PHONEME_TEXT, -1, RGB( 63, 63, 63 ) },
+ { COLOR_PHONEME_LIGHTTEXT, 0, RGB( 180, 180, 120 ) },
+ { COLOR_PHONEME_PLAYBACKTICK, 0, RGB( 255, 0, 0 ) },
+ { COLOR_PHONEME_WAVDATA, 0, RGB( 128, 31, 63 ) },
+ { COLOR_PHONEME_TIMELINE, 0, RGB( 31, 31, 127 ) },
+ { COLOR_PHONEME_TIMELINE_MAJORTICK, 0, RGB( 200, 200, 255 ) },
+ { COLOR_PHONEME_TIMELINE_MINORTICK, 0, RGB( 210, 210, 240 ) },
+ { COLOR_PHONEME_EXTRACTION_RESULT_FAIL, 0, RGB( 180, 180, 0 ) },
+ { COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS, 0, RGB( 100, 180, 100 ) },
+ { COLOR_PHONEME_EXTRACTION_RESULT_ERROR, 0, RGB( 255, 31, 31 ) },
+ { COLOR_PHONEME_EXTRACTION_RESULT_OTHER, 0, RGB( 63, 63, 63 ) },
+ { COLOR_PHONEME_TAG_BORDER, 0, RGB( 160, 100, 100 ) },
+ { COLOR_PHONEME_TAG_BORDER_SELECTED, 0, RGB( 255, 40, 60 ) },
+ { COLOR_PHONEME_TAG_FILLER_NORMAL, 0, RGB( 210, 210, 190 ) },
+ { COLOR_PHONEME_TAG_SELECTED, 0, RGB( 200, 130, 130 ) },
+ { COLOR_PHONEME_TAG_TEXT, 0, RGB( 63, 63, 63 ) },
+ { COLOR_PHONEME_TAG_TEXT_SELECTED, 0, RGB( 250, 250, 250 ) },
+ { COLOR_PHONEME_WAV_ENDPOINT, 0, RGB( 0, 0, 200 ) },
+ { COLOR_PHONEME_AB, 0, RGB( 63, 190, 210 ) },
+ { COLOR_PHONEME_AB_LINE, 0, RGB( 31, 150, 180 ) },
+ { COLOR_PHONEME_AB_TEXT, 0, RGB( 100, 120, 120 ) },
+ { COLOR_PHONEME_ACTIVE_BORDER, 0, RGB( 150, 240, 180 ) },
+ { COLOR_PHONEME_SELECTED_BORDER, 0, RGB( 255, 0, 0 ) },
+ { COLOR_PHONEME_TIMING_TAG, -1, RGB( 0, 100, 200 ) },
+
+ { COLOR_PHONEME_EMPHASIS_BG, 1, RGB( 230, 230, 200 ) },
+ { COLOR_PHONEME_EMPHASIS_BG_STRONG, 1, RGB( 163, 201, 239 ) },
+ { COLOR_PHONEME_EMPHASIS_BG_WEAK, 1, RGB( 237, 239, 163 ) },
+ { COLOR_PHONEME_EMPHASIS_BORDER, 1, RGB( 200, 200, 200 ) },
+ { COLOR_PHONEME_EMPHASIS_LINECOLOR, 1, RGB( 0, 0, 255 ) },
+ { COLOR_PHONEME_EMPHASIS_DOTCOLOR, 1, RGB( 0, 0, 255 ) },
+ { COLOR_PHONEME_EMPHASIS_DOTCOLOR_SELECTED, 1, RGB( 240, 80, 20 ) },
+ { COLOR_PHONEME_EMPHASIS_TEXT, 1, RGB( 0, 0, 0 ) },
+ { COLOR_PHONEME_EMPHASIS_MIDLINE, 1, RGB( 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:
+ int open( const char *pFileName )
+ {
+ return (int)filesystem->Open( pFileName, "rb" );
+ }
+
+ int read( void *pOutput, int size, int file )
+ {
+ if ( !file )
+ return 0;
+
+ return filesystem->Read( pOutput, size, (FileHandle_t)file );
+ }
+
+ void seek( int file, int pos )
+ {
+ if ( !file )
+ return;
+
+ filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
+ }
+
+ unsigned int tell( int file )
+ {
+ if ( !file )
+ return 0;
+
+ return filesystem->Tell( (FileHandle_t)file );
+ }
+
+ unsigned int size( int file )
+ {
+ if ( !file )
+ return 0;
+
+ return filesystem->Size( (FileHandle_t)file );
+ }
+
+ void close( int file )
+ {
+ if ( !file )
+ return;
+
+ filesystem->Close( (FileHandle_t)file );
+ }
+};
+
+class StdIOWriteBinary : public IFileWriteBinary
+{
+public:
+ int create( const char *pFileName )
+ {
+ MakeFileWriteable( pFileName );
+ return (int)filesystem->Open( pFileName, "wb" );
+ }
+
+ int write( void *pData, int size, int file )
+ {
+ return filesystem->Write( pData, size, (FileHandle_t)file );
+ }
+
+ void close( int file )
+ {
+ filesystem->Close( (FileHandle_t)file );
+ }
+
+ void seek( int file, int pos )
+ {
+ filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
+ }
+
+ unsigned int tell( int file )
+ {
+ return filesystem->Tell( (FileHandle_t)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
+ {
+ COLORREF bgColor = g_PEColors[ COLOR_PHONEME_BACKGROUND ].root_color;
+
+ int bgr, bgg, bgb;
+
+ bgr = GetRValue( bgColor );
+ bgg = GetGValue( bgColor );
+ bgb = GetBValue( bgColor );
+
+ int r, g, b;
+
+ r = GetRValue( p->root_color );
+ g = GetGValue( p->root_color );
+ b = GetBValue( p->root_color );
+
+ int avg = ( r + g + b ) / 3;
+ int bgavg = ( bgr + bgg + bgb ) / 3;
+
+ // Bias toward bg color
+ avg += ( bgavg - avg ) / 2.5;
+
+ p->gray_color = RGB( avg, avg, avg );
+ }
+ }
+}
+
+COLORREF PhonemeEditor::PEColor( int colornum )
+{
+ COLORREF clr = RGB( 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( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Edit Word" );
+ strcpy( params.m_szPrompt, "Current Word:" );
+ V_strcpy_safe( 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( &params );
+ 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( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
+ V_strcpy_safe( 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( &params );
+ 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.Size() <= 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.Size(); 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.Size(); i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = 0; j < word->m_Phonemes.Size(); 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.Size(); 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.Size() && !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 );
+
+ COLORREF 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( (LONG)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.Size(); i++ )
+ {
+ CWordTag *w = sentence.m_Words[ i ];
+ if ( !w )
+ continue;
+
+ if ( w->m_bSelected != drawselected )
+ continue;
+
+ for ( int k = 0; k < w->m_Phonemes.Size(); 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 );
+
+ COLORREF 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( (LONG)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( ( 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( 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 )
+ {
+ COLORREF 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.Size(), 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.Size() );
+
+ 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.Size() > 0 )
+ {
+ int length = drawHelper.CalcTextWidth( font, fontsize, fontweight,
+ "Undo levels: %i/%i", m_nUndoLevel, m_UndoStack.Size() );
+
+ 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.Size() );
+ }
+
+ 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.Size(); i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+
+ for ( int k = 0; k < word->m_Phonemes.Size(); 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.Size(); 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.Size(); 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.Size() == 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.Size() > 0 )
+ {
+ pop->addSeparator();
+ pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP );
+ }
+
+ if ( m_Tags.m_Words.Size() > 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.Size() > 0 )
+ {
+ pop->addSeparator();
+ pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP );
+ }
+
+ if ( m_Tags.m_Words.Size() > 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.Size(); w++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ w ];
+
+ for ( int i = 0; i < word->m_Phonemes.Size(); 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.Size() > 0 )
+ {
+ // Check last word, but only if it has some phonemes
+ CWordTag *lastWord = m_Tags.m_Words[ m_Tags.m_Words.Size() - 1 ];
+ if ( lastWord &&
+ ( lastWord->m_Phonemes.Size() > 0 ) )
+ {
+
+ CPhonemeTag *last = lastWord->m_Phonemes[ lastWord->m_Phonemes.Size() - 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.Size(); 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.Size() > 0 )
+ {
+ CWordTag *last = m_Tags.m_Words[ m_Tags.m_Words.Size() - 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.Size(); 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, &current, &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.Size(); w++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ w ];
+
+
+ for ( int i = 0; i < word->m_Phonemes.Size(); 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, &current, &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( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
+ strcpy( params.m_szName, "" );
+
+ int iret = PhonemeProperties( &params );
+ 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( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
+ strcpy( params.m_szName, "" );
+
+ int iret = PhonemeProperties( &params );
+ 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( &params, 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( &params );
+ 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( &params, 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( &params );
+ 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.Size() - 1; i >= 0; i-- )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = word->m_Phonemes.Size() - 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.Size() - 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( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Phrase Word List" );
+ strcpy( params.m_szPrompt, "Phrase" );
+
+ strcpy( params.m_szInputText, wordstring );
+
+ if ( !InputProperties( &params ) )
+ 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.Size() == 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.Size(); 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",
+ szCroppedFile,
+ m_nSelection[ 0 ],
+ m_nSelection[ 1 ] );
+ 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.Size() != m_Results.m_Words.Size() )
+ {
+ Con_Printf( "Extraction returned %i words, source had %i, try adjusting selection\n",
+ m_Results.m_Words.Size(), m_InputWords.m_Words.Size() );
+
+ 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.Size(); 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.Size(); 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.Size() ; i++ )
+ {
+ to = m_TagsExt.m_Words[ i ];
+ if ( !to || !to->m_bSelected )
+ continue;
+
+ // Found start of contiguous run
+ if ( fromWord >= m_Results.m_Words.Size() )
+ break;
+
+ from = m_Results.m_Words[ fromWord++ ];
+ Assert( from );
+ if ( !from )
+ continue;
+
+ // Remove all phonemes from destination
+ while ( to->m_Phonemes.Size() > 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.Size(); 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.Size() )
+ 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.Size(); 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.Size(); 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.Size(); 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.Size(); 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.Size(); 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.Size(); i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ Assert( word );
+ if ( !word )
+ continue;
+
+ for ( int k = 0; k < word->m_Phonemes.Size(); 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.Size(); 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.Size(); 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.Size(); w++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ w ];
+ Assert( word );
+ if ( !word )
+ continue;
+
+ for ( int i = 0 ; i < word->m_Phonemes.Size(); 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.Size() < 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.Size() - 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.Size() < 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.Size() - 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 ];
+ V_strcpy_safe( wordstring, m_Tags.GetText() );
+
+ CInputParams params;
+ memset( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Word List" );
+ strcpy( params.m_szPrompt, "Sentence:" );
+
+ strcpy( params.m_szInputText, wordstring );
+
+ if ( !InputProperties( &params ) )
+ 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.Size() == 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.Size(); w++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ w ];
+ Assert( word );
+ if ( !word )
+ continue;
+
+ for ( int i = 0; i < word->m_Phonemes.Size(); 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.Size(); w++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ w ];
+ Assert( word );
+ if ( !word )
+ continue;
+
+ for ( int i = 0; i < word->m_Phonemes.Size(); 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.Size() )
+ 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( &params, 0, sizeof( params ) );
+ strcpy( params.m_szDialogTitle, "Event Tag Name" );
+ strcpy( params.m_szPrompt, "Name:" );
+ strcpy( params.m_szInputText, "" );
+
+ if ( !InputProperties( &params ) )
+ 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.Size(); 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.Size(); i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = 0; j < word->m_Phonemes.Size(); 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.Size() > 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.Size() > 0 && m_nUndoLevel <= m_UndoStack.Size() - 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.Size() > 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.Size() > 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.Size() )
+ return NULL;
+
+ CWordTag *word = m_Tags.m_Words[ m_nClickedWord ];
+ if ( !word )
+ return NULL;
+
+ if ( m_nClickedPhoneme >= word->m_Phonemes.Size() )
+ 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.Size() )
+ 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.Size() )
+ {
+ pop->addSeparator();
+ pop->add( va( "Commit extraction" ) , IDC_COMMITEXTRACTED );
+ pop->add( va( "Clear extraction" ), IDC_CLEAREXTRACTED );
+ }
+
+ if ( m_nUndoLevel != 0 || m_nUndoLevel != m_UndoStack.Size() )
+ {
+ pop->addSeparator();
+ if ( m_nUndoLevel != 0 )
+ {
+ pop->add( va( "Undo" ), IDC_UNDO );
+ }
+ if ( m_nUndoLevel != m_UndoStack.Size() )
+ {
+ pop->add( va( "Redo" ), IDC_REDO );
+ }
+ pop->add( va( "Clear Undo Info" ), IDC_CLEARUNDO );
+ }
+
+ if ( m_Tags.m_Words.Size() > 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.Size() )
+ {
+ pop->addSeparator();
+
+ if ( m_nUndoLevel != 0 )
+ {
+ pop->add( va( "Undo" ), IDC_UNDO );
+ }
+ if ( m_nUndoLevel != m_UndoStack.Size() )
+ {
+ 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.Size() > 0 )
+ {
+ m_nSelectedPhonemeCount = 1;
+ CPhonemeTag *p = word->m_Phonemes[ direction ? word->m_Phonemes.Size() - 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.Size(); i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = 0; j < word->m_Phonemes.Size(); 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.Size() - 1;
+ }
+ else if ( nextindex >= word->m_Phonemes.Size() )
+ {
+ 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.Size(); 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.Size() > 0 )
+ {
+ CWordTag *word = m_Tags.m_Words[ direction ? m_Tags.m_Words.Size() - 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.Size(); 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.Size(); 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.Size() - 1;
+ }
+ else if ( nextword >= m_Tags.m_Words.Size() )
+ {
+ nextword = 0;
+ }
+
+ word = m_Tags.m_Words[ nextword ];
+ word->m_bSelected = true;
+
+ if ( word->m_Phonemes.Size() > 0 )
+ {
+ CPhonemeTag *phoneme = NULL;
+
+ if ( direction > 0 )
+ {
+ phoneme = word->m_Phonemes[ 0 ];
+ }
+ else
+ {
+ phoneme = word->m_Phonemes[ word->m_Phonemes.Size() - 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.Size(); 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.Size() )
+ {
+ 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.Size() > 0 )
+ {
+ if ( m_Tags.m_Words[ wordnum ]->m_Phonemes.Size() > 0 )
+ {
+ nextPhoneme = m_Tags.m_Words[ wordnum ]->m_Phonemes[ m_Tags.m_Words[ wordnum ]->m_Phonemes.Size() - 1 ];
+ break;
+ }
+ }
+ wordnum--;
+ }
+ }
+ // Try first phoneme of next word, if there is one
+ else if ( nextphoneme >= word->m_Phonemes.Size() )
+ {
+ wordnum++;
+ while ( wordnum < m_Tags.m_Words.Size() )
+ {
+ if ( ppword )
+ {
+ *ppword = m_Tags.m_Words[ wordnum ];
+ }
+ // Really it can't be zero, but check anyway
+ if ( m_Tags.m_Words.Size() > 0 )
+ {
+ if ( m_Tags.m_Words[ wordnum ]->m_Phonemes.Size() > 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.Size(); i++ )
+ {
+ CWordTag *w = m_Tags.m_Words[ i ];
+ if ( !w )
+ continue;
+
+ for ( int j = 0; j < w->m_Phonemes.Size() ; 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.Size(); 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.Size(); 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.Size() != 0 )
+ {
+ Con_Printf( "Can't insert first phoneme into %s, already has phonemes\n", cw->GetWord() );
+ return;
+ }
+
+ CPhonemeParams params;
+ memset( &params, 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( &params );
+ 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.Size(); i++ )
+ {
+ phoneme = word->m_Phonemes[ i ];
+ phoneme->m_bSelected = true;
+ }
+
+ // Now start at next word
+ wordNum++;
+
+ for ( ; wordNum < m_Tags.m_Words.Size(); wordNum++ )
+ {
+ word = m_Tags.m_Words[ wordNum ];
+
+ for ( int j = 0; j < word->m_Phonemes.Size(); 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.Size(); 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.Size(); 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.Size() ; 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.Size() ; i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = 0 ; j < word->m_Phonemes.Size(); 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.Size() ; i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ CWordTag *next = NULL;
+ if ( i < m_Tags.m_Words.Size() - 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.Size(); j++ )
+ {
+ CPhonemeTag *phoneme = word->m_Phonemes[ j ];
+ if ( !phoneme )
+ continue;
+
+ CPhonemeTag *next = NULL;
+ if ( j < word->m_Phonemes.Size() - 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.Size() ; i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ CWordTag *next = NULL;
+ if ( i < m_Tags.m_Words.Size() - 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.Size() - 1 ];
+ if ( !LastPhoneme )
+ continue;
+
+ float phoneme_dt = LastPhoneme->GetEndTime() - FirstPhoneme->GetStartTime();
+
+ float phoneme_shift = FirstPhoneme->GetStartTime();
+
+ for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ )
+ {
+ CPhonemeTag *phoneme = word->m_Phonemes[ j ];
+ if ( !phoneme )
+ continue;
+
+ CPhonemeTag *next = NULL;
+ if ( j < word->m_Phonemes.Size() - 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.Size() ; 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.Size() - 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.Size(); 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.Size() ; 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.Size() -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.Size(); i++ )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = 0; j < word->m_Phonemes.Size(); 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.Size() -1; i >= 0; i-- )
+ {
+ CWordTag *word = m_Tags.m_Words[ i ];
+ if ( !word )
+ continue;
+
+ for ( int j = word->m_Phonemes.Size() - 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 );
+ }
+
+ COLORREF gray = PEColor( COLOR_PHONEME_EMPHASIS_MIDLINE );
+
+ drawHelper.DrawOutlinedRect( PEColor( COLOR_PHONEME_EMPHASIS_BORDER ), PS_SOLID, 1, rcClient );
+
+ COLORREF lineColor = PEColor( COLOR_PHONEME_EMPHASIS_LINECOLOR );
+ COLORREF dotColor = PEColor( COLOR_PHONEME_EMPHASIS_DOTCOLOR );
+ COLORREF 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;
+
+ COLORREF clr = dotColor;
+ COLORREF 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( RGB( 0, 150, 100 ) );
+
+ COLORREF areaBorder = RGB( 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, RGB( 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 );
+}
+
+
+
+
+