Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

8799 lines
190 KiB

//========= 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 );
}