foobar2000 0.8.3 custom by Draikin

Discussion in 'Nguồn phát từ máy tính' started by tml3nr, 3/8/17.

  1. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    link tải foobar2000 v2.1 tape denon skin :
    https://www.fshare.vn/file/JVGC21O2W7Z4
    (xin phép tác giả - hình như vo7210 hay Dmitri).
    Skin này dùng CUI nên ta chuyển qua phần CUI/Layout để xem các mục cụ thể:
    - 2 PSS : 1 PSS nền và 1 PSS để chứa các items khác (có thể dùng 1 PSS là đủ).
    - Các plugin WSH và 2 VU Meter:
    + Plugin WSH : chứa mã lệnh điều khiển tape và 1 vài nút lệnh (Fb2k không hỗ trợ sẵn tính năng nên phải code).
    + Plugin VU meter: hiển thị đèn nhấp nháy.

    vậy thôi.

    Chi tiết 1 tý xem nào:
    Như các phần trước đã nêu, PSS có thể chứa mã thực thi và được Fb2k triệu hồi liên tục theo từng track nhạc hay theo từng giây. PSS thường chứa mã dựng giao diện (load image có sẵn, vẽ hình vuông tròn, vẽ chữ, dựng button điều khiển, lưu giữ biến toàn cục . . . ). Ví dụ PSS này chứa các mã :

    + Các mã thực thi theo từng track:
    Code:
    // ***GENERAL SETTINGS 1***
    $imageabs($sub($div(%ps_width%,1),1202),0,1202,460,%ps_foobar2000_path%\themes\Denon\Denon.png)
    $if(%ps_isplaying%,
    $imageabs(747,318,94,38,$get_ps_global(ThemePath)\Button\Play.png)
    $imageabs(720,253,38,13,$get_ps_global(ThemePath)\lplay.png)
    )
    $if(%ps_ispause%,
    $imageabs(514,318,65,38,$get_ps_global(ThemePath)\Button\Pause.png)
    $imageabs(766,253,38,13,$get_ps_global(ThemePath)\lpause.png)
    )
    $if($or(%ps_isplaying%,%ps_ispause%),,
    $imageabs(650,318,94,38,$get_ps_global(ThemePath)\Button\Stop.png)
    $imageabs(675,253,38,13,$get_ps_global(ThemePath)\lstop.png)
    $font(Series 60 ZDigi,16,)
    $drawtext(0:00,543,255,100,23,128-203-255)
    $font(Darkone,14,)
    $drawtext($upper(%title% • %codec% • %bitrate%  kbps),543,186,400,23,128-230-255)
    )
    $ifequal(%Minimize%,1,
    $imageabs(929,397,47,13,$get_ps_global(ThemePath)\Button\Minimize.png),
    )
    $imagebutton(48,223,63,49,$get_ps_global(ThemePath)\Button\Power_Red.png,$get_ps_global(ThemePath)\Button\Power.png,COMMAND:File/Exit)
    $imagebutton(48,329,63,27,,$get_ps_global(ThemePath)\Button\Open.png,COMMAND:File/Add folder...)
    $imagebutton(514,318,65,38,,$get_ps_global(ThemePath)\Button\Pause.png,COMMAND:Playback/Play or pause)
    $imagebutton(582,318,64,38,,$get_ps_global(ThemePath)\Button\Record.png,COMMAND:)
    $imagebutton(650,318,94,38,,$get_ps_global(ThemePath)\Button\Stop.png,COMMAND:Playback/Stop)
    $imagebutton(747,318,94,38,,$get_ps_global(ThemePath)\Button\Play.png,COMMAND:Playback/Play)
    $imagebutton(844,318,65,38,,$get_ps_global(ThemePath)\Button\Prev.png,COMMAND:Playback/Previous)
    $imagebutton(912,318,64,38,,$get_ps_global(ThemePath)\Button\Next.png,COMMAND:Playback/Next)
    $imagebutton(1063,329,44,27,,$get_ps_global(ThemePath)\Button\Mute.png,COMMAND:Playback/Volume/Mute)
    $imagebutton(576,397,48,13,,$get_ps_global(ThemePath)\Button\Options.png,COMMAND:File/Preferences)
    $imagebutton(641,397,47,13,,$get_ps_global(ThemePath)\Button\Playlist.png,COMMAND:Library/Album List)
    $imagebutton(768,397,46,13,,$get_ps_global(ThemePath)\Button\Repeat.png,COMMAND:Playback/Control/Repeat current once)
    $imagebutton(866,397,48,13,,$get_ps_global(ThemePath)\Button\Stayontop.png,COMMAND:View/Always on Top)
    $imagebutton(929,397,47,13,,$get_ps_global(ThemePath)\Button\Minimize.png,COMMAND:View/Hide,,)
    $ifequal(%Minimize%,1,
    SETGLOBAL:Minimize:0;
    REFRESH;
    WINDOWSIZE:1202:800,
    SETGLOBAL:Minimize:1;
    REFRESH;
    WINDOWSIZE:1202:460)
    )
    
    PSS đã vẽ 1 image nền hình máy hát Denon to đùng, tiếp đó là các hình vẽ nhỏ hơn trên hình vẽ nền tại 1 tọa độ cụ thể, thể hiện các nút trạng thái của máy hát. hình vẽ sau sẽ đè hình vẽ trước.
    sau cùng là các nút kèm các lệnh do Fb2k hỗ trợ sẵn.

    để ý có 1 biến ThemePath chưa được gán giá trị ? nó được gán bởi PSS gốc :
    Code:
    $init_ps_global(ThemePath,%ps_foobar2000_path%\themes\Denon,0)
    + Các mã thực thi sau mỗi giây:
    Code:
    $font(Series 60 ZDigi,16,)
    $drawtext(%playback_time% ,543,255,100,23,128-203-255)
    $font(Darkone,14,)
    $drawtext($upper(%title% • %codec% • %bitrate%  kbps),543,186,400,23,128-230-255)
    PSS sẽ liên tục cập nhật thời gian hiện tại của track đang play từ thông tin chứa trong biến (lại có biến,kk dịch từ variable) hệ thống %playback_time% và thông tin về bài hát.
    vậy là có 1 số thông tin được Fb2k cập nhật liên tục qua biến hệ thống, nhận diện qua từ khóa biến kẹp giữa 2 dấu %biến%, khi cần cứ lấy - phẻ re.

    Rồi, PSS xem như xong việc !? Chưa.
    1 việc quan trọng của PSS là sắp đặt các mục ngăn nắp, thể hiện qua vị trí, kích cỡ. và để giữ trật tự, PSS phải ép forced các mục giữ đúng vị trí.
    ví dụ: mục đầu tiên WSH Panel Volume có 1 vài giá trị : Forced Layout - ok, và khi forced thì phải chỉ rõ left 1028, top 179, width 112, height 112 là các giá trị điểm ảnh có scale DPI theo độ phân giải và co giãn của màn hình (tỷ như màn hình HD 1920 x 1080 điểm hiển thị, sau đó ta thấy chữ nhỏ quá nên zoom cỡ 150% cho chữ và hình to hơn. Khi in ra giấy thì lại có kiểu độ phân giải khác nên ta không quan tâm). vậy là cái plugin WSH này sẽ được vùng vẫy trong cái áo vuông 112 x 112 ở tọa độ cách cạnh trái (của PSS chứa nó) 1028 điểm, cách đỉnh (của PSS chứa nó) 179 điểm.

    tiếp theo lại là 1 WSH khác với các thông số và tọa độ cụ thể.
    PSS load các plugin theo thứ tự từ dưới lên trên.

    Cuối cùng. Các WSH diễn dịch lệnh trong cửa sổ cho phép là gì ? ví dụ WSH Panel Volume trên, trong không gian 112 x 112 ấy, khi có lệnh vẽ thì mặc nhiên tọa độ 0,0 là tính trong phân đất nền đã phân lô này mà thực tế là cách cạnh trái của PSS đến 1028 điểm. tương tự, các khái niệm window, panel, chiều cao h hay rộng w là giới hạn trong lô đất này. Muốn WSH tém tém lại để có thể nhìn thấu qua đất của nó thì cần chọn pseudo transparent trước khi cho nó ra đời (giao diện coding).
    Chỉ những WSH nào cần vẽ vời chi đó thì mới cần cấp đất.

    Đơn giản mà hiệu quả.

    Nhưng cái denon tape này không co giãn chi được, chỉ đứng 1 chổ ngúng nguẩy thôi.
    Muốn co giãn được không ? được. vì các skin đều là hình vẽ, mà hình vẽ thì có kích cỡ nên các cao thủ sẽ dựa vào biến hệ thống được cập nhật liên tục theo thực tế (là chiều cao và độ rộng của cửa sổ) để tính ra tỷ lệ co/giãn tương ứng và vẽ lại các hình.

    Mà vẽ lại toàn bộ hình cũng cực, đơn giản như cái tape với vài nút bấm thì ổn, chứ chứa thêm 1 mớ plugin khác thì sao, nhất là chữ nghĩa co giãn nhỏ xíu rồi to đùng ? Linh hoạt được - tức là 1 số chỗ thì cho co giãn (thường là những khoảng ở giữa cửa sổ) và 1 số chỗ thì vẫn đơ như cây cơ (các góc hay các bar trái, phải - trên dưới nên chứa các nút lệnh điều khiển).

    Mà WSH cũng vẽ được hình, lôi đầu được cả plugin ra thì cần gì dàn xếp trước layout ? cũng được luôn: chỉ cần 1 WSH code cả layout. Nhưng mà hình dung banh não luôn- dành cho những cao thủ thiết đầu.

    Ủa, đơn giản mà hiệu quả vậy sao ? thiệt. phần khó nhất là photoshop cái hình máy hát và phụ kiện nút bấm cho đẹp.
    cứ theo công thức này, ta có thể làm được hầu như các loại máy hát tape, reel to reel vì chỉ cần cái hình máy đẹp + dãy đèn VU Meter + các đoạn code quay tape và nút bấm (mà các đoạn code này hầu như đã được các cao thủ viết sẵn và tuning chỉn chu từ lâu).

    Nhưng, để ý 1 chút ta sẽ thấy cái tape quay còn chưa thực tế lắm: 1 bên đầu quay phải mỏng băng dần và ngược lại. Giải pháp là (1) vẽ thêm hình tròn và/hay (2) load image có sẵn theo các kích cỡ khác nhau như chiếu phim hoạt hình. nhưng cách nào thì cũng ngốn CPU/GPU và RAM nên thôi cứ quay cà lơ cà lơ cho lành.

    Điểm cuối cùng: Không dùng WSH được không ?
    WSH panel mod do T.P.Wang phát triển - được xem là thế hệ đầu tiên phong cho coding và diễn dịch ngôn ngữ script (Javascript, VBscript) trên Fb2k, mở ra 1 thời kỳ rực rỡ cho Fb2k cùng với các plugins (quãng 10 năm trước), đến version 1.6.3 (2015) thì dừng, sau đó Marc2003 cho ra JSctipt Panel kế thừa và dừng ở phiên bản 2.8.8 (4/2023) và hiện đang phát triển JScript Panel 3 với môi trường coding trực quan, hỗ trợ 64 bit và nhiều tập lệnh/hàm hơn. JScript Panel 3 được tác giả liên tục cập nhật, hiện là version 3.3.27 (30/12/2023) và có vẻ đã chuyển từ Core Internet Explorer sang Chromium.

    Ngoài ra, Spider Monkey Panel do TheQwertiest phát triển (hiện là 1.6.1, 1/2022) cũng là 1 môi trường coding lý tưởng. rất nhiều app (nên gọi như vây vì mức độ phức tạp và tính năng) phổ biến như Bio lấy thông tin ca sĩ, đánh giá bài hát; Quản lý thư viện nhạc; Quản lý bài hát; Quản lý danh sách các bài hát được phát triển trên trình diễn dịch này. Spider Monkey Panel xây dựng trên core nguồn mở Mozilla Firefox và hiện chưa thấy hỗ trợ 64 bit.

    Lựa chọn nào cũng tốt.
     

    Attached Files:

    Last edited: 7/1/24
    anhchiennt2 and tml3nr like this.
  2. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    phần này sẽ chuyển qua 1 giao diện hiện đại hơn: hệ thống tab DIY.
    link download ví dụ (DarkOne Mod4 beta3 - 10/2019, có lẽ là phiên bản cuối cùng dòng DarkOne. mình đã chuyển qua Fb2k 2.0)
    https://www.fshare.vn/file/E2SMEW4IXVE3

    Fb2k có sẵn các tab stack cho phép xếp chồng nhiều panel, thậm chí 1 tab chứa nhiều tab hay panel. (Nhớ lại khoảng 2008 khi chrome ra đời và sau đó giới thiệu hệ thống tab duyệt Web, như 1 luồng gió mới so với IE đang rất thịnh hành và FireFox có thị phần đang lên nhưng cả 2 vẫn trung thành với 1 window- 1 trang Web).

    Fb2k có sẵn tab rồi thì vẽ rắn thêm chân làm chi với tab DIY? À, có 1 số ngữ cảnh : Tab nằm dưới và panel ở phía trên, tab 1 nơi và panel 1 nơi; tab có tùy biến màu sắc và hình . . . lý do chính đáng.

    tab DIY là gì ? bản chất của tab DIY là các button và công việc chính là hiện/ẩn các panel - việc nhẹ lương cao.

    đặc thù công việc liên quan đến panel nên các button này thuộc biên chế của Panel Stack Splitter nền trực tiếp- là panel chứa các panel tham gia hiện/ẩn. Do là script nên phân vô khu Script của PSS, mục 'Per Track' - không phải 'Per Second' vì không cần triệu hồi liên tục tốn điện.
    ví dụ minh họa được lấy từ skin DarkOne của TedGo (https://www.deviantart.com/tedgo/art/DarkOne4Mod-v1-637189138), 1 skin quá nổi tiếng và có vẻ miễn nhiễm với thời gian - phát triển qua nhiều phiên bản bởi chính TedGo và cộng đồng vì skin mở, code dễ hiểu, dễ tùy biến thêm bớt plugin hay button, UI đúng kiểu Less Is More. nói chung là gọn gàng-ngăn nắp-cởi mở-dễ nhìn-hiện đại.
    DarkOne có nhiều tab DIY theo kiểu chồng lớp, chúng ta sẽ lấy ngay tab DIY ngoài cùng để minh họa.
    Script có gì:

    Code:
    // =========================================================
    
    // ----- ARTIST PICTURE -----
    $set_ps_global(do.artist.pic,$directory_path(%path%)\artist.*)
    $set_ps_global(do.artist.alpha,40)
    
    // =========================================================
    
    // ----- VARIABLES -----
    $puts(y,$muldiv(%ps_width%,17,640))
    $puts(h,$sub(%ps_height%,$get(y)))
    $puts(x,$ifequal(%do.view.switch%,1,$sub(%ps_width%,$add($div(%ps_width%,128),%do.panel.w%)),$ifequal(%do.view.switch%,4,$div(%ps_width%,128),$div($sub(%ps_width%,%do.panel.w%),2))))
    $puts(w,$ifequal(%do.view.switch%,1,%do.panel.w%,$ifequal(%do.view.switch%,4,$muldiv(%ps_width%,63,64),$muldiv(%ps_width%,83,128))))
    $puts(b.h,$muldiv(%ps_width%,3,160))
    $puts(p.w,$div(%ps_width%,640))
    
    // ----- BACKGROUND -----
    $fillrect(0,0,%ps_width%,$get(b.h),%do.bar.colour%)
    $drawrect($sub($div(%ps_width%,3),$get(p.w)),0,$mul($get(p.w),2),$get(b.h),24-24-24-255,24-24-24-255)
    $drawrect($sub(%ps_width%,$add($div(%ps_width%,3),$get(px.w))),0,$mul($get(p.w),2),$get(b.h),24-24-24-255,24-24-24-255)
    
    // ----- PANEL-MANAGEMENT -----
    $ifgreater(%do.view.switch%,2,
        $movepanel_c(Seekbar,$div($sub(%ps_width%,%do.panel.w%),2),$add($muldiv(%ps_width%,3,640),1),%do.panel.w%,$muldiv(%ps_width%,3,320))
    ,)
    
    $ifequal(%do.view.switch%,2,
        $showpanel_c(Left Panel,0)
        $showpanel_c(Center Panel,1)
        $showpanel_c(Right Panel,1)
        $showpanel_c(Seekbar,0)
        $showpanel_c(PSS Refresh,1)
        $showpanel_c(PSS Refresh,0)
        $movepanel_c(Left Panel,0,0,0,0)
        $movepanel_c(Center Panel,$div(%ps_width%,128),$get(y),%do.panel.w%,$get(h))
        $movepanel_c(Right Panel,$get(x),$get(y),$get(w),$get(h))
    ,
        $ifequal(%do.view.switch%,3,
            $showpanel_c(Center Panel,0)
            $showpanel_c(Left Panel,1)
            $showpanel_c(Right Panel,1)
            $showpanel_c(Seekbar,1)
            $showpanel_c(PSS Refresh,1)
            $showpanel_c(PSS Refresh,0)
            $movepanel_c(Left Panel,$div(%ps_width%,128),$get(y),%do.panel.w%,$get(h))
            $movepanel_c(Center Panel,0,0,0,0)
            $movepanel_c(Right Panel,$get(x),$get(y),$get(w),$get(h))
        ,
            $ifequal(%do.view.switch%,4,
                $showpanel_c(Center Panel,0)
                $showpanel_c(Left Panel,0)
                $showpanel_c(Right Panel,1)
                $showpanel_c(Seekbar,1)
                $showpanel_c(PSS Refresh,1)
                $showpanel_c(PSS Refresh,0)
                $movepanel_c(Left Panel,0,0,0,0)
                $movepanel_c(Center Panel,0,0,0,0)
                $movepanel_c(Right Panel,$get(x),$get(y),$get(w),$get(h))
            ,
                $showpanel_c(Left Panel,1)
                $showpanel_c(Center Panel,1)
                $showpanel_c(Right Panel,1)
                $showpanel_c(Seekbar,0)
                $showpanel_c(PSS Refresh,1)
                $showpanel_c(PSS Refresh,0)
                $movepanel_c(Left Panel,$div(%ps_width%,128),$get(y),%do.panel.w%,$get(h))
                $movepanel_c(Right Panel,$get(x),$get(y),$get(w),$get(h))
                $movepanel_c(Center Panel,$div($sub(%ps_width%,%do.panel.w%),2),$get(y),%do.panel.w%,$get(h))
            )
        )
    )
     
    // ----- BUTTON-VARIABLES -----
    $font(%do.font.name%,%do.font.size%,)
    $puts(w1,$gettextwidth(View 1))
    $puts(w2,$gettextwidth(View 2))
    $puts(w3,$gettextwidth(View 3))
    $puts(w4,$gettextwidth(View 4))
    $puts(x1,$div(%ps_width%,128))
    
    // ----- BUTTONS -----
    $textbutton(0,0,0,0,,,,,)
    $textbutton($add($get(x1),$sub($div(%do.panel.w%,16),$div($get(w1),2))),0,$get(w1),$get(b.h),View 1,View 1,SETGLOBAL:do.view.switch:1;REFRESH,fontcolor:$ifequal(%do.view.switch%,1,%do.btn.xcol%,%do.btn.ncol%),fontcolor:$ifequal(%do.view.switch%,1,%do.btn.xcol%,%do.btn.hcol%))
    $textbutton($add($get(x1),$sub($muldiv(%do.panel.w%,3,16),$div($get(w2),2))),0,$get(w2),$get(b.h),View 2,View 2,SETGLOBAL:do.view.switch:2;REFRESH,fontcolor:$ifequal(%do.view.switch%,2,%do.btn.xcol%,%do.btn.ncol%),fontcolor:$ifequal(%do.view.switch%,2,%do.btn.xcol%,%do.btn.hcol%))
    $textbutton($add($get(x1),$sub($muldiv(%do.panel.w%,5,16),$div($get(w3),2))),0,$get(w3),$get(b.h),View 3,View 3,SETGLOBAL:do.view.switch:3;REFRESH,fontcolor:$ifequal(%do.view.switch%,3,%do.btn.xcol%,%do.btn.ncol%),fontcolor:$ifequal(%do.view.switch%,3,%do.btn.xcol%,%do.btn.hcol%))
    $textbutton($add($get(x1),$sub($muldiv(%do.panel.w%,7,16),$div($get(w4),2))),0,$get(w4),$get(b.h),View 4,View 4,SETGLOBAL:do.view.switch:4;REFRESH,fontcolor:$ifequal(%do.view.switch%,4,%do.btn.xcol%,%do.btn.ncol%),fontcolor:$ifequal(%do.view.switch%,4,%do.btn.xcol%,%do.btn.hcol%))
    - Panel: có 5 panel tham gia trò chơi trốn tìm, được đặt tên: Left Panel, Center Panel, Right Panel, Seekbar, PSS Refresh. (có 1 panel Rating nhưng không tham gia).
    ngoài các panel có chứa thông tin, panel 'PSS Refresh' chỉ là hàng fake với nhiệm vụ duy nhất là refresh màn hình - tức là hiện rồi ẩn luôn. lý do: Fb2k quản lý khá nhọc việc quét lại màn hình, đặc biệt khi các panel chồng lẫn nhau thì việc refresh các panel dưới sâu rất không mượt, hậu quả là lưu vết màn hình. ngoài ra, nếu giữa các panel có khoảng hở (như với ví dụ này từ DarkOne) thì cũng bị lưu vết màn hình khi chuyển trạng thái hiện/ẩn các panel.

    nhưng việc gì cũng có 2 mặt: dụng panel 'PSS Refresh' làm màn hình sạch, nhưng sẽ có hiện tương flash khá rõ. có lẽ Fb2k không thích hàng fake.
    giải pháp: không để khoảng hở giữa các panel và không dùng panel 'PSS Refresh'.

    - các lệnh hiện/ẩn panel: cú pháp chung $showpanel_c(tên_panel,option) - option là 1 thì hiện, 0 là ẩn.

    Code:
                $showpanel_c(Left Panel,1) -> hiện panel 'Left Panel'
                $showpanel_c(Center Panel,1) -> hiện panel 'Center Panel'
                $showpanel_c(Right Panel,1) -> hiện panel 'Right Panel'
                $showpanel_c(Seekbar,0)  -> ẩn panel 'Seekbar' (panel ở phía ngay dưới cover art - là Center Panel)
                $showpanel_c(PSS Refresh,1) -> hiện panel fake
                $showpanel_c(PSS Refresh,0) -> tắt ngay panel fake
    ví dụ trên: hiện 1 lúc 3 panel, ẩn 1 panel. nếu chỉ hiện 1 panel tương ứng 1 lần click button text (và tắt các panel khác) thì quay về tab truyền thống, nhưng câu lệnh đơn giản hơn, cũng không cần đặt tên panel mà cứ duyệt hết panel theo tên-thứ tự do Fb2k tự đặt : 0, 1, 2, 3 . . . :
    Code:
    // tab truyền thống - mỗi lần hiện 1 panel.
    // biến cục bộ do.view.switch được set giá trị tương ứng theo các button text :1,2,3 . . .
    
    $showpanel(0,$ifequal(%do.view.switch%,1,1,0))
    $showpanel(1,$ifequal(%do.view.switch%,2,1,0))
    $showpanel(2,$ifequal(%do.view.switch%,3,1,0))
    - các button giả lập tab: (câu lệnh phải viết liên tục trên 1 dòng), ví dụ 1 button text.
    Code:
    $textbutton($add($get(x1),$sub($div(%do.panel.w%,16),$div($get(w1),2))),0,$get(w1),$get(b.h),View 1,View 1,SETGLOBAL:do.view.switch:1;REFRESH,fontcolor:$ifequal(%do.view.switch%,1,%do.btn.xcol%,%do.btn.ncol%),fontcolor:$ifequal(%do.view.switch%,1,%do.btn.xcol%,%do.btn.hcol%))
    câu lệnh trên hiển thị 1 button với chữ View 1 , khi click thì gán giá trị 1 cho biến cục bộ do.view.switch và yêu cầu Fb2k vào việc đọc lại sớ script ngay (không đợi Fb2k play hết track nhạc hay đang rảnh rỗi). xong việc.

    - ẩn/hiện panel:
    sau khi bị dựng dậy đọc sớ script do 'lệnh' từ button text View 1, Fb2k diễn dịch lại script từ trên xuống, đến đoạn này
    Code:
    $ifequal(%do.view.switch%,1 . . . làm gì đó,
    $ifequal(%do.view.switch%,2  . . . làm gì đó, . . .
    Fb2k đọc giá trị từ biến do.view.switch vừa được gán do người dùng click vào button text View 1 và thực hiện các chỉ thị tương ứng với từng giá trị do.view.switch ấy - tức là hiện/ẩn các panel như nêu trên.

    Tương tự cho các button text khác khi được click.
    Nếu không có button text nào được click thì Fb2k nhớ giá trị do.view.switch được gán gần nhất và vẫn cần mẫn diễn dịch thực thi script sau mỗi track nhạc, tức là cũng ẩn hiện panel nhưng do màn hình không thay đổi nên Fb2k có vẻ ghi nhớ được trạng thái màn hình nên không làm gì cả - không re-paint toàn bộ màn hình, chỉ re-paint từng khu vực theo lệnh triệu hồi cục bộ (ví dụ: hiển thị track nhạc mới theo yêu cầu từ panel đó; hiển thị cover art mới theo yêu cầu từ chính panel chứa cover art, mà bản chất là plugin trong panel).

    xong. đơn giản mà hiệu quả.

    nói thêm: TedGo tính tọa độ và kích thước các panel theo 1 công thức rất khó hiểu !!! mục đích chính để kích cỡ chữ, button không bị ảnh hưởng khi thay độ phân giải màn hình - HD, QHD hay 4K, chay mặn gì cũng dùng được. nhưng khó có one-in-1 giải pháp, không rõ có phải điều này làm TedGo nản mà bỏ ngang DarkOne Mod4 beta 3 từ khoảng 10/2019.

    bản FB2k_VNAV_2.0 mới nhất theo yêu cầu cũng có tab DIY này để ẩn/hiện 1 số panel, mà mục đích chính là thêm vài thông tin theo yêu cầu riêng (bản này đã có từ 1/1/2024 nhưng hiện nhờ vài bác xem trước để tránh lỗi như lần trước). tuy nhiên, các tọa độ đã được đơn giản rất nhiều, không theo hệ TedGo - anh rất tốt nhưng mình rất tiếc.
    Code:
    
    // vnav 31Dec23
    // I. Prepare panel : let say we have 3 panels. (adjust codes if you  have more panels)
    
    // On Panel Splitter Setting : background color as your favourite -> this will be a line bw button bar and the panels
    // Insert and set 3 panels with name and properties as follows:
    
    
    // Left Panel : Forced layout,
    // Center Panel : Forced layout,
    // Right Panel : Forced layout,
    
    // note:
    // 1. change the buttons and variables names as your perefer later on.
    // 2. panel name :  'blank' is sensitive
    // 3. if Panel contains child panels, then manipulate child panels' properties:
    //                                    + forced layout : no
    //                                    + enable resiable border : yes
    // 4. above settings are for combo panels. if set 1 panel a time, then just following these rules and let other panels hiden but 1.
    // 5. if we want space between panel, then we have to set 1 more fake PSS panel and let it on and off everytime playing/swithcing with panel, setting as i.e DarkOne theme:
    // name PSS: PSS Refresh,
    // forced layout : yes
    // hide panel on startup : yes
    // dimension: realtime following screen size
    //      left : 0;  top: $muldiv(%ps_width%,17,640) ; width: %ps_width% ; height: $sub(%ps_height%,$muldiv(%ps_width%,17,640))
    // or simply as : left : 0;  top: 0; width: %ps_width% ; height: %ps_height%
    
    
    //=============== let work =================
    //1. set global variables
    
    // panel on show
    $init_ps_global(vnav.tabBiography.view.switch,1)
    
    // color buttons
    $set_ps_global(vnav.tabBiography.bar.colour,32-32-32)
    $set_ps_global(vnav.tabBiography.btn.ncol,41-143-204)
    $set_ps_global(vnav.tabBiography.btn.hcol,155-155-155)
    $set_ps_global(vnav.tabBiography.btn.xcol,255-255-255)
    
    
    //2- show/hide combo panels
    
    // ----- local VARIABLES -----
    $puts(b.h,35) // button height, this should be in conjunction with button text font size.
    
    $puts(pn,3) // max number panel shown at once
    $puts(py,$add($get(b.h),1)) // panel y axis. add space gap bw button bar and panel to make a line-> actually the color of PSS bckgr
    $puts(ph,$sub(%ps_height%,$get(py))) // panel height -> all panels share the same height
    $puts(pw,$div(%ps_width%,$get(pn))) //  panel width -> equal width across panels
    
    
    // ----- BACKGROUND  buttons bar at top-----
    $fillrect(0,0,%ps_width%,$get(b.h),%vnav.tabBiography.bar.colour%) // hightlight buttons background
    
    // ----- PANEL-MANAGEMENT -----
    // show first, hide later, no space bw panels to address screen flashing.
    
    $ifequal(%vnav.tabBiography.view.switch%,2,
                  $showpanel_c(Left Panel,1)
                  $showpanel_c(Center Panel,1)
                  $showpanel_c(Clock Panel,1)
                  $showpanel_c(Right Panel,0)
                  $showpanel_c(Tape Panel,0)
    
                   $movepanel_c(Left Panel,0,$get(py),$get(pw),$get(ph))
                   $movepanel_c(Center Panel,$get(pw),$get(py),$get(pw),$get(ph))
                   $movepanel_c(Clock Panel,$mul($get(pw),2),$get(py),$add($get(pw),1),$get(ph))
                  ,
                   $ifequal(%vnav.tabBiography.view.switch%,3,
                               $showpanel_c(Left Panel,1)
                               $showpanel_c(Center Panel,1)
                                $showpanel_c(Right Panel,0)
                                $showpanel_c(Clock Panel,0)
                                $showpanel_c(Tape Panel,0)
    
                                $movepanel_c(Left Panel,0,$get(py),$div(%ps_width%,2),$get(ph))
                                $movepanel_c(Center Panel,$div(%ps_width%,2),$get(py),$add($div(%ps_width%,2),1),$get(ph)) // add 1 px to offset the div by 2
                              ,
                               $ifequal(%vnav.tabBiography.view.switch%,4,
                                              $showpanel_c(Tape Panel,1)
                                              $showpanel_c(Left Panel,0)
                                              $showpanel_c(Center Panel,0)
                                               $showpanel_c(Right Panel,0)
                                               $showpanel_c(Clock Panel,0)
     
                                                $movepanel_c(Tape Panel,0,$get(py),%ps_width%,$get(ph))
                                                     ,
                                                      $showpanel_c(Left Panel,1)
                                                      $showpanel_c(Center Panel,1)
                                                      $showpanel_c(Right Panel,1)
                                                      $showpanel_c(Clock Panel,0)
                                                      $showpanel_c(Tape Panel,0)
    
                                                      $movepanel_c(Left Panel,0,$get(py),$get(pw),$get(ph))
                                                      $movepanel_c(Center Panel,$get(pw),$get(py),$get(pw),$get(ph))
                                                      $movepanel_c(Right Panel,$mul($get(pw),2),$get(py),$add($get(pw),1),$get(ph))
     
                              )
        )
    )
     
    // ----- BUTTON-VARIABLES -----
    
    $font(uni 05_53,9,) // button font name and size, Segoe UI
    
    $puts(w1,$gettextwidth(=V=)) //just get width of button text. the actual button text shown is set by function construct button  -> textbuttons
    $puts(w2,$gettextwidth(=N=))
    $puts(w3,$gettextwidth(=A=))
    $puts(w4,$gettextwidth(=V=))
    $puts(x1,15)
    $puts(sb,50) // space bw 2 buttons
    
    // ----- BUTTONS -----
    //  a textbutton function should be on 1 line only.
    $textbutton($get(x1),0,$get(w1),$get(b.h),=V=,=V=,SETGLOBAL:vnav.tabBiography.view.switch:1;REFRESH,fontcolor:$ifequal(%vnav.tabBiography.view.switch%,1,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.ncol%),fontcolor:$ifequal(%vnav.tabBiography.view.switch%,1,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.hcol%))
    $textbutton($add($get(x1),$get(w1),$get(sb)),0,$get(w2),$get(b.h),=N=,=N=,SETGLOBAL:vnav.tabBiography.view.switch:2;REFRESH,fontcolor:$ifequal(%vnav.tabBiography.view.switch%,2,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.ncol%),fontcolor:$ifequal(%vnav.tabBiography.view.switch%,2,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.hcol%))
    $textbutton($add($get(x1),$get(w1),$get(w2),$mul($get(sb),2)),0,$get(w3),$get(b.h),=A=,=A=,SETGLOBAL:vnav.tabBiography.view.switch:3;REFRESH,fontcolor:$ifequal(%vnav.tabBiography.view.switch%,3,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.ncol%),fontcolor:$ifequal(%vnav.tabBiography.view.switch%,3,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.hcol%))
    $textbutton($add($get(x1),$get(w1),$get(w2),$get(w3),$mul($get(sb),3)),0,$get(w4),$get(b.h),=V=,=V=,SETGLOBAL:vnav.tabBiography.view.switch:4;REFRESH,fontcolor:$ifequal(%vnav.tabBiography.view.switch%,4,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.ncol%),fontcolor:$ifequal(%vnav.tabBiography.view.switch%,4,%vnav.tabBiography.btn.xcol%,%vnav.tabBiography.btn.hcol%))
    
    
    // done
    

    Vậy là ta vừa có kiểu vitange với hình vẽ làm trọng, chuyển qua dạng skin nói không với image - thực tế cực đoan quá cũng không hay. vì vậy, có lể linh hoạt kết hợp cả 2, đông tây hội ngộ, tân cổ giao duyên. ví dụ: làm 1 panel chứa cái tape vintage và 2 cái đồng hồ VU Meter Left/Right, 1 Wave Spectrum/VU Meter giả lập đèn LED cũng hay.

    Chúc các bác nghe nhạc vui.
     

    Attached Files:

    Last edited: 8/1/24
    tml3nr likes this.
  3. tml3nr

    tml3nr Advanced Member

    Joined:
    30/4/07
    Messages:
    3.082
    Likes Received:
    3.510
    Dạ em ghiền cái chất âm "nhừa nhựa" của nó nặng lắm rồi. Không thoát ra được :D
     
    viking likes this.
  4. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    chúc bác và gia đình năm mới Giáp Thìn 2024 an khang - an lành - sức khỏe- hạnh phúc nhé.
     
    anhchiennt2 and tml3nr like this.
  5. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    bữa nay ngày 8/1/2024, 1 ngày đẹp trời nên mình cũng sớm post bài cuối trong loạt chủ đề quan tâm về Fb2k foobar2000: Context Menu - menu hiện ra khi right click trên đối tượng.
    có thể nói, hệ thống context menu là thứ mình thích nhất khi bắt đầu dùng MS Office, từ những ngày đầu thô sơ, đến khi có cái kẹp giấy thình lình nhảy ra rồi lại quay về menu đơn giản. cảm giác Office luôn biết ta cần gì, khi không biết làm chi thì right click dù với đối tượng nào luôn là cú click thần thánh, giúp chúng ta đến gần nhau hơn, mở ra nhiều lựa chọn hợp lý.

    khi cài đặt skin fb2k, ta hay gặp nhiều tình huống cỡ chữ, vị trí hình . . . không như hàng mẫu. hay đôi khi chỉ muốn thay đổi màu sắc buttons thì context menu là hợp lý: right click ngay trên đối tượng và chọn 1 option.

    - các đối tượng xây dựng trên plugin sẽ có context menu riêng (nếu có), được built-in và ta hầu như không can thiệp được.
    - với đối tượng xây dựng từ các trình diễn dịch script (WSH, JScript, Spider Monley Panel) thì có thể chủ động tùy biến context menu, ví dụ: contect menu của dãy button (nằm dưới màn hình) của skin DarkOne theo post trên cho phép chọn 1 vài kiểu button. cách làm đơn giản: gán lệnh hiện menu vào sự kiện right click của trình diễn dịch (thuật ngữ là callback) on_mouse_rbtn_up(x, y) (thực tế có thể code bất cứ hành động nào, như exit fb2k . . . . dù ở đây chỉ nói tới context menu.)

    các trình diễn dịch có rất nhiều callback, bắt đầu với chữ on, với nhiệm vụ chính liên tục nghe ngóng Fb2k gọi tên mình để thực hiện. các nhóm on này xoay quanh các đối tượng/hành vi play track nhạc, danh sách bài hát, động thái các window . . . . và hầu như đáp ứng bất cứ hành động ta tưởng tượng ra khi tương tác với Fb2k qua chuột, bàn phím, touch màn hình.

    mình lấy ví dụ cụ thể từ đoạn script đồng hồ kim của Hunter - https://www.deviantart.com/extremehunter1972: sự kiện on_mouse_rbtn_up sẽ đưa ra 1 context menu với nhiều opton chọn màu cho đồng hồ và 2 mục khá quan trọng: can thiệp biến số và code của script. 2 hành vi này được gọi qua lệnh tương ứng:
    Code:
    window.ShowProperties(); -> mở ra cửa sổ edit biến số
    window.ShowConfigure(); -> mở ra cửa sổ coding
    
    nếu ta lưu các thông tin: tên, cỡ font chữ; vị trí các button hay hình ảnh . . . vào hệ thống biến số, người dùng sẽ can thiệp và chỉnh cho phù hợp với nhu cầu. các biến số này được CUI/DUI lưu xuống HDD nên sẽ bảo toàn giá trị dù có tắt Fb2k. nếu ta chỉnh sửa giá trị, các biến số mới sẽ có hiệu lực ngay.

    Code:
    function on_mouse_rbtn_up(x, y) {
     
        var _menu = window.CreatePopupMenu();
        var idx;
     
        _menu.AppendMenuItem(MF_DISABLED, 0, "CLOCK COLOUR");
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        _menu.AppendMenuItem(MF_STRING, 1, "Red");
        _menu.AppendMenuItem(MF_STRING, 2, "Green");
        _menu.AppendMenuItem(MF_STRING, 3, "Blue");
        _menu.AppendMenuItem(MF_STRING, 4, "Black");
        _menu.AppendMenuItem(MF_STRING, 5, "Other colour");
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        _menu.AppendMenuItem(MF_STRING, 6, "Play list infor");
        _menu.CheckMenuItem(6,window.GetProperty("DC"));
     
        if (ShiftDown) {
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        _menu.AppendMenuItem(MF_STRING, 20, "Properties");
        _menu.AppendMenuItem(MF_STRING, 21, "Configure...");
        }
     
        _menu.CheckMenuRadioItem(1, 5, window.GetProperty("ColorID", 1));
     
        idx = _menu.TrackPopupMenu(x, y);
        switch(idx) {
            case 1:
                color = RGB(140, 0, 0); // RED
                window.SetProperty("ColorCo",color)
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 2:
                color = RGB(0, 30, 0); // GREEN
                window.SetProperty("ColorCo",color)
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 3:
                color = RGB(0, 0, 100); // BLUE
                window.SetProperty("ColorCo",color)
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 4:
                color = RGB(0, 0, 0); // BLACK
                window.SetProperty("ColorCo",color)
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 5:
                color = utils.ColourPicker(window.ID, RGB(255, 0, 0));
                window.SetProperty("ColorCo",color)
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 6:
                window.GetProperty("DC") == false ? window.SetProperty("DC", true) : window.SetProperty("DC", false);
                break;
            case 20:
                window.ShowProperties();
                break;
            case 21:
                window.ShowConfigure();
                break;
        }
        _menu.Dispose();
        return true;
     
    }
    
    
    xong. đơn giản mà hiệu quả.
    điểm cuối cùng:
    Code:
    function on_mouse_rbtn_down(x, y) {
      ShiftDown = utils.IsKeyPressed(0x10) ? true : false;
    }
    
    nếu có 1 vài mục đặc biệt chỉ hiện ra khi trong context menu khi người dùng nhấn thêm phím Shift, ta sẽ thêm sự kiện khi vừa nhấn chuột phải xuống: xem người dùng có nhấn phím Shif và nhớ trong biến cục bộ ShiftDown. khi nhả chuột phải, ta sẽ xem xét giá trị biến này để hiện/ẩn 1 vài mục. trong ví dụ trên là 2 mục truy cập biến số và cửa sổ coding.
    Fb2k_VNAV_2.0 đã được thêm nhiều context menu để ta chủ động với 1 vài option, hy vọng đáp ứng yêu cầu đa số.

    Chúc các bác nghe nhạc vui, happy DIY và năm mới Giáp Thìn 2024 đầy hứng khởi - hạnh phúc.
     

    Attached Files:

    Last edited: 8/1/24
    jazzend and tml3nr like this.
  6. tml3nr

    tml3nr Advanced Member

    Joined:
    30/4/07
    Messages:
    3.082
    Likes Received:
    3.510
    Cảm ơn anh rất nhiều ạ!

    Em chúc anh cùng cả nhà năm mới nhiều niềm vui, nhiều sức khỏe. Nhiều may mắn và mọi chuyện như ý muốn.
     
    Last edited: 8/1/24
    viking likes this.
  7. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Thân gửi anh em 4rum vnav,
    Bản foobar2000 vnav V2.1 mới nhất đã có tại đây: (245 MB)
    - link Fshare: https://www.fshare.vn/file/TY1XF2P59KVE
    - hay link MediaFire (theo gợi ý của các bác) :
    https://www.mediafire.com/file/cmsv...0_v2.1_32bit__tech_vnav_2.1.0.G1.1.4.rar/file

    Bản này được xây dựng trên ý kiến của nhiều người, nên xem như đẽo cày giữa đường. 1 vài tính năng không biết thêm ra sao vì không hợp quy hoạch, mình để các đoạn script mẫu trong thư mục archive để các bác tham khảo tự DIY. 1 số tính năng khác cũng cố nhồi nhét trên tinh thần tôn trọng skin Tech của Br3tt. Vài điểm nhỏ nhưng quan trọng, cần minh họa đã được nêu ở các bài trên.

    Có điểm gì mới không ? Tất nhiên vẫn hoa lá cành và
    - tùy chỉnh button bar : ngoài button bar nguyên bản của Tech, nay có thêm 1 lựa chọn với size vừa mắt hơn. truy cập qua context menu rồi khởi động lại Fb2k.
    - thêm control bar ở dưới màn hình: control bar này của eurekagliese build theo nguyên mẫu của marc2k3 trong bộ sample theo JSP3 https://hydrogenaud.io/index.php/topic,58574.600.html, bản gần nhất 12/2023, sau khi Air Ken và Marc2k3 test xong thì mình bắt đầu mod lại cho hợp với skin Tech (bản chính được remark để ảnh em tham khảo):
    + linh động hình nền và phụ kiện theo theme màu của hình album.
    + linh hoạt layout các button theo size màn hình.
    + truy cập tắt vào tính năng: duyệt album art full màn hình (double click vào title để đóng màn hình này); nghe internet radio, bật màn hình liên tục khi play youtube, truy cập MilkDrop 3.23 là phiên bản mới nhất hỗ trợ 3.0 với hiệu ứng mượt hơn. . .
    + minh họa context menu.

    - Thêm vài tùy chỉnh loại, cỡ và vị trí các tiêu đề Tab (trên và dưới màn hình).
    - minh họa tab DIY (kiểu DarkOne vào phần Biography)
    - minh họa tape denon kiểu vintage Dmitri trong phần Biography.
    - thêm mục duyệt album art (theo hình) nhưng vẫn giữ nguyên folder structure vì các bác chỉnh lại tag cực quá.
    - vân vân và mây mây. . .

    Bản này được test trên màn hình HD, với các màn hình độ phân giải cao hơn, các bác tham khảo thông tin sau giúp (1 bác đưa, mình không có điều kiện test):
    https://www.quadraphonicquad.com/forums/threads/foobar2000-on-4k-display.29342/.

    Mình nhận được thêm vài set MilkDrop, được giới thiệu là đẹp. Sau khi lọc lựa thì cơ bản còn 3 set đính kèm, khi dùng set nào thì cần chỉ đường dẫn cho MilkDrop tới thư mục tương ứng (trong phần Preferences/Visualisations/Shpeck). Riêng MilkDrop 3.23 truy cập từ control bar như nêu trên.

    Vâng, sau khoảng 2 tháng từ 11/2023, với ý kiến đóng góp - testing từ anh em gần xa, trong và ngoài 4rum vnav, và thật sự là rất rất nhiều đam mê - chúng ta đã có được 1 bản Foobar2000 vnav 2.1 tạm cho là nghe được-nhìn hay, tuy không có cửa so với hàng hãng nhưng cũng rất đáng tự hào.

    1 lần nữa, xin cảm ơn và chúc anh em 4rum VNAV 1 năm mới Giáp Thìn 2024 cận kề thật nhiều hạnh phúc -niềm vui và mãi 1 niềm đam mê âm nhạc.

    Này là cỏ non rất mềm
    Này mùa xuân rất hiền
    Này là hoa rất thơm . . .
    dù là loài hoa mắc cỡ bên đường hôm nao . . .


    lưu ý: Foobar2000 và các plugin, chương trình đình kèm thuộc bản quyền của các nhà phát triển tương ứng. mình đã cẩn thận quét virus và xem xét nguồn nhưng không chịu trách nhiệm với các rủi ro liên quan (nếu có).

    link screen shot: (tiện thể upload vào medifire luôn).
    https://www.mediafire.com/file/69vzhunfclvh4vy/biography.png/file
    https://www.mediafire.com/file/u9jpjfqxj4sqhu9/playlist.png/file
    https://www.mediafire.com/file/0y5z6hoa3hipv2m/biography1.png/file
    https://www.mediafire.com/file/3qaqamv5tgloaw6/youtube.png/file
    https://www.mediafire.com/file/e4d63z6iazeyi71/library+filter.png/file
    https://www.mediafire.com/file/l8iqrir1p2ct3dz/nowplayng.png/file
    https://www.mediafire.com/file/wnyda6wp3b1z1nx/spectrum.png/file
     

    Attached Files:

    Last edited: 9/1/24
    Scorpio, nguyenlan and tml3nr like this.
  8. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    góc nhỏ to về script foobar2000:
    - ta có thể dùng bất cứ trình diễn dịch script nào với foobar 2000, dùng chung nhiều trình diễn dịch script cũng được. biến cục bộ của panel nào thì chỉ có giá trị lưu hành nội bộ trong panel đó, dù trùng tên với biến panel khác. nói vui thì đặt tên biến cũng là 1 nghệ thuật. các ví dụ đính kèm của DO (darkone skin) đều có những dòng code rất đẹp và tên biến gợi nhớ. đọc hiểu được code của người khác cũng đòi hỏi các bác nhẫn nại - nhất là khi code gốc nhầm lẫn gì đó. mấy dòng code mà cứ có this, that xanh lè là hàng tân thời (thuật ngữ là hướng đối tượng. ai có tham khảo bộ sách phổ cập tin học của Bác Dương Quang Thiện & cộng sự - cố mạnh thường quân được yêu mến - sẽ rất ấn tượng từ khóa này), mình người cũ nên bỏ qua cho lành.
    - wsh mod do T.P.Wang mần trên phiên bản Wsh gốc (hình như khoảng 2006 kèm foobar2000 0.9, tức là ngay sau khi Holger Stenger chuyển lại dự án cho sáng lập Peter Pawlowski [người cũ của NullSoft, winamp]), dev window mượn từ bên ngoài và chỉ hỗ trợ VB hay JavaScript cho gọn. Wang mod lại wsh đến phiên bản 1.5.6 (là phiên bản phổ biến nhất), sau đó Marc2k3 mod tiếp đến 1.6.3 thì chuyển qua làm JScript Panel đến 2.8.8 và hiện đang triển khai JScript Panel 3.
    Spider được TheQwertiest phát triển đến 1.6.1 và dừng từ 1/2022, bạn này hình như là người của Đông Âu và thời điểm này cũng có nhiều sự kiện - mong bạn ấy bình an vô sự.

    dùng trình diễn dịch mới bao giờ cũng có nhiều ưu điểm và hầu như việc chuyển code qua lại (giữa các trình diễn dịch) không gặp vấn đề lớn, nhất là tập lệnh logic.
    - lệnh switch là hàng cố cựu từ đời đục lỗ, cho phép thi hành từng khối lệnh nhưng nếu tránh được thì nên dùng cấu trúc if..else ổn định hơn. tránh dùng switch (true) rồi tả pí lù - sau này debug rối não.
    - các bác unzip các sample trong thư mục plugin thì sẽ thấy nhiều ví dụ minh họa. mình nén để các bác khỏi phân tâm. với spider plugin thì các sample đã được mod khác với nguyên bản.
    - control bar chữ nghĩa tùm lum : do bản gốc có nhiều option hay nên mình giữ lại, nếu không dùng thì các bác xóa cho dễ nhìn.
    - hệ thống biến số property của foobar2000: foobar2000 quản lý biến số theo panel nên xem như cục bộ. nguyên tắc là tất cả biến của trình diễn dịch script đều có thể được foobar2000 chuyển qua cho người dùng cuối edit trực tiếp chỉ với 1 lệnh:
    Code:
    var tên-biến-dev = window.GetProperty("tên-biến-fb2k-quản-lý ", giá-trị-mặc-định);
    Thường thì ta chỉ Get giá trị biến từ ngay đầu script và người dùng có thể tùy chỉnh giá trị qua cửa sổ tương tác property chung (như nêu ở bài trên). đặt tên biến tên-biến-fb2k-quản-lý gợi nhớ sẽ giúp người dùng dễ hình dung (vì đây là tên hiện ra trong cửa sổ edit biến số mặc định của foobar2000). sau khi edit và apply thì áp dụng giá trị ngay.
    do người dùng có thể nhập bất cứ thông tin/giá trị cho biến (như register của Windows) nên đôi lúc bị cự tuyệt xanh lè (lỗi) là bình thường, chỉ cần vô console xem báo lỗi ở dòng nào (dòng số mấy) và nhốt đoạn code đó vô cái hộp cát sandbox :
    Code:
    try { codes gây lỗi phải chi đó} catch (e) {}
    Trình diễn dịch sẽ để kệ nó kêu gào quậy phá trong cái hộp đó để đi làm việc khác.

    Âm nhạc là trò chơi cảm xúc cá nhân theo thời gian (psychoacoustics), đúng sai đôi khi không rõ ràng như 0 và 1. thấy thích thì nhích, thấy đủ là đủ, miễn phí mà có lợi thì càng nên thử.

    có bác đã edit control bar (context menu chỉnh size button/icon, hình ảnh).
    chúc các bác vui.
     
    Last edited: 11/1/24
    nguyenlan and tml3nr like this.
  9. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    script cover art:
    1. hình như cover art code bởi hunter không lấy album art nhúng (tag) theo track.
    mình chỉ thêm 1 default image phòng khi không có hình.
    script đều có option chỉ định hình cần lấy là front, back, cover hay cd nhưng không rõ nguyên tắc (dựa vào tên hay chi), bác nào biết chỉ giúp với.
    2. mình sẽ xem lại đoạn code cụ thể để các bác mod màu cho clock cho đồng bộ.

    edit:
    1. Album art cover: đoạn code sau (WSH) sẽ yêu cầu cover art của hunter ưu tiên đọc image nhúng trong tag track nhạc trước, không có thì tìm image trong thư mục cùng track nhạc - có vẻ - theo 1 số nguyên tắc đặt tên back, front, cd, artist và folder (ưu tiên nhất). nếu không có nữa thì tìm đại file nào cũng được (hoặc theo tên của các bác quy định), cuối cùng bó tay mới dùng hình mặc định.
    Code:
    var g_ext = window.GetProperty("vnav.Album art file extension", "png|jpg");
    var g_arr = g_ext.split("|");
    
              //parameter: 0: front 1: back 2: disc 3: icon 4: artist
               //1. check embedded images
                front = utils.GetAlbumArtEmbedded(metadb.RawPath, 0);
                back = utils.GetAlbumArtEmbedded(metadb.RawPath, 1);
                cd = utils.GetAlbumArtEmbedded(metadb.RawPath, 2);
                artist = utils.GetAlbumArtEmbedded(metadb.RawPath, 4);
          
              // 2. if not, check images enclosed in the same folder as audio track's
                if (! front) {front = utils.GetAlbumArtV2 (metadb, 0);}
                if (! back) {back = utils.GetAlbumArtV2(metadb, 1);}
                if (! cd) {cd = utils.GetAlbumArtV2(metadb, 2);}
                if (! artist) {artist = utils.GetAlbumArtV2(metadb, 4); }
    
            // 3. Otherwise, take whatever looks "ok" like an image in the same folder as current track's
                for (var i = 0; i < g_arr.length;i++ ) {
                    if (! front) {front = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*." + g_arr[i]);}
                    if (front ) { break;  }
                    //if (! back) {back = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*." + g_arr[i]);}
                    //if (! cd) {cd = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*." + g_arr[i]);}
                    //if (! artist) {artist = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*." + g_arr[i]);}
                }
                    if (! back) {back = front;}
                    if (! cd) {cd = front;}
                    if (! artist) {artist = front;}
                 //4. take default images . . .
    
    2. Màu analog clock theo album art:
    Dùng JScript nên đoạn code như sau, cơ bản không khác WSH do cùng 1 hệ
    Code:
    var img = utils.GetAlbumArtEmbedded(metadb.RawPath, 0);  // embedded image priority
    if (!img ) img=utils.GetAlbumArtV2(metadb, 0, false); //0: front 1: back 2: disc 3: icon 4: artist
    // 2 file ext only : JPG and PNG
    if (!img ) img = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*.jpg");
    if (!img ) img = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*.png");
    
    đoạn code sau sẽ lấy màu của album art để tô màu clock.
    Code:
    var colours = JSON.parse(img.GetColourSchemeJSON(5)); //array 0-> 4 items
    color=colours[0].col; // color var srores clock code, ie -4215627.. 
    lưu ý: background nhiều màu lấy từ album art là do ta 'bôi' blur hình (như trong script thực hiện duyệt library theo album art, mình đã thêm property blur để các bác tùy chỉnh 1-90 tăng theo độ nét hình), khác với lấy 1 mã màu như nêu trên.
    mình đã thử vài tùy chọn image như BMP, TIF, GIF thì 2 loại JPG (nén) và PNG (có hỗ trợ transparent) có vẻ "ổn" nhất.
    vậy là chúng ta có thể duyệt qua album art của tất cả track dù được thu thập chung trong 1 collection (1 phát hiện thú vị của các bác).
    chúc các bác ngày mới nhiều niềm vui.
     
    Last edited: 13/1/24
    nguyenlan and tml3nr like this.
  10. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    PSS script: (panel Stack Split, chịu trách nhiệm phân lô bán nền của CUI foobar2000 ).
    theo ý kiến của 1 bác phương xa về sự tin cậy và ổn định của script trong PSS, cụ thể là khi edit script thì thấy hên xui và hay gây lỗi, đóng foobar2000.
    sau khi lụi cụi 1 hồi thì mình thấy nguyên nhân chính là cú pháp REM (//) không có tác dụng, thậm chí khi bỏ khóa $ trước câu lệnh, PSS cũng không tha mà cố đọc rồi crash fb2k.

    với đa số ngôn ngữ lập trình, ta thường dùng '//' hay cặp /**/ trước các dòng chú giải và các đoạn code không dùng, ai ngờ PSS cũng đọc hết, may là tập lệnh ít (có khi nào nó đọc chú giải và hiểu như AI !!?).
    Chắc phải xem lại hết các plugin có hỗ trợ script (như LPlaylist, Wave Seebar, . .).

    cảm ơn các bác.

    P/S. 1 bác mần được cái analog clock để giữa 2 cái reel nhìn khá hay, mình sẽ xem qua rồi post lại đoạn script (để đảm bảo code sạch).
     
    Last edited: 14/1/24
    tml3nr likes this.
  11. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Analog Clock:
    bữa trước có bác phương xa nhá cái clock tròn tròn nằm giữa 2 cái tape nhìn rất hay, sau khi google translate qua lại 1 hồi thì "his sugar he goes, my sugar i go", có vẻ bạn ấy muốn lưu hành nội bộ.
    nãy giờ lụi cụi cũng dựng được 1 bản, không phải dạng plugin xịn xò như của bạn kia, nhưng cũng xem được:

    1 - analog clock: có sẵn trong bản dựng foobar2000_vnav_2.1 nên chỉ chỉnh lại script (JSP 2.8.8), thêm options:
    + chọn màu album art làm màu nền (như theo post trên).
    + chọn album art làm hình nền.
    + chọn hình tùy ý bên ngoài làm hình nền. hình để trong thư mục và đặt tên như sau :
    Code:
    [thư mục Foobar2000]\skins\tech\images\vnav_fb2k\analogClock.jpg
    script đầy đủ như sau:
    Code:
    
    // ==PREPROCESSOR==
    // @name "fooClock 1.0"
    // @author "Hunter, mod by vnav 01Jan24"
    // ==/PREPROCESSOR==
    
    // Coded by Hunter - https://www.deviantart.com/extremehunter1972
    // Released with DUIFOON 1.1 theme - Version 16.11.2010
    // mod by vnav Jan24 (transcoded from WSH into latest JSP 2.8.8  platform)
    // JSP 2.8.8
    
    var MF_SEPARATOR = 0x00000800;
    var MF_DISABLED = 0x00000002;
    var MF_STRING = 0x00000000;
    
    // Used in window.GetColorCUI()
    var ColourTypeCUI = {
        text: 0,
        selection_text: 1,
        inactive_selection_text: 2,
        background: 3,
        selection_background: 4,
        inactive_selection_background: 5,
        active_item_frame: 6
    };
    
    // Used in window.GetFontCUI()
    var FontTypeCUI = {
        items: 0,
        labels: 1
    };
    
    // Used in window.GetColourDUI()
    var ColourTypeDUI = {
        text: 0,
        background: 1,
        highlight: 2,
        selection: 3
    };
    
    // Used in window.GetFontDUI()
    var FontTypeDUI = {
        defaults: 0,
        tabs: 1,
        lists: 2,
        playlists: 3,
        statusbar: 4,
        console: 5
    };
    
    
    var g_is_default_ui = window.InstanceType;
    var d = new Date();
    var g_textcolor =0, g_textcolor_hl =0, g_backcolor =0 ;
    var color=window.GetProperty("ColorCo",RGB(0, 0, 0));
    var x, y, ww, wh, w_h, strPlman;
    var datefont = gdi.Font("Tahama", 15, 0); //Lucida Sans Unicode
    
    var g_art_type = window.GetProperty("vnav.Album art type", "0|1|2|4"); //0: front 1: back 2: disc 3: icon 4: artist
    var g_arr_type = g_art_type.split("|");
    var img_analog_clock=null;
    var img_path = fb.ProfilePath + "skins\\tech\\images\\vnav_fb2k\\"
    var g_img_clmask = gdi.Image(img_path+"foony_CDMask.png");
    var img_clock_default=gdi.Image(img_path+window.GetProperty("vnav.Analog clock", "analogClock.jpg"));
    var img_clock=null;
    
    
    if (!datefont) {
        if (g_is_default_ui) { // DUI
            datefont = window.GetFontDUI(FontTypeDUI.defaults);
        } else { // CUI
            datefont = window.GetFontCUI(FontTypeCUI.items);
        }
    }
    
    if (g_is_default_ui) { // DUI
            g_textcolor = window.GetColourDUI(ColourTypeDUI.text);
            g_textcolor_hl = window.GetColourDUI(ColourTypeDUI.highlight);
            g_backcolor = window.GetColourDUI(ColourTypeDUI.background);
    } else { // CUI
            g_textcolor = window.GetColourCUI(ColourTypeCUI.text);
            g_textcolor_hl = window.GetColourCUI(ColourTypeCUI.text);
            g_backcolor = window.GetColourCUI(ColourTypeCUI.background);
    }
    
    window.SetInterval(function(){ // create timer for initial clock display
        d = new Date();
       window.Repaint();
    }, 1000);
    
    
    // =============================================== //
    
    function on_paint(gr) {
     
        gr.SetTextRenderingHint(5);
        gr.SetSmoothingMode(4);
        gr.DrawRect(0,0,ww,wh,g_backcolor,g_backcolor);
        gr.FillSolidRect(0, 0, ww, wh, g_backcolor);
     
        if ((window.GetProperty("ColorID", 1)==7 && img_clock) || (window.GetProperty("ColorID", 1)==8 && img_clock_default )) {
            var g_img = img_analog_clock;
            var metadb = fb.IsPlaying ? fb.GetNowPlaying() : fb.GetFocusItem();
                if (metadb && g_img) {
                        try {
                            g_img = g_img.Resize(wh, wh * (g_img.Width / g_img.Height));
                            gr.DrawImage(g_img, 0, 0, g_img.Width , g_img.Height , 0, 0, g_img.Width, g_img.Height);
                        } catch (e) {}
                }
    
            if (g_img) g_img.Dispose();
    
        } else {    
            gr.FillEllipse(x - w_h / 2, y - w_h / 2, w_h, w_h, color); // Clock face color
        }
    
    
        // ************* Hour Dots
        var steps = 12;
        var radius = scale/4+scale/4-80;
        var hour_r = 6;
    
        for (var i = 0; i < steps; i++) {
    
            xV = (radius * Math.cos(Math.PI * i / steps * 2 - Math.PI / 2));
            yV = (radius * Math.sin(Math.PI * i / steps * 2 - Math.PI / 2));
    
            gr.FillEllipse(x + xV - hour_r / 2, y + yV - hour_r / 2, hour_r, hour_r, RGB(80, 80, 80));
    
        }
        // ************* Hour Numbers
     
        for (var i = 0; i < steps; i++) {
    
            var radius =  scale/4+scale/4-100;
    
            i == 0 ? hours = 12 : hours = i;
    
            xV = (radius * Math.cos(Math.PI * i / steps * 2 - Math.PI / 2));
            yV = (radius * Math.sin(Math.PI * i / steps * 2 - Math.PI / 2));
    
            gr.DrawString(hours, datefont, RGB(150,150,150), x + xV - hour_r / 2, y + yV - hour_r / 2, hour_r, hour_r, 0x11005000); //
        }
     
     
        // ************* Minute Dots
     
        var steps = 60;
        var radius =  scale/4+scale/4-80;
        var minute_r = 4;
    
        for (var i = 0; i < steps; i++) {
    
            xV = (radius * Math.cos(Math.PI * i / steps * 2));
            yV = (radius * Math.sin(Math.PI * i / steps * 2));
    
            gr.FillEllipse(x + xV - minute_r / 2, y + yV - minute_r / 2, minute_r, minute_r, RGB(120, 120, 120));
    
        }
     
        // ************* Minute Hand
     
        var radius =  scale/4+scale/4-110;
        var steps = 60;
        var m_angle = d.getMinutes() + d.getSeconds() / 60;
    
        xV = (radius * Math.cos(Math.PI * m_angle / steps * 2 - Math.PI / 2));
        yV = (radius * Math.sin(Math.PI * m_angle / steps * 2 - Math.PI / 2));
    
        gr.DrawLine(x, y, x + xV, y + yV, 5, RGB(50, 50, 50));
        gr.DrawLine(x, y, x + xV, y + yV, 2, RGB(100, 100, 100));
    
        // ************* Hour Hand
     
        var radius =  scale/5+scale/5-85;
        var steps = 12;
        var m_angle = d.getHours() % 12 + d.getMinutes() / 60;
    
        xV = (radius * Math.cos(Math.PI * m_angle / steps * 2 - Math.PI / 2));
        yV = (radius * Math.sin(Math.PI * m_angle / steps * 2 - Math.PI / 2));
    
        gr.DrawLine(x, y, x + xV, y + yV, 5, RGB(50, 50, 50));
        gr.DrawLine(x, y, x + xV, y + yV, 2, RGB(100, 100, 100));
    
        // ************* Second Hand
     
        var radius = scale/4+scale/4-90;
        var steps = 60;
        var s_angle = d.getSeconds();
    
        xV = (radius * Math.cos(Math.PI * s_angle / steps * 2 - Math.PI / 2));
        yV = (radius * Math.sin(Math.PI * s_angle / steps * 2 - Math.PI / 2));
    
        gr.DrawLine(x, y , x + xV, y + yV, 3, RGB(200, 50, 50));
    
        // ************* Second Hand  (oposite side)
     
        var radius =  scale/8+scale/8-45;
        var s_angle = 90 + d.getSeconds();
    
        xV = (radius * Math.cos(Math.PI * s_angle / steps * 2 - Math.PI / 2));
        yV = (radius * Math.sin(Math.PI * s_angle / steps * 2 - Math.PI / 2));
    
        gr.DrawLine(x, y, x + xV, y + yV, 3, RGB(200, 50, 50));
    
        var center_r = 10;
        gr.FillEllipse(x - center_r / 2, y - center_r / 2, center_r, center_r, RGB(200, 50, 50)); //Center dot
        var center_r = 4;
        gr.FillEllipse(x - center_r / 2, y - center_r / 2, center_r, center_r, RGB(50, 50, 50)); //Center dot2
    
        gr.FillGradRect(x - w_h / 2 - 10, y - w_h / 2 - 10, w_h + 20, w_h + 20, 40, RGBA(235, 235, 255, 150), RGBA(0, 0, 0, 0), 0.55); // glass effect
        gr.FillGradRect(x - w_h / 2 - 10, y - w_h / 2 - 10, w_h + 20, w_h + 20, 20, RGBA(255, 255, 255, 100), RGBA(0, 0, 0, 0), 0.5); // glass effect
     
        var mask = ww / 2;
        var mask_pad = 1;
     
        gr.DrawEllipse(x - w_h / 2 - mask / 2 - mask_pad, y - w_h / 2 - mask / 2 - mask_pad, w_h + mask + mask_pad * 2, w_h + mask + mask_pad * 2, mask, g_backcolor);
    
        gr.DrawEllipse(x - w_h / 2 + 7, y - w_h / 2 + 7, w_h - 14, w_h - 14, 18, RGBA(250, 250, 250, 255));
        gr.DrawEllipse(x - w_h / 2, y - w_h / 2, w_h, w_h, 2, RGB(180, 180, 180));
        gr.DrawImage(blur, 0, 0, ww, wh, 0, 0, ww, wh);
        gr.DrawEllipse(x - w_h / 2 + 7, y - w_h / 2 + 7, w_h - 14, w_h - 14, 8, RGBA(250, 250, 250, 55));
     
        //gr.DrawRect(10, 10, ww - 20, wh - 20, 1, g_textcolor_hl); // boder frame
     
     
        // ************* Digital Clock
     
        if(window.GetProperty("DC",true)){
            plmanGetDetail();
            gr.DrawString(d.toLocaleString()+strPlman, datefont, g_textcolor, 0, wh-44, ww, 40, 0x11005000);
       }
     
    }
    
    
    function plmanGetDetail() {
        strPlman = "\nPlaylist : "+ plman.PlaylistItemCount(plman.PlayingPlaylist);
        strPlman=strPlman + " - " + utils.FormatDuration(plman.GetPlaylistItems(plman.ActivePlaylist).CalcTotalDuration());
        strPlman=strPlman + " - " + utils.FormatFileSize(plman.GetPlaylistItems(plman.ActivePlaylist).CalcTotalSize());
    }
    
    // =============================================== //
    
    function on_size() {
    
        plmanGetDetail();
        ww = window.Width;
        wh = window.Height;
    
        x = ww / 2; // CENTER
        y = wh / 2; // CENTER
     
        if (ww <= wh) {
            scale = ww;  
        } else {
            scale = wh;    
           }
       
        w_h = scale/2+scale/2-100;
     
        if (ww <= 0 || wh <= 0) return;
    
        blur = gdi.CreateImage(ww, wh);
        var g = blur.GetGraphics();
        var blur_pad = 15;
        var blur_pad2 = 8;
    
        g.DrawEllipse(x - w_h / 2 + blur_pad2, y - w_h / 2 + blur_pad2, w_h - blur_pad2 * 2, w_h - blur_pad2 * 2, 3, RGBA(0, 0, 0, 250));
        g.DrawEllipse(x - w_h / 2 + blur_pad, y - w_h / 2 + blur_pad, w_h - blur_pad * 2, w_h - blur_pad * 2, 3, RGBA(0, 0, 0, 255));
    
        blur.StackBlur(10);
        blur.ReleaseGraphics(g);
    
    }
    
    function on_mouse_rbtn_down(x, y) {
      ShiftDown = utils.IsKeyPressed(0x10) ? true : false;
    }
    
    function on_mouse_rbtn_up(x, y) {
     
        var _menu = window.CreatePopupMenu();
        var idx;
     
        _menu.AppendMenuItem(MF_DISABLED, 0, "CLOCK CLOCK");
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        _menu.AppendMenuItem(MF_STRING, 1, "Red");
        _menu.AppendMenuItem(MF_STRING, 2, "Green");
        _menu.AppendMenuItem(MF_STRING, 3, "Blue");
        _menu.AppendMenuItem(MF_STRING, 4, "Black");
        _menu.AppendMenuItem(MF_STRING, 5, "Other colour");
        _menu.AppendMenuItem(MF_STRING, 6, "Album Art theme");
        _menu.AppendMenuItem(MF_STRING, 7, "Album Art image");
        _menu.AppendMenuItem(MF_STRING, 8, "Default image");
        _menu.CheckMenuRadioItem(1, 8, window.GetProperty("ColorID", 1));
     
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        _menu.AppendMenuItem(MF_STRING, 9, "Play list infor");
        _menu.CheckMenuItem(9,window.GetProperty("DC"));
     
        if (ShiftDown) {
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        _menu.AppendMenuItem(MF_STRING, 20, "Properties");
        _menu.AppendMenuItem(MF_STRING, 21, "Configure...");
        }
     
    
        var prvIDX=window.GetProperty("ColorID", 1);
        idx = _menu.TrackPopupMenu(x, y);
        switch(idx) {
            case 1:    
                color = RGB(140, 0, 0); // RED
                window.SetProperty("ColorCo",color);
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 2:
                color = RGB(0, 30, 0); // GREEN
                window.SetProperty("ColorCo",color);
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 3:
                color = RGB(0, 0, 100); // BLUE
                window.SetProperty("ColorCo",color);
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 4:
                color = RGB(0, 0, 0); // BLACK
                window.SetProperty("ColorCo",color);
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 5:
                color = utils.ColourPicker(window.ID, RGB(255, 0, 0));
                window.SetProperty("ColorCo",color);
                window.SetProperty("ColorID", idx);
                on_size()
                window.Repaint();
                break;
            case 6:
                window.SetProperty("ColorID", idx);
                if (!on_playback_new_track()) {
                    window.SetProperty("ColorID", prvIDX);
                }
                break;
            case 7:
                window.SetProperty("ColorID", idx);
                if (!on_playback_new_track()) {
                    window.SetProperty("ColorID", prvIDX);
                }
                break;
            case 8:
                window.SetProperty("ColorID", idx);
                if (!on_playback_new_track()) {
                    window.SetProperty("ColorID", prvIDX);
                }
                break;
        case 9:
                window.GetProperty("Digital C") == false ? window.SetProperty("DC", true) : window.SetProperty("DC", false);
                break;
            case 20:
                window.ShowProperties();
                break;
            case 21:
                window.ShowConfigure();
                break;
        }
        _menu.Dispose();
        return true;
     
    }
    
    
    function on_playback_new_track(handle) { //console.log("new play track action right away");
        var idColor=window.GetProperty("ColorID", 1);
        var    metadb = fb.IsPlaying ? fb.GetNowPlaying() : fb.GetFocusItem();
     
        if (idColor == 8) { // if current clock color is set to default image
            if (!img_clock_default) {
                fb.ShowPopupMessage(img_path+window.GetProperty("vnav.Analog clock", "analogClock.jpg"),"File does not exist.");
            } else {img_analog_clock= img_clock_default;}        
        } else {
            // get image
            if (metadb) {
                    if (img_clock) img_clock.Dispose();
                    //1. embeddded as tag
                    for (var i = 0; i < g_arr_type.length;i++ ) {
                        img_clock = utils.GetAlbumArtEmbedded(metadb.RawPath, g_arr_type[i]);
                        if (img_clock ) { break;  }
                    }
                 
                    //2. enclosed in the same track's folder
                    if (! img_clock) {
                            for (var i = 0; i < g_arr_type.length;i++ ) {
                                img_clock=utils.GetAlbumArtV2(metadb, g_arr_type[i], false);
                                if (img_clock ) { break;  }
                            }    
                    }
                 
                    // 3. if not, take whatever image in the same track's folder
                    if (!img_clock ) img_clock = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*.jpg");
                    if (!img_clock ) img_clock = gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*.png");
            }
            if (img_clock) img_analog_clock= img_clock;
        }
    
     
        if (idColor == 6) { // if current clock color is set to album art
            if (metadb && img_clock) {        
                    try {
                        var colours = JSON.parse(img_clock.GetColourSchemeJSON(5)); //array 0-> 4 items
                        color=colours[0].col;
                        window.SetProperty("ColorCo",color)
                        on_size();
                        window.Repaint();
                        return true;
                    } catch (e) {return false;}  
            }
            return false;
        } // ColorID==6
     
        if (idColor == 7 && !img_clock) return false;
        if (idColor == 8 && !img_clock_default) return false;
     
        if (idColor == 7 || idColor == 8) { // if current clock color is set to album art or default image
            if (metadb && img_analog_clock) {        
                    try {
                        var maskc = g_img_clmask.Resize(img_analog_clock.Width, img_analog_clock.Height);
                        img_analog_clock.ApplyMask(maskc);
                        on_size();
                        window.Repaint();
                        maskc.Dispose();
                        return true;
                    } catch (e) {return false;}  
            }
            return false;
        } // ColorID==7 or 8
    
    }
    
    
    function RGBA(r, g, b, a) {
        return ((a << 24) | (r << 16) | (g << 8) | (b));
    }
    
    function RGB(r, g, b) {
        return (0xff000000 | (r << 16) | (g << 8) | (b));
    }
    
    //EOF Jan24
    
    2- cái tape quay quay: định gỡ từ cái denon vinatage của Dmitri (như đã post) thì nhớ lại có 1 bản đâu đó dùng reel, băng cối của bác vo7210 và Hunter. Rã skin lấy được đoạn script (WSH Panel Mod 1.5.6 hay 1.6.3) và cái hình tape băng cối, sửa lại mấy dòng script để tiệm màu với skin Tech, canh 2 cái băng cối đặng có chỗ cho cái đồng hồ vô giữa:

    Code:
    // ================================================== //
    // @name "reel-to-reel example (Jan 27, 2013)"
    // @author "eXtremeHunter"reverse
    // ==================================================//
    // WSH Panel Mod 1.5.6  or 1.6.3
    
    
    var g_instancetype = window.InstanceType;
    var ww = 0, wh = 0;
    var g_backcolor = 0;
    var disc = gdi.Image(fb.ProfilePath + "skins\\tech\\images\\r2r\\reel.png");
    
    
    var angle = 0;
     
    var rotationInterval = 50;
    var rotationTimer;
    var rotationTimerStarted = false;
     
    var reverse = false;
    
    var diskDia=295;
    var discW = 295;
    var discH = discW;
    var disc = disc && disc.resize(discW, discH);
    var discAlpha = 100; // disc transparency 0-255.
    var pad=diskDia;
    var tapeColor = RGB(60,50,40);
    
    // Used in window.GetColorCUI()
    ColorTypeCUI = {
        text: 0,
        selection_text: 1,
        inactive_selection_text: 2,
        background: 3,
        selection_background: 4,
        inactive_selection_background: 5,
        active_item_frame: 6
    };
    
    // Used in window.GetColorDUI()
    ColorTypeDUI = {
        text: 0,
        background: 1,
        highlight: 2,
        selection: 3
    };
    
    if (g_instancetype == 0) { // CUI
            g_backcolor = window.GetColorCUI(ColorTypeCUI.background);
    } else if (g_instancetype == 1) { // DUI
            g_backcolor = window.GetColorDUI(ColorTypeDUI.background);
    } else {
            // None
    }
    
    function RGB(r, g, b) {
        return (0xff000000 | (r << 16) | (g << 8) | (b));
    }
     
    
    // ============================== //
    // START
    
    function on_size() {
        ww = window.Width;
        wh = window.Height;
    
        //1. stand alone
        //if (ww < diskDia*2)
        //{pad = 0; } else {pad= (ww-diskDia*2)/2;}
     
        //2. along with analog clock panel at center
        pad=ww-2*(ww-4*ww/6-diskDia)/2-diskDia*2;
     
     
        discX = (ww-4*ww/6-diskDia)/2; //(ww-diskDia*2-pad)/2;
        discY = (wh-diskDia)/2;
    }
    
    function on_paint(gr) {
        gr.FillSolidRect(0, 0, ww, wh, g_backcolor);
     
        var fillStart = discW/4;
        var fillEnd = discW + 10;
        var fill = discW-(fillEnd-fillStart);
        fill = (fill * (fb.PlaybackTime / fb.PlaybackLength));
     
        if(!isNaN(fill)){
     
        gr.SetSmoothingMode(2);
        var pos = discW - 10 - fill;
        gr.FillEllipse(discX+pos, discY+pos, discW-pos*2, discH-pos*2, tapeColor);
        var pos =  fillStart - fill;
        gr.FillEllipse(discX + discW + pad + pos, discY+pos, discW-pos*2, discH-pos*2, tapeColor);
        }
     
        disc && gr.DrawImage(disc, discX, discY, discW, discH, 0, 0, disc.Width, disc.Height, reverse ? -angle : angle, discAlpha);
        disc && gr.DrawImage(disc, discX + discW + pad, discY, discW, discH, 0, 0, disc.Width, disc.Height, reverse ? -angle : angle, discAlpha);
    }
     
    // ============================== //
    
    (function onRotationTimer() {
     
        if (!fb.IsPlaying || fb.IsPaused) {
            return;
        }
     
        if (!rotationTimerStarted) {
            rotationTimer = window.SetInterval(function () {
                if (angle >= 360) angle = 0;
                reverse ? angle += 10.1 : angle += 10; // point one for eliminating jumping in reverse mode.
                window.Repaint()
            }, rotationInterval);
            rotationTimerStarted = true;
        }
    })();
    
    function stopRotationTimer() {
        window.ClearInterval(rotationTimer);
        rotationTimerStarted = false;
    }
    
    function on_playback_stop(reason) {
        if (reason != 2) {
            stopRotationTimer();
        }
    }
    
    function on_playback_pause(state) {
        state ? stopRotationTimer() : onRotationTimer();
    }
    
    function on_playback_new_track() {
        onRotationTimer();
    }
    
    function on_mouse_rbtn_down(x, y) { ShiftDown = utils.IsKeyPressed(0x10) ? true : false; }
    function on_mouse_rbtn_up(x, y) {
        if (ShiftDown) { window.ShowConfigure(); }
        return true;
    }
    cái hình băng cối thì để trong thư mục như sau:

    Code:
    [thư mục foobar2000]\skins\tech\images\r2r\reel.png
    3- chỉnh lại PSS (panel Biography) của Foobar2000_VNAV_2.1 để trưng 2 món này:
    thêm 1 panel trên nền panel Biography (chính là WSH Panel mod diễn dịch script Reel to Reel của hunter), đặt tên là R2R Panel. chỉnh lại đoạn script của PSS nền như sau (cơ bản theo post trước đã nêu, mình chỉ minh họa đoạn script liên quan ):

    Code:
    . . .
    $ifequal(%vnav.tabBiography.view.switch%,3,
                  $showpanel_c(Clock Panel,1)
                  $showpanel_c(R2R Panel,1)
                  $showpanel_c(Left Panel,0)
                  $showpanel_c(Center Panel,0)
                  $showpanel_c(Right Panel,0)
                  $showpanel_c(Tape Panel,0)
               
                  $ifgreater($get(ph),$get(pw),
                           $movepanel_c(Clock Panel,$get(pw),$get(py),$get(pw),$get(ph))
                           $movepanel_c(R2R Panel,0,$get(py),%ps_width%,$get(ph))
                           ,
                           $movepanel_c(Clock Panel,$div($sub(%ps_width%,$get(ph)),2),$get(py),$get(ph),$get(ph))
                           $movepanel_c(R2R Panel,0,$get(py),%ps_width%,$get(ph))
                           )
                  ,
    . . .
    
    ở đây, chúng ta đã cho Clock panel làm nền, sau đó xếp R2R panel lên trên, ngay chính giữa.

    vậy là xong, đơn giản mà hiệu quả.

    Chúc các bác có nhiều khám phá thú vị với Foobar2000.
     

    Attached Files:

    Last edited: 17/1/24
    tml3nr likes this.
  12. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Analog Clock phần 2:
    Xem như các bác đã dựng được cái clock với 2 cái reel 2 bên. bữa nay chúng ta sẽ thêm vài thứ để dễ nhìn hơn:
    + điều chỉnh kích thước tape reel.
    + điều chỉnh vài thứ khác : màu cuộn băng, độ mỏng vỏ tape (độ trong suốt), tốc độ quay . . .
    + thêm 1 spectrum panel nhảy múa quanh cái clock.

    1. chỉnh script clock:
    chỉnh script reel2reel lại như sau:
    Code:
    // ================================================== //
    // @name "reel-to-reel example (Jan 27, 2013)"
    // @author "eXtremeHunter"reverse
    // ================================================== //
    
    
    var g_instancetype = window.InstanceType;
    var g_backcolor = 0;
    var ww = wh=0;
    var disc = gdi.Image(fb.ProfilePath + "skins\\tech\\images\\r2r\\reel.png");
    
    
    var angle = 0;
    var rotationTimer;
    var rotationTimerStarted = false;
    
    
    var discAlpha = window.GetProperty("vnav.Tape Reel transparency 0-255",100); // disc transparency 0-255.
    var tapeColor = eval(window.GetProperty("vnav.Tape Reel RGB color ","RGB(128,128,64)")); //60,50,40
    var reverse = window.GetProperty("vnav.Tape Reel reverse",false);
    var rotationInterval = window.GetProperty("vnav.Tape Reel speed",50);
    var pad=diskDia=discW = discH = window.GetProperty("vnav.Tape Reel diameter",400);
    disc = disc && disc.resize(discW, discH);
    
    
    // Used in window.GetColorCUI()
    ColorTypeCUI = {
        text: 0,
        selection_text: 1,
        inactive_selection_text: 2,
        background: 3,
        selection_background: 4,
        inactive_selection_background: 5,
        active_item_frame: 6
    };
    
    // Used in window.GetColorDUI()
    ColorTypeDUI = {
        text: 0,
        background: 1,
        highlight: 2,
        selection: 3
    };
    
    if (g_instancetype == 0) { // CUI
            g_backcolor = window.GetColorCUI(ColorTypeCUI.background);
    } else if (g_instancetype == 1) { // DUI
            g_backcolor = window.GetColorDUI(ColorTypeDUI.background);
    } else {
            // None
    }
    
    function RGB(r, g, b) {
        return (0xff000000 | (r << 16) | (g << 8) | (b));
    }
     
    
    // ============================== //
    // START
    
    function on_size() {
        ww = window.Width;
        wh = window.Height;
    
        // WSH seems only take the following script into account once at compiling script
        // thus, we have to recourse to fb2k property setting to let users adjust some variables, including tape diameter.
        //diskDia = discW = discH = 0.9*(ww > wh ? wh  : ww);
        //disc = disc.resize(discW, discH);
    
        //1. stand alone
        //if (ww < diskDia*2)
        //{pad = 0; } else {pad= (ww-diskDia*2)/2;}
     
        //2. along with analog clock panel at center
    
        pad=ww-2*(ww-4*ww/6-diskDia)/2-diskDia*2;
      
        discX = (ww-4*ww/6-diskDia)/2; //(ww-diskDia*2-pad)/2;
        discY = (wh-diskDia)/2;
     
    }
    
    function on_paint(gr) {
        gr.FillSolidRect(0, 0, ww, wh, g_backcolor);
     
        var fillStart = discW/4;
        var fillEnd = discW + 10;
        var fill = discW-(fillEnd-fillStart);
        fill = (fill * (fb.PlaybackTime / fb.PlaybackLength));
     
        if(!isNaN(fill)){
            gr.SetSmoothingMode(2);
            var pos = discW - 10 - fill;
            gr.FillEllipse(discX+pos, discY+pos, discW-pos*2, discH-pos*2, tapeColor);
            var pos =  fillStart - fill;
            gr.FillEllipse(discX + discW + pad + pos, discY+pos, discW-pos*2, discH-pos*2, tapeColor);
        }
        try {
        disc && gr.DrawImage(disc, discX, discY, discW, discH, 0, 0, disc.Width, disc.Height, reverse ? -angle : angle, discAlpha);
        disc && gr.DrawImage(disc, discX + discW + pad, discY, discW, discH, 0, 0, disc.Width, disc.Height, reverse ? -angle : angle, discAlpha);
        } catch (e) {
           fb.ShowPopupMessage("Properties may be set out of range.","Property set");
           stopRotationTimer();
        } 
    }
     
    // ============================== //
    
    (function onRotationTimer() {
     
        if (!fb.IsPlaying || fb.IsPaused) {
            return;
        }
     
        if (!rotationTimerStarted) {
            rotationTimer = window.SetInterval(function () {
                if (angle >= 360) angle = 0;
                reverse ? angle += 10.1 : angle += 10; // point one for eliminating jumping in reverse mode.
                window.Repaint();
            }, rotationInterval);
            rotationTimerStarted = true;
        }
    })();
    
    // ============================== //
    
    function stopRotationTimer() {
        window.ClearInterval(rotationTimer);
        rotationTimerStarted = false;
    }
    
    function on_playback_stop(reason) {
        if (reason != 2) {
            stopRotationTimer();
        }
    }
    
    function on_playback_pause(state) {
        state ? stopRotationTimer() : onRotationTimer();
    }
    
    function on_playback_new_track() {
        onRotationTimer();
    }
    // ============================== //
    function on_mouse_rbtn_down(x, y) { ShiftDown = utils.IsKeyPressed(0x10) ? true : false; }
    
    function on_mouse_rbtn_up(x, y) {
        var _menu = window.CreatePopupMenu();
    
        _menu.AppendMenuItem(0x00000000, 1, "Properties");
        if (ShiftDown) {
            _menu.AppendMenuItem(0x00000000, 2, "Configure");
        }
     
        var idx = _menu.TrackPopupMenu(x, y);
        switch(idx) {
            case 1:
                window.ShowProperties();
                break;
            case 2:
                window.ShowConfigure();
                break;
        }
        _menu.Dispose();
        return true;
    }
    
    sao chúng ta không để foobar2000 tự động điều chỉnh kích cỡ tape theo thông tin từ biến hệ thống (như đã nêu ở các post trước) ? thực ra, trình diễn dịch script theo foobar2000 chưa hẳn đã hoàn thiện - nhất là việc quản lý bộ nhớ, chưa nói đến load-balancing phân tải giữa các CPU, tận dụng GPU (fb2k không phải ngoại lệ)- nên (1) độ ổn định thấp và (2) rất chủ động né tránh những sự kiện ngốn nhiều tài nguyên. sau này, 1 số lệnh (như SetInterval) loop được chuyển cho OS quản lý luôn. ép quá thì hắn hỏi muốn tiếp tục chờ hay bỏ ngang (và lăn kềnh ra ngay, Windows cũng bó tay).

    với trường hợp này, WSH chọn compile (biên dịch) 1 lần script và nhớ "cứng" luôn các thông tin kích cỡ tape, muốn thay đổi thì phải compile lại (mở cửa số biên soạn script và save/apply).

    giải pháp: như đã đề cập ở post trước, ta biết foobar2000 quản lý biến số riêng và compile script ngay khi apply biến số mới. sao biết ? vì khi nhập 1 biến số quá hớp, foobar2000 báo lỗi ngay, tức là hắn compile lại script.

    vậy ta sẽ chuyển banh cho foobar2000 điều chỉnh vài thông số liên quan kích cỡ, màu băng và độ tron suốt vỏ băng. . . xong, đơn giản mà hiệu quả.
    Code:
    var discAlpha = window.GetProperty("vnav.Tape Reel transparency 0-255",100); // disc transparency 0-255.
    var tapeColor = eval(window.GetProperty("vnav.Tape Reel RGB color ","RGB(128,128,64)")); //60,50,40
    var reverse = window.GetProperty("vnav.Tape Reel reverse",false);
    var rotationInterval = window.GetProperty("vnav.Tape Reel speed",50);
    var pad=diskDia=discW = discH = window.GetProperty("vnav.Tape Reel diameter",400);
    
    2. Thêm 1 spectrum panel nhảy múa quanh cái clock:
    ta sẽ instert thêm 1 Channel Spectrum Panel vào cùng PSS nền cùng với analog clock, đặt tên là SpecA Panel, sau đó sửa đoạn script trong PSS nền như sau:
    Code:
    $ifequal(%vnav.tabBiography.view.switch%,3,
                  $showpanel_c(SpecA Panel,1)       
                  $showpanel_c(Clock Panel,1)
                  $showpanel_c(R2R Panel,1)
          
                  $showpanel_c(Left Panel,0)
                  $showpanel_c(Center Panel,0)
                  $showpanel_c(Right Panel,0)
                  $showpanel_c(Tape Panel,0)
    
                  $ifgreater($get(ph),$get(pw),
                           $movepanel_c(SpecA Panel,$get(pw),$sub(%ps_height%,$div(%ps_height%,2.2)),$get(pw),$div(%ps_height%,2.2))
                           $movepanel_c(Clock Panel,$get(pw),$get(py),$get(pw),$get(ph)) ,
                           $movepanel_c(SpecA Panel,$div($sub(%ps_width%,$get(ph)),2),$sub(%ps_height%,$div(%ps_height%,2.2)),$get(ph),$div(%ps_height%,2.2))
                           $movepanel_c(Clock Panel,$div($sub(%ps_width%,$get(ph)),2),$get(py),$get(ph),$get(ph))
                    )
                    $movepanel_c(R2R Panel,0,$get(py),%ps_width%,$get(ph)) 
    
    ta chọn Channel Spectrum Panel vì plugin này cho set nền trong suốt để không che clock phía dưới và có nhiều hiệu ứng hay - tiêu chí dựng foobar2000_vnav_2.0.
    chúng ta sẽ set 2 spectrum left right sao cho khi nhảy múa, các spectrum như bao quanh clock. các spectrum có dải màu đen qua trắng nên sẽ tạo hiệu ứng thị giác mạnh như nâng clock, tạo hiệu ứng 3D.

    chúc các bác vui.
     

    Attached Files:

    Last edited: 18/1/24
    tml3nr likes this.
  13. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Panel Stack Splitter (PSS):
    Sáng nay có vài ý liên quan đến dựng 1 UI/instance bằng những thao tác rất đơn giản, gửi đến các bác: Hầu như chúng ta đều biết shpeck và Channel Spectrum Panel là 2 plugin mang lại hiệu ứng khá bắt mắt khi dựng skin cho foobar2000. nếu kết hợp 2 thứ trong 1 màn hình xem ra sao.
    các bác thắc mắc nhiều về PSS nên sẽ kết hợp với PSS trong ví dụ minh họa này.

    cách làm : dùng bản dựng foobar2000_vnav_2.0 / 2.1 (do có sẵn 3 plugin này)
    - chọn PSS chứa shpeck.
    - insert thêm plugin: Channel Spectrum Panel.
    lưu ý quan trọng: như đã nêu trong post về denon vintage của Dmitri, PSS load các panel theo thứ tự từ dưới lên, do đó panel để trên trong cửa sổ Layout sẽ được load sau nên nằm trên trong màn hình (tương đương khái niệm lớp/layer xếp chồng trong các chương trình đồ họa, trong đó 1 plugin sẽ thuộc 1 layer và hoạt động trong lớp layer này). vì vậy, muốn Channel Spectrum Panel nằm trên Shpeck thì cần sắp xếp sao cho Shpeck nằm dưới trong cửa sổ Layout. (thứ tự showpanel trong script không ảnh hưởng đến thứ tự xếp chồng các panel).
    - đặt tên cho 2 panel: Spheck là SpekV Panel, panel Spectrum là SpecV Panel để dễ tham chiếu khi code.
    - Set property cho 2 panel : Forced Layout -> chọn.
    - Thêm đoạn script sau vào PSS nền:

    Code:
    $showpanel_c(SpecV Panel,1)
    $showpanel_c(SpekV Panel,1)
    $movepanel_c(SpecV Panel,0,$sub(%ps_height%,$div(%ps_height%,3)), %ps_width% ,$div(%ps_height%,3))
    $movepanel_c(SpekV Panel,0,0, %ps_width% , %ps_height%)
    
    xong.
    đơn giản mà hiệu quả.

    chỉ với vài thao tác đơn giản, chúng ta đã có thêm góc nhìn rất thú vị và - sky is the limit. nếu chúng ta chọn được 1 set màu ưng ý từ shpeck và trưng nó làm nền cho 1 playlist sẽ khá bắt mắt.
    P/S. tab DIY có thể dựng từ script nhưng sẽ dụng công hơn PSS nên mình không đề cập. các bác có thể tham khảo cách Br3tt dựng tab ngay trong skin Tech - Br3tt cùng Hunter, TedGo, kgena_ua , sau này có WilB là trong số ít những bậc thầy về coding script cho Foobar2000.

    như vậy, sau loạt bài về dựng foobar2000_VNAV 1.0 và 2.0 giới thiệu về foobar2000 và 1 số plugin, nay cũng xin khép lại loạt bài về DIY foobar2000 ở đây.

    các post này không theo qui tắc nào, vì chỉ lựa lọc theo ý đa số quan tâm, hy vọng gợi mở được chi đó.
    Xin cảm ơn bác chủ nhà @tml3nr đã cho tá túc hổm rày.

    Chúc các bác nghe nhạc vui.
     

    Attached Files:

    Last edited: 18/1/24
    tml3nr likes this.
  14. nguyenlan

    nguyenlan Advanced Member

    Joined:
    11/8/08
    Messages:
    630
    Likes Received:
    140
    Location:
    Q.12 - Hồ chí Minh
    Mình chạy Foobar của bác Viking bản dựng foobar2000_vnav_2.1 bị lỗi như thế này là sao :
    bữa trước vẫn chạy ngon , hôm nay restore lại win10 - cài lại chay bị như này ( đang chạy thêm bản
    Foobar 1.6.7 )
    FBviking.png
     
  15. nguyenlan

    nguyenlan Advanced Member

    Joined:
    11/8/08
    Messages:
    630
    Likes Received:
    140
    Location:
    Q.12 - Hồ chí Minh
    Bác Viking đã giúp giải quyết lỗi trên .
    Thanks Bác Chủ .
     
  16. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Anh cài giúp bộ font kèm theo thì foobar2000 mới hiện đúng các symbol như nút play, pause ở phần control bar.
    Dùng font symbol (thay cho hình image) có cái hay là thay đổi màu sắc rất đơn giản, 2 font đính kèm (1 của Google và 1 của Microsoft) chuyên biệt cho việc này- tuy chưa hoàn hảo nhưng dùng tốt.

    bản dựng dùng core foobar2000 2.0 và 2.1 với 1 số plugin dùng từ thời 1.x.y nên khó tránh khỏi tương thích (ví dụ tiêu biểu là PSS) nên ảnh hưởng trải nghiệm người dùng. Phong trào mod skin cũng trầm lắng nhiều - có lẽ 1 phần nguyên nhân là đây.
    ở phía ngược lại, dùng foobar2000 1.x.x cho tương thích cao với 1 số plugin quá date thì lại quay lưng với số đông plugin khác (như youtube, JSP3 được update thường xuyên) có nhiều cái mới và hay.
    Hy vọng thời gian tới có các bản cập nhật.

    Chúc bác vui.
     
    Last edited: 24/1/24
    nguyenlan, anhchiennt2 and tml3nr like this.
  17. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    bữa tất niên hội bạn già, lớp người bắt đầu có sương mù giăng ngang não nên nhớ nhớ quên quên. nhân tiện có bạn thắc mắc cái đồng hồ vạn niên ở nhà bị ngáo nên chợt có ý biến cái foobar2000 thành người nhắc việc: giỗ chạp (âm lịch) xa gần hay ngày lãnh lương hưu, sinh nhật con cháu (dương lịch) đều được tận tình thông báo.
    để ngâm cứu rồi báo cáo các bác.
     
    tml3nr likes this.
  18. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    ngồi post bài này khi ngoài kia bắt đầu có những nụ mai sớm, mắc 1 cơn mưa chứng là bung nở thôi. giờ này độ 10 ngày nữa là đêm trừ tịch, khoảnh khắc thiêng liêng giao thời giữa trời và đất, lòng bỗng thấy bồi hồi khó tả . . . bao nhiu mùa xuân đã qua và giờ ngồi đếm giọt thời gian rơi bên hiên nhà mỗi sớm mai.

    Rồi dặt dìu mùa xuân theo én về
    Mùa bình thường mùa vui nay đã về
    mùa xuân mơ ước ấy đang đến đầu tiên
    Với khói bay trên sông, gà đang gáy trưa bên sông
    một trưa nắng vui cho bao tâm hồn

    (Văn Cao)
     
    Last edited: 30/1/24
    tml3nr likes this.
  19. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    chủ đề: dùng foobar2000 quản lý sự kiện âm - dương lịch.

    phần 1: Khái niệm cơ bản âm-dương lịch.
    (theo hiểu biết rất hạn chế của mình nên rất có thể sai sót, không đúng. mục đích chính và duy nhất để bàn luận trong diễn đàn "Mạng nghe nhìn Việt Nam" vnav.vn - rất mong được góp ý).

    xuân-hạ-thu-đông, xuân phân, thu phân, hạ chí, đông chí rồi can chi chi ấy, . . . cái âm lịch mà bao đời nay chúng ta phải dựa vào để có khái niệm cho mùa và thời gian luôn hiện diện ở khắp nơi trong cuộc sống mỗi con người, từ khi sinh ra- sống-và cả khi và sau khi xa cõi tạm, rất đáng để cho chúng ta tìm hiểu.

    1 năm âm lịch thường có 12 hay 13 tháng; 1 tháng âm lịch theo tuần trăng luôn có 29,5 ngày - tức là ngày đầu tiên của tháng âm lịch (trăng non, điểm Sóc) luôn xác định; tháng 11 âm lịch (tháng 12 dương lịch) luôn có ngày Đông chí. 1 năm có 4 mùa với mỗi mùa có số ngày khác nhau, vì mùa hè trái đất ở xa mặt trời nên ít bị tác động (lực vạn vật hấp dẫn) và kiểm soát nên cứ nhàn nhã rong chơi dài ngày. ngược lại, mùa đông gần 'mặt trời' nên quay tít mù - ngày ngắn hơn. thu và xuân thì ở giữa nên bớt cực đoan hơn.

    các điểm xuân phân, thu phân, đông chí, hạ chí là thời điểm bắt đầu 1 mùa (theo Tây phương) và giữa mùa (theo Đông phương); xuân và thu là 'phân' vì đó là điểm giao/phân giữa hoàng đạo (mặt phẳng quỹ đạo trái đất) và xích đạo thiên cầu, do trái đất nghiêng (khoảng 23,44 độ vì bị chị Hằng mải seo-phì nên đụng phải). do là điểm giao/phân nên mặt trời chiếu trực diện với xích đạo trái đất 90 độ (do xích đạo thiên cầu là xích đạo trái đất phóng lớn) : ngày và đêm chính xác 12 giờ bình đẳng.

    2 điểm 'chí' kia là khi trái đất - và do đó - 2 cực ở xa mặt trời nhất: cực bắc xa nhất là đông chí, ngược lại là hạ chí, tức là trái đất 'nghiêng' nhiều nhất. chuyên môn gọi là xích đạo thiên cầu và hoàng đạo éo le nhất. tại 2 điểm 'chí' này, mặt trời vuông góc với 2 chí tuyến (bắc, nam) , 1 trong năm chí tuyến định vị bản đồ trái đất.

    vì vậy mà các mùa (trong 1 năm) có xu hướng di chuyển từ bắc xuống nam địa cầu theo thời gian của năm đó.

    vì trái đất hoàn thành 1 vòng quanh mặt trời (360 độ ~ 2 Pi) khoảng 365,25. . . ngày đêm nên xem như 1 ngày đêm (0:00:00 đến 23:59:59) ổng đi được cỡ gần 1 độ quanh cái tâm tưởng tượng (biểu kiến) đó (do quỹ đạo không phải tròn vo). tới đây, bắt đầu tưởng tượng rồi.

    như trên nói, độ dài các mùa theo số ngày đêm là khác nhau, nhưng hên là mùa được xác định bởi vị trí của trái đất (khoảng cách) so với mặt trời xunh quanh cái vòng chu kỳ 360 độ, nên khoảng cách (theo độ) giữa các mùa là đều như vắt chanh trên cái vòng này: cứ 90 độ là 1 điểm (360 độ/4).

    nhưng tính bao nhiêu độ thì phải có cạnh tham chiếu ? đúng. mỗi hành tinh quay quay mặt trời, như trái đất, sẽ tự vẽ ra 1 mặt phẳng tưởng tượng (của trái đất gọi là mặt phẳng hoàng đạo), góc tạo bởi đường thẳng nối hành tinh và mặt trời hợp với đường xuân phân (quy ước 0 độ) trong mặt phẳng hoàng đạo gọi là kinh độ mặt trời (tính từ điểm quan sát).

    vì vậy, xác định được kinh độ mặt trời là tính được các điểm giao mùa. ví dụ: đông chí trái đất khi kinh độ mặt trời là 270 độ.
    cũng hay là đông chí hay rơi vào 21,22/12 dương lịch nên cứ me vào quãnh này mà mò giá trị 270 độ kinh độ mặt trời , 1 hồi là tóm được, từ đó biết được Đông chí ( theo quy ước, 1 năm âm lịch là khoảng thời gian giữa 2 thời điểm đông chí gần nhất) nhưng không phải Tết 1/1 như in trên lịch (pháp lịch), vì ổng còn phải đi 90 độ nữa mới hết 1 vòng/1 năm và ngày Lễ, Tết do con người tự định. đây là ngày đặc biệt vì ngắn nhất (đêm dài nhất) ở phía bắc bán cầu, ngủ đã rồi ngồi không vì hồi xưa chưa có điện nên tranh thủ nghỉ ngơi là chính - Tết Đông chí.

    ở trên nhắc đến Thiên cầu, là quả cầu tưởng tượng vẽ ra bởi các ngôi/vì/chòm sao được xem là 'cố định' khi quan sát từ trái đất, khi chưa có GPS thì dùng mấy ảnh để định vị. giữa sông hồ biển cả xung quanh là nước thì đây là thượng sách, hàng hải ngày xưa phải thông thiên văn tường địa lý.

    và mặt phẳng hoàng đạo và mặt phẳng tưởng tượng của xích đạo trái đất phóng lớn ra đụng vô thiên cầu - gọi là xích đạo thiên cầu sẽ vẽ ra hệ tọa độ thiên cầu (kiểu như kinh tuyến và vĩ tuyến trái đất) cho ta định vị. điểm giao giữa mặt phẳng hoàng đạo và xích đạo thiên cầu là điểm phân : xuân phân, thu phân như nêu trên. nếu trái đất không bị 'nghiêng' 23,4 (4) độ thì chắc không có 2 điểm này.

    tóm lại:
    • đường hoàng đạo tạo bởi mặt phẳng hoàng đạo cắt 'vỏ' của thiên cầu ( không phải quỹ đạo trái đất quanh mặt trời).
    • các điểm trên đường hoàng đạo được định vị bởi kinh độ mặt trời, và do đó, là mùa ương ứng trên trái đất.
    • mỗi điểm trên hoàng đạo tương ứng với 1 trạng thái khí hậu trên trái đất, để cho dễ và tiện cho nông nghiệp (canh mưa, nắng, lạnh, . . .), người ta chia hoàng đạo thành 24 điểm (chia nhỏ nữa cũng không tác dụng vì thời tiết giữa 2 điểm nhỏ quá cũng ít thay đổi khắc nghiệt). 24 điểm này liên quan thời tiết khí hậu nên gọi là 24 tiết khí, 24 chia 2 lấy trung bình là 12 trung khí, 12 : 3 được 4 điểm trung khí đặc biệt nên trên (xuân, thu phân - hạ, đông chí). thực tế, người ta làm ngược lại, tức lấy 4 mốc quan trọng rồi cứ thêm bớt 15 độ ra tiết khí, 30 độ ra trung khí. sau đó, người ta thấy mỗi tiết khí nóng, lạnh, mát mẻ . . . theo mùa (ngày, tháng, năm), thậm chí theo giờ trong ngày lại ứng với tính khí của 1 con vật nên bắt đầu có đặt tên cho dễ nhớ. đặc biệt, 1 vài thời điểm nào đó - được cho - là thuận trời đất nên khởi sự hanh thông, vạn vật phát sanh là hợp lẽ . . . nên có thêm khái niệm lịch can-chi. lịch can chi (chữ) có thể tính ra từ âm lịch (số).
    • xác định được 2 điểm đông chí liên tục là biết được số ngày của năm âm lịch, mò thêm các điểm sóc là ra ngày đầu của tháng âm lịch (do số ngày các tháng âm lịch đều nhau 29,5 ngày - chỉ bù qua sớt lại cho tháng 29 ngày (thiếu) và tháng 30 ngày (đủ) là xong), năm nào có nhiều hơn 365 ngày là phải tính xem bỏ bớt 1 tháng (tháng có thời tiết như tháng liền kề trước - không có điểm trung khí nào cả).
    ngoài ra:
    1. âm dương lịch là gì ?
    • quy ước /bản chất 1 năm là thời gian cần thiết để trái đất quay xong 1 vòng (vẽ xong đường hoàng đạo) quanh mặt trời.
    • bản chất của 1 ngày và 1 đêm liên tục là thời gian trái đất tự xoay xong 1 vòng (chu kỳ trái đất) (gọi tắt là 1 ngày).
    • bản chất của 1 tháng âm lịch là thời gian mặt trăng quay xong 1 vòng quanh trái đất (cỡ 29,5 ngày đêm - đều như vắt chanh). đây là âm lịch đích thực như thời xa xưa đã dùng do Chị Hằng đáng tin và cần mẫn.
    nhưng bản chất của mùa - liên quan cơm áo hàng ngày - là do Mặt trời quyết định, chứ ngắm trăng không no được, nên con người mới phải lấy mặt trời làm chuẩn, trăng theo sau -> âm lịch lệch chuẩn, hay gọi là âm-dương lịch. đây chính là khái niệm âm lịch nói từ hồi hôm tới giờ và mọi người ngầm hiểu. hôm nào Ổng (trái đất) bê trễ ngày dài đêm thâu (mà Chị Hằng vẫn cần mẫn đã quay xong 1 vòng quanh ổng) thì ghi sổ thiếu, có bữa ổng xung quá đạp lẹ thì lại dư, nhưng do to lớn chậm chạp đường xa nên năm nào cộng sổ là cũng thấy Ổng thiếu mấy ngày, cộng dồn 4 năm thường dư cả tháng nên cứ 4 năm là Chị Hằng xóa nợ cho Ông trái đất 1 lần, nói nôm ra là 'nhuận' hay leap (nhảy).​
    • vì cách tính âm lịch dựa theo thiên văn (quan sát điểm đông chí) có sai số và theo qui định nên âm lịch hồi xưa - mỗi thời tính mỗi khác. từ khoảng (1912), bắt đầu chuẩn công thức thì tính âm lịch cũng chuẩn hơn, nhưng âm lịch hiện đại - như ta biết - dựa vào thời gian di chuyển hết 1 vòng quanh mặt trời của trái đât - mà ổng trái đất cũng có khi bê trễ chút lỉnh vì có tuổi, đôi khi bị vài tinh tú làm xao nhãng nên người ta cũng chỉ xác quyết được công thức tính âm lịch tới khoảng năm 2200 (dương lịch). tới đó tính tiếp. (nghe đồn thời gian 1 năm dương lịch tính bằng mấy cái đồng hồ nguyên tử lượng tử chi đó, nhưng do có dựa vào sao trên trời nên cũng có năm phải kêu trời, dù cũng chỉ +/- cỡ 0.9 giây).
    • ngược với âm lịch là dương lịch ? đương nhiên rồi. nhưng dương lịch hay nói đến là hệ lịch Gregoria do phương tây chủ xị và ngày nay được xem là chuẩn (khi không nói rõ loại lịch) để tiện cho bang giao quốc tế. dương lịch tính cũng tính theo giờ địa phương nên sẽ khác nhau theo từng múi giờ.
    2. âm lịch dùng chung giữa các nước ? không.
    do kinh độ mặt trời thay đổi theo điểm mốc xuân phân 0 độ, mà điểm này xác lập theo thời điểm ở từng vị trí trên trái đất nên người ta quy ước tính thêm múi giờ quốc tế (GMT hay UTC) của từng nước khi tính âm lịch. ví dụ: giờ Việt nam (UTC +7) sớm hơn giờ Trung quốc (UTC+8) 1 giờ nên nếu điểm Đông chí tính theo UTC+0 là 16h30ph30 ngày T thì của VN là (16h+7)30ph30=23h30ph30 vẫn là ngày T, Trung quốc là (16h+8)30ph30 = 0h30ph30 (24h nửa đêm, quy ước là 0h của ngay ngày hôm sau) của ngày T+1. vì thế, về nguyên tắc, nếu có khác biệt thì âm lịch Trung quốc sẽ đi sau Việt nam 1 giờ (như tính tuần trăng - moon phase, điểm tiết khí), 1 ngày (tết, lễ âm lịch như Thanh minh).
    cũng như dương lịch, âm lịch cũng theo từng múi giờ/quốc gia.​


    phần 2: cách tính âm lịch. (tiếp sau).
     
    Last edited: 30/1/24
    anhchiennt2 and tml3nr like this.
  20. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    sáng nay có thắc mắc về đường hoàng đạo, 1 số cho rằng đây là quỹ đạo tưởng tượng của mặt trời quanh trái đất mới đúng.
    khác nhau chỉ là cách nhìn và góc quan sát lấy điểm quy chiếu, như quan sát 2 con gà chạy quanh sân và lấy chính 1 trong 2 con làm quy chiếu cho con kia.
    đường hoàng đạo chỉ có 1, hình tròn, tâm là trái đất và cung tròn là giao cắt giữa mặt phẳng hoàng đạo (mặt phẳng quỹ đạo trái đất) với vỏ của thiên cầu.
    thiên cầu là quả cầu biểu kiến, do tưởng tượng mà ra, thấy vậy mà không phải vậy - có tâm là trái đất và bán kính rất lớn, vỏ của thiên cầu đính sương sa hạt lựu là các ngôi/vì/chòm sao mà ta quan sát được từ trái đất.
    theo cách nhìn này, rõ ràng mặt trời thuộc mặt phẳng hoàng đạo, nếu lấy trái đất làm quy chiếu thì đúng là mặt trời đang di chuyển (so với trái đất) - và khi đó thiên cầu cùng đường hoàng đạo đứng yên. 1 cách giải thích đúng chất biểu kiến để giản tiện cho các tính toán và không sai.
    vì vậy, tùy cách nhìn.

    thực tế là gì ? cả xóm mặt trời đều chuyển động trong không gian ngoài đó, nhưng bằng 1 cách nào đó mà các hành tinh luôn giữ được vị trí, quỹ đạo khá ổn định trong xóm. người ta đồ rằng có 1 dạng vật chất tối (có chứa năng lượng tối) tham gia vào việc giữ an ninh trật tự. mình cũng đã từng nêu quan điểm khi trao đổi trong diễn đàn về kính thiên văn james webb, ở đó các hố đen (1 dạng vật chất tối) là trùm cuối trong vũ trụ - mỗi thiên hà có 1 hố đen và cuối cùng quy về 1 hố đen chung của vũ trụ. điểm đen này sẽ nén vật chất (và năng lượng, bao gồm cả ánh sáng - vì khi ánh sáng đến chân trời sự kiện sẽ thành năng lượng) thành 1 điểm vô cùng nhỏ, vô cùng nặng đến khi tự sụp đổ và dẫn đến 1 sự bùng nổ - điểm big bang tái tạo vũ trụ. có nhiều hơn 1 vũ trụ, như có nhiều hơn 1 thiên hà trong 1 vũ trụ. và thế, hố đen chỉ là điểm đen nơi vật chất bị nén lại với khởi đầu như kiểu 1 ngôi sao hết hơi và đổ sụp vào lõi. và vì không có hố đen nên không có lỗ giun, tất nhiên không có du hành thời gian.

    chu kỳ của lịch can chi 60 năm lục thập hoa giáp thì mình không rõ nguyên nhân. giải thích theo lý thuyết thì đã rõ, nhưng đây chỉ là quy ước khi kết hợp 10 can x 12 chi - số tổ hợp (60) do tương khắc, nhưng bản chất là gì ? phải chăng người xưa cho rằng đời người tối đa 60 năm.

    điểm cuối, như đã nói, mùa di chuyển theo chiều bắc xuống nam địa cầu nên nước nào qua nhiều vĩ tuyến thì rất cảm nhận sự khác biệt (do cùng dịp nghỉ lễ nên di chuyển giữa các vùng). ở phương nam, trước tết còn hơi lạnh nhưng thường khi qua 1/1 âm là cảm thấy rất nắng nóng. ấy là vì mùa đang di chuyển xuống phía nam, và âm lịch dựa theo mùa nên có sự trùng hợp đó: mùa tạo ra Tết nguyên đán, không phải Tết nguyên đán tạo ra mùa.
     
    Last edited: 31/1/24
    tml3nr and anhchiennt2 like this.
  21. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    phần 2: cách tính âm lịch.

    nổ banh nhà lồng chợ xong thì vào việc thôi.

    //-------------------------//

    bước 1: tính âm lịch.
    trước khi vào việc thì mình rất tự tin, vì đây là lĩnh vực xưa như trái đất nên tài liệu tính toán âm lịch sẽ không thiếu.

    sau khi lùng sục trên Internet kiếm giải thuật cụ thể thì thấy quá rối và rõ ràng là thông tin chắp vá rời rạc thì nhiều, độ xác tín không có. hổng lẽ bỏ cuộc, trong khi đã nổ quá hớp với mấy bạn già:
    - âm lịch xưa (trước 1900) thì mỗi kỳ mỗi khác.
    - âm lịch sau này, hiện đại thì có công thức - ít nhất là đến 2200. nhưng không thể chắc được công thức vì vài chỗ phải tự thừa nhận là đúng (ví dụ, công thức theo tài liệu 'Astronomical Algorithms' của Jean Meeus, tái bản năm 1998).
    hồi nhỏ nghe Thầy Cô nói định lý, tiên đề khỏi chứng minh, đúng như mặt trời luôn mọc ở hướng đông. giờ có tuổi nên đầu cứng hơn chăng.

    chuyển hướng qua mì ăn liền - chấp nhận hàng OEM, lấy data người ta tính sẵn rồi nhập vô file text, bắt foobar2000 đọc theo cấu trúc data kiểu như XML hay JSON: session/ key/ value.
    đơn giản mà hiệu quả đúng tiêu chí DIY - do it for yourself.

    trong khi tìm hàng mẫu OEM thì, có vẻ, nguồn thông tin đều xuất phát từ trang này https://www.informatik.uni-leipzig.de/~duc/amlich/ của Anh Hồ Ngọc Đức (https://www.informatik.uni-leipzig.de/~duc/), thông tin học thuật tác giả và webite làm tăng niềm tin.
    quan trọng hơn, tác giả cho sẵn code nguồn dưới nhiều ngôn ngữ - trong đó có javascript là ngôn ngữ tích hợp theo plugin của foobar2000.

    sau khi thử vài mẫu thì đúng với pháp lịch (lịch treo tường ở nhà), nhưng lỡ ngày quan trọng lại sai thì sao !? ngồi đối chiếu từng ngày thì chắc chớt, dù tác giả dùng vài thông tin quan trọng cho cả mấy thế kỷ đã được tính trước, chỉ cần dùng công thức (như của Jean Meeus là tính ra ngày âm lịch, và do đó, lịch can chi).

    lại nhớ âm lịch Việt nam và Trung quốc chỉ chênh cỡ 1 giờ, về nguyên tắc là không thể ông nói gà bà nói vịt - cứ kiểm tra chéo thì cũng tăng độ tin tưởng.
    tìm 1 lúc thì ra tài liệu âm lịch Trung quốc https://github.com/ytliu0/ChineseCalendar của Yuk Tung Liu, có vẻ bạn này cũng làm trong lĩnh vực học thuật https://publish.illinois.edu/ytliu/educational-resources/.
    bản demo âm lịch Trung quốc ở đây https://ytliu0.github.io/ChineseCalendar/. (tiếng Anh và tiếng Trung)

    vậy là an tâm 1 phần về nguồn gốc.
    và xem như có người cầm tay chỉ việc cách tính âm lịch.

    bước 2: tích hợp nhắc việc cho foobar2000.

    mình sẽ dùng mã nguồn javascript của bản âm lịch Việt nam của Anh Hồ Ngọc Đức. các bạn có thể tham khảo mã nguồn javascript, tài liệu và minh họa giải thuật tính âm lịch Việt nam rất cụ thể và chi tiết trên trang chủ của tác giả (nêu trên).
    trong khi review mã nguồn thì thấy tác giả Hồ Ngọc Đức có giải thuật tìm xem 1 ngày âm lịch nào đó có sự kiện gì không (như Lễ Đoan ngọ, Trung thu, Lễ Vu lan . . .) để hiển thị. xem như có bột, mình chỉ cần vài bước để gột thành hồ.

    phần này thì dễ thở hơn nhiều:
    - cho người dùng nhập ngày (âm lịch, dương lich) và sự kiện tương ứng để nhắc. à há ! foobar2000 có nhược điểm là tương tác nhập liệu rất yếu và thiếu, theo cách cho người dùng nhập các kiểu dữ liệu rồi kiểm tra như điền form (vì lẽ đó mà WilB vẫn trung thàng với SMP thay vì chuyển qua JSP/JSP3, do SMP hỗ trợ hiển thị HTML, dù sơ sài - nhưng ít nhất cũng cho users tương tác được vài thông số): dùng notepad để edit thông tin này (file festival.txt, encode UTF8 BOM để hiển thị tiếng Việt có dấu). cấu trúc file và giải thích như sau.
    chúng ta cho người dùng nhập :
    + sự kiện: âm lịch, dương lịch
    + lặp lại : hàng năm, hàng tháng.
    + lặp lại vào ngày cuối tháng: các bạn edit thêm, chỉ cần lấy số ngày cuối mỗi tháng trước khi kiểm tra sự kiện.
    + thời điểm nhắc: nhắc trước (để chuẩn bị), đúng ngày và nhắc lại sau sự kiện (để lựa lời nói lại hay đi làm dù qua ngày - như lãnh lương hưu).

    Code:
    // dùng notepad để sửa thông tin             //
    // font : unicode. encoding: UTF-8 with BOM  //
    // -------- vnav.vn -------------------------//
    
    // không sửa thông tin này:
    function YearlyEvent(dd, mm, prv,nxt, info,rm) {
        this.day = dd; // number  only
        this.month = mm; // number only
        this.prvday = prv; // number only
        this.nxtday = nxt; // number only
        this.info = info; //text
        this.remind = rm; //algorithm.
    }
    
    
    // Sửa thông tin sự kiện từ đây:
    
    // Thêm dòng sự kiện theo format sau:
    // new YearlyEvent(ngày sự kiện, tháng sự kiện,số ngày nhắc trước sự kiện, số ngày nhắc lại sự kiện, 'nội dung sự kiện' )
    // ví dụ : new YearlyEvent(28,12,2,2'Sinh nhat Nguyen Van A') -> sự kiện 'sinh nhat . . .' vao ngay 28/12. nhắc trước từ 2 ngày, nhắc lại trong vòng 2 ngày.
    
    // Thêm dòng sự kiện:
    // sự kiện theo dương lịch thì nhập ngày tháng theo dương lịch và ngược lại.
    // Nếu dòng không ở cuối danh sách: thêm dấu phảy (,) vào cuối dòng.
    // Neu dòng ở cuối danh sách: bỏ dấu phảy (,) ở cuối dòng.
    // Save file này và Khởi động lại foobar2000.
    
    // Nếu muốn dùng Tiếng Việt có dấu thì phải chuyển qua mã unicode: save file as 'UTF-8 with BOM' (file này đã được chuyển).
    // các sự kiện lặp lại hàng năm.
    // nếu muốn sự kiện lặp lại hàng tháng thì để số 0 vào 'tháng sự kiện'.
    
    // nếu có sự kiện cần nhắc thỏa điều kiện, Foobar2000 sẽ hiện dấu * ở khung hiện giờ -ngày dương lịch - âm lịch.
    
    //1. sự kiện am lich,
    var lunarYEARLY_EVENTS = new Array(
      new YearlyEvent(1,    1,    15,    0,    'Tết nguyên đán'            ),
      new YearlyEvent(10,    3,    1,    0,    'Giỗ Tổ Hùng Vương 10/3 AL'        ),
      new YearlyEvent(5,    5,    1,    0,     'Lễ/Tết Đoan ngọ 5/5 AL'        ),
      new YearlyEvent(15,    7,    1,    0,     'Lễ Vu lan 15/7 AL'            ),
      new YearlyEvent(23,    12,    12,    0,     'Ông Táo về/chầu Trời 23/12 AL'        ),
      new YearlyEvent(15,    0,    6,    6,     'Rằm hàng tháng (15 AL)'        )
    );
    
    //2. sự kiện duong lich
    var solarYEARLY_EVENTS = new Array(
      new YearlyEvent(28,    1,    1,    2,    'Sự kiện 1 28/1'            ),
      new YearlyEvent(28,    1,    0,    0,    'Sự kiện A 28/1'            ),
      new YearlyEvent(24,    1,    4,    5,    'Sự kiện 2 24/1'            ),
      new YearlyEvent(29,    12,    4,    5,    'Sự kiện 3 29/12'            ),
      new YearlyEvent(29,    0,    4,    4,    'Ghé Phường lãnh lương hưu'        )
    );
    
    - code: mình chọn WSH vì plugin này tuân thủ thuần javascript, panel sử dụng plugin này là panel quản lý nhóm button phía trên bản dựng foobar2000 VNAV 2.0/2.1. plugin này sẽ load file Lunar_calendar_HoNgocDuc_WSH.js là đoạn logic script lo bếp núc (mình để riêng thành file để các bạn dễ sửa), sau khi có thành phẩm thì hiển thị ra màn hình foobar2000 (khi có sự kiện cần nhắc thì hiển thị dấu * sau thông tin giờ).
    + nhóm code tìm sự kiện dương lịch: tìm trong array sự kiện dương lịch solarYEARLY_EVENTS và đặc biệt lưu ý 1 số điểm chuyển giao giữa các tháng và năm:
    ví dụ: có 1 sự kiện vào ngày 28 hàng tháng, nhắc lại cho đến 5 ngày sau.
    do các sự kiện lặp lại hàng năm hay hàng tháng nên ta sẽ ngầm hiểu lấy năm và tháng hiện tại để so sánh. với sự kiện ở điểm giao như cuối tháng, ví như sự kiện trên. nếu thời điểm so sánh là ngày 1/3/2024, ta sẽ so sánh 1/3/2024 với 28/3/2024 và thấy chưa đủ 5 ngày nên không nhắc do chưa tới, nhưng khi giả định sự kiện xảy ra tháng trước đó , tức là 28/2 và so ngày này với ngày hôm nay thì rõ ràng là thỏa điều kiện nhắc lại. vấn đề tương tự khi xảy ra vào thời điểm giao năm (cuối tháng 12, đầu tháng 1).

    Code:
    // 2. solar festival
    function findEventsS(dd, mm) {
        var ret = new Array();
        var ret1="";
     
        for (var i = 0; i < solarYEARLY_EVENTS.length; i++) {
            evt = solarYEARLY_EVENTS[i];
            ret1="";
            ret1=checkSDate(evt.day,evt.month,evt.prvday,evt.nxtday,dd,mm);
            if (ret1 !="") {
                evt.remind= ret1;
                ret.push(evt);
            }
            //if (evt.day == dd && evt.month == mm) {ret.push(evt);}
        }
     
        return ret;
    }
    
    function checkSDate(df,mf,pd,nd,dd,mm){
        var ret="";
        var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
        var today= new Date();
        var tmonth=today.getMonth()+1, tyear = today.getFullYear();
    
        var df1=parseInt(df,10), mf1=parseInt(mf,10), dd1 =parseInt(dd,10), mm1 =parseInt(mm,10),pd1 =parseInt(pd,10),nd1 =parseInt(nd,10);
        var diffday1=diffday2=diffday3=diffDays=0;
        var theDate,festDate1,festDate2,festDate3;
        var thisYear=tyear,mf2,mf3,ty2,ty3;
     
    
        if (( !isNaN(df1) && !isNaN(mf1) && !isNaN(dd1) && !isNaN(mm1) && !isNaN(pd1) && !isNaN(nd1)) && !( df1>31 || df1<1 || mf1>12 || mf1<0 || dd1>31 || dd1<1 || mm1>12 || mm1<1 || pd1<0 || nd1<0)) {
            theDate = (new Date(thisYear,mm1-1,dd1));
            if (mf1==0) { // monthly event -> check event prv, this and next month to get the 'real' gap
                tmonth=mm1;
                mf1 = tmonth;
        
                if (tmonth==1) { // prev 1 month
                    mf2 =12;
                    ty2= tyear-1;
                } else {
                    mf2 = tmonth-1;
                    ty2=tyear;
                }
    
                if (tmonth==12) { // next 1 month
                    mf3 =1;
                    ty3= tyear+1;
                } else {
                    mf3 = tmonth+1;
                    ty3=tyear;
                }
        
                festDate1 = new Date(thisYear,mf1-1,df1);// same month
                festDate2 = new Date(ty2,mf2-1,df1);  // prv 1 month
                festDate3 = new Date(ty3,mf3-1,df1); // next 1 month
        
            } else { // edge year
                 festDate1 = new Date(thisYear,mf1-1,df1) ; // same year
                 festDate2 = new Date(thisYear-1,mf1-1,df1) ; // prv 1 year
                 festDate3 = new Date(thisYear+1,mf1-1,df1) ; // next 1 year
            }
     
            diffday1 =  Math.round((festDate1 - theDate) / oneDay);
            diffday2 =  Math.round((festDate2 - theDate) / oneDay);
            diffday3 =  Math.round((festDate3 - theDate) / oneDay);
    
            if (diffday1 ==0 ) diffDays=diffday1;
            if (diffday1 < 0 ) { diffDays = (diffday3 > Math.abs(diffday1)) ? diffday1 : diffday3; }
            if (diffday1 > 0 ) { diffDays= (diffday1 > Math.abs(diffday2)) ? diffday2 : diffday1; }
    
            ret="";
    
            if (diffDays < 0 && nd1>= Math.abs(diffDays)) {ret=" (đã qua "+  Math.abs(diffDays) +" ngày)";}
            if (diffDays == 0) {ret= " (hôm nay)";}
            if (diffDays > 0 && pd1 >= diffDays) {ret= " (còn "+  diffDays +" ngày)";}
            
        } else {
            ret = "\nFestival day/month :" + df +"/"+ mf ;
            ret += "\nDays reminded before :" + pd;
            ret += "\nDays reminded after :" + nd;
            fb.ShowPopupMessage(ret,"Festival date is not valid, please check festival.txt file");
            ret="";
        }
    
        return ret;
    }
    + code tìm sự kiện âm lịch: tương tự dương lịch, chỉ khác 2 điểm: tìm trong array âm lịch lunarYEARLY_EVENTS và đổi ngày âm lịch ra dương lịch để thực hiện phép toán tính ngày (dùng hàm Date() của plugin).

    Code:
    function findEventsL(dd, mm) {
    
        var ret = new Array();
        var ret1="";
     
        for (var i = 0; i < lunarYEARLY_EVENTS.length; i++) {
            evt = lunarYEARLY_EVENTS[i];
            ret1="";
            ret1=checkLDate(evt.day,evt.month,evt.prvday,evt.nxtday,dd,mm);
            if (ret1 !="") {
                evt.remind= ret1;
                ret.push(evt);
            }
            //if (evt.day == dd && evt.month == mm) {ret.push(evt);}
        }
    
        return ret;
    }
    
    function checkLDate(df,mf,pd,nd,dd,mm){
        var ret="";
        var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
        var today= new Date();
        var tmonth=today.getMonth()+1, tyear = today.getFullYear();
    
        var df1=parseInt(df,10), mf1=parseInt(mf,10), dd1 =parseInt(dd,10), mm1 =parseInt(mm,10),pd1 =parseInt(pd,10),nd1 =parseInt(nd,10);
        var diffday1=diffday2=diffday3=diffDays=0;
        var sd_theDate,sd_festDate1, sd_festDate2, sd_festDate3;
        var theDate,festDate1,festDate2,festDate3;
        var thisYear=tyear,mf2,mf3,ty2,ty3;
     
        if (( !isNaN(df1) && !isNaN(mf1) && !isNaN(dd1) && !isNaN(mm1) && !isNaN(pd1) && !isNaN(nd1)) && !( df1>31 || df1<1 || mf1>12 || mf1<0 || dd1>30 || dd1<1 || mm1>12 || mm1<1 || pd1<0 || nd1<0)) {
            sd_theDate = getSolarDate(dd1, mm1, thisYear);  //convert the lunar date to solar date
            theDate = (new Date(sd_theDate[2],sd_theDate[1]-1,sd_theDate[0]));
    
            if (mf1==0) { // monthly event -> check event prv, this and next month to get the 'real' gap
                tmonth=mm1;
                mf1 = tmonth;
        
                if (tmonth==1) { // prev 1 month
                    mf2 =12;
                    ty2= tyear-1;
                } else {
                    mf2 = tmonth-1;
                    ty2=tyear;
                }
    
                if (tmonth==12) { // next 1 month
                    mf3 =1;
                    ty3= tyear+1;
                } else {
                    mf3 = tmonth+1;
                    ty3=tyear;
                }
    
                 sd_festDate1 = getSolarDate(df1, mf1, thisYear); //convert event-festival lunar date to solar date - same month
                 sd_festDate2 = getSolarDate(df1, mf2, ty2); //convert event-festival lunar date to solar date - prv 1 month
                 sd_festDate3 = getSolarDate(df1, mf3, ty3); //convert event-festival lunar date to solar date - next 1 month
            } else {
                 sd_festDate1 = getSolarDate(df1, mf1, thisYear); //convert event-festival lunar date to solar date - same year
                 sd_festDate2 = getSolarDate(df1, mf1, thisYear-1); //convert event-festival lunar date to solar date - prv year
                 sd_festDate3 = getSolarDate(df1, mf1, thisYear+1); //convert event-festival lunar date to solar date - next year
            }
    
            festDate1 = (new Date(sd_festDate1[2],sd_festDate1[1]-1,sd_festDate1[0]));
            festDate2 = (new Date(sd_festDate2[2],sd_festDate2[1]-1,sd_festDate2[0]));
            festDate3 = (new Date(sd_festDate3[2],sd_festDate3[1]-1,sd_festDate3[0]));
        
            diffday1 = Math.round((festDate1 - theDate) / oneDay);
            diffday2 = Math.round((festDate2 - theDate) / oneDay);
            diffday3 = Math.round((festDate3 - theDate) / oneDay);
    
            if (diffday1 ==0 ) diffDays=diffday1;
            if (diffday1 < 0 ) { diffDays = (diffday3 > Math.abs(diffday1)) ? diffday1 : diffday3; }
            if (diffday1 > 0 ) { diffDays= (diffday1 > Math.abs(diffday2)) ? diffday2 : diffday1; }
    
            ret="";
    
            if (diffDays < 0 && nd1>= Math.abs(diffDays)) {ret=" (đã qua "+  Math.abs(diffDays) +" ngày)";}
            if (diffDays == 0) {ret= " (hôm nay)";}
            if (diffDays > 0 && pd1 >= diffDays) {ret= " (còn "+  diffDays +" ngày)";}
     
     
        } else {
            ret = "\nFestival day/month :" + df +"/"+ mf ;
            ret += "\nDays reminded before :" + pd;
            ret += "\nDays reminded after :" + nd;
            fb.ShowPopupMessage(ret,"Festival date is not valid, please check festival.txt file");
            ret="";
        }
    
        return ret;
    }
    phần trên có nói tới data âm lịch Việt nam được tính sẵn, vậy cụ thể là đâu, dùng ra sao:
    ví dụ đoạn thông tin cho thế kỷ 20 như sau.
    tác giả tính 100 thông số (cho 100 năm) và để dưới dạng hexadecimal, phân cách bởi dấu phẩy (,) muốn biết nó có nghĩa gì thì phải chuyển sang dạng bit.
    lấy năm đầu của thế kỷ 20, hexadecimal là 0x3c4bd8, chuyển sang bit (1 số trang web cho chuyển online cho nhanh, khỏi mất công tính) 00111100010010111 1011000 , được 24 bit là dạng biểu diễn nhị phân (0 1) cơ bản nhất của thông tin máy tính mà con người hiểu được. tác giả đã mã hóa 1 số thông tin theo các nhóm bit trên, được cho là như sau, theo thứ tự từ trái (high bit) qua phải:
    * - 7 bit đầu: số ngày giữa Western New Year và Lunar New Year days (1/1).
    * -1 bit tiếp theo: 0 nếu tháng nhuận 29 ngày, 1 là 30 ngày.
    * -12 bit kế: số ngày của mỗi 12 tháng (như bit số 8). (?)
    * - 4 bit cuối: 0 nếu không phải năm nhuận, ngược lại là Tháng nhuận.


    Code:
    var TK20 = new Array(
        0x3c4bd8, 0x624ae0, 0x4ca570, 0x3854d5, 0x5cd260, 0x44d950, 0x315554, 0x5656a0, 0x409ad0, 0x2a55d2,
        0x504ae0, 0x3aa5b6, 0x60a4d0, 0x48d250, 0x33d255, 0x58b540, 0x42d6a0, 0x2cada2, 0x5295b0, 0x3f4977,
        0x644970, 0x4ca4b0, 0x36b4b5, 0x5c6a50, 0x466d50, 0x312b54, 0x562b60, 0x409570, 0x2c52f2, 0x504970,
        0x3a6566, 0x5ed4a0, 0x48ea50, 0x336a95, 0x585ad0, 0x442b60, 0x2f86e3, 0x5292e0, 0x3dc8d7, 0x62c950,
        0x4cd4a0, 0x35d8a6, 0x5ab550, 0x4656a0, 0x31a5b4, 0x5625d0, 0x4092d0, 0x2ad2b2, 0x50a950, 0x38b557,
        0x5e6ca0, 0x48b550, 0x355355, 0x584da0, 0x42a5b0, 0x2f4573, 0x5452b0, 0x3ca9a8, 0x60e950, 0x4c6aa0,
        0x36aea6, 0x5aab50, 0x464b60, 0x30aae4, 0x56a570, 0x405260, 0x28f263, 0x4ed940, 0x38db47, 0x5cd6a0,
        0x4896d0, 0x344dd5, 0x5a4ad0, 0x42a4d0, 0x2cd4b4, 0x52b250, 0x3cd558, 0x60b540, 0x4ab5a0, 0x3755a6,
        0x5c95b0, 0x4649b0, 0x30a974, 0x56a4b0, 0x40aa50, 0x29aa52, 0x4e6d20, 0x39ad47, 0x5eab60, 0x489370,
        0x344af5, 0x5a4970, 0x4464b0, 0x2c74a3, 0x50ea50, 0x3d6a58, 0x6256a0, 0x4aaad0, 0x3696d5, 0x5c92e0
    );
    mình không sử dụng các thông tin bit giải mã này vì không được xác tín và cũng tôn trọng source javascript của tác giả, tạo thêm 1 hàm để tính số ngày của tháng âm lịch:
    Code:
    function getLunarMonthLength(sdd, smm, syyyy) {
        var ld1,ld2,id,monthLength;
     
        ld1=ld2=getLunarDate(sdd,smm,syyyy).day;
        monthLength=getMonth(smm,syyyy).length;
    
        if (sdd<monthLength){
            for (var id=sdd+1; id<=monthLength; id++) {
                 ld2=getLunarDate(id,smm,syyyy).day;
                 if (ld2 < ld1) {
                    break;
                } else {ld1=ld2;}
            }
        }
        //if (typeof ld2 === 'undefined') ld2="dsds";
        if (ld1 == ld2) { // check next month
            var im=smm;
            var iy=syyyy;
            if (im <12) {
                im=im+1;
            } else {
                im=1;
                iy=iy+1;
            }
            monthLength=getMonth(im,iy).length;
     
            for (var id=1; id<=monthLength; id++) {
                 ld2=getLunarDate(id,im,iy).day;
                 if (ld2 < ld1) {
                    break;
                } else {ld1=ld2;}
            }
        }
        //return  ("lMonth,lMonthLength : " + getLunarDate(sdd,smm,syyyy).month +" = "+ ld1);
        return  ld1;
    }
    xong.
    nhanh, gọn, lẹ.
    mỗi khi mở hay mỗi khi ngày thay đổi, foobar2000 sẽ cần mẫn kiểm tra có sự kiện âm, dương lịch gì thỏa điều kiện nhắc không và lưu ý người dùng - nếu có sự kiện (qua dấu * ở kế đồng hồ phía trên). right click sẽ hiển thị thông tin chi tiết ngày âm lịch, các sự kiện liên quan, thêm bớt thông tin sự kiện (dùng notepad mở file sự kiện).

    rồi so sánh với âm lịch Trung quốc ở đâu ?
    muốn in sẵn 1 năm âm lịch dán lên tường rối lấy bút viết sự kiện vô cho lẹ ?
    xem tuần trăng được không ?
    xem lịch cả tháng được không ?

    foobar2000 không làm tốt được việc này nên chúng ta sẽ nhờ đến 1 chương trình/plugin bên ngoài.

    phần tiếp sẽ đề cập.

    phần 3 (phần cuối): âm-dương lịch Việt-Trung giao duyên.

    lưu ý:
    1.như tiêu chí DIY đề ra, mình sẽ chi tiết hết mức có thể để các bác tự làm được và quan trọng - xem có chỗ nào sai sót.
    2. code javascript, giải thuật thuộc bản quyền của các nhà phát triển tương ứng. người dùng vui lòng kiểm tra lại tính chính xác của thông tin (ngày âm lịch) do chương trình cung cấp trước khi sử dụng.
    3. code javascript tích hợp nhắc việc theo âm dương lịch vào foobar2000 theo nội dung bài viết này chỉ để tham khảo, tác giả và mạng nghe nhìn Việt nam vnav.vn không chịu bất kỳ trách nhiệm với bất kỳ rủi ro liên quan nào (nếu có).
     

    Attached Files:

    Last edited: 31/1/24
    tml3nr likes this.
  22. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    phần này có vẻ được quan tâm và do bữa qua đi hơi lẹ nên bỏ qua vài điểm, các bạn phát hiện ra là không có ăn nhập gì, ý là đạo code đạo nhạc chi đó.
    thiệt tình, phần nào mình biết tác giả hay nguồn thông tin thì đều ghi chú, phần nào sửa thêm thì cũng có ghi rõ, hay đơn giản là tag //vnav, để anh em rộng đường tham khảo thêm và xác tín với nguyên bản.

    mục đích của loạt bài này là chúng ta cùng giúp nhau DIY - có thể xem như 1 phần động lực và cảm hứng cho hành trình dạo chơi với âm nhạc, tung tẩy với cảm xúc. với 1 số bác- như mình - và trong diễn đàn này, có lẽ đều chung đam mê âm nhạc. vì vậy mà đâu đó, ta dễ dàng bắt gặp những thông tin chia sẻ ngon bổ rẻ, giúp nhau khai phóng, mở ra biển kiến thức mênh mông.

    với lại, hồi xưa cơm áo gạo tiền tối mũi, giờ rảnh rỗi có thời gian cho 1 niềm vui lành mạnh cũng đáng.
    //---------------//
    quay lại phần source code (được đính kèm bài trước), phần tưởng không ăn nhập là do mình đã code sẵn cho phần cuối (phần 3).

    vậy mình cùng chuyển qua phần cuối nhé.
     
    Last edited: 31/1/24
    tml3nr likes this.
  23. hungsaker007

    hungsaker007 Approved Member

    Joined:
    28/11/12
    Messages:
    41
    Likes Received:
    17
    bản của bác có cài đc cho Macbook ko ạ
     
  24. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    mình không có Mac nên không rõ.
    mấy bác test lúc trước cũng thế hệ xưa nên quanh quanh với Windows.

    nếu ý Bạn là phần âm lịch thì trong phần 3 chủ đề này, mình sẽ đề cập về gói javascript độc lập và có thể dùng Web Browser (không cần kết nối Internet) tương tác. đây cũng là ý tưởng của các bác.

    P/S. đang định viết phần cuối thì có việc ra ngoài, mình sẽ lên bài nhanh vì phần đó xem như có sẵn trong đầu.
     
    tml3nr likes this.
  25. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    chủ đề: dùng foobar2000 quản lý sự kiện âm - dương lịch.
    phần 3/3
    : 1 cách tiếp cận toàn diện hơn với âm-dương lịch

    dùng foobar2000 nhắc việc thì được rồi, nhưng nếu chỉ để hiển thị thông tin âm lịch thì hơi phí, liệu chúng ta có thể biết được chính xác ngày giờ các tiết khí (solar term) hay tuần trăng (moon phase), xem âm lịch cả tháng và năm hay đơn giản là âm lịch của 1 ngày dương lịch bất kỳ.

    nếu dùng foobar2000, chúng ta có thể kết hợp album art (làm nền) rồi hiển thị thông tin ngày âm-dương lịch như tờ lịch treo tường.
    có thể vẽ ngày tháng như dạng table cho tháng/năm.

    ở đây, chúng ta sẽ dùng web browser mà hầu như máy tính nào cũng có sẵn, hệ điều hành nào cũng có- để làm các việc trên. với cách tiếp cận này, xem web browser như 1 plugin của foobar2000 và hơn nữa, có thể dùng chương trình âm lịch hầu như trên bất cứ hệ điều hành nào (có web browser hỗ trợ javascript).
    ta sẽ tạm chia phần 3 này thành 2 mục nhỏ.

    phần 3.1: truyền tham số cùng file html cho web-browser từ foobar2000:

    từ trong foobar2000 ta sẽ 'gọi' web-browser và truyền cho hắn 1 file html (cụ thể là index.html) cùng parameter là ngày-tháng dương lịch muốn chuyển sang âm lịch.
    do ta vẫn dùng WSH, nên câu lệnh (vẫn trong script của panel button như nêu trên) như sau:

    Code:
    var WshShell = new ActiveXObject("WScript.Shell");
    var lunar_localhref = fb.ProfilePath + "skins\\tech\\scripts\\VN_calendar_HoNgocDuc\\index.html";
    
    var ddSolar=dcDate.getDate(),mmSolar= dcDate.getMonth()+1, yyyySolar=dcDate.getFullYear();
    var href="?dd="+ddSolar+"&mm="+mmSolar+"&yy="+yyyySolar, hrefYYYY=yyyySolar;
    
    function setting_url(hre) {
        if (utils.ReadTextFile(lunar_localhref)) {
            //fb.ShowPopupMessage(utils.ReadTextFile(lunar_localhref));
            utils.WriteINI((lunar_localhref),"session","key","--> <meta name=fb2kdate CONTENT="+hre+">");
        } else return false;// fb.ShowPopupMessage("no file exist");
    }
    
    try {
          setting_url(href);
          WshShell.run("file:///"+lunar_localhref);
    } catch (e) {
          fb.ShowPopupMessage(lunar_localhref,"File Lunar calendar HTML may not exist");
          }
     
    ta để ý 2 điểm:
    a. tham số 'ngày tháng năm' truyền cho web browser là 'ngày tháng năm' nào ?

    theo script trên, rõ ràng ngày-tháng-năm truyền đi là ngày hiện tại, vậy chỉ cần bảo web browser lấy ngày hiện tại để xử lý là xong. đúng nhưng chưa đủ.
    cách tiếp cận trên cho phép foobar2000 truyền bất cứ ngày-tháng-năm nào, nói cách khác, yêu cầu web browser chuyển đổi bất kỳ ngày tháng năm nào sang âm lịch.

    b. cách truyền tham số cho web-browser

    - foobar2000 sẽ nhờ hệ điều hành open 1 file html kèm thông tin ngày-tháng-năm cho file này xử lý, hệ điều hành răm rắp gọi với vô trong nhà xem app nào xử file html và truyền file này cho hắn.
    - khi web-brower open file html này lần đầu - vì lý do an toàn - sẽ bỏ các tham số (parameter) kèm theo URL (dạng . . html?para1=value1&para2=value2&. . . ) do đây là file local, không phải từ 1 web server chuyển sang hay local file được web-browser 'tin'. toang rồi ông giáo ơi.

    giải pháp : 2 bước.
    + bước 1: foobar2000 sẽ 'cấy' 1 meta tag chứa ngày-tháng-năm vào chính file html này trước khi đẩy đi cho web browser. kheo khéo che đi bằng cú pháp <!-- hidden text --> để web browser không tênh hênh cho thiên hạ. do đó ta có câu lệnh:
    Code:
     utils.WriteINI((lunar_localhref),"session","key","--> <meta name=fb2kdate CONTENT="+hre+">");
    + bước 2: web browser sẽ ung dung mở file này sau khi bỏ qua đoạn parameters, nhưng không ngờ đây là con ngựa thàng troy - bản thân file đã chứa parameter, nhìn như sau: ví dụ này truyền ngày 2/1/2024.
    Code:
    <!--
    [session]
    key=--> <meta name=fb2kdate CONTENT=?dd=1&mm=2&yy=2024>
    và đoạn script trong file html file này tự tìm các thông số chỉ trong session này, sau khi đánh dấu để lần sau đừng thi hành sctipt nữa, ép web browser re-load lại chính file này kèm parameter trong URL (lúc này web browser chấp nhận parameter này do hắn đã tin file html).
    Code:
    <script language="JavaScript" type="text/javascript">
    <!--
        var firstRun= sessionStorage.getItem("Is_first_run");
        //sessionStorage vs localStorage vnav
        if (firstRun == null) {
            sessionStorage.setItem("Is_first_run", "Yes");
            window.location = window.location.pathname +document.querySelector('meta[name="fb2kdate"]').content;
        }
    -->
    </script>
    vì đoạn script trong foobar2000 phải đi kèm với đồng nghiệp trong file html mới song kiếm hợp bích được, do đó 1 số cho rằng đoạn script trong foobar2000 là dư thừa.

    phần 3.2. làm gì với file html (foobar2000 ngoại truyện) (phần này không thuộc foobar2000 nên mình đi rất nhanh).

    thực tế, chương trình âm lịch Việt nam và Trung quốc của 2 tác giả nêu trên đều đã hoàn chỉnh (như 1 app) trên web -browser với javascript+html+css. bản thân âm lịch Việt nam còn có cả hàm (giải thuật) tìm sự kiện âm lịch mà ta đã mod cho foobar2000.

    hổng lẽ vẽ chân cho rắn. tùy theo cách nhìn.
    • tích hợp âm lịch Việt nam và Trung quốc trong 1 file html.
    • tổng hợp các tính năng, tùy biến thêm 1 số cho âm lịch Việt nam : highlight giữ nguyên ngày khi navigate qua các tháng/năm. filter âm lịch Trung quốc theo tháng để dễ so sánh với âm lịch Việt nam.
    • layout lại các thành phần cho dễ nhìn.
    • nhắc việc âm - dương lịch : dành cho các bạn DIY. vì phần này cơ bản đã có như nguyên bản của tác giả Hồ Ngọc Đức, script code hầu như tương tự phần tích hợp theo foobar2000. các bạn chỉ cần lưu ý dùng kỹ thuật Ajax lập trình app phía client (không phải load lại trang web) là xử lý được. chương trình đã đễ sẵn 1 khu để hiển thị các thông tin dạng này (id=conversion_result).
    • đoạn script mod theo file html như sau, phần script liên quan âm lịch Trung quốc rối như chiều 30 là do mình giữ nguyên bản, vì nhiều chỗ có chữ Trung nên không hiểu rõ ý tác giả. chỉ lưu ý chương trình có nhiều ngôn ngữ nên sẽ dùng tham số ( ví dụ {e} trong hàm dựng lịch submitYear(e) ). âm lịch Trung quốc dùng data cũng được tác giả tính trước, được cho là dùng được cho giai đoạn từ năm 720 (trước công nguyên) đến tận 2200. theo mình - đây là công trình học thuật rất dày công. các bạn nên tham khảo bản đầy đủ tiếng Anh (đính kèm) hay trên web-site của tác giả.
    • thư viện data và giải thuật tính âm lịch Việt nam, Trung quốc (file javasctipt đã mod: CN_calendar_ytl.js, VN_calendar_hnd_1.0.js) được đính kèm/nhúng theo link trong file html khi web browser load trang web.
    phần âm lịch Trung quốc sẽ hiển thị thời gian cụ thể về tuần trăng, tiết khí (theo giờ Trung quốc, vốn đi sau giờ Việt nam 1 giờ như đã đề cập ở phần 1) nên có sai lệch. nếu tham khảo thì cần điều chỉnh sai số.

    cách dùng:
    • trực tiếp: download file VN_calendar_HoNgocDuc.zip đính kèm, mở file index.html trong thư mục âm lịch Việt nam (VN_calendar_HoNgocDuc).
    • gián tiếp: chép thư mục VN_calendar_HoNgocDuc vào cùng thư mục với file Lunar_calendar_HoNgocDuc_WSH.js, sau đó truy cập qua context menu khi right click vào khu vực giờ-ngày dương-âm lịch của bản dựng foobar2000 VNAV 2.1 (theo đoạn WSH script buton trong phần 2).

    nếu các bạn muốn tham khảo bản đầy đủ (tiếng Anh) âm lịch Trung quốc với rất nhiều thông tin hữu ích thì có thể tải file sau. phiên bản trên web-site của tác giả YukTungLiu có thêm nhiều ngôn ngữ khác.
    https://www.mediafire.com/file/sjnd5s0rrj2prnj/calendar.zip/file
    https://www.fshare.vn/file/KSY94K6UJS7A


    Code:
    <script language="JavaScript" type="text/javascript">
    <!--
    
           //showVietCal();
        //document.getElementById("showVNCalendar").innerHTML =printSelectedMonth();
    
        getSelectedMonth();
        document.SelectMonth.day.value = currentDay;
        document.SelectMonth.month.value = currentMonth;
        document.SelectMonth.year.value = currentYear;
    
        var today = new Date();
        var min = today.getMinutes();
        var hh = today.getHours();
        var dd = today.getDate();
        var mm = today.getMonth()+1;
        var yy = today.getFullYear();
        var weekday = ["Chủ nhật","Thứ hai","Thứ ba","Thứ  tư","Thứ năm","Thứ sáu","Thứ bảy"];
        var day = weekday[today.getDay()];
    
    
        window.document.DateForm.hour.value = hh;
        window.document.DateForm.minute.value = min;
        window.document.DateForm.day.value = dd;
        window.document.DateForm.month.value = mm;
        window.document.DateForm.year.value = yy;
    
        showData(dd, mm, yy, hh, min);
        showTodayInfo();
        updateValues(new Array(dd, mm, yy), getLunarDate(dd, mm, yy));
    
        document.getElementById("c_jdate").style.display="none";
        //document.getElementById("conversion_result").innerHTML = "";
        document.getElementById("td_today").innerHTML = "Hôm nay : "+ day+", "+dd +"/" + mm +"/"+ yy  +"<br>URL:"+document.URL+ "<br>URL Parameter :"+ (new URLSearchParams(window.location.search))+"<br>File last modified: "+document.lastModified +"<br>"+document.title;
        //+ "<br>domain :"+document.domain;
    
     
     
           function convertSolarToLunar() {
            var dd, mm, yy, sd, ld;
            dd = document.getElementById("S_DD").value-0;
            mm = document.getElementById("S_MM").value-0;
            yy = document.getElementById("S_YY").value-0;
    
            ld = getLunarDate(dd, mm, yy);
            sd = jdn2date(ld.jd);
    
            updateValues(sd, ld);
            //document.getElementById("conversion_result").innerHTML = "";
           }
    
           function computeYearCanChi() {
            var yy = document.getElementById("L_YY").value-0;
            document.getElementById("L_CC").value = getYearCanChi(yy);
           }
    
           function convertLunarToSolar() {
            var dd, mm, yy, sd, ld;
            dd = document.getElementById("L_DD").value-0;
            mm = document.getElementById("L_MM").value-0;
            yy = document.getElementById("L_YY").value-0;
    
            sd = getSolarDate(dd, mm, yy);
            ld = getLunarDate(sd[0], sd[1], sd[2]);
            updateValues(sd, ld);
            //document.getElementById("conversion_result").innerHTML = "";
           }
    
     
    
           function updateValues(sd, ld) {
            document.getElementById("S_DD").selectedIndex = sd[0]-1;
            document.getElementById("S_MM").selectedIndex = sd[1]-1;
            document.getElementById("S_YY").value = sd[2];
    
            document.getElementById("L_DD").selectedIndex = ld.day-1;
            document.getElementById("L_MM").selectedIndex = ld.month-1;
            document.getElementById("L_YY").value = ld.year;
            document.getElementById("L_CC").innerHTML = getYearCanChi(ld.year);
            if (ld.leap == 1) {
                document.getElementById("L_LL").innerHTML = "nhuận";
            } else {
                document.getElementById("L_LL").innerHTML = "";
            }
            document.getElementById("L2SButton").value = 'Đổi ngày âm lịch Việt nam '+ld.day+'/'+ld.month+' trên sang dương lịch';
            document.getElementById("L2SButtonHTML").innerHTML = '>> Đổi âm lịch Việt nam sang dương lịch cho 20 năm '+ld.year+'-'+(ld.year+19) +':';
    
            document.getElementById("conversion_result").innerHTML = "";
           }
    
    
           function computeSolarBigDates(lday,lmonth,lyear,msg){
     
            var res = "";
            var dd, mm, yy, sd, ld, i, cls, thu, colHead;
            dd = (lday == 0 || lday == "" ? document.getElementById("L_DD").value-0 : lday);
            mm = (lmonth == 0 || lmonth == "" ? document.getElementById("L_MM").value-0 : lmonth);
            yy = (lyear == 0 || lyear == "" ? document.getElementById("L_YY").value-0 : lyear);
            colHead = (msg == "" ? "Ngày âm lịch" : msg);
            res += '<table  class="table1">';
            res += '<tr style="background-color:lightgray;"><th>TT</th><th>'+ colHead +'</th><th>Ngày dương lịch</th></tr>';
            for (i=0; i<20; i++) {
                sd = getSolarDate(dd, mm, yy+i);
                ld = getLunarDate(sd[0], sd[1], sd[2]);
                if (ld.day != dd && dd == 30) {
                    sd = jdn2date(ld.jd-1);
                    ld = getLunarDate(sd[0], sd[1], sd[2]);
                }
                cls = (i % 2 == 0) ? "even" : "odd";
                thu = (ld.jd+1) % 7;
                res += '<tr class="'+cls+'"><td>'+ (i+1)+'</td><td>'+ ld.day+'/'+ld.month+'/'+(yy+i)+' '+getYearCanChi(ld.year)+'</td>\n';
                res += '<td>'+TUAN[thu] + ' ' + sd[0]+'/'+sd[1]+'/'+sd[2]+'</td></tr>\n';
            }
            res += '</table>';
            document.getElementById("conversion_result").innerHTML = res;
           }
    
           function showTodayInfo() {
     
            var sday, smonth, syear, sd, ld;
     
            getSelectedMonth();
            sday = currentDay;
            smonth = currentMonth;
            syear = currentYear;
    
     
            //sd = jdn2date(ld.jd); convert JDN to Geogria calendar
            var sd = new Array(sday, smonth, syear);
            var ld = getLunarDate(sday, smonth, syear);
    
            //alert("sday, smonth, syear : " +sday+"/"+smonth+"/"+syear);
            //alert("lday, lmonth, lyear : " +ld.day+"/"+ld.month+"/"+ld.year);
     
            var dayOfWeek = TUAN[(ld.jd + 1) % 7];
            var nhuan = (ld.leap == 1) ? ' nhuận' : '';
            var tenthang = 'Th\u00E1ng '+THANG[ld.month-1]+nhuan + " ("+ getLunarMonthLength(sday,smonth,syear) +"n)" ; //+(length == 30 ? ' (Đ)' : ' (T)');
            var thang = CAN[(ld.year*12+ld.month+3) % 10] + " " + CHI[(ld.month+1)%12];
            var ngay = CAN[(ld.jd + 9) % 10] + " " + CHI[(ld.jd+1)%12];
            var Ycanchi = getYearCanChi(ld.year);
            var Hcanchi = getCanHour0(ld.jd)+' '+CHI[0];
            var tietKhi = TIETKHI[getSunLongitude(ld.jd+1, 7.0)];
            var festival = getDayInfo(ld.day, ld.month);
            var hoangDao = getGioHoangDao(ld.jd);
    
            document.SelectMonth.day.value = sday;
            document.SelectMonth.year.value = syear;
            document.getElementById("thangduong").innerHTML = 'Tháng '+smonth+' năm '+syear;
            document.getElementById("ngayduong").innerHTML = sday;
            document.getElementById("thuduong").innerHTML = dayOfWeek;
            document.getElementById("ngayam").innerHTML = ld.day;
            document.getElementById("thangam").innerHTML = tenthang;
            document.getElementById("namam").innerHTML = 'Năm '+ Ycanchi;
            document.getElementById("canchithang").innerHTML = 'Tháng '+thang;
            document.getElementById("canchingay").innerHTML = 'Ngày '+ngay;
            document.getElementById("canchigio").innerHTML = 'Giờ '+ Hcanchi;
            document.getElementById("tietkhi").innerHTML = 'Tiết '+ tietKhi;
            document.getElementById("dayinfo").innerHTML = festival;
            document.getElementById("hoangdao").innerHTML = 'Giờ hoàng đạo: '+ hoangDao;
             }
    
     
        function showData(dd, mm, yy, hour, minute) {
             var jd = jdAtVST(dd, mm, yy, hour, minute);
             var sl = solarLongitude(jd);
             var res = ''+sl;
             document.getElementById("result").innerHTML = "Kinh độ mặt trời: "+res;
        }
     
    
    
        var geolocation="";
        //if (navigator.geolocation)
        function getLocation() {
              //try { navigator.geolocation.getCurrentPosition(showPosition);
              //} catch { geolocation = "Có lỗi khi Web Browser lấy kinh vĩ độ : "+err; }
        }
    
        function showPosition(position) {
              geolocation = "Vĩ độ (latitude): " + position.coords.latitude + "<br>Kinh độ (longitude): " + position.coords.longitude;
            document.getElementById("result1").innerHTML = geolocation;
        }
    
    
        function navigateMonth(dd,mm, yy,mth) {
            var mm1=mm,yy1=yy;
    
            if (mth >0) {
                for (var i=0; i< mth ; i++) {
                    if (mm1 < 12) {
                        mm1 = mm1 + 1;
                    } else {
                        mm1 = 1;
                        yy1 = yy1 + 1;
                    }
                }
            }
    
    
            if (mth < 0) {
                for (var i=0; i< -mth ; i++) {
                    if (mm1 > 1) {
                        mm1 = mm1 - 1;
                    } else {
                        mm1 = 12;
                        yy1 = yy1 - 1;
                    }
                }
            }
    
    
            var yy2=parseInt(yy1,10);
            var y1=1800, y2=2199;
            var msg="Âm lịch Việt nam: Năm nằm ngoài giới hạn tính!<br>Lưu ý: Được tính và khuyến cáo có giá trị tham khảo từ năm "+y1+" đến năm "+y2+".";
    
            if (checkSyear(yy2,y1,y2, msg)) {
                window.location = window.location.pathname + '?yy='+yy1+'&mm='+mm1+'&dd='+dd;
            }
        }
    
        function checkSyear(yrnToChk,frYrn,toYrn,msg){
            //check year input
             //const inpObj = document.getElementById("year");
              //if (!inpObj.checkValidity()) {alert(" Error : "+inpObj.validationMessage);
              //} else // . . .
    
            var chkYrn=parseInt(yrnToChk,10);
            if ( isNaN(chkYrn) || chkYrn < frYrn || chkYrn > toYrn){
                document.getElementById("err").innerHTML=msg;
                return false;
            } else { return true; }
     
        }
    
        function viewMonth(dd,mm, yy) {
            var yy2=parseInt(yy,10);
            var y1=1800, y2=2199;
            var msg="Âm lịch Việt nam: Năm nằm ngoài giới hạn tính!<br>Lưu ý: Được tính và khuyến cáo có giá trị tham khảo từ năm "+y1+" đến năm "+y2+".";
    
            if (checkSyear(yy2,y1,y2, msg)) { // just in case
                window.location = window.location.pathname + '?yy='+yy+'&mm='+mm+'&dd='+dd;
            }
        }
    
        function viewYear(dd,mm,yy) {
            var yy2=parseInt(yy,10);
            var y1=1800, y2=2199;
            var msg="Âm lịch Việt nam: Năm nằm ngoài giới hạn tính!<br>Lưu ý: Được tính và khuyến cáo có giá trị tham khảo từ năm "+y1+" đến năm "+y2+".";
    
            if (checkSyear(yy2,y1,y2, msg)) { // always check
                var loc = 'VN_wholeyear.html'+ '?yy='+yy+'&mm='+mm+'&dd='+dd;
                //var popWindow = window.open(loc, "popWindow", "menubar=yes,scrollbars=yes,resizable=yes");
                openModalWeb(loc);
            }
        }
     
    function openModalWeb1(href){
        var spcAbve="<br><br><br><br><br><br><br><br><br><br><br><br><br>";
        document.getElementById("conversion_result").innerHTML=spcAbve+"<img src='"+href+"' width='500' height='500'>";
    }
    
    function openModalWeb(href){
        //var popWindow = window.open(href, "popWindow", "menubar=yes,scrollbars=yes,resizable=yes");
        document.getElementById("conversion_result").innerHTML="<iframe src='"+href+"' width='500' height='600'></iframe>";
    
    }
    
    function submitYear(e){  //e=0 Vietnam-English, e=1, e=2
        document.getElementById("err").innerHTML="";
     
        //let m=parseInt(document.getElementById("month").value,10);
        //let t=parseInt(document.getElementById("year").value,10);
        let t=parseInt(document.SelectMonth.year.value,10);
    
        if(isNaN(t)||t<-721||t>2200){
            let t=["Âm lịch Trung quốc: Năm nằm ngoài giới hạn tính!<br>Lưu ý: Được tính và khuyến cáo có giá trị tham khảo từ giữa các năm -721 (721 trước công nguyên) và 2200.","",""];
            document.getElementById("err").innerHTML=t[e];
            } else t<-220?ancient_calendar_handler(e,t):(document.getElementById("Spring").style.display="none",document.getElementById("Warring").style.display="none"),calendar(e,t);
    }
    
    function ancient_calendar_handler(e,t){
        let n,o,a;
        if(t>=-479){
            for(document.getElementById("Warring").style.display="block",document.getElementById("Spring").style.display="none",n=ancient_calendar_menu("Warring"),o=0;o<n.length;o++)if(document.getElementById(n[o].id).classList.contains("active")){a=0==e?n[o].li:n[o].lic,2==e&&(a=n[o].lis),"Xia1"==a&&(a="Xia"),document.getElementById("calshownWarring").innerHTML=a,document.getElementById("guliuliDescription").innerHTML=ancient_calendar_description("Warring",n[o].li,n[o].lic,n[o].lis,e);break}}else for(document.getElementById("Spring").style.display="block",document.getElementById("Warring").style.display="none",n=ancient_calendar_menu("Spring"),o=0;o<n.length;o++)if(document.getElementById(n[o].id).classList.contains("active")){a=0==e?n[o].li:n[o].lic,2==e&&(a=n[o].lis),"Xia2"==a&&(a="Xia"),document.getElementById("calshownSpring").innerHTML=a,document.getElementById("SpringliDescription").innerHTML=ancient_calendar_description("Spring",n[o].li,n[o].lic,n[o].lis,e);break}}function select_calendar(e,t){if(""!=document.getElementById("err").innerHTML)return;let n=e.slice(-6);"Spring"!=n&&(n="Warring");let o,a=ancient_calendar_menu(n),s=a.length;for(let e=0;e<s;e++)if(o=document.getElementById(a[e].id),o.classList.contains("active")){o.classList.remove("active");break}document.getElementById(e).classList.add("active"),submitYear(t);
    }
    
    
    function calendar(e,t){
        let n=langConstant(e);
        n.region=split_calendar_handler(e,t);
        let o,a,s,i,h,r=calDataYear(t,n),f="",y=r.cmonthDate.length,l=r.cmonthYear[y-1]-r.cmonthYear[0]+1,c=[],m=[],u=[0,0,0];for(o=0;o<3;o++)u[o]=t>-110?firstMonthNum(t-1+o):r.firstMonthNum;
    
        for(o=1;o<l;o++){let e=u[r.cmonthYear[0]+o];
    
        for(a=1;a<y;a++) if(r.cmonthYear[a]==r.cmonthYear[0]+o&&r.cmonthNum[a]==e){
            for(h=r.cmonthDate[a],s=0;s<13;s++)if(h<=r.mday[s]){
                i=s;
                break}
                c.push(i),m.push(h-r.mday[i-1])
            }
        }
    
        let d=(t+725)%10,g=(t+727)%12;
        d<0&&(d+=10),g<0&&(g+=12);
        let p=[" "," "," "];
        p[0]=n.heaven[d]+" "+n.earth[g]+" ("+n.animal[g]+")";
        let w=(t+726)%10,b=(t+728)%12;w<0&&(w+=10),b<0&&(b+=12),p[1]=n.heaven[w]+" "+n.earth[b]+" ("+n.animal[b]+")";
        let _=(t+727)%10,M=(t+729)%12;_<0&&(_+=10),M<0&&(M+=12),p[2]=n.heaven[_]+" "+n.earth[M]+" ("+n.animal[M]+")";
        let D= t>1582 ? "Gregorian":t>7?"Julian":"(Proleptic) Julian";1582==t&&(D="Gregorian/Julian");
        let T=t.toString();
        t<1&&(T+=0==e?" ("+(1-t)+" BCE)":" (前"+(1-t)+")");
     
    
        let N=r.cmonthYear[0];
        if(0==e)
            f+="<h3> Dương lịch "+D+" Year: "+T+"</h3>",f+="<h3>Âm lịch Trung quốc:</h3>",f+=1==l?"<h3>"+p[N]+"</h3> <brc />":2==l?"<h3>"+p[N]+" trước "+n.gMonth[c[0]-1]+" - "+m[0]+", <brc />"+p[N+1]+" từ  "+n.gMonth[c[0]-1]+" - "+m[0]+"</h3> <br />":"<h3>"+p[N]+" trước ngày "+n.gMonth[c[0]-1]+" - "+m[0]+", <brc />"+p[N+1]+" giữa "+n.gMonth[c[0]-1]+" - "+m[0]+" and "+n.gMonth[c[1]-1]+" - "+(m[1]-1)+", <brc />"+p[N+2]+" từ sau "+n.gMonth[c[1]-1]+" - "+m[1]+"</h3> <brc />";
        else {
            let o;
            if(1==e?(
                D="("+(t>1582?"格里曆":t>7?"儒略曆":"逆推儒略曆")+")",1582==t&&(D="(儒略曆/格里曆)"),p[0]+=eraName(t-1,n.region),p[1]+=eraName(t,n.region),p[2]+=eraName(t+1,n.region)):(D="("+(t>1582?"格里历":t>7?"儒略历":"逆推儒略历")+")",1582==t&&(D="(儒略历/格里历)"),p[0]+=eraNameSim(t-1,n.region),p[1]+=eraNameSim(t,n.region),p[2]+=eraNameSim(t+1,n.region)),1==e?(f+="<h3>公曆年"+D+": "+T+"</h3>",f+="<h3>農曆年:</h3>"):(f+="<h3>公历年"+D+": "+T+"</h3>",f+="<h3>农历年:</h3>"),1==l)f+="<h3>"+p[N]+"</h3> <brc />";
            else if(2==l){
                o="<h3>"+c[0]+"月"+m[0]+"日前: "+p[N]+", ",t<1912&&(o+="<brc />"),24==t&&e>0&&(o="<h3>"+c[0]+"月"+m[0]+"日前: 癸 未 (羊)("+(1==e?"漢":"汉")+"更始元年),<brc />");let n=1==e?"日及以後: ":"日及以后: ";o+=c[0]+"月"+m[0]+n+p[N+1]+"</h3><brc />",f+=o
            } else {
                let t=1==e?"日及以後: ":"日及以后: ";
                f+="<h3>"+c[0]+"月"+m[0]+"日前: "+p[N]+",<brc /> "+c[0]+"月"+m[0]+"日至"+c[1]+"月"+(m[1]-1)+"日: "+p[N+1]+",<brc />"+c[1]+"月"+m[1]+t+p[N+2]+"</h3><brc />"}
        }
     
        0==e?(p[0]=n.heaven[d]+" "+n.earth[g],p[1]=n.heaven[w]+" "+n.earth[b],p[2]=n.heaven[_]+" "+n.earth[M]):(p[0]=n.heaven[d]+n.earth[g]+"年",p[1]=n.heaven[w]+n.earth[b]+"年",p[2]=n.heaven[_]+n.earth[M]+"年");let S=addYearInfo(t,n,r);
     
        if(""!=S){
            f+=(0==e?'<h3 style="color:brown;line-height:26px;">':'<h3 style="color:brown;letter-spacing:4px;line-height:30px;">')+S+"</h3><br /><br />"
        }
    
        //for(let o=0;o<12;o++) f+=printMonth_c(o,e,t,p,u,n,r);
        let csm=parseInt(document.SelectMonth.month.value,10)-1;
        f+=printMonth_c(csm,e,t,p,u,n,r);
        document.getElementById("calendar").innerHTML=f;
        document.getElementById("c_jdate").style.display="block";
    
    }
    
    //-->
    </script>

    vậy là chúng ta đã giải quyết xong đề bài nhắc việc bằng foobar2000, tuy có trúc trắc lúc thi triển nhưng cũng qua được.
    mong rằng, loạt bài này sẽ gợi mở cho các bạn 1 cảm hứng tích cực và nguồn vui nho nhỏ giữa bao ưu tư thường nhật.
    (có bác đã tự mod bản denon vintage thêm 1 số nút và thông tin rất hay và đẹp, mình rất vui vì tự thấy công sức hổm rày có ích).

    1 lần nữa, trước thềm xuân 2024 Giáp Thìn, mình xin chúc anh em và gia đình 1 năm thật an yên, thật khỏe mạnh và luôn lạc quan yêu đời.
    chúc diễn đàn Mạng nghe nhìn Việt nam vnav.vn ngày càng phát triển.

    //=================//

    lưu ý:
    1. code javascript, giải thuật thuộc bản quyền của các nhà phát triển tương ứng. người dùng vui lòng kiểm tra lại tính chính xác của thông tin (ngày âm lịch) do chương trình cung cấp trước khi sử dụng.
    2. code javascript tích hợp nhắc việc theo âm dương lịch vào foobar2000 theo nội dung bài viết này chỉ để tham khảo, tác giả và mạng nghe nhìn Việt nam vnav.vn không chịu bất kỳ trách nhiệm với bất kỳ rủi ro liên quan nào (nếu có).
     

    Attached Files:

    Last edited: 1/2/24
    Scorpio and tml3nr like this.
Tags:

Share This Page

Loading...