Credit Andrew - fix vorbis audio which was scratchy and ensure aging plugin does...
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / pluginclient.C
index 4e2c29847694cedc6f6bafb4350856669562a51f..d31b4b002c787bfbee824f252d40e504ebc29ed6 100644 (file)
@@ -22,6 +22,7 @@
 #include "bcdisplayinfo.h"
 #include "bchash.h"
 #include "bcsignals.h"
+#include "attachmentpoint.h"
 #include "clip.h"
 #include "condition.h"
 #include "edits.h"
 #include "pluginclient.h"
 #include "pluginserver.h"
 #include "preferences.h"
+#include "renderengine.h"
 #include "track.h"
-#include "transportque.inc"
+#include "tracks.h"
+#include "transportque.h"
 
 #include <stdio.h>
 #include <unistd.h>
 #include <ctype.h>
 #include <errno.h>
 
+PluginClientFrame::PluginClientFrame()
+{
+       position = -1;
+}
+
+PluginClientFrame::~PluginClientFrame()
+{
+}
+
 
 PluginClientThread::PluginClientThread(PluginClient *client)
  : Thread(1, 0, 0)
@@ -62,10 +74,7 @@ PluginClientThread::PluginClientThread(PluginClient *client)
 
 PluginClientThread::~PluginClientThread()
 {
-       join();
-//printf("PluginClientThread::~PluginClientThread %p %d\n", this, __LINE__);
-       delete window;  window = 0;
-//printf("PluginClientThread::~PluginClientThread %p %d\n", this, __LINE__);
+       delete window;
        delete init_complete;
 }
 
@@ -93,11 +102,9 @@ void PluginClientThread::run()
                window->lock_window("PluginClientThread::run");
 //printf("PluginClientThread::run %p %d\n", this, __LINE__);
                window->hide_window(1);
+               client->save_defaults_xml(); // needs window lock
                window->unlock_window();
                window->done_event(result);
-// Can't save defaults in the destructor because it's not called immediately
-// after closing.
-               /* if(client->defaults) */ client->save_defaults_xml();
 /* This is needed when the GUI is closed from itself */
                if(result) client->client_side_close();
        }
@@ -120,49 +127,42 @@ PluginClient* PluginClientThread::get_client()
 }
 
 
-
-
-
-
-PluginClientFrame::PluginClientFrame(int data_size,
-       int period_n,
-       int period_d)
-{
-       this->data_size = data_size;
-       force = 0;
-       this->period_n = period_n;
-       this->period_d = period_d;
-}
-
-PluginClientFrame::~PluginClientFrame()
-{
-
-}
-
-
-
-
-
 PluginClientWindow::PluginClientWindow(PluginClient *client,
        int w, int h, int min_w, int min_h, int allow_resize)
  : BC_Window(client->gui_string,
-       client->window_x /* - w / 2 */,
-       client->window_y /* - h / 2 */,
-       (int)(w*get_resources()->font_scale+0.5), (int)(h*get_resources()->font_scale+0.5),
-       (int)(min_w*get_resources()->font_scale+0.5), (int)(min_h*get_resources()->font_scale+0.5),
-       allow_resize, 0, 1)
+       client->window_x /* - w / 2 */, client->window_y /* - h / 2 */,
+       w, h, min_w, min_h, allow_resize, 0, 1)
 {
+       char title[BCTEXTLEN];
+
        this->client = client;
+
+// *** CONTEXT_HELP ***
+       if(client) {
+               strcpy(title, client->plugin_title());
+               if(! strcmp(title, "Overlay")) {
+                       // "Overlay" plugin title is ambiguous
+                       if(client->is_audio()) strcat(title, " \\(Audio\\)");
+                       if(client->is_video()) strcat(title, " \\(Video\\)");
+               }
+               if(client->server->is_ffmpeg()) {
+                       // FFmpeg plugins can be audio or video
+                       if(client->is_audio())
+                               strcpy(title, "FFmpeg Audio Plugins");
+                       if(client->is_video())
+                               strcpy(title, "FFmpeg Video Plugins");
+               }
+               context_help_set_keyword(title);
+       }
 }
 
 PluginClientWindow::PluginClientWindow(const char *title,
        int x, int y, int w, int h, int min_w, int min_h, int allow_resize)
