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
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( ¶ms, 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( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Validate parameters
|
|
if ( CSentence::CountWords( params.m_szInputText ) != 1 )
|
|
{
|
|
Con_ErrorPrintf( "Edit word: %s has multiple words in it!!!\n", params.m_szInputText );
|
|
return;
|
|
}
|
|
|
|
SetFocus( (HWND)getHandle() );
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
// Set the word and clear out the phonemes
|
|
// ->m_nPhonemeCode = TextToPhoneme( params.m_szName );
|
|
pWord->SetWord( params.m_szInputText );
|
|
|
|
PushRedo();
|
|
|
|
redraw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPhoneme -
|
|
// positionDialog -
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditPhoneme( CPhonemeTag *pPhoneme, bool positionDialog /*= false*/ )
|
|
{
|
|
if ( !pPhoneme )
|
|
{
|
|
Con_Printf( "PhonemeEditor::EditPhoneme: pPhoneme == NULL\n" );
|
|
return;
|
|
}
|
|
|
|
CPhonemeParams params;
|
|
memset( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
|
|
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( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
pPhoneme->SetPhonemeCode( TextToPhoneme( params.m_szName ) );
|
|
|
|
PushRedo();
|
|
|
|
redraw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditPhoneme( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CPhonemeTag *pPhoneme = GetClickedPhoneme();
|
|
if ( !pPhoneme )
|
|
return;
|
|
|
|
EditPhoneme( pPhoneme, false );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditWord( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CWordTag *pWord = GetClickedWord();
|
|
if ( !pWord )
|
|
return;
|
|
|
|
EditWord( pWord, false );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : dragtype -
|
|
// startx -
|
|
// cursor -
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor )
|
|
{
|
|
m_nDragType = dragtype;
|
|
m_nStartX = startx;
|
|
m_nLastX = startx;
|
|
m_nStartY = starty;
|
|
m_nLastY = starty;
|
|
|
|
if ( m_hPrevCursor )
|
|
{
|
|
SetCursor( m_hPrevCursor );
|
|
m_hPrevCursor = NULL;
|
|
}
|
|
m_hPrevCursor = SetCursor( cursor );
|
|
|
|
m_FocusRects.Purge();
|
|
|
|
RECT rc;
|
|
GetWorkspaceRect( rc );
|
|
|
|
RECT rcStart;
|
|
rcStart.left = startx;
|
|
rcStart.right = startx;
|
|
|
|
bool addrect = true;
|
|
switch ( dragtype )
|
|
{
|
|
default:
|
|
case DRAGTYPE_SCRUBBER:
|
|
{
|
|
RECT rcScrub;
|
|
GetScrubHandleRect( rcScrub, true );
|
|
|
|
rcStart = rcScrub;
|
|
rcStart.left = ( rcScrub.left + rcScrub.right ) / 2;
|
|
rcStart.right = rcStart.left;
|
|
rcStart.bottom = h2() - 18 - MODE_TAB_OFFSET;
|
|
}
|
|
break;
|
|
case DRAGTYPE_EMPHASIS_SELECT:
|
|
{
|
|
RECT rcEmphasis;
|
|
Emphasis_GetRect( rc, rcEmphasis );
|
|
|
|
rcStart.top = starty;
|
|
rcStart.bottom = starty;
|
|
}
|
|
break;
|
|
case DRAGTYPE_EMPHASIS_MOVE:
|
|
{
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
Emphasis_MouseDrag( startx, starty );
|
|
m_Tags.Resort();
|
|
|
|
addrect = false;
|
|
}
|
|
break;
|
|
case DRAGTYPE_SELECTSAMPLES:
|
|
case DRAGTYPE_MOVESELECTIONSTART:
|
|
case DRAGTYPE_MOVESELECTIONEND:
|
|
rcStart.top = rc.top;
|
|
rcStart.bottom = rc.bottom;
|
|
break;
|
|
case DRAGTYPE_MOVESELECTION:
|
|
{
|
|
rcStart.top = rc.top;
|
|
rcStart.bottom = rc.bottom;
|
|
|
|
// Compute left/right pixels for selection
|
|
rcStart.left = GetPixelForSample( m_nSelection[ 0 ] );
|
|
rcStart.right = GetPixelForSample( m_nSelection[ 1 ] );
|
|
}
|
|
break;
|
|
case DRAGTYPE_PHONEME:
|
|
{
|
|
GetPhonemeTrayTopBottom( rcStart );
|
|
m_bWordsActive = false;
|
|
}
|
|
break;
|
|
case DRAGTYPE_WORD:
|
|
{
|
|
GetWordTrayTopBottom( rcStart );
|
|
m_bWordsActive = true;
|
|
}
|
|
break;
|
|
case DRAGTYPE_MOVEWORD:
|
|
{
|
|
TraverseWords( &PhonemeEditor::ITER_AddFocusRectSelectedWords, 0.0f );
|
|
addrect = false;
|
|
m_bWordsActive = true;
|
|
}
|
|
break;
|
|
case DRAGTYPE_MOVEPHONEME:
|
|
{
|
|
TraversePhonemes( &PhonemeEditor::ITER_AddFocusRectSelectedPhonemes, 0.0f );
|
|
addrect = false;
|
|
m_bWordsActive = false;
|
|
}
|
|
break;
|
|
case DRAGTYPE_EVENTTAG_MOVE:
|
|
{
|
|
rcStart.top = TAG_TOP;
|
|
rcStart.bottom = TAG_BOTTOM;
|
|
rcStart.left -= 10;
|
|
rcStart.right += 10;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( addrect )
|
|
{
|
|
AddFocusRect( rcStart );
|
|
}
|
|
|
|
DrawFocusRect( "start" );
|
|
|
|
SetDragLimit( m_nDragType );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *event -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int PhonemeEditor::handleEvent( mxEvent *event )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
|
|
|
|
int iret = 0;
|
|
|
|
if ( HandleToolEvent( event ) )
|
|
{
|
|
return iret;
|
|
}
|
|
|
|
switch ( event->event )
|
|
{
|
|
case mxEvent::Action:
|
|
{
|
|
iret = 1;
|
|
switch ( event->action )
|
|
{
|
|
case IDC_EXPORT_SENTENCE:
|
|
{
|
|
OnExport();
|
|
}
|
|
break;
|
|
case IDC_IMPORT_SENTENCE:
|
|
{
|
|
OnImport();
|
|
}
|
|
break;
|
|
case IDC_PLAYBACKRATE:
|
|
{
|
|
m_flPlaybackRate = m_pPlaybackRate->getValue();
|
|
redraw();
|
|
}
|
|
break;
|
|
case IDC_MODE_TAB:
|
|
{
|
|
// The mode changed, so reset stuff here
|
|
EditorMode newMode = (EditorMode)m_pModeTab->getSelectedIndex();
|
|
bool needpaint = ( m_CurrentMode != newMode );
|
|
m_CurrentMode = newMode;
|
|
if ( needpaint )
|
|
{
|
|
switch ( GetMode() )
|
|
{
|
|
default:
|
|
case MODE_PHONEMES:
|
|
SetSuffix( " - Normal" );
|
|
break;
|
|
case MODE_EMPHASIS:
|
|
SetSuffix( " - Emphasis Track" );
|
|
break;
|
|
}
|
|
|
|
OnModeChanged();
|
|
redraw();
|
|
}
|
|
}
|
|
break;
|
|
case IDC_EMPHASIS_DELETE:
|
|
Emphasis_Delete();
|
|
break;
|
|
case IDC_EMPHASIS_DESELECT:
|
|
Emphasis_DeselectAll();
|
|
break;
|
|
case IDC_EMPHASIS_SELECTALL:
|
|
Emphasis_SelectAll();
|
|
break;
|
|
case IDC_API_SAPI:
|
|
OnSAPI();
|
|
break;
|
|
case IDC_API_LIPSINC:
|
|
OnLipSinc();
|
|
break;
|
|
case IDC_PLAYBUTTON:
|
|
Play();
|
|
break;
|
|
case IDC_UNDO:
|
|
Undo();
|
|
break;
|
|
case IDC_REDO:
|
|
Redo();
|
|
break;
|
|
case IDC_CLEARUNDO:
|
|
ClearUndo();
|
|
break;
|
|
case IDC_ADDTAG:
|
|
AddTag();
|
|
break;
|
|
case IDC_DELETETAG:
|
|
DeleteTag();
|
|
break;
|
|
case IDC_COMMITEXTRACTED:
|
|
CommitExtracted();
|
|
SetFocus( (HWND)getHandle() );
|
|
break;
|
|
case IDC_CLEAREXTRACTED:
|
|
ClearExtracted();
|
|
break;
|
|
case IDC_SEPARATEPHONEMES:
|
|
SeparatePhonemes();
|
|
break;
|
|
case IDC_SNAPPHONEMES:
|
|
SnapPhonemes();
|
|
break;
|
|
case IDC_SEPARATEWORDS:
|
|
SeparateWords();
|
|
break;
|
|
case IDC_SNAPWORDS:
|
|
SnapWords();
|
|
break;
|
|
case IDC_EDITWORDLIST:
|
|
EditWordList();
|
|
break;
|
|
case IDC_EDIT_PHONEME:
|
|
EditPhoneme();
|
|
break;
|
|
case IDC_EDIT_WORD:
|
|
EditWord();
|
|
break;
|
|
case IDC_EDIT_INSERTPHONEMEBEFORE:
|
|
EditInsertPhonemeBefore();
|
|
break;
|
|
case IDC_EDIT_INSERTPHONEMEAFTER:
|
|
EditInsertPhonemeAfter();
|
|
break;
|
|
case IDC_EDIT_INSERTWORDBEFORE:
|
|
EditInsertWordBefore();
|
|
break;
|
|
case IDC_EDIT_INSERTWORDAFTER:
|
|
EditInsertWordAfter();
|
|
break;
|
|
case IDC_EDIT_DELETEPHONEME:
|
|
EditDeletePhoneme();
|
|
break;
|
|
case IDC_EDIT_DELETEWORD:
|
|
EditDeleteWord();
|
|
break;
|
|
case IDC_EDIT_INSERTFIRSTPHONEMEOFWORD:
|
|
EditInsertFirstPhonemeOfWord();
|
|
break;
|
|
case IDC_PHONEME_PLAY_ORIG:
|
|
{
|
|
StopPlayback();
|
|
if ( m_pWaveFile )
|
|
{
|
|
// Make sure phonemes are loaded
|
|
FacePoser_EnsurePhonemesLoaded();
|
|
|
|
sound->PlaySound( m_pWaveFile, VOL_NORM, &m_pMixer );
|
|
}
|
|
}
|
|
break;
|
|
case IDC_PHONEME_SCROLL:
|
|
if (event->modifiers == SB_THUMBTRACK)
|
|
{
|
|
MoveTimeSliderToPos( event->height );
|
|
}
|
|
else if ( event->modifiers == SB_PAGEUP )
|
|
{
|
|
int offset = m_pHorzScrollBar->getValue();
|
|
|
|
offset -= 10;
|
|
offset = max( offset, m_pHorzScrollBar->getMinValue() );
|
|
|
|
MoveTimeSliderToPos( offset );
|
|
}
|
|
else if ( event->modifiers == SB_PAGEDOWN )
|
|
{
|
|
int offset = m_pHorzScrollBar->getValue();
|
|
|
|
offset += 10;
|
|
offset = min( offset, m_pHorzScrollBar->getMaxValue() );
|
|
|
|
MoveTimeSliderToPos( offset );
|
|
}
|
|
break;
|
|
case IDC_REDO_PHONEMEEXTRACTION:
|
|
if ( m_Tags.m_Words.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, ¤t, &next ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
if ( current && !next )
|
|
{
|
|
// cap movement
|
|
current->m_flEndTime += ( endtime - clicktime );
|
|
}
|
|
else if ( !current && next )
|
|
{
|
|
// cap movement
|
|
next->m_flStartTime += ( endtime - clicktime );
|
|
}
|
|
else
|
|
{
|
|
// cap movement
|
|
endtime = min( endtime, next->m_flEndTime - 1.0f / GetPixelsPerSecond() );
|
|
endtime = max( endtime, current->m_flStartTime + 1.0f / GetPixelsPerSecond() );
|
|
|
|
current->m_flEndTime = endtime;
|
|
next->m_flStartTime = endtime;
|
|
}
|
|
|
|
RealignPhonemesToWords( false );
|
|
CleanupWordsAndPhonemes( false );
|
|
|
|
PushRedo();
|
|
|
|
redraw();
|
|
}
|
|
|
|
CPhonemeTag *PhonemeEditor::FindPhonemeForTime( float time )
|
|
{
|
|
for ( int w = 0 ; w < m_Tags.m_Words.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, ¤t, &next ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
if ( current && !next )
|
|
{
|
|
// cap movement
|
|
current->AddEndTime( endtime - clicktime );
|
|
}
|
|
else if ( !current && next )
|
|
{
|
|
// cap movement
|
|
next->AddStartTime( endtime - clicktime );
|
|
}
|
|
else
|
|
{
|
|
// cap movement
|
|
endtime = min( endtime, next->GetEndTime() - 1.0f / GetPixelsPerSecond() );
|
|
endtime = max( endtime, current->GetStartTime() + 1.0f / GetPixelsPerSecond() );
|
|
|
|
current->SetEndTime( endtime );
|
|
next->SetStartTime( endtime );
|
|
}
|
|
|
|
RealignWordsToPhonemes( false );
|
|
CleanupWordsAndPhonemes( false );
|
|
|
|
PushRedo();
|
|
|
|
redraw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : dirty -
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::SetDirty( bool dirty, bool clearundo /*=true*/ )
|
|
{
|
|
m_WorkFile.m_bDirty = dirty;
|
|
|
|
if ( !dirty && clearundo )
|
|
{
|
|
WipeUndo();
|
|
redraw();
|
|
}
|
|
|
|
SetPrefix( dirty ? "* " : "" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool PhonemeEditor::GetDirty( void )
|
|
{
|
|
return m_WorkFile.m_bDirty;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditInsertPhonemeBefore( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CPhonemeTag *cp = GetSelectedPhoneme();
|
|
if ( !cp )
|
|
return;
|
|
|
|
float gap = GetTimeGapToNextPhoneme( false, cp );
|
|
if ( gap < MINIMUM_PHONEME_GAP )
|
|
{
|
|
Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap );
|
|
return;
|
|
}
|
|
|
|
// Don't have really long phonemes
|
|
gap = min( gap, DEFAULT_PHONEME_LENGTH );
|
|
|
|
CWordTag *word = m_Tags.GetWordForPhoneme( cp );
|
|
if ( !word )
|
|
{
|
|
Con_Printf( "EditInsertPhonemeBefore: phoneme not a member of any known word!!!\n" );
|
|
return;
|
|
}
|
|
|
|
int clicked = word->IndexOfPhoneme( cp );
|
|
if ( clicked < 0 )
|
|
{
|
|
Con_Printf( "EditInsertPhonemeBefore: phoneme not a member of any specified word!!!\n" );
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
CPhonemeTag phoneme;
|
|
|
|
CPhonemeParams params;
|
|
memset( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
|
|
strcpy( params.m_szName, "" );
|
|
|
|
int iret = PhonemeProperties( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
phoneme.SetPhonemeCode( TextToPhoneme( params.m_szName ) );
|
|
phoneme.SetTag( params.m_szName );
|
|
|
|
phoneme.SetEndTime( cp->GetStartTime() );
|
|
phoneme.SetStartTime( cp->GetStartTime() - gap );
|
|
phoneme.m_bSelected = true;
|
|
cp->m_bSelected = false;
|
|
|
|
word->m_Phonemes.InsertBefore( clicked, new CPhonemeTag( phoneme ) );
|
|
|
|
PushRedo();
|
|
|
|
// Add it
|
|
redraw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditInsertPhonemeAfter( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CPhonemeTag *cp = GetSelectedPhoneme();
|
|
if ( !cp )
|
|
return;
|
|
|
|
float gap = GetTimeGapToNextPhoneme( true, cp );
|
|
if ( gap < MINIMUM_PHONEME_GAP )
|
|
{
|
|
Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap );
|
|
return;
|
|
}
|
|
|
|
// Don't have really long phonemes
|
|
gap = min( gap, DEFAULT_PHONEME_LENGTH );
|
|
|
|
CWordTag *word = m_Tags.GetWordForPhoneme( cp );
|
|
if ( !word )
|
|
{
|
|
Con_Printf( "EditInsertPhonemeAfter: phoneme not a member of any known word!!!\n" );
|
|
return;
|
|
}
|
|
|
|
int clicked = word->IndexOfPhoneme( cp );
|
|
if ( clicked < 0 )
|
|
{
|
|
Con_Printf( "EditInsertPhonemeAfter: phoneme not a member of any specified word!!!\n" );
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
CPhonemeTag phoneme;
|
|
|
|
CPhonemeParams params;
|
|
memset( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
|
|
strcpy( params.m_szName, "" );
|
|
|
|
int iret = PhonemeProperties( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
phoneme.SetPhonemeCode( TextToPhoneme( params.m_szName ) );
|
|
phoneme.SetTag( params.m_szName );
|
|
|
|
phoneme.SetEndTime( cp->GetEndTime() + gap );
|
|
phoneme.SetStartTime( cp->GetEndTime() );
|
|
phoneme.m_bSelected = true;
|
|
cp->m_bSelected = false;
|
|
|
|
word->m_Phonemes.InsertAfter( clicked, new CPhonemeTag( phoneme ) );
|
|
|
|
PushRedo();
|
|
|
|
// Add it
|
|
redraw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditInsertWordBefore( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CWordTag *cw = GetSelectedWord();
|
|
if ( !cw )
|
|
return;
|
|
|
|
float gap = GetTimeGapToNextWord( false, cw );
|
|
if ( gap < MINIMUM_WORD_GAP )
|
|
{
|
|
Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap );
|
|
return;
|
|
}
|
|
|
|
// Don't have really long words
|
|
gap = min( gap, DEFAULT_WORD_LENGTH );
|
|
|
|
int clicked = IndexOfWord( cw );
|
|
if ( clicked < 0 )
|
|
{
|
|
Con_Printf( "EditInsertWordBefore: word not in sentence!!!\n" );
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
CInputParams params;
|
|
memset( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Insert Word" );
|
|
strcpy( params.m_szPrompt, "Word:" );
|
|
strcpy( params.m_szInputText, "" );
|
|
|
|
params.m_nLeft = -1;
|
|
params.m_nTop = -1;
|
|
|
|
params.m_bPositionDialog = true;
|
|
if ( params.m_bPositionDialog )
|
|
{
|
|
RECT rcWord;
|
|
GetWordRect( cw, rcWord );
|
|
|
|
// Convert to screen coords
|
|
POINT pt;
|
|
pt.x = rcWord.left;
|
|
pt.y = rcWord.top;
|
|
|
|
ClientToScreen( (HWND)getHandle(), &pt );
|
|
|
|
params.m_nLeft = pt.x;
|
|
params.m_nTop = pt.y;
|
|
}
|
|
|
|
int iret = InputProperties( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( strlen( params.m_szInputText ) <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int wordCount = CSentence::CountWords( params.m_szInputText );
|
|
if ( wordCount > 1 )
|
|
{
|
|
Con_Printf( "Can only insert one word at a time, %s has %i words in it!\n",
|
|
params.m_szInputText, wordCount );
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
CWordTag newword;
|
|
|
|
newword.SetWord( params.m_szInputText );
|
|
|
|
newword.m_flEndTime = cw->m_flStartTime;
|
|
newword.m_flStartTime = cw->m_flStartTime - gap;
|
|
newword.m_bSelected = true;
|
|
cw->m_bSelected = false;
|
|
|
|
m_Tags.m_Words.InsertBefore( clicked, new CWordTag( newword ) );
|
|
|
|
PushRedo();
|
|
|
|
// Add it
|
|
redraw();
|
|
|
|
// Jump to phoneme insertion UI
|
|
EditInsertFirstPhonemeOfWord();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditInsertWordAfter( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CWordTag *cw = GetSelectedWord();
|
|
if ( !cw )
|
|
return;
|
|
|
|
float gap = GetTimeGapToNextWord( true, cw );
|
|
if ( gap < MINIMUM_WORD_GAP )
|
|
{
|
|
Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap );
|
|
return;
|
|
}
|
|
|
|
// Don't have really long words
|
|
gap = min( gap, DEFAULT_WORD_LENGTH );
|
|
|
|
int clicked = IndexOfWord( cw );
|
|
if ( clicked < 0 )
|
|
{
|
|
Con_Printf( "EditInsertWordBefore: word not in sentence!!!\n" );
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
CInputParams params;
|
|
memset( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Insert Word" );
|
|
strcpy( params.m_szPrompt, "Word:" );
|
|
strcpy( params.m_szInputText, "" );
|
|
|
|
params.m_nLeft = -1;
|
|
params.m_nTop = -1;
|
|
|
|
params.m_bPositionDialog = true;
|
|
if ( params.m_bPositionDialog )
|
|
{
|
|
RECT rcWord;
|
|
GetWordRect( cw, rcWord );
|
|
|
|
// Convert to screen coords
|
|
POINT pt;
|
|
pt.x = rcWord.left;
|
|
pt.y = rcWord.top;
|
|
|
|
ClientToScreen( (HWND)getHandle(), &pt );
|
|
|
|
params.m_nLeft = pt.x;
|
|
params.m_nTop = pt.y;
|
|
}
|
|
|
|
int iret = InputProperties( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( strlen( params.m_szInputText ) <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int wordCount = CSentence::CountWords( params.m_szInputText );
|
|
if ( wordCount > 1 )
|
|
{
|
|
Con_Printf( "Can only insert one word at a time, %s has %i words in it!\n",
|
|
params.m_szInputText, wordCount );
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
CWordTag newword;
|
|
|
|
newword.SetWord( params.m_szInputText );
|
|
|
|
newword.m_flEndTime = cw->m_flEndTime + gap;
|
|
newword.m_flStartTime = cw->m_flEndTime;
|
|
newword.m_bSelected = true;
|
|
cw->m_bSelected = false;
|
|
|
|
CWordTag *w = new CWordTag( newword );
|
|
Assert( w );
|
|
if ( w )
|
|
{
|
|
m_Tags.m_Words.InsertAfter( clicked, w );
|
|
}
|
|
|
|
PushRedo();
|
|
|
|
// Add it
|
|
redraw();
|
|
|
|
EditInsertFirstPhonemeOfWord();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::EditDeletePhoneme( void )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CountSelected();
|
|
|
|
if ( m_nSelectedPhonemeCount < 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
for ( int i = m_Tags.m_Words.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( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Phrase Word List" );
|
|
strcpy( params.m_szPrompt, "Phrase" );
|
|
|
|
strcpy( params.m_szInputText, wordstring );
|
|
|
|
if ( !InputProperties( ¶ms ) )
|
|
return;
|
|
|
|
if ( strlen( params.m_szInputText ) <= 0 )
|
|
{
|
|
Con_ErrorPrintf( "Edit word list: No words entered!\n" );
|
|
return;
|
|
}
|
|
|
|
SentenceFromString( m_InputWords, params.m_szInputText );
|
|
|
|
if ( m_InputWords.m_Words.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( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Word List" );
|
|
strcpy( params.m_szPrompt, "Sentence:" );
|
|
|
|
strcpy( params.m_szInputText, wordstring );
|
|
|
|
if ( !InputProperties( ¶ms ) )
|
|
return;
|
|
|
|
if ( strlen( params.m_szInputText ) <= 0 )
|
|
{
|
|
// Could be foreign language...
|
|
Warning( "Edit word list: No words entered!\n" );
|
|
}
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
// Clear any current LISET results
|
|
ClearExtracted();
|
|
|
|
// Force text
|
|
m_Tags.SetText( params.m_szInputText );
|
|
|
|
if ( m_Tags.m_Words.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( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Event Tag Name" );
|
|
strcpy( params.m_szPrompt, "Name:" );
|
|
strcpy( params.m_szInputText, "" );
|
|
|
|
if ( !InputProperties( ¶ms ) )
|
|
return;
|
|
|
|
if ( strlen( params.m_szInputText ) <= 0 )
|
|
{
|
|
Con_ErrorPrintf( "Event Tag Name: No name entered!\n" );
|
|
return;
|
|
}
|
|
|
|
// Convert mouse position to time
|
|
float starttime = m_nLeftOffset / GetPixelsPerSecond();
|
|
float clicktime = (float)m_nClickX / GetPixelsPerSecond() + starttime;
|
|
|
|
float percent = clicktime / m_pWaveFile->GetRunningLength();
|
|
percent = min( 1.0f, percent );
|
|
percent = max( 0.0f, percent );
|
|
|
|
m_pEvent->AddRelativeTag( params.m_szInputText, percent );
|
|
|
|
g_pChoreoView->InvalidateLayout();
|
|
|
|
SetFocus( (HWND)getHandle() );
|
|
|
|
redraw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void PhonemeEditor::ClearEvent( void )
|
|
{
|
|
m_pEvent = NULL;
|
|
redraw();
|
|
}
|
|
|
|
void PhonemeEditor::TraverseWords( PEWORDITERFUNC pfn, float fparam )
|
|
{
|
|
for ( int i = 0; i < m_Tags.m_Words.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( ¶ms, 0, sizeof( params ) );
|
|
strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" );
|
|
strcpy( params.m_szName, "" );
|
|
|
|
params.m_nLeft = -1;
|
|
params.m_nTop = -1;
|
|
|
|
params.m_bPositionDialog = true;
|
|
params.m_bMultiplePhoneme = true;
|
|
|
|
if ( params.m_bPositionDialog )
|
|
{
|
|
RECT rcWord;
|
|
GetWordRect( cw, rcWord );
|
|
|
|
// Convert to screen coords
|
|
POINT pt;
|
|
pt.x = rcWord.left;
|
|
pt.y = rcWord.top;
|
|
|
|
ClientToScreen( (HWND)getHandle(), &pt );
|
|
|
|
params.m_nLeft = pt.x;
|
|
params.m_nTop = pt.y;
|
|
}
|
|
|
|
int iret = PhonemeProperties( ¶ms );
|
|
SetFocus( (HWND)getHandle() );
|
|
if ( !iret )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int phonemeCount = CSentence::CountWords( params.m_szName );
|
|
if ( phonemeCount <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float wordLength = cw->m_flEndTime - cw->m_flStartTime;
|
|
float timePerPhoneme = wordLength / (float)phonemeCount;
|
|
|
|
float currentTime = cw->m_flStartTime;
|
|
|
|
SetDirty( true );
|
|
|
|
PushUndo();
|
|
|
|
unsigned char *in;
|
|
char *out;
|
|
|
|
char phonemeName[ 128 ];
|
|
|
|
in = (unsigned char *)params.m_szName;
|
|
|
|
do
|
|
{
|
|
out = phonemeName;
|
|
|
|
while ( *in > 32 )
|
|
{
|
|
*out++ = *in++;
|
|
}
|
|
*out = 0;
|
|
|
|
CPhonemeTag phoneme;
|
|
|
|
phoneme.SetPhonemeCode( TextToPhoneme( phonemeName ) );
|
|
phoneme.SetTag( phonemeName );
|
|
|
|
phoneme.SetStartTime( currentTime );
|
|
phoneme.SetEndTime( currentTime + timePerPhoneme );
|
|
phoneme.m_bSelected = false;
|
|
|
|
cw->m_Phonemes.AddToTail( new CPhonemeTag( phoneme ) );
|
|
|
|
currentTime += timePerPhoneme;
|
|
|
|
if ( !*in )
|
|
break;
|
|
|
|
// Skip whitespace
|
|
in++;
|
|
|
|
} while ( 1 );
|
|
|
|
cw->m_Phonemes[ 0 ]->m_bSelected = true;
|
|
|
|
PushRedo();
|
|
|
|
// Add it
|
|
redraw();
|
|
}
|
|
|
|
void PhonemeEditor::SelectPhonemes( bool forward )
|
|
{
|
|
if ( GetMode() != MODE_PHONEMES )
|
|
return;
|
|
|
|
CountSelected();
|
|
|
|
if ( m_nSelectedPhonemeCount != 1 )
|
|
return;
|
|
|
|
CPhonemeTag *phoneme = GetSelectedPhoneme();
|
|
if ( !phoneme )
|
|
return;
|
|
|
|
// Figure out it's word and index
|
|
CWordTag *word = m_Tags.GetWordForPhoneme( phoneme );
|
|
if ( !word )
|
|
return;
|
|
|
|
int wordNum = IndexOfWord( word );
|
|
if ( wordNum == -1 )
|
|
return;
|
|
|
|
// Select remaining phonemes in current word
|
|
int i;
|
|
|
|
i = word->IndexOfPhoneme( phoneme );
|
|
if ( i == -1 )
|
|
return;
|
|
|
|
if ( forward )
|
|
{
|
|
// Start at next one
|
|
i++;
|
|
|
|
for ( ; i < word->m_Phonemes.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 );
|
|
}
|
|
|
|
|
|
|
|
|
|
|