Merge pull request 'Antigona Release' (#1) from filters into main

Reviewed-on: #1
This commit is contained in:
snt 2024-05-26 12:42:53 +00:00
commit 84dae057db
47 changed files with 3167 additions and 1673 deletions

View file

@ -1,55 +1,99 @@
皾湏毄讍悊€潫憙嚈曀寑剛扰臓崙櫗泛疮鷼岸沪瓲驖か<EFBFBD><EFBFBD><EFBFBD><EFBFBD>堪彪Й称蛽咴斕与栧蚜赁尬履匾娤了脜绿衼祧<EFBFBD><EFBFBD><EFBFBD><EFBFBD>雠疰鬻惆<EFBFBD><EFBFBD>
堀阜刀ā相槜灜鬯勖
и拍媚菀<EFBFBD>彏帶寣巼秮憯剴€煥覆蝶趱毤丁窚但К煯缚<EFBFBD>溟驷钽伿谠訖<EFBFBD>
増唵檮儌劀煘潨嚉櫂棅晹搾崘<EFBFBD><EFBFBD><EFBFBD>┅沪イ⒕ 榭〖缓垢范胆
≡顡嫄欃稚惹婆拿禽肋掭苒谫<EFBFBD>
哦剨廖研镱盱腓蹊珂邃汊帱
<EFBFBD><EFBFBD>魂膂豸篁耩崚崒媻増噺<EFBFBD>
桋圯諐倽湜殭槜攭敁拺惎<EFBFBD><EFBFBD>
故貔铘赭<EFBFBD>綘烤郊缓护范荡巢睘<EFBFBD>
呖敂崐槡棕寐晾咿蒉勤儇字赵佄
枚安ū菌勘钩鼬汊徉<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺趔<EFBFBD>
銋咽吓浭盅俾棙儌亐煘潥嚉櫂棖晹悗
冨耜镥皇鲴<EFBFBD><EFBFBD>臣烤郊缓归<EFBFBD>荡巢毕脱
拶稚惹婆拿垒肋掭苒谯<EFBFBD>
拍稍右研镱镳腙殍珂彐<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺趔蝰饙<EFBFBD>
煘棅増噯厔仦亐煘潨洏<EFBFBD>
妳姇敁拺惎<EFBFBD><EFBFBD><EFBFBD>ěΗ<EFBFBD>
卑綘烤郊缓护范荡巢蓖<EFBFBD>
咿资扇瞧拍赁晾咿蒈圬<EFBFBD>
刈中扔已畜铐潆鲩桤琦溷赆<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>牾趔黜饛帊悑妷帥唴寖<EFBFBD>
亐煔亷<EFBFBD>妊厞晹搾崘<EFBFBD><EFBFBD>┅沪イ<EFBFBD>
郊痪オ则牯‘毕夏兴适邵婆穆蘖肋掭苒谂
刈盅攘皠摢掣<EFBFBD>觊脲<EFBFBD>溏除<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺麸
蝰饖挓筝軟攪唴劅倎仐倽湜泤槜枙敁拺<EFBFBD>
<EFBFBD><EFBFBD>犯婶鲲黪瘗臣烤嫉Ш够丢荡吵<EFBFBD>瓮趟噬熔
婆拿铝肋<EFBFBD>
蒈圳儇字稍右研镱眇腙殍珂邃<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺趔蝽饛帊寢妷攪唴剝倎€優潨洑櫂棅晹搾憪<EFBFBD><EFBFBD>Е<EFBFBD>烤郊Ш垢范荡钞毕瓮趟噬郧婆拿铝烂掭苒谫刈收杂已酗铖
<EFBFBD>鲩桤驽溷恺<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺趔铖饛帒寢妷<EFBFBD>
噯厔儌亣優湝泦櫃枱墧捛悓
<EFBFBD><EFBFBD><EFBFBD>┊沪が<EFBFBD>烤〖翰工
范荡巢馍彝趟收惹婆孛铝烂
掭苒谫嬓收杂彝酗铐痣觊棼
驽溷忉锄恺<EFBFBD><EFBFBD><EFBFBD>鲺梵篑驌
帊寢妷蹆殔厓倽€煘潃洓櫂<EFBFBD>
枙敁拺惃箔<EFBFBD><EFBFBD>ěΕ
窘蓟汗攵<EFBFBD>闯箔衔吞资扇勤
拍寐晾屵淋圳倌字赵弦研矧
盱腙殍存<EFBFBD>汊狳<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
豸篁耩軒憣媻墧噯厔焸亐焸
潨洑櫂臇墧搾憣<EFBFBD><EFBFBD>藩┄Ш
<EFBFBD>炀〖缓工范荡<EFBFBD>毕窝
趟噬惹暸孛铝烂掭苒瀑刈稚
杂已酗巾痣觊棼驽溷<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD>ヵ梵蝰饟帊寢枆垏啓
柵偀€湆<EFBFBD>
湜殭剠剦唩巸偝<EFBFBD><EFBFBD>订ěЧぃ伎窘姬汗缚<EFBFBD>闯脖衔托
耸扇瞧牌<EFBFBD>
杏芡塘紊<EFBFBD>
倨字赵右托耦盱腙轸瑛邃汊徉泾泓<EFBFBD><EFBFBD>麝蹶篁耩弾憣晩増噯厴
憪潃煘潃
泦檮棅晥搸憣<EFBFBD><EFBFBD><EFBFBD>Е<EFBFBD>
⒔牐尽姬亥斧订疮箔弦托
酥稍勤
<EFBFBD>
皾湏毄讍悊€潫憙嚈曀寑剛扰臓崙櫗泛疮鷼岸沪瓲驖か<EFBFBD><EFBFBD><EFBFBD><EFBFBD>堪彪Й称蛽咴斕<EFBFBD>
霒逖亮揶温呢見狭嗣吢绦侅彝叫蝤臊隗颀蛄翕<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
些闯旱榆皾湏毄浊紫
晾咻廖Ё悍Τ<EFBFBD><EFBFBD>洓稔鼬晻勽<EFBFBD>灊晻<EFBFBD>犷姛崄堜<EFBFBD>
趔篪鞆帊嫍妷堈殔剝倽€煘潃洑櫂嫋晹拵憪<EFBFBD><EFBFBD><EFBFBD>Е<EFBFBD><EFBFBD>
<EFBFBD>们翁讠<EFBFBD>闯肮舷闲耸扇瞧磐<EFBFBD>
蟹梗<EFBFBD>累谫谶衷窒已酗铐煦<EFBFBD>
麕负牴厄椆<EFBFBD><EFBFBD>黯觚矧耩弾崒嫋
涱刳蕾謵潃煘潨洑渼棖晹搾憪<EFBFBD>
钾蝻栲盖<EFBFBD><EFBFBD>沧籴<EFBFBD>蓟夯胺蜂ǔ脖衔屯樦
劢檪噸英灆仛侠圳仝咧砸弦研镱眄个
麍陡鼬汊徉<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺酾<EFBFBD>
銗质拊櫀増噯厔個潃煘潨洑殑
咑膳诶烂艏爆<EFBFBD><EFBFBD>ЁЦ<EFBFBD>烤骄<EFBFBD>
<EFBFBD>丕厉徉牠衔吞耸耸燮拍寐晾俾
蠣劃僧晝喥弦研镱眍轹殍珂邃沐<EFBFBD>
騼『鞖桢<EFBFBD>鲺趔痼鞆帊寢妶垱
旟勰掱哑缽€洑櫂棖棖彃憪<EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ш垢范刀碑毕瓮趟怂<EFBFBD>
栈及拷π咎淋圳儇自兹右研镱祜<EFBFBD>
鴶憮殮凅晲檨灬<EFBFBD><EFBFBD><EFBFBD>赭梵蝰饛帉迼
橍耋桚<EFBFBD>鷳鳊朦巼殭槜枙枒帒惎<EFBFBD><EFBFBD><EFBFBD>
黑哐篡壑丿忒牷汗阜斗动脖衔吞事<EFBFBD>
诤砍曰欢赶┈ゥ扰刈终杂杏田铐祀觇猁
魸潝汃枬嫓铉<EFBFBD><EFBFBD>鲺鲴铖饛帊寛迺
<EFBFBD><EFBFBD><EFBFBD>螯撱摐煘潨洑洑嫋晹搾憮<EFBFBD>
吭逸迅凶苴本<EFBFBD>烤郊垢ジ范荡吵嘤
墀檲煁帓重詴洕剨塘苒谫刈鬃扔已酗铑瑚
鴬潤掯羼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺趔驙<EFBFBD>
<EFBFBD><EFBFBD>殝殯唴剝倎仢倽湜殭槜膲
敁挊尟<EFBFBD><EFBFBD><EFBFBD><EFBFBD>亥ぃ〗牽淼牷汗极兜闯<EFBFBD>衔刃耸扇燮拍呸晾邹淋圳弈字菰弦研珧盱腼蹊珑怿汊范泾<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
趔蝙鞚钛萋汌忠敊剝個湡灉攪殭槚姇敁拺惎<EFBFBD>
<EFBFBD><EFBFBD>吹裴<EFBFBD>敖牽看牷汉公兜床<EFBFBD>衔吞耸稍
瞧盘咝<EFBFBD>殱埼勤氽帐赵變托镱祓腙殍珂邃<EFBFBD>
忉圜怙湩<EFBFBD>鑻┆瘁铖饗軕寢寠攪唴厽倎€煘潨泦
櫂棡墕笪蕾控蝠<EFBFBD><EFBFBD>牒沪アЬ<EFBFBD>疙〖缓袱范荡巢毕<EFBFBD>
吞寺遮<EFBFBD>攳哟灉媼熖<EFBFBD>喪酥沼囄研骐耢腙梏珂邃汊徉<EFBFBD>
<EFBFBD><EFBFBD>骐槴Ъ鋮<EFBFBD>ぺ虧钰詻攪唽偀倎咽倽湜泤槜枙敁拺<EFBFBD>
截麴<EFBFBD>Еゥ竣 竣疥缓垢贩┐巢坝瓮倘<EFBFBD>
劬灇惼艳晾哌淋圹倌种赵右诱箢盱牿殍玟<EFBFBD>
鰰唤叠灬<EFBFBD><EFBFBD>澉鲷麸篌饛帊實枆垏嚈剝個<EFBFBD>
嶈睦螝媱棖晻彃憫<EFBFBD><EFBFBD><EFBFBD><EFBFBD>┄ェ工牸烤郊<EFBFBD>
ㄏ珉忄霭牠衔屯资缮勤睦寐晾挢淋圳倌字兆<EFBFBD>
困彸焊<EFBFBD><EFBFBD>桤驿<EFBFBD>忄嚆<EFBFBD><EFBFBD><EFBFBD><EFBFBD>牾趔蝽饛帍<EFBFBD>
欬衷佑菂憺亐煙仠洓檮柪晹搾搾钞<EFBFBD><EFBFBD>┄Ё<EFBFBD>
睹箐汴<EFBFBD>蓟焊し洞疮氨衔吞藶杖瞧拓寐聯<EFBFBD>
叹槃毸淖终障已扬蝻祀觊桄铢溷忾<EFBFBD><EFBFBD><EFBFBD>
铏う垮埇<EFBFBD>鈸帊寠枆垎啓唭倎€煘虁洑檺嫋晳枎
<EFBFBD><EFBFBD>浼贼<EFBFBD>沪イ⒕<EFBFBD>揪【缓垢范悒巢鼻彝趟险
惹婆拿铝<EFBFBD>
咿蒈圳儇酥赵右研矧盱腙殍珂<EFBFBD>汊徉<EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺麸蝰饛帊寢枆垏唴剝倽€煘潨洑檮棖晹搾憪钞<EFBFBD><EFBFBD>┄Шイ<EFBFBD>烤〖缓垢范胆巢毕瓮趟稚惹婆拿螺肋掭苒谫淖终杂已畜铐祀觊桤<EFBFBD>溷忉<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺梵蝰饛帊寳妷垏唴剝瀬€煘潨洑厴棖晹搾憣<EFBFBD><EFBFBD><EFBFBD>┄沪イ<EFBFBD>
<EFBFBD>Ш垢范荡钞毕瓮兴噬熔婆拿蘖肋蘖苒谫<EFBFBD>
字赵右傊箢祆膂殍珂<EFBFBD>汊狳
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>膂趑箢耩弾憣媻墧
噯厔儌亣優湝泦櫂棖墧捔檶
<EFBFBD><EFBFBD><EFBFBD>┋沪い<EFBFBD>烤〖洪堡
范荡巢庀彝退收惹婆孛铝烂
掭苒谫嬜收沼彝酗铐痣觊棼
驽溷忉楚恺<EFBFBD><EFBFBD><EFBFBD>鲺梵簌饟
帊寢妷蹌殔厠倽€煘潃洓憳<EFBFBD>
枙敁拺铆箔<EFBFBD>┑ěΕΓ
窘蓟汗鞍<EFBFBD>党箔衔吞资扇勤
拍寐晾邹淋谝倌字赵弦研矧
盱腙殍镧<EFBFBD>怅狳<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
豸篁耩噷憣妭墧噯厔焸亐焸
潨洑櫂煉墧挌憣<EFBFBD><EFBFBD>藩┄Ш
<EFBFBD>坊〖翰工范荡<EFBFBD>毕窝
趟噬惹蚊孛蒙烂掭苒瀑刈稚
杂已酗驽痣脶棼驽溷<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>梵簌饟帊寢枆垏啓
剝倎€煐虁洓憳嫋晹搸憪<EFBFBD><EFBFBD>
<EFBFBD><EFBFBD>ě<EFBFBD>牐窘蓟<EFBFBD>阜订
闯脖衔艠资认捼拍寐堇咿堇
圳儇字輥弦凶候盱腙蹊珂屮
汊徉<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>牾趔蝽饛帊<EFBFBD>
媻増噯謤焸€稚倽湜殔槚烂<EFBFBD>
搾憪<EFBFBD><EFBFBD><EFBFBD>窈イ綘捐霠
┢嚓蹉柔珉喱洀姙垿箓挏浹<EFBFBD>
晾咿廖善耸四侨右研箢盱牿殍珂<EFBFBD>汊狳<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>鲺趔<EFBFBD>
耩弾崒堒<EFBFBD>
殨殫枱悡<EFBFBD>
焵潨洑櫂嫋嫈搾憪<EFBFBD><EFBFBD><EFBFBD>Е工舰<EFBFBD>烤綘护垢范荡<EFBFBD><EFBFBD>瓮趟收
谡谂拿螺
烂蘖芮谂厮稚韵彝旭铐祀觊<EFBFBD>
琥屮泾狳<EFBFBD>恺帑纟澉牾梵<EFBFBD>
耢彃崘
増噯厔儌潃煘潨洑檮棖晹彃憪<EFBFBD><EFBFBD><EFBFBD>Е<EFBFBD>郊沪垢范┐巢庇瓮趟稚惹瀑拿铝苓掭芮谫刈收杂彝酗铐痣觊棼驽溷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>澉鲺麸蝰饛拲寢姇垏唴槂倎€優潨泦櫂棖<EFBFBD>
敁拺尟<EFBFBD><EFBFBD><EFBFBD>ě亥ぃ牸弧航景豢剧ǔ<EFBFBD>
<EFBFBD>
吞松<EFBFBD>
惹桥匮休懒掭苒谫淖日杂已畜铙祀觊桤<EFBFBD>
邃忏<EFBFBD><EFBFBD><EFBFBD><EFBFBD>黯轸眚耩弾崘嫈増噯厔<EFBFBD>
倎仢倧巼殗槜枙敁帒幆<EFBFBD><EFBFBD><EFBFBD>üΕ<EFBFBD>
<EFBFBD>郊缓垢范ù巢币瓮趟咨惹曝拿铝肋掭苒谫暸<EFBFBD>
允右研镱耢腙轸珂邃<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>黯轸睑麴弾崘嫈増噯厔焸焵煘潨泦
<EFBFBD>

View file

@ -1,35 +1,49 @@
*******************************************************************************
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Santiago Noreña 2012-2024 <lms@criptomart.net>
Code: https://git.criptomart.net/libremediaserver
LibreMediaServer Audio - An open source media server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
*******************************************************************************
Lbre Media Server ChangeLog
LibreMediaServer Changelog
v 0.1.3 (28/05/2024)
v 0.2.0 Antígona (26/05/2024)
+ Change audio engine to miniaudio because is imposible pan in SFML and it has not access to low API and audio processing.
+ Refactor all audio methods to MiniAudioEngine class.
+ Select sound device output.
+ Pan.
+ Show faders values. New SliderGroup class.
+ Entry Point 16 bits.
+ Refactor AudioMasterWidget to dmxWidget.
+ Read mp3, flac, wav (mp3 has given some errors seeking cursors...). Audio Engine is working at 48Khz and 32 bits pcm float, if media files are encoding at same configuration, it saves a resampling operation later live.
+ Removed settings dialog, only read xml conf file at startup.
+ Real dynamic variable number of layers based on conf file setting.
+ OlaThread send double channels (volume, entry point, load media) only once for each dmx frame buffer.
+ Terminal mode without graphical interface. All audio methods has been refactorized out of QWidget world.
+ Compilation without GUI (-DNOGUI).
+ New DMX personality version, better sort for audio needs (first load media, set vol, pan, etc, last playback order);
+ No QtSignals for sending data, better performance about 20% in my machine. Now, libremediaserver only updates values in AudioWidget, ui refresh is doing with a timer in audiowidget, so there is not problems between graphical and ola thread (the callback). Signals are used only from Ui to libreMediaServer to notify user interactions and Qtimers.
+ Load media files from ui clicking in the media labels.
+ New Play Modes:
- Play all medias found in one folder.
- Play all medias in one folder consecutevily.
- Play all medias in one folder randomly.
+ Multi audio devices output.
+ Vumeter for each layer
+ Show device name on Ui and ouput bus slider.
v 0.1.3 Leúcade (19/04/2024)
+ Ubuntu 22.04 jammy.
+ Use SFML as audio engine.
+ Qt 5.15.3.
+ pitch.
+ loop.
- pan.
- Show faders values.
--> Hacer UI por fader: mute/centrado, valor, visualizador:
--> Hacer UI con la visualización de tiempos.
- SettingsDialog.
- Load/save conf file.
+ Pitch.
+ Loop.
v 0.1.2 (12/08/2015)
- GUI config
- Several bugs tested in real world
- variable layers
- SFML as audio engine
v 0.1.1 (24/09/2014)
+ First Version: 4 layers playing .ogg
+ Needs Open Lighting Arquitecture => 0.9.0
+ Pure Data as audio engine
v 0.1.2 Mayordomo (12/08/2015)
+ GUI config.
+ Variable layers.
+ SFML as audio engine.
v 0.1.1 Pascual (24/09/2014)
+ First Version: 4 layers playing .ogg.
+ Needs Open Lighting Arquitecture => 0.9.0.
+ Pure Data as audio engine.
+ Qt4

View file

@ -1,6 +1,6 @@
*******************************************************************************
Libre Media Server Audio - An Open source Media Server.
(c) Santiago Noreña 2014-2024 <libremediaserver@criptomart.net>
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
*******************************************************************************
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.

View file

@ -1,7 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<dmxSettings fileVersion="1" layersNumber="4" path="/path/to/medias" universeNumber="1">
<layer0 dmx="1" universe="1" />
<layer1 dmx="17" universe="1" />
<layer2 dmx="33" universe="1" />
<layer2 dmx="49" universe="1" />
</dmxSettings>
<lmsAudio ui="1" layersNumber="4" path="../media/sound" >
<audioDevice devicesNumber="2" id0="3" id1="4"/>
<layer id="0" dmx="1" universe="1" audioDevice="0" />
<layer id="1" dmx="17" universe="1" audioDevice="0" />
<layer id="2" dmx="33" universe="1" audioDevice="1" />
<layer id="3" dmx="49" universe="1" audioDevice="1"/>
</lmsAudio>

View file

@ -1,51 +1,44 @@
*******************************************************************************
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Santiago Noreña 2012-2024 <lms@criptomart.net>
Code: https://git.criptomart.net/libremediaserver
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
*******************************************************************************
Libre Media Server Roadmap
(or a whislist...)
v 0.2.1
- skin, UI/UX
v 0.3.0
- Ui/Ux: skin, style.
- Ui/Ux; Keyboards strokes.
- Ui/Ux: Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante.
- Ui/Ux: seek cursor playback
- live input.
v 0.2.0
- Use sACN directly.
- remove ola and use sACN directly.
+ la instalación de OLA es mediante compilación, el repo de paquetes no está actualizado, nada user-friendly.
+ hay que empaquetar OLA, incluirlo en el binario, o implementar sACN y linkarlo estáticamente.
+ https://github.com/ETCLabs/sACN
- Qt6.
- audio processing (eq, rev, compresor, ...).
- CIPT/MSex, send icons play-pause-stop.
- CIPT/MSex.
- Rasp build.
- Octopus Sound Card support (6 outputs - 8 inputs).
v 1.5
- Select sound device output.
- Multi device output, router layers to devices and audio outputs.
- Jack/pipewire integration?
- Rose noise and sine generator in menu to test system.
- Play Mode:
- Play all medias found in folder consecutevily or random, with loop.
- Play all medias, consecutevily and random, with loop.
- mute/panic on layer.
- loop points.
- play offset. ¿stop offset?
- number of layers configured in conf file, up to 256.
- Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante.
- Master Layer:
- Mute.
- Pan.
- Keyboards strokes, select files from ui.
- LOGs y entrada de comandos.
- Bufgix: depurar errores cuando no carga la librería de medias, cambia el númmero de capas, cambia el universo, etc.
v 1.4
- pan.
- Show faders values.
--> Hacer UI por fader: mute/centrado, valor, visualizador:
--> Hacer UI con la visualización de tiempos.
- Master Bus Layer:
- mute/panic.
- fader + value
- pan
- magicq personality .hed
- audio device linked, outputs will be redirected there.
- dmx address + universe settings.
- compresor/limiter.
- Layer:
- audio procesadores (compresor, reveb, delay).
- mute/panic.
- Rose noise and sine generator.
- Logs, verbosity, timestamp.
- New play mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH
- SettingsDialog.
- Load/save conf file.
- ¿Exit Point? is it needed?
- Hardening: check return errors, try/catch exceptions, i'm too happy....
- Tests: errors on wrong conf file.
- ampliar writer para recibir un número n de entradas y escribirlas cada una en un buffer
- aislar miniaudio del callback dmx tal como hemos hecho con la Ui, al menos las operaciones lentas como cargar medios.
- en load media usar un fence para actualizar mediaLoaded.

View file

@ -2,35 +2,49 @@ TEMPLATE = app
TARGET = libremediaserver-audio
QT += webkitwidgets widgets
HEADERS += src/libremediaserver-audio.h \
src/clickabledoublespinbox.h \
src/clickablelabel.h \
src/clickableslider.h \
src/dmxwidget.h \
src/filterbankwidget.h \
src/libremediaserver-audio-gui.h \
src/ma_writer_node.h \
src/main.h \
src/medialibrary.h \
src/miniaudio.h \
src/ma_writer_node.h \
src/miniaudioengine.h \
src/olathread.h \
src/audiolayerwidget.h \
src/dmxPersonality.h \
src/audiowidget.h \
src/audiomasterwidget.h \
src/defines.h \
src/settings.h \
src/settingsdialog.h \
src/layersettingswidget.h
src/slidergroup.h
SOURCES += src/main.cpp \
src/clickabledoublespinbox.cpp \
src/clickablelabel.cpp \
src/clickableslider.cpp \
src/dmxwidget.cpp \
src/filterbankwidget.cpp \
src/libremediaserver-audio-gui.cpp \
src/libremediaserver-audio.cpp \
src/medialibrary.cpp \
src/miniaudio.c \
src/miniaudioengine.cpp \
src/ma_writer_node.c \
src/olathread.cpp \
src/audiolayerwidget.cpp \
src/audiowidget.cpp \
src/audiomasterwidget.cpp \
src/settings.cpp \
src/settingsdialog.cpp \
src/layersettingswidget.cpp
FORMS += src/libremediaserver-audio.ui \
src/settingsdialog.ui \
src/layersettingswidget.ui
LIBS += -lola -lolacommon
src/slidergroup.cpp
FORMS += src/libremediaserver-audio-gui.ui
CCFLAG += -msse2 -mavx2
QMAKE_CXXFLAGS += $$(CXXFLAG)
QMAKE_CFLAGS += $$(CCFLAG)
QMAKE_LFLAGS += $$(LDFLAG)
LIBS += -lola -lolacommon -ldl -lpthread -lm
# -lcitp
LIBS += -L$$PWD/SFML/lib/ -lsfml-audio -lsfml-system
INCLUDEPATH += $$PWD/SFML/include
DEPENDPATH += $$PWD/SFML/include
PRE_TARGETDEPS += $$PWD/SFML/lib/libsfml-audio.so $$PWD/SFML/lib/libsfml-system.so
OTHER_FILES += \
LICENSE.txt \
docs/compiling.txt \

View file

@ -1,291 +1,360 @@
#include "audiolayerwidget.h"
#include "dmxPersonality.h"
#include<iostream>
#include <QBoxLayout>
#include <QFileDialog>
#include <QDebug>
#include <QVBoxLayout>
#include <QFile>
#include <QTime>
AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name):
QGroupBox(parent)
AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer):
QWidget(parent)
, m_oldStatus(Status::PlayingLoop)
, m_layer(layer)
, m_suspendResumeButton(0)
, m_watchDMX(new QTimer(this))
, m_currentMedia(" ")
, m_running(false)
{
this->setTitle(name);
QVBoxLayout *layout = new QVBoxLayout;
QHBoxLayout *status = new QHBoxLayout;
m_statusLabel = new QLabel;
m_statusLabel->setText(STATUS_LABEL);
m_statusValue = new QLabel;
// m_receiveDMX = new QCheckBox("Receiving DMX", this);
// m_receiveDMX->setChecked(false);
// status->addWidget(m_receiveDMX);
status->addWidget(m_statusLabel);
status->addWidget(m_statusValue);
m_loopCheck = new QCheckBox();
m_loopCheckLabel = new QLabel;
m_loopCheckLabel->setText("Loop");
connect(m_loopCheck, SIGNAL(stateChanged(int)), this, SLOT(loopChanged(int)));
status->addWidget(m_loopCheck);
status->addWidget(m_loopCheckLabel);
layout->addLayout(status);
QHBoxLayout *folderLoaded = new QHBoxLayout;
m_folderLabel = new QLabel;
m_folderLabel->setText(FOLDER_LABEL);
m_folderValue = new QLabel;
m_folderValue->setMaximumWidth(200);
folderLoaded->addWidget(m_folderLabel);
folderLoaded->addWidget(m_folderValue);
layout->addLayout(folderLoaded);
QHBoxLayout *fileLoaded = new QHBoxLayout;
m_fileLabel = new QLabel;
m_fileLabel->setText(FILE_LABEL);
m_fileValue = new QLabel;
m_fileValue->setMaximumWidth(200);
fileLoaded->addWidget(m_fileLabel);
fileLoaded->addWidget(m_fileValue);
layout->addLayout(fileLoaded);
QGridLayout *volumeBox = new QGridLayout;
m_volumeLabel = new QLabel;
m_volumeLabel->setText(tr(VOLUME_LABEL));
m_volumeSlider = new QSlider(Qt::Horizontal);
m_volumeSlider->setMinimum(0);
m_volumeSlider->setMaximum(90);
m_volumeSlider->setSingleStep(1);
m_volumeIndicator = new QLabel;
volumeBox->addWidget(m_volumeLabel, 0, 0);
volumeBox->addWidget(m_volumeSlider, 0, 1);
volumeBox->addWidget(m_volumeIndicator, 0, 2);
connect(m_volumeSlider, SIGNAL(valueChanged(int)), this, SLOT(volumeChanged(int)));
connect(m_volumeSlider, &QSlider::valueChanged, this, [=] () {
m_volumeIndicator->setText(QString::number(m_volumeSlider->value()));
});
m_panLabel = new QLabel;
m_panLabel->setText("Pan");
m_panSlider = new QSlider(Qt::Horizontal);
m_panSlider->setMinimum(0);
m_panSlider->setMaximum(255);
m_panSlider->setSingleStep(1);
connect(m_panSlider, SIGNAL(valueChanged(int)), this, SLOT(panChanged(int)));
volumeBox->addWidget(m_panLabel, 1, 0);
volumeBox->addWidget(m_panSlider, 1, 1);
m_pitchLabel = new QLabel;
m_pitchLabel->setText("Pitch");
m_pitchSlider = new QSlider(Qt::Horizontal);
m_pitchSlider->setMinimum(0);
m_pitchSlider->setMaximum(255);
m_pitchSlider->setSingleStep(1);
connect(m_pitchSlider, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(int)));
volumeBox->addWidget(m_pitchLabel, 2, 0);
volumeBox->addWidget(m_pitchSlider, 2, 1);
layout->addLayout(volumeBox);
QHBoxLayout *progressTime = new QHBoxLayout;
m_progressTimeLabel = new QLabel;
m_progressTimeLabel->setText(PROGRESS_TIME_LABEL);
m_progressTime = new QTimeEdit;
m_progressTime->text();
m_progressTime->setDisplayFormat("h:mm:ss:zzz");
m_progressTime->setReadOnly(true);
m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons);
m_progressTime->setMaximumWidth(120);
progressTime->addWidget(m_progressTimeLabel);
progressTime->addWidget(m_progressTime);
m_totalTimeLabel = new QLabel;
m_totalTimeLabel->setText(TOTAL_TIME_LABEL);
m_totalTimeValue = new QTimeEdit;
m_totalTimeValue->setDisplayFormat("h:mm:ss:zzz");
m_totalTimeValue->setReadOnly(true);
m_totalTimeValue->setButtonSymbols(QAbstractSpinBox::NoButtons);
m_totalTimeValue->setMaximumWidth(120);
progressTime->addWidget(m_totalTimeLabel);
progressTime->addWidget(m_totalTimeValue);
layout->addLayout(progressTime);
QHBoxLayout *progressSlider = new QHBoxLayout;
m_progressLabel = new QLabel;
m_progressLabel->setText(PROGRESS_LABEL);
m_progressSlider = new QSlider(Qt::Horizontal);
progressSlider->addWidget(m_progressLabel);
progressSlider->addWidget(m_progressSlider);
layout->addLayout(progressSlider);
m_folderValue = new ClickableLabel;
m_folderValue->setAlignment(Qt::AlignLeft);
m_folderValue->setFocusPolicy(Qt::NoFocus);
m_folderValue->setMinimumWidth(MIN_WIDTH);
m_folderValue->setMaximumWidth(MAX_WIDTH);
m_folderValue->setContentsMargins(3,1,3,1);
m_folderValue->setText("Click to open media file (mp3, wav, flac)");
m_folderValue->setStyleSheet(
"margin: 0px;"
"color: white;"
"background-color: black;"
"font-size: 12px;"
);
layout->addWidget(m_folderValue);
m_fileValue = new ClickableLabel;
m_fileValue->setAlignment(Qt::AlignLeft);
m_fileValue->setFocusPolicy(Qt::NoFocus);
m_fileValue->setMinimumWidth(MIN_WIDTH);
m_fileValue->setMaximumWidth(MAX_WIDTH);
m_fileValue->setContentsMargins(3,1,3,1);
m_fileValue->setText("++ empty ++");
connect(m_fileValue, SIGNAL(clicked()), this, SLOT(openMediaDialog()));
connect(m_folderValue, SIGNAL(clicked()), this, SLOT(openMediaDialog()));
m_fileValue->setStyleSheet(
"margin: 0px;"
"color: white;"
"background-color: black;"
"font-size: 14px;"
);
layout->addWidget(m_fileValue);
m_suspendResumeButton = new QPushButton(this);
m_suspendResumeButton->setText(tr(SUSPEND_LABEL));
m_suspendResumeButton->setText(statusToString(Status::Iddle));
connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspendResume()));
layout->addWidget(m_suspendResumeButton);
m_progress = new QProgressBar(this);
m_progress->setOrientation(Qt::Horizontal);
m_progress->setFocusPolicy(Qt::NoFocus);
m_progress->setAlignment(Qt::AlignHCenter);
m_progress->setContentsMargins(0, 1, 0, 1);
m_progress->setMinimumWidth(MIN_WIDTH);
m_progress->setMaximumWidth(MAX_WIDTH);
m_progress->setMaximumHeight(15);
m_progress->setRange(0, 0);
m_progress->setValue(0);
m_progress->setFormat("%v / %m");
m_progress->setStyleSheet(
"margin: 0px;"
"font-size: 10px;"
"background-color: black;"
);
layout->addWidget(m_progress);
m_progressTime = new QTimeEdit;
m_progressTime->setToolTip("Current Time");
m_progressTime->setObjectName("Current Time");
m_progressTime->setDisplayFormat("mm:ss:zzz");
m_progressTime->setReadOnly(true);
m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons);
m_progressTime->setFocusPolicy(Qt::NoFocus);
m_progressTime->setAlignment(Qt::AlignHCenter);
m_progressTime->setContentsMargins(0,0,0,0);
m_progressTime->setMinimumWidth(MIN_WIDTH);
m_progressTime->setMaximumWidth(MAX_WIDTH);
m_progressTime->setStyleSheet(
"margin: 0px;"
"color: white;"
"background-color: black;"
"font-size: 16px;"
);
m_totalTimeValue = new QTimeEdit;
m_totalTimeValue->setObjectName("Track Length");
m_totalTimeValue->setToolTip("Track Length");
m_totalTimeValue->setDisplayFormat("mm:ss:zzz");
m_totalTimeValue->setReadOnly(true);
m_totalTimeValue->setButtonSymbols(QAbstractSpinBox::NoButtons);
m_totalTimeValue->setFocusPolicy(Qt::NoFocus);
m_totalTimeValue->setAlignment(Qt::AlignHCenter);
m_totalTimeValue->setContentsMargins(0,0,0,0);
m_totalTimeValue->setMinimumWidth(MIN_WIDTH);
m_totalTimeValue->setMaximumWidth(MAX_WIDTH);
m_totalTimeValue->setStyleSheet(
"margin: 0px;"
"color: white;"
"background-color: black;"
"font-size: 16px;"
);
QHBoxLayout *status = new QHBoxLayout;
status->addWidget(m_progressTime);
status->addWidget(m_totalTimeValue);
layout->addLayout(status);
m_filterBank = new FilterBankWidget(this);
m_filterBank->setMaximumWidth(MAX_WIDTH);
m_filterBank->setMinimumWidth(MIN_WIDTH);
connect(m_filterBank, SIGNAL(setBypass(bool)), this, SLOT(setBypass(bool)));
layout->addWidget(m_filterBank);
m_pitch = new SliderGroup("Pitch", 0 , 255, 0, NULL);
layout->addWidget(m_pitch);
connect(m_pitch, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(int)));
m_pan = new SliderGroup("Pan", 0 , 255, 0, NULL);
layout->addWidget(m_pan);
connect(m_pan, SIGNAL(valueChanged(int)), this, SLOT(panChanged(int)));
QHBoxLayout *volumeBox = new QHBoxLayout;
m_volume = new SliderGroup("Vol", 0, 65535, 2, NULL);
volumeBox->addWidget(m_volume);
connect(m_volume, SIGNAL(valueChanged(int)), this, SLOT(volumeChanged(int)));
m_bus1 = new SliderGroup("Bus 1", 0, 65535, 2, NULL);
volumeBox->addWidget(m_bus1);
connect(m_bus1, SIGNAL(valueChanged(int)), this, SLOT(bus1VolumeChanged(int)));
m_bus2 = new SliderGroup("Bus 2", 0, 65535, 2, NULL);
volumeBox->addWidget(m_bus2);
connect(m_bus2, SIGNAL(valueChanged(int)), this, SLOT(bus2VolumeChanged(int)));
volumeBox->setSpacing(0);
volumeBox->setContentsMargins(0, 0, 0, 0);
layout->addLayout(volumeBox);
QHBoxLayout *labelsBox = new QHBoxLayout;
m_level = new QLabel("-inf");
m_level->setStyleSheet("border: 1px solid #CFB0C9;"
"margin: 0px;"
"background-color: black;"
"color: white;"
"text-align: center;"
"font-size: 15px;");
m_level->setMinimumWidth(MIN_WIDTH / 2);
m_level->setMaximumWidth(MAX_WIDTH / 3);
m_level->setMinimumHeight(25);
m_level->setAlignment(Qt::AlignHCenter);
m_level->setContentsMargins(0, 4, 0, 4);
labelsBox->addWidget(m_level);
m_bus1Label = new QLabel("dummy");
m_bus1Label->setAlignment(Qt::AlignHCenter);
m_bus1Label->setContentsMargins(0, 6, 0, 6);
m_bus1Label->setMinimumHeight(25);
m_bus1Label->setMinimumWidth(MIN_WIDTH / 4);
m_bus1Label->setMaximumWidth(MAX_WIDTH / 3);
m_bus1Label->setStyleSheet("border: 1px solid #CFB0C9;"
"margin: 0px;"
"background-color: black;"
"color: white;"
"font-size: 10px;");
labelsBox->addWidget(m_bus1Label);
m_bus2Label = new QLabel("dummy");
m_bus2Label->setAlignment(Qt::AlignHCenter);
m_bus2Label->setMinimumWidth(MIN_WIDTH / 4);
m_bus2Label->setMaximumWidth(MAX_WIDTH / 3);
m_bus2Label->setContentsMargins(0, 6, 0, 6);
m_bus2Label->setMinimumHeight(25);
m_bus2Label->setStyleSheet("border: 1px solid #CFB0C9;"
"margin: 0px;"
"background-color: black;"
"color: white;"
"font-size: 10px;");
labelsBox->addWidget(m_bus2Label);
labelsBox->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(labelsBox);
layout->setAlignment(Qt::AlignHCenter);
layout->setSpacing(1);
this->setLayout(layout);
connect(m_watchDMX, SIGNAL(timeout()),
this, SLOT(refreshGUI()));
m_watchDMX->start(100);
m_music.setAttenuation(0);
}
AudioLayerWidget::~AudioLayerWidget()
{
}
AudioLayerWidget::~AudioLayerWidget() {}
// From UI.
void AudioLayerWidget::volumeChanged(int value)
{
m_music.setVolume(value);
emit(uiSliderChanged(m_layer, Slider::Volume, value));
}
void AudioLayerWidget::bus1VolumeChanged(int value)
{
emit(uiSliderChanged(m_layer, Slider::Bus1, value));
}
void AudioLayerWidget::bus2VolumeChanged(int value)
{
emit(uiSliderChanged(m_layer, Slider::Bus2, value));
}
void AudioLayerWidget::panChanged(int value)
{
m_music.setRelativeToListener(true);
sf::Vector3f pos = m_music.getPosition();
//m_music.setSpati(0, 0, 0);
qreal pan = (value - 128) / 64.0f;
qWarning("change pan %f", pan);
//m_music.setPosition(pan, 0.0, sqrtf(1.0 + pan*pan));
m_music.setPosition(pan, 0.0f, -1.0f);
//pos = m_music.getPosition();
qWarning("%f %f %f", pos.x, pos.y, pos.z);
qWarning("is rel %i", m_music.isRelativeToListener());
emit(uiSliderChanged(m_layer, Slider::Pan, value));
}
void AudioLayerWidget::pitchChanged(int value)
{
m_music.setPitch(qreal(value / 128.0 ));
emit(uiSliderChanged(m_layer, Slider::Pitch, value));
}
void AudioLayerWidget::loopChanged(int value)
void AudioLayerWidget::setBypass(bool value)
{
m_music.setLoop(value);
emit(uiSliderChanged(m_layer, Slider::Bypass, value));
}
void AudioLayerWidget::setVol(qreal vol)
{
m_music.setVolume(vol);
m_volumeSlider->blockSignals(true);
m_volumeSlider->setValue(vol);
m_volumeIndicator->setText(QString::number(vol));
m_volumeSlider->blockSignals(false);
}
void AudioLayerWidget::setPan(qreal pan)
void AudioLayerWidget::toggleSuspendResume()
{
this->panChanged(pan);
m_panSlider->blockSignals(true);
m_panSlider->setValue(pan);
m_panSlider->blockSignals(false);
}
void AudioLayerWidget::setPitch(qreal pitch)
{
m_music.setPitch(float(pitch / 128 ));
m_pitchSlider->blockSignals(true);
m_pitchSlider->setValue(pitch);
m_pitchSlider->blockSignals(false);
}
void AudioLayerWidget::loadMedia(QString file)
{
if (m_currentMedia.compare(file) == 0 ) {
return;
switch (m_status) {
case Status::PlayingLoop:
case Status::PlayingOnce:
case Status::PlayingFolder:
case Status::PlayingFolderLoop:
case Status::PlayingFolderRandom:
m_oldStatus = m_status;
this->setPlaybackStatus(Status::Paused);
emit uiPlaybackChanged(m_layer, Status::Paused);
break;
case Status::Paused:
case Status::Stopped:
this->setPlaybackStatus(m_oldStatus);
emit uiPlaybackChanged(m_layer, m_oldStatus);
case Status::Iddle:
break;
}
if (!QFile::exists(file)) {
qWarning("Can not access to file %s", file.toLatin1().constData());
return;
}
// Load an ogg music file
if (!m_music.openFromFile(file.toStdString())) {
qWarning("Can not open file %s", file.toLatin1().constData());
return;
}
m_currentMedia = file;
durationChanged(m_music.getDuration().asMilliseconds());
fileLoaded(file);
// Display music informations
std::cout << "File loaded: " << file.toLatin1().constData() << " : " << std::endl;
std::cout << " " << m_music.getDuration().asSeconds() << " seconds";
std::cout << " " << m_music.getSampleRate() << " samples / sec";
std::cout << " " << m_music.getChannelCount() << " channels" << std::endl;
}
void AudioLayerWidget::fileLoaded(QString file)
void AudioLayerWidget::openMediaDialog()
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setNameFilter(tr("Sound Tracks (*.mp3 *.mp3 *.flac *.wav"));
dialog.setViewMode(QFileDialog::Detail);
dialog.setDirectory(Settings::getInstance()->getPathMedia());
if (!dialog.exec())
return;
QStringList fileNames;
fileNames = dialog.selectedFiles();
emit uiLoadMedia(m_layer, fileNames.at(0));
this->setMediaFile(fileNames.at(0));
this->setPlaybackStatus(Status::Stopped);
}
// from DMX signals
void AudioLayerWidget::setVol(float vol)
{
m_volume->blockSignals(true);
m_volume->setValue(vol);
m_volume->blockSignals(false);
}
void AudioLayerWidget::setPan(int pan)
{
m_pan->blockSignals(true);
m_pan->setValue(pan);
m_pan->blockSignals(false);
}
void AudioLayerWidget::setPitch(int pitch)
{
m_pitch->blockSignals(true);
m_pitch->setValue(pitch);
m_pitch->blockSignals(false);
}
void AudioLayerWidget::setMediaFile(QString file)
{
QStringList list = file.split("/");
int size = list.size();
if (size >= 2) {
m_folderValue->setText(list.at(size - 2));
m_fileValue->setText(list.at(size - 1));
QString tmp = list.at(size - 2);
tmp.truncate(60);
m_folderValue->setText(tmp);
tmp = list.at(size - 1);
tmp.truncate(40);
m_fileValue->setText(tmp);
}
}
void AudioLayerWidget::durationChanged(qint64 dur)
void AudioLayerWidget::setPlaybackStatus(Status s)
{
m_progressSlider->setMaximum(dur);
m_suspendResumeButton->blockSignals(true);
m_status = s;
m_suspendResumeButton->setText(statusToString(s));
m_suspendResumeButton->blockSignals(false);
}
void AudioLayerWidget::setDuration(float dur)
{
m_progress->blockSignals(true);
m_progressTime->blockSignals(true);
m_totalTimeValue->blockSignals(true);
m_progress->setRange(0, dur);
m_progress->setValue(0);
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0));
m_totalTimeValue->setTime(QTime::fromMSecsSinceStartOfDay(dur));
m_progress->blockSignals(false);
m_progressTime->blockSignals(false);
m_totalTimeValue->blockSignals(false);
}
void AudioLayerWidget::play(bool loop)
void AudioLayerWidget::setCurrentTime(float progress)
{
m_music.play();
m_music.setLoop(loop);
m_loopCheck->blockSignals(true);
m_loopCheck->setChecked(m_music.getLoop());
m_loopCheck->blockSignals(false);
progress *= 1000;
m_progress->blockSignals(true);
m_progressTime->blockSignals(true);
m_progress->setValue(progress);
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress));
m_progress->blockSignals(false);
m_progressTime->blockSignals(false);
}
void AudioLayerWidget::pause()
void AudioLayerWidget::setFilterParam(int channel, int value)
{
m_music.pause();
}
void AudioLayerWidget::stop()
{
m_music.stop();
}
void AudioLayerWidget::refreshGUI() {
// m_receiveDMX->setChecked(false);
int progress;
switch (m_music.getStatus()) {
case sf::SoundSource::Playing:
progress = m_music.getPlayingOffset().asMilliseconds();
m_progressSlider->setValue(progress);
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress));
m_statusValue->setText(PLAY_LABEL);
m_suspendResumeButton->setText(tr(SUSPEND_LABEL));
break;
case sf::SoundSource::Paused:
m_statusValue->setText(PAUSE_LABEL);
m_suspendResumeButton->setText(tr(RESUME_LABEL));
break;
case sf::SoundSource::Stopped:
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0));
m_statusValue->setText(STOP_LABEL);
m_suspendResumeButton->setText(tr(RESUME_LABEL));
m_progressSlider->setValue(0);
break;
}
//m_loopCheck->blockSignals(true);
//m_loopCheck->setChecked(m_music.getLoop());
//m_loopCheck->blockSignals(false);
}
void AudioLayerWidget::toggleSuspendResume()
{
if (m_music.getStatus() == sf::SoundSource::Playing) {
m_music.pause();
} else {
m_music.play();
if (channel <= FILTER_BANK_GAIN - HP_FREQ){
m_filterBank->blockSignals(true);
m_filterBank->setValue(channel, value);
m_filterBank->blockSignals(false);
} else if (channel == SEND1 - HP_FREQ) {
m_bus1->blockSignals(true);
m_bus1->setValue((value * 256) + 255);
m_bus1->blockSignals(false);
} else if (channel == SEND2 - HP_FREQ) {
m_bus2->blockSignals(true);
m_bus2->setValue(value * 256 + 255);
m_bus2->blockSignals(false);
}
}
void AudioLayerWidget::setLevel(float db)
{
m_level->blockSignals(true);
if (db > -140)
m_level->setText(QString::number(db, 'f', 2));
else
m_level->setText("-inf");
m_level->blockSignals(false);
}
void AudioLayerWidget::setBusName(uint bus, char *name)
{
if (name && strlen(name) > 17)
name[16] = '\0';
if (bus == 0) {
m_bus1Label->blockSignals(true);
m_bus1Label->setText(name);
m_bus1Label->blockSignals(false);
} else if (bus == 1) {
m_bus2Label->blockSignals(true);
m_bus2Label->setText(name);
m_bus2Label->blockSignals(false);
}
}

View file

@ -1,152 +1,71 @@
#ifndef AUDIOLAYERWIDGET_H
#define AUDIOLAYERWIDGET_H
#include <cmath>
#include <QByteArray>
#include <QLabel>
#include <QPushButton>
#include <QSlider>
#include <QTimer>
#include <QTime>
#include <QTimeEdit>
#include <QGroupBox>
#include <QCheckBox>
#include <SFML/Audio.hpp>
#include <SFML/System.hpp>
#include <QProgressBar>
#include "defines.h"
#include "slidergroup.h"
#include "clickablelabel.h"
#include "settings.h"
#include "filterbankwidget.h"
class AudioLayerWidget : public QGroupBox
class AudioLayerWidget : public QWidget
{
Q_OBJECT
public:
explicit AudioLayerWidget(QWidget *parent = 0, QString name = "Layer");
explicit AudioLayerWidget(QWidget *parent = 0, int layer = 0);
~AudioLayerWidget();
/**
* @brief load a new media file
* @param file name with full path
*/
void loadMedia(QString file);
/**
* @brief play
*/
void play(bool loop);
/**
* @brief stop
*/
void stop();
/**
* @brief pause
*/
void pause();
/**
* @brief set the Volume
* @param vol volume range 0 -100
*/
void setVol(qreal vol);
/**
* @brief resume
*/
void resume();
void setPan(qreal pan);
void setPitch(qreal pitch);
void setLoop(bool on);
//void setEntryPoint(qreal entry);
//void setEndPoint(qreal end);
public slots:
/**
* @brief connected with the button
*/
void toggleSuspendResume();
/**
* @brief Connected with the slider
* @param volume 0 -100 range
*/
void volumeChanged(int vol);
void panChanged(int vol);
void pitchChanged(int vol);
void loopChanged(int vol);
// From OLA -> LibreMediaServer -> AudioWidget
void setMediaFile(QString file);
void setDuration(float dur);
void setCurrentTime(float progress);
void setPlaybackStatus(Status status);
void setVol(float vol);
void setPan(int pan);
void setPitch(int pitch);
void setFilterParam(int channel, int value);
void setLevel(float db);
void setBusName(uint bus, char *name);
private:
Status m_status;
Status m_oldStatus;
int m_layer;
QPushButton *m_suspendResumeButton;
QLabel *m_statusLabel;
QLabel * m_statusValue;
QLabel *m_volumeLabel;
QSlider *m_volumeSlider;
QLabel *m_volumeIndicator;
QLabel *m_panLabel;
QSlider *m_panSlider;
QLabel *m_pitchLabel;
QSlider *m_pitchSlider;
QLabel *m_loopCheckLabel;
QCheckBox *m_loopCheck;
QLabel * m_progressLabel;
QSlider *m_progressSlider;
QLabel *m_progressTimeLabel;
ClickableLabel *m_fileValue;
ClickableLabel * m_folderValue;
SliderGroup *m_volume;
SliderGroup *m_pan;
SliderGroup *m_pitch;
SliderGroup *m_bus1;
SliderGroup *m_bus2;
QTimeEdit *m_progressTime;
QLabel *m_totalTimeLabel;
QTimeEdit *m_totalTimeValue;
QProgressBar *m_progress;
FilterBankWidget *m_filterBank;
QLabel *m_level;
QLabel *m_bus1Label;
QLabel *m_bus2Label;
QLabel *m_fileLabel;
QLabel *m_fileValue;
QLabel * m_folderLabel;
QLabel * m_folderValue;
//QCheckBox *m_receiveDMX;
QTimer *m_watchDMX;
QString m_currentMedia;
bool m_running;
sf::Music m_music;
// From Ui
private slots:
void openMediaDialog();
void toggleSuspendResume();
void volumeChanged(int vol);
void bus1VolumeChanged(int vol);
void bus2VolumeChanged(int vol);
void panChanged(int pan);
void pitchChanged(int pitch);
void setBypass(bool value);
/**
* @brief Update the GUI elements with the name of the new file loaded
* @param file
*/
void fileLoaded(QString file);
/**
* @brief Update the GUI elements with the duration of the new file loaded
* @param dur The duration of the track in miliseconds
*/
void durationChanged(qint64 dur);
/**
* @brief Update the variable elements in GUI
*/
void refreshGUI();
signals:
void uiPlaybackChanged(int layer, Status s);
void uiSliderChanged(int layer, Slider s, int value);
void uiLoadMedia(int layer, QString s);
};
#endif // AUDIOLAYERWIDGET_H

View file

@ -1,39 +0,0 @@
#include "audiomasterwidget.h"
#include <QVBoxLayout>
AudioMasterWidget::AudioMasterWidget(QWidget *parent) :
QGroupBox(parent)
//, m_file(new QLabel)
//, m_folder(new QLabel)
//, m_vol(new QSlider)
//, m_mute(new QCheckBox)
//, m_status(new QLabel)
, m_receiveDMX(new QCheckBox)
, m_watchDMX(new QTimer)
{
QVBoxLayout *vbox = new QVBoxLayout;
//vbox->addWidget(m_status);
//vbox->addWidget(m_vol);
//vbox->addWidget(m_mute);
m_receiveDMX->setText("Receiving DMX");
vbox->addWidget(m_receiveDMX);
this->setLayout(vbox);
connect(m_watchDMX, SIGNAL(timeout()),
this, SLOT(watchDMXExpired()));
m_watchDMX->start(1000);
}
AudioMasterWidget::~AudioMasterWidget()
{
}
void AudioMasterWidget::watchDMXExpired() {
m_receiveDMX->setChecked(false);
}
void AudioMasterWidget::updateWatchDMX()
{
m_receiveDMX->setChecked(true);
}

View file

@ -1,44 +0,0 @@
#ifndef AUDIOMASTERWIDGET_H
#define AUDIOMASTERWIDGET_H
#include <QtGui>
#include <QLabel>
#include <QSlider>
#include <QCheckBox>
#include <QGroupBox>
//#include "ui_audiomasterwidget.h"
/*
namespace Ui {
class AudioMasterWidget;
}*/
class AudioMasterWidget : public QGroupBox //, public Ui::AudioMasterWidget
{
Q_OBJECT
public:
AudioMasterWidget(QWidget *parent);
~AudioMasterWidget();
public slots:
void updateWatchDMX();
private:
//QLabel *m_file;
//QLabel *m_folder;
//QSlider *m_vol;
//QCheckBox *m_mute;
//QLabel *m_status;
QCheckBox *m_receiveDMX;
QTimer *m_watchDMX;
private slots:
void watchDMXExpired();
};
#endif // AUDIOMASTERWIDGET_H

View file

@ -1,74 +1,135 @@
#include "audiowidget.h"
#include "dmxPersonality.h"
AudioWidget *AudioWidget::_instance = 0;
AudioWidget *AudioWidget::getInstance() {
if (_instance == 0) {
_instance = new AudioWidget();
Q_CHECK_PTR(_instance);
AudioWidget::AudioWidget(QWidget *parent) :
QWidget(parent)
, m_layout(new QHBoxLayout())
{
m_layers = Settings::getInstance()->getLayersNumber();
for (uint i= 0; i < m_layers; i++ ) {
AudioLayerWidget *alw = new AudioLayerWidget(this, i);
m_layout->insertWidget(i, alw);
connect(alw, SIGNAL(uiSliderChanged(int, Slider, int)), this, SIGNAL(uiSliderChanged(int, Slider, int)));
connect(alw, SIGNAL(uiPlaybackChanged(int, Status)), this, SIGNAL(uiPlaybackChanged(int, Status)));
connect(alw, SIGNAL(uiLoadMedia(int, QString)), this, SIGNAL(uiLoadMedia(int, QString)));
m_layerUpdate[i].status = Status::Iddle;
m_layerUpdate[i].duration = -1;
m_layerUpdate[i].media = "";
m_layerUpdate[i].vol = -1;
m_layerUpdate[i].pan = -1;
m_layerUpdate[i].pitch = -1;
m_layerUpdate[i].cursor = -1;
m_layerUpdate[i].level = 100;
for (int j = 0; j < FILTER_CHANNELS; j++)
m_filtersUpdate[i][j] = -1;
}
return _instance;
m_layout->setSpacing(0);
m_layout->setContentsMargins(0, 0, 0, 0);
this->setStyleSheet("margin: 2px;");
setLayout(m_layout);
m_refreshUi = new QTimer(this);
connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi()));
m_refreshUi->start(UI_REFRESH_TIME * 1.5);
}
AudioWidget::AudioWidget()
void AudioWidget::mediaLoaded(int layer, QString file, float duration)
{
layout = new QHBoxLayout();
for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) {
layout->insertWidget(i, new AudioLayerWidget(this, tr("Layer %1").arg(i)));
}
setLayout(layout);
// qDebug( "Init AudioWidget");
m_layerUpdate[layer].media = file;
m_layerUpdate[layer].duration = duration;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::mediaLoaded(int layer, QString media)
{
QLayoutItem * const item = layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->loadMedia(media);
// qDebug() << "AudioWidget::mediaLoaded Received layer: " << layer
// << "Media: " << media;
void AudioWidget::volChanged(int layer, float vol) {
m_layerUpdate[layer].vol = vol;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::volChanged(int layer, qreal vol) {
QLayoutItem * const item = layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setVol(vol);
// qDebug() << "AudioWidget::volChanged Received layer: " << layer
// << "Vol : " << vol;
void AudioWidget::panChanged(int layer, int pan) {
m_layerUpdate[layer].pan = pan;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::panChanged(int layer, qreal vol) {
QLayoutItem * const item = layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setPan(vol);
}
void AudioWidget::pitchChanged(int layer, int pitch) {
void AudioWidget::pitchChanged(int layer, qreal vol) {
QLayoutItem * const item = layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setPitch(vol);
m_layerUpdate[layer].pitch = pitch;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::playbackChanged(int layer, Status status)
{
QLayoutItem * const item = layout->itemAt(layer);
switch (status) {
case PlayingOnce:
dynamic_cast<AudioLayerWidget *>(item->widget())->play(false);
break;
case Stopped:
dynamic_cast<AudioLayerWidget *>(item->widget())->stop();
break;
case Paused:
dynamic_cast<AudioLayerWidget *>(item->widget())->pause();
break;
case PlayingLoop:
dynamic_cast<AudioLayerWidget *>(item->widget())->play(true);
break;
m_layerUpdate[layer].status = status;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::cursorChanged(int layer, float cursor)
{
m_layerUpdate[layer].cursor = cursor;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::refreshUi()
{
for (uint i = 0; i < m_layers; i++)
{
if (m_layerUpdate[i].updated) {
QLayoutItem *item = m_layout->itemAt(i);
AudioLayerWidget *alw = dynamic_cast<AudioLayerWidget *>(item->widget());
if (m_layerUpdate[i].vol > -1) {
alw->setVol(m_layerUpdate[i].vol);
m_layerUpdate[i].vol = -1;
}
if (m_layerUpdate[i].cursor > -1) {
alw->setCurrentTime(m_layerUpdate[i].cursor);
m_layerUpdate[i].cursor = -1;
}
if (m_layerUpdate[i].pan > -1) {
alw->setPan(m_layerUpdate[i].pan);
m_layerUpdate[i].pan = -1;
}
if (m_layerUpdate[i].pitch > -1) {
alw->setPitch(m_layerUpdate[i].pitch);
m_layerUpdate[i].pitch = -1;
}
if (m_layerUpdate[i].status != Status::Iddle) {
alw->setPlaybackStatus(m_layerUpdate[i].status);
m_layerUpdate[i].status = Status::Iddle;
}
if (m_layerUpdate[i].duration > -1) {
alw->setMediaFile(m_layerUpdate[i].media);
alw->setDuration(m_layerUpdate[i].duration);
m_layerUpdate[i].duration = -1;
}
for (int j = 0; j < FILTER_CHANNELS; j++) {
if (m_filtersUpdate[i][j] > -1)
alw->setFilterParam(j, m_filtersUpdate[i][j]);
m_filtersUpdate[i][j] = -1;
}
if (m_layerUpdate[i].level < 100) {
alw->setLevel(m_layerUpdate[i].level);
m_layerUpdate[i].level = 100;
}
m_layerUpdate[i].updated = false;
}
}
}
/*
void AudioWidget::layerReceived(int layer)
{
QLayoutItem * const item = layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->updateWatchDMX(true);
}*/
void AudioWidget::filterParamChanged(int layer, int channel, int value)
{
m_filtersUpdate[layer][channel - HP_FREQ] = value;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::levelChanged(int layer, float db)
{
m_layerUpdate[layer].level = db;
m_layerUpdate[layer].updated = true;
}
void AudioWidget::busNameChanged(uint bus, char* name)
{
for (uint i = 0; i < m_layers; i++) {
QLayoutItem *item = m_layout->itemAt(i);
AudioLayerWidget *alw = dynamic_cast<AudioLayerWidget *>(item->widget());
alw->setBusName(bus, name);
}
}

View file

@ -1,43 +1,44 @@
#ifndef AUDIOWIDGET_H
#define AUDIOWIDGET_H
#include <QObject>
#include <QWidget>
#include <QVBoxLayout>
#include "audiomasterwidget.h"
#include "audiolayerwidget.h"
#include "defines.h"
#include "settings.h"
#include <QTimer>
#include <QBoxLayout>
#include "SFML/Audio/SoundSource.hpp"
#include "audiolayerwidget.h"
#include "settings.h"
#include "defines.h" // MAX_LAYERS
class AudioWidget : public QWidget
{
friend class libreMediaServerAudio;
Q_OBJECT
public:
protected:
static AudioWidget *getInstance();
void mediaLoaded(int layer, QString media );
void volChanged(int layer, qreal vol);
void panChanged(int layer, qreal pan);
void pitchChanged(int layer, qreal pitch);
void playbackChanged(int layer, Status status);
AudioWidget(QWidget *parent = nullptr);
void filterParamChanged(int layer, int channel, int value);
void levelChanged(int layer, float db);
void busNameChanged(uint bus, char *name);
private:
QHBoxLayout *m_layout;
layerData m_layerUpdate[MAX_LAYERS];
QTimer *m_refreshUi;
uint m_layers;
int m_filtersUpdate[MAX_LAYERS][FILTER_CHANNELS];
static AudioWidget *_instance;
AudioWidget();
QHBoxLayout *layout;
signals:
public slots:
void volChanged(int layer, float vol);
void panChanged(int layer, int pan);
void pitchChanged(int layer, int pitch);
void cursorChanged(int layer, float cursor);
void mediaLoaded(int layer, QString media, float duration);
void playbackChanged(int layer, Status status);
private slots:
void refreshUi();
signals:
void uiPlaybackChanged(int layer, Status s);
void uiSliderChanged(int layer, Slider s, int vol);
void uiLoadMedia(int layer, QString s);
};
#endif // AUDIOWIDGET_H

View file

@ -0,0 +1,17 @@
#include "clickabledoublespinbox.h"
#include <QVBoxLayout>
ClickableDoubleSpinBox::ClickableDoubleSpinBox(QWidget *parent)
: QDoubleSpinBox(parent)
{
setFocusPolicy(Qt::NoFocus);
setButtonSymbols(QAbstractSpinBox::NoButtons);
setValue(-1);
setDecimals(1);
setAlignment(Qt::AlignHCenter);
setContentsMargins(0, 0, 0, 0);
setMaximumWidth(66);
setMinimumWidth(25);
}

View file

@ -0,0 +1,25 @@
#ifndef CLICKABLEDOUBLESPINBOX_H
#define CLICKABLEDOUBLESPINBOX_H
#include <QWidget>
#include <QDoubleSpinBox>
#include <QMouseEvent>
#include <QDebug>
class ClickableDoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
explicit ClickableDoubleSpinBox(QWidget *parent = nullptr);
protected:
void mousePressEvent ( QMouseEvent * event ) {
if (event->button() == Qt::LeftButton) {
emit click();
}
event->accept();
}
signals:
void click();
};
#endif // CLICKABLEDOUBLESPINBOX_H

14
src/clickablelabel.cpp Normal file
View file

@ -0,0 +1,14 @@
#include "clickablelabel.h"
ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f)
: QLabel{parent}
{
Q_UNUSED(f);
}
ClickableLabel::~ClickableLabel() {}
void ClickableLabel::mousePressEvent(QMouseEvent* event) {
Q_UNUSED(event);
emit clicked();
}

22
src/clickablelabel.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef CLICKABLELABEL_H
#define CLICKABLELABEL_H
#include <QLabel>
#include <QWidget>
#include <Qt>
class ClickableLabel : public QLabel
{
Q_OBJECT
public:
explicit ClickableLabel(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
~ClickableLabel();
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent* event);
};
#endif // CLICKABLELABEL_H

3
src/clickableslider.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "clickableslider.h"
ClickableSlider::ClickableSlider(QWidget *parent) : QSlider{parent} {}

30
src/clickableslider.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef CLICKABLESLIDER_H
#define CLICKABLESLIDER_H
#include <QSlider>
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>
class ClickableSlider : public QSlider
{
Q_OBJECT
public:
ClickableSlider(QWidget *parent = nullptr);
protected:
void mousePressEvent ( QMouseEvent * event )
{
if (event->button() == Qt::RightButton)
{
if (this->isEnabled()) {
qDebug() << "disabling slider";
this->setDisabled(true);
}
event->accept();
}
QSlider::mousePressEvent(event);
}
};
#endif // CLICKABLESLIDER_H

View file

@ -1,60 +1,26 @@
#ifndef DEFINES_H
#define DEFINES_H
#define VERSION "LibreMediaServer-Audio 0.1.3"
#define COPYRIGHT "(C) 2014-2024 Santi Norena lms@criptomart.net"
#define LICENSE "GPL 3 License. See LICENSE.txt and credits.txt for details"
#define VERSION "LibreMediaServerAudio v0.2.0 Antigona"
#define COPYRIGHT "(C) 2014-2024 Santi Noreña <lms@criptomart.net>"
#define LICENSE "GPL3 Licensed. See LICENSE.txt."
#define DEFAULT_FILE "lms-audio.xlm"
#define MAX_LAYERS 4
#define MAX_AUDIODEVICES 8
#define FORMAT ma_format_f32 /* Must always be f32. */
#define CHANNELS 2
#define SAMPLE_RATE 48000
#define UI_REFRESH_TIME 123
#define FADE_TIME 25
#define FILTER_CHANNELS 16 // number of dmx channels dedicated to filters by layer
#define MAX_WIDTH 500
#define MIN_WIDTH 50
#define LAYERS_NUMBER 4 // esto tiene que desaparecer
#define DEFAULT_FILE "lms-audio.xlm"
const int DurationSeconds = 1;
const int ToneSampleRateHz = 600;
const int DataSampleRateHz = 44100;
const int BufferSize = 262144;
#define SUSPEND_LABEL "Pause playback"
#define RESUME_LABEL "Resume playback"
#define PLAY_LABEL "Playing"
#define STOP_LABEL "Stopped"
#define PAUSE_LABEL "Pause"
#define IDDLE_LABEL "Iddle playback"
#define VOLUME_LABEL "Volume"
#define PROGRESS_LABEL "Progress"
#define PROGRESS_TIME_LABEL "Current"
#define REMAINING_TIME "Remaining Time: "
#define TOTAL_TIME_LABEL "Total"
#define FILE_LABEL "File:"
#define FOLDER_LABEL "Folder:"
#define STATUS_LABEL "Status: "
#define NOTIFY_INTERVAL 150
#define PULL_TIMER_INTERVAL 10
// struct where save the DMX settings for each layer
struct dmxSetting {
int address;
uint universe;
bool updated;
unsigned int universe;
int layer;
};
// Media Information for MELIn packages. v1.0
struct MediaFile {
quint8 Number; // 0-based contiguous index of the media.
QString MediaName;// Media name.
quint32 MediaLength;// Media length (in frames).
};
// Media Library for ELin packages v1.0
struct MediaFolder {
quint8 m_Id; // Library id.
QString m_Name;// Library name.
quint8 m_ElementCount;// Number of elements in the library.
QList<MediaFile> m_MediaInformation; // Pointer to the Medias Information List of this Library
int audioDevice;
};
enum Status
@ -63,7 +29,55 @@ enum Status
Paused,
PlayingOnce,
PlayingLoop,
Iddle,
PlayingFolder,
PlayingFolderLoop,
PlayingFolderRandom
};
#endif // DEFINES_H
enum Slider
{
Volume,
Pan,
Pitch,
Bypass,
Bus1,
Bus2
};
#ifdef __cplusplus
constexpr const char* statusToString(Status e) noexcept
{
switch (e)
{
case Status::Stopped: return "Stop";
case Status::Paused: return "Paused";
case Status::PlayingOnce: return "Play 1";
case Status::PlayingLoop: return "Play Loop";
case Status::Iddle: return "Iddle";
case Status::PlayingFolder: return "Play Folder";
case Status::PlayingFolderLoop: return "Play Folder Loop";
case Status::PlayingFolderRandom: return "Playing Folder Random";
default: return "--++--";
}
}
#include <QString>
struct layerData {
QString media;
Status status;
bool updated;
float vol;
float cursor;
int pan;
int pitch;
float duration;
int address;
unsigned int universe;
int device;
int bus1Vol;
int bus2Vol;
float level;
};
#endif // __cplusplus
#endif // DEFINES_H

View file

@ -1,40 +1,52 @@
#ifndef DMXPERSONALITY_H
#define DMXPERSONALITY_H
/** Define the DMX personality to avoid dealing with
* numbers and change it easyly in case
*
1 - Volumen Coarse
2 - Pan
3 - Folder
4 - File
5 - Playback
0-24 : Play once.
25-49: Stop. Returns to start of file.
50-74: Pause. It keeps the time of reproductions.
75-99: Play loop.
6 - Control - Reservado, sin uso en este momento.
7 - Volume Fine
8 - Entry Point Coarse - Punto de entrada de reproducción.
9 - Entry Point Fine - El valor de estos dos canales en centésimas de segundo.
10 - Pan
11 - Pitch
*/
#define DMX_FOLDER 0
#define DMX_FILE 1
#define VOLUME_FINE 2
#define VOLUME_COARSE 3
#define ENTRY_POINT_FINE 4
#define ENTRY_POINT_COARSE 5
#define PAN 6
#define PITCH 7
#define PLAYBACK 8
#define HP_FREQ 9
#define LOW_FREQ 10
#define LOW_Q 11
#define LOW_GAIN 12
#define MIDLOW_FREQ 13
#define MIDLOW_Q 14
#define MIDLOW_GAIN 15
#define MIDHIGH_FREQ 16
#define MIDHIGH_Q 17
#define MIDHIGH_GAIN 18
#define HIGH_FREQ 19
#define HIGH_Q 20
#define HIGH_GAIN 21
#define FILTER_BANK_GAIN 22
#define SEND1 23
#define SEND2 24
#define LAYER_CHANNELS 25
// ToDo: Tiene bastante sentido cambiar estos defines por un enum
// ¿Ganaría algo en eficiencia? En claridad del código sí.
#define VOLUME_COARSE 0
#define PAN 1
#define DMX_FOLDER 2
#define DMX_FILE 3
#define PLAYBACK 4
#define CONTROL 5
#define VOLUME_FINE 6
#define ENTRY_POINT_COARSE 7
#define ENTRY_POINT_FINE 8
#define PITCH 9
#define LAYER_CHANNELS 10
constexpr const char* dmxChannelToString(int e) noexcept
{
switch (e) {
case HP_FREQ: return "High Pass Cutoff Frec";
case LOW_FREQ: return "Low Cutoff Frec";
case LOW_Q: return "Low Slope";
case LOW_GAIN: return "Low Gain";
case MIDLOW_FREQ: return "Mid Low Frec";
case MIDLOW_Q: return "Mid Low Q";
case MIDLOW_GAIN: return "Mid Low Gain";
case MIDHIGH_FREQ: return "Mid High Frec";
case MIDHIGH_Q: return "Mid High Q";
case MIDHIGH_GAIN: return "Mid High Gain";
case HIGH_FREQ: return "High Cutoff Frec";
case HIGH_Q: return "High Slope";
case HIGH_GAIN: return "High Gain";
case FILTER_BANK_GAIN: return "Post Filters Gain";
default: return "++--++--++";
}
}
#endif // DMXPERSONALITY_H

34
src/dmxwidget.cpp Normal file
View file

@ -0,0 +1,34 @@
#include "dmxwidget.h"
dmxWidget::dmxWidget(QWidget *parent) :
QGroupBox(parent)
, m_receiveDMX(new QCheckBox)
, m_watchDMX(new QTimer)
{
this->setFocusPolicy(Qt::FocusPolicy::NoFocus);
QVBoxLayout *vbox = new QVBoxLayout;
m_receiveDMX->setText("DMX Signal");
vbox->addWidget(m_receiveDMX);
vbox->setSpacing(1);
vbox->setContentsMargins(1, 1, 1, 1);
this->setLayout(vbox);
connect(m_watchDMX, SIGNAL(timeout()),
this, SLOT(watchDMXExpired()));
m_watchDMX->start(2000);
}
dmxWidget::~dmxWidget()
{
}
void dmxWidget::watchDMXExpired() {
m_receiveDMX->setChecked(false);
}
void dmxWidget::updateWatchDMX(int uni)
{
(void)uni;
if (m_receiveDMX->isChecked() == false)
m_receiveDMX->setChecked(true);
}

29
src/dmxwidget.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef DMXWIDGET_H
#define DMXWIDGET_H
#include <QCheckBox>
#include <QGroupBox>
#include <QVBoxLayout>
#include <QTimer>
class dmxWidget : public QGroupBox
{
Q_OBJECT
public:
dmxWidget(QWidget *parent);
~dmxWidget();
public slots:
void updateWatchDMX(int uni);
private:
QCheckBox *m_receiveDMX;
QTimer *m_watchDMX;
private slots:
void watchDMXExpired();
};
#endif // DMXWIDGET_H

103
src/filterbankwidget.cpp Normal file
View file

@ -0,0 +1,103 @@
#include "filterbankwidget.h"
#include <QBoxLayout>
#include "dmxPersonality.h"
#include "defines.h"
#define BORDER "#CFB0C9;"
#define BACK "#281024;"
FilterBankWidget::FilterBankWidget(QWidget *parent)
: QWidget{parent}
{
QHBoxLayout *layout = new QHBoxLayout;
layout->setAlignment(Qt::AlignHCenter);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
this->setStyleSheet("border: 1px solid #CFB0C9;"
"margin: 0px;"
"background-color: #080402;"
"font-size: 13px;");
for (int i = 0; i < 13; i++) {
fb[i] = new ClickableDoubleSpinBox;
const char *name = dmxChannelToString(i + 9);
fb[i]->setObjectName(name);
fb[i]->setToolTip(name);
}
QVBoxLayout *master = new QVBoxLayout;
fb[0]->setRange(0, 500);
m_bypass = new QCheckBox;
master->addWidget(m_bypass);
m_bypass->setText("Bypass");
m_bypass->setMinimumWidth(MIN_WIDTH / 4);
m_bypass->setStyleSheet("QCheckBox { border: 1px solid #CFB0C9;"
"margin: 0px;"
"background-color: #c82840;"
"font-size: 8px;}");
connect(m_bypass, SIGNAL(stateChanged(int)), this, SLOT(bypassChanged(int)));
master->addWidget(fb[0]);
layout->addLayout(master);
for (int i = 1; i < 13;) {
QVBoxLayout *filterLayout= new QVBoxLayout;
for (int j = i; j < i + 3; j++) {
if ((j - 1) % 3 == 0)
fb[j]->setRange(0, 24000);
else if ((i - 1) % 3 == 1) {
fb[j]->setRange(0, 10);
} else {
fb[j]->setRange(-50, 50);
}
filterLayout->insertWidget(j, fb[j]);
}
filterLayout->setSpacing(0);
filterLayout->setAlignment(Qt::AlignHCenter);
filterLayout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(filterLayout);
i += 3;
}
setLayout(layout);
}
void FilterBankWidget::setValue(int filter, int value)
{
double result = 0;
int channel = filter + 9;
if (channel == HP_FREQ) {
result = double((value * 1.31) + 16.0f); // 16 - 350
} else if (channel == LOW_FREQ) {
result = 30 + (value * 1.647); // 30 - 450
} else if (channel == LOW_Q) {
result = (double)(value / 32.0f) + 0.1f; // 0.1 - 8
} else if (channel == LOW_GAIN) {
result = (double)(value / 21.25f) - 6.023528412f;
} else if (channel == MIDLOW_FREQ) {
result = 200 + (value * 9.019607843); // 200 - 450
} else if (channel == MIDLOW_Q) {
result = (double)( value / 64.0f) + 0.10; // 0.1 - 4
} else if (channel == MIDLOW_GAIN) {
result = (double)(value / 7.0833333333333f) - 18.0f;
} else if (channel == MIDHIGH_FREQ) {
result = 600 + (value * 25.09803922); // 600 - 7000
} else if (channel == MIDHIGH_Q) {
result = (double)( value / 64.0f) + 0.10; // 0.1 - 4
} else if (channel == MIDHIGH_GAIN) {
result = (double)(value / 7.0833333333333f) - 18.0f;
} else if (channel == HIGH_FREQ) {
result = 1500 + (value * 56.8627451); // 1500 - 16000
} else if (channel == HIGH_Q) {
result = (double)( value / 32.0f) + 0.1f;
} else if (channel == HIGH_GAIN) {
result = (double)(value / 21.25) - 6.023528412f;
} else
result = (double)value;
fb[filter]->setValue(result);
}
void FilterBankWidget::bypassChanged(int value)
{
if (value == 0)
emit setBypass(false);
else
emit setBypass(true);
}

26
src/filterbankwidget.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef FILTERBANKWIDGET_H
#define FILTERBANKWIDGET_H
#include <QObject>
#include <QWidget>
#include <QCheckBox>
#include "clickabledoublespinbox.h"
class FilterBankWidget : public QWidget
{
Q_OBJECT
public:
explicit FilterBankWidget(QWidget *parent = nullptr);
ClickableDoubleSpinBox *fb[13];
QCheckBox *m_bypass;
void setValue(int filter, int value);
private slots:
void bypassChanged(int value);
signals:
void setBypass(bool value);
};
#endif // FILTERBANKWIDGET_H

View file

@ -0,0 +1,57 @@
/*
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libremediaserver-audio-gui.h"
libreMediaServerAudioUi::libreMediaServerAudioUi(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
this->setWindowTitle(VERSION);
m_aw = new AudioWidget(this);
setCentralWidget(m_aw);
m_dmxWidget = new dmxWidget(this);
QDockWidget *topWidget = new QDockWidget(tr("Master"), this);
topWidget->setAllowedAreas(Qt::TopDockWidgetArea);
topWidget->setWidget(m_dmxWidget);
topWidget->setContentsMargins(0, 0, 0, 0);
addDockWidget(Qt::TopDockWidgetArea, topWidget);
connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup()));
this->setContentsMargins(0, 0, 0, 0);
this->setStyleSheet(
"margin: 0px;"
"color: white;"
"background-color: #3f3038;"
"selection-color: blue;"
"selection-background-color: green"
);
}
libreMediaServerAudioUi::~libreMediaServerAudioUi()
{
}
void libreMediaServerAudioUi::olasetup()
{
QWebView *view = new QWebView();
view->load(QUrl("http://localhost:9090/ola.html"));
view->show();
}

View file

@ -0,0 +1,48 @@
/*
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBREMEDIASERVERAUDIOUI_H
#define LIBREMEDIASERVERAUDIOUI_H
#include <QDockWidget>
#include <QWebView>
#include "audiowidget.h"
#include "dmxwidget.h"
#include "defines.h"
#include "ui_libremediaserver-audio-gui.h"
class libreMediaServerAudioUi : public QMainWindow
{
Q_OBJECT
public:
libreMediaServerAudioUi(QWidget *parent = 0);
virtual ~libreMediaServerAudioUi();
AudioWidget *m_aw;
dmxWidget *m_dmxWidget;
private:
Ui::LibreMediaServerAudio ui;
private slots:
void olasetup();
};
#endif // LIBREMEDIASERVERAUDIOUI_H

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Santi Noreña lms@criptomart.net</author>
<class>LibreMediaServerAudio</class>
<widget class="QMainWindow" name="LibreMediaServerAudio">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>400</height>
</rect>
</property>
<property name="font">
<font>
<family>Unifont</family>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="windowTitle">
<string>LibreMediaServer</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>../../../../criptomart/artwork/logo_v2_criptomart.net.png</normaloff>../../../../criptomart/artwork/logo_v2_criptomart.net.png</iconset>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionLaunch_OLA_Setup"/>
</widget>
<addaction name="menuFile"/>
</widget>
<action name="actionLaunch_OLA_Setup">
<property name="text">
<string>OLA Setup</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -1,7 +1,8 @@
/*
Libre Media Server - A Media Server Sotfware for stage and performing
Copyright (C) 2012-2024 Santi Noreña lms@criptomart.net
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,192 +20,271 @@
#include "libremediaserver-audio.h"
// QTextEdit * libreMediaServerAudio::textEdit = 0;
libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent)
: QMainWindow(parent)
libreMediaServerAudio::libreMediaServerAudio()
{
Q_UNUSED(args);
ui.setupUi(this);
this->setWindowTitle(VERSION);
// Lee la configuración por defecto
Settings::getInstance()->readFile();
// Inicia el objeto de conexión a ola
ola = new olaThread();
Q_CHECK_PTR(ola);
/*
if (args.contains("-log")) {
// Inicia el widget Terminal
textEdit = new QTextEdit;
textEdit->setReadOnly(true);
QDockWidget *bottomWidget = new QDockWidget(tr("Terminal"), this);
bottomWidget->setAllowedAreas(Qt::BottomDockWidgetArea);
bottomWidget->setWidget(textEdit);
addDockWidget(Qt::BottomDockWidgetArea, bottomWidget);
connect(ola, SIGNAL(toTerminal(QString)),
textEdit, SLOT(append(QString)), Qt::QueuedConnection);
}*/
/* connect(MediaLibrary::getInstance(), SIGNAL(debug(QString)),
textEdit, SLOT(append(QString)), Qt::QueuedConnection);
connect(MediaLibrary::getInstance(), SIGNAL(warning(QString)),
textEdit, SLOT(append(QString)), Qt::QueuedConnection);
*/
this->setWindowTitle(VERSION);
// qDebug() << QDate::currentDate().toString() << " "<< QTime::currentTime().toString();
qDebug() << VERSION;
qDebug() << COPYRIGHT;
qDebug() << LICENSE;
setCentralWidget(AudioWidget::getInstance());
// Inicia el widget Master.
amw = new AudioMasterWidget(this);
QDockWidget *topWidget = new QDockWidget(tr("Master"), this);
topWidget->setAllowedAreas(Qt::TopDockWidgetArea);
topWidget->setWidget(amw);
addDockWidget(Qt::TopDockWidgetArea, topWidget);
// Conectamos los menus
connect(ui.actionOpen_conf, SIGNAL(triggered()), this, SLOT(openFile()));
connect(ui.actionSave_conf, SIGNAL(triggered()), this, SLOT(saveFile()));
connect(ui.action_Settings, SIGNAL(triggered()), this, SLOT(settings()));
connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup()));
connect(Settings::getInstance(), SIGNAL( registerUniverse(int) ),
ola, SLOT( registerUniverse(int) ) );
ola->registerUniverse(); // register now all the universes
ola->blockSignals(true);
connect(ola, SIGNAL (layerReceived()),
amw, SLOT(updateWatchDMX()));
// Inicia la media Library
MediaLibrary::getInstance()->initMediaLibrary();
// Inicia la lectura de datos DMX
ola->start(QThread::TimeCriticalPriority );
ola->blockSignals(false);
connect(ola, SIGNAL( dmxOutput(int, int, int) ),
this, SLOT( dmxInput(int, int, int) ) );
qDebug("Init Complete");
m_settings = Settings::getInstance();
m_settings->readFile();
m_ui = m_settings->getShowUi();
m_layersQty = m_settings->getLayersNumber();
m_dmxSettings = m_settings->getDmxSettings();
m_mediaLibrary = new MediaLibrary;
m_mediaLibrary->initMediaLibrary();
for (uint i = 0; i < m_layersQty; i++) {
m_currentMedia[i] = "";
m_currentStatus[i] = Status::Iddle;
#ifdef NOGUI
m_updateUi[i][0] = -1;
m_updateUi[i][1] = -1;
m_updateUi[i][2] = -1;
m_updateUi[i][3] = -1;
#endif
}
if (!m_mae.startEngine(m_layersQty, m_settings->getAudioDeviceId(), m_settings->getAudioDeviceQty())) {
cout << "Can not start Audio Engine!" << endl;
exit(-1);
}
m_ola = new olaThread(this, m_layersQty);
Q_CHECK_PTR(m_ola);
m_ola->blockSignals(true);
m_ola->registerUniverse();
#ifdef NOGUI
m_ola->start(QThread::TimeCriticalPriority );
#endif
m_ola->blockSignals(false);
cout << "Core init Complete." << endl;
}
///////////////////////////////////////////////////////////////////
// Destructor
///////////////////////////////////////////////////////////////////
libreMediaServerAudio::~libreMediaServerAudio()
{
ola->stop();
// qDebug() << QDate::currentDate() << QTime::currentTime();
// qDebug() << "********************************************************************************";
m_ola->stop();
m_mae.stopEngine();
sleep(1);
cout << "bye!" << endl;
exit(0);
}
///////////////////////////////////////////////////////////////////
// Menu File
///////////////////////////////////////////////////////////////////
// Open a configuration File
void libreMediaServerAudio::openFile()
void libreMediaServerAudio::loadMedia(int layer, int folder, int file)
{
QFileDialog dialog(this);
if (!dialog.exec())
QString mediaFile = m_mediaLibrary->requestNewFile(folder, file);
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
return;
QStringList fileNames;
fileNames = dialog.selectedFiles();
QFile file(fileNames.at(0));
// open(&file);
}
// Save configuration File
void libreMediaServerAudio::saveFile()
{
QFileDialog dialog(this);
if (!dialog.exec())
return;
QStringList fileNames;
fileNames = dialog.selectedFiles();
QFile file(fileNames.at(0));
// save(&file);
}
void libreMediaServerAudio::settings()
{
SettingsDialog *sd = new SettingsDialog();
sd->show();
}
///////////////////////////////////////////////////////////////////
// OLA Stuff
///////////////////////////////////////////////////////////////////
void libreMediaServerAudio::olasetup()
{
QWebView *view = new QWebView();
view->load(QUrl("http://localhost:9090/ola.html"));
view->show();
if (QFile::exists(mediaFile)){
m_mae.loadMedia(layer, mediaFile.toLatin1().data());
m_currentMedia[layer] = mediaFile;
#ifndef NOGUI
if (m_ui)
m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));
#endif
m_mae.printFormatInfo(layer);
}
if (m_currentStatus[layer] == Status::PlayingFolder \
|| (m_currentStatus[layer] == Status::PlayingFolderLoop)\
|| (m_currentStatus[layer] == Status::PlayingFolderRandom)) {
m_played.append(file);
} else if (m_currentStatus[layer] == Status::PlayingOnce \
|| m_currentStatus[layer] == Status::PlayingLoop) {
m_played.clear();
}
}
void libreMediaServerAudio::dmxInput(int layer, int channel, int value)
{
// This qDebug slows all the program. Uncomment only for debugging purpouse and comment again in normal use
// qDebug() << tr("olaInterface|") << "newdmx layer" << layer << "channel" << channel << "value" << value;
if (layer > LAYER_CHANNELS)
if (layer >= MAX_LAYERS || channel >= LAYER_CHANNELS)
return;
QString mediaFile = NULL;
int aux;
qreal f;
switch(channel){
case DMX_FOLDER:// Folder
aux = ola->getValue(layer, DMX_FILE);
mediaFile = MediaLibrary::getInstance()->requestNewFile(value, aux);
if (QFile::exists(mediaFile))
AudioWidget::getInstance()->mediaLoaded(layer, mediaFile);
break;
case DMX_FILE:// File
aux = ola->getValue(layer, DMX_FOLDER);
mediaFile = MediaLibrary::getInstance()->requestNewFile(aux, value);
if (QFile::exists(mediaFile))
AudioWidget::getInstance()->mediaLoaded(layer, mediaFile);
break;
case VOLUME_COARSE:
f = ( value * 0x100 ) + ola->getValue(layer, VOLUME_FINE);
AudioWidget::getInstance()->volChanged(layer, f / 655.35);
break;
case VOLUME_FINE:
f = ( ola->getValue(layer, VOLUME_COARSE) * 0x100 ) + value;
AudioWidget::getInstance()->volChanged(layer, f / 655.35);
break;
case PAN:
AudioWidget::getInstance()->panChanged(layer, value);
break;
case PITCH:
AudioWidget::getInstance()->pitchChanged(layer, value);
break;
case PLAYBACK:
if (value == 0)
break;
if (channel == VOLUME_COARSE || channel == VOLUME_FINE) {
m_mae.volChanged(layer, value);
m_updateUi[layer][0] = value;
} else if (channel == PAN) {
m_mae.panChanged(layer, value);
m_updateUi[layer][1] = value;
} else if (channel == PITCH) {
m_mae.pitchChanged(layer, value);
m_updateUi[layer][2] = value;
} else if (channel == ENTRY_POINT_COARSE || channel == ENTRY_POINT_FINE) {
m_mae.setCursor(layer, value);
m_updateUi[layer][3] = value;
} else if (channel == PLAYBACK && value > 0) {
aux = value / 25;
switch (aux) {
case 0 :
AudioWidget::getInstance()->playbackChanged(layer, PlayingOnce);
break;
case 1 :
AudioWidget::getInstance()->playbackChanged(layer, Stopped);
break;
case 2 :
AudioWidget::getInstance()->playbackChanged(layer, Paused);
break;
case 3 :
AudioWidget::getInstance()->playbackChanged(layer, PlayingLoop);
break;
Status s = m_currentStatus[layer];
if (aux == 0)
s = Status::PlayingOnce;
else if (aux == 1)
s = Status::Stopped;
else if (aux == 2)
s = Status::Paused;
else if (aux == 3)
s = Status::PlayingLoop;
else if (aux == 4)
s = Status::PlayingFolder;
else if (aux == 5)
s = Status::PlayingFolderLoop;
else if (aux == 6)
s = Status::PlayingFolderRandom;
m_mae.playbackChanged(layer, s);
m_currentStatus[layer] = s;
qInfo() << "Layer" << layer << statusToString(s);
#ifndef NOGUI
if (m_ui) {
m_lmsUi->m_aw->playbackChanged(layer, s);
m_updateUi[layer][3] = 1;
m_played.clear();
m_played.append(m_ola->getValue(layer, DMX_FILE));
}
default:
#endif
} else if (channel >= HP_FREQ) {
m_mae.filterParamChanged(layer, channel, value);
#ifndef NOGUI
if (m_ui) {
m_lmsUi->m_aw->filterParamChanged(layer, channel, value);
m_played.clear();
m_played.append(m_ola->getValue(layer, DMX_FILE));
}
#endif
}
}
#ifndef NOGUI
void libreMediaServerAudio::refreshUi() {
if (!m_ui) return;
for (int i= 0; i < m_settings->getLayersNumber(); i++ ) {
if (m_updateUi[i][0] >= 0) {
m_lmsUi->m_aw->volChanged(i, m_updateUi[i][0]);
m_updateUi[i][0] = -1;
}
if (m_updateUi[i][1] >= 0) {
m_lmsUi->m_aw->panChanged(i, m_updateUi[i][1]);
m_updateUi[i][1] = -1;
}
if (m_updateUi[i][2] >= 0) {
m_lmsUi->m_aw->pitchChanged(i, m_updateUi[i][2]);
m_updateUi[i][2] = -1;
}
if (m_updateUi[i][3] >= 0 \
|| m_currentStatus[i] == Status::PlayingOnce\
|| m_currentStatus[i] == Status::PlayingLoop\
|| m_currentStatus[i] == Status::PlayingFolder\
|| m_currentStatus[i] == Status::PlayingFolderLoop
|| m_currentStatus[i] == Status::PlayingFolderRandom) {
m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i));
m_updateUi[i][3] = -1;
}
m_lmsUi->m_aw->levelChanged(i, m_mae.getLevel(i));
if (m_mae.getAtEnd(i)) {
if (m_currentStatus[i] == Status::PlayingOnce) {
m_currentStatus[i] = Status::Stopped;
m_lmsUi->m_aw->playbackChanged(i, Status::Stopped);
}
if (m_currentStatus[i] == Status::PlayingFolder) {
uint last = m_played.last();
int folder = m_ola->getValue(i, DMX_FOLDER);
last++;
if (last < m_mediaLibrary->getMediaFolderCount(folder)) {
this->loadMedia(i, folder, last);
m_mae.playbackChanged(i, Status::PlayingFolder);
}
else {
m_currentStatus[i] = Status::Stopped;
m_lmsUi->m_aw->playbackChanged(i, Status::Stopped);
}
}
else if (m_currentStatus[i] == Status::PlayingFolderLoop) {
uint last = m_played.last();
int folder = m_ola->getValue(i, DMX_FOLDER);
last++;
if (last >= m_mediaLibrary->getMediaFolderCount(folder)) {
this->loadMedia(i, folder, 0);
m_mae.playbackChanged(i, Status::PlayingFolderLoop);
} else {
this->loadMedia(i, folder, last);
m_mae.playbackChanged(i, Status::PlayingFolder);
}
}
else if (m_currentStatus[i] == Status::PlayingFolderRandom) {
int last = -1;
int folder = m_ola->getValue(i, DMX_FOLDER);
if (uint(abs(m_played.size())) >= m_mediaLibrary->getMediaFolderCount(folder))
m_played.clear();
while (last == -1) {
last = rand() % m_mediaLibrary->getMediaFolderCount(folder);
if (m_played.contains(last))
last = -1;
}
this->loadMedia(i, folder, last);
m_mae.playbackChanged(i, Status::PlayingFolderRandom);
}
}
}
}
void libreMediaServerAudio::setUi(libreMediaServerAudioUi *lmsUi)
{
m_lmsUi = lmsUi;
m_ui = true;
connect(m_ola, SIGNAL(universeReceived(int)), m_lmsUi->m_dmxWidget, SLOT(updateWatchDMX(int)));
connect(m_lmsUi->m_aw, SIGNAL(uiSliderChanged(int, Slider, int)), this, SLOT(uiSliderChanged(int, Slider, int)));
connect(m_lmsUi->m_aw, SIGNAL(uiPlaybackChanged(int, Status)), this, SLOT(uiPlaybackChanged(int, Status)));
connect(m_lmsUi->m_aw, SIGNAL(uiLoadMedia(int, QString)), this, SLOT(uiLoadMedia(int, QString)));
m_refreshUi = new QTimer(this);
connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi()));
m_refreshUi->start(UI_REFRESH_TIME);
m_ola->start(QThread::TimeCriticalPriority );
for (uint i = 0; i < m_settings->getAudioDeviceQty(); i++) {
char *name = m_mae.getDeviceName(i);
m_lmsUi->m_aw->busNameChanged(i, name);
}
};
// From Ui widgets
void libreMediaServerAudio::uiSliderChanged(int layer, Slider s, int value)
{
switch (s){
case Slider::Volume:
m_mae.volChanged(layer, value);
break;
case Slider::Pan:
m_mae.panChanged(layer, value);
break;
case Slider::Pitch:
m_mae.pitchChanged(layer, value);
break;
case Slider::Bypass:
m_mae.setBypass(m_dmxSettings.at(layer).audioDevice, layer, value);
break;
case Slider::Bus1:
m_mae.filterParamChanged(layer, SEND1, value / 255.0f);
break;
case Slider::Bus2:
m_mae.filterParamChanged(layer, SEND2, value / 255.0f);
break;
}
}
void libreMediaServerAudio::uiPlaybackChanged(int layer, Status s)
{
ma_result result;
result = m_mae.playbackChanged(layer, s);
if (result == MA_SUCCESS) {
m_currentStatus[layer] = s;
} else {
qWarning() << "ui playback change error " << result << " status " << statusToString(s) << "layer" << layer;
}
}
void libreMediaServerAudio::uiLoadMedia(int layer, QString mediaFile)
{
ma_result result;
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
return;
result = m_mae.loadMedia(layer, mediaFile.toLatin1().data());
if (result == MA_SUCCESS) {
m_currentMedia[layer] = mediaFile;
m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));
} else {
qWarning() << "ui load media error" << result << "file" << mediaFile << "layer" << layer;
}
}
#endif

