# mb_clouseau - Technical Specification

**Uncovering clues with MusicBee Clouseau.**

*A MusicBee trace/debug utility for plugin developers.*

---

## **HALRAD MusicBee Projects:**

* [MBXRemote](https://halrad.com/MBXRemote/) - Windows desktop remote client
* [MBXCast](https://halrad.com/mbxcast.html) - Google Caster for MusicBee
* [MusicBee TrueShuffle](https://halrad.com/MBTrueShuffle/) - True shuffle plugin

## Overview

mb_clouseau subscribes to ALL MusicBee notification types and logs every event with rich context. It serves as both a diagnostic tool and a reference implementation demonstrating the complete MusicBee plugin API.

## Architecture

```
mb_clouseau/
├── mb_clouseau.csproj           # Project file
├── plugin/
│   ├── Plugin.cs                # Entry point, event routing, inline log panel
│   ├── MusicBeeInterface.cs     # MusicBee API definitions (44KB)
│   ├── NLog.config              # Logging configuration
│   ├── settings.json            # Default settings template
│   ├── Core/
│   │   ├── ClouseauSettings.cs  # Settings manager (JSON)
│   │   ├── LogManager.cs        # NLog wrapper with named loggers
│   │   └── StateManager.cs      # Session state, event counts
│   ├── Logging/
│   │   ├── LogEntry.cs          # Structured log entry model
│   │   ├── EventLogger.cs       # Event handler with rich context for all 40 events
│   │   └── LogBuffer.cs         # Thread-safe ring buffer with filtering/export
│   ├── Metrics/
│   │   ├── MetricsCollector.cs  # Orchestrator (5s collection, 30s logging)
│   │   ├── MetricsSnapshot.cs   # Snapshot data model
│   │   ├── SystemMetrics.cs     # CPU, memory, disk
│   │   ├── ProcessMetrics.cs    # MusicBee process stats, handle leak detection
│   │   ├── ClrMetrics.cs        # GC, heap, threading
│   │   └── AudioMetrics.cs      # Audio device info
│   ├── Introspection/
│   │   ├── PluginInfo.cs        # Plugin metadata model
│   │   ├── PluginDiscovery.cs   # Plugin folder scanner
│   │   ├── PluginMonitor.cs     # Track plugin registrations
│   │   ├── ApiInterceptor.cs    # Hook MusicBee API calls
│   │   └── StateDumper.cs       # Deep state snapshots
│   └── UI/
│       ├── LogViewerPanel.cs    # Standalone log panel component
│       ├── SettingsDialog.cs    # Full settings UI with tabs
│       └── DashboardForm.cs     # Dashboard window with multiple tabs
├── deploy/
│   ├── www/                     # Documentation website
│   └── publish/                 # Release packages
├── README.md
├── SPEC.md
└── CHANGELOG.md
```

---

## Notification Subscription

### ReceiveNotificationFlags

mb_clouseau subscribes to ALL notification flags:

```csharp
_about.ReceiveNotifications =
    ReceiveNotificationFlags.PlayerEvents |      // 0x1
    ReceiveNotificationFlags.DataStreamEvents |  // 0x2
    ReceiveNotificationFlags.TagEvents |         // 0x4
    ReceiveNotificationFlags.DownloadEvents;     // 0x8
```

### NotificationType Enum (39 Events)

| #   | Event                         | Value | Category | Description                         |
| --- | ----------------------------- | ----- | -------- | ----------------------------------- |
| 1   | `PluginStartup`               | 0     | Core     | Plugin initialized successfully     |
| 2   | `TrackChanged`                | 1     | Player   | Current track changed               |
| 3   | `PlayStateChanged`            | 2     | Player   | Play/pause/stop state changed       |
| 4   | `AutoDjStarted`               | 3     | Player   | AutoDJ mode started                 |
| 5   | `AutoDjStopped`               | 4     | Player   | AutoDJ mode stopped                 |
| 6   | `VolumeMuteChanged`           | 5     | Player   | Mute toggled                        |
| 7   | `VolumeLevelChanged`          | 6     | Player   | Volume level changed                |
| 8   | `NowPlayingListChanged`       | 7     | Queue    | (Obsolete) Use PlayingTracksChanged |
| 9   | `NowPlayingArtworkReady`      | 8     | Player   | Artwork loaded for current track    |
| 10  | `NowPlayingLyricsReady`       | 9     | Player   | Lyrics loaded for current track     |
| 11  | `TagsChanging`                | 10    | Tags     | Tags about to be modified           |
| 12  | `TagsChanged`                 | 11    | Tags     | Tags were modified                  |
| 13  | `RatingChanged`               | 12    | Tags     | Track rating changed                |
| 14  | `PlayCountersChanged`         | 13    | Tags     | Play/skip count updated             |
| 15  | `ScreenSaverActivating`       | 14    | System   | Screen saver about to activate      |
| 16  | `RatingChanging`              | 15    | Tags     | Rating about to change              |
| 17  | `TrackChanging`               | 16    | Player   | Track about to change               |
| 18  | `ShutdownStarted`             | 17    | Core     | MusicBee shutting down              |
| 19  | `NowPlayingListEnded`         | 18    | Queue    | Reached end of queue                |
| 20  | `EmbedInPanel`                | 19    | UI       | Plugin panel embedded               |
| 21  | `PlayerRepeatChanged`         | 20    | Player   | Repeat mode changed                 |
| 22  | `PlayerShuffleChanged`        | 21    | Player   | Shuffle toggled                     |
| 23  | `PlayerEqualiserOnOffChanged` | 22    | Player   | Equalizer toggled                   |
| 24  | `PlayerScrobbleChanged`       | 23    | Player   | Scrobbling toggled                  |
| 25  | `ReplayGainChanged`           | 24    | Player   | ReplayGain mode changed             |
| 26  | `FileDeleting`                | 25    | Library  | File about to be deleted            |
| 27  | `FileDeleted`                 | 26    | Library  | File was deleted                    |
| 28  | `ApplicationWindowChanged`    | 27    | UI       | Window state changed                |
| 29  | `StopAfterCurrentChanged`     | 28    | Player   | Stop after current toggled          |
| 30  | `LibrarySwitched`             | 29    | Library  | Active library changed              |
| 31  | `FileAddedToLibrary`          | 30    | Library  | New file added to library           |
| 32  | `FileAddedToInbox`            | 31    | Library  | New file added to inbox             |
| 33  | `SynchCompleted`              | 32    | Sync     | Device sync completed               |
| 34  | `DownloadCompleted`           | 33    | Download | Download finished                   |
| 35  | `MusicBeeStarted`             | 34    | Core     | MusicBee fully initialized          |
| 36  | `PlayingTracksChanged`        | 35    | Queue    | Now playing list modified           |
| 37  | `PlayingTracksQueueChanged`   | 36    | Queue    | Queue portion changed               |
| 38  | `PlaylistCreated`             | 37    | Playlist | New playlist created                |
| 39  | `PlaylistUpdated`             | 38    | Playlist | Playlist modified                   |
| 40  | `PlaylistDeleted`             | 39    | Playlist | Playlist deleted                    |

---

## MusicBee API Reference

### API Version History

| Version | API Revision | Key Additions                 |
| ------- | ------------ | ----------------------------- |
| v2.0    | ≤25          | Core API (456 bytes)          |
| v2.1    | ≤31          | Background tasks              |
| v2.2    | ≤33          | Panel support                 |
| v2.3    | ≤38          | Playlist creation             |
| v2.4    | ≤43          | Artwork URLs                  |
| v2.5    | ≤47          | Tree nodes, downloads         |
| v3.0    | ≤48          | Plugin uninstall              |
| v3.1    | >48          | Albums, podcasts, sound graph |

### API Categories

#### 1. MB_* - MusicBee Core (15 methods)

| Method                                 | Description                          |
| -------------------------------------- | ------------------------------------ |
| `MB_ReleaseString`                     | Release string allocated by MusicBee |
| `MB_Trace`                             | Write to MusicBee's trace log        |
| `MB_GetWindowHandle`                   | Get MusicBee main window handle      |
| `MB_RefreshPanels`                     | Refresh all panels                   |
| `MB_SendNotification`                  | Send callback notification           |
| `MB_AddMenuItem`                       | Add menu item to MusicBee menus      |
| `MB_AddTreeNode`                       | Add node to navigation tree          |
| `MB_RegisterCommand`                   | Register a command handler           |
| `MB_CreateBackgroundTask`              | Create background task with progress |
| `MB_CreateParameterisedBackgroundTask` | Background task with parameters      |
| `MB_SetBackgroundTaskMessage`          | Update task progress message         |
| `MB_GetPanelBounds`                    | Get panel dimensions                 |
| `MB_AddPanel`                          | Add dockable panel                   |
| `MB_RemovePanel`                       | Remove panel                         |
| `MB_GetLocalisation`                   | Get localized string                 |
| `MB_ShowNowPlayingAssistant`           | Show now playing assistant           |
| `MB_InvokeCommand`                     | Invoke built-in command              |
| `MB_OpenFilterInTab`                   | Open filter in new tab               |
| `MB_SetWindowSize`                     | Set window dimensions                |
| `MB_DownloadFile`                      | Download file to location            |
| `MB_GetVisualiserInformation`          | Get visualizer info                  |
| `MB_ShowVisualiser`                    | Show visualizer                      |
| `MB_GetPluginViewInformation`          | Get plugin view info                 |
| `MB_ShowPluginView`                    | Show plugin view                     |
| `MB_UninstallPlugin`                   | Uninstall a plugin                   |
| `MB_SetPanelScrollableArea`            | Set scrollable area                  |

#### 2. Setting_* - Settings (10 methods)

| Method                              | Description                |
| ----------------------------------- | -------------------------- |
| `Setting_GetPersistentStoragePath`  | Get plugin data folder     |
| `Setting_GetSkin`                   | Get current skin name      |
| `Setting_GetSkinElementColour`      | Get skin color             |
| `Setting_IsWindowBordersSkinned`    | Check if borders skinned   |
| `Setting_GetFieldName`              | Get display name for field |
| `Setting_GetDefaultFont`            | Get default UI font        |
| `Setting_GetDataType`               | Get data type for field    |
| `Setting_GetLastFmUserId`           | Get Last.fm username       |
| `Setting_GetWebProxy`               | Get proxy settings         |
| `Setting_GetValue`                  | Get setting value by ID    |
| `Setting_GetFileConvertCommandLine` | Get conversion command     |

#### 3. Library_* - Library Operations (25 methods)

| Method                             | Description                                  |
| ---------------------------------- | -------------------------------------------- |
| `Library_GetFileProperty`          | Get file property (URL, size, bitrate, etc.) |
| `Library_GetFileTag`               | Get single metadata tag                      |
| `Library_GetFileTags`              | Get multiple tags at once                    |
| `Library_SetFileTag`               | Set metadata tag                             |
| `Library_CommitTagsToFile`         | Write tags to file                           |
| `Library_GetLyrics`                | Get lyrics                                   |
| `Library_GetArtwork`               | Get artwork (obsolete)                       |
| `Library_GetArtworkEx`             | Get artwork with location info               |
| `Library_SetArtworkEx`             | Set artwork                                  |
| `Library_GetArtworkUrl`            | Get artwork URL                              |
| `Library_GetArtistPicture`         | Get artist image                             |
| `Library_GetArtistPictureUrls`     | Get artist image URLs                        |
| `Library_GetArtistPictureThumb`    | Get artist thumbnail                         |
| `Library_QueryFiles`               | Query library files                          |
| `Library_QueryGetNextFile`         | Get next query result                        |
| `Library_QueryFilesEx`             | Query files (returns array)                  |
| `Library_QuerySimilarArtists`      | Find similar artists                         |
| `Library_QueryLookupTable`         | Query lookup table                           |
| `Library_QueryGetLookupTableValue` | Get lookup value                             |
| `Library_GetDevicePersistentId`    | Get device ID                                |
| `Library_SetDevicePersistentId`    | Set device ID                                |
| `Library_FindDevicePersistentId`   | Find by device ID                            |
| `Library_AddFileToLibrary`         | Add file to library                          |
| `Library_GetSyncDelta`             | Get changes since date                       |

#### 4. Player_* - Player Control (30 methods)

| Method                              | Description                |
| ----------------------------------- | -------------------------- |
| `Player_GetPosition`                | Get playback position (ms) |
| `Player_SetPosition`                | Seek to position           |
| `Player_GetPlayState`               | Get play/pause/stop state  |
| `Player_PlayPause`                  | Toggle play/pause          |
| `Player_Stop`                       | Stop playback              |
| `Player_StopAfterCurrent`           | Stop after current track   |
| `Player_PlayPreviousTrack`          | Previous track             |
| `Player_PlayNextTrack`              | Next track                 |
| `Player_PlayPreviousAlbum`          | Previous album             |
| `Player_PlayNextAlbum`              | Next album                 |
| `Player_StartAutoDj`                | Start AutoDJ               |
| `Player_EndAutoDj`                  | Stop AutoDJ                |
| `Player_GetVolume`                  | Get volume (0.0-1.0)       |
| `Player_SetVolume`                  | Set volume                 |
| `Player_GetMute`                    | Get mute state             |
| `Player_SetMute`                    | Set mute                   |
| `Player_GetShuffle`                 | Get shuffle state          |
| `Player_SetShuffle`                 | Set shuffle                |
| `Player_GetRepeat`                  | Get repeat mode            |
| `Player_SetRepeat`                  | Set repeat mode            |
| `Player_GetEqualiserEnabled`        | Get EQ state               |
| `Player_SetEqualiserEnabled`        | Set EQ state               |
| `Player_GetDspEnabled`              | Get DSP state              |
| `Player_SetDspEnabled`              | Set DSP state              |
| `Player_GetScrobbleEnabled`         | Get scrobble state         |
| `Player_SetScrobbleEnabled`         | Set scrobble               |
| `Player_ShowEqualiser`              | Show EQ window             |
| `Player_GetAutoDjEnabled`           | Check AutoDJ active        |
| `Player_GetStopAfterCurrentEnabled` | Check stop after current   |
| `Player_GetCrossfade`               | Get crossfade state        |
| `Player_SetCrossfade`               | Set crossfade              |
| `Player_GetReplayGainMode`          | Get ReplayGain mode        |
| `Player_SetReplayGainMode`          | Set ReplayGain mode        |
| `Player_QueueRandomTracks`          | Queue random tracks        |
| `Player_GetButtonEnabled`           | Check if button enabled    |
| `Player_GetShowTimeRemaining`       | Check time display mode    |
| `Player_GetShowRatingTrack`         | Check rating display       |
| `Player_GetShowRatingLove`          | Check love display         |
| `Player_OpenStreamHandle`           | Open stream for DSP        |
| `Player_UpdatePlayStatistics`       | Update play/skip count     |
| `Player_GetOutputDevices`           | Get audio devices          |
| `Player_SetOutputDevice`            | Set audio device           |

#### 5. NowPlaying_* - Current Track (15 methods)

| Method                                | Description                |
| ------------------------------------- | -------------------------- |
| `NowPlaying_GetFileUrl`               | Get current track path     |
| `NowPlaying_GetDuration`              | Get duration (ms)          |
| `NowPlaying_GetFileProperty`          | Get file property          |
| `NowPlaying_GetFileTag`               | Get tag value              |
| `NowPlaying_GetFileTags`              | Get multiple tags          |
| `NowPlaying_GetLyrics`                | Get lyrics                 |
| `NowPlaying_GetArtwork`               | Get artwork                |
| `NowPlaying_GetArtworkUrl`            | Get artwork URL            |
| `NowPlaying_GetDownloadedArtwork`     | Get downloaded artwork     |
| `NowPlaying_GetDownloadedArtworkUrl`  | Get downloaded artwork URL |
| `NowPlaying_GetDownloadedLyrics`      | Get downloaded lyrics      |
| `NowPlaying_GetArtistPicture`         | Get artist image           |
| `NowPlaying_GetArtistPictureUrls`     | Get artist image URLs      |
| `NowPlaying_GetArtistPictureThumb`    | Get artist thumbnail       |
| `NowPlaying_IsSoundtrack`             | Check if soundtrack        |
| `NowPlaying_GetSoundtrackPictureUrls` | Get soundtrack images      |
| `NowPlaying_GetSpectrumData`          | Get FFT spectrum data      |
| `NowPlaying_GetSoundGraph`            | Get waveform data          |
| `NowPlaying_GetSoundGraphEx`          | Get waveform with peaks    |

#### 6. NowPlayingList_* - Queue (15 methods)

| Method                                | Description                 |
| ------------------------------------- | --------------------------- |
| `NowPlayingList_Clear`                | Clear queue                 |
| `NowPlayingList_QueryFiles`           | Query queue files           |
| `NowPlayingList_QueryGetNextFile`     | Get next queue file         |
| `NowPlayingList_QueryFilesEx`         | Query queue (returns array) |
| `NowPlayingList_PlayNow`              | Play file immediately       |
| `NowPlayingList_QueueNext`            | Queue as next track         |
| `NowPlayingList_QueueLast`            | Queue at end                |
| `NowPlayingList_QueueFilesNext`       | Queue multiple next         |
| `NowPlayingList_QueueFilesLast`       | Queue multiple at end       |
| `NowPlayingList_PlayLibraryShuffled`  | Play library shuffled       |
| `NowPlayingList_GetCurrentIndex`      | Get current index           |
| `NowPlayingList_GetNextIndex`         | Get next index              |
| `NowPlayingList_GetListFileUrl`       | Get URL by index            |
| `NowPlayingList_GetFileProperty`      | Get property by index       |
| `NowPlayingList_GetFileTag`           | Get tag by index            |
| `NowPlayingList_GetFileTags`          | Get tags by index           |
| `NowPlayingList_IsAnyPriorTracks`     | Check for prior tracks      |
| `NowPlayingList_IsAnyFollowingTracks` | Check for following tracks  |
| `NowPlayingList_RemoveAt`             | Remove track at index       |
| `NowPlayingList_MoveFiles`            | Reorder tracks              |

#### 7. Playlist_* - Playlist Management (15 methods)

| Method                          | Description                 |
| ------------------------------- | --------------------------- |
| `Playlist_QueryPlaylists`       | Start playlist query        |
| `Playlist_QueryGetNextPlaylist` | Get next playlist           |
| `Playlist_GetName`              | Get playlist name           |
| `Playlist_GetType`              | Get playlist format         |
| `Playlist_QueryFiles`           | Query playlist files        |
| `Playlist_QueryGetNextFile`     | Get next file               |
| `Playlist_QueryFilesEx`         | Query files (returns array) |
| `Playlist_CreatePlaylist`       | Create new playlist         |
| `Playlist_DeletePlaylist`       | Delete playlist             |
| `Playlist_SetFiles`             | Replace playlist files      |
| `Playlist_AppendFiles`          | Add files to playlist       |
| `Playlist_RemoveAt`             | Remove file at index        |
| `Playlist_MoveFiles`            | Reorder files               |
| `Playlist_PlayNow`              | Play playlist               |
| `Playlist_IsInList`             | Check if file in playlist   |

#### 8. Pending_* - Pending Operations (3 methods)

| Method                    | Description               |
| ------------------------- | ------------------------- |
| `Pending_GetFileUrl`      | Get pending file URL      |
| `Pending_GetFileProperty` | Get pending file property |
| `Pending_GetFileTag`      | Get pending file tag      |

#### 9. Podcasts_* - Podcast Subscriptions (5 methods)

| Method                             | Description              |
| ---------------------------------- | ------------------------ |
| `Podcasts_QuerySubscriptions`      | Query subscriptions      |
| `Podcasts_GetSubscription`         | Get subscription details |
| `Podcasts_GetSubscriptionArtwork`  | Get subscription artwork |
| `Podcasts_GetSubscriptionEpisodes` | Get episode list         |
| `Podcasts_GetSubscriptionEpisode`  | Get episode details      |

#### 10. Sync_* - Device Sync (4 methods)

| Method                 | Description            |
| ---------------------- | ---------------------- |
| `Sync_FileStart`       | Notify sync file start |
| `Sync_FileEnd`         | Notify sync file end   |
| `Sync_FileDeleteStart` | Notify delete start    |
| `Sync_FileDeleteEnd`   | Notify delete end      |

---

## Enums Reference

### FilePropertyType (16 values)

| Value | Name                | Description      |
| ----- | ------------------- | ---------------- |
| 2     | Url                 | File path        |
| 4     | Kind                | File type        |
| 5     | Format              | Audio format     |
| 7     | Size                | File size        |
| 8     | Channels            | Audio channels   |
| 9     | SampleRate          | Sample rate      |
| 10    | Bitrate             | Bitrate          |
| 11    | DateModified        | Modified date    |
| 12    | DateAdded           | Added date       |
| 13    | LastPlayed          | Last played date |
| 14    | PlayCount           | Play count       |
| 15    | SkipCount           | Skip count       |
| 16    | Duration            | Duration (ms)    |
| 21    | Status              | File status      |
| 78    | NowPlayingListIndex | Queue index      |
| 94    | ReplayGainTrack     | Track gain       |
| 95    | ReplayGainAlbum     | Album gain       |

### MetaDataType (80+ values)

Core metadata tags including:

- Track info: `TrackTitle`, `Album`, `Artist`, `AlbumArtist`, `Year`, `Genre`, etc.
- Playback: `Rating`, `RatingLove`, `PlayCount`, `SkipCount`
- Organization: `Grouping`, `Mood`, `Occasion`, `Quality`
- Custom fields: `Custom1` through `Custom16`
- Virtual fields: `Virtual1` through `Virtual25`
- Sort fields: `SortTitle`, `SortAlbum`, `SortArtist`, `SortComposer`

### PlayState (5 values)

| Value | Name      |
| ----- | --------- |
| 0     | Undefined |
| 1     | Loading   |
| 3     | Playing   |
| 6     | Paused    |
| 7     | Stopped   |

### RepeatMode (3 values)

| Value | Name |
| ----- | ---- |
| 0     | None |
| 1     | All  |
| 2     | One  |

### SettingId (40 values)

Settings including:

- `PlayCountTriggerPercent` (7) - Play count threshold %
- `PlayCountTriggerSeconds` (8) - Play count threshold seconds
- `SkipCountTriggerPercent` (9) - Skip threshold %
- `SkipCountTriggerSeconds` (10) - Skip threshold seconds
- Custom web links 1-10

---

## Logging Architecture

### Log Entry Structure

```csharp
public class LogEntry
{
    public DateTime Timestamp { get; set; }
    public long SequenceNumber { get; set; }  // Unique event number
    public string Category { get; set; }      // Player, Library, Tags, Queue, etc.
    public string EventType { get; set; }     // NotificationType name
    public int EventTypeValue { get; set; }   // Numeric event ID
    public string Level { get; set; }         // Info, Warn, Error, Debug
    public string SourceFile { get; set; }    // File URL if applicable
    public string Message { get; set; }       // Human-readable message
    public Dictionary<string, object> Context { get; set; }  // Rich event data
}
```

### Context Data by Event Type

| Event Type | Context Properties |
|------------|-------------------|
| TrackChanged | Artist, Title, Album, Duration, Bitrate, Format, Path, Position |
| PlayStateChanged | PlayState, Position, ProgressPercent, Duration |
| VolumeLevelChanged | Volume, VolumePercent |
| PlayingTracksChanged | CurrentIndex, HasPriorTracks, HasFollowingTracks |
| RatingChanged | Rating, Track |
| TagsChanged | Before/After tag values |

### Log Categories

| Category   | Events                                                         |
| ---------- | -------------------------------------------------------------- |
| `Core`     | PluginStartup, ShutdownStarted, MusicBeeStarted                |
| `Player`   | TrackChanged, PlayStateChanged, Volume*, Shuffle, Repeat, etc. |
| `Queue`    | PlayingTracksChanged, NowPlayingListEnded, etc.                |
| `Library`  | FileAdded*, FileDeleted, LibrarySwitched                       |
| `Tags`     | TagsChanged, RatingChanged, PlayCountersChanged                |
| `Playlist` | PlaylistCreated, PlaylistUpdated, PlaylistDeleted              |
| `Sync`     | SynchCompleted                                                 |
| `Download` | DownloadCompleted                                              |
| `UI`       | EmbedInPanel, ApplicationWindowChanged                         |
| `System`   | ScreenSaverActivating                                          |

### Log Output Formats

| File | Format | Description |
|------|--------|-------------|
| `clouseau.log` | Text | Human-readable main log with timestamps |
| `clouseau.json` | JSON | Structured JSON log with all event properties |
| `events.log` | Pipe-delimited | `timestamp\|level\|category\|event\|eventId\|message\|source` |
| `events.jsonl` | JSON Lines | One JSON object per line with ALL context data |
| `metrics.log` | CSV-like | System/process metrics snapshots |
| `performance.log` | Text | API call timing and slow call detection |

### events.jsonl Sample

```json
{"ts":"2026-01-01 14:32:01.234","level":"INFO","seq":"1234","cat":"Player","event":"TrackChanged","eventId":"1","msg":"Now playing: Dark Side - Pink Floyd","src":"C:\\Music\\song.mp3","Artist":"Pink Floyd","Title":"Dark Side","Album":"DSOTM","Duration":"234000","Bitrate":"320"}
```

All context properties are flattened into the JSON object for easy parsing with `jq`, Python, or any JSON tool.

---

## UI Components

### 1. Log Viewer Panel (Dockable)

```
┌─────────────────────────────────────────────────────────────┐
│  mb_clouseau - Inspector Log                    [⚙] [📋] [🗑] │
├─────────────────────────────────────────────────────────────┤
│ Filter: [All Categories ▼] [________] [🔍]                  │
├─────────────────────────────────────────────────────────────┤
│ 14:32:01 [Player]   TrackChanged → "Song Title - Artist"    │
│ 14:32:01 [Player]   PlayStateChanged → Playing              │
│ 14:32:05 [Queue]    PlayingTracksChanged                    │
│ 14:32:15 [Player]   VolumeLevelChanged → 0.75               │
│ 14:33:42 [Tags]     RatingChanged → 4 stars                 │
│ ...                                                          │
└─────────────────────────────────────────────────────────────┘
```

### 2. Settings Dialog

- **Subscription Filters** - Enable/disable specific event types
- **Log Level** - Verbose, Normal, Minimal
- **Output Options** - File logging, panel display, auto-scroll
- **Export Settings** - Format, path, auto-export interval

### 3. API Explorer (Optional)

Interactive tool to call any MusicBee API method and see results.

---

## Configuration

### Settings File

Location: `%AppData%\MusicBee\mb_clouseau\settings.json`

```json
{
  "logLevel": "verbose",
  "enabledCategories": ["Player", "Library", "Tags", "Queue", "Playlist"],
  "fileLogging": true,
  "maxLogSizeMB": 10,
  "autoScroll": true,
  "timestampFormat": "HH:mm:ss.fff",
  "showSourceFile": true,
  "exportFormat": "json"
}
```

---

## Current Features

### Event Logging
- All 40 MusicBee notification types captured
- Rich context with 30+ fields per event (track metadata, playback state, audio properties)
- Multiple log formats: text, JSON, JSON Lines, CSV
- Ring buffer with configurable capacity (default 10,000 entries)
- Category and level filtering
- Regex search support

### System Metrics
- CPU usage (system-wide and per-core)
- Memory (physical, virtual, available, working set)
- Disk I/O (read/write throughput, queue depth)
- Network activity (bytes in/out, connections)

### Process Metrics
- MusicBee memory usage (private bytes, working set)
- Handle counts with leak detection (GDI, USER, Kernel breakdown)
- Thread count and CPU time
- I/O operations

### CLR/.NET Internals
- GC collection counts (Gen 0, 1, 2)
- % Time in GC
- Heap sizes (Gen 0/1/2, Large Object Heap)
- GC pressure detection with automatic alerts

### API Interception
- Wraps 60+ MusicBee API delegates
- Call timing with min/max/average statistics
- Slow call detection (configurable thresholds)
- Anomaly detection (rapid-fire calls, high error rates)

### UI Components
- Dockable panel with live event display
- Pop-out log viewer window
- Dashboard with Overview, Events, Settings, Diagnostics, Plugins tabs
- Settings dialog with tabbed configuration

### Export & Analysis
- JSON, CSV, Text export formats
- State dumps (JSON snapshots of complete application state)
- Plugin discovery and inspection

## Planned Features

### API Explorer
- Interactive API testing UI
- Method documentation inline
- Response viewer

### Visualization
- Event timeline charts
- Metrics graphs (CPU, memory over time)
- GC activity visualization

### Advanced
- Remote log streaming
- Plugin activity correlation
- Historical data comparison

---

## Case Study: Cross-Thread Panel Creation

This case study demonstrates how Clouseau's own logging helped diagnose a subtle UI bug.

### The Problem

The Clouseau dockable panel appeared blank on MusicBee startup. The header (Clear, Dashboard, Settings buttons) was visible, but the ListBox showing events was empty. Toggling the panel off/on would make it work.

### Initial Debugging Attempts

Several symptom-based fixes were tried without success:
- Deferred control creation with `Application.Idle`
- `VisibleChanged`/`ParentChanged` handlers with forced refresh
- `HandleCreated` event for timer initialization
- `BeginInvoke` for deferred updates
- Delayed timer refresh (1.5 seconds)
- Switching from owner-draw to standard ListBox

### Using the Logs

The breakthrough came from checking Clouseau's own error log:

```
%AppData%\MusicBee\mb_clouseau\logs\errors.log
```

```
2026-01-01 03:38:13|ERROR|Clouseau.UI|Failed to create dockable panel
System.ArgumentException: Controls created on one thread cannot be parented
to a control on a different thread.
   at System.Windows.Forms.Control.ControlCollection.Add(Control value)
   at MusicBeePlugin.Plugin.OnDockablePanelCreated(Control panel)
```

### Root Cause

MusicBee calls `OnDockablePanelCreated` from a **background thread** during startup, but WinForms controls must be created on the **UI thread**. The exception was caught and logged, but the panel appeared blank because no controls were added.

### The Fix

```csharp
public int OnDockablePanelCreated(Control panel)
{
    if (!_initialized) InitializePlugin();

    // Marshal control creation to UI thread if needed
    if (panel.InvokeRequired)
    {
        panel.Invoke(new Action(() => CreatePanelControls(panel)));
    }
    else
    {
        CreatePanelControls(panel);
    }

    return Convert.ToInt32(250 * dpiScaling);
}
```

### Key Takeaways

1. **Check the logs first** - `errors.log` captured the exact exception with stack trace
2. **Log file locations:**
   - `errors.log` - Exceptions and errors
   - `clouseau.log` - General plugin activity
   - `events.log` - MusicBee notification events
3. **Cross-thread exceptions in WinForms** - Use `InvokeRequired` + `Invoke` to marshal to UI thread
4. **Silent failures** - Try/catch logged the error but gave no visual indication
5. **Eat your own dogfood** - Using Clouseau to debug Clouseau proved the value of comprehensive logging

---

## Building

```bash
# Debug build (verbose logging enabled)
dotnet build mb_clouseau.csproj -c Debug

# Release build
dotnet build mb_clouseau.csproj -c Release
```

Output: `bin/{Debug|Release}/net48/mb_clouseau.dll`

---

## Testing

1. Copy DLL to MusicBee Plugins folder
2. Restart MusicBee
3. Enable plugin in Preferences > Plugins
4. View dockable panel (View > Arrange Panels)
5. Perform actions and observe logged events

---

*Last updated: 2026-01-01*
