*/}}
Browse Source

Stereo audio and backend selection

YimingWu 3 months ago
parent
commit
e1549ee0e8
5 changed files with 99 additions and 30 deletions
  1. 66 25
      la_audio.c
  2. 10 3
      la_interface.h
  3. 16 0
      resources/la_properties.c
  4. 5 1
      resources/la_templates.c
  5. 2 1
      resources/la_translations.c

+ 66 - 25
la_audio.c

@@ -107,7 +107,7 @@ int IDN_InputEval(laSynthNodeInput* n){
     }
     if(ss->EvalSamples==0){ n->rTrigger[0] = 10; }else{ n->rTrigger[0]=0; }
     n->OutTime->Data=n->rTime; n->OutTrigger->Data=n->rTrigger; n->OutTime->ArrLen=LA_SYNTH_PLEN; n->OutTrigger->ArrLen=LA_SYNTH_PLEN;
-    n->OutL->Data=MAIN.Audio->InputSamples; n->OutR->Data=MAIN.Audio->InputSamples; n->OutL->ArrLen=LA_SYNTH_PLEN; n->OutR->ArrLen=LA_SYNTH_PLEN;
+    n->OutL->Data=MAIN.Audio->InputSamplesL; n->OutR->Data=MAIN.Audio->InputSamplesR; n->OutL->ArrLen=LA_SYNTH_PLEN; n->OutR->ArrLen=LA_SYNTH_PLEN;
     return 1;
 }
 void IDN_InputCopy(laSynthNodeInput* new, laSynthNodeInput* old, int DoRematch){
@@ -423,14 +423,18 @@ void IDN_OutputInit(laSynthNodeOutput* n, int NoCreate){
     if(NoCreate){
         laAudioChannel* outchannel = laGetAudioChannel(SSTR(n->SendName));
         if(outchannel) memAssignRef(n,&n->Send,outchannel);
-    return; }
-    n->In=laCreateInSocket("IN",0); strSafeSet(&n->Base.Name,"Output");
+        if(!n->InL) n->InL=laCreateInSocket("L/M",0);
+        if(!n->InR) n->InR=laCreateInSocket("R",0);
+        return;
+    }
+    n->InL=laCreateInSocket("L/M",0); n->InR=laCreateInSocket("R",0); strSafeSet(&n->Base.Name,"Output");
 }
 void IDN_OutputDestroy(laSynthNodeOutput* n){
-    laDestroyInSocket(n->In); strSafeDestroy(&n->Base.Name);
+    laDestroyInSocket(n->InL); laDestroyInSocket(n->InR); strSafeDestroy(&n->Base.Name);
 }
 int IDN_OutputVisit(laSynthNodeOutput* n, laNodeVisitInfo* vi){
-    if(LA_SRC_AND_PARENT(n->In)){ laBaseNode*bn=n->In->Source->Parent; LA_VISIT_NODE(bn,vi); }
+    if(LA_SRC_AND_PARENT(n->InL)){ laBaseNode*bn=n->InL->Source->Parent; LA_VISIT_NODE(bn,vi); }
+    if(LA_SRC_AND_PARENT(n->InR)){ laBaseNode*bn=n->InR->Source->Parent; LA_VISIT_NODE(bn,vi); }
     LA_ADD_THIS_NODE(n,vi);
     laAudioChannel* outchannel = laGetAudioChannel(SSTR(n->SendName));
     memAssignRef(n,&n->Send,outchannel);
@@ -438,15 +442,21 @@ int IDN_OutputVisit(laSynthNodeOutput* n, laNodeVisitInfo* vi){
 }
 int IDN_OutputEval(laSynthNodeOutput* n){
     if(!n->Send)return 1;
-    real* input; INPUTPACKET(input,n->In); if(!input) return 1;
+    real* inputL; INPUTPACKET(inputL,n->InL); real* inputR; INPUTPACKET(inputR,n->InR); if(!inputL && !inputR) return 1;
     real Samples[LA_SYNTH_PLEN];
-    for(int i=0;i<LA_SYNTH_PLEN;i++){
-        n->Send->Samples[i] += input[i];
+    if(inputL){
+        for(int i=0;i<LA_SYNTH_PLEN;i++){ n->Send->SamplesL[i] += inputL[i]; }
+        if(n->Send){ n->Send->IsMono=1; }
+    }
+    if(inputR){
+        for(int i=0;i<LA_SYNTH_PLEN;i++){ n->Send->SamplesR[i] += inputR[i]; }
+        if(n->Send){ n->Send->IsMono=0; }
     }
     return 1;
 }
 void IDN_OutputCopy(laSynthNodeOutput* new, laSynthNodeOutput* old, int DoRematch){
-    if(DoRematch){ LA_IDN_NEW_LINK(In) return; }
+    if(DoRematch){ LA_IDN_NEW_LINK(InL) return; }
+    if(DoRematch){ LA_IDN_NEW_LINK(InR) return; }
     strSafeSet(&new->SendName,SSTR(old->SendName)); new->Send=old->Send;
 }
 void laui_OutputNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
@@ -455,7 +465,8 @@ void laui_OutputNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColum
     laColumn* cl,*cr; laSplitColumn(uil,c,0.3); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
 
     laUiItem* b=laBeginRow(uil,c,0,0);
-    laShowNodeSocket(uil,c,This,"in",0)->Flags|=LA_UI_SOCKET_LABEL_E;
+    laShowNodeSocket(uil,c,This,"in_l",0)->Flags|=LA_UI_SOCKET_LABEL_E;
+    laShowNodeSocket(uil,c,This,"in_r",0)->Flags|=LA_UI_SOCKET_LABEL_E;
     laShowLabel(uil,c,"🕪",0,0);
     laShowItemFull(uil,c,This,"send_channel_selector",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0)->Flags|=LA_UI_COLLECTION_SIMPLE_SELECTOR;
     laShowItem(uil,c,This,"send_channel_name")->Expand = 1;
@@ -807,9 +818,11 @@ int laEvalSingleSynth(laSynth* ss){
 }
 int laEvalSynthGraphs(){
     laSynth* ss; int any=0;
-    memset(MAIN.Audio->OutputSamples,0,sizeof(real)*LA_SYNTH_PLEN);
+    memset(MAIN.Audio->OutputSamplesL,0,sizeof(real)*LA_SYNTH_PLEN);
+    memset(MAIN.Audio->OutputSamplesR,0,sizeof(real)*LA_SYNTH_PLEN);
     for(laAudioChannel* ac=MAIN.Audio->Channels.pFirst;ac;ac=ac->Item.pNext){
-        memset(ac->Samples,0,sizeof(real)*LA_SYNTH_PLEN);
+        memset(ac->SamplesL,0,sizeof(real)*LA_SYNTH_PLEN);
+        memset(ac->SamplesR,0,sizeof(real)*LA_SYNTH_PLEN);
     }
 
     for(ss=MAIN.Audio->Synths.pFirst;ss;ss=ss->Item.pNext){
@@ -817,8 +830,10 @@ int laEvalSynthGraphs(){
     }
 
     for(laAudioChannel* ac=MAIN.Audio->Channels.pFirst;ac;ac=ac->Item.pNext){
+        real *RSamples = ac->IsMono?ac->SamplesL:ac->SamplesR;
         for(int i=0;i<LA_SYNTH_PLEN;i++){
-            MAIN.Audio->OutputSamples[i] += ac->Samples[i]*(ac->Volume/10);
+            MAIN.Audio->OutputSamplesL[i] += ac->SamplesL[i]*(ac->Volume/10);
+            MAIN.Audio->OutputSamplesR[i] += RSamples[i]*(ac->Volume/10);
         }
     }
     return any;
@@ -831,7 +846,8 @@ void laaudio_DataCallback(ma_device* pDevice, void* pOutput, const void* pInput,
     laSpinUnlock(&MAIN.Audio->AudioStatusLock);
     
     if(Paused){ memset(out,0,sizeof(float)*LA_SYNTH_PLEN);
-        memset(MAIN.Audio->InputSamples,0,sizeof(float)*LA_SYNTH_PLEN); return;
+        memset(MAIN.Audio->InputSamplesL,0,sizeof(float)*LA_SYNTH_PLEN);
+        memset(MAIN.Audio->InputSamplesR,0,sizeof(float)*LA_SYNTH_PLEN); return;
     }
     
     for(int i=0;i<frameCount;i++){
@@ -839,8 +855,11 @@ void laaudio_DataCallback(ma_device* pDevice, void* pOutput, const void* pInput,
             MAIN.Audio->NextAudioSample=0;
             if(laEvalSynthGraphs()){ any=1; }
         }
-        out[i]=MAIN.Audio->OutputSamples[MAIN.Audio->NextAudioSample]/10;
-        MAIN.Audio->InputSamples[MAIN.Audio->NextAudioSample] = in[i] * 10;
+        int s=i*2;
+        out[s]=MAIN.Audio->OutputSamplesL[MAIN.Audio->NextAudioSample]/10;
+        out[s+1]=MAIN.Audio->OutputSamplesR[MAIN.Audio->NextAudioSample]/10;
+        MAIN.Audio->InputSamplesL[MAIN.Audio->NextAudioSample] = in[s] * 10;
+        MAIN.Audio->InputSamplesR[MAIN.Audio->NextAudioSample] = in[s+1] * 10;
         MAIN.Audio->NextAudioSample++;
     }
 }
@@ -855,14 +874,14 @@ void la_SelectAudioDevice(laAudioDevice* ad){
     ma_device_config config = ma_device_config_init(ma_device_type_duplex);
     config.capture.pDeviceID = ad->MiniAudioID;
     config.capture.format    = ma_format_f32;
-    config.capture.channels  = 1;
+    config.capture.channels  = 2;
     config.playback.pDeviceID= ad->MiniAudioID;
     config.playback.format   = ma_format_f32;
-    config.playback.channels = 1;
+    config.playback.channels = 2;
     config.sampleRate        = SampleRate;
     config.dataCallback      = laaudio_DataCallback;
     config.pUserData         = 0;
-    if (ma_device_init(NULL, &config, &MAIN.Audio->AudioDevice) != MA_SUCCESS){ return; }
+    if (ma_device_init(&MAIN.Audio->MiniAudioContext, &config, &MAIN.Audio->AudioDevice) != MA_SUCCESS){ return; }
     MAIN.Audio->AudioSampleRate=SampleRate;
     MAIN.Audio->NextAudioSample=LA_SYNTH_PLEN;
     MAIN.Audio->AudioFrameInterval=1.0f/SampleRate;
@@ -892,6 +911,7 @@ void laRefreshAudioDevices(){
 }
 
 void laInitMiniAudio(){
+    MAIN.Audio->MiniAudioBackend=ma_backend_null;
 
     if (ma_context_init(NULL, 0, NULL, &MAIN.Audio->MiniAudioContext) != MA_SUCCESS) {
         printf("Can't init miniaudio context\n");
@@ -900,14 +920,18 @@ void laInitMiniAudio(){
     laRefreshAudioDevices();
 
     laSpinInit(&MAIN.Audio->AudioStatusLock);
-    INITPACKET(MAIN.Audio->InputSamples);
-    INITPACKET(MAIN.Audio->OutputSamples);
+    INITPACKET(MAIN.Audio->InputSamplesL);
+    INITPACKET(MAIN.Audio->InputSamplesR);
+    INITPACKET(MAIN.Audio->OutputSamplesL);
+    INITPACKET(MAIN.Audio->OutputSamplesR);
 
     la_SelectAudioDevice(MAIN.Audio->AudioDevices.pFirst);
 }
 void laDeinitAudio(){
-    memFree(MAIN.Audio->InputSamples);
-    memFree(MAIN.Audio->OutputSamples);
+    memFree(MAIN.Audio->InputSamplesL);
+    memFree(MAIN.Audio->InputSamplesR);
+    memFree(MAIN.Audio->OutputSamplesR);
+    memFree(MAIN.Audio->OutputSamplesL);
     laSpinDestroy(&MAIN.Audio->AudioStatusLock);
     ma_device_stop(&MAIN.Audio->AudioDevice);
     ma_device_uninit(&MAIN.Audio->AudioDevice);
@@ -921,7 +945,7 @@ void laStartAudio(){
     ma_device_start(&MAIN.Audio->AudioDevice);
 }
 void laStopAudio(){
-    ma_device_start(&MAIN.Audio->AudioDevice);
+    ma_device_stop(&MAIN.Audio->AudioDevice);
 }
 void laPauseAudio(){
     laSpinLock(&MAIN.Audio->AudioStatusLock);
@@ -1221,6 +1245,22 @@ void laset_SynthTriggerTrigger(laSynthNodeTrigger* n, int trig){
     if(trig){ laSynthTriggerNew(n->Target); } n->iTrigger=trig;
 }
 
+void laset_MiniAudioBackend(void* unused, int backend){
+    laStopAudio();
+    ma_device_uninit(&MAIN.Audio->AudioDevice);
+    ma_context_uninit(&MAIN.Audio->MiniAudioContext);
+
+    MAIN.Audio->MiniAudioBackend = backend;
+    ma_backend _backends[1]; _backends[0] = MAIN.Audio->MiniAudioBackend; ma_backend* backends=_backends;
+    int numback=1;
+    if(backend==ma_backend_null){ numback=0; backends=0; }
+    if (ma_context_init(backends, numback, NULL, &MAIN.Audio->MiniAudioContext) != MA_SUCCESS) {
+        printf("Can't init miniaudio context\n"); MAIN.Audio->MiniAudioBackend=ma_backend_null;
+    }
+
+    laRefreshAudioDevices();
+    la_SelectAudioDevice(MAIN.Audio->AudioDevices.pFirst);
+}
 
 void la_AudioPreFrame(){
     if(MAIN.GraphNeedsRebuild){ laRebuildSynthGraphs(1); }
@@ -1351,7 +1391,8 @@ void laInitAudio(){
     pc=laAddPropertyContainer("la_node_synth_output", "Output Node", "Sound output to system",0,laui_OutputNode,sizeof(laSynthNodeOutput),lapost_Node,0,1);
     LA_PC_IDN_OUTPUT=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,"in", "Input","Input sound","la_in_socket",0,0,0,offsetof(laSynthNodeOutput,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"in_l", "Input Left","Input sound left channel","la_in_socket",0,0,0,offsetof(laSynthNodeOutput,InL),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"in_r", "Input Right","Input sound right channel","la_in_socket",0,0,0,offsetof(laSynthNodeOutput,InR),0,0,0,0,0,0,0,LA_UDF_SINGLE);
     laAddStringProperty(pc,"send_channel_name","Channel Name","Channel name of this send target",0,0,0,0,1,offsetof(laSynthNodeOutput,SendName),0,0,laset_OutputSendName,0,0);
     laAddSubGroup(pc,"send_channel_selector","Send","Send to channel","la_audio_channel",0,0,0,-1,laget_FirstAudioChannel,0,laget_ListNext,laset_OutputSendChannel,0,0,0,LA_UDF_REFER|LA_UDF_IGNORE);
 

+ 10 - 3
la_interface.h

@@ -1971,7 +1971,7 @@ NEED_STRUCTURE(laAudioChannel);
 
 STRUCTURE(laSynthNodeOutput){
     laBaseNode Base;
-    laNodeInSocket* In;
+    laNodeInSocket* InL,*InR; int IsMono;
     laSafeString* SendName;
     /* Runtime */
     laAudioChannel* Send;
@@ -2019,7 +2019,9 @@ STRUCTURE(laAudioChannel){
     laSafeString* Name;
     real Volume;
     laSafeString* OutputChannel;
-    real Samples[LA_SYNTH_PLEN];
+    real SamplesL[LA_SYNTH_PLEN];
+    real SamplesR[LA_SYNTH_PLEN];
+    int IsMono;
 };
 
 STRUCTURE(laAudioDevice){
@@ -2039,13 +2041,16 @@ STRUCTURE(laAudio){
     laListHandle PolyLaterTrigger;
 
     SYSLOCK AudioStatusLock;
+    ma_backend MiniAudioBackend;
     ma_context MiniAudioContext;
     ma_device AudioDevice;
     laAudioDevice* UsingDevice;
     laListHandle AudioDevices;
     laSynth* AudioEvalSynth;
     real AudioFrameInterval;
-    real* InputSamples,*OutputSamples; int NextAudioSample,AudioSampleRate;//,NextInputSample;
+    real* InputSamplesL,*InputSamplesR;
+    real *OutputSamplesL,*OutputSamplesR;
+    int NextAudioSample,AudioSampleRate;//,NextInputSample;
 };
 
 
@@ -2061,6 +2066,8 @@ laAudioChannel* laGetAudioChannel(char* Name);
 
 void la_AudioPreFrame();
 
+void laset_MiniAudioBackend(void* unused, int backend);
+
 void laRebuildSynthGraphs(int current_only);
 void laInitAudio();
 void laDeinitAudio();

+ 16 - 0
resources/la_properties.c

@@ -1595,6 +1595,22 @@ void la_RegisterInternalProps(){
                 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);
+                ep=laAddEnumProperty(p,"backend","Backend","Audio backend",0,0,0,ma_backend_null,0,offsetof(laAudio,MiniAudioBackend),0,laset_MiniAudioBackend,0,0,0,0,0,0,0,0);
+                laAddEnumItemAs(ep,"AUTO","Auto","Automatic backend selection",ma_backend_null,0);
+                laAddEnumItemAs(ep,"JACK","Jack","Jack backend",ma_backend_jack,0);
+#ifdef LA_LINUX
+                laAddEnumItemAs(ep,"PULSEAUDIO","Pulseaudio","Pulseaudio backend",ma_backend_pulseaudio,0);
+                laAddEnumItemAs(ep,"ALSA","Alsa","Alsa backend",ma_backend_alsa,0);
+#endif
+#ifdef _WIN32
+                laAddEnumItemAs(ep,"WASAPI","WASAPI"," WASAPI backend",ma_backend_wasapi,0);
+                laAddEnumItemAs(ep,"DIRECTSOUND","DirectSound","DirectSound backend",ma_backend_dsound, 0);
+                laAddEnumItemAs(ep,"WINMM","WinMM","WinMM backend",ma_backend_winmm,0);
+#endif
+#ifdef LAGUI_ANDROID
+                laAddEnumItemAs(ep,"AAUDIO","AAudio"," AAudio backend",ma_backend_aaudio,0);
+                laAddEnumItemAs(ep,"OpenSL ES","OpenSL ES","OpenSL ES backend",ma_backend_opensl,0);
+#endif
             }
         }
 

+ 5 - 1
resources/la_templates.c

@@ -1387,7 +1387,11 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
             muil = laAddTabPage(bracket, "Audio");{
                 mc = laFirstColumn(muil); laSplitColumn(muil, mc, 0.5);
                 mcl = laLeftColumn(mc, 0); mcr = laRightColumn(mc, 0);
-                laShowLabel(muil,mc,"Audio Device:",0,0);
+                b=laBeginRow(muil,mcl,0,0);
+                laShowLabel(muil,mcl,"Backend:",0,0);
+                laShowItem(muil,mcl,0,"la.audio.backend")->Expand=1;
+                laEndRow(muil,b);
+                laShowLabel(muil,mc,"Device:",0,0);
                 laShowItemFull(muil,mcl,0,"la.audio.current_audio_device",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0);
                 laShowItem(muil,mcr,0,"LA_refresh_audio_devices");
             }

+ 2 - 1
resources/la_translations.c

@@ -231,7 +231,8 @@ static const char *entries[]={
 "Enable OpenGL Debug","启用 OpenGL 调试",
 "Synthesizers","合成器",
 "Loc:","位置:",
-"Audio Device:","音频设备:",
+"Backend:","后端:",
+"Device:","设备:",
 "OpenGL Debugging:","OpenGL 调试:",
 "Show Details","显示详细",
 "Scale","缩放",