From 3bf30d220f7855b995b887dc10812ae3780e6805 Mon Sep 17 00:00:00 2001
From: Good Guy <good1.2guy@gmail.com>
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 &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);
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 <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;
 }
 
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; 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);
 	}
 }
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