View file

@ -1,6 +1,7 @@
/*
Libre Media Server - A Media Server Sotfware for stage and performing
Copyright (C) 2012-2014 Santiago Noreña libremediaserver@gmail.com
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,87 +20,56 @@
#ifndef LIBREMEDIASERVERAUDIO_H
#define LIBREMEDIASERVERAUDIO_H
#include <QMainWindow>
#include <QDockWidget>
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QTextStream>
#include <QWebView>
#include <QVBoxLayout>
#include <QTextEdit>
#include <bits/stdc++.h>
using namespace std;
#include "medialibrary.h"
#include "audiowidget.h"
#include "settings.h"
#include "miniaudioengine.h"
#include "olathread.h"
#include "audiomasterwidget.h"
#include "settings.h"
#include "defines.h"
#include "settingsdialog.h"
#ifndef NOGUI
#include "libremediaserver-audio-gui.h"
#endif
#include "ui_libremediaserver-audio.h"
class QMenu;
class libreMediaServerAudio : public QMainWindow
class libreMediaServerAudio : public QObject
{
Q_OBJECT
public:
libreMediaServerAudio (QStringList args, QWidget *parent = 0);
libreMediaServerAudio();
virtual ~libreMediaServerAudio();
Ui::LibreMediaServerAudio ui;
// static QTextEdit *textEdit; // Terminal de feedback
protected:
void dmxInput(int layer, int channel, int value);
void loadMedia(int layer, int folder, int file);
#ifndef NOGUI
void setUi(libreMediaServerAudioUi *lmsUi);
bool inline getShowUi() { return m_settings->getShowUi(); }
#endif
private:
// void MessageHandler(QtMsgType type, const QMessageLogContext &logcontext, const QString &msg);
AudioMasterWidget *amw;
olaThread *ola;
void open_start();
void save_finish();
void open(QFile *file);
void save(QFile *file);
public slots:
// inline void toTerminal(QString msg) { textEdit->append(msg); }
olaThread *m_ola;
MediaLibrary *m_mediaLibrary;
MiniAudioEngine m_mae;
Settings *m_settings;
QString m_currentMedia[MAX_LAYERS];
Status m_currentStatus[MAX_LAYERS];
QList<dmxSetting> m_dmxSettings;
bool m_ui;
QList<int> m_played;
uint m_layersQty;
#ifndef NOGUI
QTimer *m_refreshUi;
libreMediaServerAudioUi *m_lmsUi;
float m_updateUi[MAX_LAYERS][4];
private slots:
void refreshUi();
void uiSliderChanged(int layer, Slider s, int value);
void uiPlaybackChanged(int layer, Status s);
void uiLoadMedia(int layer, QString s);
/**
* @brief Shows the OLA web setup page
*/
void olasetup();
/**
* @brief Parser for new dmx data arriving
* @param layer
* @param channel
* @param value
*/
void dmxInput(int layer, int channel, int value);
// Menu File
/**
* @brief REad from dis a configuration file
*/
void openFile();
/**
* @brief Write to disk a configuration file
*/
void saveFile();
/**
* @brief OPen the settings dialog
*/
void settings();
#endif
};
#endif // LIBREMEDIASERVERAUDIO_H

