<commit_before>(ns overtone.sc.machinery.server.connection
  (:import [java.io BufferedInputStream File])
  (:use [clojure.java shell]
        [overtone.config store]
        [overtone.libs event deps]
        [overtone version]
        [overtone.sc defaults]
        [overtone.sc.machinery.server comms native args]
        [overtone.osc]
        [overtone.osc.decode :only [osc-decode-packet]]
        [overtone.helpers.lib :only [print-ascii-art-overtone-logo windows-sc-path]]
        [overtone.helpers.file :only [file-exists? dir-exists? resolve-tilde-path]]
        [overtone.helpers.system :only [windows-os? get-os linux-os?]])
  (:require [overtone.config.log :as log]))

(defonce server-thread*       (ref nil))
(defonce sc-world*            (ref nil))
(defonce connection-info*     (ref {}))
(defonce connection-status*   (ref :disconnected))

(defonce external-server-log* (atom []))


(defn transient-server?
  "Return true if the server was booted by us, whether internally or
  externally."
  []
  (when (not-empty @connection-info*) true))

(defn- server-notifications-on
  "Turn on notification messages from the audio server.  This lets us
  free synth IDs when they are automatically freed with envelope
  triggers.  It also lets us receive custom messages from various
  trigger ugens.

  These messages are sent as notification of some event to all clients
  who have registered via the /notify command .

  All of these have the same arguments:
   int - node ID
   int - the node's parent group ID
   int - previous node ID, -1 if no previous node.
   int - next node ID, -1 if no next node.
   int - 1 if the node is a group, 0 if it is a synth

  The following two arguments are only sent if the node is a group:
   int - the ID of the head node, -1 if there is no head node.
   int - the ID of the tail node, -1 if there is no tail node.

   /n_go   - a node was created
   /n_end  - a node was destroyed
   /n_on   - a node was turned on
   /n_off  - a node was turned off
   /n_move - a node was moved
   /n_info - in reply to /n_query"
  []
  (server-snd "/notify" 1))

(defn- logged-sh
  "Run a shell command and log any errors. Returns stdout."
  [cmd & args]
  (let [res (apply sh cmd args)]
    (when-not (zero? (:exit res))
      (log/error "Subprocess error: " (:err res)))
    (:out res)))

