// Copyright (c) 2004 by Iain Duncan and Michael Gogins. All rights reserved. // fl_controller is licensed under the terms of the GNU Lesser General Public License. // Csound instruments should expect p3 through p6 to receive values // normalized over the range from 0 through 1. #include <csound.h> #include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Box.H> #include <FL/Fl_Dial.H> #include <FL/Fl_Button.H> #include <FL/Fl_Round_Button.H> #include <FL/Fl_Output.H> #include <FL/Fl_Multiline_Output.H> #include <FL/Fl_File_Chooser.H> #include <cstdlib> #include <cstdio> #include <map> #include <string> #include <vector> using namespace std; class ControlWindow : public Fl_Window { public: ControlWindow(int w, int h, const char* title ); virtual ~ControlWindow(); std::map<Fl_Dial *, Fl_Output *> outputs_for_dials; std::map<Fl_Dial *, int> numbers_for_dials; std::map<int, double> dial_values; // switch for dynamic versus snapshot patch loading Fl_Round_Button *button_dyn_table; Fl_Button *button_play_note; Fl_Button *button_load_snapshot; Fl_Button *button_start_csound; Fl_Button *button_stop_csound; Fl_Button *button_load_csd; Fl_Multiline_Output *output_box; protected: // FLTK callbacks: static void cb_dial_gen_( Fl_Dial*, void* p_data ); void cb_dial_gen( Fl_Dial*); static void cb_button_play_note_( Fl_Button*, void* p_data ); void cb_button_play_note( Fl_Button*); static void cb_button_load_snapshot_( Fl_Button*, void* p_data ); void cb_button_load_snapshot( Fl_Button*); static void cb_button_start_csound_( Fl_Button*, void* p_data ); void cb_button_start_csound( Fl_Button*); static void cb_button_stop_csound_( Fl_Button*, void* p_data ); void cb_button_stop_csound( Fl_Button*); static void cb_button_load_csd_( Fl_Button*, void* p_data ); void cb_button_load_csd( Fl_Button*); // Csound yield callback: static int cb_thread_yield(CSOUND *csound); void csound_start(); // Csound rendering thread routine: static uintptr_t csound_thread_routine_(void *p_data); int csound_thread_routine( ); void csound_stop(); // Other Csound routines: void csound_load_csd(); void csound_play_note( double dur, double amp, double pitch, double p5 ); void csound_load_ftable( int table_num, double val_0, double val_0, double val_0, double val_0 ); void csound_update_ftable( int table_num, int table_indx, double table_value ); // Csound state: CSOUND *csound; int cs_ftable_num;; bool cs_performing; bool go; string csd_filename; }; ControlWindow::ControlWindow(int w, int h, const char* title) : Fl_Window(w,h,title), cs_ftable_num(1), cs_performing(0) { csound = csoundCreate(this); begin(); // loop to put the dials on screen int dial_x = 10; int dial_y = 10; // put the dial names here const char *dial_label[8] = {"Dur", "Amp", "Pitch", "P5", "FC", "FQ", "FC Env", "PW" }; // put the dials on screen, with text boxes underneath them for ( int dial_num = 0; dial_num < 8; dial_num++, dial_x+=50 ) { // a spacer for the two sets of dials if ( dial_num == 4 ) dial_x+=40; // put the dials and value boxes on screen Fl_Dial *dial = new Fl_Dial( dial_x,dial_y, 40,40, dial_label[dial_num] ); Fl_Output *output = new Fl_Output( dial_x, dial_y+60, 40,20, "" ); outputs_for_dials[dial] = output; numbers_for_dials[dial] = dial_num; dial->callback((Fl_Callback*)cb_dial_gen_, this); } button_play_note = new Fl_Button(10,100, 180,30, "Play Csound Note" ); button_play_note->callback((Fl_Callback*)cb_button_play_note_, this); button_load_snapshot = new Fl_Button(250,100, 180,30, "Send Patch Snapshot" ); button_load_snapshot->callback((Fl_Callback*)cb_button_load_snapshot_, this); button_dyn_table = new Fl_Round_Button( 480,20, 200,30, "Dynamically load patch table." ); button_start_csound = new Fl_Button( 480,60, 180,30, "Start Csound" ); button_start_csound->callback((Fl_Callback*)cb_button_start_csound_, this); button_stop_csound = new Fl_Button( 480,120, 180,30, "Stop Csound" ); button_stop_csound->callback((Fl_Callback*)cb_button_stop_csound_, this); button_load_csd = new Fl_Button( 480,180, 180,30, "Load Csd File" ); button_load_csd->callback((Fl_Callback*)cb_button_load_csd_, this); output_box = new Fl_Multiline_Output( 10,200, 400,100, "" ); output_box->value("Starting value for output"); end(); resizable(this); show(); } ControlWindow::~ControlWindow() { csoundDestroy(csound); } void ControlWindow::cb_dial_gen_( Fl_Dial* dial, void *p_data ) { ((ControlWindow *)p_data)->cb_dial_gen(dial); } void ControlWindow::cb_button_play_note_( Fl_Button* button, void *p_data ) { ((ControlWindow *)p_data)->cb_button_play_note(button); } void ControlWindow::cb_button_load_snapshot_( Fl_Button* button, void *p_data ) { ((ControlWindow *)p_data)->cb_button_load_snapshot(button); } void ControlWindow::cb_button_start_csound_( Fl_Button* button, void *p_data ) { ((ControlWindow *)p_data)->cb_button_start_csound(button); } void ControlWindow::cb_button_stop_csound_( Fl_Button* button, void *p_data ) { ((ControlWindow *)p_data)->cb_button_stop_csound(button); } void ControlWindow::cb_button_load_csd_( Fl_Button* button, void *p_data ) { ((ControlWindow *)p_data)->cb_button_load_csd(button); } // If the dynamic table load button is ON, we also send a table update to csound. void ControlWindow::cb_dial_gen( Fl_Dial *p_dial) { double dial_value = p_dial->value(); Fl_Output *output = outputs_for_dials[p_dial]; int dial_number = numbers_for_dials[p_dial]; dial_values[dial_number] = dial_value; char buffer[0xff]; sprintf(buffer, "%5.2f", dial_value ); // index point is dial_num - 4 because of the dial layout in the parent window int table_index = dial_number - 4; output->value( buffer ); int dyn_table_on = button_dyn_table->value(); char outstring[50]; if ( dyn_table_on && table_index >= 0 && cs_performing ) { csound_update_ftable( cs_ftable_num, table_index, dial_value ); sprintf( outstring, "You moved dial %i.\nUpdating table %i, index %i, with value %5.2f.\n", \ dial_number, cs_ftable_num, table_index, dial_value ); } else { sprintf( outstring, "You moved dial %i to value %5.3f.\n", dial_number, dial_value ); } output_box->value( outstring ); } void ControlWindow::cb_button_play_note( Fl_Button *p_button) { if ( !cs_performing ) { char *output_str = "Csound is not running."; output_box->value( output_str ); return; } double amp, pitch, dur, p5; dur = dial_values[0]; amp = dial_values[1]; pitch = dial_values[2]; p5 = dial_values[3]; char output_str[100]; sprintf( output_str, "Playing csound note:\ni1\t0\t%5.2f\t%5.2f\t%5.2f\t%5.2f", \ dur, amp, pitch, p5 ); output_box->value( output_str ); csound_play_note( dur, amp, pitch, p5 ); } void ControlWindow::cb_button_load_snapshot( Fl_Button *p_button) { if ( !cs_performing ){ char *output_str = "Csound is not running."; output_box->value( output_str ); return; } double val_0 = dial_values[4]; double val_1 = dial_values[5]; double val_2 = dial_values[6]; double val_3 = dial_values[7]; char output_str[100]; sprintf( output_str, "Loading csound ftable #%i with snapshot.\n%5.2f\t%5.2f\t%5.2f\t%5.2f", \ cs_ftable_num, val_0, val_1, val_2, val_3 ); output_box->value( output_str ); csound_load_ftable( cs_ftable_num, val_0, val_1, val_2, val_3 ); } void ControlWindow::cb_button_start_csound( Fl_Button *p_button) { if (csd_filename.empty()) { char *output_str = "You need to load a csd file."; output_box->value( output_str ); return; } if (!cs_performing) { char *output_str = "Starting Csound performance."; output_box->value( output_str ); csound_start(); } else { char *output_str = "Csound is already running!"; output_box->value( output_str ); } } void ControlWindow::cb_button_stop_csound( Fl_Button *p_button) { if (cs_performing) { char *output_str = "Stopping Csound performance."; output_box->value( output_str ); csound_stop(); } else { char *output_str = "Csound is not running!"; output_box->value( output_str ); } } void ControlWindow::cb_button_load_csd( Fl_Button *p_button) { csound_load_csd(); } /** * While Csound is rendering, * safely dispatch FLTK events. */ int ControlWindow::cb_thread_yield(CSOUND *csound) { Fl::lock(); Fl::wait(0.0); Fl::unlock(); return 1; } uintptr_t ControlWindow::csound_thread_routine_(void *p_data) { int retval = ((ControlWindow*) p_data)->csound_thread_routine(); return (uintptr_t) ((intptr_t) retval); } /** * To permit the FLTK GUI to work during performance, * Csound must perform in a separate thread. */ int ControlWindow::csound_thread_routine() { const char *argv[] = {"csound", csd_filename.c_str()}; csoundCompile(csound, 2, (char **)argv); // cs_performing is set by Csound, and go is set by the user. for(cs_performing = true, go = true; cs_performing && go; ) { // Csound will call cb_thread_yield. cs_performing = !csoundPerformKsmps(csound); } csoundCleanup(csound); cs_performing = false; return true; } /** * Use the Csound API to create the Csound performance thread, * first making sure that Csound will call the FLTK event dispatcher callback. */ void ControlWindow::csound_start() { csoundSetYieldCallback(csound, cb_thread_yield); csoundCreateThread(&ControlWindow::csound_thread_routine_, this); } /** * Simply set a flag to indicate to the performance thread that it should * end prematurely. */ void ControlWindow::csound_stop() { go = false; } /** * Don't actually load a CSD file, just store the filename of the CSD file * for Csound to render. */ void ControlWindow::csound_load_csd() { printf("Loading Csound csd file.\n"); csd_filename = fl_file_chooser("Select .csd file", "*.csd", "*.csd" ); } /** * Send a note to instr 1: p1 = 1, p2 = 0, p3 = duration, p4=amplitude, p5=pitch, p6. */ void ControlWindow::csound_play_note( double cs_dur, double cs_amp, double cs_pitch, double cs_p6 ) { MYFLT p[7]; p[1] = (MYFLT) 1; p[2] = (MYFLT) 0; p[3] = (MYFLT) cs_dur; p[4] = (MYFLT) cs_amp; p[5] = (MYFLT) cs_pitch; p[6] = (MYFLT) cs_p6; csoundScoreEvent(csound, 'i', &(p[1]), 6); } void ControlWindow::csound_load_ftable( int cs_table_num, double cs_val_0, double cs_val_1, double cs_val_2, double cs_val_3 ) { printf( "Loading Csound table #%i:\n %5.2f %5.2f %5.2f %5.2f\n", cs_table_num, cs_val_0, cs_val_1, cs_val_2, cs_val_3 ); if (csoundTableLength(csound, cs_table_num) < 3) { printf("Invalid ftable %d\n", cs_table_num); return; } csoundTableSet(csound, cs_table_num, 0, cs_val_0); csoundTableSet(csound, cs_table_num, 1, cs_val_1); csoundTableSet(csound, cs_table_num, 2, cs_val_2); csoundTableSet(csound, cs_table_num, 3, cs_val_3); } void ControlWindow::csound_update_ftable( int cs_table_num, int cs_table_indx, double cs_table_value ) { printf( "Updating Csound table #%i, index %i, value %5.2f\n", cs_table_num, cs_table_indx, cs_table_value ); if (csoundTableLength(csound, cs_table_num) < cs_table_indx) { printf("Invalid ftable %d\n", cs_table_num); return; } csoundTableSet(csound, cs_table_num, cs_table_indx, cs_table_value); } int main() { ControlWindow *my_control_win = new ControlWindow(800,400, "Csound Controller" ); return Fl::run(); }