- : BC_Window(title, x, y,
-        (int)(w*get_resources()->font_scale+0.5), (int)(h*get_resources()->font_scale+0.5),
-        (int)(min_w*get_resources()->font_scale+0.5), (int)(min_h*get_resources()->font_scale+0.5),
-       allow_resize, 0, 1)
+ : BC_Window(title, x, y, w, h, min_w, min_h, allow_resize, 0, 1)
 {
        this->client = 0;
+// *** CONTEXT_HELP ***
+       context_help_set_keyword(title);
 }
 
 PluginClientWindow::~PluginClientWindow()
@@ -188,8 +188,243 @@ int PluginClientWindow::close_event()
        return 1;
 }
 
+void PluginClientWindow::param_updated()
+{
+    printf("PluginClientWindow::param_updated %d undefined\n", __LINE__);
+}
+
+//phyllis
+PluginParam::PluginParam(PluginClient *plugin, PluginClientWindow *gui,
+    int x1, int x2, int x3, int y, int text_w,
+    int *output_i, float *output_f, int *output_q,
+    const char *title, float min, float max)
+{
+    this->output_i = output_i;
+    this->output_f = output_f;
+    this->output_q = output_q;
+    this->title = cstrdup(title);
+    this->plugin = plugin;
+    this->gui = gui;
+    this->x1 = x1;
+    this->x2 = x2;
+    this->x3 = x3;
+    this->text_w = text_w;
+    this->y = y;
+    this->min = min;
+    this->max = max;
+    fpot = 0;
+    ipot = 0;
+    qpot = 0;
+    text = 0;
+    precision = 2;
+}
+PluginParam::~PluginParam()
+{
+    delete fpot;
+    delete ipot;
+    delete qpot;
+    delete text;
+    delete title;
+}
+
+
+void PluginParam::initialize()
+{
+    BC_Title *title_;
+    int y2 = y +
+        (BC_Pot::calculate_h() -
+        BC_Title::calculate_h(gui, _(title), MEDIUMFONT)) / 2;
+    gui->add_tool(title_ = new BC_Title(x1, y2, _(title)));
+
+    if(output_f)
+    {
+        gui->add_tool(fpot = new PluginFPot(this, x2, y));
+    }
+
+    if(output_i)
+    {
+        gui->add_tool(ipot = new PluginIPot(this, x2, y));
+    }
+
+    if(output_q)
+    {
+        gui->add_tool(qpot = new PluginQPot(this, x2, y));
+    }
+
+    int y3 = y +
+        (BC_Pot::calculate_h() -
+        BC_TextBox::calculate_h(gui, MEDIUMFONT, 1, 1)) / 2;
+    if(output_i)
+    {
+        gui->add_tool(text = new PluginText(this, x3, y3, *output_i));
+    }
+    if(output_f)
+    {
+        gui->add_tool(text = new PluginText(this, x3, y3, *output_f));
+    }
+    if(output_q)
+    {
+        gui->add_tool(text = new PluginText(this, x3, y3, *output_q));
+    }
+
+    set_precision(precision);
+}
+
+void PluginParam::update(int skip_text, int skip_pot)
+{
+    if(!skip_text)
+    {
+        if(output_i)
+        {
+            text->update((int64_t)*output_i);
+        }
+        if(output_q)
+        {
+            text->update((int64_t)*output_q);
+        }
+        if(output_f)
+        {
+            text->update((float)*output_f);
+        }
+    }
+
+    if(!skip_pot)
+    {
+        if(ipot)
+        {
+            ipot->update((int64_t)*output_i);
+        }
+        if(qpot)
+        {
+            qpot->update((int64_t)*output_q);
+        }
+        if(fpot)
+        {
+            fpot->update((float)*output_f);
+        }
+    }
+}
+
+void PluginParam::set_precision(int digits)
+{
+    this->precision = digits;
+    if(fpot)
+    {
+        if(text)
+        {
+            text->set_precision(digits);
+        }
+
+        fpot->set_precision(1.0f / pow(10, digits));
+    }
+}
+
+
+PluginFPot::PluginFPot(PluginParam *param, int x, int y)
+ : BC_FPot(x,
+        y,
+        *param->output_f,
+        param->min,
+        param->max)
+{
+    this->param = param;
+    set_use_caption(0);
+}
+
+int PluginFPot::handle_event()
+{
+        *param->output_f = get_value();
+    param->update(0, 1);
+        param->plugin->send_configure_change();
+    param->gui->param_updated();
+    return 1;
+}
+
+PluginIPot::PluginIPot(PluginParam *param, int x, int y)
+ : BC_IPot(x,
+        y,
+        *param->output_i,
+        (int)param->min,
+        (int)param->max)
+{
+    this->param = param;
+    set_use_caption(0);
+}
+
+int PluginIPot::handle_event()
+{
+        *param->output_i = get_value();
+    param->update(0, 1);
+        param->plugin->send_configure_change();
+    param->gui->param_updated();
+    return 1;
+}
+
 