(defn- connect-jack-ports
  "Connect the jack input and output ports as best we can.  If jack
  ports are always different names with different drivers or hardware
  then we need to find a better strategy to auto-connect. (For Linux
  users)"
  ([] (connect-jack-ports 2))
  ([n-channels]
     (let [port-list      (logged-sh "jack_lsp")
           sc-ins         (re-seq #"SuperCollider.*:in_[0-9]*" port-list)
           sc-outs        (re-seq #"SuperCollider.*:out_[0-9]*" port-list)
           system-ins     (re-seq #"system:capture_[0-9]*" port-list)
           system-outs    (re-seq #"system:playback_[0-9]*" port-list)
           interface-ins  (re-seq #"system:AC[0-9]*_dev[0-9]*_.*In.*" port-list)
           interface-outs (re-seq #"system:AP[0-9]*_dev[0-9]*_LineOut.*" port-list)
           connections (partition 2 (concat
                                     (interleave sc-outs system-outs)
                                     (interleave sc-outs interface-outs)
                                     (interleave system-ins sc-ins)
                                     (interleave interface-ins sc-ins)))]
       (doseq [[src dest] connections]
         (logged-sh "jack_connect" src dest)
         (log/info "jack_connect " src " " dest)))))

(when (= :linux (get-os))
  (on-deps :server-connected
           ::connect-jack-ports
           #(when (transient-server?)
              (connect-jack-ports))))

;; We have to do this to handle the change in SC, where they added a "/" to the
;; status.reply messsage, which it should have had in the first place.
(defn- setup-connect-handlers []
  (let [handler-fn
        (fn [event-info]
          (dosync
           (ref-set connection-status* :connected))
          (server-notifications-on) ; turn on notifications now that we can communicate
          (satisfy-deps :server-connected)
          (event :connection-complete)
          (remove-handler ::connected-handler1)
          (remove-handler ::connected-handler2)
          (log/debug "Server connection established")
          (println "--> Connection established"))]
    (on-sync-event "status.reply" handler-fn ::connected-handler1)
    (on-sync-event "/status.reply" handler-fn ::connected-handler2)))

(defn- connect-internal
  []
  (println "--> Connecting to internal SuperCollider server...")
  (log/debug "Connecting to internal SuperCollider server")
  (let [send-fn (fn [peer-obj buffer]
                  (scsynth-send @sc-world* buffer))
        peer (assoc (osc-peer) :send-fn send-fn)]
    (dosync (ref-set server-osc-peer* peer))
    (setup-connect-handlers)
    (server-snd "/status")))

(defn- external-connection-runner
  [host port]
  (println  "--> Connecting to external SuperCollider server:" (str host ":" port))
  (log/debug "Connecting to external SuperCollider server: " host ":" port)
  (let [sc-server (osc-client host port)]
    (osc-listen sc-server #(event [:overtone :osc-msg-received] :msg %))
    (dosync
     (ref-set server-osc-peer* sc-server))

    (setup-connect-handlers)
    (server-snd "/status")

    ;; Send /status in a loop until we get a reply
    (loop [cnt 0]
      (log/debug "connect loop...")
      (when-not (= :connected @connection-status*)
        (if (< cnt N-RETRIES)
          (do
            (log/debug (str "sending status... (" cnt ")"  ))<commit_msg>Create osc-clj peers that specifically *don't* allow nested bundles - as SuperCollider doesn't support them.<commit_after>(ns overtone.sc.machinery.server.connection
  (:import [java.io BufferedInputStream File])
  (:use [clojure.java shell]
        [overtone.config store]
        [overtone.libs event deps]
        [overtone version]
        [overtone.sc defaults]
        [overtone.sc.machinery.server comms native args]
        [overtone.osc]
        [overtone.osc.decode :only [osc-decode-packet]]
        [overtone.helpers.lib :only [print-ascii-art-overtone-logo windows-sc-path]]
        [overtone.helpers.file :only [file-exists? dir-exists? resolve-tilde-path]]
        [overtone.helpers.system :only [windows-os? get-os linux-os?]])
  (:require [overtone.config.log :as log]))

(defonce server-thread*       (ref nil))
(defonce sc-world*            (ref nil))
(defonce connection-info*     (ref {}))
(defonce connection-status*   (ref :disconnected))

(defonce external-server-log* (atom []))


(defn transient-server?
  "Return true if the server was booted by us, whether internally or
  externally."
  []
  (when (not-empty @connection-info*) true))

(defn- server-notifications-on
  "Turn on notification messages from the audio server.  This lets us
  free synth IDs when they are automatically freed with envelope
  triggers.  It also lets us receive custom messages from various
  trigger ugens.

  These messages are sent as notification of some event to all clients
  who have registered via the /notify command .

  All of these have the same arguments:
   int - node ID
   int - the node's parent group ID
   int - previous node ID, -1 if no previous node.
   int - next node ID, -1 if no next node.
   int - 1 if the node is a group, 0 if it is a synth

  The following two arguments are only sent if the node is a group:
   int - the ID of the head node, -1 if there is no head node.
   int - the ID of the tail node, -1 if there is no tail node.

   /n_go   - a node was created
   /n_end  - a node was destroyed
   /n_on   - a node was turned on
   /n_off  - a node was turned off
   /n_move - a node was moved
   /n_info - in reply to /n_query"
  []
  (server-snd "/notify" 1))

(defn- logged-sh
  "Run a shell command and log any errors. Returns stdout."
  [cmd & args]
  (let [res (apply sh cmd args)]
    (when-not (zero? (:exit res))
      (log/error "Subprocess error: " (:err res)))
    (:out res)))

(defn- connect-jack-ports
  "Connect the jack input and output ports as best we can.  If jack
  ports are always different names with different drivers or hardware
  then we need to find a better strategy to auto-connect. (For Linux
  users)"
  ([] (connect-jack-ports 2))
  ([n-channels]
     (let [port-list      (logged-sh "jack_lsp")
           sc-ins         (re-seq #"SuperCollider.*:in_[0-9]*" port-list)
           sc-outs        (re-seq #"SuperCollider.*:out_[0-9]*" port-list)
           system-ins     (re-seq #"system:capture_[0-9]*" port-list)
           system-outs    (re-seq #"system:playback_[0-9]*" port-list)
           interface-ins  (re-seq #"system:AC[0-9]*_dev[0-9]*_.*In.*" port-list)
           interface-outs (re-seq #"system:AP[0-9]*_dev[0-9]*_LineOut.*" port-list)
           connections (partition 2 (concat
                                     (interleave sc-outs system-outs)
                                     (interleave sc-outs interface-outs)
                                     (interleave system-ins sc-ins)
                                     (interleave interface-ins sc-ins)))]
       (doseq [[src dest] connections]
         (logged-sh "jack_connect" src dest)
         (log/info "jack_connect " src " " dest)))))

(when (= :linux (get-os))
  (on-deps :server-connected
           ::connect-jack-ports
           #(when (transient-server?)
              (connect-jack-ports))))

;; We have to do this to handle the change in SC, where they added a "/" to the
;; status.reply messsage, which it should have had in the first place.
(defn- setup-connect-handlers []
  (let [handler-fn
        (fn [event-info]
          (dosync
           (ref-set connection-status* :connected))
          (server-notifications-on) ; turn on notifications now that we can communicate
          (satisfy-deps :server-connected)
          (event :connection-complete)
          (remove-handler ::connected-handler1)
          (remove-handler ::connected-handler2)
          (log/debug "Server connection established")
          (println "--> Connection established"))]
    (on-sync-event "status.reply" handler-fn ::connected-handler1)
    (on-sync-event "/status.reply" handler-fn ::connected-handler2)))

(defn- connect-internal
  []
  (println "--> Connecting to internal SuperCollider server...")
  (log/debug "Connecting to internal SuperCollider server")
  (let [send-fn (fn [peer-obj buffer]
                  (scsynth-send @sc-world* buffer))
        peer (assoc (osc-peer false false) :send-fn send-fn)]
    (dosync (ref-set server-osc-peer* peer))
    (setup-connect-handlers)
    (server-snd "/status")))

(defn- external-connection-runner
  [host port]
  (println  "--> Connecting to external SuperCollider server:" (str host ":" port))
  (log/debug "Connecting to external SuperCollider server: " host ":" port)
  (let [sc-server (osc-client host port false)]
    (osc-listen sc-server #(event [:overtone :osc-msg-received] :msg %))
    (dosync
     (ref-set server-osc-peer* sc-server))

    (setup-connect-handlers)
    (server-snd "/status")

    ;; Send /status in a loop until we get a reply
    (loop [cnt 0]
      (log/debug "connect loop...")
      (when-not (= :connected @connection-status*)
        (if (< cnt N-RETRIES)
          (do
            (log/debug (str "sending status... (" cnt ")"  ))
{'fraction_non_alphanumeric': 0.12522939788516999, 'fraction_numerical': 0.005243380232456523, 'mean_word_length': 2.4668282338685246, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2005112', 'n_tokens_mistral': 3616, 'n_tokens_neox': 3495, 'n_words': 1150}