rework transportque for shuttle speed codes, add rusage, cleanup
authorGood Guy <good1.2guy@gmail.com>
Wed, 6 Feb 2019 20:29:41 +0000 (13:29 -0700)
committerGood Guy <good1.2guy@gmail.com>
Wed, 6 Feb 2019 20:29:41 +0000 (13:29 -0700)
24 files changed:
cinelerra-5.1/cinelerra/commonrender.C
cinelerra-5.1/cinelerra/cwindow.C
cinelerra-5.1/cinelerra/main.C
cinelerra-5.1/cinelerra/mwindow.C
cinelerra-5.1/cinelerra/mwindow.h
cinelerra-5.1/cinelerra/mwindowgui.C
cinelerra-5.1/cinelerra/packagerenderer.C
cinelerra-5.1/cinelerra/playbackengine.C
cinelerra-5.1/cinelerra/playbackengine.h
cinelerra-5.1/cinelerra/playtransport.C
cinelerra-5.1/cinelerra/playtransport.h
cinelerra-5.1/cinelerra/renderengine.C
cinelerra-5.1/cinelerra/shuttle.C
cinelerra-5.1/cinelerra/shuttle.h
cinelerra-5.1/cinelerra/trackcanvas.C
cinelerra-5.1/cinelerra/transportque.C
cinelerra-5.1/cinelerra/transportque.h
cinelerra-5.1/cinelerra/transportque.inc
cinelerra-5.1/cinelerra/vwindowgui.C
cinelerra-5.1/cinelerra/zwindow.C
cinelerra-5.1/cinelerra/zwindow.h
cinelerra-5.1/guicast/bcwindowbase.C
cinelerra-5.1/guicast/keys.h
cinelerra-5.1/shuttlerc

index 9561737..bc96dd8 100644 (file)
@@ -266,7 +266,7 @@ int CommonRender::get_boundaries(int64_t &current_render_length)
                }
        }
 
-       if(renderengine->command->single_frame() && !renderengine->command->audio_toggle)
+       if(renderengine->command->single_frame() && !renderengine->command->toggle_audio)
                current_render_length = 1;
 
        if(current_render_length < 0) current_render_length = 0;
@@ -299,7 +299,7 @@ int CommonRender::advance_position(int64_t current_render_length)
                current_position += current_render_length;
 
        int64_t start_position, end_position;
-       int play_loop = renderengine->command->play_loop;
+       int play_loop = renderengine->command->loop_play;
        int loop_playback = renderengine->get_edl()->local_session->loop_playback;
        if( play_loop || !loop_playback ) {
                start_position = tounits(renderengine->command->start_position, 0);
index 3a65150..6f29f27 100644 (file)
@@ -372,7 +372,7 @@ int CWindowRemoteHandler::remote_process_key(RemoteControl *remote_control, int
                transport->change_position(position);
        }
        else
-               transport->handle_transport(next_command, 0, 0);
+               transport->handle_transport(next_command);
        return 1;
 }
 
index 4725787..f422092 100644 (file)
@@ -42,6 +42,8 @@
 #include <locale.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/time.h>
+#include <sys/resource.h>
 
 #ifdef LEAKER
 #define STRC(v) printf("==new %p from %p sz %jd\n", v, __builtin_return_address(0), n)
@@ -386,6 +388,14 @@ DISABLE_BUFFER
        time_t et; time(&et);
        long dt = et - st;
        printf("Session time: %ld:%02ld:%02ld\n", dt/3600, dt%3600/60, dt%60);
+       struct rusage ru;
+       getrusage(RUSAGE_SELF, &ru);
+       long usr_ms = ru.ru_utime.tv_sec*1000 + ru.ru_utime.tv_usec/1000;
+       long us = usr_ms/1000;  int ums = usr_ms%1000;
+       long sys_ms = ru.ru_stime.tv_sec*1000 + ru.ru_stime.tv_usec/1000;
+       long ss = sys_ms/1000;  int sms = sys_ms%1000;
+       printf("Cpu time: user: %ld:%02ld:%02ld.%03d sys: %ld:%02ld:%02ld.%03d\n",
+               us/3600, us%3600/60, us%60, ums, ss/3600, ss%3600/60, ss%60, sms);
        return 0;
 }
 
index f62df89..10eb2df 100644 (file)
@@ -1259,10 +1259,10 @@ void MWindow::update_mixer_tracks()
        zwindows_lock->unlock();
 }
 
-void MWindow::queue_mixers(EDL *edl, int command, int wait_tracking,
-               int use_inout, int update_refresh, int toggle_audio, int loop_play)
+void MWindow::handle_mixers(EDL *edl, int command, int wait_tracking,
+               int use_inout, int toggle_audio, int loop_play, float speed)
 {
-       zwindows_lock->lock("MWindow::queue_mixers");
+       zwindows_lock->lock("MWindow::handle_mixers");
        for( int vidx=0; vidx<zwindows.size(); ++vidx ) {
                ZWindow *zwindow = zwindows[vidx];
                if( zwindow->idx < 0 ) continue;
@@ -1291,8 +1291,8 @@ void MWindow::queue_mixers(EDL *edl, int command, int wait_tracking,
                                track->record = track->play = 0;
                }
                zwindow->change_source(mixer_edl);
-               zwindow->issue_command(command,
-                       wait_tracking, use_inout, update_refresh, toggle_audio, loop_play);
+               zwindow->handle_mixer(command, wait_tracking,
+                               use_inout, toggle_audio, loop_play, speed);
        }
        zwindows_lock->unlock();
 }
@@ -1300,7 +1300,7 @@ void MWindow::queue_mixers(EDL *edl, int command, int wait_tracking,
 void MWindow::refresh_mixers(int dir)
 {
        int command = dir >= 0 ? CURRENT_FRAME : LAST_FRAME;
-       queue_mixers(edl,command,0,0,1,0,0);
+       handle_mixers(edl, command, 0, 0, 0, 0, 0);
 }
 
 void MWindow::stop_mixers()
@@ -1308,7 +1308,7 @@ void MWindow::stop_mixers()
        for( int vidx=0; vidx<zwindows.size(); ++vidx ) {
                ZWindow *zwindow = zwindows[vidx];
                if( zwindow->idx < 0 ) continue;
-               zwindow->issue_command(STOP, 0, 0, 0, 0, 0);
+               zwindow->handle_mixer(STOP, 0, 0, 0, 0, 0);
        }
 }
 