+PluginQPot::PluginQPot(PluginParam *param, int x, int y)
+ : BC_QPot(x,
+        y,
+        *param->output_q)
+{
+    this->param = param;
+    set_use_caption(0);
+}
+
+int PluginQPot::handle_event()
+{
+        *param->output_q = get_value();
+    param->update(0, 1);
+        param->plugin->send_configure_change();
+    param->gui->param_updated();
+    return 1;
+}
+
+PluginText::PluginText(PluginParam *param, int x, int y, int value)
+ : BC_TextBox(x,
+    y,
+    param->text_w,
+    1,
+    (int64_t)value,
+    1,
+    MEDIUMFONT)
+{
+    this->param = param;
+}
+
+PluginText::PluginText(PluginParam *param, int x, int y, float value)
+ : BC_TextBox(x,
+    y,
+    param->text_w,
+    1,
+    (float)value,
+    1,
+    MEDIUMFONT,
+    param->precision)
+{
+    this->param = param;
+}
 
+int PluginText::handle_event()
+{
+    if(param->output_i)
+    {
+        *param->output_i = atoi(get_text());
+    }
+
+    if(param->output_f)
+    {
+        *param->output_f = atof(get_text());
+    }
+
+    if(param->output_q)
+    {
+        *param->output_q = atoi(get_text());
+    }
+    param->update(1, 0);
+    param->plugin->send_configure_change();
+    param->gui->param_updated();
+    return 1;
+}
 
 
 PluginClient::PluginClient(PluginServer *server)
@@ -212,7 +447,6 @@ PluginClient::~PluginClient()
 
 // Virtual functions don't work here.
        if(defaults) delete defaults;
-       frame_buffer.remove_all_objects();
        delete update_timer;
 }
 
@@ -243,13 +477,8 @@ void PluginClient::hide_gui()
 {
        if(thread && thread->window)
        {
-//printf("PluginClient::delete_thread %d\n", __LINE__);
-/* This is needed when the GUI is closed from elsewhere than itself */
-/* Since we now use autodelete, this is all that has to be done, thread will take care of itself ... */
-/* Thread join will wait if this was not called from the thread itself or go on if it was */
                thread->window->lock_window("PluginClient::hide_gui");
                thread->window->set_done(0);
-//printf("PluginClient::hide_gui %d thread->window=%p\n", __LINE__, thread->window);
                thread->window->unlock_window();
        }
 }
@@ -321,7 +550,6 @@ int PluginClient::is_multichannel() { return 0; }
 int PluginClient::is_synthesis() { return 0; }
 int PluginClient::is_realtime() { return 0; }
 int PluginClient::is_fileio() { return 0; }
-int PluginClient::delete_buffer_ptrs() { return 0; }
 const char* PluginClient::plugin_title() { return _("Untitled"); }
 
 Theme* PluginClient::new_theme() { return 0; }
@@ -374,95 +602,138 @@ int PluginClient::set_string()
 
 
 
-void PluginClient::begin_process_buffer()
+PluginClientFrames::PluginClientFrames()
+{
+       count = 0;
+}
+PluginClientFrames::~PluginClientFrames()
 {
-// Delete all unused GUI frames
-       frame_buffer.remove_all_objects();
 }
 
+int PluginClientFrames::fwd_cmpr(PluginClientFrame *a, PluginClientFrame *b)
+{
+       double d = a->position - b->position;
+       return d < 0 ? -1 : !d ? 0 : 1;
+}
 
-void PluginClient::end_process_buffer()
+int PluginClientFrames::rev_cmpr(PluginClientFrame *a, PluginClientFrame *b)
 {
-       if(frame_buffer.size())
-       {
-               send_render_gui();
-       }
+       double d = b->position - a->position;
+       return d < 0 ? -1 : !d ? 0 : 1;
 }
 
