PAL Quick Start Guide

ugliestharrasΛογισμικό & κατασκευή λογ/κού

4 Νοε 2013 (πριν από 3 χρόνια και 9 μήνες)

216 εμφανίσεις


1

PAL Quick Start G
uide


Overview:

PAL is short for
Playlist Automation Language

and was developed to give station owners full power
over their music rotation.

The goal was that for anything a station playlist programmer could dream up
-

PAL would be able t
o achieve it.

Today hundreds of SAM Broadcaster users use the power of PAL
to completely automate major functions of their station, including switching DJ's, running two
-
for
-
Tuesday, hourly news, precise scheduled jingles and advertisements and even downl
oading content
into their rotation.


In this document we aim to quickly teach you the basics of PAL scripting and provide enough real
-
world examples to get you started.

At first PAL scripting might seem very daunting to any new user,
but just stick with i
t for a while and you will quickly realize the power you can wield in automating your
daily tasks. It is certainly worth the sweat and tears after you have overcome the initial learning curve.

And if you get stuck, the online forums are a great place to g
et help
-

as long as you do not expect
anybody to write your complete PAL script for you! You should always post your script and then ask
specific questions on what you need help with.


This guide is not a replacement for the general SAM Help and PAL docum
entation! They should
always be used as reference for more detail.

http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/

http://www.spacialaudio.com/products/sambroadcaster/help/pal/


Section 1
-

The PAL Basic L
anguage


The basics

How to

create a PAL script:

http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/PAL_Scripting____Hello
_World__Example.htm

PAL Scripting
-

"Hello World" Example

Step 1:

In SAM3 go to the menu option
Menu
-
>Window
-
>PAL scripts

and activate the P
AL script
window.

Step 2:

Click on the
Add

("+") button. This will open the script

options dialog.

Step 3:

Next to the script source

file, click on
browse

("folder") button located to the right
of the edit field. Enter the name of the script you want to lo
ad, or, if the script does not
exist, the name of the script to create. For this example,

it is "Guide.PAL". Click
OK.

(If

prompted to create the file, select yes...)

Step 4:
The
Guide.PAL

script entry will appear inside the PAL Scripts window.
Double
-
clic
k

on
the
Guide.PAL

entry to open up the
PAL IDE.


Step 5:

Inside the PAL IDE type the following text:

WriteLn(
'Hello world!'
);

Step 6:

Click on the
Run

button (or

press
[F9]

).

The BLUE line

shows which line is being executed, and

the output is written on
the right hand side of
the PAL IDE. (It

will show "Hello world!".) The status of the script in the status bar will also be
displayed in the
PAL IDE
.

Congratulations!

You have just entered the awesome world of radio automation scripting!




The PAL script
ing language was based on PASCAL, to be more exact, the OOP version of Pascal
called Delphi.

Delphi is the powerful object orientated programming language that was used to

2

develop SAM Broadcaster. Thus it was only fitting to base PAL around this powerful
language.


The basic structure of this language will be explored in the sections to follow and will form the basic
building blocks of our more advanced PAL scripts.

Note: Before we continue here you must already
know how to create a blank PAL script. See
the "PAL scripting: Hello world example"
above.


Comments

Comments are text added to
a script that is

totally ignored by the compiler. This is used to provide
more information to any human script viewer to make it easier to read and understand the logic of

the
script.

PAL supports two
different
comment
styles
:


{ This is a comment }

// This is a
nother

comment


Be on the lookout for these in the sections that follow!


Output W
indow

While developing PAL scripts
,

you will find the PAL IDE very useful. It shows

you the exact line that is
currently executing. However, many times you need to know what is going on in the actual script.
Since the PAL IDE does not support variable inspection you should use the age
-
old technique of
writing the current state of affairs

to the output window.


There are two simple commands for doing this:

WriteLn(Value);

WriteStr(Value);


The only difference between the two is that the WriteLn command will append a newline character at
the end of the string, causing the string to wrap int
o a new line in the output window.


Compare:

var T : Integer;

for T := 1 to 3 do


WriteLn('WriteLn '+IntToStr(T));


for T := 1 to 3 do


WriteStr('WriteStr '+IntToStr(T));


Variables & Constants

Variables and constants are used to give a name to a certain
data value.

Constant values can not
change during the operation of the script.

Variables however can be changed at any time during the
script.

Unlike most languages, the name of the constant or variable is not case sensitive. Thus to PAL
MyVar and MYvar

is one and the same thing.


Constant values are declared as:

const MyConstValue = ConstValue;


Examples:

const Val1 = 'This is a string value';

const Val2 = 1234;

const Val3 = 1.234;

const Val4 = false;


Varibles are declared as:

var MyVarName : vartype
= default value;


Examples:

var Val1 : String = 'This is a string value';

var Val2 : Integer = 1234;

var Val3 : Float = 1.234;

var Val4 : String;


3


As you can see
,

a default value is not required for
variables
, but it is recommended you use one where
possib
le.

The PAL language supports all major variable types like
string, integer, float, boolean,
variant and datetime.

Variables can also be
arrays

and
objects
.

Variables can get a new value
during the life of a script. Let

s look at this example:


var S :

Integer = 0;


PAL.Loop := True;

WriteLn(S);

S := S + 1;

WriteLn(S);

S := 123;

WriteLn(S);


PAL.WaitForTime('+00:00:10');


In the example above, the initial value of S is zero (0),
and then

the value is set to S + 1, which is 1+1
= 2
.
Finally
,

S is set to

123, and then we wait for 10 seconds before the script is restarted.

Each time the script restarts, the variables are cleared. Thus S will have the value of 0 again once the
script restarts.


Math


Basic integer math

var m : integer;

{Note: a & b below ar
e both of type integer}

m := a + b;

m := a
-

b;

m := a * b;

m := a div b; {Integer only; Discards remainder}

m := a mod b; Integer only; Only remainder}

m :=
-
m;


Basic float math

var m : float;

{Note: a & b below can be either of type integer or float}

m
:= a + b;

m := a
-

b;

m := a * b;

m := a / b; {Float only}

m :=
-
m;


Some other popular math functions:

m := Round(1.2345); {Rounds value to closest integer value)

m := Random; {Returns random floating point value}


Some other math functions to check out:

function Sin(a : Float):Float;

function Sinh(a : Float):Float;

function Cos(a : Float):Float;

function Cosh(a : Float):Float;

function Tan(a : Float):Float;

function Tanh(a : Float):Float;

function ArcSin(a : Float):Float;

function ArcSinh(a : Float):Float
;

function ArcCos(a : Float):Float;

function ArcCosh(a : Float):Float;

function ArcTan(a : Float):Float;

function ArcTanh(a : Float):Float;

function Cotan(a : Float):Float;

function Hypot(x : Float; y : Float):Float;


function Inc(var a : Integer; b : Inte
ger):Float;

function Abs(a : Float):Float;

function Exp(a : Float):Float;

function Ln(a : Float):Float;

function Log2(a : Float):Float;


4

function Log10(a : Float):Float;

function LogN(n : Float; x : Float):Float;


function Sqrt(v : Float):Float;

function Sq
r(v : Float):Float;

function Int(v : Float):Float;

function Frac(v : Float):Float;

function Trunc(v : Float):Float;

function Round(v : Float):Float;

function Power(base : Float; exponent : Float):Float;


function DegToRad(v : Float):Float;

function RadToDe
g(v : Float):Float;

function Max(v1 : Float; v2 : Float):Float;

function Min(v1 : Float; v2 : Float):Float;

function Pi:Float;

function Random:Float;

function RandomInt(Range : Integer):Integer;

procedure Randomize;


function RandG(mean : Float; stdDev : F
loat):Float;

function RandSeed:Integer;

function SetRandSeed(Seed:Integer);


String handling

Detailed string handling is a bit beyond the scope of this document, but I will
introduce

a few basic
techniques quickly.


Var S1 : String = 'This is my first stri
ng';

Var S2 : String = 'Another string';

Var I : Integer;

S2 := S1; {Copy a string}

S2 := S1 + ' and it rocks'; {Combine strings}

