About Foobar2000

Τέλος να πω ότι κατάφερα και το έστησα αξιοπρεπώς. Κάνει αυτό που θέλω.
Το μόνο πρόβλημα που έχω είναι με τα εξώφυλλα γιατί τα περισσότερα έχουν ότι να ναι όνομα και δεν τα βλέπει το default ενσωματωμένο plugin. Από λίγο που έψαξα δεν υπάρχει και κάποιο άλλο plugin.
Είναι και λίγο βαρετό το μαύρο skin, χρειαζόταν κανένα τόνο του γκρι ή κανένα χρώμα κάπου - δεν υπάρχουν και τα εξώφυλλα για αυτό. Αλλά θα ζήσω :p
 
Τουλάχιστον υπάρχει το JSPLITTER plugin που τρέχει σε 64bit. Έβαλα το ChatGPT 4.1 να μου γράψει ένα script να τραβάει τις φωτογραφίες από τα album και να τις δείχνει σε ένα πάνελ. Πανωλεθρία δεν έτρεχε τίποτα μετά από 5-6 προσπάθειες. Μετά έβαλα το ο4-mini-high να κάνει το ίδιο και με την πρώτη το πέτυχε. Άρα βλέπω και τις εικόνες.

Edit.
Τώρα κατάλαβα ότι επειδή έχω εγκαταστήσει το columns UI μπορούσα απλά να βάλω στο Artwork *.jpg και να παίζει...όπως και παίζει και δεν χρειάζομαι script.

Τι στοίχημα ότι σε 6 μήνες δεν θα θυμάμαι τίποτα.
 
Last edited:
  • Haha
Reactions: Tzimisce and koupa
Θα σου έλεγα να βάλεις Roon αλλά θα με κραξεις. :LOL:

Το ρημαδι το f2k έχει άπειρες ρυθμίσεις που μαμιεται ο Δίας. Όταν εγκατέστησα την 64bit είχα πόσες ώρες να ρυθμίσω το dsd να παίζει bit perfect. Τη μια έπαιζε μόνο dsd και όχι pcm. Άλλοτε έπαιζε και τα δύο στη βασική δειγματοληψία. Κλπ κλπ.

Τουλάχιστον το jriver έχει ΜΙΑ επιλογή και τέλος.

Όσο για τα εξώφυλλα τα βάζω στο αρχείο και όχι χύμα. Γενικά αποφεύγω οτιδήποτε άλλο εκτός μουσικής. Λίστες, cue, logs και λοιπά αρχεία πάνε κατευθείαν κάδο.
 
Λοιπόν...το foobar2000 64bit από τη στιγμή που στήσεις JSPLITTER και έχεις και πρόσβαση σε ένα καλό LLM χρόνο να έχεις και θα το κάνεις να πετύχει αυτό που θέλεις. Σε μισή ώρα το έχω βάλει και έχει φτιάξει ένα πάνελ που στην κορυφή έχεις ένα drop-down με όλα τα playlist που υπάρχουν διαθέσιμα. Το panel δεν επηρρεάζεται καθόλου από το Library View. Δείχνει φωτογραφία του άλμπουμ, καλλιτέχνη, δίσκο, τραγούδι, διάρκεια.
Τώρα παίζω να το φτιάξω να δέχεται drag-drop τραγούδια και μετά να μπορώ να αλλάζω και τη σειρά τους. Στην ουσία είναι σαν να έχω φτιάξει το plugin που θέλω μόνος μου.

Μετά από μερικές ώρες:
- Σχεδόν το τελείωσα. Από κάποιο σημείο και μετά το ChatGPT o4-mini-high παρέδωσε και το έστρωσα με το Gemini 2.5 Pro. Είχα λίγα μηνύματα, αλλά κατάφερε να ενσωματώσει βελάκια ώστε να αλλάζω τη σειρά των τραγουδιών στο playlist.
- Μόνο bug ότι η διάρκεια των τραγουδιών εμφανίζεται κάθετα αντί για οριζόντια. Δεν κατάφερε να το διορθώσει ούτε το Gemini 2.5 Flash ούτε το ChatGPT, θα το δοκιμάσω αύριο που θα μηδενίσουν τα μηνύματα στο Pro.
- Drag-drop και τα 2 μοντέλα μου είπαν πως δεν είναι υλοποιημένο στον κώδικα του JSPLITTER οπότε δεν μπορώ με script να το ενεργοποιήσω.

