Skip to content

Core Concepts, Explained

Below is a list of concepts and an explanation of how they're used in Tomato.

Audio Entities

Tomato's audio entities are a core concept, and they're defined below.

Audio Asset

An audio asset is a short individual audio track (called an asset for short). Think individual advertisements, individual public service announcements, or individual station IDs.

Assets always have a name and underlying audio file (like an mp3) but they can have additional data for example when they begin and end airing.

Audio Asset Example

A short advertisement audio clip named "David's Steel Guitar Ad."

Rotator

A rotator is a collection of similar audio assets. A rotator is how you to categorize assets into a group.

While an asset can belong to more than one rotator, in practice they shouldn't.

Rotator Example

From the asset example above, you might put "David's Steel Guitar Ad" along with other short ads for musical instruments in the "Musical Instrument Ads" rotator.

Stop Set

A stop set is an ordered list of rotators. A stop set can be thought of as an entire commercial break, like what you'd hear in traditional radio. In order to play a stop set, an asset is selected at random1 from each rotator in it.

A rotator can be (and often is) in a stop set more than once. Think having station ID jingles at the start and end of a stop set, as in the example below.

Stop Set Example

If stop sets are a bit confusing to you, don't worry. Follow along.

Let's say we have the following rotators created, and we've put assets in each of them them,

flowchart TD
    sids(Station IDs\n<em>Rotator</em>)
    psas(Public Service Announcements\n<em>Rotator</em>)
    ads(Advertisements\n<em>Rotator</em>)
    sid1(S_ID_1.mp3\n<em>Asset</em>)
    sid2(S_ID_2.mp3\n<em>Asset</em>)
    sid3(S_ID_3.mp3\n<em>Asset</em>)
    sid1 --> sids
    sid2 --> sids
    sid3 --> sids
    psa1(PSA_1.mp3\n<em>Asset</em>)
    psa2(PSA_2.mp3\n<em>Asset</em>)
    psa1 --> psas
    psa2 --> psas
    ad1(AD_1.mp3\n<em>Asset</em>)
    ad2(AD_2.mp3\n<em>Asset</em>)
    ad3(AD_3.mp3\n<em>Asset</em>)
    ad1 --> ads
    ad2 --> ads
    ad3 --> ads

Then we have an "Evening Stop Set", which contains this an ordered list of five rotators, shown here,

flowchart RL
    stopset(Evening Stop Set)
    subgraph "Rotators in stop set"
        direction TB
        rotator1(1. Station IDs\n<em>Rotator</em>)
        rotator2(2. Advertisements\n<em>Rotator</em>)
        rotator3(3. Advertisements\n<em>Rotator</em>\n<strong><small>Repetition Allowed!</small></strong>)
        rotator4(4. Public Service Announcements\n<em>Rotator</em>)
        rotator5(5. Station IDs\n<em>Rotator</em>)
    end
    rotator1 --- stopset
    rotator2 --- stopset
    rotator3 --- stopset
    rotator4 --- stopset
    rotator5 --- stopset

Then, when an "Evening Stop Set" is generated and played by Tomato during a commercial break, here's what's played.

flowchart LR
    subgraph "Rotators in stop set"
        direction TB
        rotator1(1. Station IDs\n<em>Rotator</em>)
        rotator2(2. Advertisements\n<em>Rotator</em>)
        rotator3(3. Advertisements\n<em>Rotator</em>)
        rotator4(4. Public Service Announcements\n<em>Rotator</em>)
        rotator5(5. Station IDs\n<em>Rotator</em>)
    end
    stopset(Evening Stop Set)
    subgraph "Assets played (generated)"
        direction TB
        asset1(S_ID_2.mp3\n<em>Asset</em>)
        asset2(AD_3.mp3\n<em>Asset</em>)
        asset3(AD_1.mp3\n<em>Asset</em>)
        asset4(PSA_2.mp3\n<em>Asset</em>)
        asset5(S_ID_1.mp3\n<em>Asset</em>)
    end
    rotator1 --- stopset
    rotator2 --- stopset
    rotator3 --- stopset
    rotator4 --- stopset
    rotator5 --- stopset
    asset1 -- randomly\nselected\nfrom rotator --- rotator1
    asset2 -- randomly\nselected\nfrom rotator --- rotator2
    asset3 -- randomly\nselected\nfrom rotator --- rotator3
    asset4 -- randomly\nselected\nfrom rotator --- rotator4
    asset5 -- randomly\nselected\nfrom rotator --- rotator5

Stop sets and generated stop sets

Note, the above is a so-called "generated" stop set. It's an actual stop set that is played in the desktop client. We refer to these as generated because Tomato picks (or generates) individual assets to play.

Relationship Diagram

Here's a simple relationship diagram for the entities described above.

