Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 84 additions & 33 deletions agenda/agenda.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
"encoding/hex"
"fmt"
"os"
"slices"
"strings"
"time"

"github.com/gofrs/uuid"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)

var fileFormats = []string{"mp3", "m4a", "webm"}
Expand All @@ -22,8 +23,8 @@ func New(filename string) (*Agenda, error) {
}

a := new(Agenda)
err = yaml.Unmarshal(data, a)
if err != nil {

if err := yaml.Unmarshal(data, a); err != nil {
return nil, fmt.Errorf("failed to read YAML: %w", err)
}

Expand Down Expand Up @@ -104,12 +105,7 @@ func (a *Agenda) AllTracks() (out []*Track) {
// Track already-seen audio files so that we do not list them twice
var seen []string
unseen := func(id string) bool {
for _, i := range seen {
if i == id {
return false
}
}
return true
return !slices.Contains(seen, id)
}

// Load Announcements first
Expand Down Expand Up @@ -203,6 +199,9 @@ type Room struct {
// played.
Sources []*Source `json:"sources" yaml:"sources"`

// PointsOfInterest are non-source points in the room which are made available as targets to screen accessibility tooling.
PointsOfInterest []*PointOfInterest `json:"pointsOfInterest" yaml:"pointsOfInterest"`

// RoomTracks is a list of audio tracks to be played in a room, sourced from
// everywhere. This is generally exclusive with Sources.
RoomTracks []*Track `json:"roomTracks" yaml:"roomTracks"`
Expand All @@ -223,6 +222,12 @@ func (r *Room) generateIDs(a *Agenda) error {
return err
}

for _, p := range r.PointsOfInterest {
if err := p.generateID(); err != nil {
return fmt.Errorf("failed to generate ID for point of interest: %w", err)
}
}

for _, s := range r.Sources {
err = s.generateIDs(a)
if err != nil {
Expand Down Expand Up @@ -281,12 +286,7 @@ func (r *Room) AllTracks() (out []*Track) {
// Track already-seen audio files so that we do not list them twice
var seen []string
unseen := func(id string) bool {
for _, i := range seen {
if i == id {
return false
}
}
return true
return !slices.Contains(seen, id)
}

// Iterate Sources first
Expand All @@ -313,13 +313,13 @@ func (r *Room) AllTracks() (out []*Track) {
// Dimensions describe the dimensions of a space.
type Dimensions struct {
// Width is the left-to-right dimension.
Width float64 `json:"width" yaml:"width"`
Width float64 `json:"width" yaml:"width"`

// Height is the up-to-down dimension (the height of a room).
Height float64 `json:"height" yaml:"height"`

// Depth is the forward-to-backward dimension.
Depth float64 `json:"depth" yaml:"depth"`
Depth float64 `json:"depth" yaml:"depth"`
}

// Surfaces describe the surface material of a room.
Expand All @@ -334,9 +334,8 @@ type Surfaces struct {
Up string `json:"up" yaml:"up"`
}

// Source describes a unique audio sequence and location
type Source struct {

// PointOfInterest describes a point of interest.
type PointOfInterest struct {
// ID is the generated unique identifier
ID string `json:"id" yaml:"-"`

Expand All @@ -346,34 +345,72 @@ type Source struct {
// Location indicates a specific 3-dimensional coordinate in the room from
// which the audio of this source emanates
Location Point `json:"location" yaml:"location"`
}

func (p *PointOfInterest) generateID() error {
// If we don't have a name, generate one
if p.Name == "" {
p.Name = uuid.Must(uuid.NewV1()).String()
}

p.ID = hashString(fmt.Sprintf("poi-%s", p.Name))

return nil
}

// Source describes a unique audio sequence and location
type Source struct {
PointOfInterest `json:",inline" yaml:",inline"`

// AutoTracks defines a pattern by which cue-track mappings are generated.
// NOTE: either RoomTracks or AutoTracks should be defined, but not both.
AutoTracks *AutoTracks `json:"autoTracks" yaml:"autoTracks"`

// Tracks is the list of audio tracks which should be played upon reaching a
// particular cue
Tracks []*Track `json:"tracks" yaml:"tracks"`
}

func (s *Source) generateIDs(a *Agenda) error {
err := s.generateID()
if err != nil {
if err := s.generateID(); err != nil {
return err
}

for _, t := range s.Tracks {
if err = t.generateID(a); err != nil {
return err
if s.AutoTracks != nil {
var autoTracks []*Track

for _, c := range a.Cues {
if slices.Contains(s.AutoTracks.IgnoreCues, c.Name) {
continue
}

t := &Track{
AudioFilePrefix: s.AutoTracks.Prefix + c.Name,
Cue: c.Name,
}

if err := t.generateID(a); err != nil {
if s.AutoTracks.IgnoreMissing {
continue
}

return fmt.Errorf("failed to generate track %s for cue %s in source %s: %w",
t.AudioFilePrefix, t.Cue, s.Name, err)
}

autoTracks = append(autoTracks, t)
}
}

return nil
}
s.Tracks = autoTracks

func (s *Source) generateID() error {
// If we don't have a name, generate one
if s.Name == "" {
s.Name = uuid.Must(uuid.NewV1()).String()
return nil
}

s.ID = hashString(fmt.Sprintf("source-%s", s.Name))
for _, t := range s.Tracks {
if err := t.generateID(a); err != nil {
return err
}
}

return nil
}
Expand Down Expand Up @@ -460,6 +497,20 @@ func (t *Track) generateID(a *Agenda) error {
return nil
}

// AutoTracks defines a method by which cue-track mappings may be autogenerated.
type AutoTracks struct {
// Prefix defines a prefix which should be added to each auto-generated audio filename.
// Note that this does NOT presume a directory separation between the prefix and the filename.
// That is, a prefix of "alpha/beta" and a cue name of "gamma" would result in a file name "alpha/betagamma.webm".
Prefix string `json:"prefix" yaml:"prefix"`

// IgnoreMissing indicates that any tracks which do not exist should simply be ignored.
IgnoreMissing bool `json:"ignoreMissing" yaml:"ignoreMissing"`

// IgnoreCues provides a list of cues for which no track should be bound.
IgnoreCues []string
}

// Point is a 3-dimensional point in space
type Point struct {
X float64 `json:"x" yaml:"x"`
Expand Down
31 changes: 27 additions & 4 deletions src/room.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,17 +242,40 @@ export class SpatialRoom extends EventTarget {
.attr("text-anchor", "middle")
.attr("class", "sourceText")
.attr("fill", "grey")
.text(function(d) { return d.name })
.text(function(d) {
return d.name
})
.merge(labels)
.attr("x", function(d) { return self.scaleX(d.location.x + self.data.dimensions.width/2) })
.attr("y", function(d) { return self.scaleY(self.data.dimensions.depth/2-d.location.z) })
.attr("x", function(d) {
return self.scaleX(d.location.x + self.data.dimensions.width/2)
})
.attr("y", function(d) {
return self.scaleY(self.data.dimensions.depth/2-d.location.z)
})
.attr("dy", "2em")

// Draw points of interest, which are hidden text labels only.
let pois = self.svg.selectAll("text.poi").data(self.data.pointsOfInterest)
pois.enter().append("text")
.attr("text-anchor", "middle")
.attr("class", "poi")
.text(function(d) {
return d.name
})
.merge(pois)
.attr("x", function(d) {
return self.scaleX(
d.location.x + self.data.dimensions.width / 2)
})
.attr("y", function(d) {
return self.scaleY(
self.data.dimensions.depth/2-d.location.z)
})

this.dispatchEvent(new Event("redraw"))
}
}


function loadAudio(room) {
loadAudioResonance(room)
}
Expand Down