*/}}
Browse Source

Audio player

YimingWu 3 months ago
parent
commit
68d5f3f71e
6 changed files with 167 additions and 24 deletions
  1. 125 8
      la_audio.c
  2. 5 0
      la_data.c
  3. 14 1
      la_interface.h
  4. 17 10
      resources/la_nodes_basic.c
  5. 2 2
      resources/la_properties.c
  6. 4 3
      resources/la_tns_drivers.c

+ 125 - 8
la_audio.c

@@ -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(){

+ 5 - 0
la_data.c

@@ -3522,6 +3522,9 @@ int laExtractUDF(laUDF *udf, laManagedUDF* mUDF, int Mode, laListHandle *Parent)
     int IsPart;
     laUDFContentNode *ucni = Parent;
 
+    ma_device_stop(&MAIN.Audio->AudioDevice);
+    laSpinLock(&MAIN.Audio->AudioStatusLock);
+
     MAIN.IsReadingUDF = 1;
 
     la_ReadBuffer(udf, sizeof(LA_UDF_IDENTIFIER) - 1, Identifier);
@@ -3583,6 +3586,8 @@ int laExtractUDF(laUDF *udf, laManagedUDF* mUDF, int Mode, laListHandle *Parent)
 
     MAIN.IsReadingUDF = 0;
 
+    laSpinUnlock(&MAIN.Audio->AudioStatusLock);
+
     return EStatus;
 }
 

+ 14 - 1
la_interface.h

@@ -1900,6 +1900,13 @@ STRUCTURE(laSynthNodeQuantize){
     int EnabledBits[12];
 };
 
+STRUCTURE(laSynthNodeDriver){
+    laBaseNode Base;
+    laNodeInSocket* Prev; laNodeOutSocket* Next;
+    laNodeInSocket* InTransport; int iTransport;
+    laNodeInSocket* InReset; int iReset,AlreadyReset; // on rising edge
+    laSynth* Target;
+};
 
 #include "miniaudio.h"
 