+void PluginClientFrames::reset()
+{
+       destroy();
+       count = 0;
+}
 
+void PluginClientFrames::add_gui_frame(PluginClientFrame *frame)
+{
+       append(frame);
+       ++count;
+}
 
-void PluginClient::plugin_update_gui()
+void PluginClientFrames::concatenate(PluginClientFrames *frames)
 {
+       concat(*frames);
+       count += frames->count;
+       frames->count = 0;
+}
 
-       update_gui();
+void PluginClientFrames::sort_position(int dir)
+{
+// enforce order
+       if( dir == PLAY_REVERSE )
+               rev_sort();
+       else
+               fwd_sort();
+}
 
-// Delete unused GUI frames
-       while(frame_buffer.size() > MAX_FRAME_BUFFER)
-               frame_buffer.remove_object_number(0);
+// pop frames until buffer passes position=pos in direction=dir
+// dir==0, pop frame; pos<0, pop all frames
+// delete past frames, return last popped frame
+PluginClientFrame* PluginClientFrames::get_gui_frame(double pos, int dir)
+{
+       if( dir ) {
+               while( first != last ) {
+                       if( pos >= 0 && dir*(first->next->position - pos) > 0 ) break;
+                       delete first;  --count;
+               }
+       }
+       PluginClientFrame *frame = first;
+       if( frame ) { remove_pointer(frame);  --count; }
+       return frame;
+}
 
+PluginClientFrame* PluginClient::get_gui_frame(double pos, int dir)
+{
+       return client_frames.get_gui_frame(pos, dir);
+}
+PluginClientFrame* PluginClient::next_gui_frame()
+{
+       return client_frames.first;
+}
+
+
+void PluginClient::plugin_update_gui()
+{
+       update_gui();
 }
 
 void PluginClient::update_gui()
 {
 }
 
-int PluginClient::get_gui_update_frames()
+int PluginClient::pending_gui_frame()
 {
-       if(frame_buffer.size())
-       {
-               PluginClientFrame *frame = frame_buffer.get(0);
-               int total_frames = update_timer->get_difference() *
-                       frame->period_d /
-                       frame->period_n /
-                       1000;
-               if(total_frames) update_timer->subtract(total_frames *
-                       frame->period_n *
-                       1000 /
-                       frame->period_d);
-
-// printf("PluginClient::get_gui_update_frames %d %ld %d %d %d\n",
-// __LINE__,
-// update_timer->get_difference(),
-// frame->period_n * 1000 / frame->period_d,
-// total_frames,
-// frame_buffer.size());
-
-// Add forced frames
-               for(int i = 0; i < frame_buffer.size(); i++)
-                       if(frame_buffer.get(i)->force) total_frames++;
-               total_frames = MIN(frame_buffer.size(), total_frames);
-
-
-               return total_frames;
-       }
-       else
-       {
-               return 0;
-       }
+       PluginClientFrame *frame = client_frames.first;
+       if( !frame ) return 0;
+       double tracking_position = get_tracking_position();
+       int direction = get_tracking_direction();
+       int ret = !(direction == PLAY_REVERSE ?
+               frame->position < tracking_position :
+               frame->position > tracking_position);
+       return ret;
 }
 
-PluginClientFrame* PluginClient::get_gui_frame()
+int PluginClient::pending_gui_frames()
 {
-       if(frame_buffer.size())
-       {
-               PluginClientFrame *frame = frame_buffer.get(0);
-               frame_buffer.remove_number(0);
-               return frame;
-       }
-       else
-       {
-               return 0;
+       PluginClientFrame *frame = client_frames.first;
+       if( !frame ) return 0;
+       double tracking_position = get_tracking_position();
+       int direction = get_tracking_direction();
+       int count = 0;
+       while( frame && !(direction == PLAY_REVERSE ?
+           frame->position < tracking_position :
+           frame->position > tracking_position) ) {
+               ++count;  frame=frame->next;
        }
+       return count;
 }
 
 void PluginClient::add_gui_frame(PluginClientFrame *frame)
 {
-       frame_buffer.append(frame);
+       client_frames.add_gui_frame(frame);
+}
+int PluginClient::get_gui_frames()
+{
+       return client_frames.total();
+}
+
+double PluginClient::get_tracking_position()
+{
+       return server->mwindow->get_tracking_position();
+}
+
+int PluginClient::get_tracking_direction()
+{
+       return server->mwindow->get_tracking_direction();
 }
 
 void PluginClient::send_render_gui()
 {
-       server->send_render_gui(&frame_buffer);
+       server->send_render_gui(&client_frames);
 }
 
 void PluginClient::send_render_gui(void *data)
@@ -475,54 +746,56 @@ void PluginClient::send_render_gui(void *data, int size)
        server->send_render_gui(data, size);
 }
 
