Broadcast Automation Software Suite — Project Plan & Status — Last updated: 2026-03-10 (v0.2.0)
move() and setGeometry() calls for floating docks had zero bounds checking, allowing docks to be positioned with negative Y coordinates (above the canvas viewport) where the title bar becomes inaccessible.qBound(0, pos, canvas->size() - dock->size())startFloating=true bounded to canvas dimensionsonModuleFloatRequested) position clamped after mapFromGlobal()applyLayoutSnapshot() restored floating positions boundedrestoreLayout() from QSettings positions boundedmoveLeft(0), moveTop(0), setRight(pw), setBottom(ph)restoreLayout(), saved x/y positions were clamped via qBound(0, x, max(0, canvasWidth - w)). But m_canvas->width() is 0 during loadSavedSurfaces() (MainWindow not yet shown), so max(0, 0 - w) = 0 collapsed ALL positions to (0,0).QTimer::singleShot(0/100/500ms) for applying column splitter sizes after first show was timing-dependent and failed for non-visible panels (other tabs in QStackedWidget where splitter width stays 0).addModule(mod, false) creates one column per module during session restore. saveLayout() now records per-column moduleIds, and restoreLayout() rebuilds correct groupings.restoreAllLayouts() ran AFTER QSettings restore, overwriting properly-restored layouts with stale YAML data. Removed.restoreLayout() now calls found->move(x, y) without clamping when canvas has zero dimensions. New clampFloatingDocks() method runs once when the canvas first receives QEvent::Resize with valid dimensions, properly constraining docks to canvas bounds.QTimer::singleShot with event filter on m_splitter for QEvent::Resize — calls applyPendingSizes() deterministically when the splitter first gets a non-zero width.saveLayout() saves per-column moduleIds; restoreLayout() tears down one-per-module default columns, recreates saved groupings using QMultiMap for duplicate IDs.IcyMetadata (artist, title, album, genre, year, BPM) via buildMetaFromDeck() lambda in MainWindow. Previously only Deck A was wired and only sent the filename as StreamTitle.MetadataModule::metadataChanged now properly connected to EncoderModule::onMetadataUpdate(), completing the metadata push pipeline to all active encoder slots.DeckPlayer::loadFile() now extracts genre and year from TagLib tags (tag->genre().to8Bit(true), tag->year()), exposed via tagGenre() and tagYear() accessors.EncoderVuPanel widget: hi-res vertical L/R bars per encoder slot, measuring real PCM peak levels from EncoderSlot::peakL/R() atomics. 20fps refresh, broadcast-standard ballistics (fast attack, slow release), 3-zone coloring (green/amber/red), peak-hold ticks. Diagnostic header: "VU" (green=signal), "VU --" (gray=receiving but silent), "VU OFF" (red=not in audio chain).DeckPlayer::processBlock() and processStreamBlock() now measure std::abs(sL * finalGain) instead of pre-gain std::abs(sL). VU meters on both decks and encoders now correctly respond to volume fader, crossfader, and mute.DeckInlineMeter completely rewritten: smooth continuous-fill QLinearGradient bars replacing 30-segment chunky LEDs. 30fps refresh, 3D depth with darker-edge gradients, 1px highlight shine, clip indicator at 0dBFS, tighter 36px layout. Matches modern broadcast software aesthetics.fabsf() instead of std::abs() to avoid MSVC resolving std::abs(float) to the integer overload.#include "Module.moc" pattern.wireChurchModules() in MainWindow scans all surfaces via qobject_cast and wires: GraphicsEngine→visual modules, SwitchCaster→all source modules, StageMon→content modules, ServiceRunner→coordination modulescom.mcaster1.database) — New surface-loadable module providing connection status display, table browser, backup/restore operations, and database initialization. Allows per-surface database management directly from the surface UI.PostgreSQL to DbServerEntry::Backend enum alongside SQLite and MySQL. Full support with backendDisplayName(), defaultPort(), and isNetworked() for all three backends.surfaceDb/<schema>/serverId and surfaceDb/<schema>/schemaName QSettings keys.executeQuery() and tableNames() pure virtual methods to the IDatabase interface, enabling generic SQL execution and schema introspection from any backend.GENERATED BY DEFAULT AS IDENTITY (Firebird 3+), dual-mode connection (embedded .fdb files or network host:port:database). Conditional: #ifdef M1_HAS_FIREBIRD with stubs. Custom cmake/FindFirebird.cmake.SQLDriversW() (Driver 18 → 17 → 13 → Native Client → legacy). Works with all editions: Express, Developer, Standard, Enterprise, Azure SQL, LocalDB. NVARCHAR for Unicode, IDENTITY(1,1), SCOPE_IDENTITY(), INFORMATION_SCHEMA.TABLES. Conditional: #ifdef M1_HAS_MSSQL (always set on WIN32 — uses odbc32.lib from Windows SDK).tests/test_databases.cpp: 77 tests across all 5 backends — driver registration, SQL dialect verification, full CRUD cycles (insert/loadAll/pathExists/update/delete/executeQuery), DatabaseFactory integration. Uses fprintf(stdout) for Windows console output.DbServerRegistry now inherits QObject — emits serverListChanged(), serverChanged(id), surfaceAssignmentChanged(surfaceName) signals on all CRUD operationsSurfaceDbContext::createConnection() fully implemented via DatabaseFactory::create() — handles SQLite file paths, networked backends, driver availability checksPreferencesDialog emits surfaceDbAssignmentChanged(surfaceName) when server combo or schema name changes — updates push instantlyMainWindow::pushDbContextToSurface(sw) scans all modules via dynamic_cast<IDbAwareModule*> and calls setSurfaceDbContext()com.mcaster1.auxdeck) — Custom auxiliary deck with per-deck audio device routing. Each instance gets an independent custom name, AIR OUT device, CUE OUT device, volume, VU meters, and full transport controls (load/play/pause/stop/CUE). Mcaster1AudioPipe virtual devices appear automatically in device lists. Users can add multiple AUX Decks per surface for dedicated playback channels (jingles, interviews, background music).C:\Users\USERNAME\Mcaster1\Mcaster1AudioPipes\Mcaster1AudioPipe.exe), launch button, and refresh pipe devices button. Virtual audio pipe devices appear in all PortAudio device dropdowns.cmake/AutoBuildNumber.cmake increments a persistent BUILD_NUMBER file on each CMake configure. version.h.in now exposes MCASTER1STUDIO_BUILD_NUMBER and MCASTER1STUDIO_VERSION_FULL (e.g. "0.3.0.42").installer/installer.nsi (406 lines): installs to C:\Users\USERNAME\Mcaster1\Mcaster1Studio (no admin required), copies all runtime files (Qt DLLs, vcpkg DLLs, themes, plugins), creates Desktop + Start Menu shortcuts + Getting Started link + Uninstaller. installer/build-installer.bat automates the full build-and-package workflow.TestDatabases — 77 tests across all 5 database backendsTestCore — ModuleRegistry, SqlDialect, DbServerEntry, SurfaceConfigTestModules — 18 broadcast module construction + identity + widget testsTestChurch — 12 church module construction + identity verificationTestPodcast — 13 podcast module construction + identity verificationTestAuxDeck — AuxDeckModule CRUD, device routing, state persistencedocs/GettingStarted.html (53KB): comprehensive 10-section first-run walkthrough covering surfaces, modules, audio setup, broadcasting, church services, podcasting, and AudioPipe.auxdeck.svg (indigo gradient, AUX text + speaker waves) matching project icon conventions.QThreadPool instances, computes thread budget based on QThread::idealThreadCount(), and manages CPU core affinity via SetThreadAffinityMask on Windows.tasksSubmitted, tasksCompleted, pendingTaskCount, peakQueueDepth. Created in SurfaceWidget constructor, destroyed in destructor.IDbAwareModule injection pattern. MainWindow calls pushThreadPoolToSurface() to inject pools via dynamic_cast<IThreadPoolAware*>.m_pool->submitTask() for heavy work with synchronous fallback.QList<PoolHealth> threadPools, totalWorkerThreads, availableCores, affinityEnabled for real-time pool monitoring.session/v2/<si>/sub/<pi>/...) that remain valid after master surface tab renames. Replaces the old name-based Surface/<name>/ keys that broke on rename.QTimer fires saveAllSessionState() after any dock created/destroyed, sub-tab added/removed/renamed/recolored, or master tab renamed. No user action required.targetPanel->addModule(mod) directly rather than sw->addModule(), avoiding the m_stack->currentIndex() race condition where the stack index is set before the new panel is inserted.customName and tabLabel are stored; on restore the tab label is applied so renamed surfaces ("GRR Broadcast" etc.) reload correctly.SubSurfaceTabChip::mouseDoubleClickEvent now emits renameRequested, triggering the same QInputDialog as the right-click "Rename..." action.SurfaceWidget::moduleLayoutChanged() — New signal emitted from connectPanel() on dockCreated / dockDestroyed; wired to scheduleAutoSave() by connectSurfaceAutoSave().wake() transitions Sleep → Connecting.EncoderDsp runs in encoder thread: 10-band biquad EQ (RBJ IIR, 6 presets: flat/broadcast/spoken_word/classic_rock/country/modern_rock), feedforward AGC with hard limiter, PTT duck (50ms linear ramp).EncoderListWidget: QTableWidget showing all slots with 2s auto-refresh. Status color-coded: green=LIVE, amber=CONNECTING/RECONNECTING, blue=STARTING, red=ERROR, gray=SLEEP/IDLE. Double-click → config dialog.EncoderConfigDialog: Basic (server/codec/source), DSP (EQ/AGC), ICY 2.2 (DNAS only, 6 groups), Archive (WAV/MP3), Stats (live read-only)./admin/stats XML (Icecast2) or /admin/mcaster1stats (DNAS) every 15s. Deduplication: one TCP fetch per unique host:port, distributed to all mounts. Emits statsUpdated.EncoderModule::setPttActive(bool) forwards to all slots with pttDuckEnabled=true. Wired from PTTModule::stateChanged in MainWindow.ModuleRegistry.h exposes availableModules(), availableEffects(), createPluginModule(), createPluginEffect(). Centralized canonical module list replaces 3 hardcoded kModules arrays in SurfaceTabBar, SubSurfaceTabBar, and CustomSurfaceDialog.IModuleHost.h defines the host services contract: logging, version queries, audio engine params, plugins directory. Passed to mcaster1_create_module().M1::initModuleRegistry() called in MainWindow constructor before module factory init. Scans plugins/modules/ and plugins/effects/ for DLLs, validates API version, logs results.M1::availableModules() which returns built-in modules + discovered plugin modules.MainWindow::createModule() first checks built-in factory map, then falls back to M1::createPluginModule() for third-party DLL modules.plugins/modules/ via CMake post-build.plugins/effects/ via CMake post-build.SDK/docs/DeveloperGuide.md covers plugin types, C ABI exports, IModule/IEffectUnit interfaces, AudioBuffer, IModuleHost, CMake template, RT safety rules, and EventBus communication.com.mcaster1.cartwall added to built-in module list and PLANNING.html module catalog.DeckPlayer::loadUrl() via StreamReader (FFmpeg avformat_open_input() with icy=1 + Icy-Version: 2.2 headers, SPSC ring buffer).setStyleSheet() calls from EncoderListWidget.cpp, EncoderConfigDialog.cpp, PTTWidget.cpp. Replaced with setObjectName() + QSS selectors in all three theme files.light.qss and every inline style in DeckWidget.cpp.MouseButtonRelease now sets State::Off (was incorrectly setting State::Armed, making the mic indicator appear permanently on after creating a DJ or Broadcast surface).m_subTabBar + m_stack setCurrentIndex) before calling panel->focusDock(). Previously only focused the dock without switching pages.feDropShadow for depth, white icon glyphs with shadow. New eject.svg added for the unload/eject button.#PTTSectionLabel, #PTTHintLabel, #PTTKnobLabel, #EncoderLiveLabel rules to all three theme files with theme-appropriate colors.SurfaceWidget::addModuleRequested was emitted but never connected to MainWindow. Fixed: now wires the signal → createModule() + addModule(startFloating=true).com.mcaster1.queue + com.mcaster1.clock added to both sub-surface chip and master tab right-click menus.AppRibbon::onAirToggled signal needs to connect to EncoderModule active-slot state; also reflect per-surface ON AIR✓ Resolved in Phase E: Sub-surface session save/restore (v2 auto-save + stable keys + rename-proof restore)
✓ Resolved in Phase G: Theme dark-bleed from Encoder/PTT modules; DeckWidget font sizes; PTT mic always-on; SurfaceTray navigation to correct sub-tab
| Component | Choice |
|---|---|
| Language | C++20 · MSVC v143 · /MP parallel build |
| UI | Qt 6.8.3 LTS (Widgets + Network + Multimedia + Svg) |
| Build | CMake 3.28+ · VS2022 generator · vcpkg x64-windows |
| Audio I/O | PortAudio 19.7 + ASIO SDK (vcpkg portaudio[asio]) |
| Codecs | libopusenc · libvorbis · libFLAC · fdk-aac · LAME (external) |
| Video | FFmpeg 8.0.1 |
| Database | SQLite3 · MySQL/MariaDB · PostgreSQL · Firebird · SQL Server (ODBC) — 5-backend architecture via DatabaseFactory |
| Tag Reading | TagLib 2.1.1 (compiled /EHa) |
| HTTP | Qt6::Network (weather, ICY) · libcurl 8.18 (monitor, metadata push) |
| Plugin ABI | DLL C ABI factory functions |
C:/Qt/6.8.3/msvc2022_64.Qt6Config.cmake.
cmake -B build -G "Visual Studio 17 2022" -A x64 \ -DCMAKE_TOOLCHAIN_FILE=.../vcpkg/scripts/buildsystems/vcpkg.cmake \ -DVCPKG_TARGET_TRIPLET=x64-windows \ -DCMAKE_PREFIX_PATH="C:/Qt/6.8.3/msvc2022_64"
cmake --build build --config Debugbuild\bin\Debug\Mcaster1Studio.exe
| Surface | Intent | Default Modules |
|---|---|---|
| Alpha | Primary radio broadcast | VU Meter · Deck A+B · Media Library · Encoder · Playlist |
| Beta | Secondary / backup broadcast | Deck A+B · Encoder · VU Meter |
| Company | Corporate streaming | Playlist · Encoder · Metadata · Monitor |
| DJ | Live DJ performance | Deck A+B · Crossfader · Effects Rack · VU Meter |
| Entertainment | TV / Video automation | Video · Encoder · Playlist |
| Social | Socialcasting + RTMP | Encoder · Metadata · Monitor |
| Podcast | Professional podcast production | PTT · Podcast · Effects Rack · Media Library + 13 podcast modules (Mixer · PTT · Recorder · Soundboard · FX · Editor · Encode · Transcribe · ShowNotes · RSS · Publisher · Analytics · Remote) |
| Church | Church AV + stream + podcast | PTT · Encoder · VU Meter · Video · Media Library + 12 church modules (TimerClock · Graphics · Lyrics · Scripture · Announce · TelePrompt · MediaCaster · StageMon · AudioMix · TranscribeRec · SwitchCaster · ServiceRunner) |
| Custom | User-defined | Any modules — chosen via Custom Surface dialog |
| Module | Plugin ID | Phase | Status |
|---|---|---|---|
| VUMeterModule | com.mcaster1.vumeter | 1 | ✓ Done |
| DeckModule (Combined) | com.mcaster1.deck | 2 | ✓ Done |
| DeckAModule | com.mcaster1.deck.a | 2 | ✓ Done |
| DeckBModule | com.mcaster1.deck.b | 2 | ✓ Done |
| MediaLibraryModule | com.mcaster1.library | 3 | ✓ Done |
| EncoderModule | com.mcaster1.encoder | 4 | ✓ Done |
| EffectsRackModule | com.mcaster1.effects | 5 | ✓ Done |
| MetadataModule | com.mcaster1.metadata | 6 | ✓ Done |
| PlaylistModule | com.mcaster1.playlist | 7 | ✓ Done |
| PodcastModule | com.mcaster1.podcast | 8 | ✓ Done |
| PTTModule | com.mcaster1.ptt | 8 | ✓ Done |
| VideoModule | com.mcaster1.video | 9 | ✓ Done |
| MonitorModule | com.mcaster1.monitor | 10 | ✓ Done |
| ClockModule | com.mcaster1.clock | A | ✓ Done |
| QueueModule (AutoDJ) | com.mcaster1.queue | B | ✓ Done |
| CartWallModule | com.mcaster1.cartwall | H | ✓ Done |
| Church Surface Modules | |||
| TimerClockModule | com.mcaster1.church.timerclock | CH | ✓ Done |
| GraphicsEngineModule | com.mcaster1.church.graphics | CH | ✓ Done |
| LyricsCasterModule | com.mcaster1.church.lyrics | CH | ✓ Done |
| ScriptureCasterModule | com.mcaster1.church.scripture | CH | ✓ Done |
| AnnounceCasterModule | com.mcaster1.church.announce | CH | ✓ Done |
| TelePromptModule | com.mcaster1.church.teleprompt | CH | ✓ Done |
| MediaCasterModule | com.mcaster1.church.mediacaster | CH | ✓ Done |
| StageMonModule | com.mcaster1.church.stagemon | CH | ✓ Done |
| AudioMixModule | com.mcaster1.church.audiomix | CH | ✓ Done |
| TranscribeRecModule | com.mcaster1.church.transcriberec | CH | ✓ Done |
| SwitchCasterModule | com.mcaster1.church.switchcaster | CH | ✓ Done |
| ServiceRunnerModule | com.mcaster1.church.servicerunner | CH | ✓ Done |
| Podcast Surface Modules | |||
| PodMixerModule | com.mcaster1.podcast.mixer | PD | ✓ Done |
| PodPTTModule | com.mcaster1.podcast.ptt | PD | ✓ Done |
| PodRecorderModule | com.mcaster1.podcast.recorder | PD | ✓ Done |
| PodSoundboardModule | com.mcaster1.podcast.soundboard | PD | ✓ Done |
| PodFXModule | com.mcaster1.podcast.fx | PD | ✓ Done |
| PodEditorModule | com.mcaster1.podcast.editor | PD | ✓ Done |
| PodEncodeModule | com.mcaster1.podcast.encode | PD | ✓ Done |
| PodTranscribeModule | com.mcaster1.podcast.transcribe | PD | ✓ Done |
| PodShowNotesModule | com.mcaster1.podcast.shownotes | PD | ✓ Done |
| PodRSSModule | com.mcaster1.podcast.rss | PD | ✓ Done |
| PodPublisherModule | com.mcaster1.podcast.publisher | PD | ✓ Done |
| PodAnalyticsModule | com.mcaster1.podcast.analytics | PD | ✓ Done |
| PodRemoteModule | com.mcaster1.podcast.remote | PD | ✓ Done |
| System Modules | |||
| HealthModule | com.mcaster1.health | HL | ✓ Done |
| DatabaseModule | com.mcaster1.database | DB | ✓ Done |
StreamTitle=
even when also sending ICY 2.2. Backward compatibility is mandatory.
| Server | ICY 1.x | ICY 2.2 | Method |
|---|---|---|---|
| Icecast2 | ✓ Required | ✗ N/A | HTTP GET /admin/metadata?mode=updinfo&song=… |
| Shoutcast v1/v2 | ✓ Required | ✗ N/A | In-stream at icy-metaint boundaries |
| Mcaster1DNAS | ✓ Required | ✓ Required | HTTP PUT + Icy-Version: 2.2 header |
| Group | Prefix | Key Fields |
|---|---|---|
| 1 — Station | icy2-station-* | id · name · logo · genre · url · language |
| 2 — Show | icy2-show-* | title · host · start · end · next · description |
| 3 — Track | icy2-track-* | title · artist · album · year · genre · artwork · bpm · key · isrc · mbid · label |
| 4 — DJ | icy2-dj-* | handle · name · bio · avatar · website · email |
| Group | Prefix | Key Fields |
|---|---|---|
| 5 — Social | icy2-social-* | twitter · instagram · tiktok · youtube · facebook · twitch · linktree · hashtags |
| 6 — Podcast | icy2-podcast-* | title · episode · season · feed · guid · duration · chapter |
| 7 — Broadcast | icy2-broadcast-* | mode · relay · cdn · lufs · codec · samplerate · channels |
| 8 — Content | icy2-content-* | explicit · live · type (radio|tv|podcast|socialcast) · rating |
wake() transitions Sleep → ConnectingsetPttActive(bool) forwards to EncoderDsp for duck attenuationEncoderModule::onAudioBlock() → AudioRingBuffer::write() (lock-free)EncoderDsp::process() → encodePcm() → IcyPusher::push() → TCP socket/admin/stats XML every 15s, emits statsUpdated signalSOURCE /stream HTTP/1.0 Authorization: Basic base64(source:password) Content-Type: audio/ogg (or audio/mpeg, audio/flac, audio/aac per codec) ice-name: Station Name ice-genre: Various ice-bitrate: 128 ice-public: 0 User-Agent: Mcaster1Studio/1.0.0 [Icy-Version: 2.2] ← DNAS targets only → HTTP/1.0 200 OK → [raw encoded bytestream…]
| Rule | Reason |
|---|---|
Never #include <version> or create file named VERSION | Collides with C++20 header on Windows case-insensitive FS |
TagLib: .to8Bit(true) + QString::fromStdString() | Never call .toWString() across DLL boundary |
TagLib: compile with /EHa | SEH exception safety on corrupt files |
onAudioBlock(): no Qt, no alloc, no mutex wait | Called from PortAudio RT callback — hard latency constraint |
QApplication::setStyle("Fusion") | Windows11 style ignores QSS; Fusion is required for all theming |
| ICY 1.x StreamTitle= always sent even with ICY 2.2 | Backward compat — all clients need ICY 1.x |
qt_add_executable() not add_executable() | Triggers MOC/RCC/UIC auto-generation |
Never use slots as variable/member name | Qt macro #define slots — use clockSlots, encoderSlots, etc. |
VUMeterWidget compact mode via setCompact(true) | Same class; switches rendering path + sizeHint; wire EventBus separately for compact instances |
RibbonBox drag MIME: application/x-m1-ribbon-box | Pointer as hex string; distinct from module dock MIME application/x-m1-module-dock |
| Project | Path | Relation to Studio |
|---|---|---|
| Mcaster1DNAS | ../mcaster1dnas/ | ICY 2.2 streaming server — DNAS encoder target |
| Mcaster1DSPEncoder | ../Mcaster1DSPEncoder/ | Encoder reference (LAME/Opus/AAC/FLAC patterns) |
| Mcaster1TagStack | ../Mcaster1TagStack/ | MySQL schema reference · ICY 2.2 editor reference |
| Mcaster1Castit | ../Mcaster1CastIt/ | Stats monitor reference for Phase 10 |
| Mcaster1AudioPipe | ../Mcaster1AudioPipe/ | Virtual audio routing — zero-latency pipe devices for AUX Deck + encoder chaining |