Σε κάθε περίπτωση τώρα είναι όπως το θέλω!!!
 
Last edited:
  • Like
Reactions: koupa
Για κάποιο λόγο μετά την αναβάθμιση στην τελευταία beta έχασα μερικά αρχεία DSD. Πάντα κοιτάω τα logs διότι όλο και κάτι θα δεις που δεν πρέπει. Ξαφνικά σε κάμποσα αρχεία έβλεπα "Service duplication". Όχι μόνο αυτά που πρόσθεσα αλλά και κάποια 2 ή 3 ετών. Τα άλλα προγράμματα τα έβλεπαν κανονικά.

Τελικά έγινε αυτό που περιγράφει ο φίλος πιο πάνω. Είχα βάλει 2 addons που τελικά "χτύπησαν" μεταξύ τους. Το κλασικό DSD+ASIO και το νέο uDSD που είναι η εξέλιξη και παίζει και στο MacOS. Έβγαλα το νέο και τελικά τα διάβασε από την αρχή.
 
Μοιράζομαι τον κώδικα από το Panel, αν και το πιο πιθανό κανείς να μην το χρειαστεί.
Ουσιαστικά η λογική του είναι να έχω ένα playlist που να το φτιάχνω και να μπορώ να το παίζω και να βλέπω τι παίζει στο πάνελ αυτό, ενώ μπορώ να φτιάχνω ένα άλλο playlist στο άλλο πάνελ με το Library View.
Στο πάνελ αυτό με αριστερό κλικ ψιλά πάει κυκλικά σε όλα τα playlist που υπάρχουν διαθέσιμα μέχρι να βρεις αυτό που θες. Με διπλό αριστερό κλικ σε τραγούδι το παίζει, τα βελάκια πάνε τα τραγούδια πάνω/κάτω και έχει και scrolling αν έχεις πολλά τραγούδια.
Με δεξί κλικ προσθέτεις από το σκληρό όποιο τραγούδι θες.

Code:
///////////////////////////////////////////////////////////////////////////////
// FixedPlaylist.js  – isolated playlist view + up/down arrows + scrollbar
// Works with foobar2000 64-bit v2.24.5  + JSplitter 4.0.3
///////////////////////////////////////////////////////////////////////////////

// ==PREPROCESSOR==
// @name    "FixedPlaylist (Isolated)"
// @version "1.8"                     // island-mode
// @author  "Original Author + Gemini"
// ==/PREPROCESSOR==

// ────────────────────────────────────────────────────────────────────────────
// 1. CONSTANTS  (unchanged except LEN_W = 90 from v1.7)
// ────────────────────────────────────────────────────────────────────────────
const BAR_HEIGHT      = 24,   LINE_HEIGHT  = 60,  IMAGE_SIZE = 50;
const ICON_SIZE       = 16,   ICON_PADDING = 8,   PADDING    = 8;
const FONT_NAME       = "Segoe UI", FONT_SIZE = 16;
const TEXT_COLOR      = 0xFFFFFFFF, BG_COLOR = 0xFF1E1E1E, HILITE_COLOR = 0xFF333333;

const SC_W = 12,  SC_BG = 0x33000000,  SC_FG = 0xFF666666, SC_FG_DRAG = 0xFFAAAAAA,
      SC_MIN_THUMB = 24;

const LEN_W = 90;   // width reserved for track length column

const DT_LEFT = 0, DT_CENTER = 1, DT_RIGHT = 2, DT_VCENTER = 4,
      DT_SINGLELINE = 32, DT_NOPREFIX = 0x800, DT_END_ELLIPSIS = 0x4000;