View file

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Santi Noreña belfegor@gmail.com</author>
<class>LibreMediaServerAudio</class>
<widget class="QMainWindow" name="LibreMediaServerAudio">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>126</width>
<height>89</height>
</rect>
</property>
<property name="windowTitle">
<string>LibreMediaServer</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>126</width>
<height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpen_conf"/>
<addaction name="actionSave_conf"/>
<addaction name="action_Settings"/>
<addaction name="actionLaunch_OLA_Setup"/>
</widget>
<addaction name="menuFile"/>
</widget>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionOpen_conf">
<property name="text">
<string>Open Configuration...</string>
</property>
</action>
<action name="actionSave_conf">
<property name="text">
<string>Save Configuration...</string>
</property>
</action>
<action name="action_Settings">
<property name="text">
<string>Settings...</string>
</property>
</action>
<action name="actionInitMSEX">
<property name="checkable">
<bool>false</bool>
</property>
<property name="text">
<string>Init</string>
</property>
</action>
<action name="actionIP_Address">
<property name="text">
<string>IP Address</string>
</property>
</action>
<action name="actionMake_Thumbs">
<property name="text">
<string>Make Thumbs</string>
</property>
</action>
<action name="actionLaunch_OLA_Setup">
<property name="text">
<string>OLA Setup...</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

