-
Notifications
You must be signed in to change notification settings - Fork 0
Scheduling Guide
This guide explains how to fill your station with programming using Grimnir Radio's scheduling system. Whether you're building an all-music station, a talk/podcast format, or a mix of webstreams and local content, this is how you make it happen.
Grimnir Radio uses a three-layer scheduling system:
┌─────────────────────────────────────────────────────┐
│ 1. CLOCK TEMPLATES (what plays each hour) │
│ Define the structure: "6am-10am = Morning Mix" │
├─────────────────────────────────────────────────────┤
│ 2. SMART BLOCKS (what content fills each slot) │
│ Define the rules: "Play Rock from the 90s, │
│ no repeats within 4 hours" │
├─────────────────────────────────────────────────────┤
│ 3. SCHEDULE ENTRIES (concrete timeline) │
│ The scheduler materializes clocks + blocks │
│ into actual entries: "13:04:22 → track.mp3" │
└─────────────────────────────────────────────────────┘
Every 30 seconds, the scheduler looks at your clock templates, runs the smart block rules, and writes concrete schedule entries into the timeline. This happens automatically — you set up the rules and the system fills the schedule.
Go to Dashboard → Media Library → Upload and upload your audio files. Each file gets analyzed automatically (duration, loudness, waveform). You can set metadata during or after upload:
- Title, Artist, Album — standard tags
- Genre — critical for smart block filtering (e.g., "Rock", "Jazz", "Podcast")
- Mood — optional vibe tag (e.g., "Upbeat", "Chill", "Intense")
- Tags — custom labels you create (e.g., "morning-friendly", "explicit", "sunday-show")
- Year, BPM, Language — additional filtering criteria
Tip: Be consistent with your genre and mood names. Smart blocks filter on exact matches, so "Rock" and "rock" are the same, but "Classic Rock" and "Rock" are different genres.
Smart blocks are rule-based content selectors. They answer the question: "Given these rules, what tracks should play?"
Go to Dashboard → Smart Blocks → New and configure:
Basic Filters (Include Rules)
| Filter | What it does | Example |
|---|---|---|
| Genre | Only pick tracks matching this genre | "Jazz" |
| Mood | Only pick tracks matching this mood | "Upbeat" |
| Artist | Only pick tracks by this artist | "Miles Davis" |
| Tags | Only pick tracks with these tags | "morning-friendly" |
| BPM Range | Only pick tracks in a tempo range | 120-140 BPM |
| Year Range | Only pick tracks from a year range | 1990-1999 |
| Language | Only pick tracks in this language | "en" |
| Text Search | Search across title/artist/album | "love" |
| Source Playlists | Only pick from specific playlists | Select playlists |
Exclude Rules — same fields, but tracks matching these are rejected.
Separation Rules — prevent repetition:
- Artist Separation: Don't repeat the same artist within X minutes (e.g., 60 min)
- Title Separation: Don't repeat the same track within X minutes (e.g., 240 min)
- Album Separation: Don't repeat tracks from the same album within X minutes
- Label Separation: Don't repeat tracks from the same label within X minutes
Weights — nudge the selection toward preferred content:
- Genre/Mood/Tag weight: Boost tracks matching a value (e.g., give "Upbeat" mood a +2.0 boost)
- New Release weight: Boost tracks uploaded within the last N days (e.g., 7 days)
Target Duration: How long the block should fill (e.g., 55 minutes for a 1-hour clock slot, leaving room for bumpers).
Example: "Daytime Rock Mix"
Include: genre = "Rock"
Exclude: explicit = true
Separation: artist 60 min, title 240 min
Weight: mood "Upbeat" +1.5, new_release 14 days +2.0
Target: 55 minutes
This produces a ~55-minute playlist of non-explicit Rock, favoring upbeat tracks and anything uploaded in the last two weeks, with no artist repeating within an hour.
Clock templates define what type of content plays during each hour of the day. Each template covers a time window (e.g., 6am-10am) and contains one or more slots.
Go to Dashboard → Clocks → New:
- Name: Descriptive label (e.g., "Morning Drive")
- Start Hour: When this clock begins (0-23, in your station's timezone)
- End Hour: When this clock ends (1-24, exclusive)
Then add slots to the clock:
| Slot Type | What it does | Key setting |
|---|---|---|
| Smart Block | Picks tracks using rules | Select which smart block |
| Playlist | Plays a specific playlist in order | Select which playlist |
| Hard Item | Plays one specific file | Select which media item |
| Webstream | Relays an internet stream | Select which webstream |
| Stopset | Commercial/underwriting break | Select playlist or media |
Each slot has:
- Position: Order within the hour (0, 1, 2...)
- Offset: When within the hour it starts (e.g., 0:00 for top of hour, 0:30 for half past)
- Duration: How long the slot lasts (optional — smart blocks use their target duration)
Example: Full-Day Music Station
| Clock | Hours | Slot | Content |
|---|---|---|---|
| Morning Drive | 6-10 | Smart Block | "Morning Mix" (upbeat pop/rock) |
| Midday | 10-14 | Smart Block | "Variety Mix" (all genres) |
| Afternoon | 14-18 | Smart Block | "Afternoon Chill" (mellow) |
| Evening | 18-22 | Smart Block | "Evening Groove" (R&B/soul) |
| Late Night | 22-2 | Smart Block | "Late Night Jazz" |
| Overnight | 2-6 | Smart Block | "Ambient Overnight" |
Once your clocks and smart blocks are set up, the scheduler automatically:
- Reads your clock templates
- Determines which clock applies to each hour
- Runs the smart block rules to pick specific tracks
- Creates concrete schedule entries in the timeline
You can see the results in Dashboard → Schedule (calendar view).
To force an immediate refresh: click Refresh Schedule or use the API:
POST /api/v1/schedule/refresh
{"station_id": "your-station-id"}
When multiple clocks overlap in time, the narrowest window wins. This lets you set up a catch-all clock and override specific hours:
Clock A: 0-24 (all day) → "General Mix" (24-hour window)
Clock B: 6-10 (morning) → "Morning Drive" (4-hour window) ← WINS 6-10
Clock C: 8-9 (breakfast) → "Breakfast Show" (1-hour window) ← WINS 8-9
At 8am, Clock C wins (narrowest). At 7am, Clock B wins. At midnight, Clock A wins.
Scenario: Your friend has a show every Sunday 1-3pm. They upload their files a day or two before.
-
Tag the show files: When uploading, set a unique tag or genre:
- Genre:
"Sunday Show"or - Tag:
"sunday-show"
- Genre:
-
Create a smart block called "Sunday Show Block":
- Include filter:
tags = "sunday-show"(orgenre = "Sunday Show") - Weight:
new_release= 7 days, weight +5.0 (heavily favor newest uploads) - Target duration: 120 minutes (2 hours)
- No separation rules needed (show files are unique each week)
- Include filter:
-
Create a recurring schedule entry (via Dashboard → Schedule):
- Source: Smart Block → "Sunday Show Block"
- Start: Sunday 1:00 PM
- End: Sunday 3:00 PM
- Recurrence: Weekly
-
Each week: Your friend uploads their files tagged
"sunday-show". The smart block automatically picks the newest ones for Sunday's slot.
If your friend wants exact control over track order:
- Create a playlist called "This Week's Sunday Show"
- Upload files and add them to the playlist in order
-
Create a recurring schedule entry:
- Source: Playlist → "This Week's Sunday Show"
- Start: Sunday 1:00 PM
- Recurrence: Weekly
- Each week: Replace the playlist contents with new files
- Create a "Sunday Afternoon" clock: hours 13-15
- Add a smart block slot pointing to the Sunday show block
- Create a separate weekday clock for the same hours (e.g., "Weekday Afternoon" 13-15)
Note: Clocks don't currently filter by day-of-week, so you'll need to use the recurring schedule entry approach (Method 1 or 2) for day-specific shows. The clock will generate entries every day; you'd override Sundays with the recurring entry, which takes priority.
To prevent your friend's show files from appearing in other smart blocks during the week:
- Use a unique genre or tag that no other smart block filters for
-
Add an exclude rule to your regular smart blocks:
exclude tags = "sunday-show" - The files will only be picked up by the "Sunday Show Block" which is only scheduled on Sundays
Podcasts work similarly to weekly shows. The key difference is that podcast episodes are typically single long files rather than a playlist of tracks.
-
Upload the episode with genre
"Podcast"and a tag like"tech-podcast" -
Create a smart block:
- Include:
tags = "tech-podcast" - Weight:
new_release= 7 days, weight +10.0 - Target duration: match your clock slot length
- Include:
- Schedule it via clock template or recurring entry
Create separate smart blocks for each podcast feed, each filtering on a different tag. Place them in different clock slots:
Clock: "Podcast Hour" (12-13)
Slot 0: Smart Block "Tech Podcast" (tags = "tech-pod", 30 min)
Slot 1: Smart Block "News Brief" (tags = "news-pod", 15 min)
Slot 2: Smart Block "Filler Music" (genre = "Ambient", 15 min)
You can schedule webstreams (internet radio relays) alongside your own media:
- Add webstreams via Dashboard → Webstreams (provide the stream URL)
- Create clock slots of type "Webstream" pointing to each stream
- Mix with smart blocks in the same day:
Clock: "Morning" (6-10) → Smart Block "Morning Mix"
Clock: "Midday" (10-12) → Webstream "BBC Radio 6 Music"
Clock: "Afternoon" (12-18) → Smart Block "Afternoon Variety"
Clock: "Evening" (18-22) → Webstream "SomaFM Drone Zone"
Clock: "Night" (22-6) → Smart Block "Late Night Ambient"
Webstream entries automatically span their clock's full time window. For HLS streams (.m3u8 URLs), metadata like track titles is polled from the stream manifest. For Icecast/SHOUTcast streams, ICY metadata is read inline.
Bumpers are short audio clips (station IDs, jingles, stingers) that play between segments.
Add a hard_item slot at a specific offset in your clock:
Clock: "Morning Drive" (6-10)
Slot 0 (offset 0:00): Smart Block "Morning Mix" (55 min)
Slot 1 (offset 0:55): Hard Item "station-id-jingle.mp3"
- Create a playlist of all your bumper/jingle files
- Use a
playlistslot with a short duration at transition points - The system picks from the playlist in order
Smart blocks support built-in interstitial insertion:
- Every N tracks: Insert an interstitial (e.g., every 4 songs, play a jingle)
- Source: Playlist or specific media item
- Per Break: How many interstitials per break (1-3)
Configure this in the smart block's advanced settings under "Interstitials".
The Dashboard → Schedule page shows a FullCalendar view of your programming:
- Week/Day/List views: Toggle between calendar layouts
- Color coding: Different source types have different colors
- Click an entry: See details including the source smart block, track list, and metadata
- Drag to reschedule: Move entries (creates override instances for recurring entries)
- Validation: The system checks for gaps and overlaps
| Source Type | What it is |
|---|---|
media |
A specific audio file (from smart block materialization or hard item) |
playlist |
A playlist (resolved to tracks at playout time) |
smart_block |
A smart block slot (materialized into individual track entries) |
webstream |
An internet stream relay |
stopset |
A commercial/underwriting break |
live |
A live DJ session |
show |
A scheduled show |
The scheduler may add warnings to entries when constraints couldn't be fully met:
| Warning | Meaning |
|---|---|
constraint_relaxed:1 |
Artist/title separation rules were dropped to find enough tracks |
constraint_relaxed:2 |
Separation and quota rules were both dropped |
constraint_relaxed:3 |
All constraints dropped (only basic filters remain) |
underfilled_target |
Ran out of matching tracks before filling the slot |
emergency_fallback |
No tracks matched at all; a random track was used |
used_fallback:<id> |
A fallback smart block was used |
If you see these regularly, it means your smart block rules are too restrictive for your library size. Either upload more content or loosen the filters.
| Field | Type | Example |
|---|---|---|
genre |
Exact match | "Rock" |
mood |
Exact match | "Chill" |
artist |
Normalized match | "The Beatles" (matches "the beatles", "THEBEATLES") |
album |
Exact match | "Abbey Road" |
title |
Exact match | "Yesterday" |
label |
Exact match | "Atlantic Records" |
language |
Exact match | "en" |
tags |
Tag UUIDs | (selected in UI) |
text_search |
Fuzzy across title/artist/album | "love" |
bpm |
Range (min-max) | 120-140 |
year |
Range (min-max) | 1990-1999 |
explicit |
Boolean | true/false |
source_playlists |
Playlist UUIDs | (selected in UI) |
| Type | Use For | Duration Behavior |
|---|---|---|
smart_block |
Music/podcast automation | Uses block's target duration or slot duration |
playlist |
Curated playlists | Plays through playlist |
hard_item |
Specific file (jingle, promo) | Uses file's actual duration |
webstream |
Internet stream relay | Spans the clock's full time window |
stopset |
Commercial breaks | Uses configured duration |
"No plans to generate" in logs
- Check that your station has at least one clock template with slots
- Verify the clock's hour window covers the current time
Schedule shows 1-minute entries for webstreams
- Make sure webstream slots don't have a
duration_msin their payload (let it auto-expand to the clock window)
Smart block keeps picking the same tracks
- Increase your separation windows (artist: 60-120 min, title: 240 min)
- Upload more content to give the engine a larger pool
"Emergency fallback" entries appearing
- Your smart block rules are too restrictive — no tracks match
- Check that your media has the correct genre/tags/mood metadata
- Check that media analysis completed successfully (
analysis_state = "complete")
Recurring show plays wrong content
- Verify the tag/genre filter is unique to the show files
- Add exclude rules to your regular smart blocks for the show's tag
- Use the
new_releaseweight to heavily favor newest uploads
Getting Started
Core Concepts
Scheduling
Deployment
Integration
Operations
Development
Reference