-void PluginClient::plugin_render_gui(void *data, int size)
+
+void PluginClient::plugin_reset_gui_frames()
 {
-       render_gui(data, size);
+       if( !thread ) return;
+       BC_WindowBase *window = thread->get_window();
+       if( !window ) return;
+       window->lock_window("PluginClient::plugin_reset_gui_frames");
+       client_frames.reset();
+       window->unlock_window();
 }
 
+void PluginClient::plugin_render_gui_frames(PluginClientFrames *frames)
+{
+       if( !thread ) return;
+       BC_WindowBase *window = thread->get_window();
+       if( !window ) return;
+       window->lock_window("PluginClient::render_gui");
+       while( client_frames.count > MAX_FRAME_BUFFER )
+               delete get_gui_frame(0, 0);
+// append client frames to gui client_frames, consumes frames
+       client_frames.concatenate(frames);
+       client_frames.sort_position(get_tracking_direction());
+       update_timer->update();
+       window->unlock_window();
+}
 
 void PluginClient::plugin_render_gui(void *data)
 {
        render_gui(data);
 }
 
-void PluginClient::render_gui(void *data)
+void PluginClient::plugin_render_gui(void *data, int size)
 {
-       if(thread)
-       {
-               thread->get_window()->lock_window("PluginClient::render_gui");
-
-// Set all previous frames to draw immediately
-               for(int i = 0; i < frame_buffer.size(); i++)
-                       frame_buffer.get(i)->force = 1;
-
-               ArrayList<PluginClientFrame*> *src =
-                       (ArrayList<PluginClientFrame*>*)data;
-
-// Shift GUI data to GUI client
-               while(src->size())
-               {
-                       this->frame_buffer.append(src->get(0));
-                       src->remove_number(0);
-               }
+       render_gui(data, size);
+}
 
-// Start the timer for the current buffer
-               update_timer->update();
-               thread->get_window()->unlock_window();
-       }
+void PluginClient::render_gui(void *data)
+{
+        printf("PluginClient::render_gui %d\n", __LINE__);
 }
 
 void PluginClient::render_gui(void *data, int size)
 {
-       printf("PluginClient::render_gui %d\n", __LINE__);
+        printf("PluginClient::render_gui %d\n", __LINE__);
 }
 
-
-
-
-
-
-
+void PluginClient::reset_gui_frames()
+{
+       server->reset_gui_frames();
+}
 
 int PluginClient::is_audio() { return 0; }
 int PluginClient::is_video() { return 0; }
@@ -688,8 +961,9 @@ double PluginClient::get_project_framerate()
 
 const char *PluginClient::get_source_path()
 {
-       int64_t source_position = server->plugin->startproject;
-       Edit *edit = server->plugin->track->edits->editof(source_position,PLAY_FORWARD,0);
+       Plugin *plugin = server->edl->tracks->plugin_exists(server->plugin_id);
+       int64_t source_position = plugin->startproject;
+       Edit *edit = plugin->track->edits->editof(source_position,PLAY_FORWARD,0);
        Indexable *indexable = edit ? edit->get_source() : 0;
        return indexable ? indexable->path : 0;
 }
@@ -733,24 +1007,24 @@ int PluginClient::get_interpolation_type()
 
 float PluginClient::get_red()
 {
-       EDL *edl = server->mwindow ? server->mwindow->edl : server->edl;
-       return !edl ? 0 : edl->local_session->use_max ?
+       EDL *edl = get_edl();
+       return edl->local_session->use_max ?
                edl->local_session->red_max :
                edl->local_session->red;
 }
 
 float PluginClient::get_green()
 {
-       EDL *edl = server->mwindow ? server->mwindow->edl : server->edl;
-       return !edl ? 0 : edl->local_session->use_max ?
+       EDL *edl = get_edl();
+       return edl->local_session->use_max ?
                edl->local_session->green_max :
                edl->local_session->green;
 }
 
 float PluginClient::get_blue()
 {
-       EDL *edl = server->mwindow ? server->mwindow->edl : server->edl;
-       return !edl ? 0 : edl->local_session->use_max ?
+       EDL *edl = get_edl();
+       return edl->local_session->use_max ?
                edl->local_session->blue_max :
                edl->local_session->blue;
 }