250
src/ma_writer_node.c Normal file
View file

@ -0,0 +1,250 @@
#include "ma_writer_node.h"
#include "miniaudio.c"
MA_API ma_writer_node_config ma_writer_node_config_init(ma_uint32 channels, ma_uint32 bufferSizeInFrames, ma_pcm_rb *rb)
{
ma_writer_node_config config;
MA_ZERO_OBJECT(&config);
config.nodeConfig = ma_node_config_init();
config.channels = channels;
config.bufferSizeInFrames = bufferSizeInFrames;
config.pBuffer = rb;
return config;
}
static void ma_writer_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
{
ma_writer_node* pWriteNode = (ma_writer_node*)pNode;
MA_ASSERT(pWriteNode != NULL);
MA_ASSERT(ma_node_get_input_bus_count(&pWriteNode->baseNode) == 2);
if (*pFrameCountIn > 0) {
void *pWriteBuffer = NULL;
ma_pcm_rb_acquire_write(pWriteNode->pBuffer, pFrameCountIn, &pWriteBuffer);
if (pWriteBuffer != NULL) {
ma_copy_pcm_frames(pWriteBuffer, ppFramesIn[1], *pFrameCountIn, ma_format_f32, pWriteNode->channels);
ma_pcm_rb_commit_write(pWriteNode->pBuffer, *pFrameCountIn);
}
}
ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, pWriteNode->channels);
}
static ma_node_vtable g_ma_writer_node_vtable =
{
ma_writer_node_process_pcm_frames,
NULL,
2,
1,
0
};
MA_API ma_result ma_writer_node_init(ma_node_graph* pNodeGraph, const ma_writer_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_writer_node* pWriteNode)
{
ma_result result;
ma_node_config baseConfig;
ma_uint32 inputChannels[2];
ma_uint32 outputChannels[1];
if (pWriteNode == NULL || pConfig == NULL || pConfig->pBuffer == NULL \
|| (pConfig->channels > MA_MAX_NODE_BUS_COUNT) ) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pWriteNode);
inputChannels[0] = pConfig->channels;
inputChannels[1] = pConfig->channels;
outputChannels[0] = pConfig->channels;
baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_writer_node_vtable;
baseConfig.pInputChannels = inputChannels;
baseConfig.pOutputChannels = outputChannels;
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pWriteNode->baseNode);
if (result != MA_SUCCESS) {
return result;
}
pWriteNode->bufferSizeInFrames = pConfig->bufferSizeInFrames;
pWriteNode->pBuffer = pConfig->pBuffer;
pWriteNode->channels = pConfig->channels;
return MA_SUCCESS;
}
MA_API void ma_writer_node_uninit(ma_writer_node* pWriteNode, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_node_uninit(&pWriteNode->baseNode, pAllocationCallbacks);
}
/*
* Data Source Ring Buffer
*/
ma_result ma_data_source_rb_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
ma_data_source_rb* ds = (ma_data_source_rb*)pDataSource;
ma_uint32 pcmFramesAvailableInRB = 0;
ma_uint32 pcmFramesProcessed = 0;
while (pcmFramesProcessed < frameCount) {
pcmFramesAvailableInRB = ma_pcm_rb_available_read(ds->rb);
if (pcmFramesAvailableInRB == 0) {
break;
}
ma_uint32 framesToRead = frameCount - pcmFramesProcessed;
if (framesToRead > pcmFramesAvailableInRB) {
framesToRead = pcmFramesAvailableInRB;
}
void* pReadBuffer = NULL;
ma_pcm_rb_acquire_read(ds->rb, &framesToRead, &pReadBuffer);
if (pReadBuffer != NULL) {
ma_copy_pcm_frames(pFramesOut, pReadBuffer, framesToRead, ma_format_f32, 2);
ma_pcm_rb_commit_read(ds->rb, framesToRead);
pcmFramesProcessed += framesToRead;
}
else {
break;
}
}
*pFramesRead += pcmFramesProcessed;
return MA_SUCCESS;
}
ma_result ma_data_source_rb_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
(void)pDataSource;
(void)frameIndex;
return MA_NOT_IMPLEMENTED;
}
ma_result ma_data_source_rb_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
{
(void)pDataSource;
*pFormat = ma_format_f32;
*pChannels = 2;
*pSampleRate = ma_standard_sample_rate_48000;
return MA_SUCCESS;
}
ma_result ma_data_source_rb_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor)
{
(void)pDataSource;
*pCursor = 0;
return MA_NOT_IMPLEMENTED;
}
ma_result ma_data_source_rb_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
{
(void)pDataSource;
*pLength = 0;
return MA_NOT_IMPLEMENTED;
}
ma_data_source_vtable g_ma_data_source_rb_vtable =
{
ma_data_source_rb_read,
ma_data_source_rb_seek,
ma_data_source_rb_get_data_format,
ma_data_source_rb_get_cursor,
ma_data_source_rb_get_length
};
ma_result ma_data_source_rb_init(ma_data_source_rb* pMyDataSource, ma_pcm_rb *ringBuffer)
{
ma_result result;
ma_data_source_config baseConfig;
baseConfig = ma_data_source_config_init();
baseConfig.vtable = &g_ma_data_source_rb_vtable;
result = ma_data_source_init(&baseConfig, &pMyDataSource->base);
if (result != MA_SUCCESS) {
return result;
}
pMyDataSource->rb = ringBuffer;
return MA_SUCCESS;
}
void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource)
{
ma_data_source_uninit(&pMyDataSource->base);
}
/*
* vumeter
*/
MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 format, ma_uint32 sampleRate)
{
ma_vumeter_node_config config;
MA_ZERO_OBJECT(&config);
config.nodeConfig = ma_node_config_init();
config.channels = channels;
config.sampleRate = sampleRate;
config.format = format;
return config;
}
static void ma_vumeter_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
{
ma_vumeter_node* pVumeterNode = (ma_vumeter_node*)pNode;
MA_ASSERT(pVumeterNode != NULL);
MA_ASSERT(ma_node_get_input_bus_count(&pVumeterNode->baseNode) == 1);
for (uint i = 0; i < *pFrameCountIn; i++) {
float input = fabsf(ppFramesIn[0][i]);
pVumeterNode->level += pVumeterNode->alpha * (input - pVumeterNode->level);
}
ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, pVumeterNode->format, pVumeterNode->channels);
}
static ma_node_vtable g_ma_vumeter_node_vtable =
{
ma_vumeter_node_process_pcm_frames,
NULL,
1,
1,
0
};
MA_API ma_result ma_vumeter_node_init(ma_node_graph* pNodeGraph, const ma_vumeter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_vumeter_node* pVumeterNode)
{
ma_result result;
ma_node_config baseConfig;
ma_uint32 inputChannels[1];
ma_uint32 outputChannels[1];
if (pVumeterNode == NULL || pConfig == NULL \
|| (pConfig->channels > MA_MAX_NODE_BUS_COUNT) ) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pVumeterNode);
inputChannels[0] = pConfig->channels;
outputChannels[0] = pConfig->channels;
baseConfig = pConfig->nodeConfig;
baseConfig.vtable = &g_ma_vumeter_node_vtable;
baseConfig.pInputChannels = inputChannels;
baseConfig.pOutputChannels = outputChannels;
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pVumeterNode->baseNode);
if (result != MA_SUCCESS) { return result;
}
pVumeterNode->sampleRate = pConfig->sampleRate;
pVumeterNode->channels = pConfig->channels;
pVumeterNode->format = pConfig->format;
pVumeterNode->level = 0;
pVumeterNode->TC = 0.250f;
pVumeterNode->alpha = 1.0 - expf( (-2.0 * M_PI) / (pVumeterNode->TC * pConfig->sampleRate));
return MA_SUCCESS;
}
MA_API void ma_vumeter_node_uninit(ma_vumeter_node* pVumeterNode, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_node_uninit(&pVumeterNode->baseNode, pAllocationCallbacks);
}