S2 := Copy(S1,2,3); {Copy only certain characters to a string. (Copy 3 characters, starting at
position 2)

WriteLn(S2);


I :=
Length(S2); {Set I to the number of characters that S2 contains}

I := Pos('my',S1); {Find the index of the first match}


Some other string functions to check out:

function IntToStr(v : Integer):String;

function StrToInt(str : String):Integer;

function StrT
oIntDef(str : String; Def : Integer):Integer;

function IntToHex(v : Integer; Digits : Integer):Integer;

function FloatToStr(v : Float):String;

function StrToFloat(str : String):Float;

function StrToFloatDef(str : String; Def :Float):Float;

function Chr(x :

Integer):String;

function Ord(s : String):String;

function CharAt(s : String; x : Integer):String;

procedure SetCharAt(var S : String; x : Integer; c : String);

procedure Delete(var S : String; Index : Integer; Len : Integer);

procedure Insert(src : Stri
ng; var Dest : String; Index : Integer);

function LowerCase(Str : String):String;

function AnsiLowerCase(Str : String):String;

function UpperCase(Str : String):String;

function AnsiUpperCase(Str : String):String;

function Pos(SubStr : String; Str : string)
:Integer;

function Length(Str : String):Integer;

function TrimLeft(Str : String):String;

function TrimRight(Str : String):String;

function Trim(Str : String):String;

function CompareText(Str1 : String; Str2 : String):Integer;

function AnsiCompareText(Str1
: String; Str2 : String):Integer;

function CompareStr(Str1 : String; Str2 : String):Integer;

function AnsiCompareStr(Str1 : String; Str2 : String):Integer;

function IsDelimiter(delims : String; s : String; Index : Integer):Boolean;

function LastDelimiter(d
elims : String; s : String):Boolean;

function QuotedStr(Str : String):String;



5

Time handling

For radio stations time is usually very important. PAL contains a lot of features that helps programmers
to deal with time.


The basic time variable type is
DateTi
me

var D : DateTime;


With this format the date and time is represented as a floating point value.

The integral part of a
DateTime value is the number of days that have passed since 12/30/1899. The fractional part of a
TDateTime value is fraction of a 24
hour day that has elapsed.


Following are some examples of TDateTime values and their corresponding dates and times:


0

12/30/1899 12:00 am

2.75

1/1/1900 6:00 pm

-
1.25

12/29/1899 6:00 am

35065

1/1/1996 12:00 am


Example statements:


D := Now; {Current dat
e/time}

D := Date; {Today, with the fraction part 0}

D := Time; {The time only, the integral part is 0}

D := Now
-

1; {Yesterday, this exact time}

D := Now + (1/24); {One hour from now}


PAL also has a very useful date mask function called T

These are expl
ained very well in the documentation and examples:

http://www.spacialaudio.com/products/sambroadcaster/help/pal/hs4020.htm


The TimeMask property is used to generate a

DateTime value.

The Mask can be in several formats.


Standard time

HH:MM:SS

Mask must be in 24 hour format and contain all fields (HH, MM and SS
-

where HH is hours, MM
is minutes and SS is seconds)

Example T['14:00:00'] would generate a DateTime value of

exactly 2pm today.


Time calculation

+HH:MM:SS

-
HH:MM:SS

Examples

T['+00:00:30'] generates a time exactly 30 seconds from the current date and time.

T['
-
01:00:00'] generates a time exactly 1 hour earlier.


Next Time (Wildcards)

XX:MM:SS

HH:XX:SS

HH:MM:XX



XX acts as a wildcard, where the mask will try and find the next time that fits in the mask.

For example lets assume the current time is 2:05pm and we use XX:00:00 as our mask. The
resulting time will be 3:00pm because that’s the closest time in the fut
ure that fits the mask
with wildcards.


Next Time (Fixed)

NEXTHOUR


NEXT60


Both returns the next hour after the current time. (Same as using


XX:00:00)


NEXTHALFHOUR

NEXT30


Both returns the next half hour after the current time.


For example if the cur
rent time is 2:15pm then using NEXT30 will result in 2:30pm.


If the
time was 2:50pm, then using NEXT30 would return 3:00pm.

NEXTQUARTER


6

NEXT15

Both will return the next quarter after the current time.


For example using the NEXT15
mask at 2:05pm will retu
rn 2:15pm


TimeMask property example

Writes the result of certain time masks to the output window.




WriteLn('
--
Standard time');

WriteLn(T['14:00:00']);


{## 2PM today}

WriteLn(T['04:30:00']);


{## 4:30AM today}

WriteLn(DateTime(T['0
9:00:00']
-
1));
{## 9AM YESTERDAY}

WriteLn(DateTime(T['09:00:00']+1));
{## 9AM TOMORROW}


WriteLn('
--
Calculation');

WriteLn(T['+01:00:00']);


{## One hour from now}

WriteLn(T['
-
00:00:30']);


{## 30seconds back}


WriteLn(T['NEXT60']);


{## Next hour}

Write
Ln(T['NEXT30']);


{## Next halfhour}

WriteLn(T['NEXT15']);


{## Next quarter}


WriteLn('
--
WildCards');

WriteLn(T['XX:00:00']);


{## Same as Next Hour}

WriteLn(T['XX:59:30']);


{## 30 seconds after next minute of the hour}

WriteLn(T['XX:XX:30']);


{## Next :30 seconds}


http://www.spacialaudio.com/products/sambroadcaster/help/pal/hs4022.htm



Few quick examples:

D := T['13:00:00']; {1pm today}

D := T['+00:00:10'
]; {10 seconds from now}

D := 1 + T['+00:00:10']; {Tomorrow, 10 seconds from now}


Decoding the date into its multiple parts:

var y,m,d : Integer;

var hh,mm,ss,ms : Integer;

var D : DateTime;


D := Now;

DecodeDate(D,y,m,d);

DecodeTime(D,hh,mm,ss,ms);


DayO
fWeek function:

if DayOfWeek(Now) = Sunday then {Do stuff};

The days of the week are stored in constant values you can use. Sunday = 1 to Saturday = 7.


Some other time functions to check out:

function Now: DateTime;

function Date: DateTime;

function Time:

DateTime;

function DateTimeToStr(dt: DateTime):string;

function StrToDateTime(str : String): DateTime;

function DateToStr(Date: DateTime):String

;

function StrToDate(Str : String): DateTime;

function TimeToStr(Time : DateTime):String;

function StrToTime(S
tr : String): DateTime;

function DayOfWeek(dt : DateTime):Integer;

function FormatDateTime(Format : String; dt : DateTime):String;

function IsLeapYear(Year : Integer):Boolean;

function IncMonth(dt : DateTime; nm : Integer):DateTime;

procedure DecodeDate(dt

: DateTime; var yy,mm,dd : Integer);

function EncodeDate(yy, mm, dd : Integer):DateTime;

procedure DecodeTime(dt : DateTime; var hh, mm, ss, ms : Integer);

function EncodeTime(hh, mm, ss, ms : Integer):DateTime
;


Decision making & selection

PAL scripts ca
n compare values and act upon these results. The basic operators are:

=

-

Equal to

<>

-

Not equal to


7

<=
-

Equal to, or less that

>=

-

Equal, or Greater than

NOT

-

Not value


AND
-

BOTH expressions must be true

OR
-

ANY expression can be true

( ) {Only use
d to logically group multiple expressions}


All of these are called logical or "boolean" operators, since the final result is always TRUE or FALSE.

For example:

Var B : Boolean;

Var X : Integer = 0;

Var Y : Integer = 1;

Var Z : Integer = 2;


B := X<0; {B=F
ALSE}

B := X = 0; {B=TRUE}

B := Z>Y; {B=TRUE}

B := NOT(X > 0); {B=TRUE}

B := (X<0) OR (X=0); {B=TRUE since X=0}

B := (X<0) AND (X=0); {B=FALSE}

B := (X<=0); {B=TRUE}

B := NOT B; {Changes value from true to false, or false to true}

B := (X<>Z); {B=TRUE, sin
ce X is not equal to Z}


B := (1+1); {Invalid!! Not boolean result}


Logical expressions can be used in IF or CASE statements (see directly below), or REPEAT..UNTIL
and WHILE..DO loops. (See Loops & Iterations section)


Very important note: Due to implemen
tation problems, you can not do PAL.WaitForXXX within an IF
statement block. This will be discussed later.


Basic selection logic

The IF..THEN logic block is used to specify what
sections of code need

to be executed depending on
a logic
expression
.


Exampl
es:

IF DayOfWeek(Now) = Sunday THEN


WriteLn('Today is Sunday!')

ELSE


WriteLn('Today is not Sunday...');


IF (Now>T['14:30:00') AND (DayOfWeek(Now)=Saturday) THEN


WriteLn('It is time for my Saturday live show!');


IF (Now>T['14:30:00') AND (DayOfWeek(Now
)=Saturday) THEN


BEGIN


WriteLn('It is time for my Saturday live show!');


WriteLn('so lets get going...');


END;


Sometimes you have to check many values at the same time. You can use nested IF statements, for
example:


IF (A = 1) THEN


BEGIN


{Do

stuff}


{And some more}


END

ELSE IF (A = 2) THEN


{Do stuff}

ELSE


{Do other stuff};


But using CASE statements are usually a more elegant solution:


CASE A OF


1 : begin


8


{Do stuff}


{And some more}


end;


2 : {Do stuff};


el
se {Do other stuff}

END;


A needs to be an
ordinal

expression or a string expression.

Ordinal expressions are data values in a
range, i.e. integer & boolean are ordinal values, float is not.


Example of a str
ing CASE statement:

var A : String = 'ABC';

CAS
E A OF


'ABC' : WriteLn('This is our ABCs');


'XYZ' : WriteLn('This is our XYZs');

END;


Loops & I
teration

In programming one often find the need to repeat certain commands a number of times until a certain
condition is met.

PAL scripts contain a few b
asic ways to achieve this.


a) Firstly, the script itself can repeat.

By setting

PAL.Loop := True;

the whole script will repeat once it has reached the end. If this is set to false during
execution, the script will stop once it reaches the end. (False is
the default value)


b) For loop

var T : Integer;

for T := 0 to 20 do


begin


WriteLn(T);


end;


c) Repeat .. until loop

T := 0;

repeat


WriteLn(T);


T := T + 1;

until (T > 10);


T:=33;

repeat


WriteLn('You will see this line exactly once!');


T := T
+ 1;

until (T > 10);


d) While..do loop

T := 0;

while (T<20) do

begin


WriteLn(T);


T := T + 1;

end;


T:=33;

while (T<10) do


WriteLn('You will NEVER see this line!');


T := T + 1;

end;


Beware of infinite loops! Loops should always have some code that

will cause it to eventually
exit the loop.

These are examples of infinite loops:

T := 0;

while (T<10) do


begin


WriteLn(T);


end;


repeat


9


T := 0;


T := T + 1;

until (T>10);


Waiting....

Although technically not part of the PAL base language, it is
crucial we cover the subject of waiting in
this section since it is such a core part of most PAL scripts.

A PAL script executes

one command or
one block of commands every second. For most scripts this is too fast
-

they usually only need to take
action on
ce a song changes, or once a certain time is reached. Theoretically
, w
e could just create a
loop that will loop

until the condition is met,
but this will just waste CPU.


Thus in PAL we have many wait commands, often collectively referred to as "WaitForXXX
"
commands. (And no, we are not talking about slow porn downloads)


PAL supports 3 waiting commands:

PAL.WaitForPlayCount(Count);

PAL.WaitForQueue(Count);

PAL.WaitForTime(Time);


Using these commands will cause SAM to "pause" the PAL script until the speci
fied event occurs.


Examples:

PAL.WaitForPlayCount(2);

This command will wait for exactly two songs to play before the script is resumed. This is done by
setting a counter internally as soon as the WaitForPlayCount is executed, then for each song that is
l
oaded into a deck and
starts playing the counter are

decreased. As soon as the counter reaches zero
-

the script is resumed.


PAL.WaitForQueue(2);

This command will wait for the queue to contain exactly two tracks. So if the queue had 4 tracks, and
you sta
rt playing tracks from the queue, the script will eventually continue once the queue reaches 2
tracks. On the flip side, if the queue had zero tracks in it, this command will wait until 2 tracks are
added to the queue.

Think carefully when you use this co
mmand
-

it can be tricky especially if you
have multiple PAL scripts managing tracks in the queue. It is very possible that other scripts can keep
the queue full (i.e. at 4 tracks) and that the queue will never reach 2 tracks.


PAL.WaitForTime(Time);

This
is indeed a very useful wait command. First we need to cover the different ways this can be
called. The "Time" can be specified in many ways.

a) As a DateTime variable

var D : DateTime;

D := Now + 1; {Tomorrow this time}

PAL.WaitForTime(D);


Another exampl
e
-

using the T timemask object we learned about earlier. This also returns a DateTime
value.

PAL.WaitForTime(T['14:00:00']);


b) As a string time mask.

The exact same time mask format used with the T time mask object can be used in the WaitForTime
command
.

Examples:

PAL.WaitForTime('+00:00:10'); {## Wait for 10 seconds}

PAL.WaitForTime('14:30:00'); {## Wait for 2:30pm TODAY}

PAL.WaitForTime('XX:00:00'); {## Wait for next hour}


PAL.WaitForTime('14:00:00'+1); {## This is invalid! See below for correct s
yntax}

Use this instead:

PAL.WaitForTime(T['14:00:00']+1); {## This works
-

uses DateTime value}


So in short
-

you can either pass a DateTime variable or value, or pass a string time mask.



10

There is a subtle logical issue you need to be aware of as a PAL

developer.


Say you have this PAL script:

PAL.Loop := True;

PAL.WaitForTime('10:00:00');

WriteLn('It is now 10am in the morning');

PAL.WaitForTime('20:00:00');

WriteLn('It is now 8pm in the afternoon');

PAL.WaitForTime('23:59:59');


If this script is star
ted at 7am in the morning, then it will work as expected. SAM will wait till 10AM,
write the message, wait till 8pm, write the message, and then wait till 1 second before midnight before
the script is restarted
-

ready for the next day.


However, if this s
cript is started at 5pm, then SAM will check the first PAL.WaitForTime('10:00'); and
realize that 10am < 5pm, so it will not wait and immediately print out the message "It is now 10am in
the morning". Then from this point onward it will work as expected
-

i.e. wait till 8pm, write the
message, and then wait till 1 second before midnight before the script is restarted
-

ready for the next
day.


More advanced PAL developers know this and write slightly more complex scripts to skip over sections
of the day tha
t already passed them by without executing the code.

We provide one quick example of
how this can be achieved
-

although there are many other methods that may be used.


var OldTime : DateTime;

OldTime := Now; {Get the time the script started}


PAL.Loop :=

True;

PAL.WaitForTime('10:00:00');

if (OldTime<=T['10:00:00']) then WriteLn('It is now 10am in the morning');

PAL.WaitForTime('20:00:00');

if (OldTime<=T['20:00:00']) then WriteLn('It is now 8pm in the afternoon');

PAL.WaitForTime('23:59:59');


Please not
e that we do not want to be waiting inside an IF statement, so make sure to place the IF
statement after your wait. (See below)

Running this script after the 10am will only result in the 8pm
code being executed, provided the script was started before 8pm.

This totally solves the problem of
scriptings being started late in the day causing unintended results.


Now, a few important concepts about waiting:

You can not wait inside

a) IF..THEN statements

b) CASE..OF statements

c) Custom functions & procedures

PAL will simply skip over the wait command. This is an unfortunate result of the implementation of the
core language PAL was based on. This language was never meant to be execute line
-
by
-
line, but
rather as a complete program. Thus we had to significantly
modify this language to meet our needs.
Unfortunately we were not able to work around this problem for the above mentioned statement
blocks.


The good news is that there is ways to avoid this problem.

1. Do not wait inside functions & procedures. Rather re
peat the source lines where needed.

2. In the case of IF..THEN and CASE..OF statements, use a WHILE..DO loop instead.


For example:

IF (A>B) THEN


begin


PAL.WaitForPlayCount(1);


end;


Can be replaced with:

var Dummy : Boolean = True;


11

WHILE (A>B) AND (
Dummy) DO

begin


PAL.WaitForPlayCount(1);


Dummy := False;

end;


While obviously not the perfect solution, it gets the job done.

Oh, another tip. You can use the Dummy
variable if you have many replacements to do. You just have to remember to either set
Dummy :=
True; before the while loop.


Speeding up execution

PAL was written to be a low priority process inside SAM so that the audio quality never gets affected.
As a result PAL executes only line of code every second.

In program
execution terms that are

very slow
-

but for PAL this works perfect 99% of the time.

But there are moments where you need that instant execution. Luckily PAL makes this easy with the


PAL.LockExecution;

{Do stuff}

PAL.UnlockExecution;


commands.

Example:


T := 0;

While (T<100) d
o


begin


WriteLn(T);


T := T + 1;


end;


This script will take at least 100 second to execute. By adding 2 lines, we can make the
script execute in under 2 seconds.


var T : Integer;


PAL.LockExecution;

T := 0;

While (T<100) do


begin


WriteLn(T);



T := T + 1;


end;

PAL.UnlockExecution;


Be careful though
-

SAM will "freeze up" while a PAL script is in locked execution
-

thus do not stay
locked for long periods of times. You can even check how long your code has been executing, and
then take a shor
t breather every now and then by either using a "WaitForXXX" command, or quickly
unlocking and then locking execution again.


Objects

PAL is a fully OOP language. Although you will most likely not use much OOP yourself, PAL contains
many objects which you
need to be able to use.

(OOP = Object Orientated Programming)


Objects are almost like variables, except that objects can also perform actions via
methods

Objects also store data via
properties
. Some properties are read only though, i.e. they can only be
u
sed to read values, not change the values.


Examples:

ActivePlayer.FadeToNext; {Method}

ActivePlayer.Volume := 255; {Read/Write property}

WriteLn(ActivePlayer.Duration); {Read
-
only property}


var Obj : TPlayer;


Obj := ActivePlayer;


12

Obj.FadeToNext;


Object
s support inheritance:

var Obj : TObject;

Obj := ActivePlayer;


if Obj is TPlayer then {Check if object is of the right type}


TPlayer(Obj).FadeToNext; {TypeCast object to correct type and use it}


You can also create and destroy many of your own objects:


var List : TStringList;


List := TStringList.Create; {Create a stringlist object}


{Do stuff with object here}


List.Free; {Remember to free the object from memory once you are done with it}


Section 1minute overview:


Declaring variables:

const A = 'Hel
lo world!';

const A = 123;


var V : Integer;

var V : String;

var V : Array[1..5] of Integer;

var V : Array of Integer;

var V : TMyClass;


var V : Integer = 1;

var V : String = 'Hello world!';


Math:

m := a + b;

m := a
-

b;

m := a * b;

m := a / b; {Float on
ly}

m := a div b; {Integer only; Discards the remainder}

m := a mod b; {Integer only; Result is only the remainder}

m :=
-
m;


Logic:

not a

a or b

a and b

a xor b

a < b

a <= b

a > b

a >= b

a <> b

a = b


Casting:

Float(x)

DateTime(x)


Type checking & casting
:

a is TMyClass

TMyClass(a)


Selection:

if {logic expression} then


{Do stuff}

else


{Do stuff};



13

case {Value} of


1 : {Do stuff}


2..4 : {Do stuff}


5,6,7 : {Do stuff}


else {Do stuff}

end;


Loops:

var C : Integer;

For C := A to B do


begin


{Do

stuff}


end;


while {Logic expression is True} do


begin


{Do stuff}


end;


repeat


{Do stuff}

until {Logic expression is True};


Waiting
:

PAL.WaitForTime(TimeMask/DateTime);

PAL.WaitForPlayCount(SongCount);

PAL.WaitForQueue(NoOfItemsInQueue);


Speeding

up execution
:

PAL.LockExecution;

PAL.UnlockExecution;


Some warnings about PAL scripting

PAL scripting is a powerful programming language. With this power comes a lot of responsibility.

Incorrectly written PAL scripts can have many bad side effects, those

include:

1. Cause SAM to lock up/freeze.

2. Cause exception errors in SAM,
affecting

the stability in SAM.

3. Although PAL does automatic garbage collection on script restart, it is possible to write scripts that
consume all available memory if memory all
ocation is done in long
-
running loops.

4. PAL gives you direct access to your SQL database. It is possible to use SQL queries to completely
erase or destroy the database and data integrity.

5. PAL gives you the power to work with files on your local hard d
isk and network. PAL can be used to
delete or modify files.


Thus beware of example scripts you download from the internet. Review them first to make sure that
they are written well and will not affect your broadcast negatively.

We also do not recommend
d
eveloping PAL scripts on a production machine. It is best to have a mirror copy of SAM running on
another machine, and then developing scripts on this machine.



Section 2
-

The main SAM objects


Although SAM Broadcaster is a complex program, it can be des
cribed as 4 main components.

2.1. The playlist/media library of tracks.

2.2. Music selection logic & playlist rules.

2.3. The audio engine consisting of different audio channels represented by decks/players.

2.4. Finally, the encoders and statistic relays.


All other objects found in the PAL scripting language are basically used to support these main
functions.

In this section we will discuss some of these main objects and use them in some real
-
world
PAL scripts to demonstrate their functionality.



14


2.1
Th
e media library/
playlist

SAM Broadcaster stores EVERYTHING it played in the media library. Or more correct, SAM can only
play a track if it exists in the media library.

Without it SAM would not be able to do the advanced
playlist management and selection l
ogic rules needed for a professional radio station.


Before we continue it is vital that you understand the playlist and categories inside SAM Broadcaster.

http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/Understanding_Playlist_
Categ
ories.htm

http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/Category_Based_Rotatio
ns.htm


2.
1
.1 Importing or exporting files to/from the media library


Importing

You can add files to either a category or directly to the queue using va
rious PAL commands.

A few examples:

CAT['MyCategory'].
AddDir(‘c:
\
music
\
’, ipBottom);

CAT['MyCategory'].
AddFile(‘c:
\
music
\
test.mp3’, ipBottom);

CAT['MyCategory'].
AddList(‘c:
\
music
\
playlist.m3u’, ipBottom);

CAT['MyCategory'].
AddURL(‘http://localhost:8000/tes
t.mp3’, ipBottom);


The same commands can also be used for adding files to the queue

Queue
.
AddDir(‘c:
\
music
\
’, ipBottom);

Queue
.
AddFile(‘c:
\
music
\
test.mp3’, ipBottom);

Queue
.
AddList(‘c:
\
music
\
playlist.m3u’, ipBottom);

Queue
.
AddURL(‘http://localhost:8000/te
st.mp3’, ipBottom);



Exporting

Starting with SAM4, you can now easily store the contents of a category or the queue to file. This can
be very useful when used with music scheduling software like M1 SE
(
http://www.spacialaudio.com/products/m1se
)


Exporting a category can be done via the following PAL commands:

CAT['MyCategory'].SaveAsM3U('c:
\
list.m3u');

{Export as m3u playlist}

CAT['MyCategory'].SaveAsCSV('c:
\
list.csv');

{Export as CSV data file}



Exporting the queue can be done as follows:

Queue.SaveAsM3U('c:
\
list.m3u');

{Export as m3u playlist}

Queue.SaveAsCSV('c:
\
list.csv');

{Export as CSV data file}


Note
: CSV files are
spreadsheet

files, also commonly known as
comma
-
delimited

files.





2.
1
.
2

Using media library for custom rotation "clockwheels"

Serious broadcasters know the importance of properly
tagging

their music, i.e. making sure the artist,
title, album and other fields are properly completed. Once that is done the music, jingles, promo
s and
even advertisements are sorted into various categories.

Once all that hard work is done, the
scheduling of music in a professional
radio format

becomes really easy.


In SAM music scheduling becomes the basic task of selecting a song from a certain c
ategory, and
adding it to the queue.

When selecting a track from a category, SA
M can use many different logics
http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/Selection_Method_Logic.
htm) and then apply certain selection rules
(http
://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/Playlist_Rotation_Rules.
htm) to get the best track that meets all these rules.

You can also specify if the track should go to the top or the bottom of the queue. We will show you
later how

to put this into good use.



15

So
let’s

jump in with a very simple script:


PAL.Loop := True;


Cat['Tracks'].QueueBottom(smLemmingLogic, EnforceRules);

Cat['Music (All)'].QueueBottom(smLRPA, EnforceRules);

Cat['Hot Hits'].QueueBottom(smLRP, EnforceRules);

Ca
t['Adz'].QueueBottom(smLRPA, NoRules);


PAL.WaitForPlayCount(4);


In the above script we first set the script to loop/restart.

Next we insert 4 tracks into the bottom of the
queue. The first track will come from the "Tracks" category and we will use the r
andom lemming logic
to select the song, and then enforce our playlist rules on the selection of the song. We do similar, but
slightly different things for the other 3 categories (see if you can spot and understand the difference!).


Finally
,

we wait for SA
M to play 4 tracks, before the script ends and restarts/loops. (Why do we wait?
What would happen if we did not wait?)


The final result of this script is that we play

One song from the category "Tracks"

One song from the category "Music (All)"

One song f
rom the category "Hot Hitz"

One song from the category "Adz"


This
format

of programming will continue until the PAL script is stopped manually.

Inserting jingles,
promos and station IDs into your format are also relatively simple.

For example:

PAL.Loop
:= True;


Cat['Tracks'].QueueBottom(smLemmingLogic, EnforceRules);

Cat['Music (All)'].QueueBottom(smLRPA, EnforceRules);

Cat['Hot Hits'].QueueBottom(smLRP, EnforceRules);

Cat['MyPromos'].QueueBottom(smLRP, NoRules);


PAL.WaitForPlayCount(4);


Cat['Tracks']
.QueueBottom(smLemmingLogic, EnforceRules);

Cat['Music (All)'].QueueBottom(smLRPA, EnforceRules);

Cat['Hot Hits'].QueueBottom(smLRP, EnforceRules);

Cat['MyJingles'].QueueBottom(smLRP, NoRules);


PAL.WaitForPlayCount(4);


Notice how I had to repeat the song

categories so I could place 1 Promo after a
set

and then 1 Jingle
after another set
-

and have this repeat.

Also notice that I used the "NoRules" in the logic. By now you should know why this is required.


Let

s move on to a slightly more complex example.

Here we will use two separate PAL scripts that run
at the SAME time to produce our playlist logic.

PAL #1 will be responsible for scheduling our music,
while PAL #2 will be responsible for adding jingles, promos and Adz to the system.


PAL 1

PAL.Loop :=
True;


Cat['Tracks'].QueueBottom(smLemmingLogic, EnforceRules);

Cat['Music (All)'].QueueBottom(smLRPA, EnforceRules);

Cat['Hot Hits'].QueueBottom(smLRP, EnforceRules);


PAL.WaitForQueue(0);


PAL 2

PAL.Loop := True;


PAL.WaitForPlayCount(5);


16

Cat['MyJingles'
].QueueTop(smLRP, NoRules);


PAL.WaitForPlayCount(3);

Cat['MyPromos'].QueueTop(smLRP, NoRules);

Cat['MyAdz'].QueueTop(smLRP, NoRules);


A few things to pay attention to:

-

We now use the WaitForQueue command in PAL 1 to know when to insert more songs into
the
queue.

-

PAL 2 uses
QueueTop

now, because there might be songs in the queue
-

and we want to place this
jingle/promo/ads content in the very next to
-
be
-
played spot at the top of the queue.

-

Also, note that MyAdz will be placed above MyPromos in the qu
eue. (In reverse order
-

make sure
you understand why this happens)


2.1.3 Scheduling advertising in SAM

(via StreamAdz)

Since SAM Broadcaster v4, SAM has direct integration with the powerful advertisement delivery
platform called StreamAdz (
http://www.spa
cialaudio.com/products/streamadz
).

Instead of selecting a track from a category, you simply execute the StreamAdz PAL command to
insert
a specified
duration of advertisements. SAM will then automatically select one or more
advertisements to fill the speci
fied advertisement block.


Examples

StreamAdz['Providers (All)'].QueueBottom(30); {Select an advertisement from any provider}

StreamAdz['Provider C'].QueueBottom(60); {Select an advertisement using ONLY provider C}


The following script demonstrated an eas
y advertisement scheduling script, inserting 2 minutes of
advertising every 15 minutes.


PAL.Loop := True;

S
treamAdz['
Providers (All)'].QueueBottom(120
);

{Insert 2 minutes of advertising}

PAL.WaitForTime(‘+00:15:00’); {Wait 15 minutes}




2.2 Music selecti
on logic & playlist rules.

Tracks stored in categories can be selected using various selection logics.

For example, smRandom
will select a random track from the category while smLRP will select the song that has not played for
the longest time. (Least Rec
ently Played)


A song can only be selected if it complies with the Playlist logic rules. These rules aim to limit the
amount of times a song, artist or album repeats within a period of time.

For example it will be very bad
to play the same song back
-
to
-
bac
k! In the USA the DCMA laws even makes it illegal to repeat songs,
artists or albums too frequently.


At this moment it is important that you fully understand both:

http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/Playlist_Rotation_R
ules.
htm

http://www.spacialaudio.com/products/sambroadcaster/help/sambroadcaster/Selection_Method_Logic.
htm


Do
-
not
-
repeat rules do

not however apply to jingles, promos, advertisements, etc. in the same way.
That is why in general we use the NoRules flag w
hen selecting these content types. However, you still
do not want to repeat these too often. Playing the same jingle back
-
to
-
back or playing the same
advertisement back
-
to
-
back is just as bad. The easiest way to avoid this is to use the smLRP rule.
That wa
y SAM will cycle through the content in a top
-
to
-
bottom fashion.

Even this fails though when inserting multiple tracks into the queue, because no repeat checking is
applied
-

the same song will be added to the queue multiple times!


To solve this we need a

special PAL script that modifies the playlist rules, insert the content, and then
restores the old content. Now you are free again to use any selection logic method you wish!


17


Example:

{Declare a procedure that we will define later}

procedure NoRules(MyCa
t : String; Logic : Integer); forward;


PAL.Loop := True;

NoRules('MyAdz',smLRP);

NoRules('MyAdz',smRandom);

NoRules('MyAdz',smLRP);


PAL.WaitForPlayCount(8);


{========================================================}

procedure NoRules(MyCat : String; Log
ic : Integer);

var


tArtist,


tAlbum,


tTitle : Integer;

begin


{Save current rules so we can restore them later}


tArtist := PlaylistRules.MinArtistTime;


tTitle := PlaylistRules.MinSongTime;


tAlbum := PlaylistRules.MinAlbumTime;




{Set some very low

repeat rules
-

1 minute}


PlaylistRules.MinAlbumTime := 1;


PlaylistRules.MinArtistTime := 1;


PlaylistRules.MinSongTime := 1;




{Insert content to top of queue using our new rules}


CAT[MyCat].QueueTop(Logic,EnforceRules);



{Restore original rules}


PlaylistRules.MinArtistTime := tArtist;


PlaylistRules.MinSongTime := tTitle;


PlaylistRules.MinAlbumTime := tAlbum;

end;

{========================================================}


While SAM should be able to cater to all your needs, some people have
very specific needs for their
rotation.

Luckily with the power of PAL
,

most have found ways to achieve their goals with some nifty
scripting.


One example is the Custom Song Balance script developed by Toby, slightly updated by Elbert:

http://www.spacialau
dio.com/knowledge/question.php?qstId=68


This PAL script applies 5 custom rules to tracks to decide what track to play next. The only downside
to this script is that it takes a lot of CPU time to select a single song.



2.3. The audio engine consisting of
different audio channels represented by decks/players.

PAL allows you to control various audio components in the audio engine.This can be very useful for
making sure audio elements play exactly like you want them to.

Let’s

go through the main
components a
long with a few examples.


DeckA, DeckB, Aux1, Aux2, Aux3 and SoundFX are all player types (Class TPlayer)

This means that all of the same operations apply to all of these players. (Note that VoiceFX can
currently not
access

via PAL since tracks should in
general not be played over this player. This is
meant exclusively for voice operation)


Reading currently playing song information

This script will read the song information of the track loaded into DeckA, and then print out the
information.


var Song : TS
ongInfo;

Song := DeckA.GetSongInfo;


18

if Song = nil then


WriteLn('No song loaded into DeckA!')

else


begin


WriteLn('Artist: '+Song['artist']);


WriteLn('Title: '+Song['title']);


WriteLn('Album: '+Song['album']);


WriteLn('Duration: '+Song['duration'])
;


end;

Song.Free;


Notice how we test if the Song = nil, because if that is the case trying to read information on the song
object will cause memory access violations since the object does not exist!


Using utility functions

PAL comes with some very handy

built
-
in functions you can use. These provide you with direct access
to the correct player object. (Between DeckA & DeckB only!)


ActivePlayer
-

This returns the player object considered the "active" player. If only one deck is player
a track, then this t
rack will be the active player. If both decks are playing a track, then the deck with the
longest duration remaining is considered the active deck.

If NEITHER DeckA or DeckB have any
tracks playing, then this function returns NIL. It is very important tha
t you check for the NIL value,
unless you are certain that there will always be an active deck.


IdlePlayer
-

A Deck is considered Idle if no song is queued up in the deck, and the deck is not actively
playing any tracks. If both decks are idle, DeckA is r
eturned as the idle object. If both decks are
queued up, or actively playing a track
-

then this function will return NIL!


QueuedPlayer
-

A player is considered in Queued mode if the Deck has a song object loaded into the
Deck, but the Deck is not current
ly actively playing any tracks.

If both decks are either idle or active,
then this function will return NIL! Otherwise it will return the first Queued deck.


Overall the ActivePlayer function is used much more than any of the others. Most of our examples
will
deal with this function.


For example c
heck if SAM is active:

if (ActivePlayer = nil) then


WriteLn('WARNING!!! Nothing is playing??');


This script will wait for the top
-
of
-
the
-
hour and then insert a news item. It will then immediately fade to
this n
ews item.


PAL.Loop := True;

PAL.WaitForTime('XX:00:00');

Queue.AddFile('c:
\
news
\
news.mp3',ipTop);

ActivePlayer.FadeToNext;


Here is a bit more of a tricky script. This one will insert some liners over the music, but it will only do so
if:

a) The music
has

an intro of more than 5 seconds

b) The music is a normal song (i.e. not a promo, jingle, advertisement, news)


{ About:


This script will play a liner in Aux1 as soon as a new track starts


The liner will only be played if


a) The song has an intro

of specified minimum duration


b) The song is of type S, i.e. a normal song.



Then the script will wait the specified amount of time before


it tries to play another liner.



This script can help brand your station and make it sound like a true


commercial

terrestrial station.


any source connected


19



Usage:


a) Make sure you use the song information editor to specify intro times for your tracks!


b) Make sure the AGC settings on Aux1 is to your liking. Also set the volume a bit loude
r


on Aux1 so you can clearly hear the liner above the active Deck audio.


c) Edit the configuration details below.


Make sure to change the category to the one you use to store your liners.

}

{ CONFIGURATION }

{=============================
=====================}

const MIN_INTRO = 5*1000; //5 seconds

const MIN_WAIT = '+00:15:00'; //Wait 15 minutes between liners

const LINERS_CATEGORY = 'Liners';



{ IMPLEMENTATION }

{
--------------------------------------------------
}

function ExtractIntro(S
ong : TSongInfo):Integer; forward;


var Song, Liner : TSongInfo;

var Waiting : Boolean = True;

var Intro : Integer = 0;

Aux1.Eject;


{Step1: Queue up the deck, ready for play}

Liner := CAT[LINERS_CATEGORY].ChooseSong(smLRP,NoRules);

if (Liner=nil) then


Wr
iteLn('No valid liner found')

else if (not Aux1.QueueSong(Liner)) then


WriteLn('Failed to queue song: '+Liner['filename']);


{Wait for a valid song with intro}

while Waiting do


begin


{Step2: Wait for the song to change}


PAL.WaitForPlayCount(1);



{S
tep3: Grab current song information}


Song := ActivePlayer.GetSongInfo;



if (Song=nil) then


WriteLn('The active player contained no song info??')


else


begin


{Extract the intro time
-

this is a bit tricky}


Intro := ExtractIntro(Song);




{Start playing the liner if the current song matches our rules}


if(Song['songtype']='S') and (Intro>=MIN_INTRO) then


begin


Aux1.Play;


Waiting := False;


end;


Song.Free; Song := nil;


end;

end;


{Wait 5 minutes before we do
this all again}

PAL.WaitForTime(MIN_WAIT);

PAL.Loop := True;


{................................................}

function ExtractIntro(Song : TSongInfo):Integer;

var


P : Integer;


XFade : String;

begin


Result :=
-
1;


XFade := Trim(Song['xfade']);



Write
Ln('Decoding XFade string');


WriteLn('XFade: '+XFade);



20


if XFade = '' then


Result :=
-
1


else


begin


P := Pos('&i=',XFade);


if (P > 0) then


begin


Delete(XFade,1,P+2);


P := Pos('&',XFade);


if (P>0) then


Delete(XFade,P,Len
gth(XFade));



Result := StrToIntDef(XFade,
-
1);


WriteLn('Intro time detected: '+XFade);


end;


end;

end;


{
--------------------------------------------------
}


Other methods and functions

Here follows a quick discussion on other methods and pr
operties available in the TPlayer object, and
how they may be used.


Properties:

The
CurTime

property can be monitored to see when a song is approaching its end (when compared
to the Duration property) and then some action can be taken right before the nex
t song is started or
queued.


For example:

Var Done : Boolean = False;

while not done do


begin


if((ActivePlayer.Duration>0) AND ((ActivePlayer.Duration
-
ActivePlayer.CurTime)>10000)) then


begin


WriteLn('This song will end in 10 seconds!');



Done := True;


end;


end;


Note:

Due to the gap killer, it may be less than 10 seconds. You can use the actual value of the
calculation to get the correct value of the time remaining.


You can read and write the Volume property of a Deck. This can be us
ed to player
a
dvertisements,
stationIDs, etc. louder than other tracks.

We recommend you use the song information editor to set
the Gain for each track of these audio content types, but this script might be
an alternative solution
.


var Done : Boolean = F
alse;

var Song : TSongInfo;


While not Done do

begin


if QueuedPlayer <> nil then


begin


Done := True;


Song := QueuedPlayer.GetSongInfo;


if (Song<>nil) then


case song['songtype'] of


'S' : QueuedPlayer.Volume := 255;


'A','J','P
' : QueuedPlayer.Volume := 350;


else QueuedPlayer.Volume := 255;


end; //case


end;

end;


PAL.Loop := True;

PAL.WaitForPlayCount(1);



21

Once again, this is not the recommended method
-

but it

does serve

as a good example of how to use
the volume o
ption in PAL.

Note:

255 is full volume, while 350 is volume with extra gain applied.


Using the above as reference, you should now be able to also create a script that can adjust the tempo
or pitch of the deck according to what song is currently queued i
n it.

This can be used for a script that
does automatic beat matching for example...


Please refer to the PAL manual about the various other properties and methods we did not discuss in
this chapter.


2.4. Encoders and statistic relays

PAL also gives you
access to the encoders & statistic relays.

This gives you the power to start/stop the encoders
-

as well as insert scripting data into the stream.

We will cover some of these techniques in the following sections.


2.4.1 Statistic relays

The Relays class ba
sically allows you the power to do 5 main things:

-

Read the status of all the relays

-

Read the current listener count

-

Read the peak listener count

-

Read the maximum available listener slots

-

Force an update of the statistic relays.


Example scripts:

{Print out relays information
-

all combined}

PAL.LockExecution;



WriteLn('Relays overview ');


WriteStr('
--
Active: ');


if Relays.AtLeastOneActive then WriteLn('Online') else WriteLn('Offline');


WriteStr('
--
Viewers: '); WriteLn(Relays.Viewer
s);


WriteStr('
--
Peak: '); WriteLn(Relays.Viewers_High);


WriteStr('
--
Max: '); WriteLn(Relays.Viewers_Max);


WriteLn('');



PAL.UnlockExecution;


{Print out relays information
-

each relay on its own}


var I : Integer;


PAL.LockExecution;


for I
:= 0 to Relays.Count
-
1 do


begin


WriteStr('Relay number '); WriteLn(I);


WriteStr('
--
Active: '); WriteLn(Relays[I].Active);


WriteStr('
--
Status: '); WriteLn(Relays[I].Status);


WriteStr('
--
Viewers: '); WriteLn(Relays[I].Viewers);


WriteStr
('
--
Peak: '); WriteLn(Relays[I].Viewers_High);


WriteStr('
--
Bitrate (kbps): '); WriteLn(Relays[I].Bitrate);


WriteStr('
--
Format: '); WriteLn(Relays[I].Format);



WriteLn('');


end;



PAL.UnlockExecution;


At the 2003 BurningRealm get
-
together
,

w
e discussed using PAL + Statistic relays to write an artificial
intelligent script that would monitor the response to tracks to try and improve the rotation of the
system. For example, if a track plays and a lot of listeners disconnect
-

then we can assume

that they
did not like the track and give the track a lower rating so that is plays less. If the listener count stayed
the same, or grew
-

then we could increase the rating for the song.

We were able to come up with a script that did a lot of this, but th
ere were many subjective issues with
this design which made it hard to use in a real
-
world environment. It was however an interesting

22

experiment in the power of PAL.


Other ideas:

-

Read the status of relays. If a relay is down, take come action. (i.e. use

the WebToFile function to
notify you about this problem)


2.4.2 Encoders


Starting/Stopping encoders

This is a simple script to start all encoders at 7am in the morning, and then stop them again at 8pm:


PAL.Loop := True;


PAL.WaitForTime('07:00:00');

Enc
oders.StartAll;


PAL.WaitForTime('20:00:00');

Encoders.StopAll;


You can also start/stop a single encoder by using the encoder array index. (Remember, we count from zero)


PAL.Loop := True;


PAL.WaitForTime('07:00:00');

Encoders[0].Start;


PAL.WaitForTime(
'20:00:00');

Encoders[0].Stop;


Archiving

Since SAM Broadcaster v3.3.2 you now have the option to specify that an encoder does absolutely no
streaming, but will be used to archive the content only.

This allows you to create a second encoder
which sole dut
y is to archive the stream.


Here is an example script which is meant for two encoders, where encoder0 is the actual streaming
encoder, and encoder1 is the archiving encoder.

This script will cause the archive to be split into
multiple sections each hour,

but restarting the encoders on the hour. Note that this will not
affect

the
streaming encoder at all since it never gets stopped!


PAL.Loop := True;

PAL.WaitForTime('XX:00:00');

Encoders[1].Stop;

Encoders[1].Start;


Switching DJ's

A lot of stations have m
ore than one DJ operating the st
ation, m
ost of the time these DJ's are spread
across the world. They employ a nifty trick to switch between DJ sessions. If you new DJ wishes to
start their session, they log into the SHOUTcast admin panel and kick the curre
nt DJ's encoder, and
then quickly
connect

with their own encoder.


The following PAL script can completely automate this process:


{ About:


This script will disconnect any source connected


to a SHOUTcast server and then connects this


SAM Broadcast
er as the new source.




Usage:


a) Create a single MP3 encoder to connect to the SHOUTcast server.


b) Supply your SHOUTcast server details in the configuration section below


c) Use the Event Scheduler to start this PAL script at the correct

time.

}

{ CONFIGURATION }

{==================================================}

const shoutcast_password = 'changeme';


23

const shoutcast_host = 'localhost';

const shoutcast_port = '8000';

{==================================================}


{ IMPLEM
ENTATION }

{
--------------------------------------------------
}


{ Build URL used to send command to SHOUTcast server }

var URL : String;

URL :=
'http://admin:'+shoutcast_password+'@'+shoutcast_host+':'+shoutcast_port+'/admin.cgi?mode=kick
src';


{ Kick sou
rce from SHOUTcast server }

WebToFile('c:
\
dummy.txt',URL);


{ Now start & connect all encoders }

{ NOTE: This assumes you only have one encoder }

Encoders.StartAll;


{TIP: Use this to start a specific encoder:

Encoders[0].Start;

}


{
-----------------------
---------------------------
}


Changing song titles

The encoder class also allows you to update the current song information or title data being sent to the
streaming server.

See TitleStreamBanners.pal for an example.

Note:

This can also be done for each in
dividual encoder, in case it does not make logical sense to
change the title data on all your active encoders.

For example if you have an archiving encoder, you
most likely do not want to update the titles in this.


Embedded scripts

While SAM Broadcaster i
s one of the few encoders (in fact the only one we are aware of) that supports
embedded ID3v2 tags inside MP3 streams, due to lack of player support we will only discuss Windows
media scripts here.


Windows Media allows for embedded scripts in the audio da
ta. These scripts can be used for
displaying current song data, captioning information or synchronizing external pictures
and text of the
current audio.
We use this in our StreamAdz system to synchronize text and banner advertising
with
audio advertising.


The following example PAL script

is

for display the current local time in the captioning area of the
Windows Media player every minute.

Please note:

For you to be able to see the captioning data, you must enable the captioning view
inside your player.

Men
u
-
>Play
-
>Captions and Subtitles
-
>On if available


PAL.Loop := True;

PAL.WaitForTime('XX:XX:00');

Encoders.WriteScript('CAPTION','The local time is now '+TimeToStr(Now));



24



Other ideas:

-

Read the status string of encoders. If an encoder is down or discon
nect, take some action. (i.e. use
the WebToFile function to notify you about this problem)



Section 3
-

Advanced PAL scripts and techniques


Now that we covered some of the basic & more often used PAL objects & commands, lets move
forward and tackle some
more obscure and advanced issues.


3.1 Working directly with the database (SQL queries)

PAL allows you to directly access the database via SQL queries, and query result sets.

Be careful
when working with the database directly
-

although this can be

very u
seful, it is also easy to

make
mistakes that can corrupt your database or cause you to lose data. Make backups of your database
before you start developing and testing new scripts. Even better, get separate machines that mirrors
your production machine and

do all your development on this machine.


This section assumes you have a good knowledge of SQL statements. Teaching you SQL is beyond
the scope of this document. You will find many good resources on SQL on the web. We recommend
buying a good SQL book whe
n you start learning SQL.


SQL parameters

Before we jump into the actual SQL code we first need to explain the usage of SQL parameters inside
SQL queries.

Usually a query looks something like:

SELECT * FROM songlist WHERE (ID = 123) AND (date_played < '200
5
-
01
-
01')


Notice how the values "123" and "2005
-
01
-
01" is hardcoded into the SQL string. This makes updating
SQL queries hard & cumbersome. To solve this PAL introduces SQL parameters ... think of them as

25

variables for SQL.


For example the above query co
uld be done like this in PAL:

var Q : TDataSet;

Q := Query('SELECT * FROM songlist WHERE (ID = :ID) AND (date_played <
:DatePlayed)',[123,'2005
-
01
-
01'],True);


Notice how easy it is to replace the actual values now!

For example, let’s use an actual date fo
r the "DatePlayed":

Q := Query('SELECT * FROM songlist WHERE (ID = :ID) AND (date_played <
:DatePlayed)',[123,Now],True);


The awesome thing about SQL parameters is that PAL will automatically convert the value in the
correct format, and also in the correc
t format that your specific database expects. For example
data/time fields in databases usually expect

this to be in a very specific format. By using SQL
parameters you can be assured that the date/time will be formatted in the correct way, and thus your
s
ame script will work in all the different database types.


SQL commands

In PAL there
are

two main SQL commands
:

function Query(Sql: String; Params: Array of Variant; ReadOnly: Boolean): TDataSet;

function ExecSQL(SQL: String; Params: Array of Variant): Int
eger;


ExecSQL is used to execute an SQL statement that does not return any data. This function returns the
number of rows affected by the query.


Examples:

{Reset the balance of songs to zero}

var cnt : Integer = 0;

cnt := ExecSQL('UPDATE songlist SET bal
ance = 0',[]);

WriteLn(IntToStr(cnt)+' tracks were updated');


{Delete a certain artist from the songlist}

var cnt : Integer = 0;

cnt := ExecSQL('DELETE FROM songlist WHERE artist LIKE :artist',['Blink182']);

WriteLn(IntToStr(cnt)+' tracks were updated');


The Query command is used exclusively with the SELECT SQL command which always returns a
result set.

This result set is encapsulated inside a TDataSet class object. Using this object you can
browse the resulting rows of data, and even change the data.


E
xamples:

{This example prints the filenames of the last 10 songs that played.

Note: This script only works in the MySQL & PostgreSQL databases due to the database specific
"LIMIT 10" statement}

var Q : TDataSet;

Q := Query('SELECT filename FROM songlist OR
DER BY date_played DESC LIMIT 10',[],True);


Q.First;

while not Q.EOF do

begin


WriteLn(Q['filename']);


Q.Next;

end;


Using the TDataset class
:

Checking if the result set contains any rows:


if Q.IsEmpty then


WriteLn('The query returned zero rows!');


Looping thought all the rows:


Q.First; {Set result set to the first row}

while not Q.EOF do {Check if we have reached the end of the result set}


26

begin


WriteLn(Q['filename']); {Do something with the record}


Q.Next; {Jump to next row}

end;


Editing da
ta:

Important:

When you want to change the field data inside a row, you must query the results with the
"ReadOnly" parameter set to false. Otherwise
,

the results will be "read
-
only" and you will not be able to
change the data.


Example:

This script will se
lect all songs that start with the letter 'A' and then reset their balance to zero.


var Q : TDataSet;

Q := Query('SELECT * FROM songlist WHERE title LIKE :title',['A%'],False);


Q.First;

while not Q.EOF do

begin


WriteLn(Q['filename']);


Q.Edit;


Q['b
alance'] := 0;


Q.Post;


Q.Next;

end;


3.2 Downloading files


Downloading files for playback
:

Using PAL scripts to download content, like static MP3 files into the playlist have been used by many
broadcasters to automate their syndication.

The hourly USA

News script that is available to registered
SAM Broadcaster users is a good example of such a script.

This script basically downloads the news
every hour, adds the news to the right categories ... and then finally inserts it into the queue to be
played at

the right time.


You may contact SpacialAudio to get a copy of this script ... provided you are a registered SAM
Broadcaster client. Make sure to include your SAM registration
key

or your client email so we can
verify this.


Here follows a very simple scr
ipt that downloads a remote file and adds this file to the queue:


const File_Remote = 'http://www.spacialaudio.com/promote/samlaunch.wma';

const File_Local = 'c:
\
content
\
samlaunch.wma';


WebToFile(File_Local,File_Remote);


Queue.AddFile(File_Local,ipTop)
;

ActivePlayer.FadeToNext;


Calling external scripts

The WebToFile function is also very useful to call external scripts. For example you can write a PAL
script that calls a PHP script on your website that updates the name and details of the current DJ in
action. When a new DJ starts his set, all he needs to do is run his special PAL script and the script
does the rest.

(Can even be combined with the KickSource.pal script!)


Of course, the KickSource.pal is also a good example of how the WebToFile function
can be used to
cause an external action
-

in this case kicking a source so a new encoder can connect.



3.3 Working with files


Filename helper functions


27

Here are some useful functions to help extract certain parts of the filename. See the PAL docs for
det
ails on how to use each of these and what the return.


ExtractFileDir

ExtractFileDrive

ExtractFileExt

ExtractFilePath

ExtractFileName


File system commands

PAL can do basic file operations like:

function CopyFile(SrcFileName, DestFile: String; FailIfExists
: Boolean): Boolean;

function RenameFile(OldName: String; NewName: String): Boolean;

function DeleteFile(Filename: String): Boolean;

function FileExists(Filename: String): Boolean;


These allow you to Copy, Rename, Delete or check if a file exists respecti
vely.


Example:

if FileExists('c:
\
music
\
promo.mp3') then


if not DeleteFile('c:
\
music
\
promo.mp3') then


WriteLn('Warning: Unable to delete promo file');


These basic commands allow you to organize the files on the disk.


Working with the actual contents

of the files however is a bit trickier.

Although PAL can work with
binary files, PAL is best suited to work with text based files.

Lets start by loading the complete
contents of a text file into a string, displaying the value
-

and then writing back ano
ther value.


var Data : String = '';

const MyFile = 'c:
\
MyTestFile.txt';

if FileExists(MyFile) then


begin


Data := FileToStr(MyFile);


WriteLn('The current contents of the file:');


WriteLn(Data);


end

else


WriteLn('Data file does not exist.');



Da
ta := Data + DateTImeToStr(Now) + #13#10;


if not StrToFile(MyFile,Data) then


WriteLn('Unable to save value');


Run this script a few times to see the end result.

Note that the #13#10 is added to the string to form a "NewLine" character. #13 is the code f
or new
line, while #10 is the code for return.

+#13#10 is the same as + #13 + #10.


A more useful way to work with textfiles is to load the contents of the file into a TStringList object.

That way we can easily access the contents of each line in the textf
ile.


As an example, lets write a small script to import *.m3u playlist files into SAM. Normally M3U files are
plain text files, with each new line containing a filename of a song in the playlist. However, extended
M3U files also contains comments and addi
tional data
-

these lines start with a #

Note: Some M3U files contain relative paths. This script will not be able to handle those correctly.
Please make sure your M3U contains fully qualified paths, otherwise this script may not work as
intended.


Example

M3U file:

#EXTM3U

#EXTINF:366,Zen Arcade
-

Crazy Over You

c:
\
music
\
Zen Arcade
-

Crazy Over You.mp3


28

#EXTINF:258,REM
-

night swimming

c:
\
music
\
REM
-

night swimming.mp3

#EXTINF:285,The Cure
-

Pictures Of You

c:
\
music
\
The Cure
-

Pictures Of You.mp3

#EXTINF:
-
1
,http://sc3.audiorealm.com:12234/

http://sc3.audiorealm.com:12234/


The Playlist loader script:


const PlaylistFile = 'c:
\
playlist.m3u';


var List : TStringList;

var T : Integer;


List := TStringList.Create;


if FileExists(PlaylistFile) then


List.LoadFro
mFile(PlaylistFile)

else


WriteLn('Playlist file does not exist!: '+PlaylistFile);


{Remove all entries that are extended data}

T := 0;

while T < List.Count do

begin


if (Trim(List[T])='') or (List[T][1]='#') then


begin


WriteLn('Deleting entry: '+Lis
t[T]);


List.Delete(T);


end


else


T := T + 1;

end;


WriteLn('
-----------------------------
');


{Print a list of the files in the playlist}

{And add the list to the queue}

for T := 0 to List.Count
-
1 do


begin


WriteLn(List[T]);


Queue.AddFile(Lis
t[T],ipBottom);


end;


List.Free;


Configuration files

The stringlist object also contains

quite a few useful methods which

make

it easy to use it as a way to
store configuration information.

Values can be assigned to the stringlist with a label, and then

the
complete contents can be stored as a text file.


const ConfigFile = 'c:
\
PALConfig.ini';


var List : TStringList;

var T : Integer;


List := TStringList.Create;


{Load configuration file}

if FileExists(ConfigFile) then


List.LoadFromFile(ConfigFile)

el
se


begin


WriteLn('Configuration file does not exist, so let’s create one');


List.Values['station'] := 'My test station';


List.Values['website'] := 'http://www.palrocks.com';


List.Values['genre'] := 'Rock, Alternative';



List.SaveToFile(ConfigF
ile);


end;



{Use values}


29

WriteLn('Station name: '+List.Values['station']);

WriteLn('Website: '+List.Values['website']);

WriteLn('Genre: '+List.Values['genre']);


List.Free;


Open the ConfigFile in Notepad to see how it looks. Here is how mine looked aft
er editing:


[Station information]

station=MyTestStation

website=http://www.mysite.com

genre=Dance,Trance


As you can see this can become very handy when you need to store data between script loops, or
want permanent configuration settings that can be chan
ged externally.


3.4 Working with remote URL's

Like any good media player, SAM is also able to play remote files and URL's.

This can be static files like



http://www.spacialaudio.com/products/mp3PRO/24mp3PRO.mp3


O
r actual live broadcast streams like


htt
p://205.188.234.38:8002


This can become very useful for syndication
-

you can either directly relay another show, or maybe
play a newscast located on a remote server.


Here is an example script that can be used to relay a remote show.

{ About:


This scr
ipt will play a remote show inside SAM


The show starts at a specified time, and then ends at


another specified time.




The script also contains some error
-
correction code


that will attempt to connect to the stream up to 20 times


in case it

goes down. We schedule one song between each attempt.