diff --git a/Classes/Mosca.sc b/Classes/Mosca.sc index cbd952b..f88d154 100644 --- a/Classes/Mosca.sc +++ b/Classes/Mosca.sc @@ -274,8 +274,8 @@ Mosca : MoscaBase ossiaPlay.set_(false); }); - if (gui.isNil) - { // when there is no gui, Automation callback does not work, + if (mainWindow.isNil) + { // when there is no mainWindow, Automation callback does not work, // so here we monitor when the transport reaches end if (control.get.now > dur) diff --git a/Classes/MoscaBase.sc b/Classes/MoscaBase.sc index 2d515a7..8f2cb16 100644 --- a/Classes/MoscaBase.sc +++ b/Classes/MoscaBase.sc @@ -1,6 +1,6 @@ MoscaBase // acts as the public interface { - var dur, servIP = "127.0.0.1"; + var <>servPortRemote = 9997; + var <>servPortLocal = 9996; + //server memory parameters + var <>memSize = 16384; + var <>memBlockSize = 64; + //serv audio parameters + var <>audioChannels = 1068; + var <>audioWireBuffer = 64; + var <>audioInputs = 2; + var <>audioOutputs = 2; + //audio spatialisation parameters + var <>spatOrder = 1; + var <>spatSpeakers = nil; + var <>spatSub = nil; + var <>spatOut = 0; + var <>spatRirBank = nil; + //mosca specific + var <>moscaSources = 4; + var <>moscaDuration = 10; + var <>path = "~/.config/Mosca/"; + var <>file = "config.cfg"; + var <>defaultSpeakerCfgFile = "default_setup.csv"; + + *new{ + ^super.new.ctr(); + } + + ctr{ + spatSpeakers = List[]; + spatSub = List[]; + } + autoConfig{ + if(File.exists((path++file).standardizePath) == false){ + if(File.exists(path.standardizePath)==false) + { + "No configuration directory found - creating a directory".postln; + File.mkdir(path.standardizePath); + }; + "No configuration file found - creating a file".postln; + this.saveConfig(); + } + {//else + "Found Configuration File. Loading".postln; + this.loadConfig(); + }; + } + *prReadProp{ + arg reader,prop; + var res = nil; + ("Reading Prop"+prop).postln; + res = reader.getLine(); + if(res.contains(prop)){ + res = res.split($=); + if(res.size >= 2) + { + res = res[1].stripWhiteSpace(); + } + }; + res.postln; + ^res; + } + *prReadInteger{ + arg reader,prop; + var res = MoscaConfig.prReadProp(reader,prop); + if(res==nil){ + res = 0; + } + {//else + res = res.asInteger; + }; + ^res; + } + + loadConfig{ + var reader,fileSize; + var tmp; + // var l,data,currentSection; //TODO implement smarter file parsing. + // data = Dictionary(); + reader = File((path++file).standardizePath,"r"); + if(reader.getLine().contains("[Address]")){ + servIP = MoscaConfig.prReadProp(reader,"ip"); + servPortRemote = MoscaConfig.prReadInteger(reader,"remotePort"); + servPortLocal = MoscaConfig.prReadInteger(reader,"localPort"); + }; + if(reader.getLine().contains("[Memory]")){ + memSize = MoscaConfig.prReadInteger(reader,"size"); + memBlockSize = MoscaConfig.prReadInteger(reader,"blockSize"); + }; + if(reader.getLine().contains("[Audio]")) + { + audioChannels = MoscaConfig.prReadInteger(reader,"channels"); + audioWireBuffer = MoscaConfig.prReadInteger(reader,"wirebuffers"); + audioInputs = MoscaConfig.prReadInteger(reader,"inputs"); + audioOutputs = MoscaConfig.prReadInteger(reader,"outputs"); + }; + if(reader.getLine().contains("[Spatialisation]")){ + spatOrder = MoscaConfig.prReadInteger(reader,"order"); + defaultSpeakerCfgFile = MoscaConfig.prReadProp(reader,"speakerConfig"); + this.loadSpeakerSetup(defaultSpeakerCfgFile); + spatSub = MoscaConfig.prReadProp(reader,"sub").split($\ ).collect({arg item,i;if(item.size!=0){item.asInteger};}); + spatSub = spatSub.select({arg item,i; item.notNil}).asList; + spatOut = MoscaConfig.prReadInteger(reader,"out"); + spatRirBank = MoscaConfig.prReadProp(reader,"rirBank"); + }; + + if(reader.getLine().contains("[Mosca]")){ + moscaSources = MoscaConfig.prReadInteger(reader,"sources"); + moscaDuration = MoscaConfig.prReadInteger(reader,"duration"); + }; + reader.close(); + } + + saveConfig{ + var writer; + // var listWriter = {arg a;var r = String.new.ccatList(a);r.removeAt(0);r;}; + writer = File(((path++file).standardizePath),"w"); + if(writer.isOpen) + { + ("Saving Config to"+(path++file).standardizePath).postln; + writer.write("[Address]\n"); + writer.write("ip ="+servIP++"\n"); + writer.write("remotePort ="+servPortRemote++"\n"); + writer.write("localPort ="+servPortLocal++"\n"); + writer.write("[Memory]\n"); + writer.write("size ="+memSize++"\n"); + writer.write("blockSize ="+memBlockSize++"\n"); + writer.write("[Audio]\n"); + writer.write("channels ="+audioChannels+"\n"); + writer.write("wireBuffers ="+audioWireBuffer++"\n"); + writer.write("inputs ="+audioInputs++"\n"); + writer.write("outputs ="+audioOutputs++"\n"); + writer.write("[Spatialisation]\n"); + writer.write("order ="+spatOrder++"\n"); + writer.write("speakerConfig ="+(path++defaultSpeakerCfgFile).standardizePath++"\n"); + if(spatSpeakers.size > 0){ + this.saveSpeakerSetup(); + }; + writer.write("sub ="+String.new.scatList(spatSub).stripWhiteSpace()++"\n"); + writer.write("out ="+spatOut++"\n"); + writer.write("rirBank = "+spatRirBank++"\n"); + writer.write("[Mosca]\n"); + writer.write("sources ="+moscaSources++"\n"); + writer.write("duration ="+moscaDuration++"\n"); + writer.close(); + } + { + ("Error: Can't create configuration file at "+((path++file).standardizePath)).postln; + }; + } + saveSpeakerSetup{ + arg path = path++defaultSpeakerCfgFile; + var file,data; + var idx = 0; + var parsingError = false; + "Saving Speaker Configuration File".postln; + if(spatSpeakers.size != 0){ + file = CSVFileWriter(path); + spatSpeakers.do{ + arg row; + file.writeLine(row); + }; + file.close(); + "Speaker Configuration File saved".postln; + } + { + parsingError = true; + "Speaker Setup is empty! Cancelling save".postln; + } + ^parsingError; + } + loadSpeakerSetup{ + arg path = path++defaultSpeakerCfgFile; + var file,data; + var idx = 0; + var parsingError = false; + var newData = List[]; + file = CSVFileReader.read(path); + data = file.collect(_.collect(_.interpret)); + while{((idx < data.size) && (parsingError.not)) && (true)} + { + if(data[idx].size != 3) + { + parsingError = true; + } + {//else + newData.add(data[idx]); + idx = idx + 1; + }; + }; + + if(parsingError){ + "Error During Parsing".postln; + }{ + "Loading Speaker Configuration File".postln; + //if load is okay then replace current setup values and update view as well + ("new data List").postln; + newData.postcs; + spatSpeakers = newData; + }; + ^parsingError.not; +} + + + +} \ No newline at end of file diff --git a/Classes/MoscaSource.sc b/Classes/MoscaSource.sc index 83de28d..6602c4d 100644 --- a/Classes/MoscaSource.sc +++ b/Classes/MoscaSource.sc @@ -411,7 +411,7 @@ MoscaSource[] this.runTrigger(); }; - if (external.value) { args = args ++ [\busini, busInd.value] }; + if (external.value) { args = args ++ [\busini, busInd.value] -1}; curentSpat = spatType; diff --git a/Classes/MoscaStartup.sc b/Classes/MoscaStartup.sc new file mode 100644 index 0000000..d13b5d5 --- /dev/null +++ b/Classes/MoscaStartup.sc @@ -0,0 +1,575 @@ +MoscaStartup +{ + var config; + var moscaInstance; + //// Server Parameters + var server = nil; + var serverStarted = false; + + //Mosca Parameters + var oscParent; + var audioPort; + var >decoder = nil; + + //// GUI variables + //Window Parameters + var window; + var <>windowW = 1260; + var <>windowH = 920; + //layout and views + var main; + var bottom; + var moscaOptions; //VLayout + var serverOptions; //GridLayout + var scrollView; + var setupViews; + + //Buttons + var startButton,stopButton,quitButton,advancedParamButton,exposeParamButton,reconnectButton; + + *new + { + ^super.new.ctr(); + } + ctr + { + config = MoscaConfig(); + config.autoConfig(); + oscParent = OSSIA_Device("SC"); + audioPort = "ossia score"; + window = Window.new("Mosca Startup", Rect(10,1000,windowW,windowH)); + } + prExposeParameters{ + oscParent.exposeOSC(config.servIP,config.servPortRemote,config.servPortLocal); + ("Exposing OSC to "+config.servIP+ ", remote port: " +config.servPortRemote + " servLocalPort: "+config.servPortLocal).postln; + } + + prReconnectWith{ + + arg device; + var devicePortName; + var pipe,i,stop; + var data,dataReader; + devicePortName = nil; + data = List(); + pipe = Pipe.new("jack_lsp -p","r"); + dataReader = pipe.getLine; + while({dataReader.notNil;},{data.add(dataReader);dataReader = pipe.getLine;}); + pipe.close; + data = Array2D.fromArray(data.size/2,2,data.asArray); + i = 0; + stop = false; + while{(i 255) ||(f.size <= 0) || (f.size > 3)){ + validIP = false; + } + }; + if(validIP) + { + i.value.postln; + config.servIP = i.value; + } + { + i.value = config.servIP; + } + } + + }; + var remotePortInput = NumberBox.new(moscaOptions).clipLo_(0).clipHi_(9999).step_(1).decimals_(0).value_(config.servPortRemote).action_({ + arg i; + config.servPortRemote = i.value.asInteger; + + }).align_(\center); + var localPortInput = NumberBox.new(moscaOptions).clipLo_(0).clipHi_(9999).step_(1).decimals_(0).value_(config.servPortLocal).action_({ + arg i; + config.servPortLocal = i.value.asInteger; + }).align_(\center); + + // var portList = PopUpMenu(window).items_(this.prGetPorts()).action_({arg i; audioPort=i.item}); + // var scanButton = Button(window).string_("Re-scan").action_{portList.items_(this.prGetPorts())}; + + //Because there is a lack of certain QT bindings this variable is necessary to locate where are the different GUI element. + var i = 1; + "Setting mosca gui".postln; + //mosca options + moscaOptions = CompositeView(window); + // moscaOptions.background = Color.rand; + moscaOptions.layout = GridLayout(); + moscaOptions.layout.addSpanning(StaticText.new().string_("Mosca options"),0,0,1,4,align:\center); + moscaOptions.layout.setColumnStretch(0,1); + moscaOptions.layout.setColumnStretch(1,2); + moscaOptions.layout.setColumnStretch(2,2); + moscaOptions.layout.setColumnStretch(3,1); + i = i+1; + // moscaOptions.layout.add(nil,i,0); + moscaOptions.layout.add(subInput[0],i,1); + moscaOptions.layout.add(subInput[1],i,2); + // moscaOptions.layout.add(nil,i,3); + i = i+1; + [nbSourcesInput,outInput,durationInput].do{ + |ezNumber| + ezNumber.labelView.align = \left; + ezNumber.numberView.align = \right; + moscaOptions.layout.add(nil,i,0); + moscaOptions.layout.add(ezNumber.labelView,i,1); + moscaOptions.layout.add(ezNumber.numberView,i,2); + moscaOptions.layout.add(nil,i,3); + i = i+1; + }; + this.prSetupGui(); + /* moscaOptions.layout.addSpanning(HLayout( + [portList], + [scanButton]),i,1,1,2); + i = i+1;*/ + moscaOptions.layout.addSpanning(nil,i,0,1,4); + i = i + 1; + moscaOptions.layout.addSpanning(scrollView,i,1,2,2); + moscaOptions.layout.setRowStretch(i,3); + i = i+1; + moscaOptions.layout.addSpanning(nil,i,0,1,4); + i = i+1; + moscaOptions.layout.addSpanning( + HLayout( + [StaticText.new().string_("Listening server IP"),stretch:1], + [StaticText.new().string_("Listening Port"),stretch:1], + [StaticText.new().string_("Local Port"),stretch:1], + ),i,1,1,2 + ); + i = i+1; + moscaOptions.layout.addSpanning( + HLayout( + [ipInput,stretch:1], + [remotePortInput,stretch:1], + [localPortInput,stretch:1] + ),i,1,1,2 + ); + // i = i+1; + + } + +} diff --git a/Classes/MoscaUtils.sc b/Classes/MoscaUtils.sc index 49a6ed4..8c13159 100644 --- a/Classes/MoscaUtils.sc +++ b/Classes/MoscaUtils.sc @@ -187,6 +187,55 @@ MoscaUtils // virtual class holding constants for Mosca related classes HOASphericalHarmonics.coefN3D(2, sphe.theta(), sphe.phi()); }); } + + *cartesianToAED + { + arg coordinatesList; + var result; + result = coordinatesList.collect({ + arg item,i; + Cartesian(item[0],item[1],item[2]).asSpherical.rotate(-pi/2) + }); + result = result.collect({ + arg item,i; + [item.theta.raddeg,item.phi.raddeg,item.rho] + }); + ^result; + } + + *aedToCartesian + { + arg coordinatesList; + var result; + result = coordinatesList.collect({ + arg item,i; + Spherical(item[2], item[0].degrad,item[1].degrad).rotate(pi/2).asCartesian.trunc(0.00000001) + }); + ^result; + } + + *cartesianToSpherical + { + arg coordinatesList; + var result; + result = coordinatesList.collect({ + arg item,i; + Cartesian(item[0],item[1],item[2]).asSpherical + }); + ^result; + } + + *sphericalToCartesian + { + arg coordinatesList; + var result; + result = coordinatesList.collect({ + arg item,i; + Spherical(item[0], item[1],item[2]).asCartesian.trunc(0.00000001) + }); + ^result; + } + *emulate_array { @@ -195,5 +244,6 @@ MoscaUtils // virtual class holding constants for Mosca related classes [ 90, 0 ], [ 135, 0 ], [ 180, 0 ], [ -135, 0 ], [ -90, 0 ], [ -45, 0 ], [ 45, -35 ], [ 135, -35 ], [ -135, -35 ], [ -45, -35 ], [ 0, -45 ], [ 90, -45 ], [ 180, -45 ], [ -90, -45 ], [ 0, -90 ] ]; - } + } + } diff --git a/HelpSource/Classes/MoscaStartup.schelp b/HelpSource/Classes/MoscaStartup.schelp new file mode 100644 index 0000000..3e3ce26 --- /dev/null +++ b/HelpSource/Classes/MoscaStartup.schelp @@ -0,0 +1,61 @@ +TITLE:: MoscaStartup +summary:: Gui Class to start Mosca with more ease. +categories:: Libraries>Ambisonic Toolkit, Libraries>HOA +related:: Guide/guide-Mosca + +DESCRIPTION:: + +This class is a GUI created to facilitate the setting up of a Mosca instance. +It also allows some control over the exposition of OSC parameters and advanced server settings. + +CLASSMETHODS:: + +METHOD:: new +(describe method here) + +returns:: (describe returnvalue here) + + +INSTANCEMETHODS:: + +PRIVATE:: prAddSetupEntry +PRIVATE:: prUpdateSetup +PRIVATE:: prClearSetupEntries +PRIVATE:: prCancel +PRIVATE:: prParseSub +PRIVATE:: prRemoveSetupEntry +PRIVATE:: prCheckConfig +PRIVATE:: prStartServer +PRIVATE:: prClose +PRIVATE:: prDoInitialSetup +PRIVATE:: prReconnectWith + +PRIVATE:: servRemoteIP +PRIVATE:: sources +PRIVATE:: setupList +PRIVATE:: order +PRIVATE:: prLoadFromFile +PRIVATE:: windowH +PRIVATE:: decoder +PRIVATE:: sub +PRIVATE:: gui +PRIVATE:: out +PRIVATE:: servRemotePort +PRIVATE:: servLocalPort + +PRIVATE:: ctr + +PRIVATE:: prExposeParameters +PRIVATE:: prSaveToFile +PRIVATE:: prSetupGui +PRIVATE:: prTestSound +PRIVATE:: prMoscaOptionsGui +PRIVATE:: prServerOptionsGui +PRIVATE:: windowW +PRIVATE:: duration + +EXAMPLES:: + +code:: +MoscaStartup.new.gui(); +::