Assists music production by grouping standalone programs into sessions. Community version of "Non Session Manager".
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1283 lines
30KB

  1. /*******************************************************************************/
  2. /* Copyright (C) 2009 Jonathan Moore Liles */
  3. /* */
  4. /* This program is free software; you can redistribute it and/or modify it */
  5. /* under the terms of the GNU General Public License as published by the */
  6. /* Free Software Foundation; either version 2 of the License, or (at your */
  7. /* option) any later version. */
  8. /* */
  9. /* This program is distributed in the hope that it will be useful, but WITHOUT */
  10. /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
  11. /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */
  12. /* more details. */
  13. /* */
  14. /* You should have received a copy of the GNU General Public License along */
  15. /* with This program; see the file COPYING. If not,write to the Free Software */
  16. /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
  17. /*******************************************************************************/
  18. /* This is the main mixer group. It contains and manages Mixer_Strips. */
  19. #include "const.h"
  20. #include "Mixer.H"
  21. #include "Mixer_Strip.H"
  22. #include <FL/Fl_Pack.H>
  23. #include <FL/Fl_Scroll.H>
  24. #include <FL/Fl_Menu_Bar.H>
  25. #include <FL/fl_ask.H>
  26. #include <FL/Fl.H>
  27. #include <FL/New_Project_Dialog.H>
  28. #include <FL/Fl_Flowpack.H>
  29. #include "Project.H"
  30. #include <FL/Fl_Menu_Settings.H>
  31. #include <FL/About_Dialog.H>
  32. #include <FL/Fl_File_Chooser.H>
  33. #include <FL/Fl_Theme_Chooser.H>
  34. #include <FL/Fl_Value_SliderX.H>
  35. #include <Spatialization_Console.H>
  36. #include "file.h"
  37. #include "Group.H"
  38. #include <string.h>
  39. #include "debug.h"
  40. #include <unistd.h>
  41. #include <sys/types.h>
  42. #include "OSC/Endpoint.H"
  43. #include <lo/lo.h>
  44. #include "Controller_Module.H"
  45. const double FEEDBACK_UPDATE_FREQ = 1.0f;
  46. extern char *user_config_dir;
  47. extern char *instance_name;
  48. #include "debug.h"
  49. #include "string_util.h"
  50. #include "NSM.H"
  51. #include <FL/Fl_Tooltip.H>
  52. #include "Chain.H"
  53. extern NSM_Client *nsm;
  54. Spatialization_Console *Mixer::spatialization_console = 0;
  55. void
  56. Mixer::show_tooltip ( const char *s )
  57. {
  58. mixer->_status->label( s );
  59. }
  60. void
  61. Mixer::hide_tooltip ( void )
  62. {
  63. mixer->_status->label( 0 );
  64. }
  65. /************************/
  66. /* OSC Message Handlers */
  67. /************************/
  68. #undef OSC_REPLY_OK
  69. #undef OSC_REPLY_ERR
  70. #undef OSC_REPLY
  71. #define OSC_REPLY_OK() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, 0, "OK" )
  72. #define OSC_REPLY( value ) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, value )
  73. #define OSC_REPLY_ERR(errcode, value) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path,errcode, value )
  74. #define OSC_ENDPOINT() ((OSC::Endpoint*)user_data)
  75. static int osc_add_strip ( const char *path, const char *, lo_arg **, int , lo_message msg, void *user_data )
  76. {
  77. OSC_DMSG();
  78. Fl::lock();
  79. ((Mixer*)(OSC_ENDPOINT())->owner)->command_add_strip();
  80. Fl::unlock();
  81. OSC_REPLY_OK();
  82. return 0;
  83. }
  84. int
  85. Mixer::osc_non_hello ( const char *, const char *, lo_arg **, int , lo_message msg, void * )
  86. {
  87. mixer->handle_hello( msg );
  88. return 0;
  89. }
  90. void
  91. Mixer::handle_hello ( lo_message msg )
  92. {
  93. int argc = lo_message_get_argc( msg );
  94. lo_arg **argv = lo_message_get_argv( msg );
  95. if ( argc >= 4 )
  96. {
  97. const char *url = &argv[0]->s;
  98. const char *name = &argv[1]->s;
  99. const char *version = &argv[2]->s;
  100. const char *id = &argv[3]->s;
  101. MESSAGE( "Got hello from NON peer %s (%s) @ %s with ID \"%s\"", name, version, url, id );
  102. mixer->osc_endpoint->handle_hello( id, url );
  103. }
  104. }
  105. void
  106. Mixer::say_hello ( void )
  107. {
  108. lo_message m = lo_message_new();
  109. lo_message_add( m, "sssss",
  110. "/non/hello",
  111. osc_endpoint->url(),
  112. APP_NAME,
  113. VERSION,
  114. instance_name );
  115. nsm->broadcast( m );
  116. lo_message_free( m );
  117. }
  118. static
  119. Fl_Menu_Item *
  120. find_item( Fl_Menu_ *menu, const char *path )
  121. {
  122. return const_cast<Fl_Menu_Item*>(menu->find_item( path ));
  123. }
  124. void
  125. Mixer::sm_active ( bool b )
  126. {
  127. sm_blinker->value( b );
  128. sm_blinker->tooltip( nsm->session_manager_name() );
  129. if ( b )
  130. {
  131. find_item( menubar, "&Project/&Open" )->deactivate();
  132. find_item( menubar, "&Project/&New" )->deactivate();
  133. }
  134. }
  135. void
  136. Mixer::redraw_windows ( void )
  137. {
  138. window()->redraw();
  139. if ( Fl::first_window() )
  140. for ( Fl_Window *w = Fl::first_window(); ( w = Fl::next_window( w ) ); )
  141. w->redraw();
  142. }
  143. void Mixer::command_new ( void )
  144. {
  145. DMESSAGE( "New project" );
  146. char *default_path = read_line( user_config_dir, "default_path" );
  147. char *result_path = default_path;
  148. char *path = new_project_chooser( &result_path );
  149. if ( path )
  150. {
  151. if ( ! Project::create( path, NULL ) )
  152. fl_alert( "Error creating project!" );
  153. free( path );
  154. }
  155. load_project_settings();
  156. update_menu();
  157. if ( result_path != default_path )
  158. free(default_path);
  159. if ( result_path )
  160. {
  161. write_line( user_config_dir, "default_path", result_path );
  162. free( result_path );
  163. }
  164. }
  165. void Mixer::cb_menu(Fl_Widget* o) {
  166. Fl_Menu_Bar *menu = (Fl_Menu_Bar*)o;
  167. /* const Fl_Menu_Item *mi = &menu->menu()[menu->value()]; */
  168. char picked[256];
  169. // const char *picked = menu->text();
  170. menu->item_pathname( picked, sizeof( picked ) );
  171. DMESSAGE( "Picked %s", picked );
  172. if (! strcmp( picked, "&Project/&New") )
  173. {
  174. command_new();
  175. }
  176. else if (! strcmp( picked, "&Project/&Open" ) )
  177. {
  178. char *path = NULL;
  179. // read_line( user_config_dir, "default_path", &path );
  180. const char *name = fl_dir_chooser( "Open Project", path );
  181. free( path );
  182. mixer->hide();
  183. if ( int err = Project::open( name ) )
  184. {
  185. fl_alert( "Error opening project: %s", Project::errstr( err ) );
  186. }
  187. update_menu();
  188. mixer->show();
  189. }
  190. else if (! strcmp( picked, "&Project/&Save" ) )
  191. {
  192. command_save();
  193. }
  194. else if (! strcmp( picked, "&Project/&Quit") )
  195. {
  196. command_quit();
  197. }
  198. else if ( !strcmp( picked, "&Mixer/&Add Strip" ) )
  199. {
  200. command_add_strip();
  201. }
  202. else if ( !strcmp( picked, "&Mixer/Add &N Strips" ) )
  203. {
  204. const char *s = fl_input( "Enter number of strips to add" );
  205. if ( s )
  206. {
  207. for ( int i = atoi( s ); i > 0; i-- )
  208. command_add_strip();
  209. }
  210. }
  211. else if ( !strcmp( picked, "&Mixer/&Import Strip" ) )
  212. {
  213. const char *s = fl_file_chooser( "Export strip to filename:", "*.strip", NULL, 0 );
  214. if ( s )
  215. {
  216. if (! Mixer_Strip::import_strip( s ) )
  217. fl_alert( "%s", "Failed to import strip!" );
  218. }
  219. }
  220. else if ( ! strcmp( picked, "&Project/Se&ttings/Learn/By Strip Name" ) )
  221. {
  222. Controller_Module::learn_by_number = false;
  223. }
  224. else if ( ! strcmp( picked, "&Project/Se&ttings/Learn/By Strip Number" ) )
  225. {
  226. Controller_Module::learn_by_number = true;
  227. }
  228. else if ( ! strcmp( picked, "&Remote Control/Start Learning" ) )
  229. {
  230. Controller_Module::learn_mode( true );
  231. tooltip( "Now in learn mode. Click on a highlighted control to teach it something." );
  232. redraw();
  233. }
  234. else if ( ! strcmp( picked, "&Remote Control/Stop Learning" ) )
  235. {
  236. Controller_Module::learn_mode( false );
  237. tooltip( "Learning complete" );
  238. redraw();
  239. }
  240. else if ( !strcmp( picked, "&Remote Control/Send State" ) )
  241. {
  242. send_feedback();
  243. }
  244. else if ( ! strcmp( picked, "&Remote Control/Clear All Mappings" ) )
  245. {
  246. if ( 1 == fl_choice( "This will remove all mappings, are you sure?", "No", "Yes", NULL ) )
  247. {
  248. command_clear_mappings();
  249. }
  250. }
  251. else if ( !strcmp( picked, "&Mixer/Paste" ) )
  252. {
  253. Fl::paste(*this);
  254. }
  255. else if (! strcmp( picked, "&Project/Se&ttings/&Rows/One") )
  256. {
  257. rows( 1 );
  258. }
  259. else if (! strcmp( picked, "&Project/Se&ttings/&Rows/Two") )
  260. {
  261. rows( 2 );
  262. }
  263. else if (! strcmp( picked, "&Project/Se&ttings/&Rows/Three") )
  264. {
  265. rows( 3 );
  266. }
  267. else if (! strcmp( picked, "&Mixer/&Spatialization Console") )
  268. {
  269. if ( ! spatialization_console )
  270. {
  271. Spatialization_Console *o = new Spatialization_Console();
  272. spatialization_console = o;
  273. }
  274. if ( ! menu->mvalue()->value() )
  275. spatialization_console->hide();
  276. else
  277. spatialization_console->show();
  278. }
  279. else if (! strcmp( picked, "&Project/Se&ttings/Make Default") )
  280. {
  281. save_default_project_settings();
  282. }
  283. else if (! strcmp( picked, "&View/&Theme") )
  284. {
  285. fl_theme_chooser();
  286. }
  287. else if ( ! strcmp( picked, "&Help/&About" ) )
  288. {
  289. About_Dialog ab( PIXMAP_PATH "/non-mixer/icon-256x256.png" );
  290. ab.logo_box->label( VERSION );
  291. ab.title->label( "Non Mixer" );
  292. ab.copyright->label( "Copyright (C) 2008-2013 Jonathan Moore Liles" );
  293. ab.credits->label(
  294. "Non Mixer was written from scratch by\n"
  295. "Jonathan Moore Liles for his own use\n"
  296. "(see the manual).\n"
  297. "\n"
  298. "Nobody planned. Nobody helped.\n"
  299. "You can help now by donating time, money,\n"
  300. "and/or replacing the rest of Linux Audio\n"
  301. "with fast, light, reliable alternatives.\n" );
  302. ab.website_url->label( "http://non.tuxfamily.org" );
  303. ab.run();
  304. }
  305. else if ( !strcmp( picked, "&Help/&Manual" ))
  306. {
  307. char *pat;
  308. asprintf( &pat, "file://%s.html", DOCUMENT_PATH "/non-mixer/MANUAL" );
  309. open_url( pat );
  310. free( pat );
  311. }
  312. }
  313. void Mixer::cb_menu(Fl_Widget* o, void* v) {
  314. ((Mixer*)(v))->cb_menu(o);
  315. }
  316. void Mixer::update_frequency ( float v )
  317. {
  318. _update_interval = 1.0f / v;
  319. Fl::remove_timeout( &Mixer::update_cb, this );
  320. Fl::add_timeout( _update_interval, &Mixer::update_cb, this );
  321. }
  322. void
  323. Mixer::update_cb ( void *v )
  324. {
  325. ((Mixer*)v)->update_cb();
  326. }
  327. void
  328. Mixer::update_cb ( void )
  329. {
  330. Fl::repeat_timeout( _update_interval, &Mixer::update_cb, this );
  331. if ( active_r() && visible_r() )
  332. {
  333. for ( int i = 0; i < mixer_strips->children(); i++ )
  334. {
  335. ((Mixer_Strip*)mixer_strips->child(i))->update();
  336. }
  337. }
  338. }
  339. static void
  340. progress_cb ( int p, void *v )
  341. {
  342. static int oldp = 0;
  343. if ( p != oldp )
  344. {
  345. oldp = p;
  346. if ( nsm )
  347. {
  348. nsm->progress( p / 100.0f );
  349. }
  350. Fl::check();
  351. }
  352. }
  353. void
  354. Mixer::save_default_project_settings ( void )
  355. {
  356. char path[256];
  357. snprintf( path, sizeof( path ), "%s/%s", user_config_dir, ".default_project_settings" );
  358. ((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Project/Se&ttings" ), path );
  359. }
  360. void
  361. Mixer::load_default_project_settings ( void )
  362. {
  363. char path[256];
  364. snprintf( path, sizeof( path ), "%s/%s", user_config_dir, ".default_project_settings" );
  365. ((Fl_Menu_Settings*)menubar)->load( menubar->find_item( "&Project/Se&ttings" ), path );
  366. }
  367. void
  368. Mixer::reset_project_settings ( void )
  369. {
  370. rows(1);
  371. load_default_project_settings();
  372. }
  373. void
  374. Mixer::save_project_settings ( void )
  375. {
  376. if ( ! Project::open() )
  377. return;
  378. ((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Project/Se&ttings" ), "options" );
  379. }
  380. void
  381. Mixer::load_project_settings ( void )
  382. {
  383. reset_project_settings();
  384. // if ( Project::open() )
  385. ((Fl_Menu_Settings*)menubar)->load( menubar->find_item( "&Project/Se&ttings" ), "options" );
  386. update_menu();
  387. }
  388. Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) :
  389. Fl_Group( X, Y, W, H, L )
  390. {
  391. Loggable::dirty_callback( &Mixer::handle_dirty, this );
  392. Loggable::progress_callback( progress_cb, NULL );
  393. Fl_Tooltip::hoverdelay( 0 );
  394. Fl_Tooltip::delay( 0 );
  395. fl_show_tooltip = &Mixer::show_tooltip;
  396. fl_hide_tooltip = &Mixer::hide_tooltip;
  397. /* Fl_Tooltip::size( 11 ); */
  398. /* Fl_Tooltip::textcolor( FL_FOREGROUND_COLOR ); */
  399. /* Fl_Tooltip::color( fl_color_add_alpha( FL_DARK1, 0 ) ); */
  400. // fl_tooltip_docked = 1;
  401. // _groups.resize(16);
  402. _rows = 1;
  403. _strip_height = 0;
  404. box( FL_FLAT_BOX );
  405. labelsize( 96 );
  406. { Fl_Group *o = new Fl_Group( X, Y, W, 24 );
  407. { Fl_Menu_Bar *o = menubar = new Fl_Menu_Bar( X, Y, W, 24 );
  408. o->add( "&Project/&New" );
  409. o->add( "&Project/&Open" );
  410. o->add( "&Project/Se&ttings/&Rows/One", '1', 0, 0, FL_MENU_RADIO | FL_MENU_VALUE );
  411. o->add( "&Project/Se&ttings/&Rows/Two", '2', 0, 0, FL_MENU_RADIO );
  412. o->add( "&Project/Se&ttings/&Rows/Three", '3', 0, 0, FL_MENU_RADIO );
  413. o->add( "&Project/Se&ttings/Learn/By Strip Number", 0, 0, 0, FL_MENU_RADIO );
  414. o->add( "&Project/Se&ttings/Learn/By Strip Name", 0, 0, 0, FL_MENU_RADIO | FL_MENU_VALUE );
  415. o->add( "&Project/Se&ttings/Make Default", 0,0,0);
  416. o->add( "&Project/&Save", FL_CTRL + 's', 0, 0 );
  417. o->add( "&Project/&Quit", FL_CTRL + 'q', 0, 0 );
  418. o->add( "&Mixer/&Add Strip", 'a', 0, 0 );
  419. o->add( "&Mixer/Add &N Strips" );
  420. o->add( "&Mixer/&Import Strip" );
  421. o->add( "&Mixer/Paste", FL_CTRL + 'v', 0, 0 );
  422. o->add( "&Mixer/&Spatialization Console", FL_F + 8, 0, 0, FL_MENU_TOGGLE );
  423. o->add( "&Remote Control/Start Learning", FL_F + 9, 0, 0 );
  424. o->add( "&Remote Control/Stop Learning", FL_F + 10, 0, 0 );
  425. o->add( "&Remote Control/Send State" );
  426. o->add( "&Remote Control/Clear All Mappings", 0, 0, 0 );
  427. o->add( "&View/&Theme", 0, 0, 0 );
  428. o->add( "&Help/&Manual" );
  429. o->add( "&Help/&About" );
  430. o->callback( cb_menu, this );
  431. }
  432. { Fl_Box *o = project_name = new Fl_Box( X + 150, Y, W, 24 );
  433. o->labelfont( FL_HELVETICA_ITALIC );
  434. o->label( 0 );
  435. o->align( FL_ALIGN_INSIDE | FL_ALIGN_CENTER );
  436. o->labeltype( FL_SHADOW_LABEL );
  437. Fl_Group::current()->resizable( o );
  438. }
  439. { sm_blinker = new Fl_Button( ( X + W) - 37, Y + 4, 35, 15, "SM");
  440. sm_blinker->box(FL_ROUNDED_BOX);
  441. sm_blinker->down_box(FL_ROUNDED_BOX);
  442. sm_blinker->color(FL_DARK2);
  443. sm_blinker->selection_color((Fl_Color)93);
  444. sm_blinker->labeltype(FL_NORMAL_LABEL);
  445. sm_blinker->labelfont(3);
  446. sm_blinker->labelsize(14);
  447. sm_blinker->labelcolor(FL_DARK3);
  448. sm_blinker->align(Fl_Align(FL_ALIGN_CENTER));
  449. sm_blinker->when(FL_WHEN_RELEASE);
  450. sm_blinker->deactivate();
  451. } // Fl_Blink_Button* sm_blinker
  452. o->end();
  453. }
  454. { Fl_Scroll *o = scroll = new Fl_Scroll( X, Y + 24, W, H - ( 100 ) );
  455. o->box( FL_FLAT_BOX );
  456. // o->type( Fl_Scroll::HORIZONTAL_ALWAYS );
  457. // o->box( Fl_Scroll::BOTH );
  458. {
  459. Fl_Flowpack *o = mixer_strips = new Fl_Flowpack( X, Y + 24, W, H - ( 18*2 + 24 ));
  460. // label( "Non-Mixer" );
  461. align( (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE) );
  462. o->flow( false );
  463. o->box( FL_FLAT_BOX );
  464. o->type( Fl_Pack::HORIZONTAL );
  465. o->hspacing( 2 );
  466. o->vspacing( 2 );
  467. o->end();
  468. Fl_Group::current()->resizable( o );
  469. }
  470. o->end();
  471. Fl_Group::current()->resizable( o );
  472. }
  473. { Fl_Box *o = _status = new Fl_Box( X, Y + H - 18, W, 18 );
  474. o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
  475. o->labelsize( 10 );
  476. o->box( FL_FLAT_BOX );
  477. o->color( FL_DARK1 );
  478. }
  479. end();
  480. resize( X,Y,W,H );
  481. update_frequency( 15 );
  482. Fl::add_timeout( FEEDBACK_UPDATE_FREQ, send_feedback_cb, this );
  483. update_menu();
  484. load_options();
  485. }
  486. /* translate message addressed to strip number to appropriate strip */
  487. int
  488. Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  489. {
  490. int n;
  491. char *rem;
  492. char *client_name;
  493. OSC::Endpoint *ep = (OSC::Endpoint*)user_data;
  494. if ( 3 != sscanf( path, "%a[^/]/strip#/%d/%a[^\n]", &client_name, &n, &rem ) )
  495. return -1;
  496. Mixer_Strip *o = mixer->track_by_number( n );
  497. if ( ! o )
  498. {
  499. DMESSAGE( "No strip by number %i", n );
  500. return 0;
  501. }
  502. char *new_path;
  503. asprintf( &new_path, "%s/strip/%s/%s", client_name, o->name(), rem );
  504. free( rem );
  505. lo_send_message( ep->address(), new_path, msg );
  506. free( new_path );
  507. return 0;
  508. }
  509. void
  510. Mixer::load_translations ( void )
  511. {
  512. FILE *fp = fopen( "mappings", "r" );
  513. if ( ! fp )
  514. {
  515. WARNING( "Error opening mappings file for reading" );
  516. return;
  517. }
  518. char *to;
  519. char *from;
  520. while ( 2 == fscanf( fp, "%a[^|> ] |> %a[^ \n]\n", &from, &to ) )
  521. {
  522. osc_endpoint->add_translation( from, to );
  523. free(from);
  524. free(to);
  525. }
  526. fclose( fp );
  527. }
  528. void
  529. Mixer::save_translations ( void )
  530. {
  531. FILE *fp = fopen( "mappings", "w" );
  532. if ( ! fp )
  533. {
  534. WARNING( "Error opening mappings file for writing" );
  535. return;
  536. }
  537. for ( int i = 0; i < osc_endpoint->ntranslations(); i++ )
  538. {
  539. const char *to;
  540. const char *from;
  541. if ( osc_endpoint->get_translation( i, &to, &from ) )
  542. {
  543. fprintf( fp, "%s |> %s\n", to, from );
  544. }
  545. }
  546. fclose( fp );
  547. }
  548. int
  549. Mixer::init_osc ( const char *osc_port )
  550. {
  551. osc_endpoint = new OSC::Endpoint();
  552. if ( int r = osc_endpoint->init( LO_UDP, osc_port ) )
  553. return r;
  554. osc_endpoint->owner = this;
  555. printf( "OSC=%s\n", osc_endpoint->url() );
  556. osc_endpoint->add_method( "/non/hello", "ssss", &Mixer::osc_non_hello, osc_endpoint, "" );
  557. //
  558. osc_endpoint->add_method( "/non/mixer/add_strip", "", osc_add_strip, osc_endpoint, "" );
  559. osc_endpoint->start();
  560. osc_endpoint->add_method( NULL, NULL, osc_strip_by_number, osc_endpoint, "");
  561. return 0;
  562. }
  563. Mixer::~Mixer ( )
  564. {
  565. DMESSAGE( "Destroying mixer" );
  566. save_options();
  567. Fl::remove_timeout( &Mixer::update_cb, this );
  568. Fl::remove_timeout( &Mixer::send_feedback_cb, this );
  569. /* FIXME: teardown */
  570. mixer_strips->clear();
  571. }
  572. void
  573. Mixer::add_group ( Group *g )
  574. {
  575. groups.push_back( g );
  576. for ( int i = mixer_strips->children(); i--; )
  577. ((Mixer_Strip*)mixer_strips->child(i))->update_group_choice();
  578. }
  579. void
  580. Mixer::remove_group ( Group *g )
  581. {
  582. groups.remove(g);
  583. for ( int i = mixer_strips->children(); i--; )
  584. ((Mixer_Strip*)mixer_strips->child(i))->update_group_choice();
  585. }
  586. void Mixer::resize ( int X, int Y, int W, int H )
  587. {
  588. Fl_Group::resize( X, Y, W, H );
  589. scroll->resize( X, Y + 24, W, H - 24 - 18 );
  590. mixer_strips->resize( X, Y + 24, W, H - (18*2) - 24 );
  591. rows( _rows );
  592. }
  593. void Mixer::add ( Mixer_Strip *ms )
  594. {
  595. MESSAGE( "Add mixer strip \"%s\"", ms->name() );
  596. mixer_strips->add( ms );
  597. ms->size( ms->w(), _strip_height );
  598. ms->redraw();
  599. ms->take_focus();
  600. }
  601. int
  602. Mixer::find_strip ( const Mixer_Strip *m ) const
  603. {
  604. return mixer_strips->find( m );
  605. }
  606. void
  607. Mixer::quit ( void )
  608. {
  609. /* TODO: save project? */
  610. while ( Fl::first_window() ) Fl::first_window()->hide();
  611. }
  612. void
  613. Mixer::insert ( Mixer_Strip *ms, Mixer_Strip *before )
  614. {
  615. // mixer_strips->remove( ms );
  616. mixer_strips->insert( *ms, before );
  617. // scroll->redraw();
  618. }
  619. void
  620. Mixer::insert ( Mixer_Strip *ms, int i )
  621. {
  622. Mixer_Strip *before = (Mixer_Strip*)mixer_strips->child( i );
  623. insert( ms, before);
  624. }
  625. void
  626. Mixer::move_left ( Mixer_Strip *ms )
  627. {
  628. int i = mixer_strips->find( ms );
  629. if ( i > 0 )
  630. insert( ms, i - 1 );
  631. /* FIXME: do better */
  632. mixer_strips->redraw();
  633. }
  634. void
  635. Mixer::move_right ( Mixer_Strip *ms )
  636. {
  637. int i = mixer_strips->find( ms );
  638. if ( i < mixer_strips->children() - 1 )
  639. insert( ms, i + 2 );
  640. /* FIXME: do better */
  641. mixer_strips->redraw();
  642. }
  643. void Mixer::remove ( Mixer_Strip *ms )
  644. {
  645. MESSAGE( "Remove mixer strip \"%s\"", ms->name() );
  646. mixer_strips->remove( ms );
  647. ms->group()->remove( ms );
  648. if ( parent() )
  649. parent()->redraw();
  650. }
  651. Mixer_Strip *
  652. Mixer::event_inside ( void )
  653. {
  654. for ( int i = mixer_strips->children(); i--; )
  655. if ( Fl::event_inside( mixer_strips->child(i) ) )
  656. return (Mixer_Strip*)mixer_strips->child(i);
  657. return NULL;
  658. }
  659. bool
  660. Mixer::contains ( Mixer_Strip *ms )
  661. {
  662. return ms->parent() == mixer_strips;
  663. }
  664. /* set the ideal number of rows... All may not actually fit. */
  665. void
  666. Mixer::rows ( int ideal_rows )
  667. {
  668. int sh = 0;
  669. int actual_rows = 1;
  670. /* calculate how many rows will actually fit */
  671. int can_fit = scroll->h() / ( Mixer_Strip::min_h() );
  672. actual_rows = can_fit > 0 ? can_fit : 1;
  673. if ( actual_rows > ideal_rows )
  674. actual_rows = ideal_rows;
  675. /* calculate strip height */
  676. if ( actual_rows > 1 )
  677. {
  678. sh = ( scroll->h() / (float)actual_rows ) - ( mixer_strips->vspacing() * ( actual_rows - 2 ));
  679. mixer_strips->flow(true);
  680. }
  681. else
  682. actual_rows = 1;
  683. if ( 1 == actual_rows )
  684. {
  685. sh = (scroll->h() - 18);
  686. mixer_strips->flow( false );
  687. }
  688. int tw = 0;
  689. for ( int i = 0; i < mixer_strips->children(); ++i )
  690. {
  691. Mixer_Strip *t = (Mixer_Strip*)mixer_strips->child( i );
  692. t->size( t->w(), sh );
  693. tw += t->w() + mixer_strips->hspacing();
  694. }
  695. if ( actual_rows > 1 )
  696. mixer_strips->size( scroll->w() - 18, mixer_strips->h() );
  697. else
  698. mixer_strips->size( tw, sh );
  699. _rows = ideal_rows;
  700. if ( _strip_height != sh );
  701. {
  702. mixer_strips->redraw();
  703. scroll->redraw();
  704. _strip_height = sh;
  705. }
  706. }
  707. int
  708. Mixer::nstrips ( void ) const
  709. {
  710. return mixer_strips->children();
  711. }
  712. /** retrun a pointer to the track named /name/, or NULL if no track is named /name/ */
  713. Mixer_Strip *
  714. Mixer::track_by_name ( const char *name )
  715. {
  716. for ( int i = mixer_strips->children(); i-- ; )
  717. {
  718. Mixer_Strip *t = (Mixer_Strip*)mixer_strips->child( i );
  719. if ( ! strcmp( name, t->name() ) )
  720. return t;
  721. }
  722. return NULL;
  723. }
  724. /** retrun a pointer to the track named /name/, or NULL if no track is named /name/ */
  725. Mixer_Strip *
  726. Mixer::track_by_number ( int n )
  727. {
  728. if ( n < 0 || n >= mixer_strips->children() )
  729. return NULL;
  730. return (Mixer_Strip*)mixer_strips->child(n);
  731. }
  732. /** return a malloc'd string representing a unique name for a new track */
  733. char *
  734. Mixer::get_unique_track_name ( const char *name )
  735. {
  736. char pat[256];
  737. strcpy( pat, name );
  738. for ( int i = 1; track_by_name( pat ); ++i )
  739. snprintf( pat, sizeof( pat ), "%s.%d", name, i );
  740. return strdup( pat );
  741. }
  742. Group *
  743. Mixer::group_by_name ( const char *name )
  744. {
  745. for ( std::list<Group*>::iterator i = groups.begin();
  746. i != groups.end();
  747. i++ )
  748. if ( !strcmp( (*i)->name(), name ))
  749. return *i;
  750. return NULL;
  751. }
  752. char *
  753. Mixer::get_unique_group_name ( const char *name )
  754. {
  755. char pat[256];
  756. strcpy( pat, name );
  757. for ( int i = 1; group_by_name( pat ); ++i )
  758. snprintf( pat, sizeof( pat ), "%s.%d", name, i );
  759. return strdup( pat );
  760. }
  761. void
  762. Mixer::handle_dirty ( int d, void *v )
  763. {
  764. //Mixer *m = (Mixer*)v;
  765. if ( !nsm )
  766. return;
  767. if ( d == 1 )
  768. nsm->is_dirty();
  769. else if ( d == 0 )
  770. nsm->is_clean();
  771. }
  772. void
  773. Mixer::snapshot ( void )
  774. {
  775. if ( spatialization_console )
  776. spatialization_console->log_create();
  777. for ( std::list<Group*>::iterator i = groups.begin(); i != groups.end(); ++i )
  778. (*i)->log_create();
  779. for ( int i = 0; i < mixer_strips->children(); ++i )
  780. ((Mixer_Strip*)mixer_strips->child( i ))->log_children();
  781. }
  782. void
  783. Mixer::new_strip ( void )
  784. {
  785. add( new Mixer_Strip( get_unique_track_name( "Unnamed" ) ) );
  786. }
  787. bool
  788. Mixer::save ( void )
  789. {
  790. MESSAGE( "Saving state" );
  791. Loggable::snapshot_callback( &Mixer::snapshot, this );
  792. Loggable::snapshot( "snapshot" );
  793. save_translations();
  794. return true;
  795. }
  796. static const char options_filename[] = "options";
  797. void
  798. Mixer::load_options ( void )
  799. {
  800. // save options
  801. /* char *path; */
  802. /* asprintf( &path, "%s/options", user_config_dir ); */
  803. /* ((Fl_Menu_Settings*)menubar)->load( menubar->find_item( "&Options" ), path ); */
  804. /* free( path ); */
  805. }
  806. void
  807. Mixer::save_options ( void )
  808. {
  809. /* char *path; */
  810. /* asprintf( &path, "%s/%s", user_config_dir, options_filename ); */
  811. /* ((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Options" ), path ); */
  812. /* free( path ); */
  813. }
  814. void
  815. Mixer::update_menu ( void )
  816. {
  817. project_name->label( Project::name() );
  818. const_cast<Fl_Menu_Item*>(menubar->find_item( "&Mixer/&Spatialization Console" ))
  819. ->flags = FL_MENU_TOGGLE | ( ( spatialization_console && spatialization_console->shown() ) ? FL_MENU_VALUE : 0 );
  820. }
  821. void
  822. Mixer::send_feedback_cb ( void *v )
  823. {
  824. Mixer *m = (Mixer*)v;
  825. m->send_feedback();
  826. Fl::repeat_timeout( FEEDBACK_UPDATE_FREQ, send_feedback_cb, v );
  827. }
  828. /** unconditionally send feedback to all mapped controls. This is
  829. * useful for updating the state of an external controller. */
  830. void
  831. Mixer::send_feedback ( void )
  832. {
  833. for ( int i = 0; i < mixer_strips->children(); i++ )
  834. {
  835. ((Mixer_Strip*)mixer_strips->child(i))->send_feedback();
  836. }
  837. }
  838. int
  839. Mixer::handle ( int m )
  840. {
  841. if ( Fl_Group::handle( m ) )
  842. return 1;
  843. switch ( m )
  844. {
  845. case FL_PASTE:
  846. {
  847. if ( ! Fl::event_inside( this ) )
  848. return 0;
  849. const char *text = Fl::event_text();
  850. char *file;
  851. if ( ! sscanf( text, "file://%a[^\r\n]\n", &file ) )
  852. {
  853. WARNING( "invalid drop \"%s\"\n", text );
  854. return 0;
  855. }
  856. unescape_url( file );
  857. printf( "pasted file \"%s\"\n", file );
  858. if (! Mixer_Strip::import_strip( file ) )
  859. fl_alert( "%s", "Failed to import strip!" );
  860. return 1;
  861. }
  862. }
  863. return 0;
  864. }
  865. #include <algorithm>
  866. std::list <std::string>
  867. Mixer::get_auto_connect_targets ( void )
  868. {
  869. std::list <std::string> sl;
  870. std::list <std::string> rl;
  871. for ( int i = mixer_strips->children(); i--; )
  872. {
  873. ((Mixer_Strip*)mixer_strips->child(i))->get_output_ports(sl);
  874. }
  875. for ( std::list<std::string>::iterator i = sl.begin(); i != sl.end(); i++ )
  876. {
  877. char *s = strdup( i->c_str() );
  878. *rindex( s, '/' ) = 0;
  879. if ( !index( s, '/' ) )
  880. {
  881. char *o;
  882. asprintf( &o, "%s/mains", s );
  883. free(s);
  884. s = o;
  885. }
  886. if ( std::find( rl.begin(), rl.end(), s ) == rl.end() )
  887. {
  888. rl.push_back( s );
  889. }
  890. free(s);
  891. }
  892. return rl;
  893. }
  894. void
  895. Mixer::auto_connect ( void )
  896. {
  897. /* give strips with group affinity the first shot */
  898. for ( int i = 0; i < mixer_strips->children(); i++ )
  899. {
  900. Mixer_Strip *s = ((Mixer_Strip*)mixer_strips->child(i));
  901. if ( s->has_group_affinity() )
  902. s->auto_connect_outputs();
  903. }
  904. /* now do that catch-alls, first one wins! */
  905. for ( int i = 0; i < mixer_strips->children(); i++ )
  906. {
  907. Mixer_Strip *s = ((Mixer_Strip*)mixer_strips->child(i));
  908. if ( ! s->has_group_affinity() )
  909. s->auto_connect_outputs();
  910. }
  911. }
  912. void
  913. Mixer::maybe_auto_connect_output ( Module::Port *p )
  914. {
  915. /* give strips with group affinity the first shot */
  916. for ( int i = 0; i < mixer_strips->children(); i++ )
  917. {
  918. Mixer_Strip *s = ((Mixer_Strip*)mixer_strips->child(i));
  919. if ( s->has_group_affinity() )
  920. if ( s->maybe_auto_connect_output( p ) )
  921. return;
  922. }
  923. /* now do that catch-alls, first one wins! */
  924. for ( int i = 0; i < mixer_strips->children(); i++ )
  925. {
  926. Mixer_Strip *s = ((Mixer_Strip*)mixer_strips->child(i));
  927. if ( ! s->has_group_affinity() )
  928. if ( s->maybe_auto_connect_output( p ) )
  929. return;
  930. }
  931. }
  932. /************/
  933. /* Commands */
  934. /************/
  935. void
  936. Mixer::command_clear_mappings ( void )
  937. {
  938. osc_endpoint->clear_translations();
  939. }
  940. bool
  941. Mixer::command_save ( void )
  942. {
  943. if ( ! Project::open() )
  944. {
  945. command_new();
  946. update_menu();
  947. }
  948. save_project_settings();
  949. return Project::save();
  950. }
  951. bool
  952. Mixer::command_load ( const char *path, const char *display_name )
  953. {
  954. mixer->deactivate();
  955. Project::close();
  956. char *pwd = (char*)malloc( PATH_MAX + 1 );
  957. getcwd( pwd, PATH_MAX );
  958. chdir( path );
  959. load_project_settings();
  960. chdir( pwd );
  961. free( pwd );
  962. if ( Project::open( path ) )
  963. {
  964. // fl_alert( "Error opening project specified on commandline: %s", Project::errstr( err ) );
  965. return false;
  966. }
  967. if ( display_name )
  968. Project::name( display_name );
  969. load_translations();
  970. update_menu();
  971. auto_connect();
  972. mixer->activate();
  973. return true;
  974. }
  975. bool
  976. Mixer::command_new ( const char *path, const char *display_name )
  977. {
  978. if ( ! Project::create( path, "" ) )
  979. return false;
  980. if ( display_name )
  981. Project::name( display_name );
  982. load_project_settings();
  983. update_menu();
  984. return true;
  985. // fl_alert( "Error creating project!" );
  986. }
  987. void
  988. Mixer::command_quit ( void )
  989. {
  990. if ( Loggable::dirty() )
  991. {
  992. int i = fl_choice( "There have been changes since the last save. Quitting now will discard them", "Discard", "Cancel", NULL );
  993. if ( i != 0 )
  994. return;
  995. }
  996. quit();
  997. }
  998. /* */
  999. void
  1000. Mixer::command_add_strip ( void )
  1001. {
  1002. new_strip();
  1003. }