1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "object/ambient_sound.hpp"
18
19#include <limits>
20
21#include "audio/sound_manager.hpp"
22#include "audio/sound_source.hpp"
23#include "editor/editor.hpp"
24#include "object/camera.hpp"
25#include "supertux/sector.hpp"
26#include "util/reader_mapping.hpp"
27#include "video/drawing_context.hpp"
28
29AmbientSound::AmbientSound(const ReaderMapping& mapping) :
30 MovingObject(mapping),
31 ExposedObject<AmbientSound, scripting::AmbientSound>(this),
32 sample(),
33 sound_source(),
34 latency(),
35 distance_factor(),
36 distance_bias(),
37 silence_distance(),
38 maximumvolume(),
39 targetvolume(),
40 currentvolume(0)
41{
42 m_col.m_group = COLGROUP_DISABLED;
43
44 float w, h;
45 mapping.get("x", m_col.m_bbox.get_left(), 0.0f);
46 mapping.get("y", m_col.m_bbox.get_top(), 0.0f);
47 mapping.get("width" , w, 32.0f);
48 mapping.get("height", h, 32.0f);
49 m_col.m_bbox.set_size(w, h);
50
51 mapping.get("distance_factor",distance_factor, 0.0f);
52 mapping.get("distance_bias" ,distance_bias , 0.0f);
53 mapping.get("sample" ,sample , "");
54 mapping.get("volume" ,maximumvolume , 1.0f);
55
56 // square all distances (saves us a sqrt later)
57
58 if (!Editor::is_active()) {
59 distance_bias*=distance_bias;
60 distance_factor*=distance_factor;
61 }
62
63 // set default silence_distance
64
65 if (distance_factor == 0)
66 silence_distance = std::numeric_limits<float>::max();
67 else
68 silence_distance = 1/distance_factor;
69
70 mapping.get("silence_distance",silence_distance);
71
72 if (!Editor::is_active()) {
73 sound_source.reset(); // not playing at the beginning
74 SoundManager::current()->preload(sample);
75 }
76 latency=0;
77}
78
79AmbientSound::AmbientSound(const Vector& pos, float factor, float bias, float vol, const std::string& file) :
80 ExposedObject<AmbientSound, scripting::AmbientSound>(this),
81 sample(file),
82 sound_source(),
83 latency(0),
84 distance_factor(factor * factor),
85 distance_bias(bias * bias),
86 silence_distance(),
87 maximumvolume(vol),
88 targetvolume(),
89 currentvolume()
90{
91 m_col.m_group = COLGROUP_DISABLED;
92
93 m_col.m_bbox.set_pos(pos);
94 m_col.m_bbox.set_size(32, 32);
95
96 // set default silence_distance
97
98 if (distance_factor == 0)
99 silence_distance = std::numeric_limits<float>::max();
100 else
101 silence_distance = 1/distance_factor;
102
103 if (!Editor::is_active()) {
104 sound_source.reset(); // not playing at the beginning
105 SoundManager::current()->preload(sample);
106 }
107}
108
109AmbientSound::~AmbientSound()
110{
111 stop_playing();
112}
113
114ObjectSettings
115AmbientSound::get_settings()
116{
117 ObjectSettings result = MovingObject::get_settings();
118
119 result.add_sound(_("Sound"), &sample, "sample");
120 result.add_float(_("Distance factor"), &distance_factor, "distance_factor");
121 result.add_float(_("Distance bias"), &distance_bias, "distance_bias");
122 result.add_float(_("Volume"), &maximumvolume, "volume");
123
124 result.reorder({"sample", "distance_factor", "distance_bias", "volume", "region", "name", "x", "y", "width", "height"});
125
126 return result;
127}
128
129void
130AmbientSound::after_editor_set()
131{
132}
133
134void
135AmbientSound::stop_playing()
136{
137 sound_source.reset();
138}
139
140void
141AmbientSound::start_playing()
142{
143 if (Editor::is_active()) return;
144
145 try {
146 sound_source = SoundManager::current()->create_sound_source(sample);
147 if (!sound_source)
148 throw std::runtime_error("file not found");
149
150 sound_source->set_gain(0);
151 sound_source->set_looping(true);
152 currentvolume=targetvolume=1e-20f;
153 sound_source->play();
154 } catch(std::exception& e) {
155 log_warning << "Couldn't play '" << sample << "': " << e.what() << "" << std::endl;
156 sound_source.reset();
157 remove_me();
158 }
159}
160
161void
162AmbientSound::update(float dt_sec)
163{
164 if (latency-- <= 0) {
165 float px,py;
166 float rx,ry;
167
168 // Camera position
169 px=Sector::get().get_camera().get_center().x;
170 py=Sector::get().get_camera().get_center().y;
171
172 // Relate to which point in the area
173 rx=px<m_col.m_bbox.get_left()?m_col.m_bbox.get_left():
174 (px<m_col.m_bbox.get_right()?px:m_col.m_bbox.get_right());
175 ry=py<m_col.m_bbox.get_top()?m_col.m_bbox.get_top():
176 (py<m_col.m_bbox.get_bottom()?py:m_col.m_bbox.get_bottom());
177
178 // calculate square of distance
179 float sqrdistance=(px-rx)*(px-rx)+(py-ry)*(py-ry);
180 sqrdistance-=distance_bias;
181
182 // inside the bias: full volume (distance 0)
183 if (sqrdistance<0)
184 sqrdistance=0;
185
186 // calculate target volume - will never become 0
187 targetvolume=1/(1+sqrdistance*distance_factor);
188 float rise=targetvolume/currentvolume;
189
190 // rise/fall half life?
191 currentvolume *= powf(rise, dt_sec * 10.0f);
192 currentvolume += 1e-6f; // volume is at least 1e-6 (0 would never rise)
193
194 if (sound_source != nullptr) {
195
196 // set the volume
197 sound_source->set_gain(currentvolume*maximumvolume);
198
199 if (sqrdistance>=silence_distance && currentvolume < 1e-3f)
200 stop_playing();
201 latency=0;
202 } else {
203 if (sqrdistance<silence_distance) {
204 start_playing();
205 latency=0;
206 }
207 else // set a reasonable latency
208 latency = static_cast<int>(0.001f / distance_factor);
209 //(int)(10*((sqrdistance-silence_distance)/silence_distance));
210 }
211 }
212
213 // heuristically measured "good" latency maximum
214
215 // if (latency>0.001/distance_factor)
216 // latency=
217}
218
219#ifndef SCRIPTING_API
220void
221AmbientSound::set_pos(const Vector& pos)
222{
223 MovingObject::set_pos(pos);
224}
225#endif
226
227void
228AmbientSound::set_pos(float x, float y)
229{
230 m_col.m_bbox.set_pos(Vector(x, y));
231}
232
233float
234AmbientSound::get_pos_x() const
235{
236 return m_col.m_bbox.get_left();
237}
238
239float
240AmbientSound::get_pos_y() const
241{
242 return m_col.m_bbox.get_top();
243}
244
245HitResponse
246AmbientSound::collision(GameObject& other, const CollisionHit& hit_)
247{
248 return ABORT_MOVE;
249}
250
251void
252AmbientSound::draw(DrawingContext& context)
253{
254 if (Editor::is_active()) {
255 context.color().draw_filled_rect(m_col.m_bbox, Color(0.0f, 0.0f, 1.0f, 0.6f),
256 0.0f, LAYER_OBJECTS);
257 }
258}
259
260/* EOF */
261