85
src/ma_writer_node.h Normal file
View file

@ -0,0 +1,85 @@
/* Include ma_writer_node.h after miniaudio.h */
#ifndef ma_writer_node_h
#define ma_writer_node_h
#ifdef __cplusplus
extern "C" {
#endif
#include "miniaudio.h"
/*
* writer
*/
typedef struct
{
ma_node_config nodeConfig;
ma_uint32 channels;
ma_uint32 bufferSizeInFrames;
ma_pcm_rb *pBuffer;
} ma_writer_node_config;
MA_API ma_writer_node_config ma_writer_node_config_init(ma_uint32 channels, ma_uint32 bufferSizeInFrames, ma_pcm_rb *rb);
typedef struct
{
ma_node_base baseNode;
ma_uint32 bufferSizeInFrames;
ma_pcm_rb *pBuffer;
ma_uint32 channels;
} ma_writer_node;
MA_API ma_result ma_writer_node_init(ma_node_graph* pNodeGraph, const ma_writer_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_writer_node* pWriteNode);
MA_API void ma_writer_node_uninit(ma_writer_node* pWriteNode, const ma_allocation_callbacks* pAllocationCallbacks);
/**
* data source ring buffer
*/
typedef struct
{
ma_data_source_base base;
ma_pcm_rb *rb;
} ma_data_source_rb;
ma_result ma_data_source_rb_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
ma_result ma_data_source_rb_seek(ma_data_source* pDataSource, ma_uint64 frameIndex);
ma_result ma_data_source_rb_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
ma_result ma_data_source_rb_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor);
ma_result ma_data_source_rb_get_length(ma_data_source* pDataSource, ma_uint64* pLength);
ma_result ma_data_source_rb_init(ma_data_source_rb* pMyDataSource, ma_pcm_rb *ringBuffer);
void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource);
/*
* VU meter
*/
typedef struct
{
ma_node_config nodeConfig;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint32 format;
} ma_vumeter_node_config;
MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 format, ma_uint32 sampleRate);
typedef struct
{
ma_node_base baseNode;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint32 format;
float level;
float TC;
float alpha;
} ma_vumeter_node;
MA_API ma_result ma_vumeter_node_init(ma_node_graph* pNodeGraph, const ma_vumeter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_vumeter_node* pVumeterNode);
MA_API void ma_vumeter_node_uninit(ma_vumeter_node* pVumeterNode, const ma_allocation_callbacks* pAllocationCallbacks);
MA_API inline float ma_vumeter_node_get_level(ma_vumeter_node* pVumeterNode) { return 5 * pVumeterNode->level; };
#ifdef __cplusplus
}
#endif
#endif /* ma_writer_node_h */

View file

