-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.rkt
82 lines (60 loc) · 2.44 KB
/
main.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#lang racket
(require rackjure/conditionals
rackjure/threading)
(require ffi/unsafe)
(require "objc-plumbing.rkt"
"scripting-bridge.rkt"
"sexp-formatter.rkt")
(define iTunes (get-scripting-bridge-app "com.apple.iTunes"))
(define (help)
'(help "Available commands:" (help) (current-track) (pause) (volume) (set-volume 0-100)))
(define (current-track)
(if (is-playing?)
(let* ([t [@ iTunes currentTrack]]
[d [@ #:type _float t duration]])
`(track (artist ,[@ t artist])
(album ,[@ t album])
(number ,[@ #:type _uint8 t trackNumber])
(name ,[@ t name])
(length ,(seconds->m:s d))))
(error "iTunes is not playing")))
(define (pause)
(define starting-volume (volume))
(set-volume 0)
[@ iTunes pause]
[@ iTunes setSoundVolume: #:type _uint8 starting-volume]
'ok)
(define (volume) [@ #:type _uint8 iTunes soundVolume])
(define (set-volume v)
(unless (<= 0 v 100) (error "Invalid volume"))
(for ([v-step (volume-steps (volume) v)])
[@ iTunes setSoundVolume: #:type _uint8 v-step]
(sleep 0.1))
'ok)
;; Command handling
; NOTE: This is very general because I plan to reuse it :)
(define (abstract-command-handler command-map command)
(with-handlers ([exn? (λ (e) (list 'error (exn-message e)))])
(match command
[(list (? symbol? name) args ...)
(if-let [proc (hash-ref command-map name #f)]
(apply proc args)
(error "No such command [try (help)]"))]
[_ (error "Command must be a list beginning with a symbol")])))
(define (make-command-handler . commands)
(curry abstract-command-handler
(for/hasheq ([x commands]) (values (object-name x) x))))
(define command-handler (make-command-handler help current-track pause volume set-volume))
;; Helpers
; NOTE: This is the best way I've found without having to make an enum for "player state".
(define (is-playing?) [@ [@ iTunes currentTrack] name])
(define (seconds->m:s x)
(let-values ([(m s) (~> x round inexact->exact (quotient/remainder 60))])
(~a m ":" (~a #:width 2 #:pad-string "0" #:align 'right s))))
(define (volume-steps start end)
(reverse (range end start (if (< end start) +5 -5))))
;; main
(module+ main
(match (current-command-line-arguments)
[(vector) (~>> (read) command-handler write-formatted-sexp) (newline)]
[_ (error "Run this program without arguments!")]))