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