type Msg { Tick }
type Car {
seat: Int
x: Float
z: Float
speed: Float
knock: Bool
}
// Um func como todo outro construtor de formas deste arquivo, e vale notar
// que um som é exatamente tão valor quanto uma box: sessenta destes são
// construídos por segundo e jogados fora, enquanto a voz do outro lado do
// nome segue rodando o tempo todo.
func noises(cars: Car[]): ui.Shape[dyn] {
mut ui.Shape[dyn] out = []
for each car in cars {
// Sustentada: dura enquanto a scene continuar a nomeando, e dobra
// com a velocidade em vez de ser reiniciada em uma nova.
append(out, ui.sound([
ui.at(car.x, 0.6, car.z),
ui.voice(ui.Voice.Engine()),
ui.pitch(0.7 + car.speed / 90.0),
ui.volume(0.6),
], "engine-{car.seat}"))
// Percutida: dispara quando o nome chega e fica surda a tudo depois
// disso. Soltar o nome e trazê-lo de volta é um segundo impacto.
if car.knock {
append(out, ui.sound([
ui.at(car.x, 0.6, car.z),
ui.voice(ui.Voice.Impact()),
ui.pitch(1.0),
ui.volume(0.8),
], "hit-{car.seat}"))
}
}
return out
}
func view(cars: Car[dyn]): ui.View {
solids := [ui.ground([ui.paint(ui.Tone.HEX("#3a3f45"))], 400.0, 400.0)]
return ui.scene([
ui.grow(1),
ui.eye(0.0, 1.4, 6.0),
ui.aim(0.0, 0.0),
ui.background(ui.Tone.HEX("#8ec5ff")),
], solids + noises(cars))
}
proc update(cars: mut Car[dyn], msg: Msg): Msg {
return msg
}
proc main(): void {
Car[dyn] grid = [Car(1, -2.0, 0.0, 40.0, false), Car(2, 2.0, -6.0, 62.0, true)]
spawn(ui.window("Som", view, update), grid)
}sound(attrs, name) é a décima forma de uma cena, e a que é ouvida de onde está em vez de desenhada ali. Ela recebe at como toda outra forma, e quatro atributos próprios: voice(Voice) é que ruído ela é, pitch(Float) o quão agudo e volume(Float) o quão alto — cada um um múltiplo daquela voz em repouso, e não hertz ou decibéis — e track(Str), uma gravação nos assets/ do próprio programa.
Ela é uma forma porque um ruído num mundo tem um lugar nele: posicionado pelo mesmo at, escrito no mesmo quadro, e ouvido do próprio eye e aim da cena, então o que se ouve à esquerda é o que se desenha à esquerda, e ele diminui conforme quem ouve se afasta.
A carga é um nome, e o nome é a identidade do som entre quadros. Uma caixa é casada com o que estava no seu lugar no quadro anterior; um oscilador reconstruído assim estalaria sessenta vezes por segundo. Então um nome que estava no último quadro continua, um que acabou de chegar começa, e um que sumiu vai se apagando. O que ele faz então é da voz:
Engine, Tyres, Brakes, Wind, Crowd e Music são sustentadas: duram enquanto a cena continuar nomeando-as.Impact, Chime e Talk são percutidas: disparam uma vez, quando o nome chega. Nomear "hit-3" enquanto um carro souber que foi atingido é um impacto; deixar o nome sumir e trazê-lo de volta é um segundo.Music e Talk não têm onde ficar, então nenhuma das duas é posicionada entre os lados.Toda voz é feita de osciladores e ruído filtrado, a menos que track nomeie um arquivo. Uma voz sustentada repete sua gravação, sendo pitch a velocidade de reprodução; uma percutida a toca uma vez, e não é ouvida se a gravação ainda estiver sendo decodificada quando o som chegar. Um arquivo ausente ou que não se consegue decodificar volta para a voz feita. Um navegador não toca som antes do primeiro clique ou tecla, e o runtime tenta de novo a cada um.