flowchart RL
    stopset{Stop Sets}
    rotator{Rotators}
    asset{Audio Assets}
    rotator -- "many-to-many\nrelationship (ordered list)" --> stopset
    asset -- "many-to-many\nrelationship (set)" --> rotator

Wait Interval

Tomato's wait interval is how long the desktop app should wait before notifying the user that a stop set is due to be played.

Weight

Weight (or random weight) is how likely random selection of an item occurs, when compared to all other items of the same type. The default weight of an item with always \(1\) unless modified.

Random weight is used when Tomato selects both assets and stop sets.

The likeliness (or "chance") of an item \(x\) being selected is calculated as follows,

\[ x_{\text{chance}} = \frac{x_{\text{weight}}}{[\text{sum all of item weights}]} \]
Weight Example

Let's dig a little deeper. For the purposes of this example, all assets are in a rotator called "Commercials."

If an asset named "Commercial A" has a weight of \(2\) and all other assets have a weight of \(1\), then asset "Commercial A" is twice as likely to be selected when compared to all other assets.

Suppose there are 26 commercials — one for each letter of the alphabet — and they're named "Commercial A" through "Commercial Z".

We assign a weight of \(2\) to "Commercial A," or \(\text{A}_\text{weight} = 2\) and assign a weight of \(1\) to "Commercial B" through "Commercial Z," or \(\text{B}_\text{weight} = 1,\ \text{C}_\text{weight} = 1,\ \ldots,\ \text{X}_\text{weight} = 1,\ \text{Z}_\text{weight} = 1\).

We get a sum of all random weights as \(27\), illustrated below,

\[ \begin{align*} [\text{sum all of item weights}] &= \sum_{x=A}^{Z} x_\text{weight} \\ &= \text{A}_\text{weight} + (\text{B}_\text{weight} + \text{C}_\text{weight} + \ldots + \text{X}_\text{weight} + \text{Z}_\text{weight}) \\ &= 2 + (1 + 1 + \ldots + 1 + 1) \\ &= 2 + 25 \\ &= 27 \end{align*} \]

Then, per the selection equation above,

\[ \begin{align*} \text{A}_\text{chance} &= \frac{\text{A}_\text{weight}}{\text{ [sum all of item weights]}} \\ &= \frac{2}{27} \\ &= 7.4\% \end{align*} \]

So the chance we'll pick "Commercial A" with a random weight of \(2\) is \(\text{A}_\text{chance} = 7.4\%\). Similarly,

\[ \begin{align*} \text{B}_\text{chance} &= \frac{\text{B}_\text{weight}}{\text{[sum all of item weights]}} \\ &= \frac{1}{27} \\ &= 3.7\% \end{align*} \]

The chance we'll pick "Commercial B" with a weight of \(1\) is \(\text{B}_\text{chance} = 3.7\%\)

So as you can see, since \(7.4\%\) is twice \(3.7\%\), "Commercial A" with weight \(2\) is twice as likely to be played as "Commercial B" with weight \(1\).

Anti-Repeat Algorithm

Tomato tries very hard not to repeat playing the same asset too soon. There are several sets of assets that can potentially be ignored as defined below.

\(\text{ignores}_\text{soft} = [\text{assets played recently (within NO_REPEAT_ASSETS_TIME)}]\)

\(\text{ignores}_\text{medium} = [\text{assets on another generated stop set within current playlist}]\)

\(\text{ignores}_\text{hard} = [\text{assets that exist as previous entries in current generated stopset}]\)

Note

\(\text{ignores}_\text{soft}\) will be an empty set if NO_REPEAT_ASSETS_TIME = 0

So \(\text{ignores}_\text{soft}\) are stuff that's played recently, \(\text{ignores}_\text{medium}\) are stuff that's going to play later in the playlist, and \(\text{ignores}_\text{hard}\) are assets in the current stopset.

Suppose Tomato is ready to select an asset from a rotator. Tomato will attempt to ignore assets by trying to ignore them using the priority order as defined below. That's to say, it'll go through several tries to ignore assets. If Tomato can't select an asset, it'll proceed to the next try.

Starting with the set of all eligible assets in a rotator (enabled and valid air dates),

  • Try #1: Ignore all assets in \(\text{ignores}_\text{soft} \cup \text{ignores}_\text{medium} \cup \text{ignores}_\text{hard}\)
  • Try #2: Ignore all assets in \(\text{ignores}_\text{medium} \cup \text{ignores}_\text{hard}\)
  • Try #3: Ignore all assets in \(\text{ignores}_\text{hard}\)
  • Try #4 (only if ALLOW_REPEATS_IN_STOPSET = True): Choose an asset from complete set of eligible assets
  • Fail (no asset selected).

  1. The random selection process can be biased by random weight and the anti-repeat algorithm