// ────────────────────────────────────────────────────────────────────────────
// 2. GLOBAL  STATE  (selIndex is *never* overwritten by fb2k events)
// ────────────────────────────────────────────────────────────────────────────
let selIndex      = null;   // locked playlist index
let itemCount     = 0,      visibleCount = 0, scrollOffset = 0;

let txtFont, iconFont, tfAlbum, tfArtist, tfTitle;
let artCache = {};

let needScroll  = false, thumbRect = {x:0,y:0,w:0,h:0};
let dragging    = false,  dragStartY = 0, dragStartOff = 0;

// ────────────────────────────────────────────────────────────────────────────
// 3. INITIALISERS
// ────────────────────────────────────────────────────────────────────────────
function ensureFonts(){
    if(!txtFont)  txtFont  = gdi.Font(FONT_NAME, FONT_SIZE, 0);
    if(!iconFont) iconFont = gdi.Font(FONT_NAME, FONT_SIZE, 0);
}
function ensureTF(){
    if(!tfAlbum)  tfAlbum  = fb.TitleFormat("%album%");
    if(!tfArtist) tfArtist = fb.TitleFormat("%artist%");
    if(!tfTitle)  tfTitle  = fb.TitleFormat("%title%");
}

// ────────────────────────────────────────────────────────────────────────────
// 4. PLAYLIST-METRICS &  SCROLLBAR GEOMETRY
// ────────────────────────────────────────────────────────────────────────────
function ensureSelValid(){
    if(plman.PlaylistCount===0){ selIndex = -1; return; }
    if(selIndex===null || selIndex<0)           selIndex = 0;
    else if(selIndex >= plman.PlaylistCount)    selIndex = plman.PlaylistCount-1;
}

function updateMetrics(){
    ensureSelValid();

    if(selIndex === -1){ itemCount=0; visibleCount=0; ensureFonts(); return; }

    itemCount    = plman.PlaylistItemCount(selIndex);
    visibleCount = Math.max(0, Math.floor((window.Height-BAR_HEIGHT)/LINE_HEIGHT));
    scrollOffset = Math.min(scrollOffset, Math.max(0, itemCount-visibleCount));
    needScroll   = itemCount > visibleCount;

    ensureFonts(); ensureTF();
    computeThumb();
}

function computeThumb(){
    if(!needScroll){ thumbRect={x:0,y:0,w:0,h:0}; return; }
    const railX=window.Width-SC_W, railY=BAR_HEIGHT, railH=window.Height-BAR_HEIGHT;
    const ratio=visibleCount/itemCount, h=Math.max(SC_MIN_THUMB,Math.floor(railH*ratio));
    const maxY=railH-h, y=railY+Math.round(maxY*(scrollOffset/(itemCount-visibleCount)));
    thumbRect={x:railX,y,w:SC_W,h};
}

// ────────────────────────────────────────────────────────────────────────────
// 5. HELPERS  (unchanged)
// ────────────────────────────────────────────────────────────────────────────
const frontPat   = [/^f.*\.(jpe?g)$/i,/.*front.*\.(jpe?g|png|bmp|gif)$/i];
const genericPat = [/^cover\.(jpe?g|png|bmp|gif)$/i,/.*\.(jpe?g|png|bmp|gif)$/i];
function findArt(folder){
    try{
        const fso=new ActiveXObject("Scripting.FileSystemObject");
        if(!fso.FolderExists(folder))return null;
        const dir=fso.GetFolder(folder);
        for(let p of frontPat) for(let f=new Enumerator(dir.Files);!f.atEnd();f.moveNext())
            if(p.test(f.item().Name)) return f.item().Path;
        for(let p of genericPat)for(let f=new Enumerator(dir.Files);!f.atEnd();f.moveNext())
            if(p.test(f.item().Name)) return f.item().Path;
        for(let s=new Enumerator(dir.SubFolders);!s.atEnd();s.moveNext()){
            const r=findArt(s.item().Path); if(r) return r; }
    }catch(_){}
    return null;
}
function fmtTime(t){ const m=Math.floor(t/60), s=Math.floor(t%60);
    return m+":"+(s<10?"0":"")+s; }