@@ -1919,6 +1926,7 @@ STRUCTURE(laAudioDevice){
 
 STRUCTURE(laAudio){
     int AudioAny;
+    int Paused;
     
     laListHandle Synths;
     laSynth* CurrentSynth;
@@ -1953,6 +1961,11 @@ void laRebuildSynthGraphs(int current_only);
 void laInitAudio();
 void laDeinitAudio();
 
+void laStartAudio();
+void laPauseAudio();
+void laStopAudio();
+void laResetAudio();
+
 #define LA_INPUT_CONTROLLER_NODE_MODE_BTN 0
 #define LA_INPUT_CONTROLLER_NODE_MODE_AXIS 1
 
@@ -2426,7 +2439,7 @@ laNodeOutSocket* laCreateOutSocket(void* NodeParentOptional, char* label, int Da
 laNodeInSocket* laCreateInSocket(char* label, int DataType);
 void laDestroyInSocket(laNodeInSocket* s);
 void laDestroyOutSocket(laNodeOutSocket* s);
-laNodeCategory* laAddNodeCategory(char* Name,laUiDefineFunc* Ui,int ForRackTypes);
+laNodeCategory* laEnsureNodeCategory(char* Name,laUiDefineFunc* Ui,int ForRackTypes);
 void laNodeCategoryAddNodeTypes(laNodeCategory* nc, ...);
 void laDestroyRack(laNodeRack* rr);
 laRackPage* laDuplicateRackPage(laRackPage* new_optional, laRackPage* from);

+ 17 - 10
resources/la_nodes_basic.c

@@ -1167,6 +1167,13 @@ void la_RegisterBasicNodes(){
     laOperatorType *at;
     laEnumProp *ep;
 
+    LA_NODE_CATEGORY_INPUT=laEnsureNodeCategory("Input",0,LA_RACK_TYPE_INPUT);
+    LA_NODE_CATEGORY_MATH=laEnsureNodeCategory("Math",0,LA_RACK_TYPE_ALL);
+    LA_NODE_CATEGORY_COLOR=laEnsureNodeCategory("Color",0,LA_RACK_TYPE_ALL);
+    LA_NODE_CATEGORY_ROUTE=laEnsureNodeCategory("Route",0,LA_RACK_TYPE_ALL);
+    LA_NODE_CATEGORY_AUX=laEnsureNodeCategory("Auxiliary",0,LA_RACK_TYPE_ALL);
+    LA_NODE_CATEGORY_DRIVER=laEnsureNodeCategory("Driver",0,LA_RACK_TYPE_DRIVER);
+
     laCreateOperatorType("LA_add_rack", "Add Rack", "Add a rack for nodes", 0,0,0,OPINV_AddNodesRack,0,'+',0);
     at=laCreateOperatorType("OPINV_AddNode", "Add Node", "Add a node to the rack",0,0,0,OPINV_AddNode,OPMOD_AddNode,'+',0);
     at->UiDefine=laui_AddNode;
@@ -1456,12 +1463,6 @@ typedef laMathNode laSmallMathNode;
     LA_IDN_REGISTER("OKHSL to RGB",0,LA_IDN_OKHSL2RGB,LA_PC_IDN_OKHSL2RGB, IDN_OKHSL2RGB, laOKHSL2RGBNode);
     LA_IDN_REGISTER("Loop",0,LA_IDN_LOOP,LA_PC_IDN_LOOP, IDN_Loop, laLoopNode);
     LA_IDN_REGISTER("Loop Index",0,LA_IDN_LOOP_INDEX,LA_PC_IDN_LOOP_INDEX, IDN_LoopIndex, laLoopIndexNode);
-   
-    LA_NODE_CATEGORY_INPUT=laAddNodeCategory("Input",0,LA_RACK_TYPE_INPUT);
-    LA_NODE_CATEGORY_MATH=laAddNodeCategory("Math",0,LA_RACK_TYPE_ALL);
-    LA_NODE_CATEGORY_COLOR=laAddNodeCategory("Color",0,LA_RACK_TYPE_ALL);
-    LA_NODE_CATEGORY_ROUTE=laAddNodeCategory("Route",0,LA_RACK_TYPE_ALL);
-    LA_NODE_CATEGORY_AUX=laAddNodeCategory("Auxiliary",0,LA_RACK_TYPE_ALL);
 
     laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_INPUT, &LA_IDN_CONTROLLER,0);
     laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_MATH, &LA_IDN_MATH, &LA_IDN_SMALL_MATH,&LA_IDN_VECTOR_MATH,&LA_IDN_MAPPER,&LA_IDN_RANDOM,&LA_IDN_MATRIX, &LA_IDN_VALUES,0);
@@ -1541,10 +1542,16 @@ laNodeInSocket* laCreateInSocket(char* label, int DataType){
 void laDestroyInSocket(laNodeInSocket* s){ strSafeDestroy(&s->Label); memLeave(s); }
 void laDestroyOutSocket(laNodeOutSocket* s){ strSafeDestroy(&s->Label); memLeave(s); }
 
-laNodeCategory* laAddNodeCategory(char* Name,laUiDefineFunc* Ui,int ForRackTypes){
-    laNodeCategory* nc=memAcquire(sizeof(laNodeCategory));
-    lstAppendItem(&MAIN.NodeCategories,nc);
-    strSafeSet(&nc->Name, Name); nc->Ui=Ui;  nc->For=ForRackTypes;
+laNodeCategory* laEnsureNodeCategory(char* Name,laUiDefineFunc* Ui,int ForRackTypes){
+    laNodeCategory* nc=0;
+    for(laNodeCategory* c=MAIN.NodeCategories.pFirst;c;c=c->Item.pNext){
+        if(strSame(SSTR(c->Name),Name)){ nc=c; break; }
+    }
+    if(!nc){
+        nc=memAcquire(sizeof(laNodeCategory));
+        lstAppendItem(&MAIN.NodeCategories,nc);
+        strSafeSet(&nc->Name, Name); nc->Ui=Ui;  nc->For=ForRackTypes;
+    }
     return nc;
 }
 void laNodeCategoryAddNodeTypes(laNodeCategory* nc, ...){

+ 2 - 2
resources/la_properties.c

@@ -1579,8 +1579,8 @@ void la_RegisterInternalProps(){
                 laAddSubGroup(p,"synths","Synthesizers","Synthesizers","la_synth",0,0,0,-1,0,0,0,0,0,0,offsetof(laAudio,Synths),0);
                 sp=laAddSubGroup(p, "current_synth", "Current Synthesizer", "Current synthesizer","la_synth",0,0,0,offsetof(laAudio,CurrentSynth),laget_FirstSynth,0,laget_ListNext,laset_CurrentSynth,0,0,0,LA_UDF_REFER);
                 laSubGroupDetachable(sp,laget_FirstSynth,laget_ListNext);
-                laAddSubGroup(p,"audio_devices","Audio Devices","Enumerated audio devices","la_audio_device",0,0,0,-1,0,0,0,0,0,0,offsetof(laAudio,AudioDevices),0);
-                laAddSubGroup(p, "current_audio_device", "Current Audio Device", "Current audio device","la_audio_device",0,0,0,offsetof(laAudio,UsingDevice),laget_FirstAudioDevice,0,laget_ListNext,laset_CurrentAudioDevice,0,0,0,LA_UDF_REFER);
+                laAddSubGroup(p,"audio_devices","Audio Devices","Enumerated audio devices","la_audio_device",0,0,0,-1,0,0,0,0,0,0,offsetof(laAudio,AudioDevices),LA_UDF_IGNORE);
+                laAddSubGroup(p, "current_audio_device", "Current Audio Device", "Current audio device","la_audio_device",0,0,0,offsetof(laAudio,UsingDevice),laget_FirstAudioDevice,0,laget_ListNext,laset_CurrentAudioDevice,0,0,0,LA_UDF_REFER|LA_UDF_IGNORE);
                 laAddSubGroup(p,"channels","Channels","Audio channels","la_audio_channel",0,0,0,-1,0,0,0,0,0,0,offsetof(laAudio,Channels),0);
             }
         }

+ 4 - 3
resources/la_tns_drivers.c

@@ -105,7 +105,7 @@ int IDN_MakeTransformEval(tnsMakeTransformNode* n){
 }
 void IDN_MakeTransformCopy(tnsMakeTransformNode* new, tnsMakeTransformNode* old, int DoRematch){
     if(DoRematch){ LA_IDN_NEW_LINK(Loc) LA_IDN_NEW_LINK(Rot) LA_IDN_NEW_LINK(Sca) LA_IDN_NEW_LINK(Angle) return; }
-    old->Out->Duplicated=new->Out; new->UseSca=old->UseSca; new->UseAngle=old->UseAngle;
+    LA_IDN_OLD_DUPL(Out); new->UseSca=old->UseSca; new->UseAngle=old->UseAngle;
     tnsVectorSet3v(new->UseLoc,old->UseLoc); tnsVectorSet3v(new->UseRot,old->UseRot);
 }
 void tnsui_MakeTransformNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
@@ -178,8 +178,9 @@ int IDN_ActionPlayerEval(tnsActionPlayerNode* n){
 }
 void IDN_ActionPlayerCopy(tnsActionPlayerNode* new, tnsActionPlayerNode* old, int DoRematch){
     if(DoRematch){ LA_IDN_NEW_LINK(Prev) return; }
-    old->Next->Duplicated=new->Next; new->Time=old->Time; new->Transport=old->Transport; new->Detached=old->Detached;
+    LA_IDN_OLD_DUPL(Next); new->Time=old->Time; new->Transport=old->Transport; new->Detached=old->Detached;
     new->iDetached = old->iDetached; new->iTransport = old->iTransport;
+    memAssignRef(new,&new->Action,old->Action);
 }
 void tnsui_ActionPlayerNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil),*cl,*cr,*cll,*clr;
@@ -276,7 +277,7 @@ void tns_RegisterNodes(){
         ->UiDefine=laui_RemoveDriverPage;
     laCreateOperatorType("LA_driver_rebuild", "Rebuild Drivers", "Rebuild drivers for evaluation",0,0,0,OPINV_RebuildDrivers,0,U'⭮',0);
 
-    LA_NODE_CATEGORY_DRIVER=laAddNodeCategory("Driver",0,LA_RACK_TYPE_DRIVER);
+    LA_NODE_CATEGORY_DRIVER=laEnsureNodeCategory("Driver",0,LA_RACK_TYPE_DRIVER);
 
     if(!MAIN.InitArgs.HasWorldObjects) return;