From b7fa914f1f95ccbadcbabc18e3ad8aa61ca244a4 Mon Sep 17 00:00:00 2001 From: Vynull App Date: Fri, 16 Oct 2026 01:16:23 +0000 Subject: [PATCH] dbserver: answer other-type Display, Play and Delivery song info as rekordbox does For a context track type other than 0x01, rekordbox answers Display (0x2002), Play (0x2102) and Delivery (0x2602) song info with a normal 0x4000 header whose total is 0xffffffff and no rows. Every link-spec golden for these kinds shows that, with or without the requesting player's status, and none shows the menu-unavailable error (0x4003) we sent since #81; that error is right only for 0x2202-0x2502. Decks browsing Vynull send type 0x01, so they're unaffected. Song-info album rows (TRACK INFO and Delivery Info) now leave argument 8 at 0, as rekordbox's do; only ALBUM menu rows repeat the album ID there as the artwork handle. --- dbserver/menuitem.go | 1 + dbserver/render.go | 7 ++++--- dbserver/songinfo_delivery.go | 2 +- dbserver/songinfo_rules.go | 11 ++++++++--- dbserver/songinfo_rules_test.go | 23 ++++++++++++++++------- dbserver/track.go | 2 +- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/dbserver/menuitem.go b/dbserver/menuitem.go index dc701de..355e095 100644 --- a/dbserver/menuitem.go +++ b/dbserver/menuitem.go @@ -39,6 +39,7 @@ type menuItem struct { // Track-row wire-flag inputs (see render.go's flag assembly). contextTrackType uint32 // packed context track type — arg 7 high byte (TT<<24) on hierarchy track leaves + noArtHandle bool // album row in a song-info list: arg 8 stays 0 trackPos uint32 // arg 9: scope position / track number (album scope uses TrackNo; 0 elsewhere) hasSongTag bool // track carries a MyTag — arg 7 bit 0x1 autoLoadHotCue bool // deck auto-loads hot cues — arg 10 bit 0x100 diff --git a/dbserver/render.go b/dbserver/render.go index 1279bd1..9d191b4 100644 --- a/dbserver/render.go +++ b/dbserver/render.go @@ -195,9 +195,10 @@ func (h *Handler) handleRenderMenu(msg *proto.DBMessage) []*proto.DBMessage { flags11 = item.newKeyID // arg 11: converted (Camelot 1-24) key index } } - // Album rows repeat the AlbumID in arg[8] (rekordbox uses it as the - // album-art handle), unlike artist/genre/label rows which leave it 0. - if item.ItemType == 0x02 { + // Album menu rows repeat the AlbumID in arg[8] (rekordbox uses it as + // the album-art handle), unlike artist/genre/label rows which leave it + // 0. Song-info album rows (TRACK INFO, Delivery Info) leave it 0 too. + if item.ItemType == 0x02 && !item.noArtHandle { flags8 = item.ID } diff --git a/dbserver/songinfo_delivery.go b/dbserver/songinfo_delivery.go index 8b57c30..0d90766 100644 --- a/dbserver/songinfo_delivery.go +++ b/dbserver/songinfo_delivery.go @@ -57,7 +57,7 @@ func (h *Handler) handleGetDeliveryInfo(msg *proto.DBMessage) []*proto.DBMessage {ID: t.ID, ItemType: 0x37}, // lyricist {ParentID: t.ID, ItemType: 0x4f}, // DeliveryControl / ISRC track, - {ID: id("album", t.Album), Label1: t.Album, ItemType: 0x02}, + {ID: id("album", t.Album), Label1: t.Album, ItemType: 0x02, noArtHandle: true}, {ID: uint32(t.Duration.Seconds()), ItemType: 0x0b}, // length } } diff --git a/dbserver/songinfo_rules.go b/dbserver/songinfo_rules.go index c0a99ee..3e1d36b 100644 --- a/dbserver/songinfo_rules.go +++ b/dbserver/songinfo_rules.go @@ -11,8 +11,9 @@ import "github.com/vynulldev/vynull/proto" // normally. // // - 0x2202, 0x2302, 0x2402, 0x2502: always a menu-unavailable error. -// - 0x2002, 0x2102, 0x2602: a menu-unavailable error unless the context -// track type is 0x01. +// - 0x2002, 0x2102, 0x2602: unless the context track type is 0x01, a +// normal header whose total is 0xffffffff, with no rows (every link-spec +// golden for these kinds; a menu-unavailable error was wrong here). // - 0x2003, 0x2103: no reply at all unless the context track type is 0x01. // // A context of 0 (no context) is exempt: rekordbox serves those normally. @@ -33,7 +34,11 @@ func songInfoContextRule(msg *proto.DBMessage) ([]*proto.DBMessage, bool) { return menuError, true case 0x2002, 0x2102, 0x2602: if !rekordboxType { - return menuError, true + return []*proto.DBMessage{{ + TxID: msg.TxID, + Type: proto.DBMsgSuccess, + Args: []proto.DBArg{proto.ArgI32(uint32(msg.Type)), proto.ArgI32(0xffffffff)}, + }}, true } case 0x2003, 0x2103: if !rekordboxType { diff --git a/dbserver/songinfo_rules_test.go b/dbserver/songinfo_rules_test.go index e8b5043..fe04102 100644 --- a/dbserver/songinfo_rules_test.go +++ b/dbserver/songinfo_rules_test.go @@ -12,6 +12,7 @@ func TestSongInfoContextRule(t *testing.T) { const ( served = iota menuError + noRows // 0x4000 header with total 0xffffffff silent ) for _, c := range []struct { @@ -20,13 +21,13 @@ func TestSongInfoContextRule(t *testing.T) { want int }{ {0x2002, 0x01010301, served}, - {0x2002, 0x01010302, menuError}, - {0x2002, 0x01010300, menuError}, + {0x2002, 0x01010302, noRows}, + {0x2002, 0x01010300, noRows}, {0x2002, 0, served}, // no context: served {0x2102, 0x01010301, served}, - {0x2102, 0x01010306, menuError}, + {0x2102, 0x01010306, noRows}, {0x2602, 0x01010301, served}, - {0x2602, 0x01010305, menuError}, + {0x2602, 0x01010305, noRows}, {0x2202, 0x01010301, menuError}, {0x2302, 0x01010302, menuError}, {0x2402, 0x01010301, menuError}, @@ -43,9 +44,17 @@ func TestSongInfoContextRule(t *testing.T) { if handled { got = silent if len(resp) == 1 { - got = menuError - if resp[0].Type != proto.DBMsgMenuError || resp[0].TxID != 7 || resp[0].Args[0].Int() != uint32(c.kind) { - t.Errorf("0x%04x ctx %#x: error reply %+v, want 0x4003 echoing the kind", c.kind, c.ctx, resp[0]) + r := resp[0] + if r.TxID != 7 || r.Args[0].Int() != uint32(c.kind) { + t.Errorf("0x%04x ctx %#x: reply %+v doesn't echo the kind", c.kind, c.ctx, r) + } + switch { + case r.Type == proto.DBMsgMenuError: + got = menuError + case r.Type == proto.DBMsgSuccess && len(r.Args) == 2 && r.Args[1].Int() == 0xffffffff: + got = noRows + default: + t.Errorf("0x%04x ctx %#x: unexpected reply %+v", c.kind, c.ctx, r) } } } diff --git a/dbserver/track.go b/dbserver/track.go index 3cc8941..8a2e7b6 100644 --- a/dbserver/track.go +++ b/dbserver/track.go @@ -161,7 +161,7 @@ func (h *Handler) handleGetMetadata(msg *proto.DBMessage) []*proto.DBMessage { metaItems := []*menuItem{ {ID: tID, Label1: title, ArtID: artworkID, ItemType: 0x0004, ParentID: 1, FileType: fileType}, // title {ID: artistID, Label1: artist, ItemType: 0x0007, ParentID: 1}, // artist - {ID: albumID, Label1: album, ItemType: 0x0002, ParentID: 1}, // album + {ID: albumID, Label1: album, ItemType: 0x0002, ParentID: 1, noArtHandle: true}, // album {ID: duration / 1000, Label1: "", ItemType: 0x000b}, // duration (parent=0) {ID: tempo, Label1: "", ItemType: 0x000d, ParentID: 1}, // BPM (* 100) {ID: keyID, Label1: key, ItemType: 0x000f, ParentID: 1}, // key