|
2 | 2 | (:require |
3 | 3 | [clojure.core.async :as a :refer [close!]] |
4 | 4 | [clojure.core.async.flow :as flow] |
| 5 | + [malli.util :as mu] |
5 | 6 | [simulflow.async :refer [vthread-loop]] |
6 | 7 | [simulflow.frame :as frame] |
| 8 | + [simulflow.schema :as schema] |
7 | 9 | [simulflow.transport.codecs :refer [make-twilio-serializer]] |
8 | 10 | [simulflow.utils.audio :as audio] |
9 | 11 | [simulflow.utils.core :as u] |
10 | 12 | [simulflow.vad.core :as vad] |
| 13 | + [simulflow.vad.factory :as vad-factory] |
11 | 14 | [taoensso.telemere :as t] |
12 | 15 | [uncomplicate.clojure-sound.core :as sound] |
13 | 16 | [uncomplicate.clojure-sound.sampled :as sampled]) |
|
16 | 19 |
|
17 | 20 | ;; Base logic for all input transport |
18 | 21 |
|
| 22 | +(def CommonTransportInputConfig |
| 23 | + [:map |
| 24 | + [:vad/analyser {:description "An instance of simulflow.vad.core/VADAnalyser protocol or one of the standard simulflow supported VAD processors to be used on new audio." |
| 25 | + :optional true} |
| 26 | + [:or schema/VADAnalyserProtocol (into [:enum] (keys vad-factory/factory))]] |
| 27 | + [:vad/args {:description "If `:vad/analyser` is a standard simulflow vad (like silero), these args are used as args to the vad factory" |
| 28 | + :optional true} [:map]] |
| 29 | + [:pipeline/supports-interrupt? {:description "Whether the pipeline supports or not interruptions." |
| 30 | + :default false |
| 31 | + :optional true} :boolean]]) |
| 32 | + |
| 33 | +(def BaseTransportInputConfig |
| 34 | + (mu/merge |
| 35 | + CommonTransportInputConfig |
| 36 | + [:map |
| 37 | + [:transport/in-ch |
| 38 | + {:description "Core async channel to take audio data from. The data is raw byte array or serialzed if a deserializer is provided"} |
| 39 | + schema/CoreAsyncChannel]])) |
| 40 | + |
19 | 41 | (def base-input-params |
20 | | - {:vad/analyser "An instance of simulflow.vad.core/VADAnalyser protocol to be used on new audio." |
21 | | - :pipeline/supports-interrupt? "Whether the pipeline supports or not interruptions."}) |
| 42 | + (schema/->describe-parameters CommonTransportInputConfig)) |
22 | 43 |
|
23 | 44 | (def base-transport-outs {:sys-out "Channel for system messages that have priority" |
24 | 45 | :out "Channel on which audio frames are put"}) |
|
32 | 53 | :data {:vad/prev-state prev-vad-state |
33 | 54 | :vad/state vad-state}}))) |
34 | 55 |
|
| 56 | +(defn init-vad! |
| 57 | + [{:vad/keys [analyser] :as params}] |
| 58 | + (when analyser |
| 59 | + (cond |
| 60 | + (satisfies? vad/VADAnalyzer analyser) |
| 61 | + analyser |
| 62 | + |
| 63 | + (keyword? analyser) |
| 64 | + (if-let [make-vad (get vad-factory/factory analyser)] |
| 65 | + (if (contains? params :vad/args) |
| 66 | + (make-vad (:vad/args params)) |
| 67 | + (make-vad)) |
| 68 | + (throw (ex-info "Something went wrong initiating :vad/analyser for transport in" |
| 69 | + {:params params |
| 70 | + :cause ::unknown-vad}))) |
| 71 | + |
| 72 | + :else |
| 73 | + (throw (ex-info "Something went wrong initiating :vad/analyser for transport in" |
| 74 | + {:params params |
| 75 | + :cause ::unknown-vad}))))) |
| 76 | + |
| 77 | +(defn base-transport-in-init! |
| 78 | + [schema params] |
| 79 | + (let [{:transport/keys [in-ch] :as parsed-params} (schema/parse-with-defaults schema params) |
| 80 | + vad-analyser (init-vad! parsed-params)] |
| 81 | + (into parsed-params {::flow/in-ports {::in in-ch} |
| 82 | + :vad/analyser vad-analyser}))) |
| 83 | + |
| 84 | +(defn base-transport-in-transition! |
| 85 | + [{::flow/keys [in-ports out-ports] :as state} transition] |
| 86 | + (when (= transition ::flow/stop) |
| 87 | + (doseq [port (remove nil? (concat (vals in-ports) (vals out-ports)))] |
| 88 | + (a/close! port)) |
| 89 | + (when-let [close-fn (::close state)] |
| 90 | + (when (fn? close-fn) |
| 91 | + (t/log! {:level :info |
| 92 | + :id :transport-in} "Closing input") |
| 93 | + (close-fn))) |
| 94 | + (when-let [analyser (:vad/analyser state)] |
| 95 | + (t/log! {:level :debug :id :transport-in :msg "Cleaning up vad analyser"}) |
| 96 | + (vad/cleanup analyser))) |
| 97 | + state) |
| 98 | + |
35 | 99 | (defn base-input-transport-transform |
36 | 100 | "Base input transport logic that is used by most transport input processors. |
37 | 101 | Assumes audio-input-raw frames that come in are 16kHz PCM mono. Conversion to |
|
71 | 135 |
|
72 | 136 | ;; Twilio transport in |
73 | 137 |
|
| 138 | +(def TwilioTransportInConfig |
| 139 | + (mu/merge |
| 140 | + BaseTransportInputConfig |
| 141 | + [:map |
| 142 | + [:twilio/handle-event |
| 143 | + {:description "[DEPRECATED] Optional function to be called when a new twilio event is received. Return a map like {cid [frame1 frame2]} to put new frames on the pipeline" |
| 144 | + :optional true} |
| 145 | + [:=> [:cat :map] :map]] |
| 146 | + [:serializer/convert-audio? |
| 147 | + {:description "If the serializer that is created should convert audio to 8kHz ULAW or not." |
| 148 | + :optional true |
| 149 | + :default false} :boolean] |
| 150 | + [:transport/send-twilio-serializer? |
| 151 | + {:description "Whether to send a `::frame/system-config-change` with a `twilio-frame-serializer` when a twilio start frame is received. Default true" |
| 152 | + :optional true |
| 153 | + :default true} :boolean]])) |
| 154 | + |
| 155 | +(def twilio-transport-in-describe |
| 156 | + {:outs base-transport-outs |
| 157 | + :params (schema/->describe-parameters TwilioTransportInConfig)}) |
| 158 | + |
| 159 | +(def twilio-transport-in-init! (partial base-transport-in-init! TwilioTransportInConfig)) |
| 160 | + |
74 | 161 | (defn twilio-transport-in-transform |
75 | 162 | [{:twilio/keys [handle-event] |
76 | 163 | :transport/keys [send-twilio-serializer?] |
77 | 164 | :or {send-twilio-serializer? true} |
78 | 165 | :as state} in input] |
79 | | - (if (= in ::twilio-in) |
| 166 | + (if (= in ::in) |
80 | 167 | (let [data (u/parse-if-json input) |
81 | 168 | output (if (fn? handle-event) |
82 | 169 | (do |
|
109 | 196 | [state])) |
110 | 197 | (base-input-transport-transform state in input))) |
111 | 198 |
|
112 | | -(defn twilio-transport-in-init! |
113 | | - [{:transport/keys [in-ch] :as state}] |
114 | | - (into state |
115 | | - {::flow/in-ports {::twilio-in in-ch}})) |
116 | | - |
117 | | -(def twilio-transport-in-describe |
118 | | - {:outs base-transport-outs |
119 | | - :params (into base-input-params |
120 | | - {:transport/in-ch "Channel from which input comes" |
121 | | - :twilio/handle-event "[DEPRECATED] Optional function to be called when a new twilio event is received. Return a map like {cid [frame1 frame2]} to put new frames on the pipeline" |
122 | | - :serializer/convert-audio? "If the serializer that is created should convert audio to 8kHz ULAW or not." |
123 | | - :transport/send-twilio-serializer? "Whether to send a `::frame/system-config-change` with a `twilio-frame-serializer` when a twilio start frame is received. Default true"})}) |
124 | | - |
125 | 199 | (defn twilio-transport-in-fn |
126 | 200 | ([] twilio-transport-in-describe) |
127 | 201 | ([params] (twilio-transport-in-init! params)) |
128 | | - ([state _] state) |
| 202 | + ([state trs] (base-transport-in-transition! state trs)) |
129 | 203 | ([state in msg] (twilio-transport-in-transform state in msg))) |
130 | 204 |
|
131 | 205 | (def twilio-transport-in |
|
155 | 229 | :params base-input-params}) |
156 | 230 |
|
157 | 231 | (defn mic-transport-in-init! |
158 | | - [state] |
159 | | - (let [{:keys [buffer-size audio-format channel-size]} mic-resource-config |
| 232 | + [params] |
| 233 | + (let [parsed-params (schema/parse-with-defaults CommonTransportInputConfig params) |
| 234 | + vad-analyser (init-vad! parsed-params) |
| 235 | + {:keys [buffer-size audio-format channel-size]} mic-resource-config |
160 | 236 | line (audio/open-line! :target audio-format) |
161 | 237 | mic-in-ch (a/chan channel-size) |
162 | 238 | buffer (byte-array buffer-size) |
|
178 | 254 | ;; Brief pause before retrying to prevent tight error loop |
179 | 255 | (Thread/sleep 100))) |
180 | 256 | (recur))) |
181 | | - (into state |
182 | | - {::flow/in-ports {::mic-in mic-in-ch} |
| 257 | + (into parsed-params |
| 258 | + {::flow/in-ports {::in mic-in-ch} |
| 259 | + :vad/analyser vad-analyser |
183 | 260 | ::close close}))) |
184 | 261 |
|
185 | | -(defn mic-transport-in-transition |
186 | | - [state transition] |
187 | | - (when (= transition ::flow/stop) |
188 | | - (when-let [close-fn (::close state)] |
189 | | - (when (fn? close-fn) |
190 | | - (t/log! {:level :info |
191 | | - :id :transport-in} "Closing input") |
192 | | - (close-fn))) |
193 | | - (when-let [analyser (:vad/analyser state)] |
194 | | - (t/log! {:level :debug :id :transport-in :msg "Cleaning up vad analyser"}) |
195 | | - (vad/cleanup analyser))) |
196 | | - |
197 | | - state) |
198 | | - |
199 | 262 | (defn mic-transport-in-transform |
200 | 263 | [state in {:keys [audio-data timestamp]}] |
201 | 264 | (base-input-transport-transform state in (frame/audio-input-raw audio-data {:timestamp timestamp}))) |
|
205 | 268 | ([] (mic-transport-in-describe)) |
206 | 269 | ([params] (mic-transport-in-init! params)) |
207 | 270 | ([state transition] |
208 | | - (mic-transport-in-transition state transition)) |
| 271 | + (base-transport-in-transition! state transition)) |
209 | 272 | ([state in msg] |
210 | 273 | (mic-transport-in-transform state in msg))) |
211 | 274 |
|
|
217 | 280 | {:outs base-transport-outs |
218 | 281 | :params (into base-input-params {:transport/in-ch "Channel from which input comes. Input should be byte array"})}) |
219 | 282 |
|
220 | | -(defn async-transport-in-transition |
221 | | - [{::flow/keys [in-ports out-ports] :as state} transition] |
222 | | - (when (= transition ::flow/stop) |
223 | | - (doseq [port (remove nil? (concat (vals in-ports) (vals out-ports)))] |
224 | | - (a/close! port)) |
225 | | - (when-let [analyser (:vad/analyser state)] |
226 | | - (t/log! {:level :debug :id :transport-in :msg "Cleaning up vad analyser"}) |
227 | | - (vad/cleanup analyser)) |
228 | | - state)) |
229 | | - |
230 | | -(defn async-transport-in-init! |
231 | | - [{:transport/keys [in-ch] :as state}] |
232 | | - (into state {::flow/in-ports {:in in-ch}})) |
| 283 | +(def async-transport-in-init! (partial base-transport-in-init! BaseTransportInputConfig)) |
233 | 284 |
|
234 | 285 | (defn async-transport-in-fn |
235 | 286 | ([] async-transport-in-describe) |
236 | 287 | ([state] (async-transport-in-init! state)) |
237 | | - ([state transition] (async-transport-in-transition state transition)) |
| 288 | + ([state transition] (base-transport-in-transition! state transition)) |
238 | 289 | ([state in msg] (base-input-transport-transform state in msg))) |
239 | 290 |
|
240 | 291 | (def async-transport-in-process (flow/process async-transport-in-fn)) |
0 commit comments