@ -1,8 +1,8 @@
/*
Libre Media Server - A media server for audio playback in stage arts
controlled by lingting protocols (DMX, ArtNet, ACN,...)
Copyright (C) 2015-2024 Santi Noreña lms@criptomart.net
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -18,162 +18,28 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QApplication>
//#include <QMutex>
//#include <QMutexLocker>
#include "libremediaserver-audio.h"
#include "main.h"
// Handler for pipe the stderr to a log file and texEdit
//bool initMessageHandler = false;
//QFile outFile;
//QMutex mutex;
//QMutexLocker mutexLocker(mutex);
/*
void MessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
bool hasUi(int &argc, char *argv[])
{
Q_UNUSED(context);
// mutex.lock();
// Create the log dir and log file
if (!initMessageHandler) {
QDir dir;
if (!dir.exists("log")) {
if (!dir.mkdir("log")) {
qDebug()<<"MessageHandler: Can not create log folder";
return;
}
}
QString filename;
QDate date = QDate::currentDate();
QTime time = QTime::currentTime();
filename.append("./log/log_");
filename.append(date.toString("ddMMyy-"));
filename.append(time.toString("hhmmss.txt"));
outFile.setFileName(filename);
if (!outFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
qWarning("main/MessageHandler/Qfile::open: can not open log file");
return;
}
initMessageHandler = true;
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "--gui"))
return true;
}
QTextStream ts(&outFile);*/
/* if (libreMediaServerAudio::textEdit == 0)
{
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
abort();
}
}
else
{
QString txt;
switch (type) {
case QtDebugMsg:
txt.append("Debug from File: ");
txt.append(context.file);
txt.append(" Line: ");
txt.append(QString::number(context.line));
// txt.append(" Function: ");
// txt.append(context.function);
txt.append(" Message: ");
txt.append(msg);
// libreMediaServerAudio::textEdit->append(msg);, context.file, context.line, context.function);
libreMediaServerAudio::textEdit->append(txt);
break;
case QtWarningMsg:
txt.append("Warning from File: ");
txt.append(context.file);
txt.append(" Line: ");
txt.append(QString::number(context.line));
txt.append(" Function: ");
txt.append(context.function);
txt.append(" Message: ");
txt.append(msg);
// libreMediaServerAudio::textEdit->append(msg);, context.file, context.line, context.function);
libreMediaServerAudio::textEdit->append(txt);
abort();
break;
case QtCriticalMsg:
// txt.append("Critical from File: ");
txt.append(context.file);
txt.append(" Line: ");
txt.append(QString::number(context.line));
txt.append(" Function: ");
txt.append(context.function);
txt.append(" Message ");
txt.append(msg);
// libreMediaServerAudio::textEdit->append(msg);, context.file, context.line, context.function);
libreMediaServerAudio::textEdit->append(txt);
abort();
break;
case QtFatalMsg:
// txt.append("Fatal from File: ");
txt.append(context.file);
txt.append(" Line: ");
txt.append(QString::number(context.line));
txt.append(" Function: ");
txt.append(context.function);
txt.append(" Message: ");
txt.append(msg);
// libreMediaServerAudio::textEdit->append(msg);, context.file, context.line, context.function);
libreMediaServerAudio::textEdit->append(txt);// ts << txt << endl;
abort();
}
// outFile.write(txt.toLatin1().constData(), txt.size());
// ts << txt << endl;
// libreMediaServerAudio::textEdit->append(txt);
}
// mutex.unlock();
}*/
return false;
}
int main(int argc, char *argv[])
{
// ToDo: discriminar niveles de log y log a fichero segúna argumentos
/*
if (args.contains("-log"))
{
qInstallMessageHandler(MessageHandler);
}*/
// qInstallMessageHandler(MessageHandler);
QApplication app(argc, argv);
QStringList args = app.arguments();
// parse the command line
if (args.size() > 1)
libreMediaServerAudio lms;
#ifndef NOGUI
if (hasUi(argc, argv) || lms.getShowUi())
{
if (args.contains("-v"))
{
qDebug() << VERSION;
qDebug() << COPYRIGHT;
qDebug() << LICENSE;
return 0;
}
if (args.contains("-h"))
{
qDebug() << VERSION;
qDebug() << COPYRIGHT;
qDebug() << LICENSE;
qDebug() << "Help for command line options:";
qDebug() << "-v show the version and exits";
qDebug() << "-log write the debug information to a log file instead stderr";
qDebug() << "-h this help";
return 0;
}
libreMediaServerAudioUi *lmsUi = new libreMediaServerAudioUi();
lms.setUi(lmsUi);
lmsUi->show();
}
libreMediaServerAudio libreMediaServerAudio(args);
libreMediaServerAudio.show();
#endif
return app.exec();
}

20
src/main.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef MAIN_H
#define MAIN_H
#include <QCoreApplication>
#include <QApplication>
#include "medialibrary.h"
#include "olathread.h"
#include "settings.h"
#include "libremediaserver-audio.h"
#include "libremediaserver-audio-gui.h"
#include "defines.h"
olaThread *m_ola;
MediaLibrary *m_mediaLibrary;
// slots
void dmxInput(int layer, int channel, int value);
#endif // MAIN_H

View file

@ -1,17 +1,5 @@
#include "medialibrary.h"
#include <QDebug>
MediaLibrary *MediaLibrary::_instance = 0;
MediaLibrary *MediaLibrary::getInstance() {
if (_instance == 0) {
_instance = new MediaLibrary();
Q_CHECK_PTR(_instance);
}
return _instance;
}
MediaLibrary::MediaLibrary(QObject *parent) :
QObject(parent)
@ -20,7 +8,6 @@ MediaLibrary::MediaLibrary(QObject *parent) :
}
void MediaLibrary::initMediaLibrary() {
qDebug("starting the media library");
QDir dir;
if (!dir.cd(Settings::getInstance()->getPathMedia())) {
qWarning("Can not cd to the path: %s", Settings::getInstance()->getPathMedia().toLatin1().constData());
@ -45,9 +32,6 @@ void MediaLibrary::initMediaLibrary() {
}
}
/**
* This set every media file included in one library/folder
*/
QList<MediaFile> MediaLibrary::getMediaInformation(QDir dir)
{
QList<MediaFile> mediaList;
@ -57,34 +41,26 @@ QList<MediaFile> MediaLibrary::getMediaInformation(QDir dir)
QFileInfo fileInfo;
for (int i = 0; i < filelist.size(); ++i) {
fileInfo = filelist.at(i);
// Update the data base with the new file
mediainf.Number = i;
mediainf.MediaName = fileInfo.absoluteFilePath();
mediainf.MediaLength = 1000; // ¿?¿?¿?¿?
mediainf.MediaLength = 1000;
mediaList.append(mediainf);
}
return mediaList;
}
/** Selects one media path from the library
*
*/
QString MediaLibrary::requestNewFile(int folder, int file){
// Select one mediafile from the media library
if (!m_media) {
qWarning("Media Library not init. Set a correct path to medias library");
qWarning("MediaLibrary is not init, set a correct path.");
return NULL;
}
QString newfile;
if (folder < m_media->size()) {
if (file < m_media->at(folder).m_MediaInformation.size()) {
newfile = m_media->at(folder).m_MediaInformation.at(file).MediaName;
} else {
qDebug("MediaLibrary::requestNewFile(): Requested file is greater than files in library");
}
} else {
qDebug("MediaLibrary::requestNewFile(): Requested folder is greater than media libraries");
}
} else
qInfo("requestNewFile: Requested file %i is greater than files in library %i", file, m_media->at(folder).m_MediaInformation.size());
} else
qInfo("requestNewFile: Requested folder %i is greater than media libraries %i", folder, m_media->size());
return newfile;
}

View file

@ -1,68 +1,42 @@
#ifndef MEDIALIBRARY_H
#define MEDIALIBRARY_H
#include <QObject>
#include <QDir>
#include "defines.h"
#include "settings.h"
#include "defines.h"
// Media Information for MELIn packages. v1.0
struct MediaFile {
quint8 Number; // 0-based contiguous index of the media.
QString MediaName;// Media name.
quint32 MediaLength;// Media length (in frames).
};
// Media Library for ELin packages v1.0
struct MediaFolder {
quint8 m_Id; // Library id.
QString m_Name;// Library name.
quint8 m_ElementCount;// Number of elements in the library.
QList<MediaFile> m_MediaInformation; // Pointer to the Medias Information List of this Library
};
class MediaLibrary : public QObject
{
Q_OBJECT
friend class libreMediaserverAudio; // Not working... WHY?
friend class Settings; // working...
public:
static MediaLibrary *getInstance();
/**
* @brief request a new file from the media library
* @param int folder - the folder required
* @param int layer - file required
* @return QString the file required with full path
*/
MediaLibrary(QObject *parent = 0);
QString requestNewFile(int folder, int layer);
/**
* @brief Inicia la biblioteca de medios
*/
void initMediaLibrary();
protected:
/**
* @brief Reload the media library on change of path or change the media files
*/
inline void rescanMediaLibrary() {
if (m_media != NULL)
delete m_media;
initMediaLibrary();
}
inline uint getMediaFolderCount(int folder) {
return m_media->at(folder).m_ElementCount;
}
private:
QList<MediaFolder> *m_media;
QList<MediaFile> getMediaInformation(QDir dir);
explicit MediaLibrary(QObject *parent = 0);
static MediaLibrary *_instance;
// Wich structure is more efficient for us?
// QList, QMap, QSet, QVector?
QList<MediaFolder> *m_media; // Library to save the folders/media libraries and index each media file inside
/**
* @brief Returns a list withe the medias contained in a folder
* @param QDir - the directory when are the media
* @return QList<MediaFile> The list with the medias in a folder
*/
QList<MediaFile> getMediaInformation(QDir dir); // Get all the information of each media file in a dir
signals:
public slots:
};
#endif // MEDIALIBRARY_H

728
src/miniaudioengine.cpp Normal file
View file

@ -0,0 +1,728 @@
#include "miniaudioengine.h"
#include "dmxPersonality.h"
#define BIAS 1.0f
#define FILTER_ORDER 3
MiniAudioEngine::MiniAudioEngine() {}
void MiniAudioEngine::audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
ma_result result;
result = ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": error audio callback.";
}
(void)pInput;
}
void MiniAudioEngine::stopEngine()
{
for (uint i = 0; i < m_mae.layersQty; i++) {
if (m_mae.mediaLoaded[i])
ma_sound_uninit(&m_mae.sounds[i]);
}
for (uint i = 0; i < m_mae.layersQty; i++) {
ma_splitter_node_uninit(&m_mae.filters[i].input, NULL);
ma_hpf_node_uninit(&m_mae.filters[i].hpf, NULL);
ma_loshelf_node_uninit(&m_mae.filters[i].loshelf, NULL);
ma_peak_node_uninit(&m_mae.filters[i].mLow, NULL);
ma_peak_node_uninit(&m_mae.filters[i].mHigh, NULL);
ma_hishelf_node_uninit(&m_mae.filters[i].hishelf, NULL);
ma_splitter_node_uninit(&m_mae.filters[i].output, NULL);
}
for (uint i = 0; i < m_mae.audioDevicesQty; i++) {
if (i > 0) {
ma_writer_node_uninit(&m_mae.sendAuxNode[i], NULL);
ma_pcm_rb_uninit(&m_mae.auxBuffers[i]);
}
ma_engine_uninit(&m_mae.engines[i]);
ma_device_uninit(&m_mae.devices[i]);
}
ma_context_uninit(&m_mae.context);
ma_resource_manager_uninit(&m_mae.resourceManager);
}
bool MiniAudioEngine::startEngine(uint layers, uint* audioDevicesId, uint audioDevicesQty)
{
ma_result result;
m_mae.layersQty = layers;
m_mae.audioDevicesId = audioDevicesId;
m_mae.audioDevicesQty = audioDevicesQty;
for (uint i =0; i < m_mae.layersQty; i++) {
m_mae.mediaLoaded[i] = MA_FALSE;
m_mae.currentStatus[i].status = Status::Iddle;
m_mae.currentStatus[i].pan = 128;
m_mae.currentStatus[i].pitch = 128;
m_mae.currentStatus[i].vol = 0.0f;
m_mae.currentStatus[i].cursor = 0;
m_mae.currentStatus[i].updated = false;
}
result = this->startContext();
if (result != MA_SUCCESS) return false;
result = this->getAllAudioDevices();
if (result != MA_SUCCESS) return false;
result = this->startDevices();
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed start audio devices." << endl;
return false;
}
result = this->setNodeGraph();
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to set node graph." << endl;
return false;
}
for (uint i = 0; i < m_mae.audioDevicesQty; i++) {
result = ma_engine_start(&m_mae.engines[i]);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to start audio device" << m_mae.audioDevicesId[i] << endl;
return false;
}
}
return true;
}
ma_result MiniAudioEngine::createFilterBank(uint layer)
{
ma_result result;
ma_node_graph *ng = ma_engine_get_node_graph(&m_mae.engines[0]);
ma_node *endpoint = ma_engine_get_endpoint(&m_mae.engines[0]);
filterBank *fb = &m_mae.filters[layer];
ma_splitter_node_config splitterConfig = ma_splitter_node_config_init(CHANNELS);
splitterConfig.outputBusCount= 3;
result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->input);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init input node." << endl;
return result;
}
fb->hpfConfig = ma_hpf_node_config_init(CHANNELS, SAMPLE_RATE, 16, FILTER_ORDER);
result = ma_hpf_node_init(ng, &fb->hpfConfig, NULL, &fb->hpf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init high pass filter node." << endl;
return result;
}
fb->loshelfConfig = ma_loshelf_node_config_init(CHANNELS, SAMPLE_RATE, 0.0f, 1.0f, 30);
result = ma_loshelf_node_init(ng, &fb->loshelfConfig, NULL, &fb->loshelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init low pass filter node." << endl;
return result;
}
fb->mLowConfig = ma_peak_node_config_init(CHANNELS, SAMPLE_RATE, 0.0, 4.0, 200); // double gainDB, double q, double frequency);
result = ma_peak_node_init(ng, &fb->mLowConfig, NULL, &fb->mLow);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init peak low filter node." << endl;
return result;
}
fb->mHighConfig = ma_peak_node_config_init(CHANNELS, SAMPLE_RATE, 0.0, 0.0, 600); // double gainDB, double q, double frequency);
result = ma_peak_node_init(ng, &fb->mHighConfig, NULL, &fb->mHigh);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init peak high filter node." << endl;
return result;
}
fb->hishelfConfig = ma_hishelf_node_config_init(CHANNELS, SAMPLE_RATE, 0.0f, 1.0f, 20000);
result = ma_hishelf_node_init(ng, &fb->hishelfConfig, NULL, &fb->hishelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init hi shelf filter node." << endl;
return result;
}
ma_vumeter_node_config vuc = ma_vumeter_node_config_init(CHANNELS, FORMAT, SAMPLE_RATE);
ma_vumeter_node_init(ng, &vuc, NULL, &fb->vumeter);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init vumeter node." << endl;
return result;
}
splitterConfig.outputBusCount = m_mae.audioDevicesQty;
result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->output);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init output node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->input, 0, &fb->hpf, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach input node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->input, 1, &fb->vumeter, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach bypass connection." << endl;
return result;
}
ma_node_set_output_bus_volume(&fb->input, 1, 0.0f);
result = ma_node_attach_output_bus(&fb->hpf, 0, &fb->loshelf, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach high pass pass filter node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->loshelf, 0, &fb->mLow, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach low shelf filter node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->mLow, 0, &fb->mHigh, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach low peaks filter node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->mHigh, 0, &fb->hishelf, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach high peaks filter node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->hishelf, 0, &fb->vumeter, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach high shelf filter node." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->vumeter, 0, &fb->output, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach high shelf filter node." << endl;
return result;
}
if (m_mae.audioDevicesQty == 1) {
result = ma_node_attach_output_bus(&fb->output, 0, endpoint, 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach output to endpoint." << endl;
return result;
}
} else {
result = ma_node_attach_output_bus(&fb->output, 0, &m_mae.sendAuxNode[1], 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach output node to aux send 1." << endl;
return result;
}
result = ma_node_attach_output_bus(&fb->output, 1, &m_mae.sendAuxNode[1], 1);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach output node to aux send 1." << endl;
return result;
}
for (uint i = 2; i < m_mae.audioDevicesQty; i++) {
result = ma_node_attach_output_bus(&fb->output, i, &m_mae.sendAuxNode[i], 1);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach output node to aux send 1." << endl;
return result;
}
}
}
return result;
}
ma_result MiniAudioEngine::setNodeGraph() {
ma_result result = MA_SUCCESS;
ma_node_graph *ng = ma_engine_get_node_graph(&m_mae.engines[0]);
for (uint i = 1; i < m_mae.audioDevicesQty; i++) {
size_t sizeInFrames = SAMPLE_RATE;
result = ma_pcm_rb_init(FORMAT, CHANNELS, sizeInFrames, NULL, NULL, &m_mae.auxBuffers[i]);
if (result != MA_SUCCESS) {
printf("Failed to initialize ring buffer.\n");
return result;
}
ma_silence_pcm_frames(m_mae.auxBuffers[i].rb.pBuffer, sizeInFrames, FORMAT, CHANNELS);
ma_writer_node_config writerConfig = ma_writer_node_config_init(CHANNELS, SAMPLE_RATE * 5, &m_mae.auxBuffers[i]);
result = ma_writer_node_init(ng, &writerConfig, NULL, &m_mae.sendAuxNode[i]);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init writer node." << endl;
return result;
}
result = ma_node_attach_output_bus(&m_mae.sendAuxNode[i], 0, ma_engine_get_endpoint(&m_mae.engines[0]), 0);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach writer node." << endl;
return result;
}
result = ma_data_source_rb_init(&m_mae.dataSourceRB[i], &m_mae.auxBuffers[i]);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to init data source ring buffer" << endl;
return result;
}
ma_data_source_node_config dataSupplyNodeConfig = ma_data_source_node_config_init(&m_mae.dataSourceRB[i]);
result = ma_data_source_node_init(ma_engine_get_node_graph(&m_mae.engines[i]), &dataSupplyNodeConfig, NULL, &m_mae.dataSupplyNode[i]);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to init data source node" << endl;
return result;
}
result = ma_node_attach_output_bus(&m_mae.dataSupplyNode[i], 0, ma_engine_get_endpoint(&m_mae.engines[i]), 0);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to attach data source rb node" << endl;
return result;
}
}
for (uint i = 0; i < m_mae.layersQty; i++) {
result = this->createFilterBank(i);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed creating filter bank." << endl;
}
}
return (result);
}
ma_result MiniAudioEngine::startDevices()
{
ma_result result = MA_SUCCESS;
ma_device_config deviceConfig;
ma_engine_config engineConfig;
deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.capture.format = m_mae.resourceManager.config.decodedFormat;
deviceConfig.capture.channels = CHANNELS;
deviceConfig.playback.channels = CHANNELS;
deviceConfig.capture.shareMode = ma_share_mode_shared;
deviceConfig.playback.format = m_mae.resourceManager.config.decodedFormat;
deviceConfig.sampleRate = m_mae.resourceManager.config.decodedSampleRate;
deviceConfig.dataCallback = audioDataCallback;
engineConfig = ma_engine_config_init();
engineConfig.pResourceManager = &m_mae.resourceManager;
engineConfig.defaultVolumeSmoothTimeInPCMFrames = SAMPLE_RATE / 500;
engineConfig.noAutoStart = MA_TRUE;
for (uint internalId = 0; internalId < m_mae.audioDevicesQty; internalId++) {
deviceConfig.capture.pDeviceID = &m_mae.pPlaybackDeviceInfos[m_mae.audioDevicesId[internalId]].id;
deviceConfig.playback.pDeviceID = &m_mae.pPlaybackDeviceInfos[m_mae.audioDevicesId[internalId]].id;
deviceConfig.pUserData = &m_mae.engines[internalId];
result = ma_device_init(&m_mae.context, &deviceConfig, &m_mae.devices[internalId]);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to initialize audio device " << m_mae.pPlaybackDeviceInfos[m_mae.audioDevicesId[internalId]].name << endl;
return result;
}
engineConfig.pDevice = &m_mae.devices[internalId];
result = ma_engine_init(&engineConfig, &m_mae.engines[internalId]);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to initialize audio engine" << endl;
return result;
}
cout << "Initialized Audio Device. internalId: " << internalId << " systemId: " << m_mae.audioDevicesId[internalId] << " " << m_mae.pPlaybackDeviceInfos[m_mae.audioDevicesId[internalId]].name << endl;
}
return result;
}
ma_result MiniAudioEngine::startContext()
{
ma_result result;
ma_resource_manager_config resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = FORMAT;
resourceManagerConfig.decodedChannels = CHANNELS;
resourceManagerConfig.decodedSampleRate = SAMPLE_RATE;
resourceManagerConfig.jobThreadCount = MAX_LAYERS;
result = ma_resource_manager_init(&resourceManagerConfig, &m_mae.resourceManager);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to initialize audio resource manager." << endl;
return result;
}
result = ma_context_init(NULL, 0, NULL, &m_mae.context);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to initialize audio context." << endl;
}
return result;
}
// enum all audio devices in system
ma_result MiniAudioEngine::getAllAudioDevices()
{
ma_result result;
result = ma_context_get_devices(&m_mae.context, &m_mae.pPlaybackDeviceInfos, &m_mae.playbackDeviceCount, NULL, NULL);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to enumerate playback devices." << endl;
ma_context_uninit(&m_mae.context);
return result;
}
cout << "Audio devices available:" << endl;
for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < m_mae.playbackDeviceCount; iAvailableDevice += 1) {
cout << iAvailableDevice << " : " << m_mae.pPlaybackDeviceInfos[iAvailableDevice].name << endl;
}
return result;
}
char* MiniAudioEngine::getDeviceName(uint id)
{
return m_mae.pPlaybackDeviceInfos[m_mae.audioDevicesId[id]].name;
}
ma_result MiniAudioEngine::loadMedia(int layer, char *file)
{
ma_result result;
if (m_mae.mediaLoaded[layer] == MA_TRUE)
{
m_mae.mediaLoaded[layer] = MA_FALSE;
ma_sound_set_volume(&m_mae.sounds[layer], 0.0f);
ma_sound_stop(&m_mae.sounds[layer]);
ma_sound_uninit(&m_mae.sounds[layer]);
}
ma_sound_config soundConfig = ma_sound_config_init();
soundConfig = ma_sound_config_init();
soundConfig.pFilePath = file;
soundConfig.pInitialAttachment = &m_mae.filters[layer].input;
soundConfig.initialAttachmentInputBusIndex = 0;
soundConfig.channelsIn = 0;
soundConfig.channelsOut = CHANNELS;
soundConfig.flags = MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT | MA_SOUND_FLAG_STREAM; //| MA_SOUND_FLAG_NO_PITCH
result = ma_sound_init_ex(&m_mae.engines[0], &soundConfig, &m_mae.sounds[layer]);
if (result != MA_SUCCESS) {
cout << "Error" << result << ": Failed to load file " << file << endl;
return result;
}
m_mae.currentStatus[layer].media = file;
m_mae.currentStatus[layer].updated = true;
m_mae.mediaLoaded[layer] = MA_TRUE;
this->refreshValues(layer);
return result;
}
float MiniAudioEngine::getDuration(int layer)
{
ma_result result;
float ret;
if (m_mae.mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST;
result = ma_sound_get_length_in_seconds(&m_mae.sounds[layer], &ret);
if (result != MA_SUCCESS) {
return result;
}
return (ret * 1000);
}
float MiniAudioEngine::getCursor(int layer)
{
ma_result result;
float ret = 0;
if (m_mae.mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST;
result = ma_sound_get_cursor_in_seconds(&m_mae.sounds[layer], &ret);
if (result != MA_SUCCESS)
{
cout << "Error" << result << ": Can not get cursor " << layer << endl;
ret = MA_ERROR;
}
return ret;
}
ma_result MiniAudioEngine::printFormatInfo(int layer)
{
ma_format format;
ma_uint32 channels;
ma_uint32 sampleRate;
if (m_mae.mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST;
ma_result result = ma_sound_get_data_format(&m_mae.sounds[layer], \
&format, &channels, &sampleRate, NULL, 0);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to get data format " << layer;
cout << endl;
} else {
cout << "Layer:" << layer << " ";
cout << m_mae.currentStatus[layer].media.toLatin1().data();
cout << " samples/sec:" << sampleRate << " format:" << format;
cout << " channels:" << channels << endl;
}
return result;
}
// Expects between 0 and 65535 vol value
void MiniAudioEngine::volChanged(int layer, int vol)
{
m_mae.currentStatus[layer].vol = vol;
if (m_mae.mediaLoaded[layer] == MA_FALSE && m_mae.currentStatus[layer].updated)
return;
float db = ((float)vol / 771.0f) - 85.0f;
if (db <= -85.0f) {
db = 0;
} else
db = ma_volume_db_to_linear(db);
ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, FADE_TIME);
}
void MiniAudioEngine::panChanged(int layer, float value)
{
float result;
m_mae.currentStatus[layer].pan = value;
if (m_mae.mediaLoaded[layer] == false)
return;
result = (value / 128.0) - 1.0;
ma_sound_group_set_pan(&m_mae.sounds[layer], result);
}
void MiniAudioEngine::pitchChanged(int layer, float value)
{
float pitch;
m_mae.currentStatus[layer].pitch = value;
if (m_mae.mediaLoaded[layer] == false)
return;
pitch = value / 128.0;
ma_sound_group_set_pitch(&m_mae.sounds[layer], pitch);
}
ma_result MiniAudioEngine::playbackChanged(int layer, Status status)
{
ma_result result = MA_SUCCESS;
float db = 0;
bool loop = false;
m_mae.currentStatus[layer].status = status;
if (m_mae.mediaLoaded[layer] == MA_FALSE)
return MA_DOES_NOT_EXIST;
m_mae.currentStatus[layer].updated = false;
switch (status) {
case Status::Paused:
result = ma_sound_stop_with_fade_in_milliseconds(&m_mae.sounds[layer], FADE_TIME);
break;
case Status::Stopped:
ma_sound_stop_with_fade_in_milliseconds(&m_mae.sounds[layer], 0.0f);
result = this->seekToCursor(layer, m_mae.currentStatus[layer].cursor);
break;
case Status::PlayingLoop:
loop = true;
if (m_mae.currentStatus[layer].cursor > 0) {
result = this->seekToCursor(layer, m_mae.currentStatus[layer].cursor);
}
case Status::PlayingOnce:
case Status::PlayingFolder:
case Status::PlayingFolderLoop:
case Status::PlayingFolderRandom:
ma_sound_set_looping(&m_mae.sounds[layer], loop);
if (ma_sound_is_playing(&m_mae.sounds[layer])) break;
ma_sound_set_stop_time_in_milliseconds(&m_mae.sounds[layer], ~(ma_uint64)0);
db = (m_mae.currentStatus[layer].vol / 771.0f) - 85.0f;
if (db <= -85.0f) db = 0;
else db = ma_volume_db_to_linear(db);
result = ma_sound_start(&m_mae.sounds[layer]);
ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], 0.000001f, 0.000000f, FADE_TIME);
if (m_mae.currentStatus[layer].cursor > 0)
usleep(FADE_TIME * 1500); // avoid glitch when load when seeking
ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], 0, db, FADE_TIME * 2);
default:
break;
}
m_mae.currentStatus[layer].updated = true;
return result;
}
ma_result MiniAudioEngine::setRangePoint(int layer, int cursor)
{
ma_result result = MA_SUCCESS;
ma_uint64 end = 0, start;
if (m_mae.mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST;
if (cursor == 0)
start = 0;
else {
result = ma_sound_get_length_in_pcm_frames(&m_mae.sounds[layer], &end);
if (result != MA_SUCCESS) { return result; }
start = (cursor * end) / 65535;
}
result = ma_data_source_set_range_in_pcm_frames(&m_mae.sounds[layer].pDataSource, start, end);
if (result != MA_SUCCESS)
cout << "ERROR " << result << " :set range point" << endl;
return (result);
}
ma_result MiniAudioEngine::setLoopPoint(int layer, int cursor)
{
ma_result result = MA_SUCCESS;
ma_uint64 end = 0, start;
if (m_mae.mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST;
if (cursor == 0)
start = 0;
else {
result = ma_sound_get_length_in_pcm_frames(&m_mae.sounds[layer], &end);
if (result != MA_SUCCESS) { return result; }
start = (cursor * end) / 65535;
}
result = ma_data_source_set_loop_point_in_pcm_frames(&m_mae.sounds[layer].pDataSource, start + 1, end - 1);
if (result != MA_SUCCESS)
cout << "ERROR " << result << " :set loop point" << endl;
return (result);
}
ma_result MiniAudioEngine::seekToCursor(int layer, int cursor)
{
ma_result result = MA_SUCCESS;
ma_uint64 end = 0, start;
if (m_mae.mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST;
if (cursor == 0)
start = 0;
else {
result = ma_sound_get_length_in_pcm_frames(&m_mae.sounds[layer], &end);
if (result != MA_SUCCESS) { return result; }
start = (cursor * end) / 65535;
}
result = ma_sound_seek_to_pcm_frame(&m_mae.sounds[layer], start);
return (result);
}
ma_result MiniAudioEngine::setCursor(int layer, int cursor)
{
ma_result result = MA_SUCCESS;
m_mae.currentStatus[layer].cursor = cursor;
result = this->seekToCursor(layer, cursor);
return (result);
}
Status MiniAudioEngine::getStatus(int layer)
{
return m_mae.currentStatus[layer].status;
}
void MiniAudioEngine::refreshValues(int layer)
{
this->panChanged(layer, m_mae.currentStatus[layer].pan);
this->pitchChanged(layer, m_mae.currentStatus[layer].pitch);
this->playbackChanged(layer, m_mae.currentStatus[layer].status);
}
ma_result MiniAudioEngine::filterParamChanged(int layer, int channel, int value)
{
ma_result result = MA_SUCCESS;
filterBank *fb = &m_mae.filters[layer];
if (channel == HP_FREQ) {
fb->hpfConfig.hpf.cutoffFrequency = double((value * 1.31) + 16.0f); // 16 - 350
result = ma_hpf_node_reinit(&fb->hpfConfig.hpf, &fb->hpf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set frecuency high pass filter node." << endl;
return result;
}
} else if (channel == LOW_FREQ) {
fb->loshelfConfig.loshelf.frequency = 30 + (value * 1.647); // 30 - 450
result = ma_loshelf_node_reinit(&fb->loshelfConfig.loshelf, &fb->loshelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set frecuency low shelf filter node." << endl;
return result;
}
} else if (channel == LOW_Q) {
fb->loshelfConfig.loshelf.shelfSlope = (double)(value / 32.0f) + 0.1f; // 0.1 - 8
result = ma_loshelf_node_reinit(&fb->loshelfConfig.loshelf, &fb->loshelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed set Q low shelf filter node." << endl;
return result;
}
} else if (channel == LOW_GAIN) {
fb->loshelfConfig.loshelf.gainDB = (double)(value / 21.25f) - 6.023528412f;
result = ma_loshelf_node_reinit(&fb->loshelfConfig.loshelf, &fb->loshelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed set gain low shelf filter node." << endl;
return result;
}
} else if (channel == MIDLOW_FREQ) {
fb->mLowConfig.peak.frequency = 200 + (value * 9.019607843); // 200 - 450
result = ma_peak_node_reinit(&fb->mLowConfig.peak, &fb->mLow);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set frecuency Mid Low pass filter node." << endl;
return result;
}
} else if (channel == MIDLOW_Q) {
fb->mLowConfig.peak.q = (double)( value / 64.0f) + 0.10; // 0.1 - 4
result = ma_peak_node_reinit(&fb->mLowConfig.peak, &fb->mLow);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set Q Mid Low filter node." << endl;
return result;
}
} else if (channel == MIDLOW_GAIN) {
fb->mLowConfig.peak.gainDB = (double)(value / 7.0833333333333f) - 18.0f;
result = ma_peak_node_reinit(&fb->mLowConfig.peak, &fb->mLow);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set gain Mid Low filter node." << endl;
return result;
}
} else if (channel == MIDHIGH_FREQ) {
fb->mHighConfig.peak.frequency = 600 + (value * 25.09803922); // 600 - 7000
result = ma_peak_node_reinit(&fb->mHighConfig.peak, &fb->mHigh);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set frecuency Mid High filter node." << endl;
return result;
}
} else if (channel == MIDHIGH_Q) {
fb->mHighConfig.peak.q = (double)( value / 64.0f) + 0.10; // 0.1 - 4
result = ma_peak_node_reinit(&fb->mHighConfig.peak, &fb->mHigh);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set Q Mid High filter node." << endl;
return result;
}
} else if (channel == MIDHIGH_GAIN) {
fb->mHighConfig.peak.gainDB = (double)(value / 7.0833333333333f) - 18.0f;
result = ma_peak_node_reinit(&fb->mHighConfig.peak, &fb->mHigh);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to set gain Mid High filter node." << endl;
return result;
}
} else if (channel == HIGH_FREQ) {
fb->hishelfConfig.hishelf.frequency = 1500 + (value * 56.8627451); // 1500 - 16000
result = ma_hishelf_node_reinit(&fb->hishelfConfig.hishelf, &fb->hishelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to frecuency high shelf filter node." << endl;
return result;
}
} else if (channel == HIGH_Q) {
fb->hishelfConfig.hishelf.shelfSlope = (double)( value / 32.0f) + 0.1f;
result = ma_hishelf_node_reinit(&fb->hishelfConfig.hishelf, &fb->hishelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed set Q high shelf filter node." << endl;
return result;
}
} else if (channel == HIGH_GAIN) {
fb->hishelfConfig.hishelf.gainDB = (double)(value / 21.25) - 6.023528412f;
result = ma_hishelf_node_reinit(&fb->hishelfConfig.hishelf, &fb->hishelf);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed set gain high shelf filter node." << endl;
return result;
}
} else if (channel == SEND1) {
float db = ((float)value / 3.0f) - 85.0f;
if (db <= -85.0f) {
db = 0;
} else
db = ma_volume_db_to_linear(db);
ma_node_set_output_bus_volume(&fb->output, 0, db);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed set Send 1 Volume." << endl;
return result;
}
} else if (channel == SEND2) {
float db = ((float)value / 3.0f) - 85.0f;
if (db <= -85.0f) {
db = 0;
} else
db = ma_volume_db_to_linear(db);
ma_node_set_output_bus_volume(&fb->output, 1, db);
if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed set Send 2 Volume." << endl;
}
return result;
}
return (result);
}
bool MiniAudioEngine::setBypass(int audioDevice, int layer, bool bypass)
{
(void)audioDevice;
filterBank *fb = &m_mae.filters[layer];
if (bypass) {
ma_node_set_output_bus_volume(&fb->input, 1, 1.0f);
ma_node_set_output_bus_volume(&fb->input, 0, 0.0f);
} else {
ma_node_set_output_bus_volume(&fb->input, 1, 0.0f);
ma_node_set_output_bus_volume(&fb->input, 0, 1.0f);
}
return true;
}

