From 3bf30d220f7855b995b887dc10812ae3780e6805 Mon Sep 17 00:00:00 2001 From: Good Guy Date: Wed, 6 Feb 2019 13:29:41 -0700 Subject: [PATCH] rework transportque for shuttle speed codes, add rusage, cleanup --- cinelerra-5.1/cinelerra/commonrender.C | 4 +- cinelerra-5.1/cinelerra/cwindow.C | 2 +- cinelerra-5.1/cinelerra/main.C | 10 ++ cinelerra-5.1/cinelerra/mwindow.C | 50 +++----- cinelerra-5.1/cinelerra/mwindow.h | 4 +- cinelerra-5.1/cinelerra/mwindowgui.C | 2 +- cinelerra-5.1/cinelerra/packagerenderer.C | 2 +- cinelerra-5.1/cinelerra/playbackengine.C | 135 ++++++++++++++-------- cinelerra-5.1/cinelerra/playbackengine.h | 25 ++-- cinelerra-5.1/cinelerra/playtransport.C | 71 ++++++++---- cinelerra-5.1/cinelerra/playtransport.h | 6 +- cinelerra-5.1/cinelerra/renderengine.C | 5 +- cinelerra-5.1/cinelerra/shuttle.C | 57 +++++++-- cinelerra-5.1/cinelerra/shuttle.h | 4 +- cinelerra-5.1/cinelerra/trackcanvas.C | 2 +- cinelerra-5.1/cinelerra/transportque.C | 113 ++++-------------- cinelerra-5.1/cinelerra/transportque.h | 57 ++++----- cinelerra-5.1/cinelerra/transportque.inc | 5 +- cinelerra-5.1/cinelerra/vwindowgui.C | 2 +- cinelerra-5.1/cinelerra/zwindow.C | 11 +- cinelerra-5.1/cinelerra/zwindow.h | 4 +- cinelerra-5.1/guicast/bcwindowbase.C | 7 +- cinelerra-5.1/guicast/keys.h | 3 + cinelerra-5.1/shuttlerc | 22 ++-- 24 files changed, 316 insertions(+), 287 deletions(-) diff --git a/cinelerra-5.1/cinelerra/commonrender.C b/cinelerra-5.1/cinelerra/commonrender.C index 95617379..bc96dd81 100644 --- a/cinelerra-5.1/cinelerra/commonrender.C +++ b/cinelerra-5.1/cinelerra/commonrender.C @@ -266,7 +266,7 @@ int CommonRender::get_boundaries(int64_t ¤t_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); diff --git a/cinelerra-5.1/cinelerra/cwindow.C b/cinelerra-5.1/cinelerra/cwindow.C index 3a65150d..6f29f27b 100644 --- a/cinelerra-5.1/cinelerra/cwindow.C +++ b/cinelerra-5.1/cinelerra/cwindow.C @@ -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; } diff --git a/cinelerra-5.1/cinelerra/main.C b/cinelerra-5.1/cinelerra/main.C index 47257871..f422092e 100644 --- a/cinelerra-5.1/cinelerra/main.C +++ b/cinelerra-5.1/cinelerra/main.C @@ -42,6 +42,8 @@ #include #include #include +#include +#include #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; } diff --git a/cinelerra-5.1/cinelerra/mwindow.C b/cinelerra-5.1/cinelerra/mwindow.C index f62df89b..10eb2dfb 100644 --- a/cinelerra-5.1/cinelerra/mwindow.C +++ b/cinelerra-5.1/cinelerra/mwindow.C @@ -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; vidxidx < 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; vidxidx < 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); } } diff --git a/cinelerra-5.1/cinelerra/mwindow.h b/cinelerra-5.1/cinelerra/mwindow.h index 0a9dfb95..ab2a5240 100644 --- a/cinelerra-5.1/cinelerra/mwindow.h +++ b/cinelerra-5.1/cinelerra/mwindow.h @@ -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); diff --git a/cinelerra-5.1/cinelerra/mwindowgui.C b/cinelerra-5.1/cinelerra/mwindowgui.C index 2b59a636..53677b42 100644 --- a/cinelerra-5.1/cinelerra/mwindowgui.C +++ b/cinelerra-5.1/cinelerra/mwindowgui.C @@ -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); } } diff --git a/cinelerra-5.1/cinelerra/packagerenderer.C b/cinelerra-5.1/cinelerra/packagerenderer.C index 4318bec7..59d429cc 100644 --- a/cinelerra-5.1/cinelerra/packagerenderer.C +++ b/cinelerra-5.1/cinelerra/packagerenderer.C @@ -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; diff --git a/cinelerra-5.1/cinelerra/playbackengine.C b/cinelerra-5.1/cinelerra/playbackengine.C index 71cd3911..aff5e579 100644 --- a/cinelerra-5.1/cinelerra/playbackengine.C +++ b/cinelerra-5.1/cinelerra/playbackengine.C @@ -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); } diff --git a/cinelerra-5.1/cinelerra/playbackengine.h b/cinelerra-5.1/cinelerra/playbackengine.h index 936657a0..9febc059 100644 --- a/cinelerra-5.1/cinelerra/playbackengine.h +++ b/cinelerra-5.1/cinelerra/playbackengine.h @@ -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 diff --git a/cinelerra-5.1/cinelerra/playtransport.C b/cinelerra-5.1/cinelerra/playtransport.C index 0165de65..94b70dcd 100644 --- a/cinelerra-5.1/cinelerra/playtransport.C +++ b/cinelerra-5.1/cinelerra/playtransport.C @@ -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); } } diff --git a/cinelerra-5.1/cinelerra/playtransport.h b/cinelerra-5.1/cinelerra/playtransport.h index 0a60e223..330a9585 100644 --- a/cinelerra-5.1/cinelerra/playtransport.h +++ b/cinelerra-5.1/cinelerra/playtransport.h @@ -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(); diff --git a/cinelerra-5.1/cinelerra/renderengine.C b/cinelerra-5.1/cinelerra/renderengine.C index 721e3be8..3c178bb0 100644 --- a/cinelerra-5.1/cinelerra/renderengine.C +++ b/cinelerra-5.1/cinelerra/renderengine.C @@ -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(); } diff --git a/cinelerra-5.1/cinelerra/shuttle.C b/cinelerra-5.1/cinelerra/shuttle.C index d32456f7..65e2f7f2 100644 --- a/cinelerra-5.1/cinelerra/shuttle.C +++ b/cinelerra-5.1/cinelerra/shuttle.C @@ -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 { diff --git a/cinelerra-5.1/cinelerra/shuttle.h b/cinelerra-5.1/cinelerra/shuttle.h index e3bd2c91..ffe805c2 100644 --- a/cinelerra-5.1/cinelerra/shuttle.h +++ b/cinelerra-5.1/cinelerra/shuttle.h @@ -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); diff --git a/cinelerra-5.1/cinelerra/trackcanvas.C b/cinelerra-5.1/cinelerra/trackcanvas.C index dd48141a..ab93d623 100644 --- a/cinelerra-5.1/cinelerra/trackcanvas.C +++ b/cinelerra-5.1/cinelerra/trackcanvas.C @@ -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; diff --git a/cinelerra-5.1/cinelerra/transportque.C b/cinelerra-5.1/cinelerra/transportque.C index 35d6f1f3..a4faa9bf 100644 --- a/cinelerra-5.1/cinelerra/transportque.C +++ b/cinelerra-5.1/cinelerra/transportque.C @@ -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(); -} diff --git a/cinelerra-5.1/cinelerra/transportque.h b/cinelerra-5.1/cinelerra/transportque.h index 1f5f9c63..351cf682 100644 --- a/cinelerra-5.1/cinelerra/transportque.h +++ b/cinelerra-5.1/cinelerra/transportque.h @@ -19,13 +19,13 @@ * */ -#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 diff --git a/cinelerra-5.1/cinelerra/transportque.inc b/cinelerra-5.1/cinelerra/transportque.inc index 2660361a..7b1c2068 100644 --- a/cinelerra-5.1/cinelerra/transportque.inc +++ b/cinelerra-5.1/cinelerra/transportque.inc @@ -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 diff --git a/cinelerra-5.1/cinelerra/vwindowgui.C b/cinelerra-5.1/cinelerra/vwindowgui.C index 985d484b..52ef387e 100644 --- a/cinelerra-5.1/cinelerra/vwindowgui.C +++ b/cinelerra-5.1/cinelerra/vwindowgui.C @@ -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"); } } diff --git a/cinelerra-5.1/cinelerra/zwindow.C b/cinelerra-5.1/cinelerra/zwindow.C index 3c340844..6508b9e4 100644 --- a/cinelerra-5.1/cinelerra/zwindow.C +++ b/cinelerra-5.1/cinelerra/zwindow.C @@ -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() diff --git a/cinelerra-5.1/cinelerra/zwindow.h b/cinelerra-5.1/cinelerra/zwindow.h index 7f46fff0..3019d438 100644 --- a/cinelerra-5.1/cinelerra/zwindow.h +++ b/cinelerra-5.1/cinelerra/zwindow.h @@ -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); diff --git a/cinelerra-5.1/guicast/bcwindowbase.C b/cinelerra-5.1/guicast/bcwindowbase.C index 0194a758..3db866f8 100644 --- a/cinelerra-5.1/guicast/bcwindowbase.C +++ b/cinelerra-5.1/guicast/bcwindowbase.C @@ -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: diff --git a/cinelerra-5.1/guicast/keys.h b/cinelerra-5.1/guicast/keys.h index e580a84f..a79230b3 100644 --- a/cinelerra-5.1/guicast/keys.h +++ b/cinelerra-5.1/guicast/keys.h @@ -131,4 +131,7 @@ #define RETURN 13 #define NEWLINE 13 +#define SKEY_MIN 0x3000000 // Shuttle speed codes +#define SKEY_MAX 0x3010000 + #endif diff --git a/cinelerra-5.1/shuttlerc b/cinelerra-5.1/shuttlerc index a41ba3c0..71390c28 100644 --- a/cinelerra-5.1/shuttlerc +++ b/cinelerra-5.1/shuttlerc @@ -29,13 +29,21 @@ 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 -- 2.26.2