<commit_before>#lang racket

(require racket/runtime-path
         racket/block)

(provide (contract-out
          [start-scsynth
           (-> (list/c fixnum?
                       (listof string?)
                       output-port?
                       (-> void?)))]))

(define-runtime-path here ".")

(unless (equal? (system-type) 'macosx)
  (fprintf (current-error-port)
           "WARNING: startup of scsynth unlikely to succeed except on mac\n"))

(define SCSYNTH-PATH (build-path here "scsynth"))

;; honestly, all of this machinery is a bit silly.
(define rng (vector->pseudo-random-generator
             (vector #x57180d1c 1 2 3 4 5)))

;; generate a random udp socket number in the dynamically assigned
;; range specified by the iana:
(define MIN-DYNAMIC-UDP 49152)
(define MAX-DYNAMIC-UDP 65535)
(define (random-udp-port)
  (+ (random (- MAX-DYNAMIC-UDP MIN-DYNAMIC-UDP) rng)
     MIN-DYNAMIC-UDP))

;; random, but always the same random.
(define persistent-random-udp-port-stream
  (let loop ()
    (stream-cons (random-udp-port)
                 (loop))))

;; FRAGILE: not clear how best to check that the server came up cleanly.
(define SUCCESSFUL-START-LINE "SuperCollider 3 server ready.")
;; FRAGILE: not clear how to check that the udp port is already taken.
(define UDP-TAKEN-MSG "unable to bind udp socket")
;; wait this many seconds before giving up on server startup
(define STARTUP-WAIT 5.0)


;; given an index into the stream of udp port numbers,
;; try to bring the server up on that udp port (also
;; takes the one before it, IIRC). If it fails with "unable to bind udp
;; socket", try again with the udp socket number increased by two.
;;
;; note: the -a, -m, and -D arguments come from sonic
;; pi, and I can't comment on their necessity, though I can tell you
;; what --help has to say:
;; -a : # of audio buses
;; -m : real-time memory size
;; -D : load default synthdefs? (1=yes,0=no)
(define (try-startup udp-port-idx)
  (define udp-port (stream-ref persistent-random-udp-port-stream
                               udp-port-idx))
  (match-define (list from-port to proc-id err control-proc)
    (process* SCSYNTH-PATH
              "-a" "1024"
              "-u" (number->string udp-port)
              "-m" "131072"
              "-D" "0"))
  
  (printf "started scsynth with process id ~v\n" proc-id)
  
  (define startup-status (make-channel))
  
  ;; all of the lines of output generated by the
  ;; server process, in reverse order
  (define lines-of-output (list))
  
  ;; record and display all lines of output, send a signal the first time
  ;; a successful startup message is seen.
  (thread
   (λ ()
     (let ([seen-success? #f])
       (let loop ()
         (define next-line (read-line from-port))
         (set! lines-of-output (cons next-line lines-of-output))
         (display next-line)
         (newline)
         (cond [(eof-object? next-line) 'process-done]
               [(and (not seen-success?)
                     (equal? next-line SUCCESSFUL-START-LINE))
                (channel-put startup-status 'server-up)
                (loop)]
               [else (loop)])))))
  
  ;; signal 
  (thread
   (λ ()
     (control-proc 'wait)
     (channel-put startup-status
                  (list 'exit-code (control-proc 'exit-code)))))
  
  (match (sync/timeout STARTUP-WAIT startup-status)
    [#f (error 'server-startup
               "server startup timed out. Perhaps the success message has changed?")]
    [(list 'exit-code 1)
     ;; wait for the rest of the output to arrive
     (sleep 1.0)
     (match lines-of-output
       [(list-rest (? eof-object?) "" (regexp (regexp-quote UDP-TAKEN-MSG)) _)
        (try-startup (add1 udp-port-idx))]
       [other
        (error 'server-startup
               "server process halted with exit code 1. Not sure why. next to Last line: ~v\n"
               (cadr lines-of-output))
        ])]
    [(list 'exit-code n)
     (error 'server-startup
            "server process halted with exit code ~v. Not sure why."
            n)]
    ['server-up
     (define udp)
     (list udp-port
           (reverse lines-of-output)
           from-port
           (λ () (control-proc 'kill)))]
    [other
     (error 'server-startup
            "internal error: unexpected result from startup: ~e"
            other)]))

(define (start-scsynth)
  (try-startup 0))<commit_msg>STILL WORKING<commit_after>#lang racket

(require racket/runtime-path
         racket/block)

(provide (contract-out
          [start-scsynth
           (-> (list/c fixnum?
                       (listof string?)
                       output-port?
                       (-> void?)))]))

(define-runtime-path here ".")

(unless (equal? (system-type) 'macosx)
  (fprintf (current-error-port)
           "WARNING: startup of scsynth unlikely to succeed except on mac\n"))

(define SCSYNTH-PATH (build-path here "scsynth"))

;; honestly, all of this machinery is a bit silly.
(define rng (vector->pseudo-random-generator
             (vector #x57180d1c 1 2 3 4 5)))

;; generate a random udp socket number in the dynamically assigned
;; range specified by the iana:
(define MIN-DYNAMIC-UDP 49152)
(define MAX-DYNAMIC-UDP 65535)
(define (random-udp-port)
  (+ (random (- MAX-DYNAMIC-UDP MIN-DYNAMIC-UDP) rng)
     MIN-DYNAMIC-UDP))

;; random, but always the same random.
(define persistent-random-udp-port-stream
  (let loop ()
    (stream-cons (random-udp-port)
                 (loop))))

;; FRAGILE: not clear how best to check that the server came up cleanly.
(define SUCCESSFUL-START-LINE "SuperCollider 3 server ready.")
;; FRAGILE: not clear how to check that the udp port is already taken.
(define UDP-TAKEN-MSG "unable to bind udp socket")
;; wait this many seconds before giving up on server startup
(define STARTUP-WAIT 5.0)


;; given an index into the stream of udp port numbers,
;; try to bring the server up on that udp port (also
;; takes the one before it, IIRC). If it fails with "unable to bind udp
;; socket", try again with the udp socket number increased by two.
;;
;; note: the -a, -m, and -D arguments come from sonic
;; pi, and I can't comment on their necessity, though I can tell you
;; what --help has to say:
;; -a : # of audio buses
;; -m : real-time memory size
;; -D : load default synthdefs? (1=yes,0=no)
(define (try-startup udp-port-idx)
  (define udp-port (stream-ref persistent-random-udp-port-stream
                               udp-port-idx))
  (match-define (list from-port to proc-id err control-proc)
    (parameterize ([current-subprocess-custodian-mode 'kill])
      (process* SCSYNTH-PATH
                "-a" "1024"
                "-u" (number->string udp-port)
                "-m" "131072"
                "-D" "0")))
  
  (printf "started scsynth with process id ~v\n" proc-id)
  
  (define startup-status (make-channel))
  
  ;; all of the lines of output generated by the
  ;; server process, in reverse order
  (define lines-of-output (list))
  
  ;; record and display all lines of output, send a signal the first time
  ;; a successful startup message is seen.
  (thread
   (λ ()
     (let ([seen-success? #f])
       (let loop ()
         (define next-line (read-line from-port))
         (set! lines-of-output (cons next-line lines-of-output))
         (display next-line)
         (newline)
         (cond [(eof-object? next-line) 'process-done]
               [(and (not seen-success?)
                     (equal? next-line SUCCESSFUL-START-LINE))
                (channel-put startup-status 'server-up)
                (loop)]
               [else (loop)])))))
  
  ;; signal 
  (thread
   (λ ()
     (control-proc 'wait)
     (channel-put startup-status
                  (list 'exit-code (control-proc 'exit-code)))))
  
  (match (sync/timeout STARTUP-WAIT startup-status)
    [#f (error 'server-startup
               "server startup timed out. Perhaps the success message has changed?")]
    [(list 'exit-code 1)
     ;; wait for the rest of the output to arrive
     (sleep 1.0)
     (match lines-of-output
       [(list-rest (? eof-object?) "" (regexp (regexp-quote UDP-TAKEN-MSG)) _)
        (try-startup (add1 udp-port-idx))]
       [other
        (error 'server-startup
               "server process halted with exit code 1. Not sure why. next to Last line: ~v\n"
               (cadr lines-of-output))
        ])]
    [(list 'exit-code n)
     (error 'server-startup
            "server process halted with exit code ~v. Not sure why."
            n)]
    ['server-up
     (list udp-port
           (reverse lines-of-output)
           from-port
           (λ () (control-proc 'kill)))]
    [other
     (error 'server-startup
            "internal error: unexpected result from startup: ~e"
            other)]))

;; start the scsynth server:
(define (start-scsynth)
  (try-startup 0))

(module+ test
  (require rackunit)

  (match-define (list udp-port headers server-stdout killer)
    (start-scsynth))
  (check-match udp-port (? exact-nonnegative-integer?))
  (check-match headers (list (? string?) ...))
  (check-match server-stdout (? input-port?))
  (check-match killer (? procedure?))

  ;; clean up....
  (killer))
{'fraction_non_alphanumeric': 0.1263226791753027, 'fraction_numerical': 0.009381477037198648, 'mean_word_length': 2.27662616154396, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3499307', 'n_tokens_mistral': 2993, 'n_tokens_neox': 2847, 'n_words': 948}