@@ -1817,23 +1817,11 @@ void MWindow::stop_transport()
 
 void MWindow::undo_before(const char *description, void *creator)
 {
-       if( cwindow->playback_engine->is_playing_back ) {
-               undo_command = cwindow->playback_engine->command->command;
-               cwindow->playback_engine->que->send_command(STOP, CHANGE_NONE, 0, 0);
-               gui->unlock_window();
-               cwindow->playback_engine->interrupt_playback(1);
-               gui->lock_window(description);
-       }
        undo->update_undo_before(description, creator);
 }
 
 void MWindow::undo_after(const char *description, uint32_t load_flags, int changes_made)
 {
-       if( undo_command != COMMAND_NONE ) {
-               cwindow->playback_engine->que->send_command(undo_command, CHANGE_NONE, edl, 1, 0);
-               undo_command = COMMAND_NONE;
-       }
-
        undo->update_undo_after(description, load_flags, changes_made);
 }
 
@@ -3083,35 +3071,25 @@ void MWindow::sync_parameters(int change_type)
        if( in_destructor ) return;
 
 // Sync engines which are playing back
-       if(cwindow->playback_engine->is_playing_back)
-       {
-               if(change_type == CHANGE_PARAMS)
-               {
+       if( cwindow->playback_engine->is_playing_back ) {
+               if( change_type == CHANGE_PARAMS ) {
 // TODO: block keyframes until synchronization is done
                        cwindow->playback_engine->sync_parameters(edl);
                }
-               else
+               else {
 // Stop and restart
-               {
                        int command = cwindow->playback_engine->command->command;
-                       cwindow->playback_engine->que->send_command(STOP,
-                               CHANGE_NONE,
-                               0,
-                               0);
+                       cwindow->playback_engine->transport_stop();
 // Waiting for tracking to finish would make the restart position more
 // accurate but it can't lock the window to stop tracking for some reason.
 // Not waiting for tracking gives a faster response but restart position is
 // only as accurate as the last tracking update.
                        cwindow->playback_engine->interrupt_playback(0);
-                       cwindow->playback_engine->que->send_command(command,
-                                       change_type,
-                                       edl,
-                                       1,
-                                       0);
+                       cwindow->playback_engine->next_command->realtime = 1;
+                       cwindow->playback_engine->transport_command(command, change_type, edl, 0);
                }
        }
-       else
-       {
+       else {
                cwindow->refresh_frame(change_type);
        }
 }
index 0a9dfb9..ab2a524 100644 (file)
@@ -211,8 +211,8 @@ public:
        void undo_before(const char *description = "", void *creator = 0);
        void undo_after(const char *description, uint32_t load_flags, int changes_made = 1);
 
-       void queue_mixers(EDL *edl, int command, int wait_tracking,
-               int use_inout, int update_refresh, int toggle_audio, int loop_play);
+       void handle_mixers(EDL *edl, int command, int wait_tracking,
+               int use_inout, int toggle_audio, int loop_play, float speed);
        ZWindow *create_mixer(Indexable *indexable);
        void create_mixers();
        void refresh_mixers(int dir=1);
index 2b59a63..53677b4 100644 (file)
@@ -2348,7 +2348,7 @@ void MWindowGUI::stop_transport(const char *lock_msg)
 {
        if( !mbuttons->transport->is_stopped() ) {
                if( lock_msg ) unlock_window();
-               mbuttons->transport->handle_transport(STOP, 1, 0, 0);
+               mbuttons->transport->handle_transport(STOP, 1);
                if( lock_msg ) lock_window(lock_msg);
        }
 }
index 4318bec..59d429c 100644 (file)
@@ -129,7 +129,7 @@ int PackageRenderer::initialize(MWindow *mwindow,
        command->command = NORMAL_FWD;
        command->get_edl()->copy_all(edl);
        command->change_type = CHANGE_ALL;
-       command->set_playback_range(edl);
+       command->set_playback_range();
 
        default_asset->frame_rate = command->get_edl()->session->frame_rate;
        default_asset->sample_rate = command->get_edl()->session->sample_rate;
index 71cd391..aff5e57 100644 (file)
@@ -52,12 +52,21 @@ PlaybackEngine::PlaybackEngine(MWindow *mwindow, Canvas *output)
        tracking_active = 0;
        audio_cache = 0;
        video_cache = 0;
-       last_command = STOP;
+       command = new TransportCommand();
+       command->command = STOP;
+       next_command = new TransportCommand();
+       next_command->change_type = CHANGE_ALL;
+       curr_command = new TransportCommand();
+       stop_command = new TransportCommand();
+       stop_command->command = STOP;
        tracking_lock = new Mutex("PlaybackEngine::tracking_lock");
        renderengine_lock = new Mutex("PlaybackEngine::renderengine_lock");
        tracking_done = new Condition(1, "PlaybackEngine::tracking_done");
        pause_lock = new Condition(0, "PlaybackEngine::pause_lock");
        start_lock = new Condition(0, "PlaybackEngine::start_lock");
+        input_lock = new Condition(1, "PlaybackEngine::input_lock");
+        output_lock = new Condition(0, "PlaybackEngine::output_lock", 1);
+
        render_engine = 0;
        debug = 0;
 }
@@ -65,16 +74,11 @@ PlaybackEngine::PlaybackEngine(MWindow *mwindow, Canvas *output)
 PlaybackEngine::~PlaybackEngine()
 {
        done = 1;
-       que->send_command(STOP,
-               CHANGE_NONE,
-               0,
-               0);
+       transport_stop();
        interrupt_playback();
 
        Thread::join();
        delete preferences;
-       delete command;
-       delete que;
        delete_render_engine();
        delete audio_cache;
        delete video_cache;
@@ -83,16 +87,17 @@ PlaybackEngine::~PlaybackEngine()
        delete pause_lock;
        delete start_lock;
        delete renderengine_lock;
+       delete command;
+       delete next_command;
+       delete curr_command;
+       delete stop_command;
+        delete input_lock;
+        delete output_lock;
 }
 
 void PlaybackEngine::create_objects()
 {
        preferences = new Preferences;
-       command = new TransportCommand;
-       que = new TransportQue;
-// Set the first change to maximum
-       que->command.change_type = CHANGE_ALL;
-
        preferences->copy_from(mwindow->preferences);
 
        done = 0;
@@ -158,7 +163,7 @@ void PlaybackEngine::create_cache()
 
 void PlaybackEngine::perform_change()
 {
-       switch(command->change_type)
+       switch( command->change_type )
        {
                case CHANGE_ALL:
                        create_cache();
@@ -294,7 +299,7 @@ double PlaybackEngine::get_tracking_position()
 // Interpolate
                {
                        double loop_start, loop_end;
-                       int play_loop = command->play_loop ? 1 : 0;
+                       int play_loop = command->loop_play ? 1 : 0;
                        EDL *edl = command->get_edl();
                        int loop_playback = edl->local_session->loop_playback ? 1 : 0;
                        if( play_loop || !loop_playback ) {
@@ -360,22 +365,19 @@ void PlaybackEngine::run()
 
        while( !done ) {
 // Wait for current command to finish
-               que->output_lock->lock("PlaybackEngine::run");
+               output_lock->lock("PlaybackEngine::run");
                wait_render_engine();
 
 // Read the new command
-               que->input_lock->lock("PlaybackEngine::run");
-               if(done) return;
-
-               command->copy_from(&que->command);
-               que->command.reset();
-               que->input_lock->unlock();
+               input_lock->lock("PlaybackEngine::run");
+               if( done ) return;
+               command->copy_from(curr_command);
+               input_lock->unlock();
 //printf("PlaybackEngine::run 1 %d\n", command->command);
 
                switch( command->command ) {
 // Parameter change only
                case COMMAND_NONE:
-//                     command->command = last_command;
                        perform_change();
                        break;
 
@@ -391,7 +393,6 @@ void PlaybackEngine::run()
 
                case CURRENT_FRAME:
                case LAST_FRAME:
-                       last_command = command->command;
                        perform_change();
                        arm_render_engine();
 // Dispatch the command
@@ -402,7 +403,6 @@ void PlaybackEngine::run()
                case SINGLE_FRAME_REWIND:
 // fall through
                default:
-                       last_command = command->command;
                        is_playing_back = 1;
 
                        perform_change();
@@ -425,7 +425,7 @@ void PlaybackEngine::run()
 
 void PlaybackEngine::stop_playback(int wait)
 {
-       que->send_command(STOP, CHANGE_NONE, 0, 0);
+       transport_stop();
        interrupt_playback(wait);
        renderengine_lock->lock("PlaybackEngine::stop_playback");
        if(render_engine)
@@ -434,22 +434,20 @@ void PlaybackEngine::stop_playback(int wait)
 }
 
 
-void PlaybackEngine::issue_command(EDL *edl, int command, int wait_tracking,
-               int use_inout, int update_refresh, int toggle_audio, int loop_play)
+void PlaybackEngine::send_command(int command, EDL *edl, int wait_tracking, int use_inout)
 {
-//printf("PlaybackEngine::issue_command 1 %d\n", command);
+//printf("PlaybackEngine::send_command 1 %d\n", command);
 // Stop requires transferring the output buffer to a refresh buffer.
-       int do_stop = 0, resume = 0;
-       int prev_command = this->command->command;
-       int prev_single_frame = this->command->single_frame();
-       int prev_audio = this->command->audio_toggle ?
-                !prev_single_frame : prev_single_frame;
-       int cur_single_frame = TransportCommand::single_frame(command);
-       int cur_audio = toggle_audio ?
-                !cur_single_frame : cur_single_frame;
+       int do_stop = 0, do_resume = 0;
+       int curr_command = this->command->command;
+       int curr_single_frame = TransportCommand::single_frame(curr_command);
+       int curr_audio = this->command->toggle_audio ?
+               !curr_single_frame : curr_single_frame;
+       int single_frame = TransportCommand::single_frame(command);
+       int next_audio = next_command->toggle_audio ? !single_frame : single_frame;
 
 // Dispatch command
-       switch(command) {
+       switch( command ) {
        case FAST_REWIND:       // Commands that play back
        case NORMAL_REWIND:
        case SLOW_REWIND:
@@ -460,19 +458,18 @@ void PlaybackEngine::issue_command(EDL *edl, int command, int wait_tracking,
        case FAST_FWD:
        case CURRENT_FRAME:
        case LAST_FRAME:
-               if( !prev_single_frame &&
-                   prev_command == command &&
-                   cur_audio == prev_audio ) {
-// Same direction pressed twice and no change in audio state,  Stop
+               if( curr_command == command && !next_command->speed &&
+                   !curr_single_frame && curr_audio == next_audio ) {
+// Same direction pressed twice, not shuttle, and no change in audio state,  Stop
                        do_stop = 1;
                        break;
                }
 // Resume or change direction
-               switch( prev_command ) {
+               switch( curr_command ) {
                default:
-                       que->send_command(STOP, CHANGE_NONE, 0, 0);
+                       transport_stop();
                        interrupt_playback(wait_tracking);
-                       resume = 1;
+                       do_resume = 1;
 // fall through
                case STOP:
                case COMMAND_NONE:
@@ -480,10 +477,10 @@ void PlaybackEngine::issue_command(EDL *edl, int command, int wait_tracking,
                case SINGLE_FRAME_REWIND:
                case CURRENT_FRAME:
                case LAST_FRAME:
+                       next_command->realtime = 1;
+                       next_command->resume = do_resume;
 // Start from scratch
-                       que->send_command(command, CHANGE_NONE, edl,
-                               1, resume, use_inout, toggle_audio, loop_play,
-                               mwindow->preferences->forward_render_displacement);
+                       transport_command(command, CHANGE_NONE, edl, use_inout);
                        break;
                }
                break;
@@ -497,14 +494,52 @@ void PlaybackEngine::issue_command(EDL *edl, int command, int wait_tracking,
        }
 
        if( do_stop ) {
-               que->send_command(STOP, CHANGE_NONE, 0, 0);
+               transport_stop();
                interrupt_playback(wait_tracking);
        }
 }
 
+int PlaybackEngine::transport_stop()
+{
+       input_lock->lock("PlaybackEngine::transport_stop 1");
+       curr_command->copy_from(stop_command);
+       input_lock->unlock();
+       output_lock->unlock();
+       return 0;
+}
+
+int PlaybackEngine::transport_command(int command, int change_type, EDL *new_edl, int use_inout)
+{
+       input_lock->lock("PlaybackEngine::transport_command 1");
+       next_command->command = command;
+       next_command->change_type |= change_type;
+       if( new_edl ) {
+// Just change the EDL if the change requires it because renderengine
+// structures won't point to the new EDL otherwise and because copying the
+// EDL for every cursor movement is slow.
+               switch( change_type ) {
+               case CHANGE_EDL:
+               case CHANGE_ALL:
+                       next_command->get_edl()->copy_all(new_edl);
+                       break;
+               case CHANGE_PARAMS:
+                       next_command->get_edl()->synchronize_params(new_edl);
+                       break;
+               }
+               next_command->set_playback_range(new_edl, use_inout,
+                               preferences->forward_render_displacement);
+       }
+       curr_command->copy_from(next_command);
+       next_command->reset();
+       input_lock->unlock();
+       output_lock->unlock();
+       return 0;
+}
+
 void PlaybackEngine::refresh_frame(int change_type, EDL *edl, int dir)
 {
-       que->send_command(dir >= 0 ? CURRENT_FRAME : LAST_FRAME,
-               change_type, edl, 1);
+       int command = dir >= 0 ? CURRENT_FRAME : LAST_FRAME;
+       next_command->realtime = 1;
+       transport_command(command, change_type, edl);
 }
 
index 936657a..9febc05 100644 (file)
@@ -38,7 +38,7 @@
 #include "renderengine.inc"
 #include "thread.h"
 #include "bctimer.h"
-#include "transportque.inc"
+#include "transportque.h"
 
 class PlaybackEngine : public Thread
 {
@@ -81,9 +81,8 @@ public:
        ChannelDB* get_channeldb();
 
        void run();
+       void send_command(int command, EDL *edl, int wait_tracking, int use_inout);
        void stop_playback(int wait);
-       void issue_command(EDL *edl, int command, int wait_tracking,
-               int use_inout, int update_refresh, int toggle_audio, int loop_play);
        void refresh_frame(int change_type, EDL *edl, int dir=1);
 
 // Maintain caches through console changes
@@ -110,18 +109,20 @@ public:
        Canvas *output;
 // Copy of main preferences
        Preferences *preferences;
-// Next command
-       TransportQue *que;
-// Currently executing command
-       TransportCommand *command;
-// Last command which affected transport
-       int last_command;
-       int done;
-       int do_cwindow;
+
+       int transport_stop();
+       int transport_command(int command, int change_type=CHANGE_NONE,
+                       EDL *new_edl=0, int use_inout=0);
+
+       Condition *input_lock, *output_lock;
+// active command
+       TransportCommand *command, *stop_command;
+       TransportCommand *curr_command, *next_command;
+
 // Render engine
        RenderEngine *render_engine;
 
-// Used by label commands to get current position
+       int done;
        int is_playing_back;
 
 // General purpose debugging register
index 0165de6..94b70dc 100644 (file)
@@ -28,6 +28,7 @@
 #include "playbackengine.h"
 #include "playtransport.h"
 #include "preferences.h"
+#include "shuttle.h"
 #include "theme.h"
 #include "transportque.h"
 #include "vframe.h"
@@ -200,19 +201,39 @@ int PlayTransport::do_keypress(int key)
                goto_end();
                return result;
        }
-// as in play_command
+
        int ctrl_key = subwindow->ctrl_down() ? 1 : 0;
        int shft_key = subwindow->shift_down() ? 1 : 0;
        int alt_key = subwindow->alt_down() ? 1 : 0;
        int use_inout = ctrl_key;
        int toggle_audio = shft_key & ~ctrl_key;
        int loop_play = shft_key & ctrl_key;
-       int command = -1, prev_command = engine->command->command;
-       using_inout = use_inout;
+       float speed = 0;
+       int command = -1;
+       int curr_command = engine->command->command;
        subwindow->unlock_window();
 
        result = 0;
-       switch( key ) {
+
+       if( key >= SKEY_MIN && key <= SKEY_MAX ) {
+               speed = SHUTTLE_MAX_SPEED *
+                        (key - (SKEY_MAX + SKEY_MIN)/2.f) /
+                               ((SKEY_MAX - SKEY_MIN) / 2.f);
+               if( speed < 0 ) {
+                       speed = -speed;
+                       command = speed < 1 ? SLOW_REWIND :
+                               speed > 1 ? FAST_REWIND : NORMAL_REWIND;
+               }
+               else if( speed > 0 ) {
+                       command = speed < 1 ? SLOW_FWD :
+                               speed > 1 ? FAST_FWD : NORMAL_FWD;
+               }
+               else
+                       command = STOP;
+               use_inout = 0;
+               loop_play = 0;
+       }
+       else switch( key ) {
        case KPINS:     command = STOP;                 break;
        case KPPLUS:    command = FAST_REWIND;          break;
        case KP6:       command = NORMAL_REWIND;        break;
@@ -222,8 +243,9 @@ int PlayTransport::do_keypress(int key)
        case KP2:       command = SLOW_FWD;             break;
        case KP3:       command = NORMAL_FWD;           break;
        case KPENTER:   command = FAST_FWD;             break;
+
        case ' ':
-               switch( prev_command ) {
+               switch( curr_command ) {
                case COMMAND_NONE:
                case CURRENT_FRAME:
                case LAST_FRAME:
@@ -265,7 +287,7 @@ int PlayTransport::do_keypress(int key)
                break;
        }
        if( command >= 0 ) {
-               handle_transport(command, 0, use_inout, 1, toggle_audio, loop_play);
+               handle_transport(command, 0, use_inout, toggle_audio, loop_play, speed);
                result = 1;
        }
 
@@ -276,24 +298,30 @@ int PlayTransport::do_keypress(int key)
 
 void PlayTransport::goto_start()
 {
-       handle_transport(REWIND, 1, 0);
+       handle_transport(REWIND, 1);
 }
 
 void PlayTransport::goto_end()
 {
-       handle_transport(GOTO_END, 1, 0);
+       handle_transport(GOTO_END, 1);
 }
 
 
 
-void PlayTransport::handle_transport(int command, int wait_tracking, int use_inout,
-               int update_refresh, int toggle_audio, int loop_play)
+void PlayTransport::handle_transport(int command, int wait_tracking,
+               int use_inout, int toggle_audio, int loop_play, float speed)
 {
        EDL *edl = get_edl();
        if( !edl ) return;
+       using_inout = use_inout;
+
        if( !is_vwindow() )
-               mwindow->queue_mixers(edl, command, wait_tracking, use_inout, update_refresh, toggle_audio, 0);
-       engine->issue_command(edl, command, wait_tracking, use_inout, update_refresh, toggle_audio, loop_play);
+               mwindow->handle_mixers(edl, command, wait_tracking,
+                               use_inout, toggle_audio, 0, speed);
+       engine->next_command->toggle_audio = toggle_audio;
+       engine->next_command->loop_play = loop_play;
+       engine->next_command->speed = speed;
+       engine->send_command(command, edl, wait_tracking, use_inout);
 }
 
 EDL* PlayTransport::get_edl()
@@ -344,7 +372,7 @@ int PTransportButton::play_command(const char *lock_msg, int command)
        int toggle_audio = shft_key & ~ctrl_key;
        int loop_play = shft_key & ctrl_key;
        unlock_window();
-       transport->handle_transport(command, 0, use_inout, 0, toggle_audio, loop_play);
+       transport->handle_transport(command, 0, use_inout, toggle_audio, loop_play, 0);
        lock_window(lock_msg);
        return 1;
 }
@@ -462,7 +490,7 @@ StopButton::StopButton(MWindow *mwindow, PlayTransport *transport, int x, int y)
 int StopButton::handle_event()
 {
        unlock_window();
-       transport->handle_transport(STOP, 0, 0);
+       transport->handle_transport(STOP);
        lock_window("StopButton::handle_event");
        return 1;
 }
@@ -472,26 +500,27 @@ int StopButton::handle_event()
 void PlayTransport::change_position(double position)
 {
        if( !get_edl() ) return;
-       int prev_command = engine->command->command;
+       int command = engine->command->command;
 // stop transport
-       if( prev_command != STOP && prev_command != COMMAND_NONE &&
-           prev_command != SINGLE_FRAME_FWD && prev_command != SINGLE_FRAME_REWIND ) {
-               engine->que->send_command(STOP, CHANGE_NONE, 0, 0);
+       if( command != STOP && command != COMMAND_NONE &&
+           command != SINGLE_FRAME_FWD && command != SINGLE_FRAME_REWIND ) {
+               engine->transport_stop();
                engine->interrupt_playback(0);
        }
        mwindow->gui->lock_window("PlayTransport::change_position");
        mwindow->goto_position(position);
        mwindow->gui->unlock_window();
 // restart command
-       switch(prev_command) {
+       switch( command ) {
        case FAST_REWIND:
        case NORMAL_REWIND:
        case SLOW_REWIND:
        case SLOW_FWD:
        case NORMAL_FWD:
        case FAST_FWD:
-               engine->que->send_command(prev_command, CHANGE_NONE,
-                               get_edl(), 1, 1, using_inout, 0);
+               engine->next_command->realtime = 1;
+               engine->next_command->resume = 1;
+               engine->transport_command(command, CHANGE_NONE, get_edl(), using_inout);
        }
 }
 
index 0a60e22..330a958 100644 (file)
@@ -50,7 +50,7 @@ public:
        int flip_vertical(int vertical, int &x, int &y);
        int keypress_event();
        int do_keypress(int key);
-// Abstract TransportQue::send_command.
+// Abstract transport send_command.
 // wait_tracking - causes stop to wail until the final tracking position
 // is updated before returning
 // use_inout - causes the in/out points to determine the beginning and end
@@ -59,9 +59,9 @@ public:
 // the refresh frame.
 // toggle_audio - reverses audio playback enable in RenderEngine::get_duty
 // loop_play - sets play_loop and plays btwn start/end position (in a loop)
+// speed - play speed for SLOW/FAST playback, zero defaults to slow=.5,fast=2.
        void handle_transport(int command, int wait_tracking=0,
-               int use_inout=0, int update_refresh=1, int toggle_audio=0,
-               int loop_play=0);
+               int use_inout=0, int toggle_audio=0, int loop_play=0, float speed=0);
        int pause_transport();
        int reset_transport();
        int get_w();
index 721e3be..3c178bb 100644 (file)
@@ -195,7 +195,7 @@ void RenderEngine::get_duty()
            get_edl()->session->audio_channels )
        {
                do_audio = !command->single_frame() ? 1 : 0;
-               if( command->audio_toggle ) do_audio = !do_audio;
+               if( command->toggle_audio ) do_audio = !do_audio;
        }
 
 //printf("RenderEngine::get_duty %d\n", __LINE__);
@@ -538,7 +538,8 @@ void RenderEngine::run()
                                playback_engine->tracking_position = position >= 0 ? position : 0;
                        }
 
-                       if(!interrupted) playback_engine->command->command = STOP;
+                       if( !interrupted )
+                               playback_engine->command->command = STOP;
                        playback_engine->stop_tracking();
 
                }
index d32456f..65e2f7f 100644 (file)
@@ -6,6 +6,7 @@
 #include "cstrdup.h"
 #include "file.h"
 #include "guicast.h"
+#include "keys.h"
 #include "linklist.h"
 #include "loadfile.h"
 #include "mainmenu.h"
@@ -41,6 +42,7 @@ static Time milliTimeClock()
 }
 
 KeySymMapping KeySymMapping::key_sym_mapping[] = {
+// button keycodes
        { "XK_Button_1", XK_Button_1 },
        { "XK_Button_2", XK_Button_2 },
        { "XK_Button_3", XK_Button_3 },
@@ -52,6 +54,22 @@ KeySymMapping KeySymMapping::key_sym_mapping[] = {
 
 KeySym KeySymMapping::to_keysym(const char *str)
 {
+       if( !strncmp("FWD_",str, 4) ) {
+               float speed = atof(str+4) / SHUTTLE_MAX_SPEED;
+               if( speed > SHUTTLE_MAX_SPEED ) return 0;
+               int key_code = (SKEY_MAX+SKEY_MIN)/2. +
+                       (SKEY_MAX-SKEY_MIN)/2. * speed;
+               if( key_code > SKEY_MAX ) key_code = SKEY_MAX;
+               return key_code;
+       }
+       if( !strncmp("REV_",str, 4) ) {
+               float speed = atof(str+4) / SHUTTLE_MAX_SPEED;
+               if( speed > SHUTTLE_MAX_SPEED ) return 0;
+               int key_code = (SKEY_MAX+SKEY_MIN)/2. -
+                       (SKEY_MAX-SKEY_MIN)/2. * speed;
+               if( key_code < SKEY_MIN ) key_code = SKEY_MIN;
+               return key_code;
+       }
        for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->str; ++ksp )
                if( !strcmp(str, ksp->str) ) return ksp->sym;
        return 0;
@@ -61,6 +79,19 @@ const char *KeySymMapping::to_string(KeySym ks)
 {
        for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->sym; ++ksp )
                if( ksp->sym == ks ) return ksp->str;
+       if( ks >= SKEY_MIN && ks <= SKEY_MAX ) {
+               double speed = SHUTTLE_MAX_SPEED *
+                       (ks-(SKEY_MAX+SKEY_MIN)/2.) / ((SKEY_MAX-SKEY_MIN)/2.);
+               static char text[BCSTRLEN];
+               sprintf(text, "%s_%0.3f", speed>=0 ? "FWD" : "REV", fabs(speed));
+               char *bp = strchr(text,'.');
+               if( bp ) {
+                       char *cp = bp+strlen(bp);
+                       while( --cp>bp && *cp=='0' ) *cp=0;
+                       if( cp == bp ) *cp = 0;
+               }
+               return text;
+       }
        return 0;
 }
 
@@ -400,6 +431,7 @@ int Shuttle::send_button(unsigned int button, int press)
        memset(b, 0, sizeof(*b));
        b->type = press ? ButtonPress : ButtonRelease;
        b->time = milliTimeClock();
+       b->send_event = 1;
        b->display = wdw->top_level->display;
        b->root = wdw->top_level->rootwin;
        b->window = win;
@@ -413,15 +445,21 @@ int Shuttle::send_button(unsigned int button, int press)
        wdw->top_level->put_event((XEvent *) b);
        return 0;
 }
-int Shuttle::send_key(KeySym keysym, int press)
+int Shuttle::send_keycode(unsigned int keycode, int press, int send)
 {
-       KeyCode keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
-       if( debug )
-               printf("key: %04x %d\n", (unsigned)keycode, press);
+       if( debug ) {
+               const char *cp = !send ? 0 :
+                       KeySymMapping::to_string(keycode);
+               if( cp )
+                       printf("key: %s %d\n", cp, press);
+               else
+                       printf("key: %04x %d\n", keycode, press);
+       }
        XKeyEvent *k = new XKeyEvent();
        memset(k, 0, sizeof(*k));
        k->type = press ? KeyPress : KeyRelease;
        k->time = milliTimeClock();
+       k->send_event = send;
        k->display = wdw->top_level->display;
        k->root = wdw->top_level->rootwin;
        k->window = win;
@@ -440,7 +478,9 @@ int Shuttle::send_keysym(KeySym keysym, int press)
 {
        return keysym >= XK_Button_1 && keysym <= XK_Scroll_Down ?
                send_button((unsigned int)keysym - XK_Button_0, press) :
-               send_key(keysym, press ? True : False);
+               send_keycode((unsigned int)keysym, press, 1);
+//     unsigned int keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
+//     return send_keycode(keycode, press, 0);
 }
 
 
@@ -481,7 +521,7 @@ void Shuttle::key(unsigned short code, unsigned int value)
 }
 
 
-void Shuttle:: shuttle(int value)
+void Shuttle::shuttle(int value)
 {
        if( value < S_7 || value > S7 ) {
                fprintf(stderr, "shuttle(%d) out of range\n", value);
@@ -595,7 +635,7 @@ int Shuttle::get_focused_window_translation()
        Display *dpy = gui->display;
        Window focus = 0;
        int ret = 0, revert = 0;
-       char win_title[BCTEXTLEN];
+       char win_title[BCTEXTLEN];  win_title[0] = 0;
        gui->lock_window("Shuttle::get_focused_window_translation");
        XGetInputFocus(dpy, &focus, &revert);
        if( last_focused != focus ) {
@@ -638,7 +678,8 @@ int Shuttle::get_focused_window_translation()
                        wdw = mwindow->cwindow->gui->canvas->get_canvas();
                cin = FOCUS_CWINDOW;
        }
-       else if( (wdw=mwindow->gui->mainmenu->load_file->thread->window) != 0 &&
+       else if( mwindow->gui->mainmenu->load_file->thread->running() &&
+                (wdw=mwindow->gui->mainmenu->load_file->thread->window) != 0 &&
                 wdw->win == focus )
                cin = FOCUS_LOAD;
        else {
index e3bd2c9..ffe805c 100644 (file)
@@ -17,6 +17,8 @@
 // delay in ms before processing each XTest event
 // CurrentTime means no delay
 #define DELAY CurrentTime
+// playback max speed -64x .. 64x
+#define SHUTTLE_MAX_SPEED 64.
 
 // protocol for events from the shuttlepro HUD device
 //
@@ -194,7 +196,7 @@ public:
        ~Shuttle();
 
        int send_button(unsigned int button, int press);
-       int send_key(KeySym keysym, int press);
+       int send_keycode(unsigned int keycode, int press, int send);
        int send_keysym(KeySym keysym, int press);
        void send_stroke_sequence(int kjs, int index);
        void key(unsigned short code, unsigned int value);
index dd48141..ab93d62 100644 (file)
@@ -3889,7 +3889,7 @@ int TrackCanvas::render_handle_frame(EDL *edl, int64_t pos, int mode)
                PlaybackEngine *playback_engine = mwindow->cwindow->playback_engine;
                if( playback_engine->is_playing_back )
                        playback_engine->stop_playback(1);
-               mwindow->cwindow->playback_engine->refresh_frame(CHANGE_EDL, edl, 0);
+               mwindow->cwindow->playback_engine->refresh_frame(CHANGE_NONE, edl, 0);
                break; }
        }
        return result;
index 35d6f1f..a4faa9b 100644 (file)
@@ -25,6 +25,7 @@
 #include "edl.h"
 #include "edlsession.h"
 #include "localsession.h"
+#include "playbackengine.h"
 #include "tracks.h"
 #include "transportque.h"
 
@@ -52,9 +53,9 @@ void TransportCommand::reset()
        infinite = 0;
        realtime = 0;
        resume = 0;
-       audio_toggle = 0;
-       play_loop = 0;
-       displacement = 0;
+       toggle_audio = 0;
+       loop_play = 0;
+       speed = 0;
 // Don't reset the change type for commands which don't perform the change
        if(command != STOP) change_type = 0;
        command = COMMAND_NONE;
@@ -88,9 +89,10 @@ void TransportCommand::copy_from(TransportCommand *command)
        this->playbackstart = command->playbackstart;
        this->realtime = command->realtime;
        this->resume = command->resume;
-       this->audio_toggle = command->audio_toggle;
-       this->play_loop = command->play_loop;
+       this->toggle_audio = command->toggle_audio;
+       this->loop_play = command->loop_play;
        this->displacement = command->displacement;
+       this->speed = command->speed;
 }
 
 TransportCommand& TransportCommand::operator=(TransportCommand &command)
@@ -104,11 +106,6 @@ int TransportCommand::single_frame(int command)
        return (command == SINGLE_FRAME_FWD || command == SINGLE_FRAME_REWIND ||
                command == CURRENT_FRAME || command == LAST_FRAME);
 }
-int TransportCommand::single_frame()
-{
-       return single_frame(command);
-}
-
 
 int TransportCommand::get_direction(int command)
 {
@@ -132,17 +129,13 @@ int TransportCommand::get_direction(int command)
        }
        return PLAY_FORWARD;
 }
-int TransportCommand::get_direction()
-{
-       return get_direction(command);
-}
 
-float TransportCommand::get_speed(int command)
+float TransportCommand::get_speed(int command, float speed)
 {
        switch(command) {
        case SLOW_FWD:
        case SLOW_REWIND:
-               return 0.5;
+               return speed ? speed : 0.5;
 
        case NORMAL_FWD:
        case NORMAL_REWIND:
@@ -154,27 +147,19 @@ float TransportCommand::get_speed(int command)
 
        case FAST_FWD:
        case FAST_REWIND:
-               return 2.;
+               return speed ? speed : 2.;
        }
 
        return 0.;
 }
-float TransportCommand::get_speed()
-{
-       return get_speed(command);
-}
 
 // Assume starting without pause
-void TransportCommand::set_playback_range(EDL *edl,
-       int use_inout, int toggle_audio, int loop_play, int use_displacement)
+void TransportCommand::set_playback_range(EDL *edl, int use_inout, int do_displacement)
 {
-       if(!edl) edl = this->edl;
+       if( !edl ) edl = this->edl;
        double length = edl->tracks->total_playable_length();
        double frame_period = 1.0 / edl->session->frame_rate;
-
        displacement = 0;
-       audio_toggle = toggle_audio;
-       play_loop = loop_play;
 
        start_position = use_inout && edl->local_session->inpoint_valid() ?
                edl->local_session->get_inpoint() :
@@ -221,15 +206,20 @@ void TransportCommand::set_playback_range(EDL *edl,
                        break;
                }
 
-               if( use_displacement && (
-                   (command != CURRENT_FRAME && get_direction() == PLAY_FORWARD ) ||
-                   (command != LAST_FRAME    && get_direction() == PLAY_REVERSE ) ) ) {
-                       start_position += frame_period;
-                       end_position += frame_period;
-                       displacement = 1;
+               if( realtime && do_displacement ) {
+                       if( (command != CURRENT_FRAME && get_direction() == PLAY_FORWARD ) ||
+                           (command != LAST_FRAME    && get_direction() == PLAY_REVERSE ) ) {
+                               start_position += frame_period;
+                               end_position += frame_period;
+                               displacement = 1;
+                       }
                }
        }
 
+//     if( start_position < 0 )
+//             start_position = 0;
+//     if( end_position > length )
+//             end_position = length;
        if( end_position < start_position )
                end_position = start_position;
 
@@ -275,60 +265,3 @@ void TransportCommand::playback_range_1frame()
 }
 
 
-TransportQue::TransportQue()
-{
-       input_lock = new Condition(1, "TransportQue::input_lock");
-       output_lock = new Condition(0, "TransportQue::output_lock", 1);
-}
-
-TransportQue::~TransportQue()
-{
-       delete input_lock;
-       delete output_lock;
-}
-
-int TransportQue::send_command(int command, int change_type,
-               EDL *new_edl, int realtime, int resume, int use_inout,
-               int toggle_audio, int loop_play, int use_displacement)
-{
-       input_lock->lock("TransportQue::send_command 1");
-       this->command.command = command;
-// Mutually exclusive operation
-       this->command.change_type |= change_type;
-       this->command.realtime = realtime;
-       this->command.resume = resume;
-
-       if(new_edl)
-       {
-// Just change the EDL if the change requires it because renderengine
-// structures won't point to the new EDL otherwise and because copying the
-// EDL for every cursor movement is slow.
-               if(change_type == CHANGE_EDL ||
-                       (uint32_t)change_type == CHANGE_ALL)
-               {
-// Copy EDL
-                       this->command.get_edl()->copy_all(new_edl);
-               }
-               else
-               if(change_type == CHANGE_PARAMS)
-               {
-                       this->command.get_edl()->synchronize_params(new_edl);
-               }
-
-// Set playback range
-               this->command.set_playback_range(new_edl, use_inout,
-                               toggle_audio, loop_play, use_displacement);
-       }
-
-       input_lock->unlock();
-
-       output_lock->unlock();
-       return 0;
-}
-
-void TransportQue::update_change_type(int change_type)
-{
-       input_lock->lock("TransportQue::update_change_type");
-       this->command.change_type |= change_type;
-       input_lock->unlock();
-}
index 1f5f9c6..351cf68 100644 (file)
  *
  */
 
-#ifndef TRANSPORTQUE_H
-#define TRANSPORTQUE_H
+#ifndef __TRANSPORTQUE_H__
+#define __TRANSPORTQUE_H__
 
 #include "canvas.inc"
 #include "condition.inc"
 #include "edl.inc"
-#include "preferences.inc"
+#include "playbackengine.inc"
 #include "transportque.inc"
 
 class TransportCommand
@@ -35,18 +35,13 @@ public:
        ~TransportCommand();
 
        void reset();
-       static int single_frame(int command);
-       int single_frame();
-// Get the direction based on the command
-       static int get_direction(int command);
-       int get_direction();
-       static float get_speed(int command);
-       float get_speed();
        void copy_from(TransportCommand *command);
        TransportCommand& operator=(TransportCommand &command);
 // Get the range to play back from the EDL
-       void set_playback_range(EDL *edl=0, int use_inout=0,
-               int toggle_audio=0, int loop_play=0, int use_displacement=0);
+       void set_playback_range(EDL *edl, int use_inout, int do_displacement);
+       static int single_frame(int command);
+       static int get_direction(int command);
+       static float get_speed(int command, float speed=0);
 
 // Adjust playback range with in/out points for rendering
        void playback_range_adjust_inout();
@@ -60,46 +55,34 @@ public:
        void delete_edl();
        void new_edl();
 
+       PlaybackEngine *engine;
        int command;
        int change_type;
-// lowest numbered second in playback range
-       double start_position;
-// highest numbered second in playback range
-       double end_position;
+// playback range
+       double start_position, end_position;
        int infinite;
 // Position used when starting playback
        double playbackstart;
-// start position at this=0/next=1 frame
+// start at this=0/next=1 frame
        int displacement;
 // Send output to device
        int realtime;
 // Use persistant starting point
        int resume;
 // reverse audio duty
-       int audio_toggle;
+       int toggle_audio;
 // playback loop
-       int play_loop;
+       int loop_play;
+// SLOW,FAST play speed
+       float speed;
+
+       int single_frame() { return single_frame(command); }
+       int get_direction() { return get_direction(command); }
+       float get_speed() { return get_speed(command, speed); }
+       void set_playback_range() { set_playback_range(0, 0, 0); }
 private:
 // Copied to render engines
        EDL *edl;
 };
 
-class TransportQue
-{
-public:
-       TransportQue();
-       ~TransportQue();
-
-       int send_command(int command,
-// The change type is ORed to accumulate changes.
-               int change_type, EDL *new_edl, int realtime,
-// Persistent starting point
-               int resume = 0, int use_inout = 0, int toggle_audio = 0,
-               int loop_play = 0, int use_displacement = 0);
-       void update_change_type(int change_type);
-
-       TransportCommand command;
-       Condition *input_lock, *output_lock;
-};
-
 #endif
index 2660361..7b1c206 100644 (file)
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef TRANSPORTQUE_INC
-#define TRANSPORTQUE_INC
+#ifndef __TRANSPORTQUE_INC__
+#define __TRANSPORTQUE_INC__
 
 // Directions
 #define PLAY_FORWARD 0
@@ -56,6 +56,5 @@
 #define CHANGE_NONE                  0x0
 
 class TransportCommand;
-class TransportQue;
 
 #endif
index 985d484..52ef387 100644 (file)
@@ -489,7 +489,7 @@ void VWindowGUI::stop_transport()
 {
        if( !transport->is_stopped() ) {
                unlock_window();
-               transport->handle_transport(STOP, 1, 0, 0);
+               transport->handle_transport(STOP, 1);
                lock_window("VWindowGUI::panel_stop_transport");
        }
 }
index 3c34084..6508b9e 100644 (file)
@@ -231,11 +231,14 @@ void ZWindow::stop_playback(int wait)
        zgui->playback_engine->stop_playback(wait);
 }
 
-void ZWindow::issue_command(int command, int wait_tracking,
-               int use_inout, int update_refresh, int toggle_audio, int loop_play)
+void ZWindow::handle_mixer(int command, int wait_tracking,
+               int use_inout, int toggle_audio, int loop_play, float speed)
 {
-       zgui->playback_engine->issue_command(edl, command,
-                       wait_tracking, use_inout, update_refresh, toggle_audio, loop_play);
+       PlaybackEngine *engine = zgui->playback_engine;
+       engine->next_command->toggle_audio = toggle_audio;
+       engine->next_command->loop_play = loop_play;
+       engine->next_command->speed = speed;
+       engine->send_command(command, edl, wait_tracking, use_inout);
 }
 
 void ZWindow::update_mixer_ids()
index 7f46fff..3019d43 100644 (file)
@@ -71,8 +71,8 @@ public:
        void handle_close_event(int result);
        void change_source(EDL *edl);
        void stop_playback(int wait);
-       void issue_command(int command, int wait_tracking, int use_inout,
-                       int update_refresh, int toggle_audio, int loop_play);
+       void handle_mixer(int command, int wait_tracking,
+               int use_inout, int toggle_audio, int loop_play, float speed);
        void update_mixer_ids();
        void set_title(const char *tp);
        void reposition(int x, int y, int w, int h);
index 0194a75..3db866f 100644 (file)
@@ -1192,8 +1192,11 @@ locking_message = event->xclient.message_type;
                }
                else {
 #endif
-
-               switch(keysym) {
+// shuttle speed codes
+               if( keysym >= SKEY_MIN && keysym <= SKEY_MAX ) {
+                       key_pressed = keysym;
+               }
+               else switch( keysym ) {
 // block out extra keys
                case XK_Alt_L:
                case XK_Alt_R:
index e580a84..a79230b 100644 (file)
 #define RETURN              13
 #define NEWLINE             13
 
+#define SKEY_MIN 0x3000000 // Shuttle speed codes
+#define SKEY_MAX 0x3010000
+
 #endif
index a41ba3c..71390c2 100644 (file)
  K3 "c"                # Pro Only - Copy
  K4 "v"                # Pro Only - Paste
 
- S-3 XK_KP_Add  # Fast reverse
- S-2 XK_KP_6    # Play reverse
- S-1 XK_KP_5    # Slow reverse
- S0 XK_KP_0     # Stop
- S1 XK_KP_2     # Slow forward
- S2 XK_KP_3     # Play forward
- S3 XK_KP_Enter # Fast forward
+ S-7 REV_16    # Next 7 are reverse keys
+ S-6 REV_8     #  the number on the end represents speed
+ S-5 REV_4     #  number can be decimal up to 64
+ S-4 REV_2     #  2 means 2x or double speed
+ S-3 REV_1
+ S-2 REV_0.5   #  note 0.25 represents 1/4 speed
+ S-1 REV_0.25
+ S0  XK_KP_0   # Stop
+ S1  FWD_0.25  # Next 7 are forward keys
+ S2  FWD_0.5
+ S3  FWD_1
+ S4  FWD_2
+ S5  FWD_4
+ S6  FWD_8
+ S7  FWD_16
  
  JL XK_KP_4     # Frame reverse
  JR XK_KP_1     # Frame forward