File size: 3,743 Bytes
565f6e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import * as THREE from 'three';

export class AudioSystem {
  public listener: THREE.AudioListener;
  public radioAudio?: THREE.PositionalAudio;
  public soundtrackGain?: GainNode;
  
  private radioPos: THREE.Vector3;
  private audioCtx: AudioContext;

  constructor(camera: THREE.Camera, scene: THREE.Scene, radioPos: THREE.Vector3 = new THREE.Vector3(0, 100, 0)) {
    this.radioPos = radioPos;
    this.listener = new THREE.AudioListener();
    camera.add(this.listener);
    this.audioCtx = this.listener.context;

    this.setupSoundtrack();
    this.setupRadio(scene, radioPos);
  }

  private setupSoundtrack() {
    // Generate a simple ambient drone soundtrack
    const osc1 = this.audioCtx.createOscillator();
    osc1.type = "sine";
    osc1.frequency.value = 55; // Low A

    const osc2 = this.audioCtx.createOscillator();
    osc2.type = "sine";
    osc2.frequency.value = 55.5; // Slight detune for phasing

    this.soundtrackGain = this.audioCtx.createGain();
    this.soundtrackGain.gain.value = 0.3; // Default volume

    osc1.connect(this.soundtrackGain);
    osc2.connect(this.soundtrackGain);
    this.soundtrackGain.connect(this.audioCtx.destination);

    osc1.start();
    osc2.start();
  }

  private setupRadio(scene: THREE.Scene, pos: THREE.Vector3) {
    this.radioAudio = new THREE.PositionalAudio(this.listener);
    
    // We generate a continuous synth radio station instead of loading a file for simplicity
    const osc = this.audioCtx.createOscillator();
    osc.type = "sawtooth";
    osc.frequency.setValueAtTime(110, this.audioCtx.currentTime);

    // Modulate the frequency slightly to make it sound like a transmission
    const lfo = this.audioCtx.createOscillator();
    lfo.type = "sine";
    lfo.frequency.value = 2; // 2Hz modulation

    const lfoGain = this.audioCtx.createGain();
    lfoGain.gain.value = 50;
    lfo.connect(lfoGain);
    lfoGain.connect(osc.frequency);

    // Apply a bandpass filter to give it a "radio" sound
    const filter = this.audioCtx.createBiquadFilter();
    filter.type = "bandpass";
    filter.frequency.value = 800;

    const gain = this.audioCtx.createGain();
    gain.gain.value = 0.5;

    osc.connect(filter);
    filter.connect(gain);
    
    // Connect to positional audio
    this.radioAudio.setNodeSource(gain as any);
    
    osc.start();
    lfo.start();

    // Directional setup
    // Muffles when behind listener (i.e. pointing away)
    this.radioAudio.setDirectionalCone(180, 230, 0.1);
    
    this.radioAudio.setRefDistance(500);
    this.radioAudio.setMaxDistance(10000);
    this.radioAudio.setDistanceModel("exponential");
    this.radioAudio.setRolloffFactor(1.5);

    // Add a visual marker for the radio tower
    const radioMesh = new THREE.Mesh(
      new THREE.BoxGeometry(20, 200, 20),
      new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })
    );
    radioMesh.position.copy(pos);
    radioMesh.add(this.radioAudio);
    scene.add(radioMesh);
  }

  public update() {
    if (!this.radioAudio || !this.soundtrackGain) return;
    
    // Ducking logic: duck soundtrack based on distance to radio
    // The world coordinate of the listener comes from the camera
    const listenerPos = new THREE.Vector3();
    this.listener.getWorldPosition(listenerPos);
    const dist = listenerPos.distanceTo(this.radioPos);
    
    // If closer than 2000 units, start ducking the soundtrack
    const duckFactor = Math.max(0.05, Math.min(0.3, (dist / 2000) * 0.3));
    
    // Smoothly interpolate main soundtrack gain
    this.soundtrackGain.gain.setTargetAtTime(duckFactor, this.audioCtx.currentTime, 0.1);
  }

  public dispose() {
    this.radioAudio?.disconnect();
    this.soundtrackGain?.disconnect();
  }
}