function clamp(v){ return Math.max(0,Math.min(v,itemCount-visibleCount)); }

// ────────────────────────────────────────────────────────────────────────────
// 6. INPUT EVENTS
// ────────────────────────────────────────────────────────────────────────────
function on_mouse_wheel(d){ if(!needScroll)return;
    scrollOffset=clamp(scrollOffset-(d>0?3:-3)); computeThumb(); window.Repaint(); }

function on_mouse_lbtn_down(x,y){
    // 1. header              → cycle inside panel only
    if(y<BAR_HEIGHT){
        if(plman.PlaylistCount>0){
            selIndex = (selIndex+1)%plman.PlaylistCount;
            scrollOffset = 0; artCache={}; updateMetrics(); window.Repaint();
        }
        return;
    }

    // 2. scrollbar drag / page
    if(needScroll && x>=window.Width-SC_W){
        if(y>=thumbRect.y && y<=thumbRect.y+thumbRect.h){
            dragging=true; dragStartY=y; dragStartOff=scrollOffset;
        }else{
            scrollOffset = clamp(y<thumbRect.y? scrollOffset-visibleCount
                                                : scrollOffset+visibleCount);
        }
        computeThumb(); window.Repaint(); return;
    }

    // 3. playlist interaction
    if(selIndex<0||itemCount===0) return;
    const row=Math.floor((y-BAR_HEIGHT)/LINE_HEIGHT), idx=scrollOffset+row;
    if(idx<0||idx>=itemCount) return;

    const xUp=PADDING, xUpE=xUp+ICON_SIZE,
          xDn=xUpE+ICON_PADDING, xDnE=xDn+ICON_SIZE;

    if(x>=xUp&&x<xUpE) { moveItem(idx,idx-1); return; }
    if(x>=xDn&&x<xDnE) { moveItem(idx,idx+1); return; }

    plman.ExecutePlaylistDefaultAction(selIndex,idx);
}

function on_mouse_move(x,y){
    if(!dragging) return;
    const railH=window.Height-BAR_HEIGHT, maxPix=railH-thumbRect.h,
          maxOff=itemCount-visibleCount, off=dragStartOff + ((y-dragStartY)/maxPix)*maxOff;
    scrollOffset=clamp(Math.round(off)); computeThumb(); window.Repaint();
}
function on_mouse_lbtn_up(){ dragging=false; }
function on_mouse_leave(){ if(!utils.IsKeyPressed(1)) dragging=false; }
function on_mouse_rbtn_down(){ fb.RunMainMenuCommand("File/Add files..."); return true; }

// ────────────────────────────────────────────────────────────────────────────
// 7. MOVE ITEM  (unchanged)
// ────────────────────────────────────────────────────────────────────────────
function moveItem(o,n){
    if(selIndex<0) return;
    n=Math.max(0,Math.min(n,itemCount-1)); if(o===n)return;
    plman.ClearPlaylistSelection(selIndex);
    plman.SetPlaylistSelectionSingle(selIndex,o,true);
    plman.MovePlaylistSelection(selIndex,n-o);
    plman.ClearPlaylistSelection(selIndex); window.Repaint();
}