105
src/miniaudioengine.h Normal file
View file

@ -0,0 +1,105 @@
#ifndef MINIAUDIOENGINE_H
#define MINIAUDIOENGINE_H
#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS
#define MA_ENABLE_JACK
#define MA_DISABLE_PULSE
#define MA_NO_GENERATION
#define MA_DEBUG_OUTPUT
#define MA_LOG_LEVEL_DEBUG DEBUG
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#include "ma_writer_node.h"
#include <bits/stdc++.h>
using namespace std;
#include "defines.h"
typedef struct
{
ma_splitter_node input;
ma_hpf_node hpf;
ma_hpf_node_config hpfConfig;
ma_loshelf_node loshelf;
ma_loshelf_node_config loshelfConfig;
ma_peak_node mLow;
ma_peak_node_config mLowConfig;
ma_peak_node mHigh;
ma_peak_node_config mHighConfig;
ma_hishelf_node hishelf;
ma_hishelf_node_config hishelfConfig;
ma_vumeter_node vumeter;
ma_splitter_node output;
} filterBank;
typedef struct
{
ma_engine engines[MAX_AUDIODEVICES];
ma_device devices[MAX_AUDIODEVICES];
filterBank filters[MAX_LAYERS];
ma_writer_node sendAuxNode[MAX_AUDIODEVICES];
ma_pcm_rb auxBuffers[MAX_AUDIODEVICES];
ma_node_graph ng;
layerData currentStatus[MAX_LAYERS];
ma_sound sounds[MAX_LAYERS];
ma_resource_manager resourceManager;
ma_context context;
ma_device_info* pPlaybackDeviceInfos;
ma_device_info pSelectedPlaybackDeviceInfos[MAX_AUDIODEVICES];
ma_uint32 playbackDeviceCount;
ma_uint32 devicesSelected;
ma_bool8 mediaLoaded[MAX_LAYERS];
uint layersQty;
uint *audioDevicesId;
uint audioDevicesQty;
ma_data_source_node dataSupplyNode[MAX_AUDIODEVICES];
ma_data_source_rb dataSourceRB[MAX_AUDIODEVICES];
} MAE;
class MiniAudioEngine
{
friend class libreMediaServerAudio;
public:
static void audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount);
protected:
MiniAudioEngine();
void stopEngine();
bool startEngine(uint layersQty, uint* audioDevicesID, uint audioDevicesQty);
ma_result loadMedia(int layer, char *media);
void volChanged(int layer, int vol);
void panChanged(int layer, float pan);
void pitchChanged(int layer, float pitch);
ma_result playbackChanged(int layer, Status status);
ma_result setCursor(int layer, int cursor);
ma_result printFormatInfo(int layer);
float getDuration(int layer);
float getCursor(int layer);
Status getStatus(int layer);
inline float getVol(int layer) {
return ma_sound_get_volume(&m_mae.sounds[layer]);
};
inline bool getAtEnd(int layer) { return m_mae.sounds[layer].atEnd; }
ma_result filterParamChanged(int layer, int channel, int value);
bool setBypass(int audioDevice, int layer, bool bypass);
inline float getLevel(int layer) {
float level = ma_vumeter_node_get_level(&m_mae.filters[layer].vumeter);
return ma_volume_linear_to_db(level) - 4.0f;
};
char* getDeviceName(uint id);
private:
MAE m_mae;
ma_result startDevices();
ma_result getAllAudioDevices();
ma_result startContext();
void refreshValues(int layer);
ma_result seekToCursor(int layer, int cursor);
ma_result setNodeGraph();
ma_result createFilterBank(uint layer);
ma_result setLoopPoint(int layer, int cursor);
ma_result setRangePoint(int layer, int cursor);
};
#endif // MINIAUDIOENGINE_H

View file