@@ -776,7 +1050,6 @@ int PluginClient::get_direction()
        return direction;
 }
 
-
 int64_t PluginClient::local_to_edl(int64_t position)
 {
        return position;
@@ -792,6 +1065,11 @@ int PluginClient::get_use_opengl()
        return server->get_use_opengl();
 }
 
+int PluginClient::to_ram(VFrame *vframe)
+{
+       return server->to_ram(vframe);
+}
+
 int PluginClient::get_total_buffers()
 {
        return total_in_buffers;
@@ -826,9 +1104,12 @@ int PluginClient::send_configure_change()
        if(server->mwindow)
                server->mwindow->undo->update_undo_before(_("tweek"), this);
 #ifdef USE_KEYFRAME_SPANNING
-       KeyFrame keyframe;
+       EDL *edl = server->edl;
+        Plugin *plugin = edl->tracks->plugin_exists(server->plugin_id);
+       KeyFrames *keyframes = plugin ? plugin->keyframes : 0;
+       KeyFrame keyframe(edl, keyframes);
        save_data(&keyframe);
-       server->apply_keyframe(&keyframe);
+       server->apply_keyframe(plugin, &keyframe);
 #else
        KeyFrame* keyframe = server->get_keyframe();
 // Call save routine in plugin
@@ -840,6 +1121,14 @@ int PluginClient::send_configure_change()
        return 0;
 }
 
+// virtual default spanning keyframe update.  If a range is selected,
+// then changed parameters are copied to (prev + selected) keyframes.
+// redefine per client for custom keyframe updates, see tracer, sketcher, crikey
+void PluginClient::span_keyframes(KeyFrame *src, int64_t start, int64_t end)
+{
+       src->span_keyframes(start, end);
+}
+
 
 KeyFrame* PluginClient::get_prev_keyframe(int64_t position, int is_local)
 {
@@ -864,14 +1153,47 @@ void PluginClient::get_projector(float *x, float *y, float *z, int64_t position)
 }
 
 
-EDLSession* PluginClient::get_edlsession()
+void PluginClient::output_to_track(float ox, float oy, float &tx, float &ty)
 {
-       if(server->edl)
-               return server->edl->session;
-       return 0;
+       float projector_x, projector_y, projector_z;
+       int64_t position = get_source_position();
+       get_projector(&projector_x, &projector_y, &projector_z, position);
+       EDL *edl = get_edl();
+       projector_x += edl->session->output_w / 2;
+       projector_y += edl->session->output_h / 2;
+       Plugin *plugin = edl->tracks->plugin_exists(server->plugin_id);
+       Track *track = plugin ? plugin->track : 0;
+       int track_w = track ? track->track_w : edl->session->output_w;
+       int track_h = track ? track->track_h : edl->session->output_h;
+       tx = (ox - projector_x) / projector_z + track_w / 2;
+       ty = (oy - projector_y) / projector_z + track_h / 2;
+}
+
+void PluginClient::track_to_output(float tx, float ty, float &ox, float &oy)
+{
+       float projector_x, projector_y, projector_z;
+       int64_t position = get_source_position();
+       get_projector(&projector_x, &projector_y, &projector_z, position);
+       EDL *edl = get_edl();
+       projector_x += edl->session->output_w / 2;
+       projector_y += edl->session->output_h / 2;
+       Plugin *plugin = edl->tracks->plugin_exists(server->plugin_id);
+       Track *track = plugin ? plugin->track : 0;
+       int track_w = track ? track->track_w : edl->session->output_w;
+       int track_h = track ? track->track_h : edl->session->output_h;
+       ox = (tx - track_w / 2) * projector_z + projector_x;
+       oy = (ty - track_h / 2) * projector_z + projector_y;
+}
+
+
+EDL *PluginClient::get_edl()
+{
+       return server->mwindow ? server->mwindow->edl : server->edl;
 }
 
 int PluginClient::gui_open()
 {
        return server->gui_open();
 }
+
+