2026-02-20 20:20:25 -07:00
|
|
|
package main
|
|
|
|
|
|
2026-02-20 21:02:18 -07:00
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"math/rand/v2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var trackNamePool = []string{
|
|
|
|
|
"Lighting", "Fill Light", "Spots", "Video", "Video OVL",
|
|
|
|
|
"Audio", "SFX", "Ambience", "Pyro", "Fog", "Motors",
|
|
|
|
|
"Follow Spot", "Haze", "Projector", "LED Wall",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lightNamePool = []string{
|
|
|
|
|
"Wash", "Focus", "Spot", "Amber", "Blue", "Cool", "Warm",
|
|
|
|
|
"Flood", "Strobe", "Blackout", "Dim", "Bright", "Sunrise",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mediaNamePool = []string{
|
|
|
|
|
"Loop", "Projection", "Background", "Overlay", "Flash",
|
|
|
|
|
"Ambience", "Underscore", "Sting", "Bumper", "Transition",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var delayNamePool = []string{
|
|
|
|
|
"1s Delay", "2s Delay", "3s Delay", "5s Delay", "Hold",
|
|
|
|
|
}
|
2026-02-20 20:20:25 -07:00
|
|
|
|
2026-02-20 22:39:29 -07:00
|
|
|
func GenerateMockShow(numTracks, numScenes, avgCuesPerScene, avgBlocksPerCue int) *Show {
|
2026-02-20 21:02:18 -07:00
|
|
|
rng := rand.New(rand.NewPCG(42, 0))
|
|
|
|
|
|
2026-02-20 20:20:25 -07:00
|
|
|
show := &Show{}
|
2026-02-20 21:02:18 -07:00
|
|
|
blockIdx := 0
|
2026-02-20 22:31:04 -07:00
|
|
|
curCueName := ""
|
|
|
|
|
nextBlockID := func(trackIdx int) string {
|
|
|
|
|
id := fmt.Sprintf("%s-t%d-b%d", curCueName, trackIdx, blockIdx)
|
2026-02-20 21:02:18 -07:00
|
|
|
blockIdx++
|
|
|
|
|
return id
|
|
|
|
|
}
|
2026-02-20 20:20:25 -07:00
|
|
|
|
2026-02-20 21:02:18 -07:00
|
|
|
names := make([]string, len(trackNamePool))
|
|
|
|
|
copy(names, trackNamePool)
|
|
|
|
|
rng.Shuffle(len(names), func(i, j int) {
|
|
|
|
|
names[i], names[j] = names[j], names[i]
|
|
|
|
|
})
|
2026-02-20 20:20:25 -07:00
|
|
|
for i := range numTracks {
|
2026-02-20 21:02:18 -07:00
|
|
|
name := names[i%len(names)]
|
|
|
|
|
if i >= len(names) {
|
|
|
|
|
name = fmt.Sprintf("%s %d", name, i/len(names)+1)
|
|
|
|
|
}
|
2026-02-20 20:20:25 -07:00
|
|
|
show.Tracks = append(show.Tracks, &Track{
|
|
|
|
|
ID: fmt.Sprintf("track_%d", i),
|
2026-02-20 21:02:18 -07:00
|
|
|
Name: name,
|
2026-02-20 20:20:25 -07:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 21:02:18 -07:00
|
|
|
randBlock := func(trackIdx int) *Block {
|
|
|
|
|
r := rng.Float64()
|
|
|
|
|
var typ, name string
|
|
|
|
|
var loop bool
|
|
|
|
|
switch {
|
|
|
|
|
case r < 0.30:
|
|
|
|
|
typ, name = "light", lightNamePool[rng.IntN(len(lightNamePool))]
|
|
|
|
|
case r < 0.55:
|
|
|
|
|
typ, name = "media", mediaNamePool[rng.IntN(len(mediaNamePool))]
|
|
|
|
|
case r < 0.70:
|
|
|
|
|
typ, name, loop = "media", mediaNamePool[rng.IntN(len(mediaNamePool))], true
|
|
|
|
|
case r < 0.80:
|
|
|
|
|
typ, name = "delay", delayNamePool[rng.IntN(len(delayNamePool))]
|
|
|
|
|
default:
|
|
|
|
|
typ, name = "light", lightNamePool[rng.IntN(len(lightNamePool))]
|
|
|
|
|
}
|
|
|
|
|
return &Block{
|
2026-02-20 22:31:04 -07:00
|
|
|
ID: nextBlockID(trackIdx),
|
2026-02-20 21:02:18 -07:00
|
|
|
Type: typ,
|
|
|
|
|
Track: fmt.Sprintf("track_%d", trackIdx),
|
|
|
|
|
Name: name,
|
|
|
|
|
Loop: loop,
|
2026-02-20 20:20:25 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 22:31:04 -07:00
|
|
|
chainFromByTrack := make(map[int]*Block)
|
|
|
|
|
needsEnd := make(map[string]*Block)
|
|
|
|
|
allowedTracks := make(map[int]bool, numTracks)
|
|
|
|
|
for i := range numTracks {
|
|
|
|
|
allowedTracks[i] = true
|
|
|
|
|
}
|
2026-02-20 21:02:18 -07:00
|
|
|
|
2026-02-20 22:39:29 -07:00
|
|
|
for scene := 1; scene <= numScenes; scene++ {
|
|
|
|
|
cuesInScene := 1 + rng.IntN(avgCuesPerScene*2)
|
2026-02-20 21:02:18 -07:00
|
|
|
|
2026-02-20 21:14:50 -07:00
|
|
|
for intra := 1; intra <= cuesInScene; intra++ {
|
2026-02-20 22:31:04 -07:00
|
|
|
for trackIdx, blk := range chainFromByTrack {
|
|
|
|
|
if needsEnd[blk.ID] == nil {
|
|
|
|
|
delete(chainFromByTrack, trackIdx)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-20 21:14:50 -07:00
|
|
|
|
2026-02-20 22:31:04 -07:00
|
|
|
curCueName = fmt.Sprintf("S%d Q%d", scene, intra)
|
2026-02-20 21:14:50 -07:00
|
|
|
cue := &Block{
|
2026-02-20 22:31:04 -07:00
|
|
|
ID: curCueName,
|
2026-02-20 21:14:50 -07:00
|
|
|
Type: "cue",
|
2026-02-20 22:31:04 -07:00
|
|
|
Name: curCueName,
|
2026-02-20 21:14:50 -07:00
|
|
|
}
|
|
|
|
|
show.Blocks = append(show.Blocks, cue)
|
|
|
|
|
|
2026-02-20 22:39:29 -07:00
|
|
|
blocksThisCue := 1 + rng.IntN(avgBlocksPerCue*2)
|
2026-02-20 21:14:50 -07:00
|
|
|
cueTargets := []TriggerTarget{}
|
2026-02-20 22:31:04 -07:00
|
|
|
for id, blk := range needsEnd {
|
|
|
|
|
cueTargets = append(cueTargets, TriggerTarget{Block: blk.ID, Hook: "END"})
|
|
|
|
|
delete(needsEnd, id)
|
|
|
|
|
for ti := range numTracks {
|
|
|
|
|
if chainFromByTrack[ti] == blk {
|
|
|
|
|
allowedTracks[ti] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-20 21:29:16 -07:00
|
|
|
for range blocksThisCue {
|
|
|
|
|
trackIdx := rng.IntN(numTracks)
|
2026-02-20 22:31:04 -07:00
|
|
|
if !allowedTracks[trackIdx] {
|
2026-02-20 21:29:16 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2026-02-20 21:14:50 -07:00
|
|
|
block := randBlock(trackIdx)
|
|
|
|
|
show.Blocks = append(show.Blocks, block)
|
2026-02-20 22:31:04 -07:00
|
|
|
if prev := chainFromByTrack[trackIdx]; prev != nil {
|
2026-02-20 21:14:50 -07:00
|
|
|
show.Triggers = append(show.Triggers, &Trigger{
|
2026-02-20 21:29:16 -07:00
|
|
|
Source: TriggerSource{Block: prev.ID, Signal: "END"},
|
|
|
|
|
Targets: []TriggerTarget{{Block: block.ID, Hook: "START"}},
|
2026-02-20 21:14:50 -07:00
|
|
|
})
|
2026-02-20 22:31:04 -07:00
|
|
|
delete(needsEnd, prev.ID)
|
2026-02-20 21:29:16 -07:00
|
|
|
} else {
|
|
|
|
|
cueTargets = append(cueTargets, TriggerTarget{Block: block.ID, Hook: "START"})
|
2026-02-20 21:02:18 -07:00
|
|
|
}
|
2026-02-20 22:31:04 -07:00
|
|
|
if !block.hasDefinedTiming() {
|
|
|
|
|
needsEnd[block.ID] = block
|
|
|
|
|
allowedTracks[trackIdx] = false
|
|
|
|
|
}
|
|
|
|
|
chainFromByTrack[trackIdx] = block
|
2026-02-20 21:14:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(cueTargets) > 0 {
|
2026-02-20 21:02:18 -07:00
|
|
|
show.Triggers = append(show.Triggers, &Trigger{
|
2026-02-20 21:14:50 -07:00
|
|
|
Source: TriggerSource{Block: cue.ID, Signal: "GO"},
|
|
|
|
|
Targets: cueTargets,
|
2026-02-20 21:02:18 -07:00
|
|
|
})
|
2026-02-20 20:20:25 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-20 21:18:32 -07:00
|
|
|
|
|
|
|
|
endTargets := []TriggerTarget{}
|
2026-02-20 22:31:04 -07:00
|
|
|
for id, blk := range needsEnd {
|
2026-02-20 21:18:32 -07:00
|
|
|
endTargets = append(endTargets, TriggerTarget{Block: blk.ID, Hook: "END"})
|
2026-02-20 22:31:04 -07:00
|
|
|
delete(needsEnd, id)
|
|
|
|
|
for ti := range numTracks {
|
|
|
|
|
if chainFromByTrack[ti] == blk {
|
|
|
|
|
allowedTracks[ti] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-20 21:18:32 -07:00
|
|
|
}
|
2026-02-20 22:31:04 -07:00
|
|
|
if len(endTargets) > 0 {
|
|
|
|
|
endCueName := fmt.Sprintf("S%d End", scene)
|
2026-02-20 21:18:32 -07:00
|
|
|
endCue := &Block{
|
2026-02-20 22:31:04 -07:00
|
|
|
ID: endCueName,
|
2026-02-20 21:18:32 -07:00
|
|
|
Type: "cue",
|
2026-02-20 22:31:04 -07:00
|
|
|
Name: endCueName,
|
2026-02-20 21:18:32 -07:00
|
|
|
}
|
|
|
|
|
show.Blocks = append(show.Blocks, endCue)
|
|
|
|
|
show.Triggers = append(show.Triggers, &Trigger{
|
|
|
|
|
Source: TriggerSource{Block: endCue.ID, Signal: "GO"},
|
|
|
|
|
Targets: endTargets,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-02-20 20:20:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return show
|
|
|
|
|
}
|