@ -1,23 +1,21 @@
#include "olathread.h"
#include "libremediaserver-audio.h"
olaThread::olaThread(QObject *parent)
olaThread::olaThread(QObject *parent, int layers) :
m_counter(0)
, m_layers(layers)
{
Q_UNUSED(parent);
m_counter = 0;
this->setParent(parent);
gettimeofday(&m_last_data, NULL);
// Init the dmx buffer to 0
for (int i=0; i < LAYERS_NUMBER; i++)
for (int i=0; i < MAX_LAYERS; i++)
{
for (int j=0; j < LAYER_CHANNELS; j++)
{
m_dmx[i][j] = 0;
}
}
init(); // start the ola connection
init();
}
// --- DECONSTRUCTOR ---
olaThread::~olaThread() {
stop();
}
@ -39,11 +37,11 @@ void olaThread::init()
m_client->SetDMXCallback(ola::NewCallback(this, &olaThread::NewDmx));
m_clientWrapper->GetSelectServer()->RegisterRepeatingTimeout(4000, ola::NewCallback(this, &olaThread::CheckDataLoss));
m_client->SetCloseHandler(ola::NewSingleCallback(this, &olaThread::socketClosed));
m_dmxSettings = Settings::getInstance()->getDmxSettings();
}
void olaThread::run()
{
emit toTerminal("Start reading DMX");
m_clientWrapper->GetSelectServer()->Run();
}
@ -64,67 +62,98 @@ void olaThread::stop()
void olaThread::NewDmx(const ola::client::DMXMetadata &data,
const ola::DmxBuffer &buffer)
{
m_counter++;
gettimeofday(&m_last_data, NULL);
uint universe = data.universe;
emit layerReceived();
foreach (const dmxSetting &i, Settings::getInstance()->getDmxSettings()) { // loop for reading the channels by layer.
if(i.universe == universe && i.address > -1) { // Compare if the layer is from this universe AND if the DMX address is 0 or greater, process this layer.
foreach (const dmxSetting &i, m_dmxSettings) {
if(i.universe == data.universe && i.address > -1) {
bool volSent = false;
bool entrySent = false;
bool fileSent = false;
int aux;
for (int j = 0; j < LAYER_CHANNELS; j++){
int value = buffer.Get((i.address) + j); // Get the value for this channel.
if (m_dmx[i.layer][j] != value) { // Compare the new value with the old value.
emit dmxOutput(i.layer,j,value);
int value = buffer.Get((i.address) + j);
if (m_dmx[i.layer][j] != value) {
m_dmx[i.layer][j] = value;
switch (j) {
case DMX_FOLDER:
aux = buffer.Get(i.address + DMX_FILE);
qobject_cast<libreMediaServerAudio *>(parent())->loadMedia(i.layer, value, aux);
m_dmx[i.layer][DMX_FILE] = aux;
fileSent = true;
break;
case DMX_FILE:
if (fileSent)
break;
aux = buffer.Get(i.address + DMX_FOLDER);
qobject_cast<libreMediaServerAudio *>(parent())->loadMedia(i.layer, aux, value);
m_dmx[i.layer][DMX_FOLDER] = aux;
fileSent = true;
break;
case VOLUME_FINE:
aux = buffer.Get(i.address + VOLUME_COARSE);
value += (aux * 0x100);
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
m_dmx[i.layer][VOLUME_COARSE] = aux;
volSent = true;
break;
case VOLUME_COARSE:
if (volSent)
break;
value = (value * 0x100) + buffer.Get(i.address + VOLUME_FINE);
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
m_dmx[i.layer][VOLUME_FINE] = buffer.Get(i.address + VOLUME_FINE);
volSent = true;
break;
case PLAYBACK:
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
break;
case ENTRY_POINT_FINE:
value = (buffer.Get(i.address + ENTRY_POINT_COARSE) * 0x100) + value;
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
m_dmx[i.layer][ENTRY_POINT_COARSE] = buffer.Get(i.address + ENTRY_POINT_COARSE);
entrySent = true;
break;
case ENTRY_POINT_COARSE:
if (entrySent)
break;
value = (value * 0x100) + buffer.Get(i.address + ENTRY_POINT_FINE);
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
m_dmx[i.layer][ENTRY_POINT_FINE] = buffer.Get(i.address + ENTRY_POINT_FINE);
entrySent = true;
break;
default:
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
break;
}
}
}
}
}
m_counter++;
gettimeofday(&m_last_data, NULL);
emit universeReceived(data.universe);
}
/**
* Check for data loss each 4 seconds.
*/
bool olaThread::CheckDataLoss() {
struct timeval now, diff;
if (timerisset(&m_last_data)) {
gettimeofday(&now, NULL);
timersub(&now, &m_last_data, &diff);
if (diff.tv_sec > 4 || (diff.tv_sec == 4 && diff.tv_usec > 4000000)) {
// loss of data
qDebug()<< "olaThread| Can not read one or several universes";
emit toTerminal("olaThread: Can not read one universe");
// return false; // Retorna false para deshabilitar el callback
qInfo()<< "olaThread| Can not read one or several universes";
}
}
return true;
}
void olaThread::resendDmx()
{
// qDebug() << "Resending DMX info";
for (int i = 0; i < Settings::getInstance()->getLayersNumber(); i++) { // loop for reading the channels by layer.
for (int j = 0; j < LAYER_CHANNELS; j++){
emit dmxOutput(i, j, m_dmx[i][j]);
}
}
}
void olaThread::socketClosed()
{
qWarning("ola close the connection. Trying reopening it... ");
emit toTerminal("OLA closed the connection. Tryin reopening it... ");
qWarning("ola daemon closed connection, reopening it... ");
m_clientWrapper->GetSelectServer()->Terminate();
m_client = NULL;
m_clientWrapper = NULL;
// setup ola connection
init();
// register universes
registerUniverse();
// start thread
run();
}

View file

@ -1,11 +1,7 @@
#ifndef OLATHREAD_H
#define OLATHREAD_H
#include <string>
#include <QObject>
#include <QThread>
#include <QDebug>
#include <ola/DmxBuffer.h>
#include <ola/Logging.h>
@ -15,19 +11,26 @@
#include <ola/client/ClientTypes.h>
#include <ola/Callback.h>
#include "defines.h"
#include "dmxPersonality.h"
#include "settings.h"
#include "defines.h"
class olaThread : public QThread
{
Q_OBJECT
public:
QList<dmxSetting> m_dmxSettings;
int m_dmx[MAX_LAYERS][LAYER_CHANNELS];
ola::client::OlaClientWrapper *m_clientWrapper;
ola::client::OlaClient *m_client;
unsigned int m_counter;
struct timeval m_last_data; // Last DMX frame received
int m_layers;
olaThread(QObject *parent = 0);
olaThread(QObject *parent = 0, int layers = 0);
virtual ~olaThread();
void run ();
/** Retorna el valor de un canal
*@param int layer the layer for we want the channel
*@param int channel the channel for the value wanted
@ -36,91 +39,49 @@ public:
inline int getValue(int layer, int channel) {
return m_dmx[layer][channel];
}
/**
* @brief resendDMX emite todo el buffer DMX
*/
void resendDmx();
private:
void run ();
ola::client::OlaClientWrapper *m_clientWrapper;
ola::client::OlaClient *m_client;
unsigned int m_counter;
struct timeval m_last_data; // Last DMX frame received
int m_dmx[LAYERS_NUMBER][LAYER_CHANNELS]; // DMX Buffer. Habría que cambiarlo si queremos hacer las capas dinámicas
/**
* @brief Callback from ola. Control de errores en el registro de Universos en OLA
* typedef SingleUseCallback1<void, const Result&> ola::client::SetCallback
* @param ola::client::Result &error
* @return void
*
*
*/
inline void RegisterComplete(const ola::client::Result &error) {
if (error.Success()) {
qDebug("Register Universe success");
emit toTerminal("Register Universe success");
} else {
qWarning("Register command failed: %s", error.Error().c_str());
emit toTerminal("olaThread| Register command failed " + QString::fromStdString(error.Error()));
qCritical("Register command failed: %s", error.Error().c_str());
}
}
/**
* @brief Check if the dmx info is arriving each 4 seconds
* @return bool
*/
bool CheckDataLoss();
/**
* @brief RepeteableDMXCallBack from ola called when arrives a new dmx frame
* typedef Callback2<void, const DMXMetadata&, const DmxBuffer&> ola::client::RepeatableDMXCallback
* This is called one for second if there is not updated in the DMX frame. We need emit only the channels that
* has changed to save resources.
*
*
* This is called one for second if there is not updated in the DMX frame.
* emit only the channels that has been changed.
*/
void NewDmx(const ola::client::DMXMetadata &dmx_meta, const ola::DmxBuffer &buffer); //
void NewDmx(const ola::client::DMXMetadata &dmx_meta, const ola::DmxBuffer &buffer);
/**
* @brief Sometimes the ola server closes the connection. This is a callback to handle this event an reconect to ola
*
*
*/
void socketClosed();
/**
* @brief Open the connection with olad and start processing data.
*
*
*/
void init();
void init();
public slots:
void stop(); // Close the connection with olad.
/**
* @brief register one Universe
* void ola::client::OlaClient::RegisterUniverse(unsigned int universe,RegisterAction register_action,SetCallback * callback
* @param universe
*/
void stop();
inline void registerUniverse(int universe) {
qDebug("Registering universe %d", universe);
qInfo("Registering universe %d", universe);
m_client->RegisterUniverse(universe,
ola::client::REGISTER,
ola::NewSingleCallback
(this, &olaThread::RegisterComplete));
}
/**
* @brief Register all the universes again
*
*/
inline void registerUniverse() {
QSet<int> unis = Settings::getInstance()->getUniverses();
foreach (const int &universe, unis) {
@ -128,17 +89,9 @@ public slots:
}
}
protected slots:
signals:
// void finished(); // Signal for closing. Not used now.
void dmxOutput(int layer, int channel, int value); // Signal when a channel has changed
void toTerminal(QString message);
void universeReceived(uint universe);
void layerReceived();
void dmxOutput(int layer, int channel, int value);
void universeReceived(int uni);
};
using namespace ola;
#endif // OLATHREAD_H

View file

@ -15,12 +15,7 @@ Settings::Settings(QObject *parent) :
QObject(parent)
{
m_layersNumber = 0;
}
void Settings::setPathMedia(QString path)
{
m_pathmedia = path;
MediaLibrary::getInstance()->rescanMediaLibrary();
m_ui = false;
}
// Read the dmx settings for dmx.xml At the moment we need:
@ -28,6 +23,8 @@ void Settings::setPathMedia(QString path)
// - The number of sources/layers controlled by DMX
// - The first DMX channel of each source/layer
// - The universe to bind in OLA
// - Audio device id
// - Show the Ui or not
void Settings::readFromFile(QString file) {
QFile* xmlFile = new QFile(file);
if (!xmlFile->open(QIODevice::ReadOnly | QIODevice::Text)) {
@ -39,117 +36,56 @@ void Settings::readFromFile(QString file) {
exit(1);
}
QXmlStreamReader* xmlReader = new QXmlStreamReader(xmlFile);
int counter = 0;
//Parse the XML until we reach end of it
while(!xmlReader->atEnd() && !xmlReader->hasError() && counter < LAYERS_NUMBER) {
// Read next element
while(!xmlReader->atEnd() && !xmlReader->hasError()) {
QXmlStreamReader::TokenType token = xmlReader->readNext();
//If token is just StartDocument - go to next
if(token == QXmlStreamReader::StartDocument) {
continue;
}
//If token is StartElement - read it
if(token == QXmlStreamReader::StartElement) {
if(xmlReader->name() == "dmxSettings") {
int version = xmlReader->attributes().value("fileVersion").toLocal8Bit().toInt();
if(version == 1) {
m_layersNumber = xmlReader->attributes().value("layersNumber").toLocal8Bit().toInt();
m_pathmedia = xmlReader->attributes().value("path").toLocal8Bit();
continue;
}
if(xmlReader->name() == "lmsAudio") {
m_ui = xmlReader->attributes().value("ui").toLocal8Bit().toInt();
m_layersNumber = xmlReader->attributes().value("layersNumber").toLocal8Bit().toInt();
m_pathmedia = xmlReader->attributes().value("path").toLocal8Bit();
continue;
}
QString add = "layer";
add.append(QString("%1").arg(counter));
if((xmlReader->name() == add)) {
if(xmlReader->name() == "audioDevice") {
m_audioDeviceQty = xmlReader->attributes().value("devicesNumber").toLocal8Bit().toInt();
for (uint i = 0; i < m_audioDeviceQty; i++)
m_audioDeviceId[i] = xmlReader->attributes().value(QString("id%1").arg(i)).toLocal8Bit().toInt();
}
if(xmlReader->name() == "layer") {
dmxSetting temp;
temp.address = xmlReader->attributes().value("dmx").toLocal8Bit().toInt() - 1;
temp.universe = xmlReader->attributes().value("universe").toLocal8Bit().toInt();
temp.layer = counter;
temp.layer = xmlReader->attributes().value("id").toLocal8Bit().toInt();
temp.audioDevice = xmlReader->attributes().value("audioDevice").toLocal8Bit().toInt();
m_settings.append(temp);
if (!m_universe.contains(temp.universe)) {
m_universe.insert(temp.universe);
// emit registerUniverse(temp.universe);
}
}
counter++;
}
}
if(xmlReader->hasError()) {
QMessageBox::critical(NULL,"File xml Parse Error ", xmlReader->errorString(), QMessageBox::Ok);
qWarning("File xml Parse Error %s", xmlReader->errorString().toLatin1().constData());
return;
// ToDo: manage this, open a dialog to load a new file.
}
//close reader and flush file
xmlReader->clear();
xmlFile->close();
delete xmlReader;
delete xmlFile;
this->printSettings();
}
void Settings::printSettings() {
qInfo() << "Settings readed:\nShow Ui:" << m_ui << "Layers:" << m_layersNumber << "Path:" << m_pathmedia <<"Audio Device qty:" << m_audioDeviceQty;
for (uint i = 0; i < m_audioDeviceQty; i++)
qInfo() << "Audio device internal id:" << i << "system id:" << m_audioDeviceId[i];
for (int i = 0; i < m_layersNumber; i++)
qInfo() << "Layer:" << m_settings[i].layer << "Address:" << m_settings[i].address << "Universe:" << m_settings[i].universe;
}
/** Read the default file
*
*/
void Settings::readFile() {
readFromFile(DEFAULT_FILE);
}
void Settings::changeLayerSetup(int layer, int universe, int address)
{
dmxSetting temp;
temp.address = address;
temp.universe = universe;
temp.layer = layer;
m_settings.replace(layer, temp);
if (!m_universe.contains(temp.universe)) {
m_universe.insert(temp.universe);
// emit registerUniverse(temp.universe);
}
}
void Settings::addLayer()
{
dmxSetting temp;
temp.address = -1;
temp.universe = -1;
temp.layer = -1;
m_settings.append(temp);
}
void Settings::removeLayer(int layer)
{
m_settings.removeAt(layer);
}
/*
void Settings::writeFile(QString filename)
{
QFile* xmlFile = new QFile(filename);
if (!xmlFile->open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(NULL,"Load XML File Problem",
QString("Couldn't open %1 to load settings for olaInterface").arg(file),
QMessageBox::Ok);
return;
}
QXmlStreamWriter* xmlWriter = new QXmlStreamWriter(xmlFile);
QXmlStreamWriter stream(&output);
stream.setAutoFormatting(true);
stream.writeStartDocument();
stream.writeStartElement("dmxSettings");
stream.writeAttribute("href", "http://qt.nokia.com/");
stream.writeTextElement("title", "Qt Home");
stream.writeEndElement();
stream.writeEndDocument();
}
void Settings::writeFile()
{
writeFile(DEFAULT_FILE);
}
*/

View file

@ -1,160 +1,43 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <QObject>
#include <QXmlStreamReader>
#include <QFile>
#include <QMessageBox>
#include <QSet>
#include <QDebug>
#include "medialibrary.h"
#include "audiowidget.h"
#include "defines.h"
/**
* This class stores the settings on lms.
* Is a singleton with set and get methods to manage the settings
* Also writes and reads the settings files.
*/
class Settings : public QObject
{
Q_OBJECT
public:
static Settings *getInstance(); //Singleton
/**
* @brief getUniverses
* @return
*/
inline QSet<int> getUniverses() { return m_universe; }
/**
* @brief getPathMedia
* @return
*/
inline QString getPathMedia() { return m_pathmedia; }
/**
* @brief setPathMedia
* @param path
*/
void setPathMedia(QString path);
/**
* @brief getDmxSettings
* @return
*/
inline QList<dmxSetting> getDmxSettings() { return m_settings; }
/**
* @brief Get the number of layer currently used
* @return
*/
inline int getLayersNumber() { return m_layersNumber; }
/**
* @brief Get the number of universes registered
* @return
*/
inline int getUniverseNumber() { return m_universe.size(); }
/**
* @brief Read the default xml configuration file at startup
*/
void readFile();
/**
* @brief changeLayerSetup
* @param layer
* @param universe
* @param address
*/
void changeLayerSetup(int layer, int universe, int address);
/**
* @brief removeLayer
* @param layer
*/
void removeLayer(int layer);
/**
* @brief addLayer
*/
void addLayer();
/**
* @brief olaThread::setLayersNumber
* @param layersNumber
*
*/
inline void setLayersNumber(int layersNumber)
{
if (layersNumber <= LAYERS_NUMBER)
m_layersNumber = layersNumber;
else
m_layersNumber = LAYERS_NUMBER;
}
private:
static Settings *_instance;
// The list where we store the settings by layer
QList<dmxSetting> m_settings;
// The path to media library
QString m_pathmedia;
/** Constructor
*
*/
explicit Settings(QObject *parent = 0);
QSet<int> m_universe; // Registered universes.
int m_layersNumber; // Number of layers in wich divide the dmx frame. Each layer, one source.
/**
* @brief readFromFile
* @param file
*/
Settings(QObject *parent = 0);
static Settings *getInstance();
inline QSet<int> getUniverses() { return m_universe; }
inline QString getPathMedia() { return m_pathmedia; }
inline QList<dmxSetting> getDmxSettings() { return m_settings; }
inline int getLayersNumber() { return m_layersNumber; }
inline uint *getAudioDeviceId() { return m_audioDeviceId; }
inline uint getAudioDeviceQty() { return m_audioDeviceQty; }
inline bool getShowUi() { return m_ui; }
void readFile();
void readFromFile(QString file);
void printSettings();
/**
* @brief writeFile
* @param filename
*/
// void writeFile(QString filename); // Not implemented yet
/**
* @brief writeFile
* overload
*/
// void writeFile(); // Not implemented yet
signals:
/**
* @brief pathChanged
* @param path
*/
void pathChanged(QString path);
/**
* @brief layersNumber
* @param number
*/
void layersNumber(int number);
/**
* @brief DMXConf
* @param universe
*/
void registerUniverse(int universe);
public slots:
private:
static Settings *_instance;
QList<dmxSetting> m_settings;
QString m_pathmedia;
uint m_audioDeviceId[MAX_AUDIODEVICES];
uint m_audioDeviceQty;
QSet<int> m_universe;
int m_layersNumber;
bool m_ui;
};
#endif // SETTINGS_H

View file

@ -2,18 +2,20 @@
#include "ui_settingsdialog.h"
SettingsDialog::SettingsDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog)
QDialog(parent)
, ui(new Ui::SettingsDialog)
, m_deviceDialog(NULL)
{
ui->setupUi(this);
this->setWindowTitle("Settings");
ui->layersNumber->setValue(Settings::getInstance()->getLayersNumber());
QString path("Path to root folder of the media tree: /n");
path.append(Settings::getInstance()->getPathMedia());
ui->mediaPath->setText(path);
connect(ui->mediaPatchButton, SIGNAL(clicked()),
//QString path("Path to root folder of the media tree: /n");
//path.append(Settings::getInstance()->getPathMedia());
//ui->mediaPath->setText(path);
connect(ui->mediaPathButton, SIGNAL(clicked()),
this, SLOT(changeMediaPath()));
connect(ui->audioDeviceButton , SIGNAL(clicked()),
this, SLOT(changeAudioDevice()));
connect(ui->layersNumber, SIGNAL(valueChanged(int)),
this, SLOT(layersChanged(int)));
int layer = 0;
@ -42,7 +44,45 @@ void SettingsDialog::changeMediaPath()
QString file = fileNames.at(0);
Settings::getInstance()->setPathMedia(file);
QString desc = tr("Media Path Changed to: %1").arg(file);
qDebug("%s", desc.toLatin1().constData());
qInfo("%s", desc.toLatin1().constData());
}
void SettingsDialog::changeAudioDevice()
{
if (!m_deviceDialog)
{
m_deviceDialog = new QDialog( this );
}
/* QLabel *msgLabel = new QLabel;
AudioWidget *aw = AudioWidget::getInstance();
QString *msg = new QString;
for (uint iAvailableDevice = 0; iAvailableDevice < aw->playbackDeviceCount; iAvailableDevice += 1) {
msg->append(tr("%1 : %2\n").arg(iAvailableDevice).arg(aw->pPlaybackDeviceInfos[iAvailableDevice].name));
}
msgLabel->setText(msg->toLatin1());
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(msgLabel);
QSpinBox *res = new QSpinBox;
res->setRange(0, aw->playbackDeviceCount);
res->setValue(AudioWidget::getInstance()->iChosenDevice);
layout->addWidget(res);
QPushButton *closeButton = new QPushButton(tr("Close"));
connect(closeButton, SIGNAL(clicked()), this, SLOT(closeAudioDeviceDialog()));
closeButton->setDefault(true);
layout->addWidget(closeButton);
m_deviceDialog->setLayout(layout);
m_deviceDialog->setModal(true);
m_deviceDialog->show();*/
}
void SettingsDialog::closeAudioDeviceDialog()
{
QLayoutItem * const item = m_deviceDialog->layout()->itemAt(1);
int value = dynamic_cast<QSpinBox *>(item->widget())->value();
Settings::getInstance()->setAudioDeviceId(value);
qInfo("device selected: %i", value);
m_deviceDialog->close();
emit Settings::getInstance()->audioDeviceChanged(value);
}
void SettingsDialog::layersChanged(int val)

View file

@ -4,8 +4,8 @@
#include <QDialog>
#include <QVBoxLayout>
#include <QFileDialog>
#include "settings.h"
#include "layersettingswidget.h"
namespace Ui {
class SettingsDialog;
@ -21,11 +21,16 @@ public:
private slots:
void changeMediaPath();
void changeAudioDevice();
void closeAudioDeviceDialog();
private:
Ui::SettingsDialog *ui;
QDialog *m_deviceDialog;
private slots:
void layersChanged(int val);
};
#endif // SETTINGSDIALOG_H

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>429</height>
<height>563</height>
</rect>
</property>
<property name="windowTitle">
@ -16,9 +16,9 @@
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>290</x>
<y>390</y>
<width>101</width>
<x>10</x>
<y>530</y>
<width>381</width>
<height>32</height>
</rect>
</property>
@ -26,14 +26,14 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QSpinBox" name="layersNumber">
<property name="geometry">
<rect>
<x>10</x>
<y>40</y>
<y>50</y>
<width>52</width>
<height>31</height>
</rect>
@ -63,8 +63,8 @@
<widget class="QLabel" name="layersNumber_label">
<property name="geometry">
<rect>
<x>60</x>
<y>40</y>
<x>70</x>
<y>50</y>
<width>111</width>
<height>21</height>
</rect>
@ -73,25 +73,12 @@
<string>Layers Number</string>
</property>
</widget>
<widget class="QLabel" name="mediaPath">
<widget class="QPushButton" name="mediaPathButton">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>371</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="mediaPatchButton">
<property name="geometry">
<rect>
<x>200</x>
<y>40</y>
<width>191</width>
<width>171</width>
<height>31</height>
</rect>
</property>
@ -108,14 +95,33 @@
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>9</x>
<y>99</y>
<width>381</width>
<height>281</height>
<x>0</x>
<y>80</y>
<width>401</width>
<height>451</height>
</rect>
</property>
<layout class="QVBoxLayout" name="layersLayout"/>
</widget>
<widget class="QPushButton" name="audioDeviceButton">
<property name="geometry">
<rect>
<x>210</x>
<y>10</y>
<width>181</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Change Audio Device</string>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</widget>
<resources/>
<connections>

113
src/slidergroup.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "slidergroup.h"
#include <cmath>
#include <QWidget>
#include <QVBoxLayout>
SliderGroup::SliderGroup(QString name,
int min,
int max,
int decimals,
QWidget *parent)
: QWidget(parent)
{
QBoxLayout *layout;
if (decimals) {
layout = new QVBoxLayout;
slider.setOrientation(Qt::Vertical);
}
else {
layout = new QHBoxLayout;
slider.setOrientation(Qt::Horizontal);
slider.setMaximumHeight(15);
valueBox.setMaximumHeight(15);
}
layout->setAlignment(Qt::AlignHCenter);
layout->setContentsMargins(0, 0, 0, 0);
slider.setFocusPolicy(Qt::StrongFocus);
slider.setTickPosition(QSlider::TicksBothSides);
slider.setTickInterval((max - min) / 11);
slider.setMinimumHeight(0);
slider.setSingleStep(1);
slider.setRange(min, max);
slider.setValue(0);
slider.setMinimumWidth(50);
slider.setToolTip(name);
slider.setStyleSheet("QSlider {"
"border: 1px solid #aa8895;"
"background: #20182d;"
"margin: 0px;}"
"QSlider::groove:vertical {"
"border: 1px solid #999999;"
"width: 25px;"
"margin: -4px;}"
"QSlider::handle:vertical {"
"background: white;"
"border: 1px solid #5c5c5c;"
"width: 29px;"
"height: 7px;"
"margin: -2px;"
"border-radius: 2px;}"
"Qslider::tickmarks:vertical {background: white;"
"color: white;}"
"QSlider::add-page:vertical {background: blue;}"
"QSlider::sub-page:vertical {background: #20182d;}");
slider.setContentsMargins(0, 0, 0, 0);
valueBox.setFocusPolicy(Qt::NoFocus);
valueBox.setButtonSymbols(QAbstractSpinBox::NoButtons);
valueBox.setMinimumWidth(50);
if (decimals) {
valueBox.setRange(-84.0f, 0.0f);
valueBox.setSpecialValueText("-inf");
} else
valueBox.setRange(min, max);
valueBox.setValue(0);
valueBox.setDecimals(decimals);
valueBox.setObjectName(name);
valueBox.setToolTip(name);
valueBox.setAlignment(Qt::AlignHCenter);
valueBox.setContentsMargins(0, 0, 0, 0);
connect(&slider, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int)));
connect(&valueBox, SIGNAL(click()), this, SLOT(enableSlider()));
layout->addWidget(&slider);
layout->addWidget(&valueBox);
this->setStyleSheet("border: 1px solid #aa8895;"
"background-color: black;"
"margin: 1px;"
);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
this->setLayout(layout);
}
void SliderGroup::sliderValueChanged(int value)
{
valueBox.blockSignals(true);
if (valueBox.decimals()) {
float db = ((float)value / 771.0f) - 85.0f;
if (db <= -84.5f) {
valueBox.setSpecialValueText("-inf");
} else
valueBox.setValue(db);
} else {
valueBox.setValue(value);
}
valueBox.blockSignals(false);
emit valueChanged(value);
};
void SliderGroup::setValue(float value)
{
float db;
slider.blockSignals(true);
valueBox.blockSignals(true);
if (int(value) != slider.value())
slider.setValue(value);
if (valueBox.decimals()) {
db = (float)(value / 771.0f) - 85.0f;
valueBox.setValue(db);
} else
valueBox.setValue(value);
slider.blockSignals(false);
valueBox.blockSignals(false);
}

37
src/slidergroup.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef SLIDERGROUP_H
#define SLIDERGROUP_H
#include <QObject>
#include <QWidget>
#include <QDebug>
#include "clickabledoublespinbox.h"
#include "clickableslider.h"
class SliderGroup : public QWidget
{
Q_OBJECT
public:
SliderGroup(QString name,
int min,
int max,
int decimals,
QWidget *parent = nullptr);
signals:
void valueChanged(int value);
public slots:
void setValue(float value);
void sliderValueChanged(int value);
private:
ClickableSlider slider;
ClickableDoubleSpinBox valueBox;
private slots:
void enableSlider() { slider.setEnabled(true); }
};
#endif