| @@ -38,8 +38,10 @@ m_UpdateTimer(0) | |||
| { | |||
| m_IncompleteWire.OutputChild=-1; | |||
| m_IncompleteWire.OutputPort=-1; | |||
| m_IncompleteWire.OutputTerminal=false; | |||
| m_IncompleteWire.InputChild=-1; | |||
| m_IncompleteWire.InputPort=-1; | |||
| m_IncompleteWire.InputTerminal=false; | |||
| m_BG=NULL; | |||
| m_BGData=NULL; | |||
| @@ -364,6 +366,7 @@ void Fl_Canvas::PortClicked(Fl_DeviceGUI* Device, int Type, int Port, bool Value | |||
| m_IncompleteWire.OutputChild=ChildNum; | |||
| m_IncompleteWire.OutputPort=Port; | |||
| m_IncompleteWire.OutputID=Device->GetID(); | |||
| m_IncompleteWire.OutputTerminal=Device->IsTerminal(); | |||
| } | |||
| else | |||
| { | |||
| @@ -378,6 +381,7 @@ void Fl_Canvas::PortClicked(Fl_DeviceGUI* Device, int Type, int Port, bool Value | |||
| m_IncompleteWire.InputChild=ChildNum; | |||
| m_IncompleteWire.InputPort=Port; | |||
| m_IncompleteWire.InputID=Device->GetID(); | |||
| m_IncompleteWire.InputTerminal=Device->IsTerminal(); | |||
| } | |||
| else | |||
| { | |||
| @@ -392,7 +396,8 @@ void Fl_Canvas::PortClicked(Fl_DeviceGUI* Device, int Type, int Port, bool Value | |||
| // send the connect callback | |||
| cb_Connection(this,(void*)&m_IncompleteWire); | |||
| m_Graph.AddConnection(m_IncompleteWire.OutputID,m_IncompleteWire.InputID); | |||
| m_Graph.AddConnection(m_IncompleteWire.OutputID,m_IncompleteWire.OutputTerminal, | |||
| m_IncompleteWire.InputID,m_IncompleteWire.InputTerminal); | |||
| // Turn on both ports | |||
| Fl_DeviceGUI* ODGUI = (Fl_DeviceGUI*)(child(m_IncompleteWire.OutputChild)); | |||
| @@ -557,30 +562,65 @@ istream &operator>>(istream &s, Fl_Canvas &o) | |||
| { | |||
| int NumWires; | |||
| s>>NumWires; | |||
| for(int n=0; n<NumWires; n++) | |||
| // my bad, didn't version this stream - remove one day... | |||
| if (NumWires==-1) | |||
| { | |||
| CanvasWire NewWire; | |||
| int version; | |||
| s>>version; | |||
| s>>NumWires; | |||
| s>>NewWire.OutputID; | |||
| s>>NewWire.OutputChild; | |||
| s>>NewWire.OutputPort; | |||
| s>>NewWire.InputID; | |||
| s>>NewWire.InputChild; | |||
| s>>NewWire.InputPort; | |||
| o.m_WireVec.push_back(NewWire); | |||
| // Notify connection by callback | |||
| o.cb_Connection(&o,(void*)&NewWire); | |||
| o.m_Graph.AddConnection(NewWire.OutputID,NewWire.InputID); | |||
| for(int n=0; n<NumWires; n++) | |||
| { | |||
| CanvasWire NewWire; | |||
| // Turn on both ports | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->AddConnection(NewWire.OutputPort+ | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->GetInfo()->NumInputs); | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.InputChild)))->AddConnection(NewWire.InputPort); | |||
| } | |||
| s>>NewWire.OutputID; | |||
| s>>NewWire.OutputChild; | |||
| s>>NewWire.OutputPort; | |||
| s>>NewWire.OutputTerminal; | |||
| s>>NewWire.InputID; | |||
| s>>NewWire.InputChild; | |||
| s>>NewWire.InputPort; | |||
| s>>NewWire.InputTerminal; | |||
| o.m_WireVec.push_back(NewWire); | |||
| // Notify connection by callback | |||
| o.cb_Connection(&o,(void*)&NewWire); | |||
| o.m_Graph.AddConnection(NewWire.OutputID,NewWire.OutputTerminal,NewWire.InputID,NewWire.InputTerminal); | |||
| // Turn on both ports | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->AddConnection(NewWire.OutputPort+ | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->GetInfo()->NumInputs); | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.InputChild)))->AddConnection(NewWire.InputPort); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for(int n=0; n<NumWires; n++) | |||
| { | |||
| CanvasWire NewWire; | |||
| s>>NewWire.OutputID; | |||
| s>>NewWire.OutputChild; | |||
| s>>NewWire.OutputPort; | |||
| s>>NewWire.InputID; | |||
| s>>NewWire.InputChild; | |||
| s>>NewWire.InputPort; | |||
| o.m_WireVec.push_back(NewWire); | |||
| // Notify connection by callback | |||
| o.cb_Connection(&o,(void*)&NewWire); | |||
| o.m_Graph.AddConnection(NewWire.OutputID,false,NewWire.InputID,false); | |||
| // Turn on both ports | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->AddConnection(NewWire.OutputPort+ | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->GetInfo()->NumInputs); | |||
| ((Fl_DeviceGUI*)(o.child(NewWire.InputChild)))->AddConnection(NewWire.InputPort); | |||
| } | |||
| } | |||
| return s; | |||
| } | |||
| @@ -588,6 +628,9 @@ istream &operator>>(istream &s, Fl_Canvas &o) | |||
| ostream &operator<<(ostream &s, Fl_Canvas &o) | |||
| { | |||
| int version=0; | |||
| s<<-1<<" "<<version<<" "; | |||
| s<<o.m_WireVec.size()<<endl; | |||
| for(vector<CanvasWire>::iterator i=o.m_WireVec.begin(); | |||
| @@ -596,9 +639,11 @@ ostream &operator<<(ostream &s, Fl_Canvas &o) | |||
| s<<i->OutputID<<" "; | |||
| s<<i->OutputChild<<" "; | |||
| s<<i->OutputPort<<" "; | |||
| s<<i->OutputTerminal<<" "; | |||
| s<<i->InputID<<" "; | |||
| s<<i->InputChild<<" "; | |||
| s<<i->InputPort<<endl; | |||
| s<<i->InputPort<<" "; | |||
| s<<i->InputTerminal<<endl; | |||
| } | |||
| return s; | |||
| @@ -40,18 +40,22 @@ public: | |||
| OutputChild=-1; | |||
| OutputPort=-1; | |||
| OutputID=-1; | |||
| OutputTerminal=false; | |||
| InputChild=-1; | |||
| InputPort=-1; | |||
| InputID=-1; | |||
| InputTerminal=false; | |||
| DelMe=false; | |||
| } | |||
| int OutputID; | |||
| int OutputChild; | |||
| int OutputPort; | |||
| bool OutputTerminal; | |||
| int InputID; | |||
| int InputChild; | |||
| int InputPort; | |||
| bool InputTerminal; | |||
| bool DelMe; | |||
| }; | |||
| @@ -51,13 +51,14 @@ int Fl_PortButton::handle(int event) | |||
| return 1; | |||
| } | |||
| Fl_DeviceGUI::Fl_DeviceGUI(const DeviceGUIInfo& Info, Fl_Group *PW, Fl_Pixmap *Icon) : | |||
| Fl_DeviceGUI::Fl_DeviceGUI(const DeviceGUIInfo& Info, Fl_Group *PW, Fl_Pixmap *Icon, bool Terminal) : | |||
| Fl_Group(Info.XPos, Info.YPos, Info.Width+(PortGroupWidth*2), Info.Height+TitleBarHeight, ""), | |||
| m_PluginWindow(NULL), | |||
| m_Icon(NULL), | |||
| m_Name(Info.Name), | |||
| m_ID(-1), | |||
| m_DelMe(false) | |||
| m_DelMe(false), | |||
| m_IsTerminal(Terminal) | |||
| { | |||
| for (int n=0; n<512; n++) Numbers[n]=n; | |||
| @@ -79,7 +79,7 @@ struct DeviceGUIInfo | |||
| class Fl_DeviceGUI : public Fl_Group | |||
| { | |||
| public: | |||
| Fl_DeviceGUI(const DeviceGUIInfo& Info, Fl_Group *PW, Fl_Pixmap *Icon); | |||
| Fl_DeviceGUI(const DeviceGUIInfo& Info, Fl_Group *PW, Fl_Pixmap *Icon, bool Terminal=false); | |||
| virtual int handle(int event); | |||
| virtual void draw(); | |||
| @@ -107,6 +107,9 @@ public: | |||
| virtual void Clear(); | |||
| int GetPortType(int n) { return m_Info.PortTypes[n]; } | |||
| // do we belong to a plugin that is an output? | |||
| bool IsTerminal() { return m_IsTerminal; } | |||
| protected: | |||
| DeviceGUIInfo m_Info; | |||
| @@ -127,6 +130,7 @@ private: | |||
| string m_Name; | |||
| int m_ID; | |||
| bool m_DelMe; | |||
| bool m_IsTerminal; | |||
| }; | |||
| #endif | |||
| @@ -48,16 +48,34 @@ void GraphSort::Sort() | |||
| // walk back from all the roots | |||
| m_Sorted.clear(); | |||
| list<int> RootNodes; | |||
| bool FoundRoot=false; | |||
| for (map<int,Node>::iterator i=m_Graph.begin(); | |||
| i!=m_Graph.end(); i++) | |||
| { | |||
| // if there are no outputs, this must be a root | |||
| if (i->second.Outputs.empty()) | |||
| { | |||
| FoundRoot=true; | |||
| RecursiveWalk(i->first); | |||
| } | |||
| } | |||
| // no roots found - try looking for a terminal node and recursing from | |||
| // there, this makes circular graphs work. | |||
| if (!FoundRoot) | |||
| { | |||
| for (map<int,Node>::iterator i=m_Graph.begin(); | |||
| i!=m_Graph.end(); i++) | |||
| { | |||
| // if there are no outputs, this must be a root | |||
| if (i->second.IsTerminal) | |||
| { | |||
| RecursiveWalk(i->first); | |||
| } | |||
| } | |||
| } | |||
| #ifdef GRAPHSORT_TRACE | |||
| for(list<int>::iterator i=m_Sorted.begin(); | |||
| i!=m_Sorted.end(); i++) | |||
| @@ -121,12 +139,13 @@ void GraphSort::Dump() | |||
| } | |||
| } | |||
| void GraphSort::AddConnection(int SID, int DID) | |||
| void GraphSort::AddConnection(int SID, bool STerminal, int DID, bool DTerminal) | |||
| { | |||
| map<int,Node>::iterator si=m_Graph.find(SID); | |||
| if (si==m_Graph.end()) | |||
| { | |||
| Node newnode; | |||
| newnode.IsTerminal = STerminal; | |||
| m_Graph[SID]=newnode; | |||
| #ifdef GRAPHSORT_TRACE | |||
| cerr<<"added "<<SID<<endl; | |||
| @@ -137,6 +156,7 @@ void GraphSort::AddConnection(int SID, int DID) | |||
| if (di==m_Graph.end()) | |||
| { | |||
| Node newnode; | |||
| newnode.IsTerminal = DTerminal; | |||
| m_Graph[DID]=newnode; | |||
| #ifdef GRAPHSORT_TRACE | |||
| cerr<<"added "<<DID<<endl; | |||
| @@ -34,7 +34,7 @@ public: | |||
| ~GraphSort(); | |||
| const list<int> &GetSortedList(); | |||
| void Sort(); | |||
| void AddConnection(int SID, int DID); | |||
| void AddConnection(int SID, bool STerminal, int DID, bool DTerminal); | |||
| void RemoveConnection(int SID, int DID); | |||
| void Clear(); | |||
| void Dump(); | |||
| @@ -43,6 +43,7 @@ public: | |||
| { | |||
| list<int> Inputs; | |||
| list<int> Outputs; | |||
| bool IsTerminal; | |||
| }; | |||
| private: | |||
| @@ -81,6 +81,7 @@ inline void FilterPluginGUI::cb_Cutoff_i(Fl_Slider* o, void* v) | |||
| float value=100.0f-o->value(); | |||
| m_GUICH->Set("Cutoff",(float)(value*value)+10.0f); | |||
| } | |||
| void FilterPluginGUI::cb_Cutoff(Fl_Slider* o, void* v) | |||
| { ((FilterPluginGUI*)(o->parent()))->cb_Cutoff_i(o,v); } | |||
| @@ -99,3 +100,10 @@ inline void FilterPluginGUI::cb_RevResonance_i(Fl_Button* o, void* v) | |||
| void FilterPluginGUI::cb_RevResonance(Fl_Button* o, void* v) | |||
| { ((FilterPluginGUI*)(o->parent()))->cb_RevResonance_i(o,v); } | |||
| const string FilterPluginGUI::GetHelpText(const string &loc){ | |||
| return string("") | |||
| + "The standard SpiralSynth filter, based on the (zxforms design).\n" | |||
| + "Quite a meaty sound - low pass only, nice for bass modulations.\n" | |||
| + "With variable emphasis/cutoff CV's.\n\n" | |||
| + "It's also pretty fast, and well tested in SpiralSynth."; | |||
| } | |||
| @@ -36,6 +36,9 @@ public: | |||
| virtual void UpdateValues(SpiralPlugin *o); | |||
| protected: | |||
| const string GetHelpText(const string &loc); | |||
| private: | |||
| Fl_Group *GUIFilterGroup; | |||
| @@ -154,7 +154,7 @@ int JackClient::Process(jack_nframes_t nframes, void *o) | |||
| } | |||
| } | |||
| } | |||
| if(RunCallback&&RunContext) | |||
| { | |||
| // do the work | |||
| @@ -345,6 +345,9 @@ m_Connected(false) | |||
| { | |||
| m_RefCount++; | |||
| // we are an output | |||
| m_IsTerminal = true; | |||
| m_PluginInfo.Name="Jack"; | |||
| m_PluginInfo.Width=200; | |||
| m_PluginInfo.Height=325; | |||
| @@ -390,7 +393,7 @@ PluginInfo &JackPlugin::Initialise(const HostInfo *Host) | |||
| PluginInfo& Info= SpiralPlugin::Initialise(Host); | |||
| host=Host; | |||
| JackClient::Get()->SetCallback(cb_Update,m_Parent); | |||
| if (m_RefCount==1) JackClient::Get()->SetCallback(cb_Update,m_Parent); | |||
| return Info; | |||
| } | |||
| @@ -409,6 +412,11 @@ void JackPlugin::Execute() | |||
| void JackPlugin::ExecuteCommands() | |||
| { | |||
| // only do this once per set of plugins | |||
| m_NoExecuted++; | |||
| if (m_NoExecuted!=m_RefCount) return; | |||
| m_NoExecuted=0; | |||
| // we want to process this whether we are connected to stuff or not | |||
| JackClient* pJack=JackClient::Get(); | |||
| @@ -71,3 +71,12 @@ inline void MoogFilterPluginGUI::cb_Resonance_i(Fl_Knob* o, void* v) | |||
| { m_GUICH->Set("Resonance",(float)(o->value())); } | |||
| void MoogFilterPluginGUI::cb_Resonance(Fl_Knob* o, void* v) | |||
| { ((MoogFilterPluginGUI*)(o->parent()))->cb_Resonance_i(o,v); } | |||
| const string MoogFilterPluginGUI::GetHelpText(const string &loc){ | |||
| return string("") | |||
| + "Classic moog filter. Very different sound to the other filters,\n" | |||
| + "needless to say, very squelchy. As well as lowpass, band and high\n" | |||
| + "pass are simultaneously calculated too. The emphasis can be pushed\n" | |||
| + "into self oscillation (careful of the speakers). In this way, it\n" | |||
| + "can be used to generate sinewave oscillations."; | |||
| } | |||
| @@ -36,6 +36,9 @@ public: | |||
| virtual void UpdateValues(SpiralPlugin *o); | |||
| protected: | |||
| const string GetHelpText(const string &loc); | |||
| private: | |||
| Fl_Group *GUIFilterGroup; | |||
| @@ -372,3 +372,19 @@ void OscillatorPluginGUI::cb_pop(Fl_Button* o, void* v) { | |||
| ((OscillatorPluginGUI*)(o->parent()))->cb_pop_i(o,v); | |||
| } | |||
| const string OscillatorPluginGUI::GetHelpText(const string &loc){ | |||
| return string("") | |||
| + "The Oscillator generates raw waveforms from CV controls. Three wave \n" | |||
| + "shapes are included, Square wave, Triangle wave and white noise.\n\n" | |||
| + "In the square and triangle shapes, the Frequency CV controls the pitch \n" | |||
| + "of the signal generated, and the pulsewidth turns the squarewave into \n" | |||
| + "a pulse wave of varying harmonics, and the triangle wave into a sawtooth,\n" | |||
| + "or reverse sawtooth wave.\n\n" | |||
| + "The sample & hold CV changes the time between samples with the white noise.\n" | |||
| + "This is usful for making the Oscillator into a random CV generator.\n\n" | |||
| + "The plugin window allows you to select the wave shape, set the octave and\n" | |||
| + "fine tune the frequency. There are also controls to set the pulsewidth,\n" | |||
| + "sample and hold manually, and control the modulation depth of the input CV's.\n\n" | |||
| + "The frequency can be set extremely low on this oscillator, so you can use\n" | |||
| + "it as an LFO for controlling other plugins."; | |||
| } | |||
| @@ -37,6 +37,9 @@ public: | |||
| virtual void UpdateValues(SpiralPlugin *o); | |||
| protected: | |||
| const string GetHelpText(const string &loc); | |||
| private: | |||
| Fl_Check_Button *ShapeSquare; | |||
| @@ -87,7 +87,10 @@ OutputPlugin::OutputPlugin() : | |||
| m_Volume(1.0f) | |||
| { | |||
| m_RefCount++; | |||
| // we are an output. | |||
| m_IsTerminal=true; | |||
| m_PluginInfo.Name="OSS"; | |||
| m_PluginInfo.Width=100; | |||
| m_PluginInfo.Height=100; | |||
| @@ -80,3 +80,17 @@ inline void SVFilterPluginGUI::cb_Reset_i(Fl_Button* o, void* v) | |||
| { } | |||
| void SVFilterPluginGUI::cb_Reset(Fl_Button* o, void* v) | |||
| { ((SVFilterPluginGUI*)(o->parent()))->cb_Reset_i(o,v); } | |||
| const string SVFilterPluginGUI::GetHelpText(const string &loc){ | |||
| return string("") | |||
| + "A State Variable Filter. First thing to say is, it's a bit\n" | |||
| + "broken. Seems to generate glitchy noise when the cutoff is \n" | |||
| + "modulated. Possibly a range bug on the cutoff too.\n" | |||
| + "On the other hand, I like some of the noises it seems to\n" | |||
| + "make, so it's here anyway (I'll fix it some day).\n\n" | |||
| + "Works pretty well at band,high and peaking useful for creating\n" | |||
| + "some different sounds.\n\n" | |||
| + "Note: Comes with a reset button, so if you break it pushing\n" | |||
| + "the emphasis up too high, you can reset the cooeficients\n" | |||
| + "(which fixes it)."; | |||
| } | |||
| @@ -36,6 +36,9 @@ public: | |||
| virtual void UpdateValues(SpiralPlugin *o); | |||
| protected: | |||
| const string GetHelpText(const string &loc); | |||
| private: | |||
| Fl_Group *GUIFilterGroup; | |||
| @@ -30,6 +30,7 @@ SpiralPlugin::SpiralPlugin() | |||
| cb_Update=NULL; | |||
| m_Parent=NULL; | |||
| m_HostID=-1; | |||
| m_IsTerminal=false; | |||
| m_AudioCH = new ChannelHandler; | |||
| } | |||
| @@ -104,6 +104,8 @@ public: | |||
| void SetParent(void *s) { m_Parent=s; } | |||
| void UpdateChannelHandler(); | |||
| // is the plugin connected to an external device (oss/alsa/jack) | |||
| bool IsTerminal() { return m_IsTerminal; } | |||
| ChannelHandler *GetChannelHandler() { return m_AudioCH; } | |||
| @@ -153,6 +155,8 @@ protected: | |||
| // tell the engine that we are taking control of the | |||
| // timing for output. | |||
| void (*cb_Blocking)(void*o ,bool m); | |||
| bool m_IsTerminal; | |||
| private: | |||
| @@ -21,6 +21,8 @@ | |||
| #include <FL/fl_draw.h> | |||
| #include <FL/fl_draw.H> | |||
| #include <FL/Fl_Multiline_Output.h> | |||
| #include <FL/Fl_Text_Display.h> | |||
| #include <FL/Fl_Text_Buffer.h> | |||
| static const int GUI_COLOUR = 154; | |||
| static const int GUIBG_COLOUR = 144; | |||
| @@ -83,14 +85,15 @@ inline void SpiralPluginGUI::cb_Help_i(Fl_Button* o, void* v) | |||
| { | |||
| int w=300,h=200; | |||
| m_HelpWin = new Fl_Double_Window(w,h,"Help"); | |||
| Fl_Multiline_Output* text= new Fl_Multiline_Output(0,0,w,h); | |||
| text->value(GetHelpText(SpiralInfo::LOCALE).c_str()); | |||
| Fl_Text_Display* text = new Fl_Text_Display(0,0,10,10); | |||
| text->buffer(new Fl_Text_Buffer); | |||
| text->insert(GetHelpText(SpiralInfo::LOCALE).c_str()); | |||
| text->textsize(10); | |||
| text->set_output(); | |||
| m_HelpWin->add(text); | |||
| m_HelpWin->resizable(text); | |||
| m_HelpWin->show(); | |||
| text->size(w,h); // hack to get the text widget to appear??? | |||
| } | |||
| else | |||
| { | |||
| @@ -504,3 +504,17 @@ void WaveTablePluginGUI::cb_pop(Fl_Button* o, void* v) { | |||
| ((WaveTablePluginGUI*)o->parent())->cb_pop_i(o,v); | |||
| } | |||
| const string WaveTablePluginGUI::GetHelpText(const string &loc){ | |||
| return string("") | |||
| + "The WaveTable plugin is a fast multifunction oscillator with a variety \n" | |||
| + "of wave shapes:\n" | |||
| + "Sine, Square, Saw, Reverse Saw, Triangle, Two pulse shapes and an inverse\n" | |||
| + "sinewave.\n\n" | |||
| + "These wave shapes are internally represented as samples, rather than\n" | |||
| + "being continually calculated like the conventional oscillator. This \n" | |||
| + "makes the plugin fast, but restricts the modulations you can do on the\n" | |||
| + "wave forms (no pulsewidth).\n\n" | |||
| + "The oscillator can be pitched very low for use as a LFO CV generator,\n" | |||
| + "using any of the supported wave shapes. User wave shapes are planned,\n" | |||
| + "so you will be able to load your own samples in."; | |||
| } | |||
| @@ -36,6 +36,9 @@ public: | |||
| virtual void UpdateValues(SpiralPlugin* o); | |||
| protected: | |||
| const string GetHelpText(const string &loc); | |||
| private: | |||
| Fl_Check_Button *ShapeSquare; | |||
| @@ -49,7 +49,7 @@ void XFadePluginGUI::UpdateValues(SpiralPlugin *o) | |||
| inline void XFadePluginGUI::cb_Mix_i(Fl_Slider* o, void* v) | |||
| { | |||
| m_GUICH->Set("Mix",o->value()); | |||
| m_GUICH->Set("Mix",(float)o->value()); | |||
| } | |||
| void XFadePluginGUI::cb_Mix(Fl_Slider* o, void* v) | |||
| { ((XFadePluginGUI*)(o->parent()))->cb_Mix_i(o,v); } | |||
| @@ -567,7 +567,7 @@ DeviceWin* SynthModular::NewDeviceWin(int n, int x, int y) | |||
| Info.XPos = x; //TOOLBOX_WIDTH+(rand()%400); | |||
| Info.YPos = y; //rand()%400; | |||
| nlw->m_DeviceGUI = new Fl_DeviceGUI(Info, temp, Pix); | |||
| nlw->m_DeviceGUI = new Fl_DeviceGUI(Info, temp, Pix, nlw->m_Device->IsTerminal()); | |||
| m_Canvas->add(nlw->m_DeviceGUI); | |||
| m_Canvas->redraw(); | |||