diff --git a/install.sh b/install.sh index 99909fdc8..007ce6200 100644 --- a/install.sh +++ b/install.sh @@ -4,6 +4,21 @@ alias=r1 . /srv/http/bash/settings/addons.sh +# 20240105 +if [[ -e /usr/bin/camilladsp ]]; then + rm -f $dirsystem/camilla.conf + mkdir -p $dircamilladsp/raw + if [[ $( camilladsp -V ) != 'CamillaDSP 2.0.0' ]]; then + systemctl stop camilladsp + pacman -Sy --needed --noconfirm camilladsp + readarray -t files <<< $( grep -rl enable_resampling $dircamilladsp ) + for f in "${files[@]}"; do + sed -i '/enable_resampling\|resampler_type/ d' "$f" + done + [[ -e $dirsystem/camilladsp ]] && systemctl start camilladsp + fi +fi + # 20231216 if [[ ! -e /boot/kernel.img && $( pacman -Q python-websockets ) != 'python-websockets 12.0-1' ]]; then pacman -Sy --needed --noconfirm python-websockets @@ -62,32 +77,6 @@ if [[ -e /boot/kernel8.img ]]; then pacman -Q wiringpi | grep 181 && pacman -Sy --noconfirm wiringpi fi -# 29231101 -[[ ! -e /usr/bin/vcgencmd ]] && cp /opt/vc/bin/{dtoverlay,vcgencmd} /usr/bin - -# 20231022 -if [[ -e /boot/kernel.img && ! -e /lib/python3.10/site-packages/websocket ]]; then - echo ' -[alarm] -SigLevel = PackageRequired -Include = /etc/pacman.d/mirrorlist - -[community] -SigLevel = PackageRequired -Include = /etc/pacman.d/mirrorlist' >> /etc/pacman.conf - pacman -Sy --noconfirm python-websocket-client - systemctl restart websocket -fi - -# 20231020 -file=$dirsystem/localbrowser.conf -if [[ -e $file ]] && ! grep -q runxinitrcd $file; then - sed -i -e '/hdmi/ d -' -e '$ a\ -runxinitrcd= -' $file -fi - #------------------------------------------------------------------------------- installstart "$1" diff --git a/srv/http/assets/css/camilla.css b/srv/http/assets/css/camilla.css index 40e221036..9e568166b 100644 --- a/srv/http/assets/css/camilla.css +++ b/srv/http/assets/css/camilla.css @@ -1,5 +1,139 @@ -#divtabs { +#vu { + position: relative; + width: 230px; + margin-bottom: 15px; + font-family: Inconsolata; +} +#in, +#out { + position: relative; + line-height: 18px; +} +#out { + margin-top: 20px; +} +#in::before, +#out::before { + position: absolute; + display: block; + left: -60px; + top: calc(50% - 7px); + width: 45px; + line-height: 16px; + text-align: center; + background: var( --cg ); + border-radius: 3px; +} +#in::before { + color: #000; + background: var( --cgl ); +} +#in::before { + content: 'In'; +} +#out::before { + content: 'Out'; +} +.vubar { + position: absolute; + width: 100%; + height: 3px; + background: var( --cga ); + margin-bottom: 10px; +} +.vubar.peak { + width: 0; + background: var( --cm ); + transition-duration: 1s; +} +.vubar.peak.red { + background: #f00 !important; +} +.vubar.rms { position: relative; + width: 0; + background: var( --cm ); + transition-duration: 0.1s; +} +#vugrid { + position: absolute; + left: 7px; + width: 100%; + height: 100%; +} +#vugrid a { + display: inline-block; + margin-right: 32px; + width: 32px; + height: 100%; + border: 1px solid var( --cga ); + border-top: none; + border-bottom: none; +} +#vugrid .g3 { + border-left: 1px solid var( --cgl ); + border-right: none; +} +#vulabel { + position: absolute; + top: 16px; + width: 100%; + height: 20px; + line-height: 20px; + font-size: 14px; + color: var( --cg60 ); + background: #000; + z-index: 0; +} +#vulabel a { + display: inline-block; + width: 33px; +} +#vulabel a:nth-child(even) { + color: var( --cg ); +} +#vulabel .l0 { + width: 24px; +} +#vulabel .l6 { + margin-left: 7px; +} +#divvolume { + margin-bottom: 10px; +} +#volume { + float: left; +} +#divvolume .i-minus { + color: var( --cg60 ); +} +#divvolume .i-plus, +#divvolume .i-volume { + width: 40px; + line-height: 40px; + vertical-align: 0; + font-size: 24px; + text-align: center; + color: var( --cg60 ); +} +.db, +#divvolume .level { + display: inline-block; + width: 45px; + line-height: 16px; + vertical-align: 4px; + text-align: center; + color: var( --cw ); + background-color: var( --cga ); +} +.db { + float: left; +} +#divconfiguration { + margin-top: 10px; +} +#tabconfig { + display: none !important; } .tab { margin-top: -16px; @@ -18,9 +152,6 @@ height: auto; min-height: 50px; } -.entries.sub li { - cursor: default; -} .tab li i { min-width: 40px; } @@ -52,6 +183,9 @@ .section hr { border-bottom-color: var( --cga ); } +.col-l { + padding-right: 14px; +} .col-r hr { margin-left: 0; max-width: 230px; @@ -59,6 +193,9 @@ .tablemapping { border-collapse: collapse !important; } +.tablemapping input[ type=checkbox ] { + margin: 0; +} .trhead { border: 1px solid var( --cgl ); border-left: none; @@ -113,9 +250,6 @@ tr .i-remove { .tab li i { margin: 0 !important; } -#filters li { - cursor: default !important; -} .tab li.active { background: var( --cgd ); } @@ -126,6 +260,7 @@ tr .i-remove { padding-bottom: 20px; } .liinput { + position: relative; display: flex !important; align-items: center; padding-top: 0 !important; @@ -135,11 +270,11 @@ tr .i-remove { background: var( --cga ) !important; border: none !important; } -.liinput .filter { - width: 130px; +.liinput.main i.disabled { + color: var( --cgl ) !important; } -.liinput .filter div { - display: block; +.liinput .name { + width: 142px; } .liinput.main div:first-child, .liinput .select2-container { @@ -152,48 +287,23 @@ tr .i-remove { width: 100% !important; height: 40px !important; } -input[ type=range ] { - --track: linear-gradient( 90deg, transparent 10px, var( --cga ) 10px, var( --cga ) calc( 100% - 10px ), transparent 10px ); - --trackborder : 1px solid var( --cga ); +.container input[ type=range ] { + --track: linear-gradient( 90deg, transparent 10px, var( --cga ) 10px, var( --cga ) calc( 100% - 10px ), transparent 10px ) !important; + --trackborder : 1px solid var( --cga ) !important; max-width: 230px; - margin: 2px 5px 0 10px; } -#volume { - margin: 0; -} -.divgain { - display: inline-block; - width: 120px !important; -} -.divgain i { - width: 40px; - font-size: 24px; - text-align: center; - color: var( --cg60 ); - -webkit-user-select: none; - user-select: none; -} -#divvolume .col-l { - padding-top: 6px; -} -#divvolume .i-volume { - width: 40px; - margin-right: 8px; - line-height: 40px; - text-align: center; - color: var( --cg60 ) +i.disabled { + pointer-events: none; + color: var( --cg ) !important; } -li .i-mute { - margin-left: 20px !important; +.liinput .i-volume::before { + margin-left: 7px !important; } -#divvolume .divgain { - position: absolute; - padding-left: 5px; +#divvolume c { + margin: 0 -4px; } -i.disabled, -.divgain.disabled i { - pointer-events: none; - color: var( --cg ) !important; +.i-volume.mute { + color: var( --cml ) !important; } .liinput input[ type=checkbox ] { margin: 0 16px; @@ -219,125 +329,6 @@ i.disabled, .overplot .lines path { stroke-opacity: 0.75 !important; } -#vu { - position: relative; - width: 230px; - margin-bottom: 15px; - font-family: Inconsolata; -} -#in, -#out { - position: relative; - line-height: 18px; -} -#out { - margin-top: 20px; -} -#in::before, -#out::before { - position: absolute; - display: block; - left: -60px; - top: calc(50% - 7px); - width: 45px; - line-height: 16px; - text-align: center; - background: var( --cg ); - border-radius: 3px; -} -#in::before { - color: #000; - background: var( --cgl ); -} -.db, -#volume-text { - display: inline-block; - width: 45px; - line-height: 16px; - text-align: center; - color: var( --cw ); - background-color: var( --cga ); -} -#mixers .db { - margin: 0 0 0 10px; -} -#in::before { - content: 'In'; -} -#out::before { - content: 'Out'; -} -.vubar { - position: absolute; - width: 100%; - height: 3px; - background: var( --cga ); - margin-bottom: 10px; -} -.vubar.peak { - width: 3px; - transition-duration: 1s; -} -.vubar.peak.red { - background: #f00 !important; -} -.vubar.rms { - position: relative; - width: 0; - background: var( --cm ); - transition-duration: 0.1s; -} -#vugrid { - position: absolute; - width: 100%; - height: 100%; - padding-left: calc( 100% * 0.0625 ); -} -#vugrid a { - display: inline-block; - width: calc( 100% * 0.125 ); - height: 100%; - margin-right: calc( 100% * 0.125 ); - border: 1px solid var( --cga ); - border-top: none; - border-bottom: none; -} -#vugrid .g3 { - border-left: 1px solid var( --cgl ); -} -#vulabel { - position: absolute; - top: 16px; - width: 100%; - height: 20px; - line-height: 20px; - font-size: 14px; - color: var( --cg60 ); - background: #000; - z-index: 0; -} -#vulabel a { - display: inline-block; - width: calc( 100% * 0.117 ); -} -#vulabel a:nth-child(even) { - color: var( --cg ); -} -#vulabel .l4 { - padding-left: 1px; -} -#vulabel .l5 { - padding-left: 4px; -} -#vulabel .l6 { - padding-left: 12px; -} -#vulabel .l7 { - padding-left: 11px; -} -#divdevices { - margin-top: 10px; -} .flowchart { display: block; margin: 0 auto; @@ -353,7 +344,7 @@ i.disabled, .sortable-ghost .li2 { color: var( --cg ) !important; } -.clipped::after, +.value .clipped::after, .sortable-ghost::before { position: absolute; content: "\f601"; @@ -364,18 +355,14 @@ i.disabled, text-align: center; color: var( --cw ); } -.clipped { +.value .clipped { color: #f80; } -.clipped::after { +.value .clipped::after { content: "\f515"; - line-height: 22px; + line-height: 24px; color: var( --cg60 ); } -#divconfiguration { - margin-top: 20px; - height: 50px; -} .helpmenu { border-bottom: 1px solid var( --cga ); } @@ -388,10 +375,9 @@ i.disabled, } .slider .track { position: absolute; - width: calc( 100% - 20px ); + width: 100%; height: 4px; top: 18px; - left: 10px; border: 1px solid var( --cga ); background: linear-gradient( 90deg, transparent 10px, var( --cga ) 10px, var( --cga ) calc( 100% - 10px ), transparent 10px ); } @@ -412,6 +398,22 @@ i.disabled, background-color: var( --cm ); box-shadow: var( --shadow-btn ); } +.slider.disabled .thumb { + background-color: var( --cmd ); +} +#eq { + height: 385px !important; +} +#eq .vertical::before { + right: 150px !important; +} +#eq .bottom { + bottom: 50px !important; +} +#codeoutput { + margin-top: 15px; +} + @media (max-width: 700px) { #mixers .i-inverted { display: none } } @@ -426,10 +428,22 @@ i.disabled, } } @media (max-width: 570px) { - input[ type=range ] { width: 100% } - .divgain { display: none !important } + .container input[ type=range ] { width: 100% } } @media (max-width: 500px) { .helphead { display: none } } +.db, +#divvolume { + cursor: pointer; +} +.entries.sub li, +#filters li { + cursor: default !important; +} +.i-minus, +.i-plus { + -webkit-user-select: none; + user-select: none; +} \ No newline at end of file diff --git a/srv/http/assets/css/colors.css b/srv/http/assets/css/colors.css index 3654c9300..1c5f779cd 100644 --- a/srv/http/assets/css/colors.css +++ b/srv/http/assets/css/colors.css @@ -44,3 +44,5 @@ wh, .wh { color : var( --cw ) !important } .bgm { background : var( --cm ) !important } .bgr { background : var( --cgd ) !important } .bgr60 { background-color : var( --cg60 ) !important } + +i.bl { color : var( --cml ) !important } diff --git a/srv/http/assets/css/common.css b/srv/http/assets/css/common.css index e11b84167..e968890b0 100644 --- a/srv/http/assets/css/common.css +++ b/srv/http/assets/css/common.css @@ -10,7 +10,7 @@ @font-face { font-family : rern; - src : url( '/assets/fonts/rern.woff2?v=1702394239' ); + src : url( '/assets/fonts/rern.woff2?v=1704028430' ); } @font-face { font-family : Lato; @@ -60,6 +60,7 @@ i { .i-chevron-down::before { content: '\F605' } .i-chevron-up::before { content: '\F604' } .i-close::before { content: '\F56C' } +.i-code::before { content: '\F59E' } .i-composer::before { content: '\F558' } .i-conductor::before { content: '\F559' } .i-config::before { content: '\F53E' } @@ -121,6 +122,7 @@ i { .i-librandom::before { content: '\F569' } .i-library::before { content: '\F506' } .i-libupdate::before { content: '\F506'; animation: blinkopaque 1.5s linear infinite; } +.i-linear::before { content: '\F52F' } .i-link::before { content: '\F56B' } .i-localbrowser::before { content: '\F584' } .i-lock::before { content: '\F574' } @@ -157,6 +159,7 @@ i { .i-power::before { content: '\F520' } .i-powerbutton::before { content: '\F520' } .i-previous::before { content: '\F501' } +.i-processors::before { content: '\F576' } .i-radiofrance::before { content: '\F586' } .i-radioparadise::before { content: '\F593'; position: absolute; color: var( --cgl ); } .i-radioparadise::after { content: '\F594'; position: relative; } @@ -309,11 +312,6 @@ body { #data .data { margin-top: 50px; } -#data g, -#data gr, -#data .gr { - color: var( --cw ) !important; -} #debug { position: fixed; @@ -429,21 +427,17 @@ codered { font-size: 13px; } -.range, -#infoRange { +input[ type=range ] { --track: linear-gradient( 90deg, transparent 10px, #000 10px, #000 calc( 100% - 10px ), transparent 10px ) !important; --trackborder : 1px solid var( --cgd ) !important; -} -input[type=range] { height: 40px; width: 300px; margin: 0; max-width: 70%; background: transparent; -webkit-appearance: none; - cursor: pointer; } -input[type=range]::-webkit-slider-thumb { +input[ type=range ]::-webkit-slider-thumb { height: 40px; width: 40px; margin-top: -20px; @@ -454,7 +448,7 @@ input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; box-shadow: var( --shadow-btn ); } -input[type=range]::-moz-range-thumb { /* cannot be combined */ +input[ type=range ]::-moz-range-thumb { /* cannot be combined */ height: 40px; width: 40px; border: none; @@ -463,42 +457,41 @@ input[type=range]::-moz-range-thumb { /* cannot be combined */ background-color: var( --cm ); box-shadow: var( --shadow-btn ); } -input[type=range]:disabled::-webkit-slider-thumb { +input[ type=range ]:disabled::-webkit-slider-thumb { background-color: var( --cmd ); } -input[type=range]:disabled::-moz-range-thumb { +input[ type=range ]:disabled::-moz-range-thumb { background-color: var( --cmd ); } -input[type=range]::-webkit-slider-runnable-track { +input[ type=range ]::-webkit-slider-runnable-track { width: 100%; height: 4px; background: var( --track ); border: var( --trackborder ); } -input[type=range]:focus::-webkit-slider-runnable-track { +input[ type=range ]:focus::-webkit-slider-runnable-track { background: var( --track ); } -input[type=range]::-moz-range-track { +input[ type=range ]::-moz-range-track { width: 100%; height: 2px; background: var( --track ); border: var( --trackborder ); } -#infoRange { +.inforange { text-align: center; } -#infoRange .value { +.inforange .value { height: 30px; font-size: 18px; font-weight: 300; } -#infoRange i { +.inforange i { position: relative; width: 40px !important; - line-height: 40px; - vertical-align: 14px !important; + line-height: 39px; + vertical-align: top !important; color: var( --cg60 ) !important; - cursor: pointer; } /* info */ @@ -525,7 +518,6 @@ input[ type=checkbox ] { color: var( --cw ) !important; background-color: var( --cg ); box-shadow: var( --shadow-btn ); - cursor: pointer; -webkit-user-select: none; user-select: none; } @@ -542,6 +534,9 @@ input[ type=checkbox ] { color: var( --cg60 ) !important; pointer-events: none; } +.noclick { + pointer-events: none; +} .infobtn.disabled { background-color: var( --cgd ) !important; box-shadow: none; @@ -561,18 +556,19 @@ input[ type=checkbox ].disabled { } .btnbottom span { color: var( --cg60 ); - cursor: pointer; } .btnbottom i, -.btnicon { +.updn { width: 40px; font-size: 22px; vertical-align: -3px; text-align: center; color: var( --cw ); - cursor: pointer; } - +.updn { + width: 35px !important; + line-height: 40px; +} hr { border: none; @@ -626,7 +622,7 @@ hr { #infoOverlay .button-coverart::before { top: 1px; } -#infoContent .bklabel { +#infoList .bklabel { height: auto; line-height: 30px; } @@ -681,7 +677,6 @@ hr { padding: 0; font-size: 24px; color: var( --cm ); - cursor: pointer; } #infoTitle { display: inline-block; @@ -717,35 +712,43 @@ hr { width: 24px; color: var( --cg60 ); } -#infoContent { +#infoList { padding: 20px 10px 10px 10px; font-size: 16px; box-shadow: -2px -2px 2px rgba( 0, 0, 0, 0.5 ); } -#infoContent table { +#infoList table { margin: 0 auto; + border-spacing: 5px 1px; } -#infoContent tr { +#infoList tr { height: 42px; } -#infoContent td { +#infoList td { padding: 0; text-align: left; white-space: nowrap; } -#infoContent label i { +#infoList td:first-child { /* label */ + padding-right: 5px; + text-align: right; +} +#infoList td:last-child { /* chekbox only which has no label */ + text-align: left; +} +#infoList label i { text-align: center; width: 22px; font-size: 20px; margin-right: 5px; } -#infoContent i { +#infoList i { width: 24px; font-size: 20px; text-align: center; color: var( --cw ); } -#infoContent pre { +#infoList pre { margin: 0; padding: 0 10px; text-align: left; @@ -753,16 +756,16 @@ hr { color: var( --cg60 ); background: none; } -#infoContent .menu { +#infoList .menu { position: relative; left: 0; margin: 10px auto; text-align: left; } -#infoContent .menu a { +#infoList .menu a { height: 41px; } -#infoContent .menu i { +#infoList .menu i { width: 40px; } .infomessage { @@ -779,18 +782,20 @@ hr { .infoheader, .infomessage, .infofooter { - width: fit-content; - max-width: 100%; + width: 100%; max-height: 800px; - margin: 0 auto 5px; + margin: 0 auto; + padding: 10px 5px; white-space: pre-wrap; word-wrap: break-word; overflow: hidden; color: var( --cg60 ); } -.infoheader, -.infofooter { - padding: 0 5px; +.infomessage { + padding-top: 5px; +} +.infoprompt { + padding: 15px 5px; } .infomessage i { width: 25px; @@ -825,7 +830,7 @@ hr { font-size: 18px; margin: 0; } -#infoContent textarea { +#infoList textarea { width: 100%; height: 100px; margin: 0 !important; @@ -876,7 +881,7 @@ input[ type=number ] { margin-top: 0 !important; outline: none; } -#infoContent .i-eye { +#infoList .i-eye { width: 30px; margin-right: -25px; text-align: center; @@ -915,18 +920,28 @@ input[ type=radio ]:disabled { background-color: var( --cgd ); box-shadow: var( --shadow-select ); } -#infoContent button { +#infoList button { height: 40px; margin-left: 5px; padding-right: 25px; font-size: 16px; } +#infoList .imgicon { + width: 30px !important; + margin-right: 5px !important; + vertical-align: -10px; + border: none; +} +#infoList .select2-container { + max-width : none; + margin-bottom : 0; +} #infoFilename { max-width: 95%; - margin-bottom: 10px; + height: 37px; } -#infoButtons { - padding-top: 10px; +#infoButton { + margin-top: 5px; } #infoOverlay code { display: inline-block; @@ -978,10 +993,10 @@ input[ type=radio ]:disabled { } @media ( max-height: 420px ) { - #infoContent { padding: 10px 5px } + #infoList { padding: 10px 5px } } @media ( max-width: 420px ) { - #infoContent { padding: 20px 5px 10px 5px } + #infoList { padding: 20px 5px 10px 5px } .infoheader, .infofooter { padding: 0 5px } } @media ( max-width: 330px ) { @@ -998,13 +1013,22 @@ input:focus { @media ( hover:hover ) { #debug { display: block } /* :hover *********************************************************/ - #infoTab, - #infoButton gr, label, input[ type=checkbox ], input[ type=radio ], + input[ type=range ], + .btnbottom i, + .btnbottom span, + .divgain, .i-eye, - .pointer { + .i-volume, + .infobtn, + .inforange i, + .pointer, + .updn, + #infoTab, + #infoButton gr, + #infoX { cursor: pointer; } #infoX:hover, @@ -1020,16 +1044,16 @@ input:focus { input[ type=number ]:focus-visible, input[ type=tel ]:focus-visible, input[ type=password ]:focus-visible, - #infoContent textarea:focus-visible { + #infoList textarea:focus-visible { outline: 1px solid var( --cgl ) !important; } input[ type=radio ]:focus-visible, input[ type=checkbox ]:focus-visible, - #infoRange input:focus-visible::-webkit-slider-thumb { + input[ type=range ]:focus-visible::-webkit-slider-thumb { outline: 2px solid var( --cgl ); outline-offset: 1px; } - #infoRange input:focus-visible::-moz-range-thumb { + input[ type=range ]:focus-visible::-moz-range-thumb { outline: 2px solid var( --cgl ); outline-offset: 1px; } diff --git a/srv/http/assets/css/equalizer.css b/srv/http/assets/css/equalizer.css index c519c8d8f..f445b3ba6 100644 --- a/srv/http/assets/css/equalizer.css +++ b/srv/http/assets/css/equalizer.css @@ -76,7 +76,6 @@ bottom: 15px; left: 50%; transform: translateX( -50% ); - font-size: 0; /* force no space between inline-block */ } #eq i { font-size: 24px; diff --git a/srv/http/assets/css/main.css b/srv/http/assets/css/main.css index 1b5b36735..024d52597 100644 --- a/srv/http/assets/css/main.css +++ b/srv/http/assets/css/main.css @@ -36,13 +36,6 @@ #time .rs-inner-container::after { z-index: 1 } @media ( orientation: portrait ) { @media ( max-width: 480px ) { #bioimg { z-index: -5 } } } -#infoContent .imgicon { - width: 30px !important; - margin-right: 5px !important; - vertical-align: -10px; - border: none; -} - img { -webkit-touch-callout: none; } diff --git a/srv/http/assets/css/select2.css b/srv/http/assets/css/select2.css index eae0d526a..97c5ad5f8 100644 --- a/srv/http/assets/css/select2.css +++ b/srv/http/assets/css/select2.css @@ -7,10 +7,7 @@ line-height : 24px; border-radius : 4px; box-shadow : var( --shadow-select ); -} -#infoContent .select2-container { - max-width : none; - margin-bottom : 0; + text-align : left; } .select2-selection:focus-visible { outline: none; diff --git a/srv/http/assets/css/settings.css b/srv/http/assets/css/settings.css index 34f05097f..1ebb141df 100644 --- a/srv/http/assets/css/settings.css +++ b/srv/http/assets/css/settings.css @@ -62,7 +62,8 @@ ul { margin: 0; } .container::after, -.section::after { +.section::after, +.row::after { content: ''; display: block; clear: both; @@ -136,10 +137,10 @@ heading + .helpblock { padding-left: 0; text-align: center; } -#infoContent .helpmenu.label { +#infoList .helpmenu.label { background: var( --cgd ); } -#infoContent p i { +#infoList p i { width: 40px; } .helpmenu.label i { @@ -582,12 +583,12 @@ input.disabled + .switchlabel:after { border-radius: 6px; background: var( --cw ); } -#infoContent .gpio tr td:nth-child( odd ) { +#infoList .gpio tr td:nth-child( odd ) { width: 35px !important; padding-right: 2px !important; text-align: right !important; } -#infoContent .gpio tr td:nth-child( even ) { +#infoList .gpio tr td:nth-child( even ) { width: 55px !important; } #hostname { diff --git a/srv/http/assets/fonts/rern.woff2 b/srv/http/assets/fonts/rern.woff2 index 34a7cdc65..0a24851a4 100644 Binary files a/srv/http/assets/fonts/rern.woff2 and b/srv/http/assets/fonts/rern.woff2 differ diff --git a/srv/http/assets/js/addons.js b/srv/http/assets/js/addons.js index f6f9c1d81..62f2db1e4 100644 --- a/srv/http/assets/js/addons.js +++ b/srv/http/assets/js/addons.js @@ -49,11 +49,11 @@ $( '.container' ).on( 'click', '.revision', function() { addonData( $( e.currentTarget ) ); info( { - icon : icon - , title : V.addon.title - , textlabel : 'Branch / Release' - , values : 'UPDATE' - , ok : () => { + icon : icon + , title : V.addon.title + , list : [ 'Branch / Release', 'text' ] + , values : 'UPDATE' + , ok : () => { V.branch = infoVal(); if ( ! V.branch ) return @@ -74,14 +74,11 @@ function buttonLabel( icon, label ) { return ico( icon ) +' '+ label +''; } function optionGet() { - info( $.extend( - { + info( $.extend( { icon : icon , title : V.addon.title , ok : () => postData( infoVal() ) - } - , V.addon.option ) - ); + }, V.addon.option ) ); } function postData( opt ) { var htmlform = ''; diff --git a/srv/http/assets/js/camilla.js b/srv/http/assets/js/camilla.js index d55209b67..f30879365 100644 --- a/srv/http/assets/js/camilla.js +++ b/srv/http/assets/js/camilla.js @@ -1,179 +1,362 @@ -// var ////////////////////////////////////////////////////////////////////////////// -V = { - clipped : 0 - , graph : { filters: {}, pipeline: {} } - , graphlist : {} +// variables ////////////////////////////////////////////////////////////////////////////// +V = { + clipped : false + , graph : { filters: [], pipeline: [] } , prevconfig : {} , sortable : {} , tab : 'filters' + , timeoutred : true } -var wscamilla; -var format = {}; +var wscamilla = null +var $master = $( '#volume, #divvolume .i-minus, #divvolume .i-plus' ); +var R = {} +// filters ////////////////////////////////////////////////////////////////////////////// +var F0 = { + type : [ + 'Type' + , 'select' + , [ 'Gain', 'Loudness', 'Delay', 'Conv', 'Biquad', 'BiquadCombo', 'Dither', 'Limiter', 'DiffEq' ] // omit Volume - use alsa directly + ] + , subtype : { + Conv : [ + 'Subtype' + , 'select' + , [ 'Dummy', 'Raw', 'Wav', 'Values' ] + ] + , Biquad : [ + 'Subtype' + , 'select' + , [ 'Free', 'Lowpass', 'Highpass', 'Lowshelf', 'Highshelf', 'LowpassFO', 'HighpassFO', 'LowshelfFO', 'HighshelfFO' + , 'Peaking', 'Notch', 'GeneralNotch', 'Bandpass', 'Allpass', 'AllpassFO', 'LinkwitzTransform' ] + ] + , BiquadCombo : [ + 'Subtype' + , 'select' + , [ 'ButterworthLowpass', 'ButterworthHighpass', 'LinkwitzRileyLowpass', 'LinkwitzRileyHighpass', 'Tilt', 'FivePointPeq', 'GraphicEqualizer' ] + ] + , Dither : [ + 'Subtype' + , 'select' + , [ 'None', 'Flat', 'Highpass', 'Fweighted441', 'FweightedShort441', 'FweightedLong441', 'Gesemann441', 'Gesemann48', 'Lipshitz441', 'LipshitzLong441' + , 'Shibata441', 'ShibataHigh441', 'ShibataLow441', 'Shibata48', 'ShibataHigh48', 'ShibataLow48', 'Shibata882', 'ShibataLow882', 'Shibata96', 'ShibataLow96', 'Shibata192', 'ShibataLow192' ] + ] + } + , freq : [ 'Frequency', 'number' ] + , gain : [ 'Gain', 'number' ] + , q : [ 'Q', 'number' ] + , qbandwidth : [ '', 'radio', { Q: 'q', Bandwidth: 'bandwidth' } ] + , name : [ 'Name', 'text' ] + , fader : [ 'Fader', 'text' ] + , FivePointPeq : { + Lowshelf : [ 'fls', 'gls', 'qls' ] + , Peaking1 : [ 'fp1', 'gp1', 'qp1' ] + , Peaking2 : [ 'fp2', 'gp2', 'qp2' ] + , Peaking3 : [ 'fp3', 'gp3', 'qp3' ] + , Highshelf : [ 'fhs', 'ghs', 'qhs' ] + } +} +var F1 = { + pass : [ F0.name, F0.type, F0.subtype.Biquad, F0.freq, F0.q ] + , conv : [ F0.name, F0.type, F0.subtype.Cov ] + , fader : [ F0.name, F0.type, F0.fader ] + , combo : [ F0.name, F0.type, F0.subtype.BiquadCombo, [ 'Order', 'number' ], F0.freq ] +} +var Flist = { + pass : F1.pass + , shelf : [ ...F1.pass.slice( 0, 4 ), F0.gain, F0.q, [ '', 'radio', { Q: 'q', Slope: 'slope' } ] ] + , passFO : F1.pass.slice( 0, 4 ) + , shelfFO : [ ...F1.pass.slice( 0, 4 ), F0.gain ] + , notch : [ ...F1.pass, F0.qbandwidth ] +} +var F = { + Gain : [ + F0.name + , F0.type + , F0.gain + , [ '', 'radio', { dB: 'dB', Linear: 'linear' } ] + , [ 'Inverted', 'checkbox' ] + , [ 'Mute', 'checkbox' ] + ] + , Volume : [ + ...F1.fader + , [ 'Ramp time', 'number' ] + ] + , Loudness : [ + ...F1.fader + , [ 'Reference level', 'number' ] + , [ 'High boost', 'number' ] + , [ 'Low boost', 'number' ] + , [ 'Attenuate mid', 'checkbox' ] + ] + , Delay : [ + F0.name + , F0.type + , [ 'ms', 'number' ] + , [ '', 'radio', { ms: 'ms', mm: 'mm', Samples: 'samples' } ] + , [ 'Subsample', 'checkbox' ] + ] + , Conv : { + Dummy : [ + ...F1.conv + , [ 'Length', 'number' ] + ] + , Raw : [ + ...F1.conv + , [ 'File', 'select' ] + , [ 'Format', 'select', [ 'TEXT' ] ] + , [ 'Skip bytes lines', 'number' ] + , [ 'Read bytes lines', 'number' ] + ] + , Wav : [ + ...F1.conv + , [ 'File', 'select' ] + , [ 'Channel', 'number' ] + ] + , Values : [ + ...F1.conv + , [ 'Values', 'text' ] + ] + } + , Biquad : { + Free : [ + ...F1.pass.slice( 0, 3 ) + , [ 'a1', 'number' ] + , [ 'a2', 'number' ] + , [ 'b0', 'number' ] + , [ 'b1', 'number' ] + , [ 'b2', 'number' ] + ] + , Lowpass : Flist.pass + , Highpass : Flist.pass + , Lowshelf : Flist.shelf + , Highshelf : Flist.shelf + , LowpassFO : Flist.passFO + , HighpassFO : Flist.passFO + , LowshelfFO : Flist.shelfFO + , HighshelfFO : Flist.shelfFO + , Peaking : [ ...F1.pass.slice( 0, 4 ), F0.gain, F0.q, F0.qbandwidth ] + , Notch : Flist.notch + , GeneralNotch : [ + F0.name + , F0.type + , [ 'Zero frequency', 'number' ] + , [ 'Pole frequency', 'number' ] + , [ 'Pole Q', 'number' ] + , [ 'Normalize at DC', 'checkbox' ] + ] + , Bandpass : Flist.notch + , Allpass : Flist.notch + , AllpassFO : Flist.passFO + , LinkwitzTransform : [ + ...F1.pass.slice( 0, 3 ) + , [ 'Q act', 'number' ] + , [ 'Q target', 'number' ] + , [ 'Frequency act', 'number' ] + , [ 'Frequency target', 'number' ] + ] + } + , BiquadCombo : { + ButterworthLowpass : F1.combo + , ButterworthHighpass : F1.combo + , LinkwitzRileyLowpass : F1.combo + , LinkwitzRileyHighpass : F1.combo + , Tilt : [ + ...F1.combo.slice( 0, 3 ) + , [ 'Gain', 'number' ] + ] + , FivePointPeq : [ + ...F1.combo.slice( 0, 3 ) + , [ 'Lowshelf', 'text' ] // fls, gls, qls + , [ 'Peaking 1', 'text' ] // fp1, gp1, qp1 + , [ 'Peaking 2', 'text' ] // fp2, gp2, qp2 + , [ 'Peaking 3', 'text' ] // fp3, gp3, qp3 + , [ 'Highshelf', 'text' ] // fhs, ghs, qhs + , [ '', '', ' freq, gain, q' ] + ] + , GraphicEqualizer : [ + ...F1.combo.slice( 0, 3 ) + , [ 'Frequency min', 'number' ] + , [ 'Frequency max', 'number' ] + , [ 'Bands', 'number' ] + ] + } + , Dither : [ + F0.name + , F0.type + , F0.subtype.Dither + , [ 'Bits', 'number' ] + ] + , Limiter : [ + F0.name + , F0.type + , [ 'Clip limit', 'number' ] + , [ 'Soft clip', 'checkbox' ] + ] + , DiffEq : [ + F0.name + , F0.type + , [ 'a', 'text' ] + , [ 'b', 'text' ] + ] +// + , values : { + Gain : { name: '', type: '', gain: 0, scale: 'dB', inverted: false, mute: false } // +-150dB / +-10 linear + , Volume : { name: '', type: '', ramp_time: 400, fader: 'Aux1' } + , Loudness : { name: '', type: '', fader : 'main', reference_level: 25, high_boost: 10, low_boost: 10, attenuate_mid: false } + , Delay : { name: '', type: '', delay: 0, unit: 'ms', subsample: false } + // Conv + , Dummy : { name: '', type: '', subtype: '', length: 65536 } // min = 1 + , Raw : { name: '', type: '', subtype: '', filename: '', format: 'TEXT', skip_bytes_lines: 0, read_bytes_lines: 0 } + , Wav : { name: '', type: '', subtype: '', filename: '', channel: 0 } + , Values : { name: '', type: '', subtype: '', values: [ 1, 0, 0, 0 ] } + // Biquad + , pass : { name: '', type: '', subtype: '', freq: 1000, q: 0 } + , shelf : { name: '', type: '', subtype: '', freq: 1000, gain: 0, q: 0, unit: 'q' } + , passFO : { name: '', type: '', subtype: '', freq: 1000, name: '' } + , shelfFO : { name: '', type: '', subtype: '', freq: 1000, gain: 0 } + , notch : { name: '', type: '', subtype: '', freq: 1000, q: 0, unit: 'q' } + , GeneralNotch : { name: '', type: '', subtype: '', freq_z: 0, freq_p: 0, q_p: 0, normalize_at_dc:false } + , Peaking : { name: '', type: '', subtype: '', freq: 1000, gain: 0, q: 0, unit: 'q' } + , LinkwitzTransform : { name: '', type: '', subtype: '', q_act: 1.5, q_target: 0.5, freq_act: 50, freq_target: 25 } + , Free : { name: '', type: '', subtype: '', a1: 0, a2: 0, b0: -1, b1: 1, b2: 0 } + // BiquadCombo + , BiquadCombo : { name: '', type: '', subtype: '', order: 2, freq: 1000 } + , Tilt : { name: '', type: '', subtype: '', gain: 0 } + , FivePointPeq : { name: '', type: '', subtype: '', Lowshelf: [ 0, 0, 0 ], Peaking1: [ 0, 0, 0 ], Peaking2: [ 0, 0, 0 ], Peaking3: [ 0, 0, 0 ], Highshelf: [ 0, 0, 0 ] } + , GraphicEqualizer : { name: '', type: '', subtype: '', freq_min: 20, freq_max: 20000, bands: 10 } + // + , Dither : { name: '', type: '', subtype: '', bits: 16 } + , Limiter : { name: '', type: '', clip_limit: -10.0, soft_clip: false } + , DiffEq : { name: '', type: '', a: [ 1, 0 ], b: [ 1, 0 ] } + } +} +var P = { // processor + Compressor : [ + [ 'Name', 'text' ] + , [ 'Type', 'select', [ 'Compressor' ] ] + , [ 'Channels', 'number' ] + , [ 'Attack', 'number' ] + , [ 'Release', 'number' ] + , [ 'Threshold', 'number' ] + , [ 'Factor', 'number' ] + , [ 'Makeup gain', 'number' ] + , [ 'Clip limit', 'number' ] + , [ 'Soft clip', 'checkbox' ] + , [ 'Monitor channels', 'text' ] + , [ 'Process channels', 'text' ] + ] + , values : { + Compressor : { name: '', type: '', channels: 2, attack: 0.025, release: 1.0, threshold: -25, factor: 5.0, makeup_gain: 0, clip_limit: 0, soft_clip: false, monitor_channels: '0, 1', process_channels: '0, 1' } + } +} +// devices ///////////////////////////////////////////////////////////////////////////////////////// +var format = {}; [ 'S16LE', 'S24LE', 'S24LE3', 'S32LE', 'FLOAT32LE', 'FLOAT64LE', 'TEXT' ].forEach( k => { var key = k .replace( 'FLOAT', 'Float' ) .replace( 'TEXT', 'Text' ); format[ key ] = k; } ); -// const ////////////////////////////////////////////////////////////////////////////// -var C = { - format : format - , freeasync : { - keys : [ 'sinc_len', 'oversampling_ratio', 'interpolation', 'window', 'f_cutoff' ] - , interpolation : [ 'Cubic', 'Linear', 'Nearest' ] - , window : [ 'Blackman', 'Blackman2', 'BlackmanHarris', 'BlackmanHarris2', 'Hann', 'Hann2' ] - } +var D0 = { + main : [ 'samplerate', 'chunksize', 'queuelimit', 'silence_threshold', 'silence_timeout' ] , samplerate : [ 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000, 705600, 768000, 'Other' ] - , sampletype : [ 'AccurateAsync', 'BalancedAsync', 'FastAsync', 'FreeAsync', 'Synchronous' ] - , sampling : [ 'samplerate', 'chunksize', 'queuelimit', 'silence_threshold', 'silence_timeout' ] - , signal : [ 'GetCaptureSignalPeak', 'GetCaptureSignalRms', 'GetPlaybackSignalPeak', 'GetPlaybackSignalRms', 'GetClippedSamples' ] - , subtype : { - Biquad : [ 'Lowpass', 'Highpass', 'Lowshelf', 'Highshelf', 'LowpassFO', 'HighpassFO', 'LowshelfFO', 'HighshelfFO' - , 'Peaking', 'Notch', 'Bandpass', 'Allpass', 'AllpassFO', 'LinkwitzTransform', 'Free' ] - , BiquadCombo : [ 'ButterworthLowpass', 'ButterworthHighpass', 'LinkwitzRileyLowpass', 'LinkwitzRileyHighpass' ] - , Conv : [ 'Raw', 'Wav', 'Values' ] - , Dither : [ 'Simple', 'Uniform', 'Lipshitz441', 'Fweighted441', 'Shibata441', 'Shibata48', 'None' ] - } - , type : [ 'Biquad', 'BiquadCombo', 'Conv', 'Delay', 'Gain', 'Loudness', 'DiffEq', 'Dither' ] // omit Volume - use raAudio volume } -// capture / playback ////////////////////////////////////////////////////////////////////////////// -var CPkv = { - tc : { - number : { channels: 2 } - } - , tcsd : { - select : { device: '', format: '' } - , number : { channels: 2 } - } - , wasapi : { - select : { device: '', format: '' } - , number : { channels: 2 } - , checkbox : { exclusive: false, loopback: false } - } +var Dlist = { + type : [ 'Type', 'select', [ 'AsyncSinc', 'AsyncPoly', 'Synchronous' ] ] + , profile : [ 'Profile', 'select', [ 'Accurate ', 'Balanced', 'Fast', 'VeryFast', 'Custom' ] ] + , typeC : [ 'Type', 'select' ] // option: wait for ws 'GetSupportedDeviceTypes' + , typeP : [ 'Type', 'select' ] // ^ + , deviceC : [ 'Device', 'select' ] // ^ + , deviceP : [ 'Device', 'select' ] // ^ + , format : [ 'Format', 'select', format ] + , filename : [ 'Filename', 'select', S.lsraw ] + , channels : [ 'Channels', 'number' ] + , extra_samples : [ 'Extra samples', 'number' ] + , skip_bytes : [ 'Skip bytes', 'number' ] + , read_bytes : [ 'Read bytes', 'number' ] + , capture_samplerate : [ 'Capture samplerate', 'number' ] + , exclusive : [ 'Exclusive', 'checkbox' ] + , loopback : [ 'Loopback', 'checkbox' ] + , change_format : [ 'Change format', 'checkbox' ] } -var CP = { - capture : { - Alsa : CPkv.tcsd - , CoreAudio : { - select : { device: '', format: '' } - , number : { channels: 2 } - , checkbox : { change_format: false } - } - , Pulse : CPkv.tcsd - , Wasapi : CPkv.wasapi - , Jack : CPkv.tc - , Stdin : { - select : { format: '' } - , number : { channels: 2, extra_samples: 0, skip_bytes: 0, read_bytes: 0 } - } - , File : { - select : { format: '' } - , text : { filename: '' } - , number : { channels: 2, extra_samples: 0, skip_bytes: 0, read_bytes: 0 } - } - } - , playback : { - Alsa : CPkv.tcsd - , CoreAudio : { - select : { device: '', format: '' } - , number : { channels: 2 } - , checkbox : { exclusive: false, change_format: false } - } - , Pulse : CPkv.tcsd - , Wasapi : CPkv.wasapi - , Jack : CPkv.tc - , Stdout : { - select : { format: '' } - , number : { channels: 2 } - } - , File : { - select : { filename: '', format: '' } - , number : { channels: 2 } - } - } -} -// filters ////////////////////////////////////////////////////////////////////////////// -var Fkv = { - pass : { - number : { freq: 1000, q: 0 } - } - , shelf : { - number : { gain: 0, freq: 1000, q: 0 } - , radio : [ 'Q', 'Samples' ] - } - , passFO : { - number : { freq: 1000 } - } - , shelfFO : { - number : { gain: 0, freq: 1000 } - } - , notch : { - number : { freq: 1000, q: 0 } - , radio : [ 'Q', 'Bandwidth' ] - } +var D1 = { + AlsaC : [ Dlist.typeC, Dlist.deviceC, Dlist.format, Dlist.channels ] + , AlsaP : [ Dlist.typeP, Dlist.deviceP, Dlist.format, Dlist.channels ] + , extra : [ Dlist.extra_samples, Dlist.skip_bytes, Dlist.read_bytes ] } -var F = { - Lowpass : Fkv.pass - , Highpass : Fkv.pass - , Lowshelf : Fkv.shelf - , Highshelf : Fkv.shelf - , LowpassFO : Fkv.passFO - , HighpassFO : Fkv.passFO - , LowshelfFO : Fkv.shelfFO - , HighshelfFO : Fkv.shelfFO - , Peaking : { - number : { gain: 0, freq: 1000, q: 0 } - , radio : [ 'Q', 'Bandwidth' ] - } - , Notch : Fkv.notch - , Bandpass : Fkv.notch - , Allpass : Fkv.notch - , AllpassFO : Fkv.passFO - , LinkwitzTransform : { - number: { q_act: 1.5, q_target: 0.5, freq_act: 50, freq_target: 25 } - } - , Free : { - number: { a1: 0, a2: 0, b0: -1, b1: 1, b2: 0 } - } - , BiquadCombo : { - number: { order: 2, freq: 1000 } - } - , Raw : { - select : { filename: '' } - , number : { skip_bytes_lines: 0, read_bytes_lines: 0 } - } - , Wav : { - select : { filename: '' } - , number : { channel: 0 } - } - , Values : { - text : { values: '1, 0, 0, 0' } - , number : { length: 0 } - } - , Delay : { - number : { ms: 0 } - , radio : [ 'ms', 'Samples' ] - , checkbox : { subsample: false } - } - , Gain : { - number : { gain: 0 } - , checkbox : { inverted: false, mute: false } - } - , Volume : { - number: { ramp_time: 200 } - } - , Loudness : { - number: { reference_level: 5, high_boost: 5, low_boost: 5, ramp_time: 200 } - } - , DiffEq : { - text: { a: '1, 0', b: '1, 0' } - } - , Dither : { - number: { bits: 16 } +var D = { + main : [ + [ 'Sample Rate', 'select', D0.samplerate ] + , [ 'Other', 'number' ] + , [ 'Chunk size', 'number' ] + , [ 'Queue limit', 'number' ] + , [ 'Silence Threshold', 'number' ] + , [ 'Silence Timeout', 'number' ] + ] + , capture : { + Alsa : D1.AlsaC + , CoreAudio : [ ...D1.AlsaC, Dlist.change_format ] + , Pulse : D1.AlsaC + , Wasapi : [ ...D1.AlsaC, Dlist.exclusive, Dlist.loopback ] + , Jack : [ Dlist.typeC, Dlist.channels ] + , Stdin : [ Dlist.typeC, Dlist.format, Dlist.channels, ...D1.extra ] + , File : [ Dlist.typeC, Dlist.filename, Dlist.format, Dlist.channels, ...D1.extra ] + } + , playback : { + Alsa : D1.AlsaP + , CoreAudio : [ ...D1.AlsaP, Dlist.change_format ] + , Pulse : D1.AlsaP + , Wasapi : [ ...D1.AlsaP, Dlist.exclusive, Dlist.loopback ] + , Jack : [ Dlist.typeP, Dlist.channels ] + , Stdout : [ Dlist.typeP, Dlist.format, Dlist.channels ] + , File : [ Dlist.typeP, Dlist.filename, Dlist.format, Dlist.channels ] + } + , values : { + Alsa : { type: '', device: '', format: '', channels: 2 } + , CoreAudio : { type: '', device: '', format: '', channels: 2, change_format: '' } + , Pulse : { type: '', device: '', format: '', channels: 2 } + , Wasapi : { type: '', device: '', format: '', channels: 2, exclusive: false, loopback: false } + , Jack : { type: '', channels: 2 } + , Stdin : { type: '', format: '', channels: 2, extra_samples: 0, skip_bytes: 0, read_bytes: 0 } + , Stdout : { type: '', format: '', channels: 2 } + , File : { type: '', filename: '', format: '', channels: 2, extra_samples: 0, skip_bytes: 0, read_bytes: 0 } + , FileP : { type: '', filename: '', format: '', channels: 2 } + } + , resampler : { + AsyncSinc : [ + Dlist.type + , Dlist.profile + , Dlist.capture_samplerate + ] + , Custom : [ + Dlist.type + , Dlist.profile + , [ 'Sinc length', 'number' ] + , [ 'Oversampling factor', 'number' ] + , [ 'F cutoff', 'number' ] + , [ 'Interpolation', 'select', [ 'Nearest', 'Linear', 'Quadratic', 'Cubic' ] ] + , [ 'Window', 'select', [ 'Hann2', 'Blackman2', 'BlackmanHarris2', 'BlackmanHarris2' ] ] + , Dlist.capture_samplerate + ] + , AsyncPoly : [ + Dlist.type + , [ 'Interpolation', 'select', [ 'Linear', 'Cubic', 'Quintic', 'Septic' ] ] + , Dlist.capture_samplerate + ] + , Synchronous : [ + Dlist.type + , Dlist.capture_samplerate + ] + , values : { + AsyncSinc : { type: 'AsyncSinc', profile: 'Balanced', capture_samplerate: '' } + , Custom : { type: 'AsyncSinc', profile: 'Custom', sinc_len: 128, oversampling_factor: 256, f_cutoff: 0.9, interpolation: 'Cubic', window: 'Hann2', capture_samplerate: '' } + , AsyncPoly : { type: 'AsyncSinc', interpolation: 'Cubic', capture_samplerate: '' } + , Synchronous : { type: 'Synchronous', capture_samplerate: '' } + } } } // graph ////////////////////////////////////////////////////////////////////////////// -var color = { +var color = { g : 'hsl( 100, 90%, 40% )' , gd : 'hsl( 100, 90%, 20% )' , gr : 'hsl( 200, 3%, 30% )' @@ -189,7 +372,7 @@ var color = { , w : 'hsl( 200, 3%, 60% )' , wl : 'hsl( 200, 3%, 80% )' } -var plots = { +var plots = { magnitude : { yaxis : 'y' , type : 'scatter' @@ -216,13 +399,13 @@ var plots = { , line : { width : 1, color: color.g } } } -var ycommon = { +var ycommon = { overlaying : 'y' , side : 'right' , anchor : 'free' , autoshift : true } -var axes = { +var axes = { freq : { title : { text : 'Frequency' @@ -305,50 +488,27 @@ var axes = { // functions ////////////////////////////////////////////////////////////////////////////// function renderPage() { // common from settings.js - wscamilla && wscamilla.readyState === 1 ? util.wsGetConfig() : util.webSocket(); + wscamilla && wscamilla.readyState === 1 ? common.wsGetConfig() : common.webSocket(); } function psOnClose() { if ( V.off ) return clearInterval( V.intervalvu ); if ( wscamilla ) wscamilla.close(); - $( '#divstate .label' ).html( 'Buffer · Sampling' ); -} -function playbackButton() { - var play = S.state === 'play'; - if ( S.player === 'mpd' ) { - if ( S.pllength ) { - var btn = play ? 'pause' : 'play'; - } else { - var btn = 'play disabled'; - } - } else { - var btn = play ? 'stop' : 'play disabled'; - } - $( '.icon' ).prop( 'class', 'icon i-'+ S.player ); - $( '.playback' ).prop( 'class', 'playback i-'+ btn ); } function psVolume( data ) { - var vol = data.val; + if ( V.local ) return + if ( [ 'mute', 'unmute' ].includes( data.type ) ) { - V.local = false; - if ( data.type === 'mute' ) { - vol = 0; - S.volumemute = data.val; - } else { - S.volumemute = 0; - } - } - if ( ! V.local ) { - if ( data.type === 'dragpress' ) { - V.dragpress = true; - setTimeout( () => V.dragpress = false, 300 ); - } - util.volume( vol, 'push' ); + S.volumemute = data.type === 'mute' ? data.val : 0; + } else if ( data.type === 'dragpress' ) { + V.dragpress = true; + setTimeout( () => V.dragpress = false, 300 ); } + common.volume( S.volumemute ? 0 : data.val, 'push' ); } -var graph = { +var graph = { gain : () => { var $divgraph = $( '.divgraph' ); if ( ! $divgraph.length ) return @@ -360,36 +520,20 @@ var graph = { } ); }, 300 ); } - , list : () => { - var $divgraph = $( '#'+ V.tab +' .divgraph' ); - if ( $divgraph.length ) { - $divgraph.each( ( i, el ) => { - var $this = $( el ); - var val = $this.data( 'val' ); - if ( jsonChanged( S.config[ V.tab ][ val ], V.graph[ V.tab ][ val ] ) ) { - if ( $this.hasClass( 'hide' ) ) { // remove - changed + hide - $this.remove(); - return - - } - } - V.graphlist[ val ] = $this[ 0 ].outerHTML; // include in re-render - } ); - } - } , pipeline : () => { if ( ! $( '.flowchart' ).hasClass( 'hide' ) ) createPipelinePlot(); } - , plot : ( $li ) => { + , plot : $li => { if ( ! $li ) $li = V.li; $li.addClass( 'disabled' ); if ( typeof Plotly !== 'object' ) { $.getScript( '/assets/js/plugin/'+ jfiles.plotly, () => graph.plot() ); return } + var filters = V.tab === 'filters'; var val = $li.data( filters ? 'name' : 'index' ); - V.graph[ V.tab ][ val ] = jsonClone( S.config[ V.tab ][ val ] ); + V.graph[ V.tab ].push( val ); var filterdelay = false; if ( filters ) { filterdelay = FIL[ val ].type === 'Delay'; @@ -403,7 +547,7 @@ var graph = { if ( delay0 && 'gain' in filter.parameters && filter.parameters.gain !== 0 ) delay0 = false; } ); } - notify( V.tab, util.key2label( V.tab ), 'Plot ...' ); + notify( V.tab, common.tabTitle(), 'Plot ...' ); var cmd = filters ? " '"+ JSON.stringify( FIL[ val ] ) +"'" : " '"+ JSON.stringify( S.config ) +"' "+ val; bash( [ 'settings/camilla.py', V.tab + cmd ], data => { // groupdelay = delay, magnitude = gain var impulse = 'impulse' in data; @@ -491,35 +635,14 @@ var graph = { $svg.find( '.plot' ).before( $svg.find( '.overplot' ) ); elementScroll( $divgraph.parent() ); bannerHide(); - $divgraph.append( '' ); + $divgraph + .append( '' ) + .removeClass( 'hide' ); $li.removeClass( 'disabled' ); }, 'json' ); } - , refresh : () => { - var $divgraph = $( '#'+ V.tab +' .divgraph' ).not( '.hide' ); - if ( $divgraph.length ) $divgraph.each( ( i, el ) => graph.plot( $( el ).parent() ) ); - } - , toggle : () => { - var $divgraph = V.li.find( '.divgraph' ); - if ( ! $divgraph.length ) { - graph.plot(); - return - } - - if ( ! $divgraph.hasClass( 'hide' ) ) { - $divgraph.addClass( 'hide' ); - } else { - var val = $divgraph.data( 'val' ); - if ( jsonChanged( S.config[ V.tab ][ val ], V.graph[ V.tab ][ val ] ) ) { - graph.plot(); - } else { - $divgraph.removeClass( 'hide' ); - elementScroll( $divgraph.parent() ); - } - } - } } -var render = { +var render = { page : () => { if ( S.bluetooth ) S.lsconfigs = S[ 'lsconfigs-bt' ]; if ( ! S.range ) S.range = { MIN: -10, MAX: 10 }; @@ -530,14 +653,18 @@ var render = { } ); $( '.container' ).removeClass( 'hide' ); render.status(); - render[ V.tab ](); bannerHide(); } , status : () => { - V.statusget = [ 'GetState', 'GetCaptureRate', 'GetBufferLevel' ]; // Clipped samples already got by signals - if ( DEV.enable_rate_adjust ) V.statusget.push( 'GetRateAdjust' ); - V.statuslast = V.statusget[ V.statusget.length - 1 ]; - render.statusValue(); + playbackButton(); + if ( S.volume !== false ) { + $( '#divvolume' ).removeClass( 'hide' ); + $( '#volume .thumb' ).css( 'margin-left', $( '#volume .slide' ).width() / 100 * S.volume ); + render.volume(); + } else { + $( '#divvolume' ).addClass( 'hide' ); + } + $( '.rateadjust' ).toggleClass( 'hide', ! S.enable_rate_adjust ); if ( S.bluetooth ) { if ( ! $( '#divconfiguration .col-l i' ).length ) $( '#divconfiguration a' ).after( ico( 'bluetooth' ) ); } else { @@ -549,16 +676,17 @@ var render = { $( '#configuration' ).prop( 'disabled', $( '#configuration option' ).length === 1 ); if ( $( '.vubar' ).length ) return + // run once var vugrid = '
'; for ( i = 0; i < 4; i++ ) vugrid += ''; var vulabel = '
'; - [ '', -96, -48, -24, -12, -6, 0, 6 ].forEach( ( l, i ) => vulabel += ''+ l +'' ); + [ '', -96, -48, -24, -12, -6, 0 ].forEach( ( l, i ) => vulabel += ''+ l +'' ); var vubar = '
' + vugrid +'
' +'
'; [ 'capture', 'playback' ].forEach( k => { var lb = false; - var cp = k[ 0 ].toUpperCase(); + var cp = k[ 0 ]; if ( ! lb && k === 'playback' ) { lb = true; vubar += '
'+ vulabel +'
'; @@ -572,194 +700,239 @@ var render = { $( '#divvu .value' ).html( vubar +'
' ); var ch = DEV.capture.channels > DEV.playback.channels ? DEV.capture.channels : DEV.playback.channels; $( '.flowchart' ).attr( 'viewBox', '20 '+ ch * 30 +' 500 '+ ch * 80 ); - } - , statusValue : () => { - if ( ! ( 'status' in S ) ) S.status = { GetState: blinkdot } - playbackButton(); - var label = 'Buffer · Sampling'; - var status = S.status.GetState; - if ( [ 'Running', 'Starting' ].includes( status ) ) { - status = []; - [ 'GetBufferLevel', 'GetCaptureRate' ].forEach( k => status.push( S.status[ k ].toLocaleString() ) ); - if ( DEV.enable_rate_adjust ) { - label += ' · Adj'; - status.push( S.status.GetRateAdjust.toLocaleString() ); - } - status = status.join( ' · ' ); - } - var clipped = S.status.GetClippedSamples; - S.clipped > clipped ? S.clipped = 0 : clipped = clipped - S.clipped; - if ( clipped ) { - label += '
Clipped'; - status += '
'+ clipped.toLocaleString() +''; - } - $( '#divstate .label' ).html( label ); - $( '#divstate .value' ).html( status ); - if ( S.volume !== false ) { - $( '#divvolume' ).removeClass( 'hide' ); - $( '#volume .thumb' ).css( 'margin-left', $( '#volume .slide' ).width() / 100 * S.volume ); - render.volume(); - } else { - $( '#divvolume' ).addClass( 'hide' ); - } + $( '#devices' ).prepend( $( '#codeoutput' ) ); } , tab : () => { - var title = util.key2label( V.tab ); - if ( V.tab === 'filters' ) { - title += ico( 'folder-filter' ); - } else if ( V.tab === 'pipeline' && PIP.length ) { - title += ico( 'flowchart' ); - } else if ( V.tab === 'config' ) { - title += 'uration'; - } - if ( V.tab === 'filters' || V.tab === 'mixers' ) title += ico( 'gear' ); - title += ico( V.tab === 'devices' ? 'gear' : 'add' ); - $( '#divsettings .headtitle' ).eq( 0 ).html( title ); - $( '#divsettings .tab' ).addClass( 'hide' ); - $( '#'+ V.tab ).removeClass( 'hide' ); + $( '.section:not( #divstatus )' ).addClass( 'hide' ); + $( '#div'+ V.tab ).removeClass( 'hide' ); $( '#bar-bottom div' ).removeClass( 'active' ); $( '#tab'+ V.tab ).addClass( 'active' ); - if ( V.tab === 'config' ) { - render.config(); + if ( [ 'config', 'devices', 'pipeline' ].includes( V.tab ) ) { + render[ V.tab ](); return } - if ( $( '#'+ V.tab +' .entries.main' ).is( ':empty' ) ) { + var $main = $( '#'+ V.tab +' .entries.main' ); + if ( $main.is( ':empty' ) ) { render.prevconfig(); render[ V.tab ](); } else { if ( ! jsonChanged( S.config[ V.tab ], V.prevconfig[ V.tab ] ) ) return render.prevconfig(); - if ( ! $( '#'+ V.tab +' .entries.main' ).hasClass( 'hide' ) ) { - render[ V.tab ](); - } else { - var data = V.tab === 'mixers' ? 'name' : 'index'; - var val = $( '#'+ V.tab +' .entries.sub .lihead' ).data( data ); + if ( $main.hasClass( 'hide' ) ) { + var data = V.tab === 'pipeline' ? 'index' : 'name'; + var val = $( '#'+ V.tab +' .entries.sub li' ).eq( 0 ).data( data ); render[ V.tab +'Sub' ]( val ); + } else { + render[ V.tab ](); } } } - , vu : () => { - $( '.peak' ).css( 'background', 'var( --cm )' ); - V.intervalvu = setInterval( () => C.signal.forEach( k => wscamilla.send( '"'+ k +'"' ) ), 100 ); + , volume : () => { + if ( S.volumemute ) { + $master.addClass( 'disabled' ); + $( '#divvolume .level' ).addClass( 'bl' ); + $( '#divvolume .i-volume' ).addClass( 'mute' ); + } else { + $master.removeClass( 'disabled' ); + $( '#divvolume .i-minus' ).toggleClass( 'disabled', S.volume === 0 ); + $( '#divvolume .i-plus' ).toggleClass( 'disabled', S.volume === 100 ); + $( '#divvolume .level' ).removeClass( 'bl' ); + $( '#divvolume .i-volume' ).removeClass( 'mute' ); + } + $( '#divvolume .level' ).text( S.volumemute || S.volume ) } - , vuClear() { + , vuClear : () => { if ( ! ( 'intervalvu' in V ) ) return - + + V.signal = false; clearInterval( V.intervalvu ); delete V.intervalvu; - $( '.peak' ).css( { left: 0, background: 'var( --cga )' } ); - $( '.rms' ).css( 'width', 0 ); + $( '.peak, .rms' ).css( { 'transition-duration': '0s', width: 0 } ); + $( '.peak' ).css( 'left', 0 ); } - , volume : () => { - $( '#volume-text' ) - .text( S.volumemute || S.volume ) - .toggleClass( 'bl', S.volumemute > 0 ); - $( '#divvolume .i-volume' ).toggleClass( 'mute bl', S.volumemute > 0 ); - } //--------------------------------------------------------------------------------------------- + , vuLevel : ( rms, cpi, db ) => { + if ( db < -98 ) { + var width = 0; + var left = 0; + } else { + var width = Math.log10( ( 100 + db ) / 10 ) * 200; // -99 = -1, - 100 = -Infinity + var left = width - 2; + } + if ( rms ) { + $( '.rms.'+ cpi ).css( 'width', width +'px' ); + } else { + $( '.peak.'+ cpi ).css( 'left', left +'px' ); + if ( db > 0 ) { + if ( ! V.timeoutred ) return + + clearTimeout( V.timeoutred ); + V.timeoutred = false; + $( '.peak, .clipped' ) + .css( 'transition-duration', '0s' ) + .addClass( 'red' ); + } else if ( ! V.timeoutred ) { + V.timeoutred = setTimeout( () => { + $( '.peak, .clipped' ) + .css( 'transition-duration', '' ) + .removeClass( 'red' ); + }, 200 ); + } + } + } //----------------------------------------------------------------------------------- , filters : () => { - graph.list(); + if ( ! Object.keys( FIL ).length ) return + var data = render.dataSort( 'filters' ); var li = ''; $.each( data, ( k, v ) => li += render.filter( k, v ) ); - render.toggle( li ); - graph.refresh(); + $( '#'+ V.tab +' .entries.main' ).html( li ); + render.toggle(); } , filter : ( k, v ) => { - var param = v.parameters; - if ( 'gain' in v.parameters ) { - var val = util.dbRound( param.gain ); - var licontent = '
'+ k +'
' - +'
'+ param.freq +'Hz '+ ( 'q' in param ? 'Q:'+ param.q : 'S:'+ param.slope ) +'
' - +'
' - +''+ val +'' - +'' - +'
'+ ico( 'minus' ) + ico( 'set0' ) + ico( 'plus' ) +'
' - +'
'; - if ( k in V.graphlist ) licontent += V.graphlist[ k ]; + var param = v.parameters; + var scale = false; + var icongain = ''; + var disabled = ''; + if ( v.type === 'Gain' ) { + var scale = param.scale === 'linear' ? 10 : 100; + icongain = ico( param.mute ? 'volume mute' : 'volume' ) + + ico( param.inverted ? 'inverted bl' : 'inverted' ) + + ico( param.scale === 'linear' ? 'linear bl' : 'linear' ); + disabled = param.mute ? ' disabled' : ''; + } + if ( 'gain' in param ) { + var gain = param.gain; + var li = '
'+ k +'
' + +'
' + + ( 'freq' in param ? param.freq +'Hz ' : v.type ) + + ( 'q' in param ? 'Q:'+ param.q : '' ) + + ( 'slope' in param ? 'S:'+ param.slope : '' ) + +'
' + +'
' + + render.htmlRange( scale, gain, disabled ) + + icongain + +'
'; } else { - var licontent = '
'+ k +'
' - +'
'+ v.type +' · '+ render.json2string( param ) +'
'; + var paramdata = [ 'FivePointPeq', 'GraphicEqualizer' ].includes( param.type ) ? param.type : render.json2string( param ); + var li = '
'+ k +'
' + +'
'+ v.type +' · '+ paramdata +'
'; + } + var $graph = $( '#filters .entries.main li[data-name="'+ k +'"]' ).find( '.divgraph' ); + if ( $graph.length ) li += $graph[ 0 ].outerHTML; + if ( param.type === 'GraphicEqualizer' ) { + var icon = 'equalizer'; + var classeq = ' class="eq"'; + } else { + var icon = 'filters'; + var classeq = ''; } - return '
  • '+ ico( 'filters liicon edit graph' ) + licontent +'
  • '; + return '
  • '+ ico( icon +' liicon edit graph' ) + li +'
  • ' } - , filtersSub : ( k ) => { + , filtersSub : k => { var li = '
  • '+ ico( 'folder-filter' ) +'FIR coefficients'+ ico( 'add' ) + ico( 'back' ) +'
  • '; if ( S.lscoeffs.length ) S.lscoeffs.forEach( k => li += '
  • '+ ico( 'file liicon' ) + k +'
  • ' ); - render.toggle( li, 'sub' ); - } //--------------------------------------------------------------------------------------------- + $( '#'+ V.tab +' .entries.sub' ).html( li ); + render.toggle( 'sub' ); + } //----------------------------------------------------------------------------------- , mixers : () => { + if ( ! Object.keys( MIX ).length ) return + var data = render.dataSort( 'mixers' ); var li = ''; $.each( data, ( k, v ) => li+= render.mixer( k, v ) ); - render.toggle( li ); + $( '#'+ V.tab +' .entries.main' ).html( li ); + render.toggle(); } , mixer : ( k, v ) => { - return '
  • '+ ico( 'mixers liicon edit' ) - +'
    '+ k +'
    ' - +'
    In: '+ v.channels.in +' - Out: '+ v.channels.out +'
    ' - +'
  • ' - } - , mixersSub : ( name ) => { - var data = MIX[ name ].mapping; - var chmapping = data.length; - var chin = DEV.capture.channels; - var chout = DEV.playback.channels; - var iconadd = chout === chmapping ? '' : ico( 'add' ); - var li = '
  • '+ ico( 'mixers' ) + name + iconadd + ico( 'back' ) +'
  • '; - var optin = htmlOption( chin ); - var optout = htmlOption( chout ); + return '
  • '+ ico( 'mixers liicon edit' ) + +'
    '+ k +'
    ' + +'
    In: '+ v.channels.in +' - Out: '+ v.channels.out +'
    ' + +'
  • ' + } + , mixersSub : name => { + var iconadd = max => ico( max ? 'add disabled' : 'add' ); + var data = MIX[ name ].mapping; + var chin = DEV.capture.channels; + var chout = DEV.playback.channels; + var li = '
  • '+ ico( 'mixers subicon' ) + name + + iconadd( chout === data.length ) + ico( 'back' ) + +'
  • '; + var optin = htmlOption( chin ); + var optout = htmlOption( chout ); data.forEach( ( kv, i ) => { - var dest = kv.dest; - var opts = optout.replace( '>'+ dest, ' selected>'+ dest ); - var i_name = ' data-index="'+ i +'" data-name="'+ name +'"'; - li += '
  • '+ ico( 'output liicon' ) + var dest = kv.dest; + var opts = optout.replace( '>'+ dest, ' selected>'+ dest ); + var i_name = ' data-index="'+ i +'" data-name="'+ name +'"'; + li += '
  • '+ ico( 'output liicon' ) +'
    ' - +'
    '+ ico( kv.mute ? 'volume mute bl' : 'volume' ) + ico( 'add' ) + + ico( kv.mute ? 'volume mute' : 'volume' ) + iconadd( chout === kv.sources.length ) +'
  • '; kv.sources.forEach( ( s, si ) => { var source = data[ i ].sources[ si ]; var channel = source.channel; var opts = optin.replace( '>'+ channel, ' selected>'+ channel ); - var val = util.dbRound( source.gain ); - var disabled = ( source.mute ? ' disabled' : '' ); - li += '
  • '+ ico( 'input liicon' ) +'' - + ico( source.mute ? 'volume mute bl' : 'volume' ) +''+ val +'' - +'' - +'
    '+ ico( 'minus' ) + ico( 'set0' ) + ico( 'plus' ) +'
    ' + var gain = source.gain || 0; + var disabled = source.mute ? ' disabled' : ''; + var linear = source.scale === 'linear'; + li += '
  • '+ ico( 'input liicon' ) +'' + + render.htmlRange( linear ? 10 : 100, gain, disabled ) + + ico( source.mute ? 'volume mute' : 'volume' ) + ico( source.inverted ? 'inverted bl' : 'inverted' ) + + ico( linear ? 'linear bl' : 'linear' ) +'
  • '; } ); } ); - render.toggle( li, 'sub' ); + $( '#'+ V.tab +' .entries.sub' ).html( li ); + render.toggle( 'sub' ); selectSet( $( '#mixers select' ) ); - } //--------------------------------------------------------------------------------------------- + } //----------------------------------------------------------------------------------- + , processors : () => { + if ( ! PRO || ! Object.keys( PRO ).length ) return + + var data = render.dataSort( 'processors' ); + var li = ''; + $.each( data, ( k, v ) => { + var param = jsonClone( v.parameters ); + [ 'channels', 'monitor_channels', 'process_channels' ].forEach( k => delete param[ k ] ); + li += '
  • '+ ico( 'processors liicon edit' ) + +'
    '+ k +'
    ' + +'
    '+ v.type +' · '+ render.json2string( param )+'
    ' + +'
  • ' + } ); + $( '#'+ V.tab +' .entries.main' ).html( li ); + render.toggle(); + } //----------------------------------------------------------------------------------- , pipeline : () => { - graph.list(); + $( '.i-flowchart' ).toggleClass( 'disabled', PIP.length === 0 ); + if ( ! PIP.length ) return + var li = ''; PIP.forEach( ( el, i ) => li += render.pipe( el, i ) ); - render.toggle( li ); + $( '#'+ V.tab +' .entries.main' ).html( li ); + render.toggle(); render.sortable( 'main' ); - graph.refresh(); } , pipe : ( el, i ) => { + var icon = 'pipeline liicon'; if ( el.type === 'Filter' ) { - var graph = 'graph'; - var each = '
    ' + el.type +'
    ' - +'
    channel '+ el.channel +': '+ el.names.join( ', ' ) +'
    '; - if ( i in V.graphlist ) each += V.graphlist[ i ]; + icon += ' graph'; + var li = '
    ' + el.type +'
    ' + +'
    channel '+ el.channel +': '+ el.names.join( ', ' ) +'
    '; } else { - var graph = ''; - var each = 'Mixer: '+ el.name; + var li = 'Mixer: '+ el.name; } - return '
  • '+ ico( 'pipeline liicon '+ graph ) + each +'
  • ' + var $graph = $( '#filters .entries.main li[data-index="'+ i +'"]' ).find( '.divgraph' ); + if ( $graph.length ) li += $graph[ 0 ].outerHTML; + return '
  • '+ ico( icon ) + li +'
  • ' } - , pipelineSub : ( index ) => { + , pipelineSub : index => { var data = PIP[ index ]; var li = '
  • '+ ico( 'pipeline' ) +'Channel '+ data.channel + ico( 'add' ) + ico( 'back' ) +'
  • '; data.names.forEach( ( name, i ) => li += render.pipeFilter( name, i ) ); - render.toggle( li, 'sub' ); + $( '#'+ V.tab +' .entries.sub' ).html( li ); + render.toggle( 'sub' ); render.sortable( 'sub' ); } , pipeFilter : ( name, i ) => { @@ -768,7 +941,7 @@ var render = { +'
    '+ FIL[ name ].type +' · '+ render.json2string( FIL[ name ].parameters ) +'
    ' +'' } - , sortable : ( el ) => { + , sortable : el => { if ( el in V.sortable ) return V.sortable[ el ] = new Sortable( $( '#pipeline .entries.'+ el )[ 0 ], { @@ -791,7 +964,7 @@ var render = { graph.pipeline(); } } ); - } //--------------------------------------------------------------------------------------------- + } //----------------------------------------------------------------------------------- , devices : () => { var li = ''; [ 'playback', 'capture' ].forEach( d => { @@ -799,43 +972,55 @@ var render = { var data = jsonClone( dev ); [ 'device', 'type' ].forEach( k => delete data[ k ] ); li += '
  • '+ ico( d === 'capture' ? 'input' : 'output' ) - +'
    '+ util.key2label( d ) +' · '+ render.typeReplace( dev.type ) - + ( 'device' in dev ? ' · '+ dev.device +'
    ' : '' ) - +'
    '+ render.json2string( data ) +'
    ' - +'
  • '; + +'
    '+ common.key2label( d ) +' · '+ render.typeReplace( dev.type ) + + ( 'device' in dev ? ' · '+ dev.device +'
    ' : '' ) + +'
    '+ render.json2string( data ) +'
    ' + +''; } ); $( '#devices .entries.main' ).html( li ); - [ 'enable_rate_adjust', 'stop_on_rate_change', 'enable_resampling' ].forEach( k => S[ k ] = DEV[ k ] ); var labels = ''; var values = ''; - C.sampling.forEach( k => { - labels += util.key2label( k ) +'
    '; - values += DEV[ k ].toLocaleString() +'
    '; + D0.main.forEach( k => { + if ( k in DEV ) { + labels += common.key2label( k ) +'
    '; + values += DEV[ k ].toLocaleString() +'
    '; + } } ); var keys = []; - if ( DEV.enable_rate_adjust ) keys.push( 'adjust_period', 'target_level' ); - if ( DEV.enable_resampling ) keys.push( 'resampler_type', 'capture_samplerate' ); - if ( DEV.stop_on_rate_change ) keys.push( 'rate_measure_interval' ); + if ( S.enable_rate_adjust ) keys.push( 'adjust_period', 'target_level' ); + if ( S.stop_on_rate_change ) keys.push( 'rate_measure_interval' ); if ( keys.length ) { labels += '
    '; values += '
    '; keys.forEach( k => { - labels += util.key2label( k ) +'
    '; + labels += common.key2label( k ) +'
    '; values += DEV[ k ] +'
    '; } ); } + if ( S.resampler ) { + labels += 'Resampler
    ' + values += DEV.resampler.type +'
    '; + if ( 'profile' in DEV.resampler ) { + labels += 'Profile
    ' + values += DEV.resampler.profile +'
    '; + } + if ( S.capture_samplerate ) { + labels += 'Capture samplerate
    ' + values += DEV.capture_samplerate +'
    '; + } + } $( '#divsampling .label' ).html( labels ); $( '#divsampling .value' ).html( values.replace( /bluealsa|Bluez/, 'BlueALSA' ) ); switchSet(); - $( '#divenable_rate_adjust input' ).toggleClass( 'disabled', DEV.enable_resampling && DEV.resampler_type === 'Synchronous' ); - } //--------------------------------------------------------------------------------------------- + $( '#divenable_rate_adjust input' ).toggleClass( 'disabled', S.resampler && DEV.resampler.type === 'Synchronous' ); + } //----------------------------------------------------------------------------------- , config : () => { var li = ''; S.lsconfigs.forEach( f => { li += '
  • '+ ico( 'file liicon' ) +''+ f +'
  • '; } ); - $( '#config .entries.main' ).html( li ); - } //--------------------------------------------------------------------------------------------- + $( '#'+ V.tab +' .entries.main' ).html( li ); + } //----------------------------------------------------------------------------------- , dataSort : () => { var kv = S.config[ V.tab ]; var data = {}; @@ -843,7 +1028,26 @@ var render = { keys.sort().forEach( k => data[ k ] = kv[ k ] ); return data } - , json2string : ( json ) => { + , htmlRange : ( scale, gain, disabled ) => { + if ( scale === 100 ) { // filter - Gain / mixer - dB + var db = gain; + var value = gain; + } else { + var db = gain.toFixed( 1 ); + var value = gain * 10 + } + var disabled = { + range : disabled + , min : disabled || ( value === -100 ? ' disabled' : '' ) + , max : disabled || ( value === 100 ? ' disabled' : '' ) + , db : disabled || ( value === 0 ? ' disabled' : '' ) + } + return '' + +'' + +'' + +''+ db +'' + } + , json2string : json => { return JSON.stringify( json ) .replace( /[{"}]/g, '' ) .replace( /type:|filename:.*\/|format:TEXT,|skip_bytes_lines:.*|read_bytes_lines:.*/g, '' ) @@ -851,143 +1055,111 @@ var render = { .replace( /([:,])/g, '$1 ' ) } , prevconfig : () => V.prevconfig[ V.tab ] = jsonClone( S.config[ V.tab ] ) - , toggle : ( li, sub ) => { - var ms = sub ? [ 'main', 'sub' ] : [ 'sub', 'main' ]; - $( '#'+ V.tab +' .entries.'+ ms[ 0 ] ).addClass( 'hide' ); - $( '#'+ V.tab +' .entries.'+ ms[ 1 ] ) - .html( li ) - .removeClass( 'hide' ); + , toggle : ( sub ) => { + var $main = $( '#'+ V.tab +' .entries.main' ); + var $sub = $( '#'+ V.tab +' .entries.sub' ); + if ( sub || $main.hasClass( 'hide' ) ) { + $main.addClass( 'hide' ); + $sub.removeClass( 'hide' ); + } else { + $main.removeClass( 'hide' ); + $sub.addClass( 'hide' ); + } $( '#menu' ).addClass( 'hide' ); + if ( [ 'filters', 'pipeline' ].includes( V.tab ) && V.graph[ V.tab ].length ) { + var val = V.tab === 'filters' ? 'name' : 'index'; + $( '#'+ V.tab +' .entries.main li' ).each( ( i, el ) => { + var $el = $( el ); + if ( V.graph[ V.tab ].includes( $el.data( val ) ) ) graph.plot( $el ); + } ); + } } - , typeReplace : ( str ) => { + , typeReplace : str => { return str .replace( 'Alsa', 'ALSA' ) .replace( 'Std', 'std' ) } } -var setting = { - filter : ( type, subtype, name, newname ) => { - if ( name ) { - var ekv = { type : type } - $.each( FIL[ name ].parameters, ( k, v ) => ekv[ k === 'type' ? 'subtype' : k ] = v ); - } - // select - var selectlabel = [ 'type' ]; - var select = [ jsonClone( C.type ) ]; - var values = { type: type } - if ( subtype ) { - selectlabel.push( 'subtype' ) - select.push( jsonClone( C.subtype[ type ] ) ); - values.subtype = subtype; - var key_val = subtype in F ? jsonClone( F[ subtype ] ) : jsonClone( F[ type ] ); - if ( subtype === 'Uniform' ) key_val.amplitude = 1; - } - if ( ! key_val ) var key_val = jsonClone( F[ type ] ); - if ( 'select' in key_val ) { - var kv = key_val.select; - var k = Object.keys( kv ); - selectlabel = [ ...selectlabel, ...k ]; - if ( [ 'Raw', 'Wav' ].includes( subtype ) ) { - var lscoef = jsonClone( S[ subtype === 'Raw' ? 'lscoefraw' : 'lscoefwav' ] ); - select = [ ...select, lscoef ]; +var setting = { + filter : ( type, subtype, name ) => { + var list = subtype ? F[ type ][ subtype ] : F[ type ]; + if ( type === 'Biquad' ) { + if ( [ 'Hig', 'Low' ].includes( subtype.slice( 0, 3 ) ) ) { + var vsubtype = subtype.replace( /High|Low/, '' ); + } else if ( subtype.slice( -4 ) === 'pass' ) { + var vsubtype = 'notch'; + } else if ( subtype === 'AllpassFO' ) { + var vsubtype = 'passFO'; + } else { + var vsubtype = type; } - if ( name ) k.forEach( key => kv[ key ] = ekv[ key ] ); - values = { ...values, ...kv }; - } - selectlabel = util.labels2array( selectlabel ); - // text - var textlabel = [ 'name' ]; - values.name = name || newname; - if ( 'text' in key_val ) { - var kv = key_val.text; - var k = Object.keys( kv ); - textlabel = [ ...textlabel, ...k ]; - if ( name ) k.forEach( key => kv[ key ] = ekv[ key ] ); - values = { ...values, ...kv }; + } else if ( type === 'BiquadCombo' ) { + var vsubtype = [ 'Tilt', 'FivePointPeq', 'GraphicEqualizer' ].includes( subtype ) ? subtype : type; + } else { + var vsubtype = type; } - textlabel = util.labels2array( textlabel ); - // number - var numberlabel = false; - if ( 'number' in key_val ) { - var kv = key_val.number; - var k = Object.keys( kv ); - numberlabel = k; - if ( name ) { - k.forEach( key => { - if ( [ 'q', 'samples' ].includes( key ) ) { - if ( ! ( 'q' in ekv ) ) { - delete kv.q; - key = 'samples'; - } - numberlabel[ numberlabel.length - 1 ] = key; - } - kv[ key ] = ekv[ key ]; + var values = F.values[ vsubtype ]; + values.type = type; + if ( subtype ) values.subtype = subtype; + if ( name ) { + values.name = name; + if ( subtype === 'FivePointPeq' ) { + Object.keys( F0.FivePointPeq ).forEach( k => { + values[ k ] = []; + F0.FivePointPeq[ k ].forEach( key => values[ k ].push( FIL[ name ].parameters[ key ] ) ); + } ); + } else if ( subtype === 'GraphicEqualizer' ) { + $.each( FIL[ name ].parameters, ( k, v ) => { + if ( k === 'type' ) return + + k === 'gains' ? values.bands = v.length : values[ k ] = v; + } ); + } else { + $.each( FIL[ name ].parameters, ( k, v ) => { + if ( k === 'type' ) return + + values[ k ] = v; } ); } - values = { ...values, ...kv }; - numberlabel = util.labels2array( numberlabel ); } - // radio - q / samples - var radio = false; - if ( 'radio' in key_val ) { - radio = key_val.radio; - values = { ...values, radio: numberlabel[ numberlabel.length - 1 ] }; - } - // checkbox - var checkbox = false; - if ( 'checkbox' in key_val ) { - var kv = key_val.checkbox; - var k = Object.keys( kv ); - checkbox = util.labels2array( k ); - if ( name ) k.forEach( key => kv[ key ] = ekv[ key ] ); - values = { ...values, ...kv }; - } - if ( 'filename' in values ) values.filename = values.filename.split( '/' ).pop(); var title = name ? 'Filter' : 'Add Filter'; info( { icon : V.tab , title : title - , selectlabel : selectlabel - , select : select - , textlabel : textlabel - , numberlabel : numberlabel - , radio : radio - , radiosingle : true - , checkbox : checkbox + , list : list , boxwidth : 198 - , order : [ 'select', 'text', 'number', 'radio', 'checkbox' ] , values : values , checkblank : true - , checkchanged : name + , checkchanged : name in FIL , beforeshow : () => { - $( '#infoContent td:first-child' ).css( 'min-width', '125px' ); - var $tdname = $( '#infoContent td' ).filter( function() { - return $( this ).text() === 'Name' - } ); - $( '#infoContent tr' ).eq( 0 ).before( $tdname.parent() ); - var $select = $( '#infoContent select' ); - var $selecttype = $select.eq( 0 ); - $selecttype.on( 'input', function() { - var type = $( this ).val(); - var subtype = type in C.subtype ? C.subtype[ type ][ 0 ] : ''; - setting.filter( type, subtype, '', infoVal().name ); + if ( name ) $( '#infoList select' ).slice( 0, 2 ).prop( 'disabled', true ); + $( '#infoList td:first-child' ).css( 'min-width', '125px' ); + var $select = $( '#infoList select' ); + $select.eq( 0 ).on( 'input', function() { + var val = infoVal(); + var subtype = val.type in F0.subtype ? F0.subtype[ val.type ][ 2 ][ 0 ] : ''; + setting.filter( val.type, subtype, val.name ); } ); - if ( $select.length > 1 ) { + if ( subtype ) { $select.eq( 1 ).on( 'input', function() { - var type = $selecttype.val(); - var subtype = $( this ).val(); - setting.filter( type, subtype, '', infoVal().name ); + var val = infoVal(); + if ( val.type === 'Conv' && [ 'Raw', 'Wav' ].includes( val.subtype ) && ! S.lscoeffs.length ) { + info( { + icon : V.tab + , title : title + , message : 'Filter files not available.' + , ok : () => setting.filter( 'Conv', subtype, val.name ) + } ); + } else { + setting.filter( val.type, val.subtype, val.name ); + } } ); } - if ( radio ) { - var $tr = $( '#infoContent .trradio' ).prev(); - var itr = $tr.index() - var $label = $tr.find( 'td' ).eq( 0 ); - var $radio = $( '#infoContent input:radio' ); + var $radio = $( '#infoList input:radio' ); + if ( $radio.length ) { + var $label = $radio.parents( 'tr' ).prev().find( 'td' ).eq( 0 ); $radio.on( 'input', function() { - var val = $( this ).filter( ':checked' ).val(); - I.keys[ itr ] = val.toLowerCase(); - $label.text( val ); + $label.text( $( this ).filter( ':checked' ).parent().text() ); } ); } } @@ -996,15 +1168,39 @@ var setting = { var newname = val.name; type = val.type; subtype = val.subtype; - var param = { type: subtype }; - [ 'name', 'type', 'subtype', 'radio' ].forEach( k => delete val[ k ] ); + if ( type === 'DiffEq' ) { + [ 'a', 'b' ].forEach( k => val[ k ] = common.list2array( val[ k ] ) ); + } else if ( subtype === 'FivePointPeq' ) { + Object.keys( F0.FivePointPeq ).forEach( k => { + var v = common.list2array( val[ k ] ); + F0.FivePointPeq[ k ].forEach( ( key, i ) => { + val[ key ] = v[ i ]; + } ); + delete val[ k ]; + } ); + } else if ( subtype === 'GraphicEqualizer' ) { + var bands = val.bands; + delete val.bands; + val.gains = Array( bands ).fill( 0 ); + } else if ( subtype === 'Values' ) { + val.values = common.list2array( val.values ); + } + var param = {} + if ( 'subtype' in val ) param.type = subtype; + [ 'name', 'type', 'subtype' ].forEach( k => delete val[ k ] ); + if ( 'q' in values && 'unit' in values ) { + var q = val.q; + var unit = val.unit; + [ 'q', 'bandwidth', 'slope', 'unit' ].forEach( k => delete val[ k ] ); + val[ unit ] = q; + } $.each( val, ( k, v ) => param[ k ] = v ); if ( 'filename' in param ) { param.filename = '/srv/http/data/camilladsp/coeffs/'+ param.filename; if ( subtype === 'Raw' ) param.format = 'TEXT'; } FIL[ newname ] = { type: type, parameters : param } - if ( name !== newname ) { + if ( name in FIL && name !== newname ) { delete FIL[ name ]; PIP.forEach( p => { if ( p.type === 'Filter' ) { @@ -1018,14 +1214,14 @@ var setting = { render.filters(); } } ); - } //--------------------------------------------------------------------------------------------- - , mixer : ( name ) => { + } //----------------------------------------------------------------------------------- + , mixer : name => { var title = name ? 'Mixer' : 'Add Mixer' info( { icon : V.tab , title : title , message : name ? 'Rename '+ name +' to:' : '' - , textlabel : 'Name' + , list : [ 'Name', 'text' ] , values : name , checkblank : true , checkchanged : name @@ -1071,46 +1267,25 @@ var setting = { } ); } , mixerMap : ( name, index ) => { - var option = { - dest : htmlOption( DEV.playback.channels ) - , source : htmlOption( DEV.capture.channels ) - } - var trdest = ` - - - - - `; - var trsource = ` - - SourceGainMuteInvert - - - - - - - `; - - if ( index === '' ) { + if ( index === 'dest' ) { var title = 'Add Destination'; info( { - icon : V.tab - , title : title - , content : ''+ trdest + trsource +'
    ' - , contentcssno : true - , values : [ MIX[ name ].mapping.length, 0, 0, false, false ] - , checkblank : true - , ok : () => { - var s = {} - $( '.trsource' ).find( 'select, input' ).each( ( i, el ) => { - var $this = $( el ) - s[ $this.data( 'k' ) ] = $this.is( ':checkbox' ) ? $this.prop( 'checked' ) : +$this.val(); - } ); + icon : V.tab + , title : title + , list : [ 'Playback channel', 'select', DEV.playback.channels ] + , boxwidth : 70 + , ok : () => { var mapping = { - dest : +$( '.trsource select' ).val() + dest : infoVal() , mute : false - , sources : [ s ] + , sources : [ + { + channel : 0 + , gain : 0 + , inverted : false + , mute : false + } + ] } MIX[ name ].mapping.push( mapping ); setting.save( title, 'Save ...' ); @@ -1120,46 +1295,82 @@ var setting = { } else { var title = 'Add Source'; info( { - icon : V.tab - , title : title - , content : ''+ trsource +'
    ' - , contentcssno : true - , values : [ 0, 0, false, false ] - , checkblank : true - , ok : () => { - var s = {} - $( '.trsource' ).find( 'select, input' ).each( ( i, el ) => { - var $this = $( el ) - s[ $this.data( 'k' ) ] = $this.is( ':checkbox' ) ? $this.prop( 'checked' ) : +$this.val(); - } ); - MIX[ name ].mapping[ index ].sources.push( s ); + icon : V.tab + , title : title + , list : [ 'Capture channel', 'select', DEV.capture.channels ] + , boxwidth : 70 + , ok : () => { + var source = { + channel : infoVal() + , gain : 0 + , inverted : false + , mute : false + } + MIX[ name ].mapping[ index ].sources.push( source ); setting.save( title, 'Save ...' ); render.mixersSub( name ); } } ); } - } //--------------------------------------------------------------------------------------------- + } //----------------------------------------------------------------------------------- + , processor : name => { + var type = name ? PRO[ name ].type : 'Compressor'; + var values = jsonClone( P.values[ type ] ); + if ( name ) { + $.each( PRO[ name ].parameters, ( k, v ) => values[ k ] = v ); + values.name = name; + } + var title = name ? 'Processor' : 'Add Processor' + info( { + icon : V.tab + , title : title + , list : name ? P[ PRO[ name ].type ] : P.Compressor + , boxwidth : 150 + , values : values + , checkblank : true + , checkchanged : name + , beforeshow : () => { + if ( name ) $( '#infoList select' ).eq( 0 ).prop( 'disabled', true ); + } + , ok : () => { + var val = infoVal(); + var typenew = val.type; + var namenew = val.name; + [ 'name', 'type' ].forEach( k => delete val[ k ] ); + [ 'monitor_channels', 'process_channels' ].forEach( k => val[ k ] = common.list2array( val[ k ] ) ); + if ( ! PRO ) { + S.config.processors = {} + PRO = S.config.processors; + } + PRO[ namenew ] = { type: v.type, parameters: val } + if ( name in PRO && name !== namenew ) delete PRO[ name ]; + setting.save( title, name ? 'Change ...' : 'Save ...' ); + render.processors(); + } + } ); + } //----------------------------------------------------------------------------------- , pipeline : () => { var filters = Object.keys( FIL ); info( { - icon : V.tab - , title : 'Add Pipeline' - , tablabel : [ ico( 'filters' ) +' Filter', ico( 'mixers' ) +' Mixer' ] - , tab : [ '', setting.pipelineMixer ] - , selectlabel : [ 'Channel', 'Filters' ] - , select : [ [ ...Array( DEV.playback.channels ).keys() ], filters ] - , beforeshow : () => { - $( '#infoContent .select2-container' ).eq( 0 ).addClass( 'channel' ) - $( '#infoContent td' ).last().append( ico( 'add' ) ); - var tradd = ''+ ico( 'remove' ) +''; - $( '#infoContent' ).on( 'click', '.i-add', function() { - $( '#infoContent table' ).append( tradd.replace( 'VALUE', $( '#infoContent select' ).eq( 1 ).val() ) ); + icon : V.tab + , title : 'Add Pipeline' + , tablabel : [ ico( 'filters' ) +' Filter', ico( 'mixers' ) +' Mixer' ] + , tab : [ '', setting.pipelineMixer ] + , list : [ + [ 'Channel', 'select', [ ...Array( DEV.playback.channels ).keys() ] ] + , [ 'Filters', 'select', filters, ico( 'add' ) ] + ] + , beforeshow : () => { + $( '#infoList .select2-container' ).eq( 0 ).attr( 'style', 'width: 70px !important' ); + var tradd = ' '+ ico( 'remove' ) +''; + $( '#infoList' ).on( 'click', '.i-add', function() { + $( '#infoList table' ).append( tradd.replace( 'VALUE', $( '#infoList select' ).eq( 1 ).val() ) ); } ).on( 'click', '.i-remove', function() { $( this ).parents( 'tr' ).remove(); } ); } - , ok : () => { - var $input = $( '#infoContent input' ); + , ok : () => { + var $input = $( '#infoList input' ); if ( $input.length ) { var names = []; $input.each( ( i, el ) => names.push( $( el ).val() ) ); @@ -1168,7 +1379,7 @@ var setting = { } PIP.push( { type : 'Filter' - , channel : +$( '#infoContent select' ).eq( 0 ).val() + , channel : +$( '#infoList select' ).eq( 0 ).val() , names : names } ); setting.pipelineSave(); @@ -1176,14 +1387,23 @@ var setting = { } ); } , pipelineMixer : () => { + if ( ! Object.keys( MIX ).length ) { + info( { + icon : V.tab + , title : 'Add Pipeline' + , message : 'No mixers found.' + , ok : setting.pipeline + } ); + return + } + info( { - icon : V.tab - , title : 'Add Pipeline' - , tablabel : [ ico( 'filters' ) +' Filter', ico( 'mixers' ) +' Mixer' ] - , tab : [ setting.pipeline, '' ] - , selectlabel : 'Mixers' - , select : Object.keys( MIX ) - , ok : () => { + icon : V.tab + , title : 'Add Pipeline' + , tablabel : [ ico( 'filters' ) +' Filter', ico( 'mixers' ) +' Mixer' ] + , tab : [ setting.pipeline, '' ] + , list : [ 'Mixers', 'select', Object.keys( MIX ) ] + , ok : () => { PIP.push( { type : 'Mixer' , name : infoVal() @@ -1196,94 +1416,41 @@ var setting = { setting.save( 'Add Pipeline', 'Save ...' ); render.pipeline(); } - , sortRefresh : ( k ) => { + , sortRefresh : k => { V.sortable[ k ].destroy(); delete V.sortable[ k ]; render.sortable( k ); - } //--------------------------------------------------------------------------------------------- + } //----------------------------------------------------------------------------------- , device : ( dev, type ) => { - var key_val, kv, k, v; - var data = jsonClone( DEV[ dev ] ); - var type = type || data.type; - // select - var selectlabel = [ 'type' ]; - var select = [ jsonClone( C.devicetype[ dev ] ) ]; - var values = { type: type } - key_val = jsonClone( CP[ dev ][ type ] ); - if ( 'select' in key_val ) { - kv = key_val.select; - k = Object.keys( kv ); - k.forEach( key => { - if ( key === 'format' ) { - var s = jsonClone( dev === 'capture' ? C.format : S.format ); - var v = { format: data.format }; - } else if ( key === 'device' ) { - var s = jsonClone( C.devices[ dev ] ); - var v = { device: data.device }; - } else if ( key === 'filename' ) { - var s = S.lscoef.length ? S.lscoeffs : [ '(n/a)' ]; - var v = { filename: data.filename }; - } - selectlabel = [ ...selectlabel, key ]; - select = [ ...select, s ]; - values = { ...values, ...v }; - } ); - } - selectlabel = util.labels2array( selectlabel ); - // text - var textlabel = false; - if ( 'text' in key_val ) { - kv = key_val.text; - k = Object.keys( kv ); - textlabel = util.labels2array( k ); - k.forEach( key => { - if ( key in data ) kv[ key ] = data[ key ]; - } ); - values = { ...values, ...kv }; - } - // number - var numberlabel = false; - if ( 'number' in key_val ) { - kv = key_val.number; - k = Object.keys( kv ); - numberlabel = util.labels2array( k ); - k.forEach( key => { - if ( key in data ) kv[ key ] = data[ key ]; - } ); - values = { ...values, ...kv }; - } - // checkbox - var checkbox = false; - if ( 'checkbox' in key_val ) { - kv = key_val.checkbox; - k = Object.keys( kv ); - checkbox = util.labels2array( k ); - k.forEach( key => { - if ( key in data ) kv[ key ] = data[ key ]; - } ); - values = { ...values, ...kv }; - } - $.each( v, ( k, v ) => values[ k ] = v ); - var title = util.key2label( dev ); + var type = type || 'Alsa'; + var vtype = type === 'File' && dev === 'playback' ? 'FileP' : type; + var values = jsonClone( D.values[ vtype ] ); + values.type = type; + if ( DEV[ dev ].type === type ) $.each( values, ( k, v ) => values[ k ] = DEV[ dev ][ k ] ); + var title = common.key2label( dev ); info( { icon : V.tab , title : title - , selectlabel : selectlabel - , select : select - , textlabel : textlabel - , numberlabel : numberlabel - , checkbox : checkbox + , list : D[ dev ][ type ] , boxwidth : 198 - , order : [ 'select', 'text', 'number', 'checkbox' ] , values : values , checkblank : true - , checkchanged : type === data.type + , checkchanged : true , beforeshow : () => { - $( '#infoContent input[type=number]' ).css( 'width', '70px' ); - $( '#infoContent td:first-child' ).css( 'width', '128px' ); - var $select = $( '#infoContent select' ); - $select.eq( 0 ).on( 'input', function() { - setting.device( dev, $( this ).val() ); + $( '#infoList input[type=number]' ).css( 'width', '70px' ); + $( '#infoList td:first-child' ).css( 'width', '128px' ); + $( '#infoList select' ).eq( 0 ).on( 'input', function() { + var typenew = $( this ).val(); + if ( typenew === 'File' && ! S.lsraw.length ) { + info( { + icon : V.tab + , title : title + , message : 'No raw files available.' + , ok : () => setting.device( dev, type ) + } ); + } else { + setting.device( dev, typenew ); + } } ); } , ok : () => { @@ -1292,31 +1459,30 @@ var setting = { } } ); } - , devicesampling : () => { - var textlabel = [ ...C.sampling ].slice( 1 ); - textlabel.push( 'Other' ); - var values = {}; - C.sampling.forEach( k => values[ k ] = DEV[ k ] ); - if ( ! C.samplerate.includes( DEV.samplerate ) ) values.samplerate = 'Other'; - values.other = values.samplerate; - var title = util.key2label( V.tab ); + , main : () => { + var values = {}; + D0.main.forEach( k => { + values[ k ] = DEV[ k ]; + if ( k === 'samplerate' ) values.other = DEV.samplerate; + } ); + if ( ! D0.samplerate.includes( DEV.samplerate ) ) values.samplerate = 'Other'; + var title = common.tabTitle(); info( { icon : V.tab , title : title - , selectlabel : 'Sample Rate' - , select : C.samplerate - , textlabel : util.labels2array( textlabel ) + , list : D.main , boxwidth : 120 - , order : [ 'select', 'text' ] , values : values , checkblank : true , checkchanged : true , beforeshow : () => { - $( '.trselect' ).after( $( 'tr' ).last() ); - var $trother = $( '.trtext' ).eq( 0 ); + var $trother = $( '#infoList tr' ).eq( 1 ); $trother.toggleClass( 'hide', values.samplerate !== 'Other' ); - $( '.trselect select' ).on( 'input', function() { - setting.hidetrinfo( $trother, $( this ).val() ); + $( '#infoList select' ).on( 'input', function() { + var rate = $( this ).val(); + var other = rate === 'Other'; + $trother.toggleClass( 'hide', ! other ); + if ( ! other ) $trother.find( 'input' ).val( rate ); } ); } , ok : () => { @@ -1328,103 +1494,158 @@ var setting = { render.devices(); } } ); - } //--------------------------------------------------------------------------------------------- - , resampling : ( freeasync ) => { - var rateadjust = DEV.enable_rate_adjust; - var samplerate = DEV.capture_samplerate; - var selectlabel = [ 'Resampler type', 'Capture samplerate' ]; - var select = [ rateadjust ? C.sampletype.slice( 0, -1 ) : C.sampletype, C.samplerate ]; - var numberlabel = [ 'Other' ]; - var capturerate = C.samplerate.includes( samplerate ) ? samplerate : 'Other'; - if ( freeasync ) { - selectlabel.push( 'interpolation', 'window' ); - select.push( C.freeasync.interpolation, C.freeasync.window ); - numberlabel.push( 'Sinc length', 'Oversampling ratio', 'Frequency cutoff' ); - var f = jsonClone( DEV.resampler_type.FreeAsync ) || {}; - var values = { - resampler_type : 'FreeAsync' - , capture_samplerate : capturerate - , interpolation : f.interpolation || 'Linear' - , window : f.window || 'Blackman2' - , other : capturerate - , sinc_len : f.sinc_len || 128 - , oversampling_ratio : f.oversampling_ratio || 1024 - , f_cutoff : f.f_cutoff || 0.925 - } - } else { - var values = { - resampler_type : rateadjust && DEV.resampler_type === 'Synchronous' ? 'BalancedAsync' : DEV.resampler_type - , capture_samplerate : capturerate - , other : capturerate - } - } + } //----------------------------------------------------------------------------------- + , resampler : ( type, profile ) => { + var Dtype = D.resampler[ type ]; + var values = D.resampler.values[ type ]; + if (profile ) values.profile = profile; + if ( S.resampler ) DEV.resample.each( ( k, v ) => values[ k ] = v ); + values.capture_samplerate = DEV.capture_samplerate || DEV.samplerate; info( { icon : V.tab , title : SW.title - , selectlabel : selectlabel - , select : select - , numberlabel : numberlabel + , list : Dtype , boxwidth : 160 - , order : [ 'select', 'number' ] , values : values - , checkchanged : DEV.enable_resampling + , checkblank : true + , checkchanged : S.resampler , beforeshow : () => { - var $trnumber = $( '.trnumber' ); - var $trother = $trnumber.eq( 0 ); - var indextr = freeasync ? [ 2, 1, 0 ] : [ 0 ] - indextr.forEach( i => $( '.trselect' ).eq( 1 ).after( $trnumber.eq( i ) ) ); - $trother.toggleClass( 'hide', values.capture_samplerate !== 'Other' ); - $( '.trselect select' ).eq( 0 ).on( 'input', function() { - if ( $( this ).val() === 'FreeAsync' ) { - setting.resampling( 'freeasync' ); - } else if ( $trnumber.length > 1 ) { - setting.resampling(); - } - } ); - $( '.trselect select' ).eq( 1 ).on( 'input', function() { - setting.hidetrinfo( $trother, $( this ).val() ); + $( 'select' ).eq( 0 ).on( 'input', function() { + setting.resampler( $( this ).val() ); } ); + if ( values.type === 'AsyncSinc' ) { + $( 'select' ).eq( 1 ).on( 'input', function() { + var profile = $( this ).val(); + if ( type === 'Custom' ) { + setting.resampler( 'AsyncSinc', profile ); + } else { + if ( profile === 'Custom' ) setting.resampler( 'Custom' ); + } + } ); + } } , cancel : switchCancel , ok : () => { var val = infoVal(); - if ( val.capture_samplerate === 'Other' ) val.capture_samplerate = val.other; - [ 'resampler_type', 'capture_samplerate' ].forEach( k => DEV[ k ] = val[ k ] ); - if ( freeasync ) { - var v = {} - C.freeasync.keys.forEach( k => v[ k ] = val[ k ] ); - DEV.resampler_type = { FreeAsync: v } - } - setting.switchSave( 'enable_resampling' ); + DEV.capture_samplerate = val.capture_samplerate === DEV.samplerate ? null : val.capture_samplerate; + delete val.capture_samplerate; + if ( val.type === 'Synchronous' && S.enable_rate_adjust ) DEV.enable_rate_adjust = false; + DEV.resampler = val; + setting.switchSave( 'resampler' ); } } ); - } //--------------------------------------------------------------------------------------------- - , hidetrinfo : ( $trother, rate ) => { - var other = rate === 'Other'; - $trother.toggleClass( 'hide', ! other ); - if ( ! other ) $trother.find( 'input' ).val( rate ); + } //----------------------------------------------------------------------------------- + , mixerGet : $this => { + var $li = $this.parents( 'li' ); + return { + $li : $li + , name : $li.data( 'name' ) + , index : $li.hasClass( 'lihead' ) ? 'dest' : $li.data( 'index' ) + , si : $li.data( 'si' ) + , checked : ! ( $this.hasClass( 'bl' ) || $this.hasClass( 'mute' ) ) + } + } + , muteToggle : ( $this, checked ) => { + $this.toggleClass( 'mute', checked ); + $this.siblings( 'input' ).prop( 'disabled', checked ); + $this.parent().find( '.i-minus, .i-plus, .db' ).toggleClass( 'disabled', checked ); + } + , rangeGet : ( $this, type ) => { + var input = type === 'input'; + var $li = $this.parents( 'li' ); + var $gain = input ? $this : $this.siblings( 'input' ); + R = { + $gain : $gain + , $db : $li.find( 'c' ) + , val : +$gain.val() + , cmd : input ? '' : $this.prop( 'class' ).replace( 'i-', '' ) + , up : input ? '' : $this.hasClass( 'i-plus' ) + , scale : $gain.data( 'scale' ) + , name : $li.data( 'name' ) + , index : $li.data( 'index' ) + , si : $li.data( 'si' ) + } + if ( input ) { + setting.rangeSet(); + } else if ( type === 'press' ) { + V.intervalgain = setInterval( () => { + R.up ? R.val++ : R.val--; + setting.rangeSet(); + }, 100 ); + } else { + switch ( R.cmd ) { + case 'minus': + R.val--; + break; + case 'plus': + R.val++; + break; + default: // db + R.val = 0; + } + setting.rangeSet(); + } } - , switchSave : ( id, disable ) => { - if ( disable === 'disable' ) { - var msg = 'Disable ...'; - DEV[ id ] = false; + , rangeSet : () => { + if ( R.val === -100 || R.val === 100 ) clearTimeout( V.timeoutgain ); + R.$gain.val( R.val ); + if ( R.scale === 100 ) { // filter - Gain dB / mixer - dB + var val = R.val; + var db = R.val; } else { - var msg = DEV[ id ] ? 'Change ...' : 'Enable ...'; - DEV[ id ] = true; + var val = R.val / 10; + var db = val.toFixed( 1 ); + } + R.$db + .text( db ) + .toggleClass( 'disabled', R.val === 0 ); + if ( V.tab === 'filters' ) { + FIL[ R.name ].parameters.gain = val; + } else { + MIX[ R.name ].mapping[ R.index ].sources[ R.si ].gain = val; + } + setting.save(); + } + , scaleSet : ( checked, key, $this ) => { + var $db = $this.siblings( '.db' ); + if ( checked ) { + var gain = key.gain / 10; + key.scale = 'linear'; + key.gain = gain; + $db.text( gain.toFixed( 1 ) ); + } else { + var gain = key.gain * 10; + key.scale = 'dB'; + key.gain = gain; + $db.text( gain ); } - setting.save( SW.title, msg ); - render.devices(); } , save : ( titlle, msg ) => { setTimeout( () => { var config = JSON.stringify( S.config ).replace( /"/g, '\\"' ); wscamilla.send( '{ "SetConfigJson": "'+ config +'" }' ); - wscamilla.send( '"Reload"' ); - setTimeout( util.save2file, 300 ); + if ( ! V.press ) setting.save2file(); }, wscamilla ? 0 : 300 ); - util.webSocket(); // websocket migth be closed by setting.filter() if ( msg ) banner( V.tab, titlle, msg ); } + , save2file : () => { + clearTimeout( V.timeoutsave ); + V.timeoutsave = setTimeout( () => { + local(); + bash( [ 'saveconfig' ] ); + }, 1000 ); + } + , switchSave : ( id, disable ) => { + if ( disable === 'disable' ) { + var msg = 'Disable ...'; + DEV[ id ] = null; + } else { + var msg = DEV[ id ] ? 'Change ...' : 'Enable ...'; + DEV[ id ] = true; + } + setting.save( SW.title, msg ); + render.devices(); + } , upload : () => { var filters = V.tab === 'filters'; var title = 'Add File'; @@ -1436,15 +1657,12 @@ var setting = { var message = 'Upload configuration file:' } info( { - icon : V.tab - , title : title - , message : message - , fileoklabel : ico( 'file' ) +'Upload' - , filetype : dir === 'coeffs' ? '.dbl,.pcm,.raw,.wav' : '.yml' - , cancel : () => { - util.webSocket(); - } - , ok : () => { + icon : V.tab + , title : title + , message : message + , file : { oklabel: ico( 'file' ) +'Upload', type: dir === 'coeffs' ? '.dbl,.pcm,.raw,.wav' : '.yml' } + , cancel : common.webSocket + , ok : () => { notify( V.tab, title, 'Upload ...' ); var formdata = new FormData(); formdata.append( 'cmd', 'camilla' ); @@ -1458,27 +1676,13 @@ var setting = { infoWarning( V.tab, title, message ); } } ); - util.webSocket(); + common.webSocket(); } } ); } } -var util = { - db2percent : ( v ) => { - var value = 0; - if ( v >= -12 ) { - value = 81.25 + 12.5 * v / 6 - } else if ( v >= -24 ) { - value = 68.75 + 12.5 * v / 12 - } else { - value = 56.25 + 12.5 * v / 24 - } - return value < 0 ? 0 : ( value > 100 ? 100 : value ) - } - , dbRound : ( num ) => { - return Math.round( num * 10 ) / 10 - } - , inUse : ( name ) => { +var common = { + inUse : name => { var filters = V.tab === 'filters'; var inuse = []; if ( filters && ! ( name in FIL ) ) { // file @@ -1512,7 +1716,7 @@ var util = { return false } - , key2label : ( key ) => { + , key2label : key => { if ( key === 'ms' ) return 'ms' var str = key[ 0 ].toUpperCase(); @@ -1531,13 +1735,16 @@ var util = { .slice( 1 ) return str + key } - , labels2array : ( array ) => { - var capitalized = array.map( el => util.key2label( el ) ); + , labels2array : array => { + if ( ! array ) return false + + var capitalized = array.map( el => common.key2label( el ) ); return capitalized } - , save2file : () => { - bash( [ 'saveconfig' ] ); + , list2array : list => { // '1, 2, 3' > [ 1, 2, 3 ] + return list.replace( /[ \]\[]/g, '' ).split( ',' ).map( Number ) } + , tabTitle : () => V.tab[ 0 ].toUpperCase() + V.tab.slice( 1 ) , volume : ( pageX, type ) => { var bandW = $( '#volume .slide' ).width(); if ( V.start ) { @@ -1550,26 +1757,33 @@ var util = { } if ( V.drag ) { S.volume = vol; - util.volumeThumb(); + common.volumeThumb(); + local(); volumeSetAt(); + render.volume(); } else { var diff = V.dragpress ? 3 : Math.abs( vol - S.volume ); - $( '#volume, #divvolume .divgain i' ).addClass( 'disabled' ); + $master.addClass( 'noclick' ); $( '#volume .thumb' ).animate( { 'margin-left': posX } , { duration : diff * 40 , easing : 'linear' - , complete : () => $( '#volume, #divvolume .divgain i' ).removeClass( 'disabled' ) + , complete : () => { + $master + .removeClass( 'noclick' ) + .toggleClass( 'disabled', S.volumemute > 0 ); + render.volume(); + } } ); S.volume = vol; if ( ! type ) { // not from push + volumeSetAt(); volumePush( vol ); volumeSetAt(); } } - render.volume(); } , volumeThumb : () => { $( '#volume .thumb' ).css( 'margin-left', $( '#volume .slide' ).width() / 100 * S.volume ); @@ -1577,12 +1791,22 @@ var util = { , webSocket : () => { if ( wscamilla && wscamilla.readyState < 2 ) return + var cmd_el = { + GetBufferLevel : 'buffer' + , GetCaptureRate : 'capture' + , GetClippedSamples : 'clipped' + , GetProcessingLoad : 'load' + , GetRateAdjust : 'rate' + } wscamilla = new WebSocket( 'ws://'+ window.location.host +':1234' ); wscamilla.onready = () => { // custom - util.wsGetConfig(); - S.status = { GetState: ' '+ blinkdot } + common.wsGetState(); + common.wsGetConfig(); V.intervalstatus = setInterval( () => { - if ( ! V.local ) V.statusget.forEach( k => wscamilla.send( '"'+ k +'"' ) ); + if ( V.local ) return + + common.wsGetState(); + if ( S.enable_rate_adjust ) wscamilla.send( '"GetRateAdjust"' ); }, 1000 ); } wscamilla.onopen = () => { @@ -1591,119 +1815,93 @@ var util = { wscamilla.onclose = () => { render.vuClear(); clearInterval( V.intervalstatus ); - util.save2file(); - $( '#divstate .value' ).html( ' · · ·' ); } wscamilla.onmessage = response => { var data = JSON.parse( response.data ); var cmd = Object.keys( data )[ 0 ]; var value = data[ cmd ].value; - var cl, cp, css, v; + var cp, p, v; switch ( cmd ) { - case 'GetCaptureSignalPeak': - case 'GetCaptureSignalRms': - case 'GetPlaybackSignalPeak': - case 'GetPlaybackSignalRms': - cp = cmd[ 3 ]; - if ( cmd.slice( -1 ) === 'k' ) { - if ( V.clipped ) break; - - cl = '.peak'; - css = 'left'; - V[ cmd ] = value; - V[ cp ] = []; - } else { - cl = '.rms' - css = 'width'; - } - value.forEach( ( v, i ) => { - v = S.volumemute && cp === 'P' ? 0 : util.db2percent( v ); - V[ cp ].push( v ); - $( '.'+ cmd[ 3 ] + i + cl ).css( css, v +'%' ); - } ); - break; - case 'GetClippedSamples': - if ( ! ( 'status' in S ) ) return - - if ( V.clipped ) { - S.status.GetClippedSamples = value; - break; + case 'GetSignalLevels': + if ( S.state !== 'play' ) { + render.vuClear(); + return } - if ( value > S.status.GetClippedSamples ) { - V.clipped = true; - clearTimeout( V.timeoutclipped ); - $( '.peak, .clipped' ) - .css( 'transition-duration', 0 ) - .addClass( 'red' ); - V.timeoutclipped = setTimeout( () => { - V.clipped = false; - $( '.peak, .clipped' ) - .removeClass( 'red' ) - .css( 'transition-duration', '' ); - // set clipped value to previous peak - [ 'C', 'P' ].forEach( ( k, i ) => $( '.peak.'+ k + i ).css( 'left', util.db2percent( V[ k ][ i ] ) +'%' ) ); - }, 1000 ); + if ( ! V.signal ) { // restore after 1st set + V.signal = true; + $( '.peak' ).css( 'width', '3px' ); + $( '.rms' ).css( 'transition-duration', '' ); + setTimeout( () => $( '.peak' ).css( 'transition-duration', '' ), 200 ); } - S.status.GetClippedSamples = value; + [ 'playback_peak', 'playback_rms', 'capture_peak', 'capture_rms' ].forEach( k => { + cp = k[ 0 ]; + value[ k ].forEach( ( db, i ) => { + if ( S.volumemute && cp === 'p' ) db = -99; + render.vuLevel( k.slice( -1 ) === 's', cp + i, db ); + } ); + } ); break; - case 'GetState': - case 'GetCaptureRate': case 'GetBufferLevel': + case 'GetCaptureRate': + case 'GetProcessingLoad': case 'GetRateAdjust': - if ( ! ( 'status' in S ) ) S.status = { GetState: blinkdot } - if ( cmd === 'GetState' ) { - if ( value !== 'Running' ) { - render.vuClear(); - if ( S.status.GetState !== value ) { - S.status.GetState = value; - render.statusValue(); - } - } else { - S.status.GetState = value; - if ( ! ( 'intervalvu' in V ) ) { - $( '.peak' ).css( 'background', '' ); - render.vu(); - } - } + if ( S.state !== 'play' ) { + render.vuClear(); + return + } + + v = cmd === 'GetProcessingLoad' ? value.toLocaleString( undefined, { minimumFractionDigits: 3 } ) : value.toLocaleString(); + $( '#divstate .'+ cmd_el[ cmd ] ).text( v ); + break; + case 'GetClippedSamples': + if ( V.local ) return + + if ( value ) { + $( '.divclipped' ) + .removeClass( 'hide' ) + .find( '.clipped' ).text( value.toLocaleString() ); } else { - S.status[ cmd ] = value; - if ( cmd === V.statuslast ) render.statusValue(); + $( '.divclipped' ).addClass( 'hide' ); } break; - // config + case 'GetState': + if ( 'intervalvu' in V || S.state !== 'play' ) return + + V.intervalvu = setInterval( () => wscamilla.send( '"GetSignalLevels"' ), 100 ); + break; case 'GetConfigJson': S.config = JSON.parse( value ); - DEV = S.config.devices; - FIL = S.config.filters; - MIX = S.config.mixers; - PIP = S.config.pipeline; - [ 'enable_rate_adjust', 'enable_resampling', 'stop_on_rate_change' ].forEach( k => S[ k ] = DEV[ k ] ); + DEV = S.config.devices; + FIL = S.config.filters; + MIX = S.config.mixers; + PIP = S.config.pipeline; + PRO = S.config.processors; + [ 'capture_samplerate', 'enable_rate_adjust', 'resampler', 'stop_on_rate_change' ].forEach( k => { + S[ k ] = ! [ null, false ].includes( DEV[ k ] ); + } ); + if ( ! $( '#data' ).hasClass( 'hide' ) ) $( '#data' ).html( highlightJSON( S ) ); render.page(); render.tab(); break; - case 'GetConfigName': + case 'GetConfigFilePath': S.configname = value.split( '/' ).pop(); break; case 'GetSupportedDeviceTypes': - S.devicetype = { - capture : value[ 1 ].sort() - , playback : value[ 0 ].sort() - }; - [ 'devices', 'devicetype' ].forEach( k => C[ k ] = { capture: {}, playback: {} } ); - [ 'capture', 'playback' ].forEach( k => { - S.devices[ k ].forEach( d => { - v = d.replace( /bluealsa|Bluez/, 'BlueALSA' ); - C.devices[ k ][ v ] = d; - } ); - S.devicetype[ k ].forEach( t => { + var type = {}; + [ 'playback', 'capture' ].forEach( ( k, i ) => { + type[ k ] = {}; + value[ i ].forEach( t => { v = render.typeReplace( t ); - C.devicetype[ k ][ v ] = t; // [ 'Alsa', 'Bluez' 'CoreAudio', 'Pulse', 'Wasapi', 'Jack', 'Stdin/Stdout', 'File' ] + type[ k ][ v ] = t; // [ 'Alsa', 'Bluez' 'CoreAudio', 'Pulse', 'Wasapi', 'Jack', 'Stdin/Stdout', 'File' ] } ); } ); + Dlist.typeC[ 2 ] = type.capture; + Dlist.typeP[ 2 ] = type.playback; + Dlist.deviceC[ 2 ] = S.devices.capture; + Dlist.deviceP[ 2 ] = S.devices.playback; + $( '#divvolume .col-l gr' ).text( S.control ); showContent(); - render.status(); - render.tab(); break; case 'Invalid': info( { @@ -1717,134 +1915,124 @@ var util = { } , wsGetConfig : () => { setTimeout( () => { - [ 'GetConfigName', 'GetConfigJson', 'GetSupportedDeviceTypes' ].forEach( cmd => wscamilla.send( '"'+ cmd +'"' ) ); + [ 'GetConfigFilePath', 'GetConfigJson', 'GetSupportedDeviceTypes' ].forEach( cmd => wscamilla.send( '"'+ cmd +'"' ) ); }, wscamilla.readyState === 1 ? 0 : 300 ); } + , wsGetState : () => { + [ 'GetState', 'GetBufferLevel', 'GetCaptureRate', 'GetClippedSamples', 'GetProcessingLoad' ].forEach( k => { + wscamilla.send( '"'+ k +'"' ); + } ); + } } $( function() { // document ready start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -$( '.close' ).on( 'click', function() { - util.save2file(); -} ); +// volume --------------------------------------------------------------------------------- $( '#volume' ).on( 'touchstart mousedown', function( e ) { V.start = true; } ).on( 'touchmove mousemove', function( e ) { if ( ! V.start ) return V.drag = true; - util.volume( e.pageX || e.changedTouches[ 0 ].pageX ); + common.volume( e.pageX || e.changedTouches[ 0 ].pageX ); } ).on( 'touchend mouseup', function( e ) { if ( ! V.start ) return - V.drag ? volumePush() : util.volume( e.pageX || e.changedTouches[ 0 ].pageX ); + V.drag ? volumePush() : common.volume( e.pageX || e.changedTouches[ 0 ].pageX ); V.start = false; setTimeout( () => V.drag = false, 1000 ); } ).on( 'mouseleave', function() { if ( V.start ) $( '#volume' ).trigger( 'mouseup' ); } ); -$( '#voldn, #volup' ).on( 'click', function() { - var up = this.id === 'volup'; +$( '#divvolume' ).on( 'click', '.i-minus, .i-plus', function() { + var up = $( this ).hasClass( 'i-plus' ); if ( ( ! up && S.volume === 0 ) || ( up && S.volume === 100 ) ) return up ? S.volume++ : S.volume--; + render.volume(); volumePush( S.volume ); volumeSetAt(); - $( '#volume-text' ).text( S.volume ); -} ).on( 'touchend mouseup', function() { +} ).on( 'touchend mouseup mouseleave', function() { + if ( ! V.press ) return + clearInterval( V.intervalvolume ); volumePush(); -} ).on( 'mouseleave', function() { - if ( V.press ) $( '#voldn' ).trigger( 'mouseup' ); -} ).press( function( e ) { - var up = e.target.id === 'volup'; +} ).press( '.i-minus, .i-plus', function( e ) { + var up = $( e.target ).hasClass( 'i-plus' ); V.intervalvolume = setInterval( () => { up ? S.volume++ : S.volume--; volumeSetAt(); - util.volumeThumb(); - $( '#volume-text' ).text( S.volume ); + common.volumeThumb(); + $( '#divvolume .level' ).text( S.volume ); if ( S.volume === 0 || S.volume === 100 ) { clearInterval( V.intervalvolume ); volumePush(); } }, 100 ); -} ); -$( '#volmute' ).on( 'click', function() { +} ).on( 'click', '.i-volume, .db', function() { S.volumemute ? volumePush( S.volumemute, 'unmute' ) : volumePush( S.volume, 'mute' ); volumeSet( S.volumemute, 'toggle' ); + $( '#out .peak' ).css( 'transition-duration', '0s' ); + setTimeout( () => $( '#out .peak' ).css( 'transition-duration', '' ), 100 ); + } ); -$( '#filters, #mixers' ).on( 'click', '.divgain i', function() { - var $this = $( this ); - if ( $this.parent().hasClass( 'disabled' ) ) return +// common --------------------------------------------------------------------------------- +$( '.entries' ).on( 'click', '.i-minus, .i-plus, .db', function() { // filters, mixersSub + setting.rangeGet( $( this ), 'click' ); +} ).on( 'touchend mouseup mouseleave', '.i-minus, .i-plus, .db', function() { + if ( ! V.press ) return - clearTimeout( V.timeoutgain ); - var $gain = $this.parent().prev(); - var $db = $gain.prev(); - var val = +$gain.val(); - if ( $this.hasClass( 'i-set0' ) ) { - if ( val === 0 ) return - - val = 0; - } else if ( $this.hasClass( 'i-minus' ) ) { - if ( val === $gain.prop( 'min' ) ) return - - val -= 0.1; - } else if ( $this.hasClass( 'i-plus' ) ) { - if ( val === $gain.prop( 'max' ) ) return - - val += 0.1; - } - $gain - .val( val ) - .trigger( 'input' ); - if ( V.li.find( '.divgraph' ).length || $( '#pipeline .divgraph' ).length ) graph.gain(); -} ).on( 'touchend mouseup mouseleave', function() { clearInterval( V.intervalgain ); -} ).press( '.divgain i', function( e ) { - var $this = $( e.currentTarget ); - var $gain = $this.parent().prev(); - var val = +$gain.val(); - var up = $this.hasClass( 'i-plus' ); - V.intervalgain = setInterval( () => { - val = up ? val + 0.1 : val - 0.1; - $gain.val( val ).trigger( 'input' ); - }, 100 ); + if ( $( this ).parents( 'li' ).find( '.divgraph' ).length || $( '#pipeline .divgraph' ).length ) graph.gain(); + setting.save2file(); +} ).press( '.i-minus, .i-plus', function( e ) { + setting.rangeGet( $( e.currentTarget ), 'press' ); } ); $( '#divstate' ).on( 'click', '.clipped', function() { - S.clipped = S.status.GetClippedSamples; - bash( [ 'clippedreset', S.clipped, 'CMD CLIPPED' ] ); - render.status(); + local( 2000 ); + $( '.divclipped' ).addClass( 'hide' ); + wscamilla.send( '"ResetClippedSamples"' ); } ); $( '#configuration' ).on( 'input', function() { if ( V.local ) return var name = $( this ).val(); - bash( [ 'confswitch', name, 'CMD NAME' ], () => { - wscamilla.send( '{ "SetConfigName": "/srv/http/data/camilladsp/configs/'+ name +'" }' ); + var path = '/srv/http/data/camilladsp/configs'+ ( S.bluetooth ? '-bt/' : '/' ) + name; + bash( [ 'confswitch', path, 'CMD PATH' ], () => { + wscamilla.send( '{ "SetConfigFilePath": "'+ path +'" }' ); wscamilla.send( '"Reload"' ); S.configname = name; - setTimeout( () => util.wsGetConfig(), 300 ); + setTimeout( () => common.wsGetConfig(), 300 ); } ); notify( 'camilladsp', 'Configuration', 'Switch ...' ); - V.graph = { filters: {}, pipeline: {} } } ); $( '#setting-configuration' ).on( 'click', function() { - $( '#tabconfig' ).trigger( 'click' ); + if ( $( '#divconfig' ).hasClass( 'hide' ) ) { + V.tabprev = V.tab; + $( '#tabconfig' ).trigger( 'click' ); + } else { + $( '#tab'+ V.tabprev ).trigger( 'click' ); + } +} ); +$( '.tab' ).on( 'click', '.graphclose', function() { + var $this = $( this ); + var $li = $this.parents( 'li' ); + $this.parent().remove(); + var val = $li.data( V.tab === 'filters' ? 'name' : 'index' ); + V.graph[ V.tab ] = V.graph[ V.tab ].filter( v => v !== val ); } ); -$( '#divtabs' ).on( 'click', '.graphclose', function() { - $( this ).parent().addClass( 'hide' ); +$( '.tab .headtitle' ).on( 'click', function() { + if ( $( '#'+ V.tab +' .entries.main' ).hasClass( 'hide' ) ) $( '#'+ V.tab +' .i-back' ).trigger( 'click' ); } ); -$( '.headtitle' ).on( 'click', '.i-folder-filter', function() { +$( 'heading' ).on( 'click', '.i-folder-filter', function() { render.filtersSub(); } ).on( 'click', '.i-add', function() { if ( V.tab === 'filters' ) { setting.filter( 'Biquad', 'Lowpass' ); - } else if ( V.tab === 'mixers' ) { - setting.mixer(); - } else if ( V.tab === 'pipeline' ) { - setting.pipeline(); } else if ( V.tab === 'config' ) { setting.upload(); + } else { + setting[ V.tab.replace( /s$/, '' ) ](); } } ).on( 'click', '.i-flowchart', function() { var $flowchart = $( '.flowchart' ); @@ -1861,48 +2049,9 @@ $( '.headtitle' ).on( 'click', '.i-folder-filter', function() { } else { $flowchart.addClass( 'hide' ); } -} ).on( 'click', '.i-gear', function() { - if ( V.tab === 'devices' ) { - setting.devicesampling(); - return - } - - var TAB = V.tab.toUpperCase(); - var values = {}; - [ 'MAX', 'MIN' ].forEach( k => values[ TAB + k ] = S.range[ TAB + k ] ); - info( { - icon : V.tab - , title : 'Gain Slider Range' - , numberlabel : [ 'Max', 'Min' ] - , footer : '(50 ... -50)' - , boxwidth : 110 - , values : values - , beforeshow : () => { - var $input = $( '#infoContent input' ); - var $max = $input.eq( 0 ); - var $min = $input.eq( 1 ); - $( '#infoContent' ).on( 'blur', 'input', function() { - if ( $max.val() > 50 ) { - $max.val( 50 ); - } else if ( $max.val() < 0 ) { - $max.val( 0 ); - } - if ( $min.val() < -50 ) { - $min.val( -50 ); - } else if ( $min.val() > 0 ) { - $min.val( 0 ); - } - } ); - } - , ok : () => { - var val = infoVal(); - [ 'MAX', 'MIN' ].forEach( k => S.range[ TAB + k ] = val[ TAB + k ] ); - $( '#'+ V.tab +' input[type=range]' ).prop( { min: S.range[ TAB +'MIN' ], max: S.range[ TAB +'MAX' ] } ); - bash( [ 'camilla', ...Object.values( S.range ), 'CFG '+ Object.keys( S.range ).join( ' ' ) ] ); - } - } ); } ); -$( '.entries' ).on( 'click', '.liicon', function() { +$( '.entries' ).on( 'click', '.liicon', function( e ) { + e.stopPropagation(); var $this = $( this ); V.li = $this.parent(); var active = V.li.hasClass( 'active' ); @@ -1920,15 +2069,16 @@ $( '.entries' ).on( 'click', '.liicon', function() { var name = $( '#mixers .lihead' ).text(); if ( ! MIX[ name ].mapping.length ) { // no mapping left delete MIX[ name ]; - setting.save( title, 'Remove ...' ); + setting.save( 'Mixer', 'Remove ...' ); } } else if ( V.tab === 'pipeline' ) { if ( ! $( '#pipeline .i-filters' ).length ) { var pi = $( '#pipeline .lihead' ).data( 'index' ); PIP.splice( pi, 1 ); - setting.save( title, 'Remove filter ...' ); + setting.save( 'Pipeline', 'Remove filter ...' ); } } + $( '#'+ V.tab +' .entries.main' ).removeClass( 'hide' ); render[ V.tab ](); } ); $( 'body' ).on( 'click', function( e ) { @@ -1941,7 +2091,7 @@ $( '#menu a' ).on( 'click', function( e ) { var $this = $( this ); var cmd = $this.prop( 'class' ); if ( cmd === 'graph' ) { - graph.toggle(); + graph.plot(); return } @@ -1957,7 +2107,7 @@ $( '#menu a' ).on( 'click', function( e ) { icon : V.tab , title : title , message : 'Rename '+ name +' to:' - , textlabel : 'Name' + , list : [ 'Name', 'text' ] , values : name , checkblank : true , checkchanged : true @@ -1977,7 +2127,7 @@ $( '#menu a' ).on( 'click', function( e ) { } break; case 'delete': - if ( util.inUse( name ) ) return + if ( common.inUse( name ) ) return info( { icon : V.tab @@ -1995,7 +2145,6 @@ $( '#menu a' ).on( 'click', function( e ) { } break; case 'mixers': - var title = 'Mixer'; var name = V.li.data( 'name' ); var main = $( '#mixers .entries.sub' ).hasClass( 'hide' ); switch ( cmd ) { @@ -2005,22 +2154,27 @@ $( '#menu a' ).on( 'click', function( e ) { case 'delete': var dest = V.li.hasClass( 'liinput main' ); if ( main ) { - if ( util.inUse( name ) ) return + if ( common.inUse( name ) ) return + var title = 'Mixer'; var msg = name; } else if ( dest ) { - var mi = V.li.data( 'index' ); - var msg = 'output #'+ mi; + var mi = V.li.data( 'index' ); + var title = 'Destination'; + var msg = '#'+ mi; } else { - var mi = V.li.siblings( '.main' ).data( 'index' ); - var si = V.li.data( 'index' ); - var msg = 'input #'+ si; + var mi = V.li.siblings( '.main' ).data( 'index' ); + var title = 'Source'; + var si = V.li.data( 'index' ); + var msg = '#'+ si; } var message = 'Delete '+ msg +' ?'; info( { icon : V.tab , title : title , message : message + , oklabel : ico( 'remove' ) +'Delete' + , okcolor : red , ok : () => { if ( main ) { delete MIX[ name ]; @@ -2042,12 +2196,34 @@ $( '#menu a' ).on( 'click', function( e ) { break; } break; + case 'processors': + var title = 'Processors'; + var name = V.li.data( 'name' ); + switch ( cmd ) { + case 'edit': + setting.processor( name ); + break; + case 'delete': + info( { + icon : V.tab + , title : title + , message : 'Delete '+ name +' ?' + , ok : () => { + delete PRO[ name ]; + setting.save( title, 'Remove ...' ); + V.li.remove(); + } + } ); + break; + } + break; case 'pipeline': + var title = 'Pipeline'; var main = $( '#pipeline .entries.sub' ).hasClass( 'hide' ); var type = main ? V.li.data( 'type' ).toLowerCase() : 'filter'; info( { icon : V.tab - , title : 'Pipeline' + , title : title , message : main ? 'Delete this '+ type +'?' : 'Delete '+ V.li.data( 'name' ) +' ?' , ok : () => { if ( main ) { @@ -2058,7 +2234,7 @@ $( '#menu a' ).on( 'click', function( e ) { var ni = V.li.data( 'index' ); PIP[ pi ].names.splice( ni, 1 ); } - setting.save( 'Pipeline', 'Remove '+ type +' ...' ); + setting.save( title, 'Remove '+ type +' ...' ); V.li.remove(); setting.sortRefresh( main ? 'main' : 'sub' ); graph.pipeline(); @@ -2076,7 +2252,7 @@ $( '#menu a' ).on( 'click', function( e ) { icon : V.tab , title : 'Configuration' , message : 'File: '+ name +'' - , textlabel : 'Copy as' + , list : [ 'Copy as', 'text' ] , values : [ name ] , checkchanged : true , ok : () => { @@ -2090,7 +2266,7 @@ $( '#menu a' ).on( 'click', function( e ) { info( { icon : V.tab , title : 'Configuration' - , textlabel : 'Rename to' + , list : [ 'Rename to', 'text' ] , values : [ name ] , checkchanged : true , ok : () => { @@ -2117,85 +2293,138 @@ $( '#menu a' ).on( 'click', function( e ) { } } } ); +// filters -------------------------------------------------------------------------------- $( '#filters' ).on( 'click', '.i-add', function() { setting.upload(); } ).on( 'input', 'input[type=range]', function() { - var $this = $( this ); - var val = +$this.val(); - $this.prev().text( util.dbRound( val ) ); - V.li = $this.parents( 'li' ); - var name = V.li.data( 'name' ); - FIL[ name ].parameters.gain = val; - setting.save(); + setting.rangeGet( $( this ), 'input' ); } ).on( 'touchend mouseup keyup', 'input[type=range]', function() { graph.gain(); +} ).on( 'click', '.i-volume', function() { + var $this = $( this ); + var name = $this.parents( 'li' ).data( 'name' ); + var checked = ! $this.hasClass( 'mute' ); + setting.muteToggle( $this, checked ); + FIL[ name ].parameters.mute = checked; + setting.save(); +} ).on( 'click', '.i-inverted, .i-linear', function() { + var $this = $( this ); + var name = $this.parents( 'li' ).data( 'name' ); + var checked = ! $this.hasClass( 'bl' ); + $this.toggleClass( 'bl', checked ); + var param = FIL[ name ].parameters; + $this.hasClass( 'i-inverted' ) ? param.inverted = checked : setting.scaleSet( checked, param, $this ); + setting.save(); +} ).on( 'click', 'li.eq', function( e ) { + if ( $( e.target ).parents( '.divgraph' ).length ) return + + var name = $( this ).data( 'name' ); + var param = FIL[ name ].parameters; + var bands = param.gains.length; + var min = Math.log10( param.freq_min ); // Hz > log10 : 20 > 1.3 + var max = Math.log10( param.freq_max ); // Hz > log10 : 20000 > 4.3 + var width = ( max - min ) / bands; // log10 / band + var v0 = min + width / 2; // log10 midband + var v = [ v0 ]; + for ( i = 0; i < bands - 1; i++ ) { + v0 += width; + v.push( v0 ); + } + var labelhz = ''; + v.forEach( val => { + var hz = Math.round( Math.pow( 10, val ) ); // log10 > Hz + if ( hz > 999 ) hz = Math.round( hz / 1000 ) +'k' + labelhz += ''+ hz +''; + } ); + var list = ` +
    +
    ${ labelhz }
    +
    ${ labelhz }
    +
    ${ ''.repeat( bands ) }
    +
    `; + var flatButton = () => $( '#infoOk' ).toggleClass( 'disabled', param.gains.reduce( ( a, b ) => a + b, 0 ) === 0 ); + info( { + icon : 'equalizer' + , title : name + , list : list + , width : 50 * bands + 40 + , values : param.gains + , beforeshow : () => { + flatButton(); + $( '.inforange input' ).on( 'input', function() { + var $this = $( this ); + param.gains[ $this.index() ] = +$this.val(); + setting.save(); + flatButton(); + } ); + $( '#eq .label a' ).on( 'click', function() { + var $this = $( this ); + var i = $this.index(); + var gain = param.gains[ i ]; + $this.parent().hasClass( 'up' ) ? gain++ : gain--; + $( '.inforange input' ).eq( i ).val( gain ); + param.gains[ i ] = gain; + setting.save(); + flatButton(); + } ); + } + , oklabel : ico( 'set0' ) +'Flat' + , oknoreset : true + , ok : () => { + param.gains = Array( bands ).fill( 0 ); + setting.save(); + $( '.inforange input' ).val( 0 ); + $( '#infoOk' ).addClass( 'disabled' ); + } + } ); } ); +// mixers --------------------------------------------------------------------------------- $( '#mixers' ).on( 'click', 'li', function( e ) { - var $this = $( this ); + var $this = $( this ); if ( $( e.target ).is( 'i' ) || $this.parent().hasClass( 'sub' ) ) return - var name = $this.find( '.li1' ).text(); + var name = $this.find( '.li1' ).text(); render.mixersSub( name ); -} ).on( 'click', 'li i', function() { +} ).on( 'input', 'input[type=range]', function() { + setting.rangeGet( $( this ), 'input' ); +} ).on( 'touchend mouseup keyup', 'input[type=range]', function() { + graph.gain(); +} ).on( 'click', '.i-volume', function() { + var $this = $( this ); + var M = setting.mixerGet( $this ); + var mapping = MIX[ M.name ].mapping[ M.index ]; + setting.muteToggle( $this, M.checked ); + typeof M.si === 'number' ? mapping.sources[ M.si ].mute = M.checked : mapping.mute = M.checked; + setting.save(); +} ).on( 'click', '.i-inverted, .i-linear', function() { var $this = $( this ); - if ( $this.is( '.liicon, .i-mixers, .i-back' ) || $this.parent().hasClass( 'divgain' ) ) return - - V.li = $this.parents( 'li' ); - var name = V.li.data( 'name' ); - var title = util.key2label( V.tab ); - if ( $this.hasClass( 'i-add' ) ) { - var index = V.li.hasClass( 'lihead' ) ? '' : V.li.data( 'index' ); - setting.mixerMap( name, index ); - } else { - var index = V.li.data( 'index' ); - var si = V.li.data( 'si' ); - var mapping = MIX[ name ].mapping[ index ]; - var source = mapping.sources[ si ]; - var checked = ! $this.hasClass( 'bl' ); - if ( $this.hasClass( 'i-volume' ) ) { - if ( V.li.hasClass( 'main' ) ) { - mapping.mute = checked; - } else { - source.mute = checked; - V.li.find( 'input[type=range]' ).prop( 'disabled', checked ); - V.li.find( '.divgain' ).toggleClass( 'disabled', checked ); - } - $this.toggleClass( 'mute bl', checked ); - } else if ( $this.hasClass( 'i-inverted' ) ) { - $this.toggleClass( 'bl', checked ); - source.inverted = checked; - } - setting.save( 'Mixer', 'Change ...' ); - } + var M = setting.mixerGet( $this ); + var source = MIX[ M.name ].mapping[ M.index ].sources[ M.si ]; + $this.toggleClass( 'bl', M.checked ); + $this.hasClass( 'i-inverted' ) ? source.inverted = M.checked : setting.scaleSet( M.checked, source, $this ); + setting.save(); } ).on( 'input', 'select', function() { - var $this = $( this ); - V.li = $this.parents( 'li' ); - var name = V.li.data( 'name' ); - var mi = V.li.data( 'index' ); - var val = +$this.val(); - if ( V.li.hasClass( 'main' ) ) { - MIX[ name ].mapping[ mi ].dest = val; + var M = setting.mixerGet( $( this ) ); + var val = +$this.val(); + if ( typeof M.si === 'number' ) { + MIX[ M.name ].mapping[ M.index ].sources[ M.si ].channel = val; } else { - var si = V.li.data( 'si' ); - MIX[ name ].mapping[ mi ].sources[ si ].channel = val; + MIX[ M.name ].mapping[ M.index ].dest = val; } - setting.save( 'Mixer', 'Change ...'); -} ).on( 'input', 'input[type=range]', function() { - var $this = $( this ); - var val = +$this.val(); - $this.prev().text( util.dbRound( val ) ); - V.li = $( this ).parents( 'li' ); - var name = V.li.data( 'name' ); - var index = V.li.data( 'index' ); - var si = V.li.data( 'si' ); - MIX[ name ].mapping[ index ].sources[ si ].gain = val; - setting.save(); -} ).on( 'touchend mouseup keyup', 'input[type=range]', function() { - graph.gain(); + setting.save( M.name, 'Change ...' ); +} ).on( 'click', '.i-add', function() { + var M = setting.mixerGet( $( this ) ); + setting.mixerMap( M.name, M.index ); +} ); +// processors --------------------------------------------------------------------------------------- +$( '#processors' ).on( 'click', 'li', function( e ) { + e.stopPropagation(); + $( this ).find( '.liicon' ).trigger( 'click' ); } ); -$( '#pipeline' ).on( 'click', 'li', function( e ) { +// pipeline ------------------------------------------------------------------------------- +$( '#pipeline' ).on( 'click', 'li', function( e ) { var $this = $( this ); - if ( $( e.target ).is( 'i' ) || $this.parent().is( '.sub, .divgain' ) ) return + if ( $( e.target ).is( 'i' ) || $this.parent().is( '.sub' ) ) return var index = $this.data( 'index' ); if ( $this.data( 'type' ) === 'Filter' ) { @@ -2209,7 +2438,7 @@ $( '#pipeline' ).on( 'click', 'li', function( e ) { icon : V.tab , title : 'Pipeline' , message : values - , select : names + , list : [ '', 'select', names ] , values : values , ok : () => { PIP[ index ].name = infoVal(); @@ -2221,17 +2450,15 @@ $( '#pipeline' ).on( 'click', 'li', function( e ) { var $this = $( this ); if ( $this.is( '.liicon, .i-back' ) ) return - V.li = $this.parents( 'li' ); - var title = util.key2label( V.tab ); - var index = V.li.data( 'index' ); + var title = common.tabTitle(); + var index = $this.parents( 'li' ).data( 'index' ); if ( $this.hasClass( 'i-add' ) ) { var title = 'Add Filter'; info( { - icon : V.tab - , title : title - , selectlabel : 'Filter' - , select : Object.keys( FIL ) - , ok : () => { + icon : V.tab + , title : title + , list : [ 'Filter', 'select', Object.keys( FIL ) ] + , ok : () => { PIP[ index ].names.push( infoVal() ); setting.save( title, 'Save ...' ); setting.sortRefresh( 'sub' ); @@ -2241,9 +2468,11 @@ $( '#pipeline' ).on( 'click', 'li', function( e ) { } ); } } ); +// devices -------------------------------------------------------------------------------- $( '#devices' ).on( 'click', 'li', function() { setting.device( $( this ).data( 'type' ) ); } ); +// config --------------------------------------------------------------------------------- $( '#config' ).on( 'click', '.i-add', function() { setting.upload(); } ).on( 'click', 'li', function( e ) { @@ -2261,6 +2490,7 @@ $( '#config' ).on( 'click', '.i-add', function() { } ); } } ); +// ---------------------------------------------------------------------------------------- $( '.switch' ).on( 'click', function() { var id = this.id; var $setting = $( '#setting-'+ id ); @@ -2272,7 +2502,7 @@ $( '#setting-enable_rate_adjust' ).on( 'click', function() { info( { icon : V.tab , title : SW.title - , message : 'Resampling type is Synchronous' + , message : 'Resampler type is Synchronous' } ); switchCancel(); return @@ -2281,13 +2511,16 @@ $( '#setting-enable_rate_adjust' ).on( 'click', function() { info( { icon : V.tab , title : SW.title - , numberlabel : [ 'Adjust period', 'Target level' ] + , list : [ + [ 'Adjust period', 'number' ] + , [ 'Target level', 'number' ] + ] , boxwidth : 100 , values : { - adjust_period : DEV.adjust_period - , target_level : DEV.target_level + adjust_period : DEV.adjust_period + , target_level : DEV.target_level } - , checkchanged : DEV.enable_rate_adjust + , checkchanged : S.enable_rate_adjust , cancel : switchCancel , ok : () => { var val = infoVal(); @@ -2300,10 +2533,10 @@ $( '#setting-stop_on_rate_change' ).on( 'click', function() { info( { icon : V.tab , title : SW.title - , numberlabel : 'Rate mearsure interval' + , list : [ 'Rate mearsure interval', 'number' ] , boxwidth : 65 , values : DEV.rate_measure_interval - , checkchanged : DEV.stop_on_rate_change + , checkchanged : S.stop_on_rate_change , cancel : switchCancel , ok : () => { DEV.rate_measure_interval = infoVal(); @@ -2311,8 +2544,8 @@ $( '#setting-stop_on_rate_change' ).on( 'click', function() { } } ); } ); -$( '#setting-enable_resampling' ).on( 'click', function() { - setting.resampling( DEV.resampler_type === 'FreeAsync' ); +$( '#setting-resampler' ).on( 'click', function() { + setting.resampler( S.resampler ? DEV.resampler.type : 'AsyncSinc' ); } ); $( '#bar-bottom div' ).on( 'click', function() { V.tab = this.id.slice( 3 ); diff --git a/srv/http/assets/js/common.js b/srv/http/assets/js/common.js index b399f3138..f20e38a3c 100644 --- a/srv/http/assets/js/common.js +++ b/srv/http/assets/js/common.js @@ -202,9 +202,9 @@ function highlightJSON( json ) { var json = Object.keys( json ) .sort() .reduce( ( r, k ) => ( r[ k ] = json[ k ], r ), {} ); // https://stackoverflow.com/a/29622653 - json = '\n\n'+ JSON.stringify( json, null, '\t' ); - json = json.replace( /'+ match +'' } - } ); // source: https://stackoverflow.com/a/7220510 + } ); + return '\n\n'+ json.replace( /: null,/g, ': null,' ); } function ico( cls, id ) { return '' @@ -230,7 +231,7 @@ function ico( cls, id ) { $( '#infoOverlay' ).press( '#infoIcon', function() { // usage window.open( 'https://github.com/rern/js/blob/master/info/README.md#infojs', '_blank' ); } ); -$( '#infoOverlay' ).on( 'click', '#infoContent', function() { +$( '#infoOverlay' ).on( 'click', '#infoList', function() { $( '.infobtn, .filebtn' ).removeClass( 'active' ); } ); $( '#infoOverlay' ).on( 'keydown', function( e ) { @@ -268,12 +269,10 @@ select: [U] [D] - check } ); I = { active: false } -var $infocontent; function info( json ) { local(); // flag for consecutive info - I = json; - V.iwidth = I.width; + I = json; if ( 'values' in I ) { if ( ! Array.isArray( I.values ) ) { @@ -295,15 +294,13 @@ function info( json ) {
    ${ ico( 'close', 'infoX' ) }
    -
    +
    ` ); - $infocontent = $( '#infoContent' ); - // title - if ( I.width ) $( '#infoBox' ).css( 'width', I.width ); - if ( I.height ) $( '#infoContent' ).css( 'height', I.height ); + if ( I.width ) $( '#infoBox' ).css( 'min-width', I.width ); + if ( I.height ) $( '#infoList' ).css( 'height', I.height ); if ( I.icon ) { I.icon.charAt( 0 ) !== '<' ? $( '#infoIcon' ).addClass( 'i-'+ I.icon ) : $( '#infoIcon' ).html( I.icon ); } else { @@ -349,17 +346,14 @@ function info( json ) { infoButtonCommand( I.ok ); } ); - if ( I.fileoklabel ) { // file api - var htmlfile = '
    ' - +'(select file)
     ' - +'' : '>' ) - +'
    ' + if ( I.file ) { + var htmlfile = '
    (select file)
    ' + +'' : '>' ) +'' - + ( I.filelabel || ico( 'folder-open' ) +' File' ) +''; + + ( I.file.label || ico( 'folder-open' ) +' File' ) +''; $( '#infoButton' ).prepend( htmlfile ) $( '#infoOk' ) - .html( I.fileoklabel ) + .html( I.file.oklabel ) .addClass( 'hide' ); $( '#infoFileLabel' ).on( 'click', function() { $( '#infoFileBox' ).trigger( 'click' ); @@ -371,24 +365,24 @@ function info( json ) { var filename = I.infofile.name; var typeimage = I.infofile.type.slice( 0, 5 ) === 'image'; I.filechecked = true; - if ( I.filetype ) { - if ( I.filetype === 'image/*' ) { + if ( I.file.type ) { + if ( I.file.type === 'image/*' ) { I.filechecked = typeimage; } else { var ext = filename.includes( '.' ) ? filename.split( '.' ).pop() : 'none'; - I.filechecked = I.filetype.includes( ext ); + I.filechecked = I.file.type.includes( ext ); } } if ( ! I.filechecked ) { - var htmlprev = $( '#infoContent' ).html(); + var htmlprev = $( '#infoList' ).html(); $( '#infoFilename, #infoFileLabel' ).addClass( 'hide' ); - $( '#infoContent' ).html( '' - +'
    Selected file :'+ filename +'
    File not :'+ I.filetype +'
    ' ); + $( '#infoList' ).html( '' + +'
    Selected file :'+ filename +'
    File not :'+ I.file.type +'
    ' ); $( '#infoOk' ).addClass( 'hide' ); $( '.infobtn.file' ).addClass( 'infobtn-primary' ) $( '#infoButton' ).prepend( 'OK' ); $( '#infoButton' ).one( 'click', '.btntemp', function() { - $( '#infoContent' ).html( htmlprev ); + $( '#infoList' ).html( htmlprev ); infoSetValues(); $( this ).remove(); $( '#infoFileLabel' ).removeClass( 'hide' ); @@ -404,152 +398,33 @@ function info( json ) { } } ); } - // tab if ( I.tab ) { - htmltab = '
    '; + var htmltab = ''; I.tablabel.forEach( ( lbl, i ) => { - var active = I.tab[ i ] ? '' : 'class="active"'; - htmltab += ''+ lbl +''; + htmltab += ''+ lbl +''; } ); - htmltab += '
    '; - $( '#infoTopBg' ).after( htmltab ); + $( '#infoTopBg' ).after( '
    '+ htmltab +'
    ' ); $( '#infoTab a' ).on( 'click', function() { if ( ! $( this ).hasClass( 'active' ) ) I.tab[ $( this ).index() ](); } ); } - - if ( I.content ) { // custom html content - var htmlcontent = I.content; - } else { - var htmls = {}; - [ 'header', 'message', 'footer' ].forEach( k => { - if ( I[ k ] ) { - var kalign = k +'align' - var align = I[ kalign ] ? ' style="text-align:'+ I[ kalign ] +'"' : ''; - htmls[ k ] = '
    '+ I[ k ] +'
    '; - } - } ); - // inputs html /////////////////////////////////////////////////////////// - if ( I.textlabel ) { - infoKey2array( 'textlabel' ); - htmls.text = ''; - I.textlabel.forEach( lbl => htmls.text += ''+ lbl +'' ); - } - if ( I.numberlabel ) { - infoKey2array( 'numberlabel' ); - htmls.number = ''; - I.numberlabel.forEach( lbl => htmls.number += ''+ lbl +'' ); - } - if ( I.passwordlabel ) { - infoKey2array( 'passwordlabel' ); - htmls.password = ''; - I.passwordlabel.forEach( lbl => htmls.password += ''+ lbl +''+ ico( 'eye' ) +'' ); - } - if ( I.textarea ) { - htmls.textarea = ''; - } - var td0 = htmls.text || htmls.number || htmls.password ? '' : ''; - if ( I.radio ) { - if ( Array.isArray( I.radio ) ) { - var kv = {} - I.radio.forEach( v => kv[ v ] = v ); - I.radio = kv; - } - infoKey2array( 'radio' ); - I.radio.forEach( radio => { - if ( Array.isArray( radio ) ) { - var kv = {} - radio.forEach( v => kv[ v ] = v ); - radio = kv; - } - var line; - var i = 0; - htmls.radio = ''; - $.each( radio, ( k, v ) => { - var html = k ? '' : ''; - if ( I.radiosingle ) { - htmls.radio += html +' '; - } else { - line = ''+ html +''; - if ( ! I.radiocolumn ) { - htmls.radio += ''+ td0 + line +''; - } else { - i++ - if ( i % 2 ) { - htmls.radio += ''+ td0 + line; - return - } else { - htmls.radio += line +''; - } - } - } - } ); - if ( I.radiosingle ) htmls.radio = ''+ htmls.radio +''; - } ); - } - if ( I.checkbox ) { - infoKey2array( 'checkbox' ); - var isstring = typeof I.checkbox[ 0 ] === 'string'; - var line, lbl, val; - var i = 0; - htmls.checkbox = ''; - $.each( I.checkbox, ( k, v ) => { // i, k - if ( isstring ) { - lbl = v; - } else { - lbl = k; - val = 'value="'+ v +'"'; - } - line = ''+ ( lbl ? '' : '' ) +''; - if ( ! I.checkcolumn ) { - htmls.checkbox += ''+ td0 + line +''; - } else { - i++ - if ( i % 2 ) { - htmls.checkbox += ''+ td0 + line; - return - } else { - htmls.checkbox += line +''; - } - } - } ); - } - if ( I.select ) { - if ( ! Array.isArray( I.selectlabel ) ) { - I.selectlabel = [ I.selectlabel ]; - I.select = [ I.select ]; - } - htmls.select = ''; - I.select.forEach( ( el, i ) => { - htmls.select += ''+ ( I.selectlabel[ i ] || '' ) +''; - } ); - } - if ( I.rangelabel ) { - infoKey2array( 'rangelabel' ); - htmls.range = '
    ' - I.rangelabel.forEach( range => { - htmls.range += '
    '+ I.rangelabel +'
    ' - +'
    ' - + ico( 'minus dn' ) +''+ ico( 'plus' ) - + ( I.rangesub ? '
    '+ I.rangesub +'
    ' : '' ) - } ); - htmls.range += '
    '; - } - var htmlcontent = htmls.header || ''; - htmlcontent += htmls.tab || ''; - htmlcontent += htmls.message || ''; - if ( ! I.order ) I.order = [ 'text', 'number', 'password', 'textarea', 'radio', 'checkbox', 'select', 'range' ]; - var htmlinputs = ''; - I.order.forEach( type => { - if ( type in htmls ) htmlinputs += htmls[ type ]; - } ); - if ( htmlinputs ) htmlcontent += ''+ htmlinputs +'
    '; - htmlcontent += htmls.footer || ''; + if ( I.prompt ) { + I.oknoreset = true; + $( '#infoList' ).after( '
    '+ I.prompt +'
    ' ); } - if ( ! htmlcontent ) { + var htmls = {}; + [ 'header', 'message', 'footer' ].forEach( k => { + if ( I[ k ] ) { + var kalign = k +'align' + var align = I[ kalign ] ? ' style="text-align:'+ I[ kalign ] +'"' : ''; + htmls[ k ] = '
    '+ I[ k ] +'
    '; + } else { + htmls[ k ] = ''; + } + } ); + if ( ! I.list ) { I.active = true; + $( '#infoList' ).html( Object.values( htmls ).join( '' ) ); $( '#infoButton' ).css( 'padding', '0 0 20px 0' ); $( '#infoOverlay' ).removeClass( 'hide' ); $( '#infoBox' ).css( 'margin-top', $( window ).scrollTop() ); @@ -558,13 +433,97 @@ function info( json ) { return } + [ 'range', 'updn' ].forEach( k => I[ k ] = [] ); + if ( typeof I.list === 'string' ) { + htmls.list = I.list; + } else { + htmls.list = ''; + if ( typeof I.list[ 0 ] !== 'object' ) I.list = [ I.list ]; + I.checkboxonly = ! I.list.some( l => l[ 1 ] !== 'checkbox' ); + var td0 = I.checkboxonly ? '' : ''; // no label + var label, type; + var i = 0; // for radio name + I.list.forEach( l => { + label = l[ 0 ]; + type = l[ 1 ]; + switch ( type ) { + case 'checkbox': + htmls.list += htmls.list.slice( -3 ) === 'tr>' ? td0 : ''; + break; + case 'hidden': + htmls.list += ''+ label +''; + break; + case 'radio': + htmls.list += ''+ label +''; + break; + case 'range': + htmls.list += ''; + break; + default: + htmls.list += ''+ label +''; + } + switch ( type ) { + case 'checkbox': + htmls.list += label ? '' : ''; + htmls.list += l[ 2 ] === 'td' ? '' : ''; // same line || 1:1 line + break; + case 'hidden': + case 'number': + case 'text': + var unit = typeof l[ 2 ] === 'object' ? false : l[ 2 ]; + var updn = unit ? false : l[ 2 ]; + htmls.list += ''; + if ( unit ) { + htmls.list += ' '+ unit +''; + } else if ( updn ) { + I.updn.push( updn ); + htmls.list += ''+ ico( 'remove updn dn' ) + ico( 'plus-circle updn up' ) +''; + } + htmls.list += ''; + break; + case 'password': + htmls.list += ''+ ico( 'eye' ) +''; + break; + case 'radio': + var isarray = $.isArray( l[ 2 ] ); + $.each( l[ 2 ], ( k, v ) => { + var k = isarray ? v : k; + htmls.list += ''; + htmls.list += l[ 3 ] === 'br' ? '
    ' : ' '; // 1:1 line || same line + } ); + htmls.list += ''; + i++; + break; + case 'range': + I.range = true; + htmls.list += '
    ' + +'
    '+ label +'
    ' + +'
    ' + + ico( 'minus dn' ) +''+ ico( 'plus up' ) + +'
    '; + break + case 'select': + htmls.list += ''; + htmls.list += l[ 3 ] ? ' '+ l[ 3 ] +'' : ''; // unit + break; + case 'textarea': + htmls.list += ''; + break; + default: // generic string + htmls.list += l[ 2 ] +'' + } + } ); + if ( type !== 'range' ) htmls.list = ''+ htmls.list +'
    '; + } + // populate layout ////////////////////////////////////////////////////////////////////////////// - $( '#infoContent' ).html( htmlcontent ).promise().done( function() { - - $( '#infoContent input:text' ).prop( 'spellcheck', false ); + var content = ''; + [ 'header', 'message', 'list', 'footer' ].forEach( k => content += htmls[ k ] ); + $( '#infoList' ).html( content ).promise().done( function() { + $( '#infoList input:text' ).prop( 'spellcheck', false ); // get all input fields - $inputbox = $( '#infoContent' ).find( 'input:text, input[type=number], input:password, textarea' ); - $input = $( '#infoContent' ).find( 'input, select, textarea' ); + $inputbox = $( '#infoList' ).find( 'input:text, input[type=number], input:password, textarea' ); + $input = $( '#infoList' ).find( 'input, select, textarea' ); var name, nameprev; $input = $input.filter( ( i, el ) => { // filter each radio per group ( multiple inputs with same name ) name = el.name; @@ -578,26 +537,21 @@ function info( json ) { // assign values infoSetValues(); // set height shorter if checkbox / radio only - $( '#infoContent tr' ).each( ( i, el ) => { + $( '#infoList tr' ).each( ( i, el ) => { var $this = $( el ); if ( $this.find( 'input:checkbox, input:radio' ).length ) $this.css( 'height', '36px' ); } ); - // show + // show $( '#infoOverlay' ).removeClass( 'hide' ); + // set at current scroll position + $( '#infoBox' ).css( 'margin-top', $( window ).scrollTop() ); I.active = true; - if ( 'focus' in I ) { - $inputbox.eq( I.focus ).focus(); - } else { - $( '#infoOverlay' ).focus(); - } + 'focus' in I ? $inputbox.eq( I.focus ).focus() : $( '#infoOverlay' ).focus(); if ( $( '#infoBox' ).height() > window.innerHeight - 10 ) $( '#infoBox' ).css( { top: '5px', transform: 'translateY( 0 )' } ); infoButtonWidth(); // set width: text / password / textarea infoWidth(); - if ( [ 'localhost', '127.0.0.1' ].includes( location.hostname ) ) $( '#infoContent a' ).removeAttr( 'href' ); - // set at current scroll position - $( '#infoBox' ).css( 'margin-top', $( window ).scrollTop() ); - if ( I.tab && $input.length === 1 ) $( '#infoContent' ).css( 'padding', '30px' ); + if ( [ 'localhost', '127.0.0.1' ].includes( location.hostname ) ) $( '#infoList a' ).removeAttr( 'href' ); // check inputs: blank / length / change if ( I.checkblank ) { if ( I.checkblank === true ) I.checkblank = [ ...Array( $inputbox.length ).keys() ]; @@ -616,41 +570,77 @@ function info( json ) { I.nochange = I.values && I.checkchanged ? true : false; $( '#infoOk' ).toggleClass( 'disabled', I.blank || I.notip || I.short || I.nochange ); // initial check infoCheckSet(); - if ( I.rangelabel ) { - var $range = $( '#infoRange input' ); + if ( I.range ) { var timeout, val; - $range.on( 'input', function() { // drag/click - val = +$range.val(); - $( '#infoRange .value' ).text( val ); - if ( I.rangechange ) I.rangechange( val ); - } ).on( 'touchend mouseup keyup', function() { // drag stop - if ( I.rangestop ) setTimeout( () => I.rangestop( val ), 300 ); + $( '.inforange input' ).on( 'input', function() { + var $this = $( this ); + $this.siblings( '.value' ).text( +$this.val() ); } ); - $( '#infoRange i' ).on( 'mouseup keyup', function() { // increment up/dn + var rangeset = ( $range, up ) => { + val = +$range.val(); + up ? val++ : val--; + $range + .val( val ) + .siblings( '.value' ).text( val ); + } + $( '.inforange i' ).on( 'touchend mouseup keyup', function() { // increment up/dn clearTimeout( timeout ); - if ( ! V.press ) { - val = +$range.val(); - $( this ).hasClass( 'dn' ) ? val-- : val++; - $range - .val( val ) - .trigger( 'input' ); - } - if ( I.rangestop ) timeout = setTimeout( () => I.rangestop( val ), 600 ); + var $this = $( this ); + if ( ! V.press ) rangeset( $this.siblings( 'input' ), $this.hasClass( 'up' ) ); } ).press( function( e ) { - val = +$range.val(); - var up = $( e.target ).hasClass( 'dn' ) - timeout = setInterval( () => { - up ? val-- : val++; - $range - .val( val ) - .trigger( 'input' ); - }, 100 ); + var $this = $( e.target ); + var $range = $this.siblings( 'input' ) + var up = $this.hasClass( 'up' ); + timeout = setInterval( () => rangeset( $range, up ), 100 ); + } ); + } + if ( I.updn.length ) { + I.updn.forEach( ( el, i ) => { + var $tr = $( '#infoList .updn' ).parent().eq( i ).parent() + var $updn = $tr.find( '.updn' ); + var $num = $updn.parent().prev().find( 'input' ); + var step = el.step; + var v = 0; + var interval, timeout; + function numberset( up ) { + v = +$num.val(); + v = up ? v + step : v - step; + if ( v === el.min || v === el.max ) { + clearInterval( interval ); + clearTimeout( timeout ); + } + $num.val( v ); + if ( I.checkchanged ) $num.trigger( 'input' ); + updnToggle( v ); + } + function updnToggle( v ) { + $updn.eq( 0 ).toggleClass( 'disabled', v === el.min ); + $updn.eq( 1 ).toggleClass( 'disabled', v === el.max ); + } + updnToggle( I.values[ $tr.index() ] ); + $updn.on( 'click', function() { + if ( ! V.press ) numberset( $( this ).hasClass( 'up' ) ); + } ).press( function( e ) { + var up = $( e.target ).hasClass( 'up' ); + interval = setInterval( () => numberset( up ), 100 ); + timeout = setTimeout( () => { // @5 after 3s + clearInterval( interval ); + step *= 5; + v = v > 0 ? v + ( step - v % step ) : v - ( step + v % step ); + $num.val( v ); + interval = setInterval( () => numberset( up ), 100 ); + }, 3000 ); + } ).on( 'touchend mouseup keyup', function() { + clearInterval( interval ); + clearTimeout( timeout ); + step = el.step; + } ); } ); } // custom function before show if ( I.beforeshow ) I.beforeshow(); } ); - $( '#infoContent .i-eye' ).on( 'click', function() { + $( '#infoList .i-eye' ).on( 'click', function() { var $this = $( this ); var $pwd = $this.parent().prev().find( 'input' ); if ( $pwd.prop( 'type' ) === 'text' ) { @@ -718,7 +708,7 @@ function infoCheckLength() { } function infoCheckSet() { if ( I.checkchanged || I.checkblank || I.checkip || I.checklength ) { - $( '#infoContent' ).find( 'input, select, textarea' ).on( 'input', function() { + $( '#infoList' ).find( 'input, select, textarea' ).on( 'input', function() { if ( I.checkchanged ) I.nochange = I.values.join( '' ) === infoVal( 'array' ).join( '' ); if ( I.checkblank ) setTimeout( infoCheckBlank, 0 ); // ios: wait for value if ( I.checklength ) setTimeout( infoCheckLength, 25 ); @@ -795,7 +785,7 @@ function infoFileImageReader() { } } reader.readAsDataURL( I.infofile ); - $( '#infoContent' ) + $( '#infoList' ) .off( 'click', '.infoimgnew' ) .on( 'click', '.infoimgnew', function() { if ( ! $( '.infomessage .rotate' ).length ) return @@ -850,6 +840,15 @@ function infoFileImageResize( ext, imgW, imgH ) { function infoKey2array( key ) { if ( ! Array.isArray( I[ key ] ) ) I[ key ] = [ I[ key ] ]; } +function infoPrompt( message ) { + var $toggle = $( '#infoX, #infoTab, .infoheader, #infoList, .infofooter, .infoprompt' ); + $( '.infoprompt' ).html( message ); + $toggle.toggleClass( 'hide' ); + $( '#infoOk' ).off( 'click' ).on( 'click', function() { + $toggle.toggleClass( 'hide' ); + $( '#infoOk' ).off( 'click' ).on( 'click', I.ok ); + } ); +} function infoSetValues() { var $this, type, val; $input.each( ( i, el ) => { @@ -858,17 +857,17 @@ function infoSetValues() { val = I.values[ i ]; if ( type === 'radio' ) { // reselect radio by name if ( val ) { - $( '#infoContent input:radio[name='+ el.name +']' ).val( [ val ] ); + $( '#infoList input:radio[name='+ el.name +']' ).val( [ val ] ); } else { - $( '#infoContent input:radio' ).eq( 0 ).prop( 'checked', true ); + $( '#infoList input:radio' ).eq( 0 ).prop( 'checked', true ); } } else if ( type === 'checkbox' ) { $this.prop( 'checked', val ); } else if ( $this.is( 'select' ) ) { val ? $this.val( val ) : el.selectedIndex = 0; - } else { // text, password, textarea, range + } else { $this.val( val ); - if ( type === 'range' ) $('#infoRange .value' ).text( val ); + if ( type === 'range' ) $('.inforange .value' ).text( val ); } } ); } @@ -879,37 +878,38 @@ function infoVal( array ) { $this = $( el ); type = $this.prop( 'type' ); switch ( type ) { - case 'radio': // radio has only single checked - skip unchecked inputs - val = $( '#infoContent input:radio[name='+ el.name +']:checked' ).val(); - if ( val === 'true' ) { - val = true; - } else if ( val === 'false' ) { - val = false; - } - break; case 'checkbox': val = $this.prop( 'checked' ); if ( val && $this.attr( 'value' ) ) val = $this.val(); // if value defined break; - case 'textarea': - val = $this.val().trim().replace( /\n/g, '\\n' ); + case 'number': + case 'range': + val = +$this.val(); break; case 'password': val = $this.val().trim().replace( /(["&()\\])/g, '\$1' ); // escape extra characters break; + case 'radio': // radio has only single checked - skip unchecked inputs + val = $( '#infoList input:radio[name='+ el.name +']:checked' ).val(); + if ( val === 'true' ) { + val = true; + } else if ( val === 'false' ) { + val = false; + } + break; case 'text': val = $this.val().trim(); break; - case 'number': - val = +$this.val(); + case 'textarea': + val = $this.val().trim().replace( /\n/g, '\\n' ); break; - default: + default: // hidden, select val = $this.val(); } - if ( typeof val !== 'string' // boolean - || val === '' // empty - || isNaN( val ) // NotaNumber - || val[ 0 ] === '0' && val[ 1 ] !== '.' // '0123' not 0.123 + if ( typeof val !== 'string' // boolean + || val === '' // empty + || isNaN( val ) // Not a Number + || ( val[ 0 ] === '0' && val[ 1 ] !== '.' ) // '0123' not 0.123 ) { values.push( val ); } else { @@ -982,25 +982,21 @@ function infoWidth() { , 'max-width' : maxw } ); } - var allW = $( '#infoContent' ).width(); - var labelW = $( '#infoContent td:first-child' ).width() || 0; - I.boxW = ( widthmax ? allW - labelW - 20 : I.boxwidth ); - } else if ( ! I.contentcssno ) { - I.boxW = 230; + var allW = $( '#infoList' ).width(); + var labelW = $( '#infoList td:first-child' ).width() || 0; + var boxW = ( widthmax ? allW - labelW - 20 : I.boxwidth ); + } else { + var boxW = 230; } - if ( I.boxW ) $( '#infoContent' ).find( 'input:text, input[type=number], input:password, textarea, select' ).parent().css( 'width', I.boxW ); - if ( $( '#infoContent select' ).length ) selectSet(); // render select to set width - if ( ! I.contentcssno && $( '#infoContent tr:eq( 0 ) td' ).length > 1 ) { // column gutter - var $td1st = $( '#infoContent td:first-child' ); - var input = $td1st.find( 'input' ).length; - $td1st.css( { - 'padding-right': input ? '10px' : '5px' // checkbox/radio gutter : text label - , 'text-align' : input ? '' : 'right' // text label - } ); + $( '#infoList table' ).find( 'input:text, input[type=number], input:password, textarea' ).parent().css( 'width', boxW ); + if ( $( '#infoList select' ).length ) { + selectSet(); // render select to set width + $( '#infoList .select2-container' ).attr( 'style', 'width: '+ boxW +'px !important' ); } if ( I.headeralign || I.messagealign || I.footeralign ) { - $( '#infoContent' ).find( '.infoheader, .infomessage, .infofooter' ).css( 'width', $( '#infoContent table' ).width() ); + $( '#infoList' ).find( '.infoheader, .infomessage, .infofooter' ).css( 'width', $( '#infoList table' ).width() ); } + if ( I.checkboxonly ) $( '#infoList td' ).css( 'text-align', 'left' ); } function capitalize( str ) { @@ -1064,7 +1060,7 @@ function local( delay ) { function selectSet( $select ) { var options = { minimumResultsForSearch: 10 } if ( ! $select ) { - $select = $( '#infoContent select' ); + $select = $( '#infoList select' ); if ( $( '#eq' ).length ) options.dropdownParent = $( '#eq' ); } $select @@ -1091,7 +1087,7 @@ function selectText2Html( pattern ) { } var $rendered = $( '.select2-selection__rendered' ).eq( 0 ); htmlSet( $rendered ); - $( '#infoContent select' ).on( 'select2:open', () => { + $( '#infoList select' ).on( 'select2:open', () => { setTimeout( () => $( '.select2-results__options li' ).each( ( i, el ) => htmlSet( $( el ) ) ), 0 ); } ).on( 'select2:select', function() { htmlSet( $rendered ); diff --git a/srv/http/assets/js/context.js b/srv/http/assets/js/context.js index 6caa7ba6b..08104e41c 100644 --- a/srv/http/assets/js/context.js +++ b/srv/http/assets/js/context.js @@ -82,13 +82,13 @@ function bookmarkNew() { , title : 'Add Bookmark' , message : '' +'
    '+ msgpath +'' - , textlabel : 'As:' + , list : [ 'As:', 'text' ] , focus : 0 , values : name , checkblank : true , beforeshow : () => { - $( '#infoContent input' ).parents( 'tr' ).addClass( 'hide' ); - $( '#infoContent img' ).off( 'error' ).on( 'error', function() { + $( '#infoList input' ).parents( 'tr' ).addClass( 'hide' ); + $( '#infoList img' ).off( 'error' ).on( 'error', function() { imageOnError( this, 'bookmark' ); } ); } @@ -113,7 +113,7 @@ function currentSet() { S.song = V.list.index; setPlaylistScroll(); local(); - bash( [ 'mpcskip', V.list.index + 1, 'CMD POS' ] ); + bash( [ 'mpcskip', V.list.index + 1, 'stop', 'CMD POS ACTION' ] ); } function directoryList() { if ( [ 'album', 'latest' ].includes( V.mode ) ) { @@ -184,7 +184,7 @@ function playlistNew( name ) { icon : 'file-playlist' , title : 'Save Playlist' , message : 'Save current playlist as:' - , textlabel : 'Name' + , list : [ 'Name', 'text' ] , focus : 0 , values : name , checkblank : true @@ -197,7 +197,7 @@ function playlistRename() { icon : 'file-playlist' , title : 'Rename Playlist' , message : 'From: '+ name +'' - , textlabel : 'To' + , list : [ 'To', 'text' ] , focus : 0 , values : name , checkchanged : true @@ -252,7 +252,7 @@ function savedPlaylistAdd() { } function savedPlaylistRemove() { local(); - var plname = $( '#pl-path .lipath' ).text(); + var plname = $( '#savedpl-path .lipath' ).text(); bash( [ 'savedpledit', plname, 'remove', V.list.li.index() + 1, 'CMD NAME TYPE POS' ] ); V.list.li.remove(); } @@ -289,7 +289,7 @@ function tagEditor() { name[ 1 ] = 'Album Artist'; var label = []; format.forEach( ( el, i ) => { - label.push( ''+ name[ i ] +' ' ); + label.push( [ ''+ name[ i ] +' ', 'text' ] ); } ); if ( V.library ) { var $img = V.librarytrack ? $( '.licoverimg img' ) : V.list.li.find( 'img' ); @@ -311,25 +311,25 @@ function tagEditor() { , width : 500 , message : message , messagealign : 'left' + , list : label , footer : footer , footeralign : 'left' - , textlabel : label , boxwidth : 'max' , values : values , checkchanged : true , beforeshow : () => { - $( '#infoContent img' ).on( 'error', function() { + $( '#infoList img' ).on( 'error', function() { imageOnError( this ); } ); - $( '#infoContent .infomessage' ).addClass( 'tagmessage' ); - $( '#infoContent .infofooter' ).addClass( 'tagfooter' ); - $( '#infoContent td i' ).css( 'cursor', 'pointer' ); - if ( V.playlist ) $( '#infoContent input' ).prop( 'disabled', 1 ); - var tableW = $( '#infoContent table' ).width(); - $( '#infoContent' ).on( 'click', '#taglabel', function() { + $( '#infoList .infomessage' ).addClass( 'tagmessage' ); + $( '#infoList .infofooter' ).addClass( 'tagfooter' ); + $( '#infoList td i' ).css( 'cursor', 'pointer' ); + if ( V.playlist ) $( '#infoList input' ).prop( 'disabled', 1 ); + var tableW = $( '#infoList table' ).width(); + $( '#infoList' ).on( 'click', '#taglabel', function() { if ( $( '.taglabel' ).hasClass( 'hide' ) ) { $( '.taglabel' ).removeClass( 'hide' ); - $( '#infoContent table' ).width( tableW ); + $( '#infoList table' ).width( tableW ); } else { $( '.taglabel' ).addClass( 'hide' ); } @@ -438,9 +438,7 @@ function webRadioCoverart() { , title : ( mode === 'webradio' ? 'Web' : 'DAB' ) +' Radio Cover Art' , message : '' + '

    '+ name +'

    ' - , filelabel : ico( 'folder-open' ) +'File' - , fileoklabel : ico( 'flash' ) +'Replace' - , filetype : 'image/*' + , file : { oklabel: ico( 'flash' ) +'Replace', type: 'image/*' } , beforeshow : () => { $( '.imgold' ).on( 'error', function() { imageOnError( this ); @@ -510,7 +508,7 @@ function wrDirectoryRename() { info( { icon : V.mode , title : 'Rename Folder' - , textlabel : 'Name' + , list : [ 'Name', 'text' ] , focus : 0 , values : name , checkblank : true @@ -519,37 +517,33 @@ function wrDirectoryRename() { , ok : () => bash( [ 'wrdirrename', V.mode +'/'+ path, name, infoVal(), 'CMD MODE NAME NEWNAME' ] ) } ); } -var htmlwebradio = `\ - - - - - - -
    Name
    URL
    Charset -  ${ ico( 'help i-lg gr' ) } - ${ ico( 'folder-plus i-lg' ) } New folder  -
    -`; +var listwebradio = { + list : [ + [ 'Name', 'text' ] + , [ 'URL', 'text' ] + , [ 'Charset', 'text' ] + , [ '', '', ''+ ico( 'folder-plus i-lg' ) +' New folder ' ] + ] + , help : ' '+ ico( 'help i-lg gr' ) +'' + , fn : () => { + $( '#infoList input' ).last() + .css( 'width', '230px' ) + .after( listwebradio.help ); + } +} function webRadioEdit() { - var url = V.list.path; + var url = V.list.path; + var rprf = url.includes( 'stream.radioparadise.com' ) || url.includes( 'icecast.radiofrance.fr' ); info( { icon : 'webradio' , title : 'Edit Web Radio' - , content : htmlwebradio + , message : '' + , list : rprf ? listwebradio.list.slice( 0, 2 ) : listwebradio.list.slice( 0, -1 ) , values : [ V.list.name, url, V.list.li.data( 'charset' ) || 'UTF-8' ] , checkchanged : true , checkblank : [ 0, 1 ] , boxwidth : 'max' - , beforeshow : () => { - var src = V.list.li.find( 'img' ).attr( 'src' ) || V.coverdefault; - $( '#infoContent' ).prepend( '
    ' ); - if ( url.includes( 'stream.radioparadise.com' ) || url.includes( 'icecast.radiofrance.fr' ) ) { - $( '#infoContent tr' ).last().remove(); - } else { - $( '#addwebradiodir' ).remove(); - } - } + , beforeshow : rprf ? '' : listwebradio.fn , oklabel : ico( 'save' ) +'Save' , ok : () => { var dir = $( '#lib-path .lipath' ).text(); @@ -574,13 +568,14 @@ function webRadioExists( error, name, url, charset ) { } function webRadioNew( name, url, charset ) { info( { - icon : 'webradio' - , title : ( V.library ? 'Add' : 'Save' ) +' Web Radio' - , boxwidth : 'max' - , content : htmlwebradio - , values : [ name, url, charset || 'UTF-8' ] - , checkblank : [ 0, 1 ] - , beforeshow : () => { + icon : 'webradio' + , title : ( V.library ? 'Add' : 'Save' ) +' Web Radio' + , boxwidth : 'max' + , list : listwebradio.list + , values : [ name, url, charset || 'UTF-8' ] + , checkblank : [ 0, 1 ] + , beforeshow : () => { + listwebradio.fn() if ( $( '#lib-path .lipath' ).text() ) { $( '#addwebradiodir' ).remove(); } else { @@ -588,16 +583,16 @@ function webRadioNew( name, url, charset ) { info( { icon : 'webradio' , title : 'Add New Folder' - , textlabel : 'Name' + , list : [ 'Name', 'text' ] , checkblank : true , cancel : () => $( '.button-webradio-new' ).trigger( 'click' ) , ok : () => bash( [ 'wrdirnew', $( '#lib-path .lipath' ).text(), infoVal(), 'CMD DIR SUB' ] ) } ); } ); } - if ( V.playlist ) $( '#infoContent input' ).eq( 1 ).prop( 'disabled', true ); + if ( V.playlist ) $( '#infoList input' ).eq( 1 ).prop( 'disabled', true ); } - , ok : () => { + , ok : () => { var values = infoVal(); var name = values[ 0 ]; var url = values[ 1 ]; diff --git a/srv/http/assets/js/equalizer.js b/srv/http/assets/js/equalizer.js index f97939fd8..54187bc87 100644 --- a/srv/http/assets/js/equalizer.js +++ b/srv/http/assets/js/equalizer.js @@ -7,7 +7,7 @@ freq.forEach( ( hz, i ) => { band.push( '0'+ i +'. '+ freq[ i ] + ( i < 5 ? ' Hz' : ' kHz' ) ); labelhz += ''+ hz + ( i < 5 ? '' : 'k' ) +''; } ); -var content = ` +var htmllist = `
    ${ labelhz }
    @@ -18,26 +18,25 @@ var content = ` ${ ico( 'add', 'eqnew' ) + ico( 'back bl hide', 'eqback' ) }
    -
    ${ ''.repeat( 10 ) }
    +
    ${ ''.repeat( 10 ) }
    `; function equalizer() { bash( [ 'equalizerget' ], data => { E = data || { active: "Flat", preset: { Flat: Array.from( new Array( 10 ), () => 62 ) } } equser = [ 'airplay', 'spotify' ].includes( S.player ) ? 'root' : 'mpd'; info( { - icon : 'equalizer' - , title : 'Equalizer' - , content : content.replace( 'PRESETS', eqOptionPreset() ) - , contentcssno : true - , values : [ '', E.active, ...E.preset[ E.active ] ] - , beforeshow : () => { + icon : 'equalizer' + , title : 'Equalizer' + , list : htmllist.replace( 'PRESETS', eqOptionPreset() ) + , values : [ '', E.active, ...E.preset[ E.active ] ] + , beforeshow : () => { $( '#infoBox' ).css( 'width', 550 ); $( '#eqrename' ).toggleClass( 'disabled', E.active === 'Flat' ); if ( /Android.*Chrome/i.test( navigator.userAgent ) ) { // fix: chrome android drag var $this, ystart, val, prevval; - var yH = $( '#infoRange input' ).width() - 40; + var yH = $( '.inforange input' ).width() - 40; var step = yH / 40; - $( '#infoRange input' ).on( 'touchstart', function( e ) { + $( '.inforange input' ).on( 'touchstart', function( e ) { $this = $( this ); ystart = e.changedTouches[ 0 ].pageY; val = +$this.val(); @@ -56,7 +55,7 @@ function equalizer() { eqSlideEnd(); } ); } else { - $( '#infoRange input' ).on( 'input', function() { + $( '.inforange input' ).on( 'input', function() { var $this = $( this ); eqSlide( band[ $this.index() ], +$this.val() ); } ).on( 'touchend mouseup keyup', function() { @@ -64,8 +63,8 @@ function equalizer() { } ); } } - , cancel : () => E = {} - , okno : true + , cancel : () => E = {} + , okno : true } ); }, 'json' ); } @@ -168,7 +167,7 @@ $( '#infoOverlay' ).on( 'click', '#eqrename', function() { var v = '1%-'; var updn = -1; } - var $range = $( '#infoRange input' ).eq( i ); + var $range = $( '.inforange input' ).eq( i ); $range.val( +$range.val() + updn ); eqSlide( band[ i ], v ); eqtimeout = setTimeout( eqSlideEnd, 1000 ); diff --git a/srv/http/assets/js/features.js b/srv/http/assets/js/features.js index f3acbbb9d..285d8d5d7 100644 --- a/srv/http/assets/js/features.js +++ b/srv/http/assets/js/features.js @@ -26,7 +26,7 @@ var default_v = { , UPNP : true } , stoptimer : { - MIN : '' + MIN : 30 , POWEROFF : false } } @@ -46,7 +46,7 @@ $( '#setting-snapclient' ).on( 'click', function() { icon : SW.icon , title : SW.title , message : 'Sync SnapClient with SnapServer:' - , numberlabel : 'Latency (ms)' + , list : [ 'Latency (ms)', 'number' ] , focus : 0 , checkblank : true , values : S.snapclientconf @@ -79,8 +79,7 @@ $( '#setting-spotifyd' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , selectlabel : 'Device' - , select : list.devices + , list : [ 'Device', 'select', list.devices ] , boxwidth : 300 , values : list.current , checkchanged : true @@ -115,14 +114,17 @@ $( '#setting-spotifyd' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , textlabel : [ 'ID', 'Secret' ] + , list : [ + [ 'ID', 'text' ] + , [ 'Secret', 'text' ] + ] , focus : 0 , footer : '
    ID and Secret from Spotify private app '+ ico( 'help help' ) , footeralign : 'right' , boxwidth : 320 , checklength : { 0: 32, 1: 32 } , beforeshow : () => { - $( '#infoContent .help' ).on( 'click', function() { + $( '#infoList .help' ).on( 'click', function() { $( '.container .help' ).eq( 0 ).trigger( 'click' ); $( '#infoX' ).trigger( 'click' ); } ); @@ -150,7 +152,10 @@ $( '#setting-hostapd' ).on( 'click', function() { icon : SW.icon , title : SW.title , footer : '(8 characters or more)' - , textlabel : [ 'IP', 'Password' ] + , list : [ + [ 'IP', 'text' ] + , [ 'Password', 'text' ] + ] , values : S.hostapd , checkchanged : S.hostapd , checkblank : true @@ -164,7 +169,11 @@ $( '#setting-autoplay' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , checkbox : [ 'Bluetooth connected', 'Audio CD inserted', 'Power on / Reboot' ] + , list : [ + [ 'Bluetooth connected', 'checkbox' ] + , [ 'Audio CD inserted', 'checkbox' ] + , [ 'Power on / Reboot', 'checkbox' ] + ] , values : S.autoplayconf || default_v.autoplay , checkchanged : S.autoplay , cancel : switchCancel @@ -173,80 +182,53 @@ $( '#setting-autoplay' ).on( 'click', function() { } ); } ); $( '#setting-localbrowser' ).on( 'click', function() { - var htmlbrightness = S.brightness ? ''+ ico( 'gear' ) +' Brightness' : ''; - var content = ` - - - - - - - - - - - -
    Rotation -
    Zoom % ${ ico( 'remove btnicon dn' ) } ${ ico( 'plus-circle btnicon up' ) }
    Screen off -  minutes
    -

    - ${ htmlbrightness } -  ${ ico( 'redo' ) } Reload  - ${ ico( 'screenoff' ) } On/Off
      -
    `; + var brightness = S.brightness ? ''+ ico( 'gear' ) +' Brightness ' : ''; + var button = ''+ ico( 'redo' ) +' Reload'+ ico( 'screenoff' ) +' On/Off'; info( { icon : SW.icon , title : SW.title - , content : content + , list : [ + [ 'Rotation', 'select', { Normal: 0, '90° CW': 90, '90° CCW': 270, '180°': 180 } ] + , [ 'Zoom (%)', 'number', { step: 5, min: 50, max: 300 } ] + , [ 'Screen off (min)', 'number', { step: 1, min: 0, max: 60 } ] + , [ 'On while play', 'checkbox' ] + , [ 'Mouse pointer', 'checkbox' ] + , [ 'run xinitrc.d', 'checkbox' ] + ] + , footer : '
    '+ brightness + button , boxwidth : 110 , values : S.localbrowserconf || default_v.localbrowser , checkchanged : S.localbrowser , beforeshow : () => { - selectText2Html( { '90° CW': '90° '+ ico( 'redo' ), '90° CCW': '90° '+ ico( 'undo' ) } ); - $( '#onwhileplay' ).prop( 'disabled', S.localbrowserconf.SCREENOFF === 0 ); - $( '#infoContent .btnicon' ).on( 'click', function() { - var up = $( this ).hasClass( 'up' ); - var zoom = +$( '#zoom' ).val(); - if ( ( up && zoom < 300 ) || ( ! up && zoom > 50 ) ) $( '#zoom' ).val( up ? zoom += 5 : zoom -= 5 ); - $( '#infoOk' ).toggleClass( 'disabled', I.values.join( '' ) === infoVal( 'array' ).join( '' ) ); - } ); - $( '#infoContent' ).on( 'input', '#screenoff', function() { - if ( $( this ).val() != 0 ) { - $( '#onwhileplay' ).prop( 'disabled', 0 ); + var $onwhileplay = $( '#infoList input:checkbox' ).eq( 0 ); + $onwhileplay.prop( 'disabled', S.localbrowserconf.SCREENOFF === 0 ); + $( '.infofooter' ).toggleClass( 'hide', ! S.localbrowser ); + $( '#infoList tr:eq( 2 )' ).on( 'click', '.updn', function() { + if ( $( this ).parents( 'td' ).prev().find( 'input' ).val() != 0 ) { + $onwhileplay.prop( 'disabled', false ); } else { - $( '#onwhileplay' ) - .prop( 'checked', 0 ) - .prop( 'disabled', 1 ); + $onwhileplay + .prop( 'disabled', true ) + .prop( 'checked', false ); } } ); - $( '.btnbottom' ).toggleClass( 'hide', ! S.localbrowser ); - $( '.brightness' ).on( 'click', function() { + $( '#infoList' ).on( 'click', '.brightness', function() { switchCancel(); info( { icon : 'firefox' , title : 'Browser on RPi' - , rangelabel : 'Brightness' + , list : [ 'Brightness', 'range' ] , values : S.brightness - , rangechange : val => bash( [ 'brightness', val, 'CMD VAL' ] ) + , beforeshow : () => { + $( '#infoList input' ).on( 'input', function() { + bash( [ 'brightness', val, 'CMD VAL' ] ) + } ); + } , okno : true } ); - } ); - $( '.reload' ).on( 'click', function() { + } ).on( 'click', '.reload', function() { bash( [ 'localbrowserreload' ] ); - } ); - $( '.screenoff' ).on( 'click', function() { + } ).on( 'click', '.screenoff', function() { bash( [ 'screenofftoggle' ] ); } ); } @@ -260,7 +242,10 @@ $( '#setting-smb' ).on( 'click', function() { icon : SW.icon , title : SW.title , message : 'Write permission:' - , checkbox : [ '/mnt/MPD/SD', '/mnt/MPD/USB' ] + , list : [ + [ '/mnt/MPD/SD', 'checkbox' ] + , [ '/mnt/MPD/USB', 'checkbox' ] + ] , values : S.smbconf , checkchanged : S.smb , cancel : switchCancel @@ -271,30 +256,34 @@ $( '#setting-lyrics' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , textlabel : [ 'URL', 'Start tag', 'End tag' ] - , checkbox : 'Embedded lyrics' + , list : [ + [ 'URL', 'text' ] + , [ 'Start tag', 'text' ] + , [ '', '', 'Lyrics content ...' ] + , [ 'End tag', 'text' ] + , [ 'Embedded lyrics', 'checkbox' ] + ] , boxwidth : 300 , values : S.lyricsconf || default_v.lyrics , checkchanged : S.lyrics , checkblank : true - , beforeshow : () => $( '#infoContent tr' ).eq( 1 ).after( 'Lyrics content ...' ) , cancel : switchCancel , ok : switchEnable , fileconf : true } ); } ); $( '#setting-multiraudio' ).on( 'click', function() { - var trhtml = '' - +'' - +' '+ ico( 'remove i-lg pointer ipremove' ) +''; - var content = ' Name IP / URL '+ ico( 'add i-lg wh pointer ipadd' ) +''+ trhtml; + var trhtml = '' + +'' + +' '+ ico( 'remove i-lg pointer ipremove' ) +''; + var list = ' Name IP / URL '+ ico( 'add i-lg wh pointer ipadd' ) +''+ trhtml; if ( S.multiraudioconf ) { var keys = Object.keys( S.multiraudioconf ).sort(); var values = []; keys.forEach( k => values.push( k, S.multiraudioconf[ k ] ) ); var iL = values.length / 2 - 1; - for ( i = 0; i < iL; i++ ) content += trhtml; + for ( i = 0; i < iL; i++ ) list += trhtml; } else { values = [ S.hostname, S.hostip ]; } @@ -302,28 +291,30 @@ $( '#setting-multiraudio' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , content : ''+ content +'
    ' - , contentcssno : true + , list : ''+ list +'
    ' + , boxwidth : 130 , values : values , checkchanged : S.multiraudio && values.length > 2 , checkblank : I.checkblank , checkip : I.checkip , checkunique : true , beforeshow : () => { + $( '#infoList td:first-child' ).css( 'width', '180px' ); + $( '#infoList td' ).eq( 0 ).css( 'text-align', 'left' ); setTimeout( () => $( '#infoOk' ).toggleClass( 'disabled', I.values.length < 3 ), 0 ); - $( '#infoContent input' ).each( ( i, el ) => { + $( '#infoList input' ).each( ( i, el ) => { if ( $( el ).val() === S.hostip ) $( el ).addClass( 'disabled' ); } ); - $( '#infoContent' ).on( 'click', 'i', function() { + $( '#infoList' ).on( 'click', 'i', function() { var $this = $( this ); var add = $this.hasClass( 'ipadd' ); if ( add ) { - $( '#infoContent table' ).append( trhtml ); - $( '#infoContent input' ).last().val( S.ipsub ); + $( '#infoList table' ).append( trhtml ); + $( '#infoList input' ).last().val( S.ipsub ); } else { $this.parents( 'tr' ).remove(); } - $inputbox = $( '#infoContent input' ); + $inputbox = $( '#infoList input' ); $input = $inputbox; infoCheckEvenOdd( $input.length ); infoCheckSet(); @@ -358,14 +349,14 @@ $( '#login' ).on( 'click', function() { $( '#setting-login' ).trigger( 'click' ); } else { info( { - icon : SW.icon - , title : SW.title - , message : 'Disable:' - , passwordlabel : 'Password' - , focus : 0 - , checkblank : true - , cancel : switchCancel - , ok : () => { + icon : SW.icon + , title : SW.title + , message : 'Disable:' + , list : [ 'Password', 'password' ] + , focus : 0 + , checkblank : true + , cancel : switchCancel + , ok : () => { notifyCommon(); $.post( 'cmd.php', { cmd : 'login' @@ -379,15 +370,22 @@ $( '#login' ).on( 'click', function() { } } ); $( '#setting-login' ).on( 'click', function() { + var list = { + existing : [ + [ 'Existing', 'password' ] + , [ 'New', 'password' ] + ] + , new : [ 'Password', 'password' ] + } info( { - icon : SW.icon - , title : SW.title - , message : ( S.login ? 'Change password:' : 'New setup:' ) - , passwordlabel : ( S.login ? [ 'Existing', 'New' ] : 'Password' ) - , focus : 0 - , checkblank : true - , cancel : switchCancel - , ok : () => { + icon : SW.icon + , title : SW.title + , message : ( S.login ? 'Change password:' : 'New setup:' ) + , list : S.login ? list.existing : list.new + , focus : 0 + , checkblank : true + , cancel : switchCancel + , ok : () => { var infoval = infoVal(); notifyCommon(); $.post( 'cmd.php', { @@ -405,11 +403,11 @@ $( '#setting-scrobble' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , checkbox : [ - ico( 'airplay' ) +'AirPlay' - , ico( 'bluetooth' ) +'Bluetooth' - , ico( 'spotify' ) +'Spotify' - , ' '+ ico( 'upnp' ) +' UPnP / DLNA' + , list : [ + [ ico( 'airplay' ) +'AirPlay', 'checkbox' ] + , [ ico( 'bluetooth' ) +'Bluetooth', 'checkbox' ] + , [ ico( 'spotify' ) +'Spotify', 'checkbox' ] + , [ ' '+ ico( 'upnp' ) +' UPnP / DLNA', 'checkbox' ] ] , boxwidth : 170 , values : S.scrobbleconf || default_v.scrobble @@ -447,13 +445,13 @@ $( '#setting-stoptimer' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , radio : { '5 minutes': 5, '15 minutes': 15, '30 minutes': 30, '60 minutes': 60 } - , checkbox : [ 'Power off on stop' ] + , list : [ + [ 'Minutes', 'number', { step: 5, min: 5, max: 120 } ] + , [ 'Power off on stop', 'checkbox' ] + ] + , boxwidth : 70 , values : S.stoptimerconf || default_v.stoptimer , checkchanged : S.stoptimer - , beforeshow : () => { - $( '#infoContent tr:last' ).css( 'height', '60px' ); - } , cancel : switchCancel , ok : switchEnable , fileconf : true diff --git a/srv/http/assets/js/function.js b/srv/http/assets/js/function.js index 0f0a84252..ca9143c2f 100644 --- a/srv/http/assets/js/function.js +++ b/srv/http/assets/js/function.js @@ -147,13 +147,13 @@ function changeIP() { // for android app icon : 'networks' , title : 'IP Address' , message : 'Switch rAudio:' - , textlabel : 'New IP' + , list : [ 'New IP', 'text' ] , focus : 0 , boxwidth : 170 , values : location.host , checkchanged : true , checkip : [ 0 ] - , beforeshow : () => $( '#infoContent input' ).prop( 'type', 'tel' ) + , beforeshow : () => $( '#infoList input' ).prop( 'type', 'tel' ) , ok : () => { var ip = infoVal(); var changed = Android.changeIP( ip ); @@ -364,9 +364,7 @@ function coverartChange() { +'
    '+ ico( 'artist wh' ) +' '+ artist +'

    ' , footer : embedded , beforeshow : () => $( '.imgold' ).attr( 'src', src ) // fix direct replace src - , filelabel : ico( 'folder-open' ) +'File' - , fileoklabel : ico( 'flash' ) +'Replace' - , filetype : 'image/*' + , file : { oklabel: ico( 'flash' ) +'Replace', type: 'image/*' } , buttonlabel : ! coverartlocal ? '' : ico( 'remove' ) +'Remove' , buttoncolor : ! coverartlocal ? '' : red , button : ! coverartlocal ? '' : () => { @@ -524,7 +522,7 @@ function displayPlayback() { } function displaySave() { var values = infoVal(); - [ 'libmain', 'liboption', 'playback', 'playlist' ].forEach( chk => { + [ 'library', 'libraryoption', 'playback', 'playlist' ].forEach( chk => { $.each( chkdisplay[ chk ], ( k, v ) => { if ( ! ( k in values ) && k !== '-' ) values[ k ] = D[ k ]; } ); @@ -609,7 +607,7 @@ function imageOnError( el, bookmark ) { var icon = ico( 'bookmark bl' ); if ( ! V.librarylist ) icon += ''+ bookmark +''; $this.replaceWith( icon ); - $( '#infoContent input' ).parents( 'tr' ).removeClass( 'hide' ); + $( '#infoList input' ).parents( 'tr' ).removeClass( 'hide' ); } } function imageReplace( type, imagefilenoext, bookmarkname ) { @@ -631,44 +629,52 @@ function infoDisplayKeyValue( type ) { keys = keys.filter( k => k !== '-' ); var values = {} keys.forEach( k => { values[ k ] = D[ k ] } ); - return { keys : keys, values: values, checkbox: Object.values( json ) } + var list = []; + Object.values( json ).forEach( ( l, i ) => { + if ( [ 'library', 'playback' ].includes( type ) ) { + list.push( i % 2 ? [ l, 'checkbox' ] : [ l, 'checkbox', 'td' ] ); + } else { + list.push( [ l, 'checkbox' ] ); + } + } ); + return { keys : keys, values: values, list: list } } function infoLibrary() { - var kv = infoDisplayKeyValue( 'libmain' ); + var kv = infoDisplayKeyValue( 'library' ); info( { icon : 'library' , title : 'Library' , tablabel : [ 'Show', 'Options' ] , tab : [ '', infoLibraryOption ] , messagealign : 'left' - , checkbox : kv.checkbox + , list : kv.list , checkcolumn : true , values : kv.values , checkchanged : true , beforeshow : () => { var $el = {}; - kv.keys.forEach( ( k, i ) => $el[ k ] = $( '#infoContent input' ).eq( i ) ); + kv.keys.forEach( ( k, i ) => $el[ k ] = $( '#infoList input' ).eq( i ) ); $el.sd.add( $el.usb ).prop( 'disabled', S.shareddata ); } , ok : displaySave } ); } function infoLibraryOption() { - var kv = infoDisplayKeyValue( 'liboption' ); + var kv = infoDisplayKeyValue( 'libraryoption' ); info( { icon : 'library' , title : 'Library' , tablabel : [ 'Show', 'Options' ] , tab : [ infoLibrary, '' ] , messagealign : 'left' - , checkbox : kv.checkbox + , list : kv.list , values : kv.values , checkchanged : true , beforeshow : () => { var $el = {} - kv.keys.forEach( ( k, i ) => $el[ k ] = $( '#infoContent input' ).eq( i ) ); - $( '#infoContent tr' ).css( 'height', '36px' ); - $( '#infoContent td' ).css( 'width', '294px' ); + kv.keys.forEach( ( k, i ) => $el[ k ] = $( '#infoList input' ).eq( i ) ); + $( '#infoList tr' ).css( 'height', '36px' ); + $( '#infoList td' ).css( 'width', '293px' ); $el.albumyear.prop( 'disabled', ! D.albumbyartist ); $el.fixedcover.prop( 'disabled', D.hidecover ); $el.albumbyartist.on( 'input', function() { @@ -703,7 +709,7 @@ function infoTitle() { var noparen = title.slice( -1 ) !== ')'; var titlenoparen = title.replace( / $|\(.*$/, '' ); var paren = title.replace( /^.*\(/, '(' ); - var content = `\ + var htmllist = `\ @@ -720,7 +726,7 @@ function infoTitle() { info( { icon : 'playback' , title : 'Current Track' - , content : content + , list : htmllist , width : 460 , boxwidth : 'max' , values : noparen ? [ artist, title, album ] : [ artist, titlenoparen, album ] @@ -728,24 +734,24 @@ function infoTitle() { if ( noparen ) { $( '#paren' ).addClass( 'hide' ); } else { - $( '#infoContent input:checkbox' ).on( 'input', function() { - $( '#infoContent input:text' ).eq( 1 ).val( $( this ).prop( 'checked' ) ? title : titlenoparen ); + $( '#infoList input:checkbox' ).on( 'input', function() { + $( '#infoList input:text' ).eq( 1 ).val( $( this ).prop( 'checked' ) ? title : titlenoparen ); } ); } - $( '#infoContent input.required' ).on( 'input', function() { + $( '#infoList input.required' ).on( 'input', function() { var $this = $( this ); $this.css( 'border-color', $this.val() ? '' : 'red' ); - $( '#infoContent .scrobble' ).toggleClass( 'disabled', $this.val() === '' ); + $( '#infoList .scrobble' ).toggleClass( 'disabled', $this.val() === '' ); } ); - $( '#infoContent .lyrics' ).toggleClass( 'hide', ! S.lyrics ); - $( '#infoContent .album' ).toggleClass( 'hide', album === '' ); + $( '#infoList .lyrics' ).toggleClass( 'hide', ! S.lyrics ); + $( '#infoList .album' ).toggleClass( 'hide', album === '' ); if ( S.player === 'mpd' ) { var btnscrobble = S.scrobble && S.webradio; } else { var btnscrobble = S.scrobble && ! S.scrobbleconf[ S.player ]; } - $( '#infoContent .scrobble' ).toggleClass( 'hide', ! btnscrobble ); - $( '#infoContent' ).on( 'click', '.btnbottom span', function() { + $( '#infoList .scrobble' ).toggleClass( 'hide', ! btnscrobble ); + $( '#infoList' ).on( 'click', '.btnbottom span', function() { var values = infoVal(); var artist = values[ 0 ] var title = values[ 1 ] @@ -780,11 +786,11 @@ function infoUpdate( path ) { icon : 'refresh-library' , title : 'Library Database' , message : path ? ico( 'folder' ) +' '+ path +'' : '' - , radio : path ? '' : { 'Only changed files' : '', 'Rebuild entire database': 'rescan' } + , list : path ? '' : [ '', 'radio', { 'Only changed files' : '', 'Rebuild entire database': 'rescan' }, 'br' ] , beforeshow : () => { if ( ! C ) { - $( '#infoContent input' ).eq( 0 ).prop( 'disabled', true ); - $( '#infoContent input' ).eq( 1 ).prop( 'checked', true ); + $( '#infoList input' ).eq( 0 ).prop( 'disabled', true ); + $( '#infoList input' ).eq( 1 ).prop( 'checked', true ); } } , ok : () => bash( [ 'mpcupdate', path || infoVal(), 'CMD DIR' ] ) @@ -970,7 +976,7 @@ function playbackStatusGet( withdisplay ) { } ); } function playlistInsert( indextarget ) { - var plname = $( '#pl-path .lipath' ).text(); + var plname = $( '#savedpl-path .lipath' ).text(); bash( [ 'savedpledit', plname, 'add', indextarget, V.pladd.file, 'CMD NAME TYPE TO FILE' ], () => { renderSavedPlTrack( plname ); if ( indextarget === 'last' ) { @@ -981,7 +987,7 @@ function playlistInsert( indextarget ) { } function playlistInsertSelect( $this ) { var track = ''+ ( $this.index() + 1 ) +' - '+ $this.find( '.name' ).text(); - var content = `\ + var htmllist = `\ ${ V.pladd.title }
    ${ V.pladd.album }

    @@ -994,7 +1000,7 @@ ${ track } info( { icon : 'file-playlist' , title : 'Insert' - , content : content + , list : htmllist , values : [ 1 ] , buttonlabel : ico( 'undo' ) +'Select' , button : playlistInsertTarget @@ -1011,10 +1017,10 @@ function playlistInsertTarget() { +'
    '+ V.pladd.album +'
    ' +'Select where to add:' - , radio : { First : 1, Select: 'select', Last: 'last' } + , list : [ '', 'radio', { First : 1, Select: 'select', Last: 'last' }, 'br' ] , values : 'last' , beforeshow : () => { - $( '#infoContent input' ).eq( 1 ).on( 'click', function() { + $( '#infoList input' ).eq( 1 ).on( 'click', function() { local(); $( '#infoX' ).trigger( 'click' ); } ); @@ -1056,7 +1062,7 @@ function playlistFilter() { } } function playlistGet() { - list( { playlist: 'current' }, ( data ) => renderPlaylist( data ), 'json' ); + list( { playlist: 'current' }, data => renderPlaylist( data ), 'json' ); } function playlistRemove( $li ) { if ( $( '#pl-list li' ).length === 1 ) { @@ -1781,7 +1787,7 @@ function setPlaylistScroll() { var $stationname = $this.find( '.li2 .stationname' ); $stationname.addClass( 'hide' ); if ( S.state === 'stop' ) { - if ( S.webradio ) $name.text( $this.find( '.liname' ).text() ); + if ( $liactive.hasClass( 'webradio' ) ) $name.text( $this.find( '.liname' ).text() ); $stationname.addClass( 'hide' ); } else { if ( S.elapsed === false ) return diff --git a/srv/http/assets/js/main.js b/srv/http/assets/js/main.js index c61333164..06c702260 100644 --- a/srv/http/assets/js/main.js +++ b/srv/http/assets/js/main.js @@ -64,7 +64,7 @@ var icon_player = { } var vumeter = ' '; var chkdisplay = { - libmain : { + library : { album : ico( 'album' ) +'Album' , nas : ico( 'networks' ) +'Network' , albumartist : ico( 'albumartist' ) +'Album Artist' @@ -82,7 +82,7 @@ var chkdisplay = { , latest : ico( 'latest' ) +'Latest' , label : 'Label' } - , liboption : { + , libraryoption : { albumbyartist : ico( 'album' ) +'Album - Sort by artist' , albumyear : ico( 'album' ) +'Sort by artist > year' , tapaddplay : 'Select track = '+ ico( 'play-plus infomenusub' ) +'Add + Play' @@ -92,7 +92,7 @@ var chkdisplay = { , hidecover : 'Hide coverart band in tracks view' , fixedcover : 'Fix coverart band on large screen' } - , playback : { + , playback : { bars : 'Top-Bottom bars' , barsalways : 'Bars always on' , time : 'Time' @@ -107,7 +107,7 @@ var chkdisplay = { , '-' : '' , conductorname : ico( 'conductor' ) +'Conductor' } - , playlist : { + , playlist : { plclear : 'Confirm on'+ ico( 'replace' ) +'Replace'+ ico( 'play-replace sub' ) + '' , plsimilar : 'Confirm on '+ ico( 'lastfm' ) +'Add similar' , audiocdplclear : 'Clear on '+ ico( 'audiocd' ) +'Audio CD load' @@ -260,13 +260,13 @@ $( '#settings' ).on( 'click', '.submenu', function() { info( { icon : 'multiraudio' , title : 'Switch rAudio' - , radio : data.list + , list : [ '', 'radio', data.list, 'br' ] , values : currentip - , beforeshow : function() { - $( '#infoContent input' ).each( ( i, el ) => { + , beforeshow : () => { + $( '#infoList input' ).each( ( i, el ) => { if ( $( el ).val() === currentip ) $( el ).prop( 'disabled', true ); } ); - $( '#infoContent input' ).on( 'input', function() { + $( '#infoList input' ).on( 'input', function() { var ip = infoVal(); if ( typeof Android === 'object' ) Android.changeIP( ip ); loader(); @@ -289,12 +289,11 @@ $( '#displayplayback' ).on( 'click', function() { , title : 'Playback' , message : 'Show:Options:' , messagealign : 'left' - , checkbox : kv.checkbox - , checkcolumn : true + , list : kv.list , values : kv.values , checkchanged : true , beforeshow : () => { - var $chk = $( '#infoContent input' ); + var $chk = $( '#infoList input' ); var $el = {} kv.keys.forEach( ( k, i ) => $el[ k ] = $chk.eq( i ) ); function restoreEnabled() { @@ -378,7 +377,7 @@ $( '#displayplaylist' ).on( 'click', function() { , title : 'Playlist' , message : 'Options:' , messagealign : 'left' - , checkbox : kv.checkbox + , list : kv.list , values : kv.values , checkchanged : true , ok : displaySave @@ -605,7 +604,7 @@ $( '#volume' ).roundSlider( { $volumehandlerotate.css( 'transition-duration', speed +'ms' ); setTimeout( () => { $volumehandlerotate.css( 'transition-duration', '100ms' ); - $( '#volume-knob, #volmute' ).removeClass( 'disabled' ); + $( '#volume-knob, #button-volume i' ).removeClass( 'noclick' ); $( '#voldn' ).toggleClass( 'disabled', e.value === 0 ); $( '#volup' ).toggleClass( 'disabled', e.value === 100 ); }, speed ); @@ -618,7 +617,7 @@ $( '#volume' ).roundSlider( { , change : function( e ) { if ( V.drag ) return - $( '#volume-knob, #button-volume i' ).addClass( 'disabled' ); + $( '#volume-knob, #button-volume i' ).addClass( 'noclick' ); volumeSet( e.value ); $volumehandle.rsRotate( e.value ? -this._handle1.angle : -310 ); } @@ -1223,7 +1222,7 @@ $( '#lib-mode-list' ).on( 'click', function( e ) { var $img = V.list.li.find( '.bkcoverart' ); var icon = $img.length ? '' : ico( 'bookmark bl' ); - var content = `\ + var htmllist = `\
    ${ icon } ${ V.list.name } ${ V.list.path } @@ -1238,11 +1237,11 @@ $( '#lib-mode-list' ).on( 'click', function( e ) { info( { icon : 'playlist' , title : 'Add to Playlist' - , content : content + , list : htmllist , values : 'addplay' , beforeshow : () => { - $( '#infoContent .pllength' ).toggleClass( 'hide', ! S.pllength ); - $( '#infoContent' ).on( 'click', '.cmd', function() { + $( '#infoList .pllength' ).toggleClass( 'hide', ! S.pllength ); + $( '#infoList' ).on( 'click', '.cmd', function() { V.list.li = $( '.infomessage' ); V.mpccmd = V.action === 'playnext' ? [ 'mpcaddplaynext', V.list.path ] : [ 'mpcadd', V.list.path ]; V.action = $( this ).data( 'cmd' ); @@ -1313,7 +1312,7 @@ $( '#lib-mode-list' ).on( 'click', function( e ) { , title : 'Rename Bookmark' , message : '
    '+ ico( 'bookmark bookmark' ) +'
    '+ name +'
    ' - , textlabel : 'To:' + , list : [ 'To:', 'text' ] , values : name , checkblank : true , checkchanged : true @@ -1338,9 +1337,7 @@ $( '#lib-mode-list' ).on( 'click', function( e ) { icon : icon , title : 'Bookmark Thumbnail' , message : message - , filelabel : ico( 'folder-open' ) +'File' - , fileoklabel : ico( 'flash' ) +'Replace' - , filetype : 'image/*' + , file : { oklabel: ico( 'flash' ) +'Replace', type: 'image/*' } , buttonlabel : ! thumbnail ? '' : ico( 'bookmark' ) +'Default' , buttoncolor : ! thumbnail ? '' : orange , button : ! thumbnail ? '' : () => bash( [ 'bookmarkcoverreset', name, 'CMD NAME' ] ) @@ -1619,9 +1616,9 @@ $( '#button-pl-librandom' ).on( 'click', function() { icon : icon , title : title , message : 'Randomly add songs and play continuously.' - , checkbox : [ 'Start playing the random songs' ] + , list : [ 'Start playing the random songs', 'checkbox' ] , values : [ true ] - , beforeshow : () => $( '#infoContent table' ).toggleClass( 'hide', S.song + 1 === S.pllength ) + , beforeshow : () => $( '#infoList table' ).toggleClass( 'hide', S.song + 1 === S.pllength ) , ok : () => { S.librandom = true; $this.addClass( 'bl' ); @@ -1745,7 +1742,7 @@ $( '#pl-list' ).on( 'click', 'li', function( e ) { } else { intervalClear(); $( '.elapsed' ).empty(); - bash( [ 'mpcskip', $this.index() + 1, 'play', 'CMD POS PLAY' ] ); + bash( [ 'mpcskip', $this.index() + 1, 'play', 'CMD POS ACTION' ] ); $( '#pl-list li.active, #playback-controls .btn' ).removeClass( 'active' ); $this.add( '#play' ).addClass( 'active' ); } diff --git a/srv/http/assets/js/networks.js b/srv/http/assets/js/networks.js index 756bbc26b..671351656 100644 --- a/srv/http/assets/js/networks.js +++ b/srv/http/assets/js/networks.js @@ -42,12 +42,12 @@ $( '#listwlscan' ).on( 'click', 'li', function() { var encrypt = $this.data( 'encrypt' ); if ( encrypt === 'on' ) { info( { - icon : 'wifi' - , title : ssid - , passwordlabel : 'Password' - , focus : 0 - , oklabel : 'Connect' - , ok : () => connectWiFi( { ESSID: ssid, KEY: infoVal(), SECURITY: security } ) + icon : 'wifi' + , title : ssid + , list : [ 'Password', 'password' ] + , focus : 0 + , oklabel : 'Connect' + , ok : () => connectWiFi( { ESSID: ssid, KEY: infoVal(), SECURITY: security } ) } ); } else { connectWiFi( { ESSID: ssid } ); @@ -231,7 +231,10 @@ function infoLan() { info( { icon : icon , title : title - , textlabel : [ 'IP', 'Gateway' ] + , list : [ + [ 'IP', 'text' ] + , [ 'Gateway', 'text' ] + ] , focus : 0 , values : { IP: S.ipeth, GATEWAY: S.listeth.gateway } , checkchanged : true @@ -272,18 +275,21 @@ function infoWiFi( v ) { var values = default_v.dhcp; } info( { - icon : 'wifi' - , title : v ? 'Saved Connection' : 'Add Connection' - , tablabel : [ 'DHCP', 'Static IP' ] - , tab : [ '', () => infoWiFiTab( infoVal() ) ] - , boxwidth : 180 - , textlabel : [ 'SSID' ] - , passwordlabel : 'Password' - , checkbox : [ 'WEP', 'Hidden SSID' ] - , values : values - , checkblank : [ 0 ] - , checkchanged : ! V.wifistatic - , ok : () => connectWiFi( infoVal() ) + icon : 'wifi' + , title : v ? 'Saved Connection' : 'Add Connection' + , tablabel : [ 'DHCP', 'Static IP' ] + , tab : [ '', () => infoWiFiTab( infoVal() ) ] + , boxwidth : 180 + , list : [ + [ 'SSID', 'text' ] + , [ 'Password', 'password' ] + , [ 'WEP', 'checkbox' ] + , [ 'Hidden SSID', 'checkbox' ] + ] + , values : values + , checkblank : [ 0 ] + , checkchanged : ! V.wifistatic + , ok : () => connectWiFi( infoVal() ) } ); } function infoWiFiGet() { @@ -310,13 +316,18 @@ function infoWiFiStatic( v ) { , tablabel : [ 'DHCP', 'Static IP' ] , tab : [ () => infoWiFiTab( infoVal() ), '' ] , boxwidth : 180 - , textlabel : [ 'SSID', 'Password', 'IP', 'Gateway' ] - , checkbox : [ 'WEP', 'Hidden SSID' ] + , list : [ + [ 'SSID', 'text' ] + , [ 'Password', 'password' ] + , [ 'IP', 'text' ] + , [ 'Gateway', 'text' ] + , [ 'WEP', 'checkbox' ] + , [ 'Hidden SSID', 'checkbox' ] + ] , values : values , checkblank : [ 0 ] , checkchanged : V.wifistatic , checkip : [ 2, 3 ] - , beforeshow : () => $('#infoContent input' ).eq( 1 ).attr( 'type', 'password' ) , ok : () => connectWiFi( infoVal() ) } ); } diff --git a/srv/http/assets/js/passive.js b/srv/http/assets/js/passive.js index e5735bd94..41fa2b070 100644 --- a/srv/http/assets/js/passive.js +++ b/srv/http/assets/js/passive.js @@ -276,8 +276,8 @@ function psOrder( data ) { function psPlaylist( data ) { if ( V.local || V.sortable || $( '.pl-remove' ).length ) return - if ( 'skip' in data ) { - S.song = data.skip; + if ( 'song' in data ) { + $.each( data, ( k, v ) => S[ k ] = v ); if ( V.playlist ) setPlaylistScroll(); return } @@ -355,7 +355,7 @@ function psRelays( response ) { }, 1000 ); } else { if ( I.active ) { - $( '#infoContent .msg-r' ).html( response.message ); + $( '#infoList .msg-r' ).html( response.message ); return } @@ -368,7 +368,7 @@ function psRelays( response ) { , oknoreset : true , beforeshow : () => { $( '#infoX' ).addClass( 'hide' ); - if ( state === 'OFF' ) $( '#infoContent .msg-r' ).addClass( 'wh' ); + if ( state === 'OFF' ) $( '#infoList .msg-r' ).addClass( 'wh' ); } } ); } @@ -388,17 +388,14 @@ function psSavedPlaylists( data ) { if ( V.savedpl ) { count ? renderSavedPl( data ) : $( '#playlist' ).trigger( 'click' ); } else if ( V.savedpltrack ) { - if ( 'delete' in data && $( '#pl-path .lipath' ).text() === data.delete ) $( '#playlist' ).trigger( 'click' ); + if ( 'delete' in data && $( '#savedpl-path .lipath' ).text() === data.delete ) $( '#playlist' ).trigger( 'click' ); } $( '#button-pl-playlists' ).toggleClass( 'disabled', count === 0 ); $( '#mode-playlists gr' ).text( count || '' ); } function psVolume( data ) { V.volumeprev = S.volume; - if ( [ 'mute', 'unmute' ].includes( data.type ) ) { - V.local = false; // allow beforeValueChange() - $( '#volume-knob, #button-volume i' ).addClass( 'disabled' ); - } + if ( [ 'mute', 'unmute' ].includes( data.type ) ) V.local = false; // allow beforeValueChange() if ( data.type === 'mute' ) { S.volume = 0; S.volumemute = data.val; diff --git a/srv/http/assets/js/player.js b/srv/http/assets/js/player.js index b92f76517..42ec30c2f 100644 --- a/srv/http/assets/js/player.js +++ b/srv/http/assets/js/player.js @@ -6,7 +6,7 @@ $( '.btoutputall' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , checkbox : [ 'Enable all while Bluetooth connected' ] + , list : [ 'Enable all while Bluetooth connected', 'checkbox' ] , values : S.btoutputall , checkchanged : true , ok : () => { @@ -25,8 +25,7 @@ $( '#hwmixer' ).on( 'input', function() { bash( [ 'hwmixer', D.aplayname, $( this ).val(), 'CMD APLAYNAME HWMIXER' ] ); } ); $( '#setting-hwmixer, #setting-bluealsa' ).on( 'click', function() { - var bt = this.id === 'setting-bluealsa'; - if ( bt ) { + if ( this.id.slice( -1 ) === 'a' ) { var cmd = 'volumebt'; var cmd0db = 'volume0dbbt'; S.control = S.btaplayname; @@ -40,18 +39,28 @@ $( '#setting-hwmixer, #setting-bluealsa' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , rangelabel : bt ? S.control.replace( ' - A2DP', '' ) : S.control - , rangesub : 'dB' + , list : [ S.control.replace( ' - A2DP', '' ), 'range' ] + , prompt : warning , beforeshow : () => { + $( '.inforange' ).append( '
    ' ); + $( '#infoList input' ).on( 'input', function() { + volumeSetAt( +$( this ).val() ); + } ).on( 'touchend mouseup keyup', function() { + bash( [ 'volumepush' ] ); + } ); volumeInfoSet(); - $( '#infoContent' ).after( '
    '+ warning +'
    ' ); } - , rangechange : val => volumeSetAt( val ) - , rangestop : () => bash( [ 'volumepush' ] ) + , cancel : () => { + if ( ! $( '.infoprompt' ).hasClass( 'hide' ) ) { + local(); + $( '#infoList, .infoprompt' ).toggleClass( 'hide' ); + setTimeout( () => I.oknoreset = true, 300 ); + } + } , oklabel : ico( 'set0' ) +'0dB' , ok : () => { - if ( $( '#infoRange .sub' ).text() < '0 dB' && $( '.confirm' ).hasClass( 'hide' ) ) { - $( '#infoContent, .confirm' ).toggleClass( 'hide' ); + if ( parseFloat( $( '.inforange .sub' ).text() ) < 0 && $( '.infoprompt' ).hasClass( 'hide' ) ) { + $( '#infoList, .infoprompt' ).toggleClass( 'hide' ); } else { bash( [ cmd0db ] ); } @@ -79,10 +88,15 @@ $( '#setting-mixertype' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , rangelabel : 'MPD Software' + , list : [ 'MPD Software', 'range' ] , values : S.volumempd - , rangechange : val => volumeSetAt( val ) - , rangestop : val => volumePush( val, 'mpd' ) + , beforeshow : () => { + $( '#infoList input' ).on( 'input', function() { + volumeSetAt( +$( this ).val() ); + } ).on( 'touchend mouseup keyup', function() { + volumePush( +$( this ).val(), 'mpd' ); + } ); + } , okno : true } ); } ); @@ -123,9 +137,9 @@ $( '#setting-crossfade' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , numberlabel : 'Seconds' + , list : [ 'Seconds', 'number', { step: 1, min: 0, max: 10 } ] , focus : 0 - , boxwidth : 60 + , boxwidth : 70 , values : S.crossfadeconf , checkchanged : S.crossfade , checkblank : true @@ -139,7 +153,7 @@ $( '#setting-replaygain' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , radio : { Auto: 'auto', Album: 'album', Track: 'track' } + , list : [ '', 'radio', { Auto: 'auto', Album: 'album', Track: 'track' }, 'br' ] , footer : hardware ? '' : '' , values : S.replaygainconf , checkchanged : S.replaygain @@ -164,10 +178,8 @@ $( '#setting-buffer' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , numberlabel : 'audio_buffer_size (kB)' - , focus : 0 - , footer : '(default: 4096)' - , footeralign : 'right' + , message : 'audio_buffer_size' + , list : [ 'kB', 'number', { step: 1024, min: 1024, max: 8192 } ] , boxwidth : 110 , values : S.bufferconf , checkchanged : true @@ -180,10 +192,9 @@ $( '#setting-outputbuffer' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , numberlabel : 'max_output_buffer_size (kB)' + , message : 'max_output_buffer_size' + , list : [ 'kB', 'number', { step: 1024, min: 1024, max: 16384 } ] , focus : 0 - , footer : '(default: 8192)' - , footeralign : 'right' , boxwidth : 110 , values : S.outputbufferconf , checkchanged : true @@ -195,7 +206,8 @@ $( '#setting-outputbuffer' ).on( 'click', function() { $( '#setting-soxr' ).on( 'click', function() { S.soxrquality === 'custom' ? infoSoxrCustom() : infoSoxr(); } ); -var custominfo = `\ +$( '#setting-custom' ).on( 'click', function() { + var htmllist = `\
    ${ ico( 'artist wh' ) }
    ${ ico( 'music wh' ) }
    ... audio_output { ... - mixer_device "hw:N" + mixer_device "hw:${ S.asoundcard }"
    mpd.conf
    @@ -206,12 +218,11 @@ user                   "mpd"
     }
    `; -$( '#setting-custom' ).on( 'click', function() { bash( [ 'customget', D.aplayname, 'CMD APLAYNAME' ], val => { var val = val.split( '^^' ); var global = val[ 0 ].trim(); // remove trailing @@ -219,7 +230,7 @@ $( '#setting-custom' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , content : custominfo.replace( 'N', S.asoundcard ) + , list : htmllist , values : [ global, output ] , checkchanged : S.custom , cancel : switchCancel @@ -252,64 +263,12 @@ $( '#setting-custom' ).on( 'click', function() { } ); // document ready end <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -var soxr = `\ - - - - - - - -
    Quality
    Threads  -
    `; -var soxrcustom = ` -
    - - - - - - - - - - - - - - - - - - - -
    Precision bit
    Phase Response 0-100
    Passband End 0-100%
    Stopband Begin 100-150%
    Attenuation 0-30dB
    Bitmask Flag -
    `; -var warning = ` +var warning = `
    ${ iconwarning }Lower speakers / headphones volume - -Signal will be set to original level at 0dB. -Beware of too high volume. -
    `; +
    +
    Signal will be set to original level at 0dB. +
    Beware of too high volume. +
     `; function infoSoxr( quality ) { delete S.soxrconf.PLUGIN @@ -318,7 +277,10 @@ function infoSoxr( quality ) { , title : SW.title , tablabel : [ 'Presets', 'Custom' ] , tab : [ '', infoSoxrCustom ] - , content : soxr + , list : [ + [ 'Quality', 'select', { 'Very high': 'very high', High: 'high', Medium: 'medium', Low: 'low', Quick: 'quick' } ] + , [ 'Threads', 'radio', { Auto: 0, Single: 1 } ] + ] , values : S.soxrconf , checkblank : true , checkchanged : S.soxr @@ -329,16 +291,36 @@ function infoSoxr( quality ) { } function infoSoxrCustom() { delete S.soxrcustomconf.PLUGIN + var flag = { + 'Rolloff - Small' : 0 + , 'Rolloff - Medium' : 1 + , 'Rolloff - None' : 2 + , 'High precision' : 8 + , 'Double precision' : 16 + , 'Variable rate' : 32 + } info( { icon : SW.icon , title : SW.title , tablabel : [ 'Presets', 'Custom' ] , tab : [ infoSoxr, '' ] - , content : soxrcustom + , list : [ + [ 'Type', 'hidden' ] + , [ 'Precision', 'select', [ 16, 20, 24, 28, 32 ], 'bit' ] + , [ 'Phase Response', 'number', '0-100' ] + , [ 'Passband End', 'number', '0-100%' ] + , [ 'Stopband Begin', 'number', '100-150%' ] + , [ 'Attenuation', 'number', '0-30dB' ] + , [ 'Bitmask Flag', 'select', flag ] + ] , values : S.soxrcustomconf , checkblank : true , checkchanged : S.soxr - , boxwidth : 85 + , boxwidth : 105 + , beforeshow : () => { + $( '#infoList td' ).last().prop( 'colspan', 2 ); + $( '#infoList .select2-container' ).last().attr( 'style', 'width: 100% !important' ) + } , cancel : switchCancel , ok : switchEnable } ); @@ -401,7 +383,7 @@ function renderPage() { } $.each( S.lists, ( k, v ) => $( '#divlists .subhead[data-status="'+ k +'"]' ).toggleClass( 'hide', ! v ) ); $( '#divlists' ).toggleClass( 'hide', ! Object.values( S.lists ).includes( true ) ); - if ( I.rangelabel ) $( '#setting-'+ ( S.btaplayname ? 'bluealsa' : 'hwmixer' ) ).trigger( 'click' ); + if ( I.range ) $( '#setting-'+ ( S.btaplayname ? 'bluealsa' : 'hwmixer' ) ).trigger( 'click' ); showContent(); } function setMixerType( mixertype ) { @@ -415,34 +397,26 @@ function volumeGetPush() { function volumeInfoSet() { var val = S.volume.val || 0; var db = S.volume.db; - $( '#infoRange .value' ).text( val ); - $( '#infoRange input' ).val( val ); - $( '#infoRange .sub' ).text( val ? db +' dB' : 'Mute' ); + $( '.inforange .value' ).text( val ); + $( '.inforange input' ).val( val ); + $( '.inforange .sub' ).text( val ? db +' dB' : 'Mute' ); $( '#infoOk' ).toggleClass( 'disabled', db === 0 || db === '' ); V.local = false; } -function playbackButton() { - if ( S.pllength ) { - var btn = S.state === 'play' ? 'pause' : 'play'; - } else { - var btn = 'play disabled'; - } - $( '.playback' ).prop( 'class', 'playback i-'+ btn ); -} function psVolume( data ) { data.type === 'mpd' ? S.volumempd = data.val : S.volume = data; - if ( ! I.rangelabel ) return + if ( ! $( '.inforange' ).length ) return if ( data.type === 'mpd' ) { // info software volume - $( '#infoRange .value' ).text( S.volumempd ); - $( '#infoContent input' ).val( S.volumempd ); + $( '.inforange .value' ).text( S.volumempd ); + $( '#infoList input' ).val( S.volumempd ); return } clearTimeout( V.debounce ); V.debounce = setTimeout( () => { V.local = true; - $( '#infoContent' ).removeClass( 'hide' ); + $( '#infoList' ).removeClass( 'hide' ); $( '.confirm' ).addClass( 'hide' ); volumeInfoSet(); }, 300 ); diff --git a/srv/http/assets/js/settings.js b/srv/http/assets/js/settings.js index d4495d09d..a81d9c4f3 100644 --- a/srv/http/assets/js/settings.js +++ b/srv/http/assets/js/settings.js @@ -112,6 +112,14 @@ function notifyCommon( message ) { } banner( SW.icon +' blink', SW.title, message, -1 ); } +function playbackButton() { + if ( S.pllength ) { + var btn = S.state === 'play' ? 'pause' : 'play'; + } else { + var btn = 'play disabled'; + } + $( '.playback' ).prop( 'class', 'playback i-'+ btn ); +} function refreshData() { if ( page === 'guide' || ( I.active && ! I.rangelabel ) ) return @@ -124,7 +132,7 @@ function refreshData() { switchSet(); renderPage(); } else { - $( '#data' ).html( highlightJSON( S ) ) + page === 'camilla' ? renderPage() : $( '#data' ).html( highlightJSON( S ) ); $( '#button-data, #data' ).removeClass( 'hide' ); } } ); @@ -153,7 +161,7 @@ function switchEnable() { function switchIdIconTitle( id ) { id = id.replace( 'setting-', '' ); SW.id = id; - SW.title = $( '#div'+ id +' .name' ).text(); + SW.title = $( '#div'+ id +' .label' ).text(); if ( page === 'player' ) { SW.icon = $( '#divoptions #'+ id ).length ? 'mpd' : 'volume'; } else { @@ -186,7 +194,8 @@ function psOnMessage( message ) { switch ( channel ) { case 'bluetooth': psBluetooth( data ); break; case 'camilla': psCamilla( data ); break; - case 'mpdplayer': psMpdPlayer( data ); break; + case 'mpdplayer': + case 'mpdradio': psMpdPlayer( data ); break; case 'notify': psNotify( data ); break; // in common.js case 'player': psPlayer( data ); break; case 'power': psPower( data ); break; @@ -223,9 +232,9 @@ function psCamilla( data ) { $( '.tab input[type=range]' ).prop( { min: S.range.GAINMIN, max: S.range.GAINMAX } ); } function psMpdPlayer( data ) { - if ( ! [ '', 'camilla', 'player' ].includes( page ) ) return + if ( ! [ 'camilla', 'player' ].includes( page ) ) return - [ 'player', 'pllength', 'state' ].forEach( k => S[ k ] = data[ k ] ); + [ 'player', 'state' ].forEach( k => S[ k ] = data[ k ] ); playbackButton(); } function psPlayer( data ) { @@ -241,6 +250,8 @@ function psPlayer( data ) { function psRefresh( data ) { if ( data.page !== page ) return + if ( V.local && page === 'camilla' ) return + clearTimeout( V.debounce ); V.debounce = setTimeout( () => { $.each( data, ( k, v ) => { S[ k ] = v } ); // need braces @@ -348,7 +359,7 @@ $( '#button-data' ).on( 'click', function() { renderPage(); $( '#button-data, #data' ).addClass( 'hide' ); } ); -$( '.status .headtitle, .col-l.status' ).on( 'click', function() { +$( '.container' ).on( 'click', '.status .headtitle, .col-l.status', function() { var $this = $( this ); var id = $this.hasClass( 'col-l' ) ? $this.data( 'status' ) : $this.parent().data( 'status' ); var $code = $( '#code'+ id ); @@ -380,6 +391,7 @@ $( '.helphead' ).on( 'click', function() { } ); $( '.playback' ).on( 'click', function() { // for player and camilla S.state = S.state === 'play' ? 'pause' : 'play'; + if ( page === 'camilla' && S.state === 'pause' ) render.vuClear(); playbackButton(); bash( [ 'cmd.sh', 'mpcplayback' ] ); } ); diff --git a/srv/http/assets/js/simplekeyboard.js b/srv/http/assets/js/simplekeyboard.js index b59fa6c5f..073144caf 100644 --- a/srv/http/assets/js/simplekeyboard.js +++ b/srv/http/assets/js/simplekeyboard.js @@ -82,7 +82,7 @@ var inputs = 'input[type=text], input[type=textarea], input[type=pass $( 'body' ).on( 'click', inputs, function() { $kb.removeClass( 'hide' ); - $( '#infoContent input' ).removeClass( 'active' ); + $( '#infoList input' ).removeClass( 'active' ); $( this ).addClass( 'active' ); keyboard.setInput( $( this ).val() ); } ).on( 'click touchstart', function( e ) { @@ -102,7 +102,7 @@ function onChange( value ) { if ( $( 'input.active' ).prop( 'id' ) === 'pl-search-input' ) { playlistFilter(); } else { - $( '#infoContent input' ).trigger( 'keyup' ); + $( '#infoList input' ).trigger( 'keyup' ); } } function onKeyPress( key ) { // input value not yet changed until onChange diff --git a/srv/http/assets/js/system.js b/srv/http/assets/js/system.js index 748b1b130..8d2119107 100644 --- a/srv/http/assets/js/system.js +++ b/srv/http/assets/js/system.js @@ -100,8 +100,17 @@ var board2bcm = { 3:2, 5:3, 7:4, 8:14, 10:15, 11:17, 12:18, 13:27, 15:22, 16:23, 18:24, 19:10, 21:9 , 22:25, 23:11, 24:8, 26:7, 29:5, 31:6, 32:12, 33:13, 35:19, 36:16, 37:26, 38:20, 40:21 } -var html_optionpin = htmlOption( board2bcm ); -var html_boardpin = htmlOption( Object.keys( board2bcm ) ); +var lcdcharaddr = S.lcdcharaddr || [ 39, 63 ]; +var i2caddress = {}; +lcdcharaddr.forEach( el => i2caddress[ '0x'+ el.toString( 16 ) ] = el ); +var lcdcharlist = [ + [ 'Type', 'hidden' ] + , [ 'Size', 'radio', { '20x4': 20, '16x2': 16 } ] + , [ 'Character Map', 'radio', [ 'A00', 'A02' ] ] + , [ 'Address', 'radio', i2caddress ] + , [ 'I²C Chip', 'select', [ 'PCF8574', 'MCP23008', 'MCP23017' ] ] + , [ 'Sleep (60s)', 'checkbox' ] +]; var tabshareddata = [ 'CIFS', 'NFS', ico( 'rserver' ) +' rAudio' ]; $( function() { // document ready start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -125,7 +134,7 @@ $( '.close' ).off( 'click' ).on( 'click', function() { // off close in settings. } var line = 'Reboot required for:

    '; - list.split( '\n' ).forEach( id => line += ico( id ) + $( '#div'+ id +' .name' ).text() +'\n' ); + list.split( '\n' ).forEach( id => line += ico( id ) + $( '#div'+ id +' .label' ).text() +'\n' ); info( { icon : page , title : 'System Setting' @@ -263,7 +272,7 @@ $( '#setting-softlimit' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , radio : { '65°C': 65, '70°C': 70, '75°C': 75 } + , list : [ '', 'radio', { '65°C': 65, '70°C': 70, '75°C': 75 } ] , values : S.softlimitconf || default_v.softlimit , checkchanged : S.softlimit , cancel : switchCancel @@ -275,7 +284,7 @@ $( '#setting-hddsleep' ).on( 'click', function() { icon : SW.icon , title : SW.title , message : 'Timer:' - , radio : { '2 minutes': 24, '5 minutes': 60, '10 minutes': 120 } + , list : [ '', 'radio', { '2 minutes': 24, '5 minutes': 60, '10 minutes': 120 }, 'br' ] , values : { APM: S.hddsleep } || default_v.hddsleep , checkchanged : S.hddsleep , cancel : switchCancel @@ -286,7 +295,10 @@ $( '#setting-bluetooth' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , checkbox : [ 'Discoverable by senders', 'Sampling 16bit 44.1kHz to receivers' ] + , list : [ + [ 'Discoverable by senders', 'checkbox' ] + , [ 'Sampling 16bit 44.1kHz to receivers', 'checkbox' ] + ] , values : S.bluetoothconf || default_v.bluetooth , checkchanged : S.bluetooth , cancel : switchCancel @@ -295,16 +307,13 @@ $( '#setting-bluetooth' ).on( 'click', function() { } ); $( '#setting-wlan' ).on( 'click', function() { bash( [ 'regdomlist' ], list => { - var options = htmlOption( list ); - var infowifi = `\ - - - -
    Country
    `; info( { icon : SW.icon , title : SW.title - , content : infowifi + , list : [ + [ 'Country', 'select', list ] + , [ 'Auto start Access Point', 'checkbox' ] + ] , boxwidth : 250 , values : S.wlanconf || default_v.wlan , checkchanged : S.wlan @@ -340,7 +349,7 @@ $( '#setting-i2smodule' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , checkbox : [ 'Disable I²S HAT EEPROM read' ] + , list : [ 'Disable I²S HAT EEPROM read', 'checkbox' ] , values : S.i2seeprom , checkchanged : S.i2seeprom , ok : () => bash( infoVal() ? [ 'i2seeprom' ] : [ 'i2seeprom', 'OFF' ] ) @@ -373,25 +382,20 @@ $( '#setting-relays' ).on( 'click', function() { S.relays ? infoRelays() : infoRelaysName(); } ); $( '#setting-rotaryencoder' ).on( 'click', function() { - var pin = ''; - var inforotaryencoder = `\ - -${ pin } -${ pin } -${ pin } - - - - -
    CLK
    DT
    SW
    Step
    `; info( { icon : SW.icon , title : SW.title - , content : gpiosvg + inforotaryencoder + , message : gpiosvg + , list : [ + [ 'CLK', 'select', board2bcm ] + , [ 'DT', 'select', board2bcm ] + , [ 'SW', 'select', board2bcm ] + , [ 'Step', 'radio', { '1%': 1, '2%': 2 } ] + ] , boxwidth : 70 , values : S.rotaryencoderconf || default_v.rotaryencoder , checkchanged : S.rotaryencoder - , beforeshow : () => $( '#infoContent svg .power' ).remove() + , beforeshow : () => $( '#infoList svg .power' ).remove() , cancel : switchCancel , ok : switchEnable , fileconf : true @@ -409,8 +413,10 @@ $( '#setting-mpdoled' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , selectlabel : [ 'Controller', 'Refresh (baud)' ] - , select : [ chip, [ 800000, 1000000, 1200000 ] ] + , list : [ + [ 'Controller', 'select', chip ] + , [ 'Refresh', 'select', [ 800000, 1000000, 1200000 ], 'baud' ] + ] , values : S.mpdoledconf , checkchanged : S.mpdoled , boxwidth : 140 @@ -433,14 +439,14 @@ $( '#setting-tft' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , selectlabel : 'Type' - , select : { - 'Generic' : 'tft35a' - , 'Waveshare (A)' : 'waveshare35a' - , 'Waveshare (B)' : 'waveshare35b' - , 'Waveshare (B) Rev 2.0' : 'waveshare35b-v2' - , 'Waveshare (C)' : 'waveshare35c' - } + , list : [ 'Type', 'select', { + 'Generic' : 'tft35a' + , 'Waveshare (A)' : 'waveshare35a' + , 'Waveshare (B)' : 'waveshare35b' + , 'Waveshare (B) Rev 2.0' : 'waveshare35b-v2' + , 'Waveshare (C)' : 'waveshare35c' + } + ] , values : { MODEL: S.tftconf || 'tft35a' } , checkchanged : S.tft , boxwidth : 190 @@ -462,14 +468,13 @@ $( '#setting-tft' ).on( 'click', function() { } ); } ); $( '#setting-vuled' ).on( 'click', function() { - var htmlpins = ''; - for ( i = 1; i < 8; i++ ) { - htmlpins += ''+ i +'/7'; - } + var list = []; + for ( i = 1; i < 8; i++ ) list.push( [ i +'/7', 'select', board2bcm ] ); info( { icon : SW.icon , title : SW.title - , content : gpiosvg +''+ htmlpins +'
    ' + , message : gpiosvg + , list : list , values : S.vuledconf || default_v.vuled , checkchanged : S.vuled , boxwidth : 70 @@ -482,13 +487,18 @@ $( '#ledcalc' ).on( 'click', function() { info( { icon : 'led' , title : 'LED Resister Calculator' - , textlabel : [ 'GPIO (V)', 'Current (mA)', 'LED forward voltage (V)', 'Resister (Ω)' ] + , list : [ + [ 'GPIO (V)', 'number' ] + , [ 'Current (mA)', 'number' ] + , [ 'LED forward voltage (V)', 'number' ] + , [ 'Resister (Ω)', 'number' ] + ] , focus : 0 , values : [ 3.3, 5 ] , boxwidth : 70 , beforeshow : () => { - $( '#infoContent input' ).prop( 'disabled', 1 ); - $( '#infoContent input' ).eq( 2 ) + $( '#infoList input' ).prop( 'disabled', 1 ); + $( '#infoList input' ).eq( 2 ) .prop( 'disabled', 0 ) .on( 'input', function() { var fv = $( this ).val(); @@ -497,7 +507,7 @@ $( '#ledcalc' ).on( 'click', function() { } else { var ohm = fv ? Math.round( ( 3.3 - fv ) / 0.005 ) : ''; } - $( '#infoContent input' ).eq( 3 ).val( ohm ); + $( '#infoList input' ).eq( 3 ).val( ohm ); } ); } , okno : true @@ -510,13 +520,13 @@ $( '#hostname' ).on( 'mousedown touchdown', function() { info( { icon : SW.icon , title : SW.title - , textlabel : 'Name' + , list : [ 'Name', 'text' ] , focus : 0 , values : { NAME: S.hostname } , checkblank : true , checkchanged : true , beforeshow : () => { - $( '#infoContent input' ).on( 'input', function() { + $( '#infoList input' ).on( 'input', function() { $( this ).val( $( this ).val().replace( /[^a-zA-Z0-9-]+/g, '' ) ); } ); } @@ -545,8 +555,12 @@ $( '#setting-soundprofile' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , numberlabel : [ 'Swappiness', 'Maximum Transmission Unit (B)', 'Transmit Queue Length' ] - , boxwidth : 80 + , list : [ + [ 'Swappiness', 'number' ] + , [ 'Max Transmission Unit', 'number', 'byte' ] + , [ 'Transmit Queue Length', 'number' ] + ] + , boxwidth : 70 , values : S.soundprofileconf , checkchanged : true , checkblank : true @@ -559,7 +573,7 @@ $( '#setting-volumeboot' ).on( 'click', function() { info( { icon : SW.icon , title : SW.title - , rangelabel : 'Volume' + , list : [ 'Volume', 'range' ] , values : S.volumebootconf , checkchanged : S.volumeboot , cancel : switchCancel @@ -568,10 +582,16 @@ $( '#setting-volumeboot' ).on( 'click', function() { } ); } ); $( '#backup' ).on( 'click', function() { + var d = new Date(); + var month = '0'+ ( d.getMonth() + 1 ); + var date = '0'+ d.getDate(); + var ymd = d.getFullYear() + month.slice( -2 ) + date.slice( -2 ); info( { icon : SW.icon , title : SW.title - , message : 'Save all data and settings to file?' + , message : 'Save all data and settings' + , list : [ 'Filename', 'text', '.gz' ] + , values : 'rAudio_backup-'+ ymd , ok : () => { notifyCommon( 'Process ...' ); bash( [ 'settings/system-databackup.sh' ], data => { @@ -584,7 +604,7 @@ $( '#backup' ).on( 'click', function() { var a = document.createElement( 'a' ); a.style.display = 'none'; a.href = url; - a.download = 'backup.gz'; + a.download = infoVal() +'.gz'; document.body.appendChild( a ); a.click(); setTimeout( () => { @@ -707,22 +727,6 @@ function i2sSelectShow() { $( '#divi2smodulesw' ).addClass( 'hide' ); $( '#divi2smodule, #setting-i2smodule' ).removeClass( 'hide' ); } -var htmllcdchar = { - common : ` - - - - - - - - - -` - , sleep : `\ -
    Size
    Character Map
    -` -} function htmlOption( values ) { var options = ''; if ( Array.isArray( values ) ) { @@ -733,31 +737,14 @@ function htmlOption( values ) { return options } function infoLcdChar() { - var lcdcharaddr = S.lcdcharaddr || [ 39, 63 ]; - var i2caddress = ''; - lcdcharaddr.forEach( el => { - i2caddress += ''; - } ); - var options = htmlOption( [ 'PCF8574', 'MCP23008', 'MCP23017' ] ); - var content = ` -${ htmllcdchar.common } -Address${ i2caddress } -I²C Chip - - -${ htmllcdchar.sleep } -`; info( { icon : SW.icon , title : SW.title , tablabel : [ 'I²C', 'GPIO' ] , tab : [ '', infoLcdCharGpio ] - , content : content + , list : lcdcharlist , boxwidth : 180 - , values : values2info( - Object.keys( default_v.lcdchar_i2c ) - , S.lcdcharconf || default_v.lcdchar_i2c - ) + , values : S.lcdcharconf || default_v.lcdchar_i2c , checkchanged : S.lcdchar && S.lcdcharconf.INF === 'i2c' , beforeshow : infoLcdcharButton , cancel : switchCancel @@ -766,27 +753,17 @@ ${ htmllcdchar.sleep } } ); } function infoLcdCharGpio() { - var optpins = ''; - var content = ` -${ gpiosvg } -${ htmllcdchar.common } - - - - -${ htmllcdchar.sleep } -`; + var list = lcdcharlist.slice( 0, 3 ); + [ 'RS', 'RW', 'E', 'D4', 'D5', 'D6', 'D7' ].forEach( k => list.push( [ k, 'select', board2bcm ] ) ); + list.push( lcdcharlist.slice( -1 )[ 0 ] ); info( { icon : SW.icon , title : SW.title , tablabel : [ 'I²C', 'GPIO' ] , tab : [ infoLcdChar, '' ] - , content : content - , boxwidth : 180 - , values : values2info( - Object.keys( default_v.lcdchar_gpio ) - , S.lcdcharconf || default_v.lcdchar_gpio - ) + , list : list + , boxwidth : 70 + , values : S.lcdcharconf || default_v.lcdchar_gpio , checkchanged : S.lcdchar && S.lcdcharconf.INF === 'gpio' , beforeshow : infoLcdcharButton , cancel : switchCancel @@ -795,7 +772,7 @@ ${ htmllcdchar.sleep } } ); } function infoLcdcharButton() { - $( '#infoContent svg .power' ).remove(); + $( '#infoList svg .power' ).remove(); if ( ! S.lcdchar || S.lcdcharreboot ) return $( '#infoOk' ) @@ -805,34 +782,6 @@ function infoLcdcharButton() { bash( [ 'lcdcharset', this.id.slice( 3 ), 'CMD ACTION' ] ) } ); } -var contentmount = { - common : `\ - -
    RS${ optpins }RW${ optpins }E${ optpins }
    D4${ optpins }D5${ optpins }D6${ optpins }D7${ optpins }
    - - - - - - - - -` - , cifs : `\ - - - - - -` - , option : `\ - - - -
    Name *
    Server IP *
    Share *
    User
    Password
    Options
    ` -} function infoMirror() { SW.id = 'mirror'; SW.title = 'Servers'; @@ -841,8 +790,7 @@ function infoMirror() { , title : SW.title , tablabel : [ 'Time', 'Package Mirror' ] , tab : [ infoNtp, '' ] - , selectlabel : 'Mirror' - , select : V.htmlmirror + , list : [ 'Mirror', 'select', V.htmlmirror ] , boxwidth : 240 , values : { MIRROR: S.mirror } , checkchanged : true @@ -856,7 +804,7 @@ function infoMirrorList() { } else { notifyCommon( 'Get mirror server list ...' ); bash( [ 'mirrorlist' ], list => { - V.htmlmirror = htmlOption( list ); + V.htmlmirror = list; infoMirror(); bannerHide(); }, 'json' ); @@ -865,25 +813,29 @@ function infoMirrorList() { function infoMount( nfs ) { var nfs = nfs || false; var shareddata = SW.id === 'shareddata'; - if ( I.active && $input.length ) { - var v = infoVal(); - if ( 'USER' in v || nfs ) var nfs = true; - v.PROTOCOL = nfs ? 'nfs' : 'cifs'; - var values = values2info( Object.keys( default_v[ nfs ? 'mountnfs' : 'mountcifs' ] ), v ); - } else { - var values = default_v.mountcifs; - values.IP = S.ipsub; - } - var tab = nfs ? [ infoMount, '' ] : [ '', infoMount ]; + var values = default_v.mountcifs; + values.IP = S.ipsub; + var tab = nfs ? [ infoMount, '' ] : [ '', () => infoMount( 'nfs' ) ]; if ( shareddata ) tab.push( infoMountRserver ); var icon = 'networks'; var title = shareddata ? 'Shared Data Server' : 'Add Network Storage'; + var list = [ + [ 'Type', 'hidden' ] + , [ 'Name', 'text' ] + , [ 'Server IP', 'text' ] + , [ 'Share', 'text' ] + , [ 'User', 'text'] + , [ 'Password', 'password' ] + , [ 'Options', 'text' ] + ]; + if ( nfs ) list.splice( 3, 2 ); info( { icon : icon , title : title , tablabel : shareddata ? tabshareddata : [ 'CIFS', 'NFS' ] , tab : tab - , content : contentmount.common + ( nfs ? '' : contentmount.cifs ) + contentmount.option + , list : list + , prompt : true , values : values , checkblank : [ 0, 2 ] , checkip : [ 1 ] @@ -907,57 +859,42 @@ function infoMount( nfs ) { var keys = Object.keys( infoval ); var vals = Object.values( infoval ); notify( icon, title, shareddata ? 'Enable ...' : 'Add ...' ); - bash( [ 'mount', ...vals, 'CMD '+ keys.join( ' ' ) ], error => { - if ( error ) { - info( { - icon : icon - , title : title - , message : error - , ok : () => setTimeout( infoMount, 0 ) - } ); - bannerHide(); - } else { - refreshData(); - } - } ); + bash( [ 'mount', ...vals, 'CMD '+ keys.join( ' ' ) ], error => infoMountSet( error ) ); } } ); } function infoMountRserver() { info( { - icon : SW.icon - , title : SW.title - , tablabel : tabshareddata - , tab : [ infoMount, () => infoMount( 'nfs' ), '' ] - , textlabel : 'Server IP' - , values : { IP: I.active && I.values ? infoVal().IP : S.ipsub } - , checkip : [ 0 ] - , cancel : switchCancel - , ok : () => { + icon : SW.icon + , title : SW.title + , tablabel : tabshareddata + , tab : [ infoMount, () => infoMount( 'nfs' ), '' ] + , list : [ 'Server IP', 'text' ] + , prompt : true + , values : { IP: I.active && I.values ? infoVal().IP : S.ipsub } + , checkip : [ 0 ] + , cancel : switchCancel + , ok : () => { notify( SW.icon, SW.title, 'Connect Server rAudio ...' ); - bash( [ 'mount', infoVal().IP, 'CMD IP' ], error => { - bannerHide(); - if ( error ) { - info( { - icon : SW.icon - , title : SW.title - , message : error - , cancel : switchCancel - , ok : () => setTimeout( infoMountRserver, 0 ) - } ); - return - } - } ); + bash( [ 'mount', infoVal().IP, 'CMD IP' ], error => infoMountSet( error ) ); } } ); } +function infoMountSet( error ) { + if ( error ) { + infoPrompt( 'Mount failed:

    '+ error ); + } else { + $( '#infoX' ).trigger( 'click' ); + } + bannerHide(); +} function infoNtp() { SW.id = 'ntp'; SW.title = 'Servers'; var json = { icon : SW.icon , title : SW.title - , textlabel : 'NTP' + , list : [ 'NTP', 'text' ] , boxwidth : 240 , values : { NTP: S.ntp } , checkchanged : true @@ -971,30 +908,29 @@ function infoNtp() { info( json ); } function infoPowerbutton() { - var optionpin = htmlOption( Object.keys( board2bcm ) ); - var infopowerbutton = `\ - - - - - -
    On
    Off
    LED
    Reserved
    -`; + var pins = Object.keys( board2bcm ); info( { icon : SW.icon , title : SW.title , tablabel : [ 'Generic', 'Audiophonic' ] , tab : [ '', infoPowerbuttonAudiophonics ] - , content : gpiosvg + infopowerbutton + , message : gpiosvg + , list : [ + [ 'On', 'text' ] + , [ 'Off', 'select', pins ] + , [ 'LED', 'select', pins ] + , [ 'Reserved', 'select', pins ] + ] , boxwidth : 70 , values : S.powerbuttonconf || default_v.powerbutton , checkchanged : S.powerbutton , beforeshow : () => { - var $sw = $( '#infoContent select' ).eq( 0 ); - var $reserved = $( '#infoContent .reserved' ); - $reserved.toggleClass( 'hide', $sw.val() == 5 ); + $( '#infoList input' ).addClass( 'disabled' ); + var $sw = $( '#infoList select' ).eq( 0 ); + var $trreserved = $( '#infoList tr' ).last(); + $trreserved.toggleClass( 'hide', $sw.val() == 5 ); $sw.on( 'input', function() { - $reserved.toggleClass( 'hide', $( this ).val() == 5 ); + $trreserved.toggleClass( 'hide', $( this ).val() == 5 ); } ); } , cancel : switchCancel @@ -1008,7 +944,7 @@ function infoPowerbuttonAudiophonics() { , title : SW.title , tablabel : [ 'Generic', 'Audiophonic' ] , tab : [ infoPowerbutton, '' ] - , checkbox : 'Power management module' + , list : [ 'Power management module', 'checkbox' ] , checkchanged : S.powerbutton , values : { ON: S.poweraudiophonics } , cancel : switchCancel @@ -1027,29 +963,42 @@ function infoRelays() { } } ); var option_delay = htmlOption( [ ...Array(10).keys() ] ); - var td_name = ''; + var td_name = ''; var tr_name = ''+ td_name + td_name +''; - var td_delay = 'sec.'; + var td_delay = ''+ ico( 'remove updn dn' ) + ico( 'plus-circle updn up' ) +''; var tr_delay = ''+ td_delay + td_delay +''; - var content = ''+ ico( 'power grn' ) +' On'+ ico( 'power red' ) +' Off'; + var list = ''+ ico( 'power grn' ) +' On (s)'+ ico( 'power red' ) +' Off (s)'; for ( i = 0; i < pL; i++ ) { - content += tr_name; - if ( i < ( pL -1 ) ) content += tr_delay; + list += tr_name; + if ( i < ( pL -1 ) ) list += tr_delay; } - content += ''+ ico( 'stoptimer yl' ) +' Idle' - +'min. to '+ ico( 'power red' ) +' Off'; + list += ''+ ico( 'stoptimer yl' ) +' Idle to Off (m)' + +''+ ico( 'remove updn dn' ) + ico( 'plus-circle updn up' ) +''; info( { icon : SW.icon , title : SW.title , tablabel : [ 'Sequence', 'Name' ] , tab : [ '', infoRelaysName ] - , content : ''+ content +'
    ' - , contentcssno : true + , list : ''+ list +'
    ' , values : values , checkchanged : S.relays , beforeshow : () => { - $( '#infoContent td' ).css( { width: '90px', padding: '0 0 0 5px' } ); - $( 'tr:even .select2-selection__rendered' ).css( 'background', 'var( --cgd )' ); + $( '#infoList td:first-child' ).css( 'text-align', 'left' ); + $( '#infoList tr:last-child td:first-child' ).css( 'text-align', '' ); + var min = 0; + var max = 10; + $( '#infoList .updn' ).on( 'touchend mouseup keyup', function() { + var $this = $( this ); + var up = $this.hasClass( 'up' ); + var $up = up ? $this : $this.next(); + var $dn = up ? $this.prev() : $this; + var $num = $this.parent().prev().find( 'input' ); + var val = +$num.val(); + up ? val++ : val--; + $num.val( val ); + $up.toggleClass( 'disabled', val === max ); + $dn.toggleClass( 'disabled', val === min ); + } ); } , cancel : switchCancel , ok : infoRelaysCmd @@ -1093,36 +1042,40 @@ function infoRelaysName() { var name = S.relaysnameconf || default_v.relaysname; var values = []; $.each( name, ( k, v ) => values.push( k, v ) ); - var pin_name = ''; - var content = ''+ ico( 'gpiopins bl' ) +' Pin'+ ico( 'tag bl' ) +' Name'; - for( i = 0; i < 4; i++ ) content += pin_name; + var pin_name = ''; + var list = ''+ ico( 'gpiopins bl' ) +' Pin'+ ico( 'tag bl' ) +' Name'; + for( i = 0; i < 4; i++ ) list += pin_name; info( { icon : SW.icon , title : SW.title , tablabel : [ 'Sequence', 'Name' ] , tab : [ infoRelays, '' ] - , content : gpiosvg + '
     '+ content +'

    ' + , list : gpiosvg + '
     '+ list +'

    ' , values : values , checkchanged : S.relays , beforeshow : () => { - $( '#infoContent tr td:first-child' ).css( { 'text-align': 'left', width: '70px' } ); - $( '#infoContent tr td:last-child' ).css( 'width', '160px' ); + $( '#infoList td:first-child' ).css( { 'text-align': 'left', width: '75px' } ); + $( '#infoList td:last-child' ).css( 'width', '160px' ); } , cancel : switchCancel , ok : infoRelaysCmd } ); } function infoRestore( reset ) { + var list = [ + [ 'Keep Library data', 'checkbox' ] + , [ 'Keep Network settings', 'checkbox' ] + ]; info( { - icon : SW.icon - , title : SW.title - , tablabel : [ 'From Backup', 'Reset To Default' ] - , tab : reset ? [ infoRestore, '' ] : [ '', () => infoRestore( 'reset' ) ] - , checkbox : reset ? [ 'Keep Library data', 'Keep Network settings' ] : [ 'Library data only' ] - , fileoklabel : reset ? '' : ico( 'restore' ) +'Restore' - , filetype : '.gz' - , okcolor : orange - , ok : reset ? () => { + icon : SW.icon + , title : SW.title + , tablabel : [ 'From Backup', 'Reset To Default' ] + , tab : reset ? [ infoRestore, '' ] : [ '', () => infoRestore( 'reset' ) ] + , list : reset ? list : list[ 0 ] + , file : reset ? '' : { oklabel: ico( 'restore' ) +'Restore', type : '.gz' } + , oklabel : ico( 'restore' ) +'Restore' + , okcolor : orange + , ok : reset ? () => { notifyCommon( 'Reset to default ...' ); bash( [ 'settings/system-datareset.sh '+ infoVal().join( ' ' ) ] ); loader(); @@ -1228,8 +1181,3 @@ function renderStorage() { .empty() .addClass( 'hide' ); } -function values2info( keys, v ) { - var values = {} - keys.forEach( k => values[ k ] = v[ k ] || '' ); - return values -} diff --git a/srv/http/bash/bluetoothcommand.sh b/srv/http/bash/bluetoothcommand.sh index e8727e17a..68360e1e7 100644 --- a/srv/http/bash/bluetoothcommand.sh +++ b/srv/http/bash/bluetoothcommand.sh @@ -19,6 +19,7 @@ disconnectRemove() { [[ ! $type ]] && type=$( bluetoothctl info $mac | sed -E -n '/UUID: Audio/ {s/\s*UUID: Audio (.*) .*/\1/; p}' | xargs ) sed -i "/^$mac/ d" $dirshm/btconnected [[ ! $( awk NF $dirshm/btconnected ) ]] && rm $dirshm/btconnected + mpc -q stop $dirbash/cmd.sh playerstop if [[ $type == Source ]]; then icon=btsender diff --git a/srv/http/bash/cmd-skipdata.sh b/srv/http/bash/cmd-skipdata.sh index 7ee3a7ecd..6584bdcdd 100644 --- a/srv/http/bash/cmd-skipdata.sh +++ b/srv/http/bash/cmd-skipdata.sh @@ -1,7 +1,5 @@ #!/bin/bash -# cmd-trackdata.sh POS - . /srv/http/bash/common.sh . <( mpc playlist -f 'album="%album%"; artist="%artist%"; composer="%composer%"; conductor="%conductor%"; file="%file%"; time=%time%; title="%title%"' | sed "$1 q;d" ) diff --git a/srv/http/bash/cmd.sh b/srv/http/bash/cmd.sh index 29756dc74..aa1e59a20 100644 --- a/srv/http/bash/cmd.sh +++ b/srv/http/bash/cmd.sh @@ -386,7 +386,16 @@ equalizer ) pushData equalizer $( < $dirsystem/equalizer.json ) ;; equalizerget ) - cat $dirsystem/equalizer.json 2> /dev/null || echo false + if [[ -e $dirsystem/equalizer.json ]]; then + cat $dirsystem/equalizer.json + else + echo '{ + "active" : "Flat" +, "preset" : { + "Flat": [ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62 ] + } +}' + fi ;; equalizerset ) # slide sudo -u $USER amixer -MqD equal sset "$BAND" $VAL @@ -607,6 +616,15 @@ mpcsimilar ) notify lastfm 'Add Similar' "$added tracks added." ;; mpcskip ) + if [[ $ACTION ]]; then # playlist + mpc -q play $POS + Time=$( mpc status %totaltime% | awk -F: '{print ($1 * 60) + $2}' ) + [[ $Time == 0 ]] && Time=false + [[ $ACTION == stop ]] && mpc -q stop + pushData playlist '{ "song": '$(( POS - 1 ))', "elapsed": 0, "Time": '$Time', "state": "'$ACTION'" }' + exit + fi + touch $dirshm/skip . <( mpc status 'state=%state%; consume=%consume%' ) $dirbash/cmd-pskipdata.sh $POS & @@ -622,7 +640,7 @@ mpcskip ) rm -f $dirshm/skip [[ ! $PLAY ]] && mpc -q stop fi - [[ -e $dirsystem/librandom ]] && plAddRandom || pushData playlist '{ "skip": '$(( POS - 1 ))' }' + [[ -e $dirsystem/librandom ]] && plAddRandom || pushData playlist '{ "song": '$(( POS - 1 ))' }' ;; mpcupdate ) if [[ $DIR ]]; then @@ -687,15 +705,11 @@ savedpledit ) # $DATA: remove - file, add - position-file, move - from-to pushSavedPlaylist ;; savedplrename ) - plfile="$dirplaylists/$NEWNAME.m3u" - if [[ $REPLACE ]]; then - rm -f "$plfile" - elif [[ -e "$plfile" ]]; then - echo -1 - exit + if [[ ! $REPLACE ]]; then + mpc lsplaylists | grep -q "$NEWNAME" && echo -1 && exit fi - mv "$dirplaylists/$NAME.m3u" "$plfile" + mpc renplaylist "$NAME" "$NEWNAME" pushSavedPlaylist ;; savedplsave ) diff --git a/srv/http/bash/common.sh b/srv/http/bash/common.sh index 36045df02..793bdc7b4 100644 --- a/srv/http/bash/common.sh +++ b/srv/http/bash/common.sh @@ -241,26 +241,6 @@ notify() { # icon title message delayms fi websocat ws://$ip:8080 <<< $( tr -d '\n' <<< $data ) } -package() { - local file urlio - urlio=https://github.com/rern/rern.github.io/raw/main - file=$( dialog --colors --no-shadow --no-collapse --output-fd 1 --nocancel --menu " -Package: -" 8 0 0 \ -1 Build \ -2 'Update repo' \ -3 'AUR setup' \ -4 'Create regdomcodes.json' \ -5 'Create guide.tar.xz' ) - case $file in - 1 ) file=pkgbuild;; - 2 ) file=repoupdate;; - 3 ) file=aursetup;; - 4 ) bash <( curl -L $urlio/wirelessregdom.sh ); exit;; - 5 ) bsdtar cjvf guide.tar.xz -C /srv/http/assets/img/guide .; exit;; - esac - bash <( curl -L $urlio/$file.sh ) -} packageActive() { local active pkg pkgs status pkgs=$@ @@ -319,13 +299,15 @@ pushRefresh() { $dirsettings/$page-data.sh $push } radioStatusFile() { - status=$( grep -vE '^Album|^Artist|^coverart|^elapsed|^Title' $dirshm/status ) + status=$( grep -vE '^Album|^Artist|^coverart|^elapsed|^state|^Title' $dirshm/status ) status+=' Artist="'$artist'" Album="'$album'" coverart="'$coverart'" -Title="'$title'" -elapsed='$elapsed +elapsed='$elapsed' +pllength='$pllength' +state="play" +Title="'$title'"' echo "$status" > $dirshm/status $dirbash/status-push.sh statusradio & # for snapcast ssh - for: mpdoled, lcdchar, vumeter, snapclient(need to run in background) } diff --git a/srv/http/bash/powerbutton.sh b/srv/http/bash/powerbutton.sh index 7c98452d7..745cbd689 100644 --- a/srv/http/bash/powerbutton.sh +++ b/srv/http/bash/powerbutton.sh @@ -27,4 +27,4 @@ else gpio -1 wfi $sw falling fi -/srv/http/bash/power.sh +/srv/http/bash/power.sh off diff --git a/srv/http/bash/settings/camilla-data.sh b/srv/http/bash/settings/camilla-data.sh index 20381366b..afedd448c 100644 --- a/srv/http/bash/settings/camilla-data.sh +++ b/srv/http/bash/settings/camilla-data.sh @@ -33,7 +33,6 @@ control=${vcc[2]} data=' , "bluetooth" : '$bluetooth' , "card" : '$card' -, "clipped" : '$( cat $dirshm/clipped 2> /dev/null || echo 0 )' , "control" : "'$control'" , "devices" : { "capture" : [ '$( echo $capture | tr ' ' , )' ] @@ -42,11 +41,11 @@ data=' , "format" : [ '$( getContent $dirsystem/camilladsp )' ] , "player" : "'$player'" , "pllength" : '$( mpc status %length% )' -, "range" : '$( conf2json camilla.conf )' , "state" : "'$state'" , "volume" : '$volume' , "volumemute" : '$volumemute -for dir in coeffs configs configs-bt; do +dirs=$( ls $dircamilladsp ) +for dir in $dirs; do ######## data+=' , "ls'$dir'" : [ '$( ls -1 $dircamilladsp/$dir | tr '\n' ^ | sed 's/\^$/"/; s/^/"/; s/\^/", "/g' )' ]' diff --git a/srv/http/bash/settings/camilla.sh b/srv/http/bash/settings/camilla.sh index 96706c54c..b0eb999e5 100644 --- a/srv/http/bash/settings/camilla.sh +++ b/srv/http/bash/settings/camilla.sh @@ -6,8 +6,13 @@ dircoeffs=$dircamilladsp/coeffs dirconfigs=$dircamilladsp/configs saveConfig() { + data=$( echo '"GetConfigJson"' \ + | websocat ws://127.0.0.1:1234 \ + | jq -r .GetConfigJson.value \ + | sed 's|^{|{"page":"camilla",|' ) + pushData refresh "$data" configfile=$( getVar CONFIG /etc/default/camilladsp ) - config=$( echo '"GetConfig"' | websocat ws://192.168.1.94:1234 ) + config=$( echo '"GetConfig"' | websocat ws://127.0.0.1:1234 ) echo -e "$config " | sed 's/.*GetConfig.*/---/; $d; s/\\"/"/g' > "$configfile" } @@ -15,9 +20,6 @@ args2var "$1" case $CMD in -camilla ) - pushData camilla $( conf2json camilla.conf ) - ;; clippedreset ) echo $CLIPPED > $dirshm/clipped pushRefresh @@ -47,7 +49,7 @@ confrename ) ;; confswitch ) saveConfig - sed -i -E "s|^(CONFIG.*/).*|\1$NAME|" /etc/default/camilladsp + sed -i -E "s|^(CONFIG=).*|\1$PATH|" /etc/default/camilladsp ;; restart ) systemctl restart camilladsp @@ -55,42 +57,12 @@ restart ) saveconfig ) saveConfig ;; -setformat ) - card=$( < $dirsystem/asoundcard ) - configfile=$( getVar CONFIG /etc/default/camilladsp ) - sed -i -E "/playback:/,/device:/ s/(device: hw:).*/\1$card,0/" $configfile - notify 'camilladsp blink' CamillaDSP "Set Playback format ..." - formats=( FLOAT64LE FLOAT32LE S32LE S24LE3 S24LE S16LE ) - for (( i=0; i < 6; i++ )); do - format=${formats[i]} - sed -i -E '/playback:/,/format:/ {/format:/ {s/(.*: ).*/\1'$format'/}}' $configfile - camilladsp $configfile &> /dev/null & - sleep 1 - if pgrep -x camilladsp &> /dev/null; then - killall camilladsp - break - else - format= - fi - done - if [[ $format ]]; then - notify camilladsp CamillaDSP "Playback format: $format" - sed -E 's/ /" "/g; s/^|$/"/g' <<< ${formats[@]:i} > $dirsystem/camilladsp - else - notify camilladsp CamillaDSP "Setting failed: Playback format" 10000 - $dirsettings/features.sh camilladsp - exit - fi - ;; statusconfiguration ) [[ ! $FILE ]] && FILE=$( getVar CONFIG /etc/default/camilladsp ) cat "$FILE" ;; -statuslog ) - cat /var/log/camilladsp.log - ;; -volume ) - $dirbash/cmd.sh "$1" +statusoutput ) + $dirsettings/player.sh statusoutput ;; esac diff --git a/srv/http/bash/settings/features.sh b/srv/http/bash/settings/features.sh index d3aeb1dbb..2aec4b95c 100644 --- a/srv/http/bash/settings/features.sh +++ b/srv/http/bash/settings/features.sh @@ -50,11 +50,37 @@ camilladsp ) enableFlagSet if [[ $ON ]]; then if grep -q configs-bt /etc/default/camilladsp && [[ ! -e $dirshm/btreceiver ]]; then - fileconfig=$( ls -1 $dircamilladsp/configs/* | head -1 ) - sed -i 's|^CONFIG=.*|CONFIG="'$fileconfig'"|' /etc/default/camilladsp + configfile=$( ls -1 $dircamilladsp/configs/* | head -1 ) + sed -i 's|^CONFIG=.*|CONFIG="'$configfile'"|' /etc/default/camilladsp + fi + + notify 'camilladsp blink' CamillaDSP "Set Playback format ..." + modprobe snd_aloop + card=$( < $dirsystem/asoundcard ) + configfile=$( getVar CONFIG /etc/default/camilladsp ) + sed -i -E "/playback:/,/device:/ s/(device: hw:).*/\1$card,0/" $configfile + formats=( FLOAT64LE FLOAT32LE S32LE S24LE3 S24LE S16LE ) + for (( i=0; i < 6; i++ )); do + format=${formats[i]} + sed -i -E '/playback:/,/format:/ {/format:/ {s/(.*: ).*/\1'$format'/}}' $configfile + camilladsp $configfile &> /dev/null & + sleep 0.5 + if pgrep -x camilladsp &> /dev/null; then + killall camilladsp + break + else + format= + fi + done + if [[ $format ]]; then + notify camilladsp CamillaDSP "Playback format: $format" + sed -E 's/ /" "/g; s/^|$/"/g' <<< ${formats[@]:i} > $dirsystem/camilladsp + pushRestartMpd camilladsp $TF + else + notify camilladsp CamillaDSP "Setting failed: Playback format" 10000 + rm $dirsystem/camilladsp + rmmod snd-aloop &> /dev/null fi - pushRestartMpd camilladsp $TF - ! systemctl -q is-active camilladsp && rm $dirsystem/camilladsp else $dirsettings/camilla.sh saveconfig [[ -e /etc/default/camilladsp.backup ]] && mv -f /etc/default/camilladsp{.backup,} diff --git a/srv/http/bash/settings/networks.sh b/srv/http/bash/settings/networks.sh index da3758ca0..27f100a1a 100644 --- a/srv/http/bash/settings/networks.sh +++ b/srv/http/bash/settings/networks.sh @@ -163,15 +163,9 @@ statuslan ) $( ifconfig $lan | grep -E -v 'RX|TX|^\s*$' )" ;; statuswebui ) - hostname=$( hostname ) echo "\ -# avahi-browse -arp | cut -d';' -f7,8 | grep $hostname -$( timeout 1 avahi-browse -arp \ - | cut -d';' -f7,8 \ - | grep $hostname \ - | grep -v 127.0.0.1 \ - | sed 's/;/ : /' \ - | sort -u )" +# avahi-browse -d local _http._tcp -rpt | awk -F';' '!/^+|^=;lo/ {print \$7\": \"\$8}' +$( avahi-browse -d local _http._tcp -rpt | awk -F';' '!/^+|^=;lo/ {print $7": "$8}' )" ;; statuswl ) wlandev=$( < $dirshm/wlan ) diff --git a/srv/http/bash/settings/player-asound.sh b/srv/http/bash/settings/player-asound.sh index bd2228071..063b9226f 100644 --- a/srv/http/bash/settings/player-asound.sh +++ b/srv/http/bash/settings/player-asound.sh @@ -100,9 +100,7 @@ if [[ $camilladsp ]]; then ! grep -q configs-bt /etc/default/camilladsp && $dirsettings/camilla-bluetooth.sh receiver else grep -q configs-bt /etc/default/camilladsp && mv -f /etc/default/camilladsp{.backup,} - if ! systemctl -q is-active camilladsp; then - [[ -e $dirshm/startup ]] && $dirsettings/camilla.sh setformat || systemctl start camilladsp - fi + systemctl restart camilladsp fi else if [[ $bluetooth ]]; then @@ -111,7 +109,7 @@ else amixer -MqD bluealsa sset "$bluetooth" $btvolume% 2> /dev/null fi fi - if [[ -e $dirsystem/equalizer ]]; then + if [[ -e $dirsystem/equalizer && -e $dirsystem/equalizer.json ]]; then value=$( sed -E -n '/"current":/ {s/.*: "(.*)",/\1/; p}' $dirsystem/equalizer.json ) [[ $( < $dirshm/player ) =~ (airplay|spotify) ]] && user=root || user=mpd $dirbash/cmd.sh "equalizer diff --git a/srv/http/bash/settings/system-data.sh b/srv/http/bash/settings/system-data.sh index c3f659ef0..8809c80db 100644 --- a/srv/http/bash/settings/system-data.sh +++ b/srv/http/bash/settings/system-data.sh @@ -75,6 +75,7 @@ else 12 ) soc+=2710A1;; # 0 2W esac;; 3 ) soc+=2711;; # 4 + 4 ) soc+=2712;; # 5 esac fi kernel=$( uname -rm | sed -E 's|-rpi-ARCH (.*)| \1|' ) diff --git a/srv/http/bash/settings/system-datadefault.sh b/srv/http/bash/settings/system-datadefault.sh index 3f8b3f0e6..e8a67fb4a 100644 --- a/srv/http/bash/settings/system-datadefault.sh +++ b/srv/http/bash/settings/system-datadefault.sh @@ -4,7 +4,7 @@ # data directories mkdir -p $dirdata/{addons,audiocd,bookmarks,camilladsp,lyrics,mpd,mpdconf,playlists,system,webradio,webradio/img} /mnt/MPD/{NAS,SD,USB} -mkdir -p $dircamilladsp/{coeffs,configs,configs-bt} +mkdir -p $dircamilladsp/{coeffs,configs,configs-bt,raw} ln -sf /dev/shm $dirdata ln -sf /mnt /srv/http/ chown -h http:http $dirshm /srv/http/mnt @@ -15,15 +15,7 @@ done [[ $1 ]] && echo $1 > $diraddons/r1 # camilladsp -if [[ -e /usr/bin/camilladsp ]]; then - echo "\ -filtersmax=10 -filtersmin=-10 -mixersmax=10 -mixersmin=-10" > $dirsystem/camilla.conf -else - rm -rf $dircamilladsp -fi +[[ ! -e /usr/bin/camilladsp ]] && rm -rf $dircamilladsp # display true='album albumartist artist bars buttons composer conductor count cover date fixedcover genre diff --git a/srv/http/bash/settings/system-mount.sh b/srv/http/bash/settings/system-mount.sh index 10f6ac7ab..81af7e85c 100644 --- a/srv/http/bash/settings/system-mount.sh +++ b/srv/http/bash/settings/system-mount.sh @@ -45,10 +45,7 @@ if [[ $? != 0 ]]; then mv -f /etc/fstab{.backup,} rmdir "$mountpoint" systemctl daemon-reload - echo "\ -Mount failed: -
    $source -
    $( sed -n '1 {s/.*: //; p}' <<< $std )" + sed -n '1 {s/.*: //; p}' <<< $std exit else @@ -66,5 +63,5 @@ done if [[ $SHAREDDATA ]]; then $dirsettings/system.sh shareddataset else - pushRefresh + pushRefresh system fi diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index 7ffbd6f23..378031541 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -472,20 +472,16 @@ statusstatus ) filebootlog=/tmp/bootlog [[ -e $filebootlog ]] && cat $filebootlog && exit - journal="\ -# journalctl -b" - journal+=" -$( journalctl -b | sed -n '1,/Startup finished.*kernel/ {s|Failed to start .*|&|; p}' ) -" - startupfinished=$( sed -E -n '/Startup finished/ {s/^.*(Startup)/\1/; p}' <<< $journal ) - if [[ $startupfinished ]]; then + startupfinished=$( systemd-analyze ) + if grep -q 'Startup finished' <<< $startupfinished; then echo "\ -# journalctl -b -o cat -g 'Startup finished' +# systemd-analyze $startupfinished -$journal" | tee $filebootlog +# journalctl -b +$( journalctl -b | sed -n '1,/Startup finished.*kernel/ p' )" | tee $filebootlog else - echo "$journal" + journalctl -b fi ;; statusstorage ) @@ -621,6 +617,7 @@ wlan ) else systemctl -q is-active hostapd && $dirsettings/features.sh hostapd$'\n'OFF ifconfig wlan0 down + rmmod brcmfmac_wcc &> /dev/null rmmod brcmfmac fi pushRefresh diff --git a/srv/http/bash/startup.sh b/srv/http/bash/startup.sh index 10b789ddf..35b3b38c9 100644 --- a/srv/http/bash/startup.sh +++ b/srv/http/bash/startup.sh @@ -34,15 +34,15 @@ if [[ -e /boot/backup.gz ]]; then fi if [[ -e /boot/wifi && $wlandev ]]; then - wifi=$( sed 's/\r//' /boot/wifi ) # remove windows return chars - ssid=$( sed -E -n '/^ESSID/ {s/^.*="*|"$//g; p}' <<< $wifi ) - key=$( sed -E -n '/^Key/ {s/^.*="*|"$//g; p}' <<< $wifi ) + wifi=$( sed 's/\r//; s/\$/\\$/g' /boot/wifi ) # remove windows \r and escape $ + ssid=$( getVar ESSID <<< $wifi ) + key=$( getVar Key <<< $wifi ) filebootwifi="/etc/netctl/$ssid" cat << EOF > "$filebootwifi" Interface=$wlandev $( grep -E -v '^#|^\s*$|^Interface|^ESSID|^Key' <<< $wifi ) -ESSID="$( stringEscape $ssid )" -Key="$( stringEscape $key )" +ESSID="$ssid" +Key="$key" EOF $dirsettings/networks.sh "profileconnect $ssid @@ -175,6 +175,7 @@ fi if (( $( rfkill | grep -c wlan ) > 1 )) \ || ! rfkill | grep -q wlan \ || ( ! systemctl -q is-active hostapd && ! netctl list | grep -q -m1 '^\*' ); then + rmmod brcmfmac_wcc &> /dev/null rmmod brcmfmac &> /dev/null onboardwlan=false else diff --git a/srv/http/bash/status-radio.sh b/srv/http/bash/status-radio.sh index b7d09497a..2bade709c 100644 --- a/srv/http/bash/status-radio.sh +++ b/srv/http/bash/status-radio.sh @@ -135,14 +135,18 @@ $( jq -r .albumTitle <<< $track )" fi fi elapsed=$( mpcElapsed ) + pllength=$( mpc status %length% ) data=' - "Album" : "'$album'" + "player" : "mpd" +, "Album" : "'$album'" , "Artist" : "'$artist'" , "elapsed" : '$elapsed' +, "pllength" : '$pllength' +, "state" : "play" , "Title" : "'$title'"' if [[ $coverart ]]; then data+=' -, "coverart" : "'$coverart'"' +, "coverart" : "'$coverart'"' else $dirbash/status-coverartonline.sh "cmd $artist diff --git a/srv/http/bash/status.sh b/srv/http/bash/status.sh index ccc315cf3..6b9b101d0 100644 --- a/srv/http/bash/status.sh +++ b/srv/http/bash/status.sh @@ -49,11 +49,6 @@ if [[ $1 == withdisplay ]]; then }' fi -comsume_pos=( $( mpc status '%consume% %songpos%' ) ) -[[ ${comsume_pos[0]} == on ]] && consume=true -pos=${comsume_pos[1]} # mpc songpos : start at 1 -(( $pos > 0 )) && song=$(( pos - 1 )) # mpd song : start at 0 - if [[ $1 == snapclient ]]; then snapclient=1 player=mpd @@ -75,10 +70,8 @@ else , "player" : "'$player'" , "btreceiver" : '$( exists $dirshm/btreceiver )' , "card" : '$card' -, "consume" : '$consume' , "control" : "'$control'" , "counts" : '$( getContent $dirmpd/counts )' -, "file" : "" , "icon" : "'$icon'" , "librandom" : '$( exists $dirsystem/librandom )' , "lyrics" : '$( exists $dirsystem/lyrics )' @@ -129,13 +122,14 @@ if [[ $player != mpd && $player != upnp ]]; then status+=' , "Album" : "'$( getContent $dirairplay/Album )'" , "Artist" : "'$( getContent $dirairplay/Artist )'" -, "Title" : "'$( getContent $dirairplay/Title )'" , "coverart" : "/data/shm/airplay/coverart.jpg" , "elapsed" : '$elapsed' +, "file" : "" , "sampling" : "16 bit 44.1 kHz 1.41 Mbit/s • AirPlay" , "state" : "'$state'" , "Time" : '$Time' -, "timestamp" : '$timestamp +, "timestamp" : '$timestamp' +, "Title" : "'$( getContent $dirairplay/Title )'"' ;; bluetooth ) ######## @@ -166,13 +160,10 @@ $( < $dirshm/spotify/status )" outputStatus fi -(( $( grep -cE '"cover".*true|"vumeter".*false' $dirsystem/display.json ) == 2 )) && displaycover=1 - -#. <( mpc playlist -f 'Album="%album%"; Artist="%artist%"; Composer="%composer%"; Conductor="%conductor%"; file="%file%"; time=%time%; title="%title%"' | sed "$song q;d" ) -#. <( mpc status 'state=%state%; current=%songpos%; length=%length%; random=%random%; consume=%consume%' ) - -filter='Album AlbumArtist Artist Composer Conductor audio bitrate duration file Name state Time Title' -[[ ! $snapclient ]] && filter+=' playlistlength random repeat single' +pos=$( mpc status %songpos% ) +(( $pos > 0 )) && song=$(( pos - 1 )) || song=0 # mpd song : start at 0 +filter='Album AlbumArtist Artist Composer Conductor audio bitrate duration file state Time Title' +[[ ! $snapclient ]] && filter+=' playlistlength consume random repeat single' filter=^${filter// /:|^}: # ^Album|^AlbumArtist|^Artist... readarray -t lines <<< $( { echo clearerror; echo status; echo playlistinfo $song; sleep 0.05; } \ | telnet 127.0.0.1 6600 2> /dev/null \ @@ -181,9 +172,9 @@ for line in "${lines[@]}"; do key=${line/:*} val=${line#*: } case $key in - audio ) + audio ) # samplerate:bitdepth:channel samplerate=${val/:*} - bitdepth=${val/*:} + bitdepth=$( cut -d: -f2 <<< $val ) ;; bitrate ) bitrate=$(( val * 1000 )) @@ -191,7 +182,7 @@ for line in "${lines[@]}"; do duration | playlistlength | state | Time ) printf -v $key '%s' $val ;; # value of $key as "var name" - value of $val as "var value" - Album | AlbumArtist | Artist | Composer | Conductor | Name | Title ) + Album | AlbumArtist | Artist | Composer | Conductor | Title ) printf -v $key '%s' "$( stringEscape $val )" ;; # string to escape " for json and trim leading/trailing spaces file ) @@ -199,7 +190,7 @@ for line in "${lines[@]}"; do [[ $filenoesc == *".cue/track"* ]] && filenoesc=$( dirname "$filenoesc" ) file=$( stringEscape "$val" ) ;; # escape " for json - random | repeat | single ) + consume | random | repeat | single ) [[ $val == 1 ]] && val=true || val=false ######## status+=' @@ -209,7 +200,6 @@ for line in "${lines[@]}"; do done [[ $playlistlength ]] && pllength=$playlistlength || pllength=0 -status=$( grep -v '^, "file"' <<< $status ) ######## status+=' , "file" : "'$file'" @@ -228,6 +218,7 @@ if [[ $pllength == 0 && ! $snapclient ]]; then # >>>>>>>>>> empty playlist outputStatus fi +(( $( grep -cE '"cover".*true|"vumeter".*false' $dirsystem/display.json ) == 2 )) && displaycover=1 fileheader=${file:0:4} [[ 'http rtmp rtp: rtsp' =~ ${fileheader,,} ]] && stream=true # webradio dab upnp if [[ $fileheader == cdda ]]; then @@ -284,7 +275,6 @@ elif [[ $stream ]]; then *stream.radioparadise.com* ) icon=radioparadise;; esac fi - # before webradio play: no 'Name:' - use station name from file instead url=${file/\#charset*} urlname=${url//\//|} radiofile=$dirradio/$urlname @@ -359,7 +349,6 @@ elif [[ $stream ]]; then , "Album" : "'$Album'" , "Artist" : "'$Artist'" , "stationcover" : "'$stationcover'" -, "Name" : "'$Name'" , "state" : "'$state'" , "station" : "'$station'" , "Time" : false diff --git a/srv/http/bash/stoptimer.sh b/srv/http/bash/stoptimer.sh index 61c87290e..bf014ed08 100644 --- a/srv/http/bash/stoptimer.sh +++ b/srv/http/bash/stoptimer.sh @@ -30,7 +30,7 @@ $control $card" if [[ $poweroff ]]; then - $dirbash/power.sh + $dirbash/power.sh off elif [[ -e $dirshm/relayson ]]; then $dirbash/relays.sh off fi diff --git a/srv/http/common.php b/srv/http/common.php index fd124ea4f..a98b9c9cd 100644 --- a/srv/http/common.php +++ b/srv/http/common.php @@ -83,6 +83,7 @@ $icon = 'camilladsp'; $pagetitle = 'Camilla DSP'; $css[] = 'camilla'; + $css[] = 'equalizer'; $jsp[] = 'Sortable'; } else if ( $guide ) { $icon = 'help'; diff --git a/srv/http/login.php b/srv/http/login.php index f9f232e46..9b36031b8 100644 --- a/srv/http/login.php +++ b/srv/http/login.php @@ -25,7 +25,7 @@

    -
    Wrong password.
    +
    Wrong password.
    OK
    diff --git a/srv/http/mpdplaylist.php b/srv/http/mpdplaylist.php index 8ed90ce83..5de6c347c 100644 --- a/srv/http/mpdplaylist.php +++ b/srv/http/mpdplaylist.php @@ -52,7 +52,7 @@ function currentPlaylist() { , $lists ); // avoid json literal issue with escape double quotes if ( ! count( $lists ) ) exit( '-1' ); - if ( substr( $list[ 3 ], 0, 4 ) === 'cdda' ) { + if ( substr( $lists[ 3 ], 0, 4 ) === 'cdda' ) { foreach( $lists as $list ) { $list = explode( '^^', $list ); $each = ( object )[]; diff --git a/srv/http/settings.php b/srv/http/settings.php index 0faf3b6f9..bcef8fb70 100644 --- a/srv/http/settings.php +++ b/srv/http/settings.php @@ -44,7 +44,7 @@ function tab( $icon ) { $htmlbar = '
    '; $prefix = ''; if ( $camilla ) { - $tabs = [ 'Filters', 'Mixers', 'Pipeline', 'Devices', 'Config' ]; + $tabs = [ 'Filters', 'Mixers', 'Processors', 'Pipeline', 'Devices', 'Config' ]; $prefix = 'tab'; } else { $tabs = [ 'Features', 'Player', 'Networks', 'System', 'Addons' ]; @@ -76,32 +76,34 @@ function tab( $icon ) { if ( $addons ) exit; /* +$id_data = [ 'ID' => [ // REQUIRED + 'label' => 'LABEL' + , 'sub' => 'SUBLABEL' + , 'setting' => 'TYPE' + , 'status' => 'SCRIPTCOMMAND' +]; $head = [ - 'title' => 'TITLE' // REQUIRED - , 'subhead' => true/false // with no help icon - , 'status' => 'COMMAND' // include status icon and status box - , 'button' => [ 'ID' => 'ICON', ... ] // icon button - , 'back' => true/false // back button + 'title' => 'TITLE' // REQUIRED + , 'subhead' => true/false // with no help icon + , 'status' => 'COMMAND' // include status icon and status box + , 'button' => [ 'ICON', ... ] // icon button + , 'back' => true/false // back button , 'nohelp' => true/false , 'help' => 'HELP' ]; $body = [ - 'HTML' // for status section + 'HTML' // for non-switch section , [ - 'label' => 'LABEL' // REQUIRED - , 'sublabel' => 'SUB LABEL' - , 'id' => 'ID' // REQUIRED - , 'status' => 'COMMAND' // include status icon and status box + 'id' => 'ID' // REQUIRED , 'input' => 'HTML' // alternative - if not switch - , 'setting' => *** // default = $( '#setting-'+ id ).click() before enable + , 'setting' => TYPE // default = $( '#setting-'+ id ).click() before enable // false = no setting // 'custom' = custom setting // 'none' = no setting - custom enable - // false = no icon - , 'disabled' => 'MESSAGE' // set data-diabled - prompt on setting + , 'disabled' => 'MESSAGE' // set data-diabled - prompt on click setting // 'js' = set by js condition , 'help' => 'HELP' - , 'exist' => *** // omit if not exist + , 'exist' => 'COMMAND' // hide if COMMAND = false ] , ... ]; @@ -121,7 +123,7 @@ function htmlHead( $data ) { $html = '' : '>'; $html .= ''.$title.''; - if ( $button ) foreach( $button as $btnid => $icon ) $html.= i( $icon.' '.$btnid ); + if ( $button ) foreach( $button as $icon ) $html.= i( $icon ); $html .= isset( $data[ 'nohelp' ] ) || $subhead ? '' : i( 'help help' ); $html .= isset( $data[ 'back' ] ) ? i( 'back back' ) : ''; $html .= ''; @@ -144,14 +146,13 @@ function htmlSection( $head, $body, $id = '' ) { } echo '
    '; } -function htmlSectionStatus( $id, $labels = '', $help = '' ) { +function htmlSectionStatus( $id, $labels = '', $values = '', $help = '' ) { if ( ! $labels ) $labels = ' '; if ( $help ) $help = '
    '.$help.'
    '; return ' -
    +
    '.$labels.'
    -
    -
    +
    '.$values.'
    '.$help.'
    '; } @@ -169,23 +170,24 @@ function htmlSetting( $data ) { global $page; $id = $data[ 'id' ]; $iddata = $id_data[ $id ]; - $name = $iddata[ 'name' ]; + $label = $iddata[ 'label' ]; $sublabel = $iddata[ 'sub' ] ?? false; $status = $iddata[ 'status' ] ?? false; $setting = $iddata[ 'setting' ] ?? 'common'; - $label = ''.$name.''; + $label = ''.$label.''; $input = $data[ 'input' ] ?? false; $settingicon = ! $setting || $setting === 'none' ? false : 'gear'; $help = $data[ 'help' ] ?? false; $icon = $data[ 'icon' ] ?? false; + if ( $page === 'features' || $page === 'system' ) $icon = $id; - $html = '
    '; + $html = '
    '; // col-l $html .= '
    ' : '">'; $html .= $sublabel ? ''.$label.''.$sublabel.'' : $label; - $html .= $page === 'features' || $page === 'system' || $data[ 'icon' ] ? i( $id ) : ''; // icon + $html .= $icon ? i( $icon ) : ''; // icon $html .= '
    '; // col-r $html .= '
    '; diff --git a/srv/http/settings/addonsprogress.php b/srv/http/settings/addonsprogress.php index bf32a7ea4..3c0a34d95 100644 --- a/srv/http/settings/addonsprogress.php +++ b/srv/http/settings/addonsprogress.php @@ -81,7 +81,7 @@
    -
    +
    OK
    diff --git a/srv/http/settings/camilla.php b/srv/http/settings/camilla.php index 89796eb28..c7231f7ac 100644 --- a/srv/http/settings/camilla.php +++ b/srv/http/settings/camilla.php @@ -1,118 +1,141 @@ [ 'name' => 'Configuration', 'setting' => 'custom', 'sub' => 'current', 'status' => true ] - , 'enable_rate_adjust' => [ 'name' => 'Rate Adjust', 'setting' => 'custom' ] - , 'stop_on_rate_change' => [ 'name' => 'Stop on Rate Change', 'setting' => 'custom' ] - , 'enable_resampling' => [ 'name' => 'Resampling', 'setting' => 'custom' ] + 'volume' => [ 'label' => 'Master', 'setting' => false, 'sub' => 'hw' ] + , 'configuration' => [ 'label' => 'Configuration', 'setting' => 'custom', 'sub' => 'current', 'status' => true ] + , 'enable_rate_adjust' => [ 'label' => 'Rate Adjust', 'setting' => 'custom' ] + , 'stop_on_rate_change' => [ 'label' => 'Stop on Rate Change', 'setting' => 'custom' ] + , 'resampler' => [ 'label' => 'Resampler', 'setting' => 'custom' ] ]; -$sliderrange = i( 'gear btn' ).' Gain slider range'; -$contextfilters = i( 'filters btn' ).' Context menu: '.i( 'graph btn' ).i( 'edit btn' ).i( 'remove btn' ); -$contextmixers = i( 'mixers btn' ).' Context menu: '.i( 'edit btn' ).i( 'remove btn' ); -$contextpipeline = str_replace( 'filters' , 'pipeline', $contextfilters ); -$contextconfig = str_replace( 'mixers' , 'config', $contextmixers ); -$gaincontrols = i( 'minus btn' ).i( 'set0 btn' ).i( 'plus btn' ).' -0.1dB · 0 · +0.1dB'; -$help = [ - 'filters' => <<< EOF -{$Fi( 'folder-filter btn' )} FIR coefficient -{$sliderrange} -{$contextfilters} -{$gaincontrols} +$btnfilters = i( 'filters btn' ).' Context menu: '.i( 'graph btn' ).i( 'edit btn' ).i( 'remove btn' ); +$btnmixers = i( 'mixers btn' ).' Context menu: '.i( 'edit btn' ).i( 'remove btn' ); +$button = [ + 'filters' => i( 'filters btn' ).' Context menu: '.i( 'graph btn' ).i( 'edit btn' ).i( 'remove btn' ) + , 'mixers' => $btnmixers + , 'processors' => str_replace( 'mixers' , 'processors', $btnmixers ) + , 'pipeline' => str_replace( 'filters' , 'pipeline', $btnfilters ) + , 'config' => str_replace( 'mixers' , 'config', $btnmixers ) + , 'control' => i( 'volume btn' ).i( 'inverted btn' ).i( 'linear btn' ) +]; +$help = [ + 'status' => <<< EOF +{$Fi( 'play btn' )}{$Fi( 'pause btn' )}{$Fi( 'stop btn' )} Playback control + +Camilla DSP - Create audio processing pipelines for applications such as active crossovers or room correction. +EOF + , 'volume' => <<< EOF +{$Fi( 'gear btn' )} Configuration files +{$Fi( 'set0 btn' )} Reset clipped count (if any) +EOF + , 'filters' => <<< EOF +{$Fi( 'folder-filter btn' )}{$Fi( 'plus btn' )} FIR coefficient files · New +{$button[ 'filters' ]} Graph · Edit · Delete +{$Fi( 'code btn' )} Set 0 +{$button[ 'control' ]} Mute · Invert · Linear (Gain) EOF , 'mixers' => <<< EOF -{$sliderrange} -{$contextmixers} -{$gaincontrols} -{$Fi( 'volume btn' )}{$Fi( 'inverted btn' )} Mute, Invert +{$Fi( 'plus btn' )} New +{$button[ 'mixers' ]} Edit · Delete +{$Fi( 'code btn' )}{$button[ 'control' ]} Set 0 · Mute · Invert · Linear +EOF + , 'processors' => <<< EOF +{$Fi( 'plus btn' )} New +{$button[ 'processors' ]} Edit · Delete EOF , 'pipeline' => <<< EOF -{$Fi( 'flowchart btn' )} Step flowchart -{$contextpipeline} +{$Fi( 'flowchart btn' )}{$Fi( 'plus btn' )} Step flowchart · New +{$button[ 'pipeline' ]} Graph · Edit · Delete EOF , 'devices' => <<< EOF {$Fi( 'gear btn' )} Capture sampling {$Fi( 'input btn' )}{$Fi( 'output btn' )} Device settings EOF , 'config' => <<< EOF -{$contextconfig} +{$$button[ 'config' ]} EOF ]; -$htmltabs = '
    '; -foreach( [ 'filters', 'mixers', 'pipeline', 'devices', 'config' ] as $id ) { - $htmltabs.= '
    '.$help[ $id ].'
    '; - if ( $id === 'pipeline' ) $htmltabs.= ''; - $htmltabs.= '
      '; +$htmls = [ + 'volume' => ' +
      +
      +
      +
      +
      + +0 + +' + , 'labels' => ' +Buffer · Load · Clipped +
      Sampling · Adjust +' + , 'values' => ' +· · · · · +
      · · · +' +]; +$tabs = [ 'filters', 'mixers', 'processors', 'pipeline', 'devices', 'config' ]; +$htmltabs = []; +foreach( $tabs as $id ) { + $html = '
      '.$help[ $id ].'
      '; + if ( $id === 'pipeline' ) $html.= ''; + $html.= '
        '; if ( $id === 'devices' ) { - $htmltabs.= ' + $html.= '
        '.htmlSectionStatus( 'sampling' ).'
        '.htmlSetting( [ 'id' => 'enable_rate_adjust', 'returnhtml' => true ] ).' '.htmlSetting( [ 'id' => 'stop_on_rate_change', 'returnhtml' => true ] ).' -'.htmlSetting( [ 'id' => 'enable_resampling', 'returnhtml' => true ] ).' +'.htmlSetting( [ 'id' => 'resampler', 'returnhtml' => true ] ).'
        '; } else if ( $id !== 'config' ) { - $htmltabs.= '
          '; + $html.= '
            '; } - $htmltabs.= '
            '; + $htmltabs[ $id ] = $html.'
            '; } - -$htmltabs.= '
            '; -$htmlvolume = ' -
            -
            Volume 0
            -
            -
            -
            -
            -
            -
            -
            - - - -
            -
            -
            -
            -'; +$button = [ + 'filters' => [ 'folder-filter', 'add' ] + , 'mixers' => [ 'add' ] + , 'processors' => [ 'add' ] + , 'pipeline' => [ 'flowchart', 'add' ] + , 'devices' => [ 'gear' ] + , 'config' => '' +]; ////////////////////////////////// $head = [ 'title' => 'Status' , 'status' => 'camilladsp' - , 'button' => [ 'icon' => 'mpd', 'playback' => 'play' ] - , 'help' => <<< EOF -{$Fi( 'file btn' )} Log -{$Fi( 'play btn' )}{$Fi( 'pause btn' )}{$Fi( 'stop btn' )} Playback control - -Camilla DSP - Create audio processing pipelines for applications such as active crossovers or room correction. -EOF + , 'button' => [ 'mpd icon', 'play playback' ] + , 'help' => $help[ 'status' ] ]; $body = [ - '
            '
            -	, htmlSectionStatus( 'vu' )
            -	, $htmlvolume
            -	, htmlSectionStatus( 'state', '
            ' ) + htmlSectionStatus( 'vu' ) + , [ 'id' => 'volume' + , 'icon' => 'minus' + , 'input' => $htmls[ 'volume' ] + ] + , htmlSectionStatus( 'state', $htmls[ 'labels' ], $htmls[ 'values' ] ) , [ - 'id' => 'configuration' + 'id' => 'configuration' , 'status' => true - , 'input' => '' - , 'help' => <<< EOF -{$Fi( 'gear btn' )} Configuration files -{$Fi( 'minus btn' )}{$Fi( 'volume btn' )}{$Fi( 'plus btn' )} -1% · mute · +1% -{$Fi( 'set0 btn' )} Reset clipped count (if any) -EOF + , 'input' => '' + , 'help' => $help[ 'volume' ] ] ]; htmlSection( $head, $body, 'status' ); ////////////////////////////////// -$head = [ - 'title' => 'Filters' -]; -$body = [ $htmltabs ]; -htmlSection( $head, $body, 'settings' ); +foreach( $tabs as $id ) { + $head = [ + 'title' => ucfirst( $id ).( $id === 'config' ? 'uration' : '' ) + , 'button' => $button[ $id ] + , 'status' => $id === 'devices' ? 'output' : false + ]; + $body = [ $htmltabs[ $id ] ]; + htmlSection( $head, $body, $id ); +} ?>