LinJam.cpp 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516
  1. /*
  2. ==============================================================================
  3. Linjam.cpp
  4. Created: 24 May 2014 5:03:01pm
  5. Author: me
  6. ==============================================================================
  7. */
  8. #include "LinJam.h"
  9. #include "./Trace/TraceLinJam.h"
  10. #ifdef _MSC_VER
  11. # include <float.h>
  12. # define isnan(x) _isnan(x)
  13. #endif
  14. /* LinJam class public class variables */
  15. LinJamConfig* LinJam::Config ;
  16. /* LinJam class private class variables */
  17. NJClient* LinJam::Client = nullptr ; // Initialize()
  18. MainContent* LinJam::Gui = nullptr ; // Initialize()
  19. MultiTimer* LinJam::Timer = nullptr ; // Initialize()
  20. audioStreamer* LinJam::Audio = nullptr ; // Initialize()
  21. String LinJam::AutoJoinHost ; // Initialize()
  22. Value LinJam::Status = Value() ; // Initialize()
  23. bool LinJam::IsAudioInitialized = false ; // InitializeAudio()
  24. SortedSet<int> LinJam::FreeAudioSources = SortedSet<int>() ; // InitializeAudio()
  25. SortedSet<int> LinJam::FreeAudioSourcePairs = SortedSet<int>() ; // InitializeAudio()
  26. double LinJam::GuiBeatOffset ; // InitializeAudio()
  27. File LinJam::SessionDir ; // PrepareSessionDirectory()
  28. int LinJam::RetryLogin ; // Connect()
  29. String LinJam::PrevRecordingTime ; // Disconnect()
  30. URL LinJam::PollUrl ;
  31. ScopedPointer<LinJam::RoomSort> LinJam::RoomSorter = new LinJam::RoomSort() ;
  32. /* LinJam class public class methods */
  33. /* state methods */
  34. void LinJam::SignIn(String host , String login , String pass , bool is_anonymous)
  35. {
  36. Config->setCredentials(host , login , pass , is_anonymous) ;
  37. RetryLogin = NETWORK::N_LOGIN_RETRIES ; Connect() ;
  38. }
  39. void LinJam::Connect()
  40. {
  41. Client->Disconnect() ;
  42. String host = str( Config->server[CONFIG::HOST_ID ]) ;
  43. String login = str( Config->server[CONFIG::LOGIN_ID ]) ;
  44. String pass = str( Config->server[CONFIG::PASS_ID ]) ;
  45. bool is_anonymous = bool(Config->server[CONFIG::IS_ANONYMOUS_ID]) ;
  46. if (is_anonymous) { login = "anonymous:" + login ; pass = "" ; }
  47. DEBUG_TRACE_CONNECT
  48. Gui->statusbar->setStatusL(GUI::CONNECTING_TEXT + host) ;
  49. Client->Connect(host.toRawUTF8() , login.toRawUTF8() , pass.toRawUTF8()) ;
  50. }
  51. void LinJam::Disconnect() { Client->Disconnect() ; PrevRecordingTime = "" ; }
  52. /* getters/setters */
  53. ValueTree LinJam::GetCredentials(String host) { return Config->getCredentials(host) ; }
  54. bool LinJam::IsAgreed() { return bool(Config->server[CONFIG::IS_AGREED_ID]) ; }
  55. SortedSet<int> LinJam::GetFreeAudioSources() { return FreeAudioSources ; }
  56. SortedSet<int> LinJam::GetFreeAudioSourcePairs() { return FreeAudioSourcePairs ; }
  57. /* GUI event handlers */
  58. bool LinJam::AddLocalChannel(ValueTree channel_store)
  59. {
  60. DEBUG_TRACE_ADD_LOCAL_CHANNEL
  61. int channel_idx = int(channel_store[CONFIG::CHANNEL_IDX_ID]) ;
  62. int source_n = int(channel_store[CONFIG::SOURCE_N_ID ]) ;
  63. bool is_stereo = int(channel_store[CONFIG::STEREO_ID ]) != CONFIG::MONO ;
  64. // sanity check
  65. int n_sources = GetNumAudioSources() ;
  66. int max_n_channels = Client->GetMaxLocalChannels() ;
  67. int n_vacant_channels = GetNumVacantChannels() ;
  68. bool is_valid_channel_idx = channel_idx >= 0 && channel_idx < max_n_channels ;
  69. bool is_valid_source_n = source_n >= 0 && source_n < n_sources ;
  70. bool does_channel_exist = IsConfiguredChannel(channel_idx) ;
  71. bool are_sufficient_n_channels = (!is_stereo && n_vacant_channels >= 1) ||
  72. (is_stereo && n_vacant_channels >= 2) ;
  73. if (does_channel_exist || !is_valid_source_n || !are_sufficient_n_channels)
  74. return false ;
  75. // assign NJClient channel index for new unstored local channel
  76. if (!is_valid_channel_idx)
  77. {
  78. channel_idx = GetVacantLocalChannelIdx() ;
  79. channel_store.setProperty(CONFIG::CHANNEL_IDX_ID , channel_idx , nullptr) ;
  80. }
  81. // add new channel to store
  82. channel_store = Config->addChannel(Config->localChannels , channel_store) ;
  83. if (!channel_store.isValid()) return false ;
  84. DEBUG_TRACE_DUMP_FREE_INPUTS_VB
  85. DEBUG_TRACE_INSTANTIATE_LOCAL_CHANNEL
  86. // create local Channel GUI
  87. Gui->mixer->addChannel(GUI::LOCALS_GUI_ID , channel_store) ;
  88. // configure NJClient
  89. ConfigureLocalChannel(channel_store , CONFIG::CONFIG_INIT_ID) ;
  90. return true ;
  91. }
  92. void LinJam::RemoveLocalChannel(ValueTree channel_store)
  93. {
  94. DEBUG_TRACE_REMOVE_LOCAL_CHANNEL
  95. Identifier channel_id = channel_store.getType() ;
  96. int channel_idx = int(channel_store[CONFIG::CHANNEL_IDX_ID]) ;
  97. int pair_idx = int(channel_store[CONFIG::PAIR_IDX_ID]) ;
  98. // configure NJClient
  99. Client->DeleteLocalChannel(channel_idx) ;
  100. Client->DeleteLocalChannel(pair_idx) ;
  101. Client->NotifyServerOfChannelChange() ;
  102. // destroy channel GUI
  103. Gui->mixer->removeChannel(GUI::LOCALS_GUI_ID , channel_id) ;
  104. // destroy channel storage
  105. Config->removeChannel(Config->localChannels , channel_store) ;
  106. DEBUG_TRACE_DUMP_FREE_INPUTS_VB
  107. }
  108. void LinJam::SendChat(String chat_text)
  109. {
  110. DEBUG_TRACE_CHAT_OUT
  111. if ((chat_text = chat_text.trim()).isEmpty()) return ;
  112. if (!chat_text.startsWith("/"))
  113. Client->ChatMessage_Send(CLIENT::CHATMSG_TYPE_MSG.toRawUTF8() , chat_text.toRawUTF8()) ;
  114. else
  115. {
  116. // handle irc-style command
  117. String command = chat_text.upToFirstOccurrenceOf(" " , false , false) ;
  118. bool is_me_command = (!command.compare(CLIENT::CHATMSG_CMD_ME)) ;
  119. bool is_pm_command = (!command.compare(CLIENT::CHATMSG_CMD_MSG)) ;
  120. bool is_admin_command = (!command.compare(CLIENT::CHATMSG_CMD_ADMIN)) ;
  121. bool is_user_command = (!command.compare(CLIENT::CHATMSG_CMD_TOPIC) ||
  122. !command.compare(CLIENT::CHATMSG_CMD_KICK) ||
  123. !command.compare(CLIENT::CHATMSG_CMD_BPI) ||
  124. !command.compare(CLIENT::CHATMSG_CMD_BPM) ) ;
  125. #ifndef ACCEPT_CHAT_COMMANDS // (issue #19)
  126. Gui->chat->addChatLine(GUI::SERVER_NICK , "commands disabled") ; return ;
  127. #endif // CHAT_COMMANDS_BUGGY
  128. if (is_me_command)
  129. {
  130. String msg = String(Client->GetUserName()) + " " + chat_text ;
  131. Client->ChatMessage_Send(CLIENT::CHATMSG_TYPE_MSG.toRawUTF8() , msg.toRawUTF8()) ;
  132. }
  133. else if (is_user_command)
  134. {
  135. String msg = chat_text.substring(1) ;
  136. Client->ChatMessage_Send(CLIENT::CHATMSG_TYPE_ADMIN.toRawUTF8() , msg.toRawUTF8()) ;
  137. }
  138. else if (is_admin_command)
  139. {
  140. String msg = chat_text.substring(6).trim() ;
  141. Client->ChatMessage_Send(CLIENT::CHATMSG_TYPE_ADMIN.toRawUTF8() , msg.toRawUTF8()) ;
  142. }
  143. else if (is_pm_command)
  144. {
  145. String to_user = chat_text.substring(4).trim() ;
  146. to_user = to_user.upToFirstOccurrenceOf(StringRef(" ") , false , false) ;
  147. String msg = to_user.fromFirstOccurrenceOf(StringRef(" ") , false , false).trim() ;
  148. if (to_user.isEmpty() || msg.isEmpty())
  149. Gui->chat->addChatLine(GUI::SERVER_NICK , GUI::INVALID_PM_MSG) ;
  150. else // if (does_user_exist(to_user)) // TODO: this safe yea ? // (issue #19)
  151. {
  152. Client->ChatMessage_Send(CLIENT::CHATMSG_TYPE_PRIVMSG.toRawUTF8() , msg.toRawUTF8()) ;
  153. Gui->chat->addChatLine("(PM -> " + to_user + ")" , msg) ;
  154. }
  155. }
  156. else Gui->chat->addChatLine(GUI::SERVER_NICK , GUI::UNKNOWN_COMMAND_MSG) ;
  157. }
  158. }
  159. void LinJam::CleanSessionDir() { SessionDir.deleteRecursively() ; }
  160. /* LinJam class private class methods */
  161. /* initialization methods */
  162. bool LinJam::Initialize(NJClient* nj_client , MainContent* main_content ,
  163. MultiTimer* multi_timer , const String& cli_args )
  164. {
  165. DEBUG_TRACE_INIT
  166. Client = nj_client ;
  167. Gui = main_content ;
  168. Timer = multi_timer ;
  169. AutoJoinHost = cli_args ; // TODO: parse/validate command line for auto-join (issue #9)
  170. Status = APP::LINJAM_STATUS_INIT ;
  171. // prepare runtime initialized constants
  172. APP::Initialize() ;
  173. // load persistent configuration and prepare audio save directory
  174. if ((Config = new LinJamConfig()) == nullptr ||
  175. !Config->isConfigValid() ||
  176. !PrepareSessionDirectory() ) return false ;
  177. // instantiate GUI components requiring model hooks
  178. Gui->instantiate(Config->gui , Config->client , Config->blacklist ,
  179. Config->audio , Config->server , Config->servers ,
  180. Status ) ;
  181. // configure NINJAM client and initialize networking
  182. ConfigureNinjam() ; JNL::open_socketlib() ;
  183. // instantiate audioStreamer
  184. if (InitializeAudio()) Status = APP::LINJAM_STATUS_READY ;
  185. // start NJClient pump and GUI update timers
  186. Timer->startTimer(APP::CLIENT_TIMER_ID , APP::CLIENT_DRIVER_IVL) ;
  187. Timer->startTimer(APP::GUI_LO_TIMER_ID , APP::GUI_LO_UPDATE_IVL) ;
  188. //Timer->startTimer(APP::GUI_MD_TIMER_ID , APP::GUI_MD_UPDATE_IVL) ; // unused
  189. Timer->startTimer(APP::GUI_HI_TIMER_ID , APP::GUI_LO_UPDATE_IVL) ;
  190. ConfigureGui(CONFIG::UPDATE_IVL_ID) ;
  191. return true ;
  192. }
  193. bool LinJam::PrepareSessionDirectory()
  194. {
  195. SessionDir = Config->dataDir.getChildFile(CLIENT::SESSION_DIRNAME) ;
  196. SessionDir.createDirectory() ;
  197. DEBUG_TRACE_SESSIONDIR
  198. bool does_session_dir_exist = SessionDir.isDirectory() ;
  199. if (does_session_dir_exist)
  200. Client->SetWorkDir(SessionDir.getFullPathName().toRawUTF8()) ;
  201. return does_session_dir_exist ;
  202. }
  203. void LinJam::ConfigureNinjam()
  204. {
  205. int save_audio_mode = int( Config->client [CONFIG::SAVE_AUDIO_MODE_ID]) ;
  206. bool should_save_log = bool(Config->client [CONFIG::SHOULD_SAVE_LOG_ID]) ;
  207. int debug_level = int( Config->client [CONFIG::DEBUG_LEVEL_ID ]) ;
  208. int subscribe_mode = int( Config->blacklist[CONFIG::SUBSCRIBE_MODE_ID ]) ;
  209. Client->LicenseAgreementCallback = OnLicense ;
  210. Client->ChatMessage_Callback = OnChatmsg ;
  211. Client->config_savelocalaudio = save_audio_mode ;
  212. Client->config_debug_level = debug_level ;
  213. Client->config_autosubscribe = subscribe_mode ;
  214. // set log file
  215. if (should_save_log && save_audio_mode > NJClient::SAVE_NONE)
  216. Client->SetLogFile((SessionDir.getFullPathName() + CLIENT::LOG_FILENAME).toRawUTF8()) ;
  217. // add bots and ignored users to ignore list
  218. ConfigureBlacklist() ;
  219. }
  220. void LinJam::ConfigureGui(const Identifier& a_key)
  221. {
  222. if (a_key != CONFIG::UPDATE_IVL_ID) return ;
  223. int gui_update_ivl_n = int(Config->gui[CONFIG::UPDATE_IVL_ID]) ;
  224. int gui_update_ivl = APP::GUI_HI_UPDATE_IVLS[gui_update_ivl_n] ;
  225. if (!!gui_update_ivl) Timer->startTimer(APP::GUI_HI_TIMER_ID , gui_update_ivl) ;
  226. else Timer->stopTimer( APP::GUI_HI_TIMER_ID ) ;
  227. if (!!gui_update_ivl) return ;
  228. // zero all VUs
  229. int n_channels = Config->localChannels.getNumChildren() ;
  230. for (int channel_n = 0 ; channel_n < n_channels ; ++channel_n)
  231. {
  232. ValueTree channel_store = Config->localChannels.getChild(channel_n) ;
  233. channel_store.setProperty(CONFIG::VU_LEFT_ID , 0.0 , nullptr) ;
  234. channel_store.setProperty(CONFIG::VU_RIGHT_ID , 0.0 , nullptr) ;
  235. }
  236. int n_users = Config->remoteUsers.getNumChildren() ;
  237. for (int user_n = 0 ; user_n < n_users ; ++user_n)
  238. {
  239. ValueTree user_store = Config->remoteUsers.getChild(user_n) ;
  240. int n_channels = user_store.getNumChildren() ;
  241. for (int channel_n = 0 ; channel_n < n_channels ; ++channel_n)
  242. {
  243. ValueTree channel_store = user_store.getChild(channel_n) ;
  244. channel_store.setProperty(CONFIG::VU_LEFT_ID , 0.0 , nullptr) ;
  245. channel_store.setProperty(CONFIG::VU_RIGHT_ID , 0.0 , nullptr) ;
  246. }
  247. }
  248. ValueTree master_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::MASTER_ID) ;
  249. ValueTree metro_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::METRO_ID ) ;
  250. master_store.setProperty(CONFIG::VU_LEFT_ID , 0.0 , nullptr) ;
  251. master_store.setProperty(CONFIG::VU_RIGHT_ID , 0.0 , nullptr) ;
  252. metro_store .setProperty(CONFIG::VU_LEFT_ID , 0.0 , nullptr) ;
  253. metro_store .setProperty(CONFIG::VU_RIGHT_ID , 0.0 , nullptr) ;
  254. }
  255. void LinJam::ConfigureBlacklist()
  256. {
  257. DEBUG_TRACE_BLACKLIST
  258. ValueTree blacklist = Config->blacklist.createCopy() ;
  259. int subscribe_mode = int( Config->blacklist[CONFIG::SUBSCRIBE_MODE_ID ]) ;
  260. int should_hide_bots = bool(Config->client [CONFIG::SHOULD_HIDE_BOTS_ID]) ;
  261. bool should_ignore_users = subscribe_mode == NJClient::SUBSCRIBE_DENY ;
  262. Client->config_autosubscribe_userlist.clear() ;
  263. if (!should_ignore_users) return ;
  264. if (should_hide_bots)
  265. for (int bot_n = 0 ; bot_n < NETWORK::KNOWN_BOTS.getNumChildren() ; ++bot_n)
  266. {
  267. Identifier bot_name = NETWORK::KNOWN_BOTS.getChild(bot_n).getType() ;
  268. blacklist.getOrCreateChildWithName(bot_name , nullptr) ;
  269. }
  270. for (int user_n = 0 ; user_n < blacklist.getNumChildren() ; ++user_n)
  271. {
  272. String user_name = STRING(blacklist.getChild(user_n).getType()) ;
  273. Client->config_autosubscribe_userlist.insert(user_name.toStdString()) ;
  274. }
  275. }
  276. bool LinJam::InitializeAudio()
  277. {
  278. if (Audio != nullptr) { delete Audio ; Audio = nullptr ; }
  279. // load config
  280. int audio_api_n = int(Config->audio[CONFIG::AUDIO_API_ID ]) ;
  281. #ifdef _WIN32
  282. int asio_driver_n = int(Config->audio[CONFIG::ASIO_DRIVER_ID ]) ;
  283. int asio_input_b_n = int(Config->audio[CONFIG::ASIO_INPUTB_ID ]) ;
  284. int asio_input_e_n = int(Config->audio[CONFIG::ASIO_INPUTE_ID ]) ;
  285. int asio_output_b_n = int(Config->audio[CONFIG::ASIO_OUTPUTB_ID ]) ;
  286. int asio_output_e_n = int(Config->audio[CONFIG::ASIO_OUTPUTE_ID ]) ;
  287. bool should_show_asio_cp = int(Config->audio[CONFIG::ASIO_CONTROL_ID ]) ;
  288. int ks_sample_rate = int(Config->audio[CONFIG::KS_SAMPLERATE_ID ]) ;
  289. int ks_input_device_n = int(Config->audio[CONFIG::KS_INPUT_ID ]) ;
  290. int ks_output_device_n = int(Config->audio[CONFIG::KS_OUTPUT_ID ]) ;
  291. int ks_bit_depth = int(Config->audio[CONFIG::KS_BITDEPTH_ID ]) ;
  292. int ks_n_buffers = int(Config->audio[CONFIG::KS_NBLOCKS_ID ]) ;
  293. int ks_buffer_size = int(Config->audio[CONFIG::KS_BLOCKSIZE_ID ]) ;
  294. String ds_input_device = str(Config->audio[CONFIG::DS_INPUT_ID ]) ;
  295. String ds_output_device = str(Config->audio[CONFIG::DS_OUTPUT_ID ]) ;
  296. int ds_sample_rate = int(Config->audio[CONFIG::DS_SAMPLERATE_ID ]) ;
  297. int ds_bit_depth = int(Config->audio[CONFIG::DS_BITDEPTH_ID ]) ;
  298. int ds_n_buffers = int(Config->audio[CONFIG::DS_NBLOCKS_ID ]) ;
  299. int ds_buffer_size = int(Config->audio[CONFIG::DS_BLOCKSIZE_ID ]) ;
  300. int wave_input_device_n = int(Config->audio[CONFIG::WAVE_INPUT_ID ]) ;
  301. int wave_output_device_n = int(Config->audio[CONFIG::WAVE_OUTPUT_ID ]) ;
  302. int wave_sample_rate = int(Config->audio[CONFIG::WAVE_SAMPLERATE_ID]) ;
  303. int wave_bit_depth = int(Config->audio[CONFIG::WAVE_BITDEPTH_ID ]) ;
  304. int wave_n_buffers = int(Config->audio[CONFIG::WAVE_NBLOCKS_ID ]) ;
  305. int wave_buffer_size = int(Config->audio[CONFIG::WAVE_BLOCKSIZE_ID ]) ;
  306. #else // _WIN32
  307. # ifdef _MAC
  308. String ca_input_device = str(Config->audio[CONFIG::CA_INPUT_ID ]) ;
  309. String ca_output_device = str(Config->audio[CONFIG::CA_OUTPUT_ID ]) ;
  310. int ca_n_channels = int(Config->audio[CONFIG::CA_NCHANNELS_ID ]) ;
  311. int ca_sample_rate = int(Config->audio[CONFIG::CA_SAMPLERATE_ID ]) ;
  312. int ca_bit_depth = int(Config->audio[CONFIG::CA_BITDEPTH_ID ]) ;
  313. # else // _MAC
  314. int jack_server_n = int(Config->audio[CONFIG::JACK_SERVER_ID ]) ;
  315. String jack_client_name = str(Config->audio[CONFIG::JACK_NAME_ID ]) ;
  316. int jack_n_inputs = int(Config->audio[CONFIG::JACK_NINPUTS_ID ]) ;
  317. int jack_n_outputs = int(Config->audio[CONFIG::JACK_NOUTPUTS_ID ]) ;
  318. String alsa_input_device = str(Config->audio[CONFIG::ALSA_INPUT_ID ]) ;
  319. String alsa_output_device = str(Config->audio[CONFIG::ALSA_OUTPUT_ID ]) ;
  320. int alsa_n_channels = int(Config->audio[CONFIG::ALSA_NCHANNELS_ID ]) ;
  321. int alsa_sample_rate = int(Config->audio[CONFIG::ALSA_SAMPLERATE_ID]) ;
  322. int alsa_bit_depth = int(Config->audio[CONFIG::ALSA_BITDEPTH_ID ]) ;
  323. int alsa_n_buffers = int(Config->audio[CONFIG::ALSA_NBLOCKS_ID ]) ;
  324. int alsa_buffer_size = int(Config->audio[CONFIG::ALSA_BLOCKSIZE_ID ]) ;
  325. # endif // _MAC
  326. #endif // _WIN32
  327. #ifdef _WIN32
  328. DEBUG_TRACE_AUDIO_INIT_WIN
  329. switch ((audioStreamer::WinApi)audio_api_n)
  330. {
  331. # ifndef NO_SUPPORT_ASIO
  332. case audioStreamer::WIN_AUDIO_ASIO:
  333. {
  334. Audio = audioStreamer::NewASIO(OnSamples , asio_driver_n ,
  335. asio_input_b_n , asio_input_e_n ,
  336. asio_output_b_n , asio_output_e_n ,
  337. should_show_asio_cp ) ;
  338. break ;
  339. }
  340. # endif // NO_SUPPORT_ASIO
  341. # ifndef NO_SUPPORT_KS
  342. case audioStreamer::WIN_AUDIO_KS:
  343. {
  344. // TODO: ks_input_device_n, ks_output_device_n unused
  345. Audio = audioStreamer::NewKS(OnSamples ,
  346. ks_sample_rate , ks_bit_depth ,
  347. &ks_n_buffers , &ks_buffer_size) ;
  348. // store back possibly modified params
  349. Config->audio.removeListener(Config) ;
  350. Config->audio.setProperty(CONFIG::KS_NBLOCKS_ID , ks_n_buffers , nullptr) ;
  351. Config->audio.setProperty(CONFIG::KS_BLOCKSIZE_ID , ks_buffer_size , nullptr) ;
  352. Config->audio.addListener(Config) ;
  353. break ;
  354. }
  355. # endif // NO_SUPPORT_KS
  356. # ifndef NO_SUPPORT_DS
  357. case audioStreamer::WIN_AUDIO_DS:
  358. {
  359. GUID input_guid , output_guid ;
  360. audioStreamer::GetDsGuidByName(ds_input_device .toRawUTF8() , &input_guid) ;
  361. audioStreamer::GetDsGuidByName(ds_output_device.toRawUTF8() , &output_guid) ;
  362. Audio = audioStreamer::NewDS(OnSamples ,
  363. input_guid , output_guid ,
  364. ds_sample_rate , ds_bit_depth ,
  365. ds_n_buffers , ds_buffer_size) ;
  366. break ;
  367. }
  368. # endif // NO_SUPPORT_DS
  369. # ifndef NO_SUPPORT_WAVE
  370. case audioStreamer::WIN_AUDIO_WAVE:
  371. {
  372. Audio = audioStreamer::NewWAVE(OnSamples ,
  373. wave_input_device_n , wave_output_device_n ,
  374. wave_sample_rate , wave_bit_depth ,
  375. wave_n_buffers , wave_buffer_size ) ;
  376. break ;
  377. }
  378. # endif // NO_SUPPORT_WAVE
  379. default: break ;
  380. }
  381. #else // _WIN32
  382. # ifdef _MAC
  383. UNUSED(audio_api_n) ;
  384. std::string input_device = ca_input_device .toStdString() ;
  385. std::string output_device = ca_output_device.toStdString() ;
  386. Audio = audioStreamer::NewCA(OnSamples , input_device , output_device ,
  387. ca_n_channels , ca_sample_rate , ca_bit_depth ) ;
  388. Config->audio.setProperty(CONFIG::CA_DEVICE_ID , device_names , nullptr) ;
  389. DEBUG_TRACE_AUDIO_INIT_MAC
  390. # else // _MAC
  391. DEBUG_TRACE_AUDIO_INIT_NIX
  392. switch ((audioStreamer::NixApi)audio_api_n)
  393. {
  394. case audioStreamer::NIX_AUDIO_JACK:
  395. {
  396. UNUSED(jack_server_n) ;
  397. std::string jack_name = jack_client_name.toStdString() ;
  398. Audio = audioStreamer::NewJACK(OnSamples , Client ,
  399. jack_name , jack_n_inputs , jack_n_outputs) ;
  400. DEBUG_TRACE_AUDIO_INIT_JACK_FAIL
  401. if (Audio != nullptr) break ; // else fallback on ALSA
  402. }
  403. case audioStreamer::NIX_AUDIO_ALSA:
  404. {
  405. std::string input_device = alsa_input_device .toStdString() ;
  406. std::string output_device = alsa_output_device.toStdString() ;
  407. Audio = audioStreamer::NewALSA(OnSamples ,
  408. input_device , output_device ,
  409. alsa_n_channels ,
  410. alsa_sample_rate , alsa_bit_depth ,
  411. alsa_n_buffers , alsa_buffer_size) ;
  412. break ;
  413. }
  414. default: break ;
  415. }
  416. # endif // _MAC
  417. #endif // _WIN32
  418. DEBUG_TRACE_AUDIO_INIT
  419. bool isAudioEnabled = Audio != nullptr ;
  420. if (isAudioEnabled)
  421. {
  422. // kludge to sync loop progress to audible ticks
  423. // TODO: this may not be necessary at hipri update speed
  424. // probably just disable loop progress at lopri speed
  425. double update_ivl = double(Config->gui[CONFIG::UPDATE_IVL_ID]) ;
  426. GuiBeatOffset = Audio->getSampleRate() * (update_ivl * 0.002) ;
  427. // populate input source names arrays for ChannelConfig GUI
  428. int n_audio_sources = GetNumAudioSources() ;
  429. FreeAudioSources.clear() ; FreeAudioSourcePairs.clear() ;
  430. for (int source_n = 0 ; source_n < n_audio_sources ; ++source_n)
  431. {
  432. FreeAudioSources.add(source_n) ;
  433. if (source_n % 2) FreeAudioSourcePairs.add(source_n - 1) ;
  434. }
  435. // create master and stored local input channels
  436. if (!IsAudioInitialized) { ConfigureInitialChannels() ; IsAudioInitialized = true ; }
  437. }
  438. // set audio and status value holders for Config GUI
  439. Status = (isAudioEnabled) ? APP::LINJAM_STATUS_CONFIGPENDING :
  440. APP::LINJAM_STATUS_AUDIOERROR ;
  441. return isAudioEnabled ;
  442. }
  443. void LinJam::ConfigureInitialChannels()
  444. {
  445. if (IsAudioInitialized) return ;
  446. // add master and metro channel GUI mixers and configure NJClient master channels
  447. ValueTree master_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::MASTER_ID) ;
  448. ValueTree metro_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::METRO_ID) ;
  449. Gui->mixer->addChannel(GUI::MASTERS_GUI_ID , master_store) ;
  450. Gui->mixer->addChannel(GUI::MASTERS_GUI_ID , metro_store) ;
  451. ConfigureMasterChannel(CONFIG::CONFIG_INIT_ID) ;
  452. ConfigureMetroChannel( CONFIG::CONFIG_INIT_ID) ;
  453. DEBUG_TRACE_INITIAL_CHANNELS
  454. // add local Channel GUI mixers and configure NJClient input channels
  455. ValueTree channels = Config->localChannels ;
  456. for (int channel_n = 0 ; channel_n < channels.getNumChildren() ; ++channel_n)
  457. {
  458. ValueTree channel_store = Config->localChannels.getChild(channel_n) ;
  459. if (!AddLocalChannel(channel_store))
  460. {
  461. // destroy corrupted channel storage
  462. Config->removeChannel(Config->localChannels , channel_store) ;
  463. --channel_n ;
  464. }
  465. }
  466. }
  467. void LinJam::Shutdown()
  468. {
  469. // NJClient teardown
  470. JNL::close_socketlib() ;
  471. // LinJam teardown
  472. RoomSorter = nullptr ; delete Audio ; delete Config ;
  473. // Constants teardown
  474. // delete NETWORK::KNOWN_HOSTS ; delete NETWORK::KNOWN_BOTS ;
  475. DEBUG_TRACE_SHUTDOWN
  476. }
  477. /* NJClient callbacks */
  478. int LinJam::OnLicense(int user32 , char* license_text)
  479. {
  480. UNUSED(user32) ;
  481. if (!IsAgreed()) Gui->license->setLicenseText(CharPointer_UTF8(license_text)) ;
  482. DEBUG_TRACE_LICENSE
  483. return IsAgreed() ;
  484. }
  485. void LinJam::OnChatmsg(int user32 , NJClient* instance , const char** parms , int nparms)
  486. {
  487. UNUSED(user32) ; UNUSED(instance) ; UNUSED(nparms) ;
  488. if (!parms[0]) return ;
  489. String chat_type = String(CharPointer_UTF8(parms[CLIENT::CHATMSG_TYPE_IDX])) ;
  490. String chat_user = String(CharPointer_UTF8(parms[CLIENT::CHATMSG_USER_IDX]))
  491. .upToFirstOccurrenceOf(CONFIG::USER_IP_SPLIT_CHAR , false , false) ;
  492. String chat_text = String(CharPointer_UTF8(parms[CLIENT::CHATMSG_MSG_IDX])) ;
  493. bool is_topic_msg = (!chat_type.compare(CLIENT::CHATMSG_TYPE_TOPIC)) ;
  494. bool is_bcast_msg = (!chat_type.compare(CLIENT::CHATMSG_TYPE_MSG)) ;
  495. bool is_priv_msg = (!chat_type.compare(CLIENT::CHATMSG_TYPE_PRIVMSG)) ;
  496. bool is_join_msg = (!chat_type.compare(CLIENT::CHATMSG_TYPE_JOIN)) ;
  497. bool is_part_msg = (!chat_type.compare(CLIENT::CHATMSG_TYPE_PART)) ;
  498. DEBUG_TRACE_CHAT_IN
  499. if (is_topic_msg)
  500. {
  501. if (chat_text.isEmpty()) return ;
  502. Gui->chat->setTopic(chat_text) ;
  503. if (chat_user.isEmpty()) chat_text = GUI::TOPIC_TEXT + chat_text ;
  504. else chat_text = chat_user + GUI::SET_TOPIC_TEXT + chat_text ;
  505. chat_user = GUI::SERVER_NICK ;
  506. }
  507. else if (is_bcast_msg)
  508. {
  509. if (chat_text.isEmpty()) return ;
  510. if (chat_user.isEmpty()) chat_user = GUI::SERVER_NICK ;
  511. else if (chat_text.startsWith(CLIENT::CHATMSG_CMD_VOTE))
  512. {
  513. // customize voting messages
  514. StringArray tokens = StringArray::fromTokens(StringRef(chat_text) , false) ;
  515. String bpi_bpm_cmd = tokens[1] ;
  516. String bpi_bpm_val = tokens[2] ;
  517. bool is_bpi_msg = !bpi_bpm_cmd.compare(CLIENT::CHATMSG_CMD_BPI.substring(1).trim()) ;
  518. bool is_bpm_msg = !bpi_bpm_cmd.compare(CLIENT::CHATMSG_CMD_BPM.substring(1).trim()) ;
  519. if ((is_bpi_msg || is_bpm_msg) && bpi_bpm_val.containsOnly(NETWORK::DIGITS))
  520. {
  521. chat_text = chat_user + " votes to set " + bpi_bpm_cmd + " to " + bpi_bpm_val ;
  522. chat_user = GUI::SERVER_NICK ;
  523. }
  524. }
  525. }
  526. else if (is_priv_msg)
  527. {
  528. if (chat_user.isEmpty() || chat_text.isEmpty()) return ;
  529. chat_user += GUI::PM_TEXT ;
  530. }
  531. else if (is_join_msg || is_part_msg)
  532. {
  533. if (chat_user.isEmpty()) return ;
  534. chat_text = chat_user + GUI::JOINPART_TEXTa +
  535. ((is_join_msg)? GUI::JOIN_TEXT : GUI::PART_TEXT) + GUI::JOINPART_TEXTb ;
  536. chat_user = GUI::SERVER_NICK ;
  537. }
  538. Gui->chat->addChatLine(chat_user , chat_text) ;
  539. }
  540. void LinJam::OnSamples(float** input_buffer , int n_input_channels ,
  541. float** output_buffer , int n_output_channels ,
  542. int n_samples , int sample_rate )
  543. {
  544. if (Audio == nullptr)
  545. {
  546. // clear all output buffers
  547. size_t n_bytes = n_samples * sizeof(float) ;
  548. for (int channel_n = 0 ; channel_n < n_output_channels ; ++channel_n)
  549. memset(output_buffer[channel_n] , 0 , n_bytes) ;
  550. }
  551. else Client->AudioProc(input_buffer , n_input_channels ,
  552. output_buffer , n_output_channels ,
  553. n_samples , sample_rate ) ;
  554. }
  555. /* NJClient runtime routines */
  556. void LinJam::HandleTimer(int timer_id)
  557. {
  558. #ifdef DEBUG_EXIT_IMMEDIATELY
  559. DBG("[DEBUG]: DEBUG_EXIT_IMMEDIATELY defined - bailing") ; Client->quit() ;
  560. #endif // DEBUG_EXIT_IMMEDIATELY
  561. switch (timer_id)
  562. {
  563. case APP::CLIENT_TIMER_ID: PumpClient() ; break ;
  564. case APP::GUI_LO_TIMER_ID: UpdateGuiLowPriority() ; break ;
  565. // case APP::GUI_MD_TIMER_ID: /* unused */ break ;
  566. case APP::GUI_HI_TIMER_ID: UpdateGuiHighPriority() ; break ;
  567. case APP::AUDIO_INIT_TIMER_ID: InitializeAudio() ; break ;
  568. default: break ;
  569. }
  570. if (timer_id == APP::AUDIO_INIT_TIMER_ID) Timer->stopTimer(timer_id) ;
  571. }
  572. void LinJam::PumpClient()
  573. {
  574. UpdateStatus() ;
  575. if (Client->HasUserInfoChanged() ) HandleUserInfoChanged() ;
  576. if (Client->GetStatus() >= APP::NJC_STATUS_OK) while (!Client->Run()) ;
  577. }
  578. void LinJam::UpdateStatus()
  579. {
  580. // update status if not in an init, error, or hold state
  581. int status = int(Status.getValue()) ;
  582. bool is_ready = status >= APP::LINJAM_STATUS_READY ;
  583. if (is_ready) status = Client->GetStatus() ;
  584. String error_msg = CharPointer_UTF8(Client->GetErrorStr()) ;
  585. bool is_licence_pending = status == APP::NJC_STATUS_INVALIDAUTH && !IsAgreed() ;
  586. bool is_room_full = is_ready && !error_msg.compare(CLIENT::SERVER_FULL_ERROR) ;
  587. if (is_licence_pending) status = APP::LINJAM_STATUS_LICENSEPENDING ;
  588. else if (is_room_full ) status = APP::LINJAM_STATUS_ROOMFULL ;
  589. Status = status ;
  590. }
  591. void LinJam::HandleStatusChanged()
  592. {
  593. DEBUG_TRACE_STATUS_CHANGED
  594. APP::LinJamStatus status = (APP::LinJamStatus)int(Status.getValue()) ;
  595. // ignore sentinel value
  596. if (status == APP::LINJAM_STATUS_READY) return ;
  597. // set status indicator
  598. String host = Client->GetHostName() ;
  599. String status_text =
  600. (status == APP::LINJAM_STATUS_AUDIOERROR ) ? GUI::AUDIO_INIT_ERROR_MSG :
  601. (status == APP::LINJAM_STATUS_AUDIOINIT ) ? GUI::AUDIO_INIT_MSG :
  602. (status == APP::LINJAM_STATUS_CONFIGPENDING ) ? GUI::CONFIG_PENDING_MSG :
  603. (status == APP::LINJAM_STATUS_LICENSEPENDING) ? GUI::LICENSE_PENDING_TEXT :
  604. (status == APP::LINJAM_STATUS_ROOMFULL ) ? GUI::ROOM_FULL_TEXT :
  605. (status == APP::NJC_STATUS_DISCONNECTED ) ? GUI::DISCONNECTED_TEXT :
  606. (status == APP::NJC_STATUS_INVALIDAUTH ) ? GUI::INVALID_AUTH_TEXT :
  607. (status == APP::NJC_STATUS_CANTCONNECT ) ? GUI::FAILED_CONNECTION_TEXT :
  608. (status == APP::NJC_STATUS_OK ) ? GUI::CONNECTED_TEXT + host :
  609. (status == APP::NJC_STATUS_PRECONNECT ) ? GUI::IDLE_TEXT :
  610. Status.toString() ;
  611. Gui->statusbar->setStatusL(status_text) ;
  612. // set front-most GUI container
  613. Component* top_component =
  614. (status == APP::LINJAM_STATUS_AUDIOERROR ) ? Gui->config :
  615. (status == APP::LINJAM_STATUS_AUDIOINIT ) ? Gui->config :
  616. (status == APP::LINJAM_STATUS_CONFIGPENDING ) ? Gui->config :
  617. (status == APP::LINJAM_STATUS_LICENSEPENDING) ? Gui->license :
  618. (status == APP::LINJAM_STATUS_ROOMFULL ) ? Gui->login :
  619. (status == APP::NJC_STATUS_DISCONNECTED ) ? Gui->login :
  620. (status == APP::NJC_STATUS_INVALIDAUTH ) ? Gui->login :
  621. (status == APP::NJC_STATUS_CANTCONNECT ) ? Gui->login :
  622. (status == APP::NJC_STATUS_OK ) ? Gui->chat :
  623. (status == APP::NJC_STATUS_PRECONNECT ) ? Gui->login :
  624. (Component*)Gui->background ;
  625. top_component ->toFront(true) ;
  626. Gui->background->toBehind(top_component) ;
  627. // responses
  628. switch (status)
  629. {
  630. // retry login
  631. case APP::NJC_STATUS_INVALIDAUTH:
  632. case APP::NJC_STATUS_CANTCONNECT: // retry login (server occasionally rejects)
  633. if (RetryLogin-- > 0) Connect() ; break ;
  634. case APP::NJC_STATUS_OK: // store server credentials and present mixer GUI
  635. Config->storeServer() ;
  636. UpdateGuiLowPriority() ;
  637. Gui->mixer->toFront(false) ;
  638. Gui->loop ->toFront(false) ; break ;
  639. case APP::NJC_STATUS_PRECONNECT: // auto-join
  640. if (AutoJoinHost.isNotEmpty() && Gui->login->quickLogin(AutoJoinHost))
  641. Gui->background->toFront(true) ;
  642. AutoJoinHost = "" ; break ;
  643. default: break ;
  644. }
  645. }
  646. void LinJam::HandleUserInfoChanged()
  647. {
  648. #ifdef NO_UPDATE_REMOTES
  649. return ;
  650. #endif // NO_UPDATE_REMOTES
  651. Identifier host = LinJamConfig::MakeHostId(str(Config->server[CONFIG::HOST_ID])) ;
  652. DEBUG_TRACE_REMOTE_CHANNELS_VB
  653. // initialize dictionary for pruning parted users GUI elements
  654. ValueTree active_users = ValueTree("active-users") ;
  655. // fetch remote user states from server
  656. int user_idx = -1 ; String user_name ;
  657. while ((user_name = GetRemoteUserName(++user_idx)).isNotEmpty())
  658. {
  659. Identifier user_id = Config->MakeUserId(user_name) ;
  660. std::string nick = (user_name = STRING(user_id)).toStdString() ;
  661. bool is_ignored = !!Client->config_autosubscribe_userlist.count(nick) ;
  662. bool is_bot = str(NETWORK::KNOWN_BOTS.getProperty(host , "")) == user_name ;
  663. // cache bot user_idx for recording time updates
  664. if (is_bot) Config->server.setProperty(CONFIG::BOT_USERIDX_ID , user_idx , nullptr) ;
  665. // TODO: we may be able to bail now without storing bot userdata (issue #64)
  666. // get or create remote user storage
  667. ValueTree user_store = Config->getOrAddRemoteUser(user_name) ;
  668. if (!user_store.isValid()) continue ;
  669. // update stored remote user state
  670. Config->updateRemoteUserState(user_store , user_idx , !is_ignored) ;
  671. if (is_ignored) continue ;
  672. // create remote user GUI
  673. if (Gui->mixer->addRemoteUser(user_store))
  674. {
  675. // create remote master channel storage
  676. ValueTree master_store = Config->getOrAddRemoteChannel(user_id , CONFIG::MASTER_KEY) ;
  677. if (!master_store.isValid()) continue ;
  678. // create remote master channel GUI and restore stored NJClient user state
  679. if (Gui->mixer->addChannel(user_id , master_store))
  680. ConfigureRemoteChannel(user_store , master_store , CONFIG::CONFIG_INIT_ID) ;
  681. }
  682. // initialize array for pruning removed channels GUI elements
  683. Array<var> active_channels = Array<var>() ;
  684. int channel_n = -1 ; int channel_idx ;
  685. while (~(channel_idx = Client->EnumUserChannels(user_idx , ++channel_n)))
  686. {
  687. // get or create remote channel storage
  688. String channel_name = GetRemoteChannelClientName(user_idx , channel_idx) ;
  689. ValueTree channel_store = Config->getOrAddRemoteChannel(user_id , channel_name ,
  690. channel_idx ) ;
  691. String stored_name = GetStoredChannelName(channel_store) ;
  692. if (!channel_store.isValid()) continue ;
  693. // rename existing channel (and GUI asynchronously)
  694. if (channel_name.compare(stored_name))
  695. channel_store.setProperty(CONFIG::CHANNEL_NAME_ID , channel_name , nullptr) ;
  696. // update faux-stereo status (configures NJClient and GUI asynchronously)
  697. int stereo_status = Config->setRemoteStereo(user_store , channel_store , stored_name) ;
  698. // create remote channel GUI and restore stored NJClient remote channel state
  699. if (Gui->mixer->addChannel(user_id , channel_store))
  700. ConfigureRemoteChannel(user_store , channel_store , CONFIG::CONFIG_INIT_ID) ;
  701. // add channel to GUI prune list unless hidden stereo pair channel
  702. if (stereo_status != CONFIG::STEREO_R)
  703. active_channels.add(var(STRING(channel_store.getType()))) ;
  704. }
  705. // add user to GUI prune list
  706. active_users.setProperty(user_id , active_channels , nullptr) ;
  707. }
  708. // prune user and channel GUIs
  709. Gui->mixer->pruneRemotes(active_users) ;
  710. }
  711. void LinJam::UpdateGuiHighPriority() { UpdateLoopProgress() ; UpdateVuMeters() ; }
  712. void LinJam::UpdateGuiLowPriority() { UpdateRooms() ; UpdateRecordingTime() ; }
  713. void LinJam::UpdateLoopProgress()
  714. {
  715. #ifdef NO_UPDATE_LOOP_PROGRESS_GUI
  716. return ;
  717. #endif // NO_UPDATE_LOOP_PROGRESS_GUI
  718. // set loop progress to strobing effect when idle
  719. if (Status != APP::NJC_STATUS_OK) { Gui->loop->loopProgress = 1.0 ; return ; }
  720. // compute loop progress
  721. int sample_n , n_samples ; Client->GetPosition(&sample_n , &n_samples) ;
  722. int bpi = Client->GetBPI() ;
  723. float bpm = Client->GetActualBPM() ;
  724. double linear_progress = (sample_n + GuiBeatOffset) / n_samples ;
  725. int beat_n = ((int)(bpi * linear_progress) % bpi) + 1 ;
  726. double discrete_progress = (float)beat_n / bpi ;
  727. // update statusbar loop progress
  728. Gui->loop->updateBeatN(beat_n) ;
  729. Gui->loop->loopProgress = discrete_progress ; // linear_progress ;
  730. Gui->statusbar->setStatusR(String(bpi) + " bpi @ " + String(bpm) + " bpm") ;
  731. // sompute metro VU loop progress
  732. ValueTree metro_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::METRO_ID) ;
  733. double metro_is_muted = bool( metro_store[CONFIG::IS_MUTED_ID]) ;
  734. double metro_pan = double(metro_store[CONFIG::PAN_ID]) ;
  735. double metro_vu = (metro_is_muted) ? 0.0 : discrete_progress ;
  736. double metro_vu_l = (metro_pan < 0.0) ? metro_vu : metro_vu * (1.0 - metro_pan) ;
  737. double metro_vu_r = (metro_pan > 0.0) ? metro_vu : metro_vu * (1.0 + metro_pan) ;
  738. // update metro VU loop progress
  739. metro_store.setProperty(CONFIG::VU_LEFT_ID , metro_vu_l , nullptr) ;
  740. metro_store.setProperty(CONFIG::VU_RIGHT_ID , metro_vu_r , nullptr) ;
  741. }
  742. void LinJam::UpdateVuMeters()
  743. {
  744. #ifdef NO_UPDATE_VU_METERS_GUI
  745. return ;
  746. #endif // NO_UPDATE_VU_METERS_GUI
  747. double master_vu_l = 0.0 ; double master_vu_r = 0.0 ;
  748. /* update local VUs */
  749. int channel_n = -1 ; int channel_idx ;
  750. while (~(channel_idx = Client->EnumLocalChannels(++channel_n)))
  751. {
  752. ValueTree channel_store = Config->getChannelByIdx(Config->localChannels , channel_idx) ;
  753. double local_pan = double(channel_store[CONFIG::PAN_ID]) ;
  754. double local_vu_l = GetChannelDb(channel_idx) - GUI::VU_DB_MIN ;
  755. int stereo_status = int(channel_store[CONFIG::STEREO_ID]) ;
  756. if (!channel_store.isValid()) continue ;
  757. if (stereo_status == CONFIG::MONO)
  758. {
  759. channel_store.setProperty(CONFIG::VU_LEFT_ID , local_vu_l , nullptr) ;
  760. // compensate master VUs for local channel pan
  761. ScalePannedMonoVus(local_vu_l , local_pan , &master_vu_l , &master_vu_r) ; // mutates
  762. }
  763. else if (stereo_status == CONFIG::STEREO_L)
  764. {
  765. // ensure this faux-stereo channel has a matching faux-stereo pair channel
  766. int pair_idx = int(channel_store[CONFIG::PAIR_IDX_ID]) ;
  767. double local_vu_r = GetChannelDb(pair_idx) - GUI::VU_DB_MIN ;
  768. // compensate faux-stereo VUs for faux-stereo pan
  769. ComputePannedVus(local_pan , &local_vu_l , &local_vu_r) ; // mutates
  770. // update local channel VUs asynchronously
  771. channel_store.setProperty(CONFIG::VU_LEFT_ID , local_vu_l , nullptr) ;
  772. master_vu_l = AddDecibels(master_vu_l , local_vu_l) ;
  773. channel_store.setProperty(CONFIG::VU_RIGHT_ID , local_vu_r , nullptr) ;
  774. master_vu_r = AddDecibels(master_vu_r , local_vu_r) ;
  775. }
  776. }
  777. /* update remote VUs */
  778. int user_idx = -1 ; String user_name ;
  779. while ((user_name = GetRemoteUserName(++user_idx)).isNotEmpty())
  780. {
  781. Identifier user_id = Config->MakeUserId(user_name) ;
  782. ValueTree user_store = Config->getUserById(user_id) ;
  783. double remote_master_vu_l = 0.0 ;
  784. double remote_master_vu_r = 0.0 ; channel_n = -1 ; Identifier vu_id ;
  785. while (~(channel_idx = Client->EnumUserChannels(user_idx , ++channel_n)))
  786. {
  787. ValueTree channel_store = Config->getChannelByIdx(user_store , channel_idx) ;
  788. double remote_pan = double(channel_store[CONFIG::PAN_ID]) ;
  789. double remote_vu_l = GetChannelDb(user_idx , channel_idx) - GUI::VU_DB_MIN ;
  790. int stereo_status = int(channel_store[CONFIG::STEREO_ID]) ;
  791. if (!channel_store.isValid()) continue ;
  792. if (stereo_status == CONFIG::MONO)
  793. {
  794. channel_store.setProperty(CONFIG::VU_LEFT_ID , remote_vu_l , nullptr) ;
  795. // compensate remote master VUs for remote channel pan
  796. ScalePannedMonoVus( remote_vu_l , remote_pan ,
  797. &remote_master_vu_l , &remote_master_vu_r) ; // mutates
  798. }
  799. else if (stereo_status == CONFIG::STEREO_L)
  800. {
  801. // ensure this faux-stereo channel has a matching faux-stereo pair channel
  802. int pair_idx = int(channel_store[CONFIG::PAIR_IDX_ID]) ;
  803. double remote_vu_r = GetChannelDb(user_idx , pair_idx) - GUI::VU_DB_MIN ;
  804. // compensate faux-stereo VUs for faux-stereo pan
  805. ComputePannedVus(remote_pan , &remote_vu_l , &remote_vu_r) ; // mutates
  806. // update remote channel VUs asynchronously
  807. channel_store.setProperty(CONFIG::VU_LEFT_ID , remote_vu_l , nullptr) ;
  808. remote_master_vu_l = AddDecibels(remote_master_vu_l , remote_vu_l) ;
  809. channel_store.setProperty(CONFIG::VU_RIGHT_ID , remote_vu_r , nullptr) ;
  810. remote_master_vu_r = AddDecibels(remote_master_vu_r , remote_vu_r) ;
  811. }
  812. }
  813. // update remote master VUs asynchronously
  814. ValueTree remote_master_store = Config->getChannelById(user_id , CONFIG::MASTER_ID) ;
  815. double remote_master_pan = double(remote_master_store[CONFIG::PAN_ID]) ;
  816. if (!remote_master_store.isValid()) continue ;
  817. // compensate remote master VUs for remote master pan
  818. if (remote_master_pan > 0.0)
  819. ScalePannedMonoVus( remote_master_vu_l , remote_master_pan ,
  820. &remote_master_vu_l , &remote_master_vu_r) ; // mutates
  821. if (remote_master_pan < 0.0)
  822. ScalePannedMonoVus( remote_master_vu_r , remote_master_pan ,
  823. &remote_master_vu_l , &remote_master_vu_r) ; // mutates
  824. remote_master_store.setProperty(CONFIG::VU_LEFT_ID , remote_master_vu_l , nullptr) ;
  825. remote_master_store.setProperty(CONFIG::VU_RIGHT_ID , remote_master_vu_r , nullptr) ;
  826. master_vu_l = AddDecibels(master_vu_l , remote_master_vu_l) ;
  827. master_vu_r = AddDecibels(master_vu_r , remote_master_vu_r) ;
  828. }
  829. /* update master VU */
  830. ValueTree master_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::MASTER_ID) ;
  831. double master_pan = double(master_store[CONFIG::PAN_ID]) ;
  832. // compensate master VUs for master pan
  833. if (master_pan > 0.0)
  834. ScalePannedMonoVus(master_vu_l , master_pan , &master_vu_l , &master_vu_r) ; // mutates
  835. if (master_pan < 0.0)
  836. ScalePannedMonoVus(master_vu_r , master_pan , &master_vu_l , &master_vu_r) ; // mutates
  837. master_store.setProperty(CONFIG::VU_LEFT_ID , master_vu_l , nullptr) ;
  838. master_store.setProperty(CONFIG::VU_RIGHT_ID , master_vu_r , nullptr) ;
  839. }
  840. void LinJam::UpdateRooms()
  841. {
  842. #ifdef NO_UPDATE_ROOMS_GUI
  843. return ;
  844. #endif // NO_UPDATE_ROOMS_GUI
  845. SetPollUrl() ; // TODO: shuld be done elsewhere on some state changes
  846. String response = PollUrl.readEntireTextStream() ;
  847. StringArray rooms = APP::ParseLines(response) ;
  848. int userdata_idx = (Status == APP::NJC_STATUS_OK) ? rooms.size() - 1 : -1 ;
  849. String userdata = APP::Pluck(&rooms , userdata_idx) ;
  850. DEBUG_UPDATE_ROOMS_RESP
  851. DEBUG_UPDATE_ROOMS_USERDATA
  852. for (int room_n = 0 ; room_n < rooms.size() ; ++room_n)
  853. {
  854. StringArray nicks = APP::ParseCSV(rooms[room_n]) ;
  855. String host = APP::Pluck(&nicks , 0) ;
  856. ValueTree clients = ValueTree(CONFIG::CLIENTS_ID) ;
  857. nicks.trim() ; nicks.removeEmptyStrings() ;
  858. DEBUG_UPDATE_ROOMS_ROOMDATA
  859. while (nicks.size() > 0)
  860. {
  861. String nick = APP::Pluck(&nicks , 0) ;
  862. ValueTree nick_store = ValueTree(Config->MakeUserId(nick)) ;
  863. nick_store.setProperty(CONFIG::LOGIN_ID , var(nick) , nullptr) ;
  864. clients.addChild(nick_store , -1 , nullptr) ;
  865. }
  866. ValueTree clients_store = Config->getServer(host).getChildWithName(CONFIG::CLIENTS_ID) ;
  867. for (int client_n = 0 ; client_n < clients_store.getNumChildren() ; ++client_n)
  868. {
  869. ValueTree client_store = clients_store.getChild(client_n) ;
  870. if (!clients.getChildWithName(client_store.getType()).isValid())
  871. clients_store.removeChild(client_store , nullptr) ;
  872. }
  873. while (clients.getNumChildren() > 0)
  874. {
  875. ValueTree client = clients.getChild(0) ; clients.removeChild(client , nullptr) ;
  876. if (!clients_store.getChildWithName(client.getType()).isValid())
  877. clients_store.addChild(client , -1 , nullptr) ;
  878. }
  879. }
  880. // sort rooms by occupancy
  881. Config->servers.sort(*RoomSorter , nullptr , true) ;
  882. }
  883. void LinJam::UpdateRecordingTime()
  884. {
  885. #ifdef NO_UPDATE_RECORDING_TIME_GUI
  886. return ;
  887. #endif // NO_UPDATE_RECORDING_TIME_GUI
  888. if (Status != APP::NJC_STATUS_OK) return ;
  889. // NOTE: parsing recording time is somewhat brittle - (issue #64)
  890. // dependent on constants such as NETWORK::KNOWN_BOTS and CLIENT::BOT_CHANNELIDX
  891. // though these values are more conventional than canonical
  892. int bot_useridx = Config->server[CONFIG::BOT_USERIDX_ID] ;
  893. String host = String(Client->GetHostName()) ;
  894. String bpi = String(Client->GetBPI()) ;
  895. String bpm = String((int)Client->GetActualBPM()) ;
  896. String recording_time = String() ;
  897. if (~bot_useridx)
  898. {
  899. recording_time = GetRemoteChannelClientName(bot_useridx , CLIENT::BOT_CHANNELIDX) ;
  900. bool has_recording_time_changed = !!recording_time.compare(PrevRecordingTime) ;
  901. bool is_this_first_pass = PrevRecordingTime.isEmpty() ;
  902. bool should_show_time = (has_recording_time_changed && !is_this_first_pass) ;
  903. PrevRecordingTime = recording_time ;
  904. recording_time = (should_show_time)? " - " + recording_time : String() ;
  905. }
  906. Gui->setTitle(host + " - " + bpi + "bpi / " + bpm + "bpm" + recording_time) ;
  907. }
  908. /* NJClient configuration */
  909. void LinJam::ConfigureAudio()
  910. {
  911. // defer initialization of audio devices to accommodate flooding
  912. // e.g. per ConfigAudio::restoreDefults()
  913. Status = APP::LINJAM_STATUS_AUDIOINIT ;
  914. Timer->startTimer(APP::AUDIO_INIT_TIMER_ID , APP::AUDIO_INIT_DELAY) ;
  915. }
  916. void LinJam::ConfigureMasterChannel(Identifier a_key)
  917. {
  918. ValueTree master_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::MASTER_ID) ;
  919. if (!master_store.isValid()) return ;
  920. // load stored config for this channel
  921. float volume = float(master_store[CONFIG::VOLUME_ID ]) ;
  922. float pan = float(master_store[CONFIG::PAN_ID ]) ;
  923. bool is_muted = bool( master_store[CONFIG::IS_MUTED_ID]) ;
  924. // determine which NJClient channel params to modify
  925. bool should_init_all = (a_key == CONFIG::CONFIG_INIT_ID ) ;
  926. bool should_set_volume = (a_key == CONFIG::VOLUME_ID || should_init_all) ;
  927. bool should_set_pan = (a_key == CONFIG::PAN_ID || should_init_all) ;
  928. bool should_set_is_muted = (a_key == CONFIG::IS_MUTED_ID || should_init_all) ;
  929. // configure NJClient master channel
  930. if (should_set_volume ) Client->config_mastervolume = (float)DB2VAL(volume) ;
  931. if (should_set_pan ) Client->config_masterpan = pan ;
  932. if (should_set_is_muted) Client->config_mastermute = is_muted ;
  933. }
  934. void LinJam::ConfigureMetroChannel(Identifier a_key)
  935. {
  936. ValueTree metro_store = Config->getChannelById(CONFIG::MASTERS_ID , CONFIG::METRO_ID) ;
  937. if (!metro_store.isValid()) return ;
  938. // load stored config for this channel
  939. float volume = float(metro_store[CONFIG::VOLUME_ID ]) ;
  940. float pan = float(metro_store[CONFIG::PAN_ID ]) ;
  941. bool is_muted = bool( metro_store[CONFIG::IS_MUTED_ID]) ;
  942. int source_n = int( metro_store[CONFIG::SOURCE_N_ID]) ;
  943. bool is_stereo = int( metro_store[CONFIG::STEREO_ID ]) != CONFIG::MONO ;
  944. // determine which NJClient channel params to modify
  945. bool should_init_all = (a_key == CONFIG::CONFIG_INIT_ID ) ;
  946. bool should_set_volume = (a_key == CONFIG::VOLUME_ID || should_init_all) ;
  947. bool should_set_pan = (a_key == CONFIG::PAN_ID || should_init_all) ;
  948. bool should_set_is_muted = (a_key == CONFIG::IS_MUTED_ID || should_init_all) ;
  949. bool should_set_source_n = (a_key == CONFIG::SOURCE_N_ID || should_init_all) ;
  950. bool should_set_stereo = (a_key == CONFIG::STEREO_ID || should_init_all) ;
  951. // configure NJClient metro channel
  952. if (should_set_volume ) Client->config_metronome = (float)DB2VAL(volume) ;
  953. if (should_set_pan ) Client->config_metronome_pan = pan ;
  954. if (should_set_is_muted) Client->config_metronome_mute = is_muted ;
  955. if (should_set_source_n) Client->config_metronome_channel = source_n ;
  956. if (should_set_stereo ) Client->config_metronome_stereoout = is_stereo ;
  957. }
  958. void LinJam::ConfigureLocalChannel(ValueTree channel_store , Identifier a_key)
  959. {
  960. // filter uninteresting config params
  961. if (!channel_store.isValid() ||
  962. a_key == CONFIG::CHANNEL_IDX_ID || a_key == CONFIG::PAIR_IDX_ID ||
  963. a_key == CONFIG::VU_LEFT_ID || a_key == CONFIG::VU_RIGHT_ID ) return ;
  964. // determine which NJClient channel params to modify
  965. bool should_init_all = (a_key == CONFIG::CONFIG_INIT_ID ) ;
  966. bool should_set_name = (a_key == CONFIG::CHANNEL_NAME_ID || should_init_all) ;
  967. bool should_set_volume = (a_key == CONFIG::VOLUME_ID || should_init_all) ;
  968. bool should_set_pan = (a_key == CONFIG::PAN_ID ||
  969. a_key == CONFIG::STEREO_ID || should_init_all) ;
  970. bool should_set_is_xmit = (a_key == CONFIG::IS_XMIT_RCV_ID || should_init_all) ;
  971. bool should_set_is_muted = (a_key == CONFIG::IS_MUTED_ID || should_init_all) ;
  972. bool should_set_is_solo = (a_key == CONFIG::IS_SOLO_ID || should_init_all) ;
  973. bool should_set_source_n = (a_key == CONFIG::SOURCE_N_ID || should_init_all) ;
  974. bool should_set_bit_depth = (a_key == CONFIG::BIT_DEPTH_ID || should_init_all) ;
  975. bool should_set_is_stereo = (a_key == CONFIG::STEREO_ID || should_init_all) ;
  976. // load stored config for this channel
  977. String channel_name = str( channel_store[CONFIG::CHANNEL_NAME_ID]) ;
  978. int channel_idx = int( channel_store[CONFIG::CHANNEL_IDX_ID ]) ;
  979. int pair_idx = int( channel_store[CONFIG::PAIR_IDX_ID ]) ;
  980. float volume = float(channel_store[CONFIG::VOLUME_ID ]) ;
  981. float pan = float(channel_store[CONFIG::PAN_ID ]) ;
  982. bool is_xmit = bool( channel_store[CONFIG::IS_XMIT_RCV_ID ]) ;
  983. bool is_muted = bool( channel_store[CONFIG::IS_MUTED_ID ]) ;
  984. bool is_solo = bool( channel_store[CONFIG::IS_SOLO_ID ]) ;
  985. int source_n = int( channel_store[CONFIG::SOURCE_N_ID ]) ;
  986. int bit_depth = int( channel_store[CONFIG::BIT_DEPTH_ID ]) ;
  987. int stereo_status = int( channel_store[CONFIG::STEREO_ID ]) ;
  988. channel_name = Config->MakeStereoName(channel_name , stereo_status) ;
  989. DEBUG_TRACE_CONFIGURE_LOCAL_CHANNEL
  990. // handle channel name change
  991. const char* new_name = (should_set_name)? channel_name.toRawUTF8() : nullptr ;
  992. // handle faux-stereo panning
  993. if (should_set_pan) pan = ClientPan(pan , stereo_status) ;
  994. // configure NJClient local channel
  995. if (should_set_name || should_set_source_n || should_set_bit_depth || should_set_is_xmit)
  996. Client->SetLocalChannelInfo(channel_idx , new_name ,
  997. should_set_source_n , source_n ,
  998. should_set_bit_depth , bit_depth ,
  999. should_set_is_xmit , is_xmit ) ;
  1000. if (should_set_volume || should_set_pan || should_set_is_muted || should_set_is_solo)
  1001. Client->SetLocalChannelMonitoring(channel_idx ,
  1002. should_set_volume , (float)DB2VAL(volume) ,
  1003. should_set_pan , pan ,
  1004. should_set_is_muted , is_muted ,
  1005. should_set_is_solo , is_solo ) ;
  1006. // configure faux-stereo pair implicitly
  1007. if (stereo_status == CONFIG::STEREO_L)
  1008. {
  1009. if (should_set_is_stereo)
  1010. {
  1011. // handle mono->stereo conversion
  1012. if (~(pair_idx = GetVacantLocalChannelIdx()))
  1013. {
  1014. a_key = CONFIG::CONFIG_INIT_ID ;
  1015. channel_store.setProperty(CONFIG::PAIR_IDX_ID , pair_idx , nullptr) ;
  1016. }
  1017. else
  1018. {
  1019. channel_store.setProperty(CONFIG::STEREO_ID , CONFIG::MONO , nullptr) ;
  1020. return ;
  1021. }
  1022. }
  1023. // force mono conversion bypassing configuration of pair to be deleted (fires next case)
  1024. if (a_key == CONFIG::SOURCE_N_ID && source_n % 2)
  1025. channel_store.setProperty(CONFIG::STEREO_ID , CONFIG::MONO , nullptr) ;
  1026. // configure unstored pair channel
  1027. else
  1028. {
  1029. ValueTree pair_store = channel_store.createCopy() ;
  1030. String pair_name = Config->MakeStereoName(channel_name , CONFIG::STEREO_R) ;
  1031. int pair_source_n = source_n + 1 ;
  1032. pair_store.setProperty(CONFIG::CHANNEL_NAME_ID , pair_name , nullptr) ;
  1033. pair_store.setProperty(CONFIG::CHANNEL_IDX_ID , pair_idx , nullptr) ;
  1034. pair_store.setProperty(CONFIG::SOURCE_N_ID , pair_source_n , nullptr) ;
  1035. pair_store.setProperty(CONFIG::STEREO_ID , CONFIG::STEREO_R , nullptr) ;
  1036. ConfigureLocalChannel(pair_store , a_key) ;
  1037. }
  1038. // update client channels names on mono->stereo conversion
  1039. if (should_set_is_stereo)
  1040. channel_store.setProperty(CONFIG::CHANNEL_NAME_ID , channel_name , nullptr) ;
  1041. }
  1042. // handle stereo->mono conversion
  1043. else if (stereo_status == CONFIG::MONO && should_set_is_stereo)
  1044. {
  1045. Client->DeleteLocalChannel(pair_idx) ;
  1046. channel_store.setProperty(CONFIG::PAIR_IDX_ID , CONFIG::DEFAULT_CHANNEL_IDX , nullptr) ;
  1047. channel_store.setProperty(CONFIG::CHANNEL_NAME_ID , channel_name , nullptr) ;
  1048. }
  1049. Client->NotifyServerOfChannelChange() ;
  1050. }
  1051. void LinJam::ConfigureRemoteChannel(ValueTree user_store , ValueTree channel_store ,
  1052. Identifier a_key )
  1053. {
  1054. // filter uninteresting config params
  1055. if (!user_store.isValid() || !channel_store.isValid() ||
  1056. a_key == CONFIG::CHANNEL_NAME_ID ||
  1057. a_key == CONFIG::CHANNEL_IDX_ID || a_key == CONFIG::PAIR_IDX_ID ||
  1058. a_key == CONFIG::VU_LEFT_ID || a_key == CONFIG::VU_RIGHT_ID ) return ;
  1059. // determine which NJClient channel params to modify
  1060. bool should_init_all = (a_key == CONFIG::CONFIG_INIT_ID ) ;
  1061. bool should_set_volume = (a_key == CONFIG::VOLUME_ID || should_init_all) ;
  1062. bool should_set_pan = (a_key == CONFIG::PAN_ID ||
  1063. a_key == CONFIG::STEREO_ID || should_init_all) ;
  1064. bool should_set_is_rcv = (a_key == CONFIG::IS_XMIT_RCV_ID || should_init_all) ;
  1065. bool should_set_is_muted = (a_key == CONFIG::IS_MUTED_ID || should_init_all) ;
  1066. bool should_set_is_solo = (a_key == CONFIG::IS_SOLO_ID || should_init_all) ;
  1067. bool should_set_is_stereo = (a_key == CONFIG::STEREO_ID || should_init_all) ;
  1068. // load stored config for this channel
  1069. int user_idx = int( user_store [CONFIG::USER_IDX_ID ]) ;
  1070. int channel_idx = int( channel_store[CONFIG::CHANNEL_IDX_ID]) ;
  1071. int pair_idx = int( channel_store[CONFIG::PAIR_IDX_ID ]) ;
  1072. float volume = float(channel_store[CONFIG::VOLUME_ID ]) ;
  1073. float pan = float(channel_store[CONFIG::PAN_ID ]) ;
  1074. bool is_rcv = bool( channel_store[CONFIG::IS_XMIT_RCV_ID]) ;
  1075. bool is_muted = bool( channel_store[CONFIG::IS_MUTED_ID ]) ;
  1076. bool is_solo = bool( channel_store[CONFIG::IS_SOLO_ID ]) ;
  1077. int sink_n = 0 ; // TODO: not yet clear how to handle remote sink_n
  1078. int stereo_status = int( channel_store[CONFIG::STEREO_ID ]) ; \
  1079. bool is_pannable = true ;
  1080. DEBUG_TRACE_CONFIGURE_REMOTE_CHANNEL
  1081. if (channel_idx == CONFIG::MASTER_CHANNEL_IDX)
  1082. {
  1083. int channel_n = -1 ;
  1084. // configure NJClient remote master channel
  1085. if (should_set_volume || should_set_pan || should_set_is_muted)
  1086. Client->SetUserState(user_idx ,
  1087. should_set_volume , (float)DB2VAL(volume) ,
  1088. should_set_pan , pan ,
  1089. should_set_is_muted , is_muted ) ;
  1090. // or apply user master pseudo rcv or solo control over all real user channels
  1091. else if (should_set_is_rcv || should_set_is_solo)
  1092. while (~(channel_idx = Client->EnumUserChannels(user_idx , ++channel_n)))
  1093. {
  1094. channel_store = Config->getChannelByIdx(user_store , channel_idx) ;
  1095. ConfigureRemoteChannel(user_store , channel_store , a_key) ;
  1096. }
  1097. }
  1098. else
  1099. {
  1100. // handle faux-stereo panning
  1101. if (should_set_pan) pan = ClientPan(pan , stereo_status) ;
  1102. // configure NJClient remote channel allowing master overrides
  1103. ValueTree master_store = Config->getUserMasterChannel(user_store) ;
  1104. bool is_master_rcv = bool(master_store[CONFIG::IS_XMIT_RCV_ID]) ;
  1105. bool is_master_solo = bool(master_store[CONFIG::IS_SOLO_ID ]) ;
  1106. Client->SetUserChannelState(user_idx , channel_idx ,
  1107. should_set_is_rcv , is_rcv && is_master_rcv ,
  1108. should_set_volume , (float)DB2VAL(volume) ,
  1109. should_set_pan , pan ,
  1110. should_set_is_muted , is_muted ,
  1111. should_set_is_solo , is_solo || is_master_solo ,
  1112. should_init_all , sink_n ,
  1113. should_init_all , is_pannable ) ;
  1114. // configure faux-stereo pair implicitly
  1115. if (stereo_status == CONFIG::STEREO_L)
  1116. {
  1117. ValueTree pair_store = Config->getChannelByIdx(user_store , pair_idx) ;
  1118. if (should_set_is_stereo || should_init_all)
  1119. ConfigureRemoteChannel(user_store , pair_store , CONFIG::CONFIG_INIT_ID) ;
  1120. else pair_store.setProperty(a_key , channel_store[a_key] , nullptr) ;
  1121. }
  1122. }
  1123. }
  1124. /* audio signal helpers */
  1125. double LinJam::AddDecibels(double vu_l , double vu_r)
  1126. {
  1127. return 10 * log10(pow(10.0 , (vu_l / 10.0)) + pow(10.0 , (vu_r / 10.0))) ;
  1128. }
  1129. void LinJam::ComputePannedVus(double pan , double* vu_l , double* vu_r)
  1130. {
  1131. double vu_l_in = *vu_l ;
  1132. double vu_r_in = *vu_r ;
  1133. // scale master VU meters per master pan
  1134. if (pan > 0.0) // pan right
  1135. {
  1136. // decrease left contribution to left vu
  1137. *vu_l *= log10(10.0 * (1.0 - pan)) ;
  1138. // increase left contribution to right vu
  1139. *vu_r = AddDecibels(vu_r_in , vu_l_in * pan) ;
  1140. }
  1141. if (pan < 0.0) // pan left
  1142. {
  1143. // increase right contribution to left VU
  1144. *vu_l = AddDecibels(vu_l_in , vu_r_in * -pan) ;
  1145. // decrease right contribution to right VU
  1146. *vu_r *= log10(10.0 * (1.0 + pan)) ;
  1147. }
  1148. // cap underflows
  1149. if (isnan(*vu_l)) *vu_l = 0.0 ;
  1150. if (isnan(*vu_r)) *vu_r = 0.0 ;
  1151. if (vu_l_in == 0.0 && vu_r_in == 0.0) *vu_l = *vu_r = 0.0 ;
  1152. }
  1153. void LinJam::ScalePannedMonoVus(double vu_mono , double pan , double* vu_l , double* vu_r)
  1154. {
  1155. // compute panned mono channel contributions to left and right master VU meters
  1156. *vu_l = (pan > 0.0) ? vu_mono * log10(10.0 * (1.0 - pan)) : vu_mono ; // pan_left
  1157. *vu_r = (pan < 0.0) ? vu_mono * log10(10.0 * (1.0 + pan)) : vu_mono ; // pan right
  1158. // cap underflows
  1159. if (isnan(*vu_l)) *vu_l = 0.0 ;
  1160. if (isnan(*vu_r)) *vu_r = 0.0 ;
  1161. }
  1162. float LinJam::ClientPan(float pan , int stereo_status)
  1163. {
  1164. // interpret faux-stereo pan for a real NJClient channel
  1165. bool is_mono_channel = stereo_status == CONFIG::MONO ;
  1166. bool is_pair_channel = stereo_status == CONFIG::STEREO_R ;
  1167. return (is_mono_channel)? pan : (!is_pair_channel) ?
  1168. ((pan <= 0.0f) ? -1.0f : -1.0f + (pan * 2.0f)) :
  1169. ((pan >= 0.0f) ? +1.0f : +1.0f + (pan * 2.0f)) ;
  1170. }
  1171. /* NJClient config helpers */
  1172. int LinJam::GetNumAudioSources()
  1173. {
  1174. return (Audio != nullptr)? Audio->getNInputChannels() : 0 ;
  1175. }
  1176. int LinJam::GetNumLocalChannels()
  1177. {
  1178. int n_occupied_slots = -1 ; while (~Client->EnumLocalChannels(++n_occupied_slots)) ;
  1179. return n_occupied_slots ;
  1180. }
  1181. int LinJam::GetNumVacantChannels()
  1182. {
  1183. return (Audio != nullptr)? GetNumAudioSources() - GetNumLocalChannels() : 0 ;
  1184. }
  1185. int LinJam::GetVacantLocalChannelIdx()
  1186. {
  1187. // find the first vacant NJClient local channel slot index
  1188. int channel_idx = -1 ; while (IsConfiguredChannel(++channel_idx)) ;
  1189. bool is_vacant_slot = (channel_idx < GetNumAudioSources()) ;
  1190. return (is_vacant_slot)? channel_idx : -1 ;
  1191. }
  1192. String LinJam::GetStoredChannelName(ValueTree channel_store)
  1193. {
  1194. return str(channel_store[CONFIG::CHANNEL_NAME_ID]) ;
  1195. }
  1196. String LinJam::GetLocalChannelClientName(int channel_idx)
  1197. {
  1198. return CharPointer_UTF8(Client->GetLocalChannelName(channel_idx)) ;
  1199. }
  1200. String LinJam::GetRemoteUserName(int user_idx)
  1201. {
  1202. return CharPointer_UTF8(Client->GetUserState(user_idx)) ;
  1203. }
  1204. String LinJam::GetRemoteChannelClientName(int user_idx , int channel_idx)
  1205. {
  1206. return CharPointer_UTF8(Client->GetUserChannelState(user_idx , channel_idx)) ;
  1207. }
  1208. bool LinJam::IsConfiguredChannel(int channel_idx)
  1209. {
  1210. return GetLocalChannelClientName(channel_idx).isNotEmpty() ;
  1211. }
  1212. double LinJam::GetChannelDb(int channel_idx)
  1213. {
  1214. return VAL2DB(Client->GetLocalChannelPeak(channel_idx)) ;
  1215. }
  1216. double LinJam::GetChannelDb(int user_idx , int channel_idx)
  1217. {
  1218. return VAL2DB(Client->GetUserChannelPeak(user_idx , channel_idx)) ;
  1219. }
  1220. /* signalling */
  1221. void LinJam::SetPollUrl()
  1222. {
  1223. // TODO: server should handle empty params
  1224. // String current_user = Client->GetUserName() ;
  1225. // String current_user = Config->server[CONFIG::LOGIN_ID] ;
  1226. String current_user = "sumdood" ;
  1227. String current_host = String(Client->GetHostName()) ;
  1228. StringPairArray poll_params = StringPairArray() ;
  1229. poll_params.set(NETWORK::LOGIN_KEY , current_user) ;
  1230. if (current_host.isNotEmpty()) poll_params.set (NETWORK::HOST_KEY , current_host) ;
  1231. else poll_params.remove(StringRef(NETWORK::HOST_KEY)) ;
  1232. PollUrl = NETWORK::POLL_URL.withParameters(poll_params) ;
  1233. }