// ────────────────────────────────────────────────────────────────────────────
// 8. PAINT   (unchanged from v1.7)
// ────────────────────────────────────────────────────────────────────────────
function on_paint(gr){
    ensureFonts(); computeThumb();
    gr.FillSolidRect(0,0,window.Width,window.Height,BG_COLOR);
    gr.FillSolidRect(0,0,window.Width,BAR_HEIGHT,0xFF2A2A2A);

    const plName = (selIndex!==-1 && plman.PlaylistCount>0)?
        (plman.GetPlaylistName(selIndex)||"(Unnamed)") : "(No playlist)";
    gr.DrawString(`Playlist: ${plName} (${itemCount} items) | L-click cycle, R-click add`,
                  txtFont,TEXT_COLOR,PADDING,0,window.Width-2*PADDING,BAR_HEIGHT,
                  DT_VCENTER|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX|DT_END_ELLIPSIS);

    if(selIndex<0||itemCount===0){
        gr.DrawString("Playlist is empty or not selected.",
                      txtFont,TEXT_COLOR,PADDING,BAR_HEIGHT+PADDING,
                      window.Width-2*PADDING,LINE_HEIGHT,
                      DT_LEFT|DT_VCENTER|DT_SINGLELINE); return;
    }

    const list = plman.GetPlaylistItems(selIndex);
    if(!list||list.Count===0) return;

    const rows = Math.min(visibleCount,itemCount-scrollOffset),
          availW = window.Width - (needScroll?SC_W:0);

    const xUp=PADDING, xDn=xUp+ICON_SIZE+ICON_PADDING, xArt=xDn+ICON_SIZE+ICON_PADDING,
          xTxt0=xArt+IMAGE_SIZE+PADDING;

    const txtTot = availW - xTxt0 - LEN_W - PADDING,
          albumW = Math.floor(txtTot*0.35), artistW=Math.floor(txtTot*0.30),
          titleW = txtTot - albumW - artistW;

    const xAlbum=xTxt0, xArtist=xAlbum+albumW+PADDING,
          xTitle=xArtist+artistW+PADDING, xLen=availW-PADDING-LEN_W;

    const flagI = DT_VCENTER|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX|DT_END_ELLIPSIS,
          flagG = DT_CENTER|DT_VCENTER|DT_SINGLELINE;

    for(let r=0;r<rows;r++){
        const idx=scrollOffset+r, h=list[idx], y=BAR_HEIGHT+r*LINE_HEIGHT;
        if(plman.PlayingPlaylist===selIndex && idx===plman.PlayingItemIndex)
            gr.FillSolidRect(0,y,availW,LINE_HEIGHT,HILITE_COLOR);

        const icoY=y+(LINE_HEIGHT-ICON_SIZE)/2;
        gr.DrawString("▲",iconFont,TEXT_COLOR,xUp,icoY,ICON_SIZE,ICON_SIZE,flagG);
        gr.DrawString("▼",iconFont,TEXT_COLOR,xDn,icoY,ICON_SIZE,ICON_SIZE,flagG);

        const folder=utils.SplitFilePath(h.Path)[0];
        let art=artCache[folder];
        if(art===undefined){ const p=findArt(folder); art=p?gdi.Image(p):null; artCache[folder]=art; }
        const artY=y+(LINE_HEIGHT-IMAGE_SIZE)/2;
        if(art) gr.DrawImage(art,xArt,artY,IMAGE_SIZE,IMAGE_SIZE,0,0,art.Width,art.Height);
        else {
            gr.DrawRect(xArt,artY,IMAGE_SIZE,IMAGE_SIZE,1,0xFF505050);
            gr.DrawString("N/A",txtFont,0xFF808080,
                          xArt,artY,IMAGE_SIZE,IMAGE_SIZE,
                          DT_CENTER|DT_VCENTER|DT_SINGLELINE); }

        gr.DrawString(tfAlbum .EvalWithMetadb(h)||"-",txtFont,TEXT_COLOR,
                      xAlbum ,y,albumW ,LINE_HEIGHT,flagI);
        gr.DrawString(tfArtist.EvalWithMetadb(h)||"-",txtFont,TEXT_COLOR,
                      xArtist,y,artistW,LINE_HEIGHT,flagI);
        gr.DrawString(tfTitle .EvalWithMetadb(h)||"-",txtFont,TEXT_COLOR,
                      xTitle ,y,titleW ,LINE_HEIGHT,flagI);
        gr.DrawString(h.Length?fmtTime(h.Length):"-",txtFont,TEXT_COLOR,
                      xLen   ,y,LEN_W  ,LINE_HEIGHT,
                      DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
    }

    if(needScroll){
        gr.FillSolidRect(window.Width-SC_W,BAR_HEIGHT,SC_W,window.Height-BAR_HEIGHT,SC_BG);
        gr.FillSolidRect(thumbRect.x,thumbRect.y,thumbRect.w,thumbRect.h,
                         dragging?SC_FG_DRAG:SC_FG); }
}

// ────────────────────────────────────────────────────────────────────────────
// 9. FB2K CALLBACKS  (only those that must keep the view consistent)
// ────────────────────────────────────────────────────────────────────────────
function on_init()                     { updateMetrics(); }
function on_size()                     { updateMetrics(); window.Repaint(); }

/* IMPORTANT: unlike earlier versions, the panel deliberately IGNORES the user
   switching playlists elsewhere. We only refresh sizes. */
function on_playlist_switch()          { updateMetrics(); window.Repaint(); }

/* Keep track of deletions / additions to maintain validity, but never overwrite
   selIndex unless it becomes out-of-range (handled in ensureSelValid()). */
function on_playlists_changed()        { updateMetrics(); window.Repaint(); }

function on_playlist_items_added(p){   if(p===selIndex){updateMetrics(); window.Repaint();} }
function on_playlist_items_removed(p){ if(p===selIndex){updateMetrics(); window.Repaint();} }
function on_playlist_items_reordered(p){if(p===selIndex) window.Repaint(); }

function on_playback_new_track(){ window.Repaint(); }
function on_playback_stop(){ window.Repaint(); }
///////////////////////////////////////////////////////////////////////////////

Ένα bug έχει μόνο που δεν μπόρεσε να το διορθώσει κανένα llm την κάθετη απεικόνιση του χρόνου του κάθε track που φαίνεται κάθετα.

Edit: Αυτό το έφτιαξα και ο κώδικας πιο πάνω δεν το κάνει πιά. Η μόνη νέα λειτουργικότητα που ίσως θα ήθελα να βάλω είναι να μην αλλάζει το view όταν αλλάζεις το view στο βασικό παράθυρο. Βέβαια με αριστερό κλικ μετά είναι εύκολο να το ξαναφέρεις.
 
Last edited:
Νομίζω ότι θα έπρεπε να ισχύει το ανάποδο. Πια δεν χρειάζεται να ψάχνεις μέρες να βρεις το plugin που θες, μετά να ψάχνεις προτεινόμενες ρυθμίσεις και τέλος να κάνεις χειροκίνητες δοκιμές μέχρι να το έχεις. Τώρα ακόμη και κάτι πολύ δύσκολο μέσα σε λίγες ώρες με ένα bot το έχεις. Άσε που σε 1 χρόνο αυτό θα γίνει μισή ώρα και μετά σε 2-3 ανταλλαγές μηνυμάτων.
Και επίσης δεν ντρέπεται να σου πει ότι κάτι που θες δεν μπορεί να στο φτιάξει. Πχ ήθελα να δουλεύει drag/drop και μου λέει δεν το έχει ενσωματώσει ο developer στο JSPLITTER οπότε ξέχνα το εκτός αν θες να προσθέσεις κάτι τέτοιο πάνω στο build που δεν μπορώ να στο κάνω εγώ αυτό, αλλά μόνο να σε βοηθήσω αν ξέρεις. Και μου πρότεινε και workaround για να έχω τη χρηστικότητα που ήθελα.
Αλλάζει ο τρόπος που αλληλεπιδρούμε με το λογισμικό. Σύντομα μάλλον θα μας φτιάχνει το λογισμικό που -νομίζουμε- ότι θέλουμε σχεδόν σε πραγματικό χρόνο.
 
Γνωρίζει κανείς, το έχει κάνει;
Μπορεί να ρυθμιστεί το Foobar, ώστε να παίζει αρχεία DSD native, εφ' όσον ο DAC το υποστηρίζει;
Δεν εννοώ με μετατροπή σε PCM, αυτό ήδη το κανω, αλλά αυτούσια {natively).
Αν είναι κάπου γραμμένο εδώ μέσα, παρακαλώ πείτε μου, για να μη διαβάζω όλο το νήμα από την αρχή...