55
56
Sound(const std::shared_ptr<Clock>& clock,
56
const std::string& filename,
57
const std::string& uri,
57
58
unsigned int volume,
58
59
unsigned int duration_minutes,
64
m_canberra_id(get_next_canberra_id()),
65
m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, duration_minutes, 0.0))
65
m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, (int)duration_minutes, 0.0))
68
static std::once_flag once;
69
std::call_once(once, [](){
70
GError* error = nullptr;
71
gst_init_check (nullptr, nullptr, &error);
74
g_critical("Unable to play alarm sound: %s", error->message);
69
81
g_debug("Looping '%s' until cutoff time %s",
71
83
m_loop_end_time.format("%F %T").c_str());
75
g_debug("Playing '%s' once", m_filename.c_str());
78
const auto rv = ca_context_create(&m_context);
85
g_warning("Failed to create canberra context: %s", ca_strerror(rv));
87
g_debug("Playing '%s' once", m_uri.c_str());
90
m_play = gst_element_factory_make("playbin", "play");
92
auto bus = gst_pipeline_get_bus(GST_PIPELINE(m_play));
93
m_watch_source = gst_bus_add_watch(bus, bus_callback, this);
94
gst_object_unref(bus);
94
g_clear_pointer(&m_context, ca_context_destroy);
103
g_source_remove(m_watch_source);
105
if (m_play != nullptr)
107
gst_element_set_state (m_play, GST_STATE_NULL);
108
g_clear_pointer (&m_play, gst_object_unref);
101
if (m_context != nullptr)
103
const auto rv = ca_context_cancel(m_context, m_canberra_id);
104
if (rv != CA_SUCCESS)
105
g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
110
g_source_remove(m_loop_tag);
116
if (m_play != nullptr)
118
gst_element_set_state (m_play, GST_STATE_PAUSED);
117
auto context = m_context;
118
g_return_if_fail(context != nullptr);
120
const auto filename = m_filename.c_str();
121
const float gain = get_gain_level(m_volume);
123
ca_proplist* props = nullptr;
124
ca_proplist_create(&props);
125
ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
126
ca_proplist_setf(props, CA_PROP_CANBERRA_VOLUME, "%f", gain);
127
const auto rv = ca_context_play_full(context, m_canberra_id, props,
128
on_done_playing, this);
129
if (rv != CA_SUCCESS)
130
g_warning("Unable to play '%s': %s", filename, ca_strerror(rv));
132
g_clear_pointer(&props, ca_proplist_destroy);
135
static float get_gain_level(unsigned int volume)
137
const unsigned int clamped_volume = CLAMP(volume, 1, 100);
139
/* This range isn't set in stone --
140
arrived at from manual tests on Nextus 4 */
141
constexpr float gain_low = -10;
142
constexpr float gain_high = 10;
144
constexpr float gain_range = gain_high - gain_low;
145
return gain_low + (gain_range * (clamped_volume / 100.0f));
148
static void on_done_playing(ca_context*, uint32_t, int rv, void* gself)
150
// if we still need to loop, wait a second, then play it again
152
if (rv == CA_SUCCESS)
124
g_return_if_fail(m_play != nullptr);
126
g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(),
127
"volume", get_volume(),
129
gst_element_set_state (m_play, GST_STATE_PLAYING);
132
// convert settings range [1..100] to gst playbin's range is [0...1.0]
133
gdouble get_volume() const
135
constexpr int in_range_lo = 1;
136
constexpr int in_range_hi = 100;
137
const double in = CLAMP(m_volume, in_range_lo, in_range_hi);
138
const double pct = (in - in_range_lo) / (in_range_hi - in_range_lo);
140
constexpr double out_range_lo = 0.0;
141
constexpr double out_range_hi = 1.0;
142
return out_range_lo + (pct * (out_range_hi - out_range_lo));
145
static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself)
147
auto self = static_cast<Sound*>(gself);
149
if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) &&
151
(self->m_clock->localtime() < self->m_loop_end_time))
154
auto self = static_cast<Self*>(gself);
155
if ((self->m_loop_tag == 0) &&
157
(self->m_clock->localtime() < self->m_loop_end_time))
159
self->m_loop_tag = g_timeout_add_seconds(1, play_idle, self);
153
gst_element_seek(self->m_play,
160
(gint64)GST_CLOCK_TIME_NONE);
164
static gboolean play_idle(gpointer gself)
166
auto self = static_cast<Self*>(gself);
167
self->m_loop_tag = 0;
169
return G_SOURCE_REMOVE;
163
return G_SOURCE_CONTINUE; // keep listening
176
static int32_t get_next_canberra_id()
178
static int32_t next_canberra_id = 1;
179
return next_canberra_id++;
182
170
const std::shared_ptr<Clock> m_clock;
183
const std::string m_filename;
171
const std::string m_uri;
184
172
const unsigned int m_volume;
185
173
const bool m_loop;
186
const int32_t m_canberra_id;
187
174
const DateTime m_loop_end_time;
188
ca_context* m_context = nullptr;
189
guint m_loop_tag = 0;
175
guint m_watch_source = 0;
176
GstElement* m_play = nullptr;
192
179
class SoundBuilder
195
182
void set_clock(const std::shared_ptr<Clock>& c) {m_clock = c;}
196
void set_filename(const std::string& s) {m_filename = s;}
183
void set_uri(const std::string& uri) {m_uri = uri;}
197
184
void set_volume(const unsigned int v) {m_volume = v;}
198
185
void set_duration_minutes(int unsigned i) {m_duration_minutes=i;}
199
186
void set_looping(bool b) {m_looping=b;}
201
188
Sound* operator()() {
202
189
return new Sound (m_clock,
205
192
m_duration_minutes,