|  | @@ -37,6 +37,7 @@ laBaseNodeType LA_IDN_OUTPUT;
 | 
	
		
			
				|  |  |  laBaseNodeType LA_IDN_SCOPE;
 | 
	
		
			
				|  |  |  laBaseNodeType LA_IDN_ENVELOPE;
 | 
	
		
			
				|  |  |  laBaseNodeType LA_IDN_QUANTIZE;
 | 
	
		
			
				|  |  | +laBaseNodeType LA_IDN_SYNTH_DRIVER;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_INPUT;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_FM;
 | 
	
	
		
			
				|  | @@ -46,6 +47,7 @@ laPropContainer* LA_PC_IDN_OUTPUT;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_SCOPE;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_ENVELOPE;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_QUANTIZE;
 | 
	
		
			
				|  |  | +laPropContainer* LA_PC_IDN_DRIVER;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #define _2PI 6.283185307
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -546,6 +548,72 @@ void laui_QuantizeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laCol
 | 
	
		
			
				|  |  |      laEndRow(uil,b);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +void IDN_SynthDriverInit(laSynthNodeDriver* n, int NoCreate){
 | 
	
		
			
				|  |  | +    if(NoCreate){ return; }
 | 
	
		
			
				|  |  | +    n->Prev=laCreateInSocket("PREV",0); n->Next=laCreateOutSocket(n,"NEXT",0);
 | 
	
		
			
				|  |  | +    n->InTransport=laCreateInSocket("TRANSPORT",LA_PROP_FLOAT|LA_PROP_ARRAY);
 | 
	
		
			
				|  |  | +    n->InReset=laCreateInSocket("RESET",LA_PROP_FLOAT|LA_PROP_ARRAY);
 | 
	
		
			
				|  |  | +    strSafeSet(&n->Base.Name,"Sound Player");
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void IDN_SynthDriverDestroy(laSynthNodeDriver* n){
 | 
	
		
			
				|  |  | +    laDestroyInSocket(n->InTransport); laDestroyInSocket(n->InReset); strSafeDestroy(&n->Base.Name);
 | 
	
		
			
				|  |  | +    laDestroyInSocket(n->Prev); laDestroyOutSocket(n->Next);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +int IDN_SynthDriverVisit(laSynthNodeDriver* n, laNodeVisitInfo* vi){
 | 
	
		
			
				|  |  | +    LA_GUARD_THIS_NODE(n,vi);
 | 
	
		
			
				|  |  | +    if(LA_SRC_AND_PARENT(n->Prev)){ laBaseNode*bn=n->Prev->Source->Parent; LA_VISIT_NODE(bn,vi); }
 | 
	
		
			
				|  |  | +    if(LA_SRC_AND_PARENT(n->InTransport)){ laBaseNode*bn=n->InTransport->Source->Parent; LA_VISIT_NODE(bn,vi); }
 | 
	
		
			
				|  |  | +    if(LA_SRC_AND_PARENT(n->InReset)){ laBaseNode*bn=n->InReset->Source->Parent; LA_VISIT_NODE(bn,vi); }
 | 
	
		
			
				|  |  | +    LA_ADD_THIS_NODE(n,vi);
 | 
	
		
			
				|  |  | +    return LA_DAG_FLAG_PERM;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +int IDN_SynthDriverEval(laSynthNodeDriver* n){
 | 
	
		
			
				|  |  | +    laSynth*ss=n->Target; if(!ss){ return 0; }
 | 
	
		
			
				|  |  | +    int trans=n->iTransport; LA_GET_SRC_AS_VALUE(trans,n->InTransport);
 | 
	
		
			
				|  |  | +    int res=n->iReset; LA_GET_SRC_AS_VALUE(res,n->InReset); int dif=0;
 | 
	
		
			
				|  |  | +    if(n->AlreadyReset && res==0){ n->AlreadyReset=0; res=0; }
 | 
	
		
			
				|  |  | +    elif(!n->AlreadyReset && res!=0){ n->AlreadyReset=1; }
 | 
	
		
			
				|  |  | +    else{ res=0; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laSpinLock(&ss->Lock);
 | 
	
		
			
				|  |  | +    if(ss->Playing!=trans){ dif=1; }
 | 
	
		
			
				|  |  | +    ss->Playing = trans; if(res){ ss->EvalSamples=0; ss->EvalTime=0; }
 | 
	
		
			
				|  |  | +    laSpinUnlock(&ss->Lock);
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(dif){ laNotifyInstanceUsers(ss); }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    return 1;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void IDN_SynthDriverCopy(laSynthNodeDriver* new, laSynthNodeDriver* old, int DoRematch){
 | 
	
		
			
				|  |  | +    if(DoRematch){ LA_IDN_NEW_LINK(InTransport); LA_IDN_NEW_LINK(InReset); return; }
 | 
	
		
			
				|  |  | +    new->iTransport = old->iTransport; new->iReset = old->iReset;
 | 
	
		
			
				|  |  | +    memAssignRef(new,&new->Target,old->Target);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void laui_SynthDriverNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
 | 
	
		
			
				|  |  | +    laColumn* c=laFirstColumn(uil),*cl,*cr,*cll,*clr;
 | 
	
		
			
				|  |  | +    LA_BASE_NODE_HEADER(uil,c,This);
 | 
	
		
			
				|  |  | +    laSplitColumn(uil,c,0.6); cl=laLeftColumn(c,0); cr=laRightColumn(c,3);
 | 
	
		
			
				|  |  | +    //laSplitColumn(uil,cl,0.4); cll=laLeftColumn(cl,3); clr=laRightColumn(cl,0);
 | 
	
		
			
				|  |  | +    laUiItem* b2, *rui;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laShowNodeSocket(uil,cr,This,"prev",0)->Flags|=LA_UI_SOCKET_LABEL_W;
 | 
	
		
			
				|  |  | +    laShowNodeSocket(uil,cr,This,"next",0)->Flags|=LA_UI_SOCKET_LABEL_W;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laUiItem* b=laBeginRow(uil,cl,0,0);
 | 
	
		
			
				|  |  | +    laShowNodeSocket(uil,cl,This,"in_transport",0); b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"in_transport.source")));{
 | 
	
		
			
				|  |  | +        laShowItemFull(uil,cl,This,"transport",0,"text=Transport",0,0)->Expand=1;
 | 
	
		
			
				|  |  | +    }laElse(uil,b2);{
 | 
	
		
			
				|  |  | +        laShowLabel(uil,cl,"Transport",0,0)->Expand=1;
 | 
	
		
			
				|  |  | +    }laEndCondition(uil,b2);
 | 
	
		
			
				|  |  | +    laShowNodeSocket(uil,cl,This,"in_reset",0); b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"in_reset.source")));{
 | 
	
		
			
				|  |  | +        laUiItem* u=laShowItemFull(uil,cl,This,"reset",0,"text=Reset",0,0);u->Expand=1;u->Flags|=LA_UI_FLAGS_MOMENTARY;
 | 
	
		
			
				|  |  | +    }laElse(uil,b2);{
 | 
	
		
			
				|  |  | +        laShowLabel(uil,cl,"Reset",0,0)->Expand=1;
 | 
	
		
			
				|  |  | +    }laEndCondition(uil,b2);
 | 
	
		
			
				|  |  | +    laEndRow(uil,b);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laShowItemFull(uil,cl,This,"target",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void laRebuildSynthGraphs(int current_only){
 | 
	
		
			
				|  |  |      if(MAIN.Audio->CurrentSynth && current_only){
 | 
	
	
		
			
				|  | @@ -586,7 +654,13 @@ int laEvalSynthGraphs(){
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void laaudio_DataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount){
 | 
	
		
			
				|  |  | -    float* out=pOutput; int any=1;
 | 
	
		
			
				|  |  | +    float* out=pOutput; int any=1; int Paused;
 | 
	
		
			
				|  |  | +    laSpinLock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +    Paused = MAIN.Audio->Paused;
 | 
	
		
			
				|  |  | +    laSpinUnlock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(Paused){ memset(out,0,sizeof(float)*LA_SYNTH_PLEN); return; }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      for(int i=0;i<frameCount;i++){
 | 
	
		
			
				|  |  |          if(MAIN.Audio->NextAudioSample>=LA_SYNTH_PLEN){
 | 
	
		
			
				|  |  |              MAIN.Audio->NextAudioSample=0;
 | 
	
	
		
			
				|  | @@ -653,11 +727,11 @@ void laInitMiniAudio(){
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      laRefreshAudioDevices();
 | 
	
		
			
				|  |  | -    la_SelectAudioDevice(MAIN.Audio->AudioDevices.pFirst);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    INITPACKET(MAIN.Audio->AudioSamples);
 | 
	
		
			
				|  |  |      laSpinInit(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +    INITPACKET(MAIN.Audio->AudioSamples);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    la_SelectAudioDevice(MAIN.Audio->AudioDevices.pFirst);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void laDeinitAudio(){
 | 
	
		
			
				|  |  |      laSpinDestroy(&MAIN.Audio->AudioStatusLock);
 | 
	
	
		
			
				|  | @@ -666,6 +740,28 @@ void laDeinitAudio(){
 | 
	
		
			
				|  |  |      ma_context_uninit(&MAIN.Audio->MiniAudioContext);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +void laStartAudio(){
 | 
	
		
			
				|  |  | +    laSpinLock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +    MAIN.Audio->Paused = 0;
 | 
	
		
			
				|  |  | +    laSpinUnlock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +    ma_device_start(&MAIN.Audio->AudioDevice);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void laStopAudio(){
 | 
	
		
			
				|  |  | +    ma_device_start(&MAIN.Audio->AudioDevice);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void laPauseAudio(){
 | 
	
		
			
				|  |  | +    laSpinLock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +    MAIN.Audio->Paused = 1;
 | 
	
		
			
				|  |  | +    laSpinUnlock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void laResetAudio(){
 | 
	
		
			
				|  |  | +    for(laSynth* s=MAIN.Audio->Synths.pFirst;s;s=s->Item.pNext){
 | 
	
		
			
				|  |  | +        laSpinLock(&s->Lock);
 | 
	
		
			
				|  |  | +        s->EvalTime = 0; s->EvalSamples = 0; s->Playing = 0;
 | 
	
		
			
				|  |  | +        laSpinUnlock(&s->Lock);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // Operations
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  int OPINV_laRefreshAudioDevices(laOperator* a, laEvent* e){
 | 
	
	
		
			
				|  | @@ -731,7 +827,7 @@ int OPINV_laSynthPlay(laOperator* a, laEvent* e){
 | 
	
		
			
				|  |  |      ss->Playing=play; if(!play){ ss->EvalSamples=0; ss->EvalTime=0; }
 | 
	
		
			
				|  |  |      laSpinUnlock(&ss->Lock);
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  | -    ma_device_start(&MAIN.Audio->AudioDevice); laNotifyInstanceUsers(ss);
 | 
	
		
			
				|  |  | +    if(play){ laStartAudio(); } laNotifyInstanceUsers(ss);
 | 
	
		
			
				|  |  |      return LA_FINISHED;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -898,7 +994,8 @@ void la_AudioPreFrame(){
 | 
	
		
			
				|  |  |      laSpinLock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  |      anyaudio=MAIN.Audio->AudioAny;
 | 
	
		
			
				|  |  |      laSpinUnlock(&MAIN.Audio->AudioStatusLock);
 | 
	
		
			
				|  |  | -    if(!anyaudio){ ma_device_stop(&MAIN.Audio->AudioDevice); }
 | 
	
		
			
				|  |  | +    //if(anyaudio){ ma_device_start(&MAIN.Audio->AudioDevice); }
 | 
	
		
			
				|  |  | +    //else{ ma_device_stop(&MAIN.Audio->AudioDevice); }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void laInitAudio(){
 | 
	
	
		
			
				|  | @@ -1030,6 +1127,22 @@ void laInitAudio(){
 | 
	
		
			
				|  |  |      laAddEnumItemAs(p,"IDLE","Idle","Trigger idle",0,0);
 | 
	
		
			
				|  |  |      laAddEnumItemAs(p,"TRIG","Trigger","Trigger active",1,0);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pc=laAddPropertyContainer("la_node_synth_driver", "Synth Driver Node", "Control playing status of the synth",0,laui_SynthDriverNode,sizeof(laSynthNodeDriver),lapost_Node,0,1);
 | 
	
		
			
				|  |  | +    LA_PC_IDN_DRIVER=pc; laPropContainerExtraFunctions(pc,0,0,0,0,laui_DefaultNodeOperationsPropUiDefine);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"prev", "Previous","Previous node","la_in_socket",0,0,0,offsetof(laSynthNodeDriver,Prev),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"next", "Next","Next node","la_out_socket",0,0,0,offsetof(laSynthNodeDriver,Next),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"in_transport", "In Transport","Transport input","la_in_socket",0,0,0,offsetof(laSynthNodeDriver,InTransport),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"in_reset", "In Reset","Reset input (trigger on rising edge)","la_in_socket",0,0,0,offsetof(laSynthNodeDriver,InReset),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 | 
	
		
			
				|  |  | +    p=laAddEnumProperty(pc,"transport","Transport","Play or stop the synth playing",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laSynthNodeDriver,iTransport),0,0,0,0,0,0,0,0,0,0);
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"STOPPED","Stopped","Synth is stopped",0,0);
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"PLAYING","Playing","Synth is playing",1,0);
 | 
	
		
			
				|  |  | +    p=laAddEnumProperty(pc,"reset","Reset","Reset Synth",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laSynthNodeDriver,iReset),0,0,0,0,0,0,0,0,0,0);
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"IDLE","Idle","Trigger idle",0,0);
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"TRIG","Trigger","Trigger active",1,0);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"target","Target","Target synth to control","la_synth",0,0,0,offsetof(laSynthNodeDriver,Target),laget_FirstSynth,0,laget_ListNext,0,0,0,0,LA_UDF_REFER);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      pc=laAddPropertyContainer("la_node_synth_quantize", "Quantize Node", "Quantize control voltages",0,laui_QuantizeNode,sizeof(laSynthNodeQuantize),lapost_Node,0,1);
 | 
	
		
			
				|  |  |      LA_PC_IDN_QUANTIZE=pc; laPropContainerExtraFunctions(pc,0,0,0,0,laui_DefaultNodeOperationsPropUiDefine);
 | 
	
		
			
				|  |  |      laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
 | 
	
	
		
			
				|  | @@ -1048,12 +1161,16 @@ void laInitAudio(){
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Sound Output",U'🕪',LA_IDN_OUTPUT, LA_PC_IDN_OUTPUT, IDN_Output, laSynthNodeOutput);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Envelope",U'^',LA_IDN_ENVELOPE, LA_PC_IDN_ENVELOPE, IDN_Envelope, laSynthNodeEnvelope);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Quantize",U'#',LA_IDN_QUANTIZE, LA_PC_IDN_QUANTIZE, IDN_Quantize, laSynthNodeQuantize);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    LA_NODE_CATEGORY_SYNTHESIZER=laAddNodeCategory("OSC",0,LA_RACK_TYPE_AUDIO);
 | 
	
		
			
				|  |  | -    LA_NODE_CATEGORY_SYSTEM_SOUND=laAddNodeCategory("System",0,LA_RACK_TYPE_AUDIO);
 | 
	
		
			
				|  |  | +    LA_IDN_REGISTER("Sound Player",U'⏯',LA_IDN_SYNTH_DRIVER, LA_PC_IDN_DRIVER, IDN_SynthDriver, laSynthNodeDriver);
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    LA_NODE_CATEGORY_SYNTHESIZER=laEnsureNodeCategory("OSC",0,LA_RACK_TYPE_AUDIO);
 | 
	
		
			
				|  |  | +    LA_NODE_CATEGORY_SYSTEM_SOUND=laEnsureNodeCategory("System",0,LA_RACK_TYPE_AUDIO);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_SYNTHESIZER, &LA_IDN_INPUT,&LA_IDN_FM,&LA_IDN_NOISE,&LA_IDN_VCA,&LA_IDN_ENVELOPE,&LA_IDN_QUANTIZE,&LA_IDN_SCOPE,0);
 | 
	
		
			
				|  |  |      laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_SYSTEM_SOUND, &LA_IDN_OUTPUT,0);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    LA_NODE_CATEGORY_DRIVER=laEnsureNodeCategory("Driver",0,LA_RACK_TYPE_DRIVER);
 | 
	
		
			
				|  |  | +    laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_DRIVER, &LA_IDN_SYNTH_DRIVER,0);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void laAudioDummy(){
 |