// License : GNU/GPL 2+
VAR_INPUT
volume : INT;
base_speed : TIME;
song_DB : BLOCK_DB;
END_VAR
VAR
notes_index : INT;
octave_shift : INT;
sharp : BOOL;
tie : BOOL;
wait_time_active : BOOL;
wait_timestamp : TIME;
prev_note_id : BYTE;
prev_note_value : BYTE;
END_VAR
VAR_TEMP
cur_note : BYTE;
cur_note_id : BYTE;
cur_note_value : BYTE;
base_freq : DINT;
deci_hz : INT;
period : WORD;
remaining_ms : TIME;
now : TIME;
END_VAR
BEGIN
// Get the current time
CALL SFC 64 (
RET_VAL := #now
)
// Check if we need to wait
U #wait_time_active
SPBN NWAI
L #now
L #wait_timestamp
-D
SLD 1 // sign extension
SSD 1 // sign extension
L 0
I
SPB NEND
L 0
T #notes_index
L DBB 0
T #cur_note
NEND: NOP 0
// Extract note ID
L #cur_note
UW W#16#0F
T #cur_note_id
// Extract note value
L #cur_note
UW W#16#70
SRW 4
T #cur_note_value
// If this is a special note, handle it.
L #cur_note_id
L W#16#F
==I
SPB SPEC
// Check if the current note is equal to the previous note.
L #cur_note_id
L #prev_note_id
==I
SPB SAME
// Tune the note.
// Get the note frequency in deci-Hz.
TUNE: AUF "DB_notes"
U #sharp
SPBN NOSH
AUF "DB_notes_sharp"
NOSH: L #cur_note_id
SLW 4
LAR1
L DBW [AR1, P#0.0]
T #deci_hz
// Octave shift the frequency.
L #octave_shift
L 0
>=I
SPB POSO
// Negative octave shift
L #octave_shift
NEGI
L #deci_hz
SRW
SPA OCTE
// Positive octave shift
POSO: L #octave_shift
L #deci_hz
SLW
OCTE: T #deci_hz
// Calculate the PWM period from the frequency.
L #deci_hz
L 0
==I
SPB NPER
L L#20000000 // pwm_baseFreqHz * 10
L #deci_hz
/D
NPER: T #period
// Write period to PWM output hardware
CALL "FC_setPWM" (
period := #period,
volume := #volume,
)
// Calculate the wait timestamp from the current note value.
CALL "FC_calcWaitTime" (
now := #now,
note_value := #cur_note_value,
base_speed := #base_speed,
wait_timestamp := #wait_timestamp,
wait_time_active := #wait_time_active,
)
// We are done with this note.
// Reset all flags.
CLR
= #sharp
= #tie
L #cur_note_id
T #prev_note_id
L #cur_note_value
T #prev_note_value
SPA NEXT
// If this is a special flags note, handle it.
SPEC: L #cur_note_value
SPL INVA // Invalid
SPA SHRP // Activate "sharp" (Kreuz)
SPA DOT // Activate "dot" (Punktierung)
SPA TIE // Activate "tie" (Haltebogen)
SPA SHUP // Shift one octave up
SPA SHDN // Shift one octave down
SPA INVA // Invalid
SPA INVA // Invalid
SPA INVA // Invalid
INVA: BEA
// Activate "sharp" (Kreuz)
SHRP: SET
= #sharp
SPA NEXT
// Activate "dot" (Punktierung)
// Get the value of the previous note (that is the currenly tuned one)
// and wait for half its time.
DOT: L #prev_note_value
+ 1 // Decrease value by 50%
T #prev_note_value
CALL "FC_calcWaitTime" (
now := #now,
note_value := #prev_note_value,
base_speed := #base_speed,
wait_timestamp := #wait_timestamp,
wait_time_active := #wait_time_active,
)
SPA NEXT
// Activate "tie" (Haltebogen)
TIE: SET
= #tie
SPA NEXT
// Shift one octave up
SHUP: L #octave_shift
+ 1
T #octave_shift
SPA NEXT
// Shift one octave down
SHDN: L #octave_shift
+ -1
T #octave_shift
SPA NEXT
// The current note is the same as the previous one.
// If "tie" is not active, deactivate the PWM and wait
// a tiny amount of time. Then re-activate the note.
// If "tie" is active, tune the note.
SAME: U #tie
SPB TUNE
CALL "FC_setPWM" (
period := W#16#0,
volume := #volume,
)
L #now
L T#30ms
+D
UD DW#16#7FFFFFFF
T #wait_timestamp
SET
= #wait_time_active
// Set "tie" to re-activate the note
// after the wait time has passed.
SET
= #tie
SPB NINC
// Increment notes index
NEXT: L #notes_index
+ 1
T #notes_index
NINC: BE
END_FUNCTION_BLOCK
FUNCTION "FC_setPWM" : VOID
VAR_INPUT
period : WORD;
volume : INT;
END_VAR
VAR_TEMP
volume_shift : INT;
END_VAR
BEGIN
// Calculate the duty cycle shift from the volume.
// #volume can be 0-15
L 15
L #volume
-I
+ 1
T #volume_shift
// Write period to PWM output hardware
L #period
T "pwm_period"
// Write duty cycle to PWM output hardware
L #volume_shift
L #period
SRW
T "pwm0"
END_FUNCTION
FUNCTION "FC_calcWaitTime" : VOID
VAR_INPUT
now : TIME;
note_value : BYTE;
base_speed : TIME;
END_VAR
VAR_OUTPUT
wait_timestamp : TIME;
wait_time_active : BOOL;
END_VAR
VAR_TEMP
remaining_ms : TIME;
END_VAR
BEGIN
// Calculate the remaining time from the note value.
L #note_value
L #base_speed // base speed, in milliseconds
SRW
T #remaining_ms
// Calculate the next timestamp to wait for.
L #now
L #remaining_ms
+D
UD DW#16#7FFFFFFF
T #wait_timestamp
SET
= #wait_time_active
END_FUNCTION
]]>