forked from mcdejonge/rs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rs-midi-core.rkt
164 lines (132 loc) · 4.91 KB
/
rs-midi-core.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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#lang racket/base
;; Core MIDI related functionality for rs. Wraps the *true* core MIDI
;; functionality in an interface.
;;
;; Do not call these functions directly when sequencing. Use the
;; higher level rs-m instead.
;;
;; Currently based on RtMidi
;; (https://docs.racket-lang.org/rtmidi/index.html)
(provide rs-midi-core-open-out-port!
rs-midi-core-close-output!
rs-midi-core-list-ports
rs-midi-core-print-port-list
rs-midi-core-send-cc!
rs-midi-core-send-note!)
;; To make this work, you must link
;; /Users/matthijs/Library/Racket/7.5/pkgs/rtmidi/rtmidi/wrap-rtmidi.dylib
;; to the directory of this file.
;; TODO maybe fork rtmidi and fix the path to the dylib
;; RtMidi is not documented. Code is here
;; https://github.com/jbclements/rtmidi/blob/master/rtmidi/main.rkt
(require racket/contract/base
racket/contract/region
racket/list
racket/math
rtmidi)
;; Setup
; Create RtMidiIn and RtMidiOut
(define in (make-rtmidi-in))
(define out (make-rtmidi-out))
(define in-ports (rtmidi-ports in))
(define out-ports (rtmidi-ports out))
; List input and output ports
(define (rs-midi-core-list-ports)
; Return a list of available ports.
out-ports)
; Void
(define (rs-midi-core-print-port-list)
; Print a list of available ports to STDOUT.
(printf "Input ports: ~a~n" in-ports)
(printf "Output ports: ~a~n" out-ports))
; Void
(define (rs-midi-core-close-output!)
; Close the currently active output port.
(rtmidi-close-port out))
(define (string-or-natural? input)
; Helper function to use in a contract.
(or (string? input) (natural? input)))
; String or int -> Void
(define/contract (rs-midi-core-open-out-port! name-or-index)
;; Open a MIDI out port by name or index and closes any other open ports
;; as there can be only one opened at the same time.
(-> string-or-natural? void)
(cond
[(string? name-or-index) (rs-midi-core-open-out-port-by-name! name-or-index)]
[(natural? name-or-index) (rs-midi-core-open-out-port-by-index! name-or-index)]
[else (raise-argument-error 'name-or-index "string? or natural?" name-or-index)]))
; String -> Void
(define (rs-midi-core-open-out-port-by-name! name)
;; Open a MIDI out port by name and closes any other open ports
;; as there can be only one opened at the same time.
(let ((port-num (index-of (rtmidi-ports in) name)))
(cond
[(equal? #f port-num) (error "No MIDI port with name: " name)]
[else
(rtmidi-close-port out)
(rtmidi-open-port out port-num)])))
; Int -> Void
(define (rs-midi-core-open-out-port-by-index! index)
(cond
[(< index (length out-ports)) (rtmidi-close-port out)
(rtmidi-open-port out index)]
[else (error "No such MIDI port: " index)]))
; Helper functions for the rs-midi-core-send-note! contract.
(define (midi-value? input)
(and (natural? input)
(> input 0)
(< input 128)))
(define (midi-channel-number? input)
(and (natural? input)
(> input 0)
(< input 17)))
; Int, Natural, Int (0-127), Int (1-16) -> Thread
(define/contract (rs-midi-core-send-note! pitch duration-ms [velocity 127] [channel 1])
(->* (midi-value? natural?)
(midi-value? midi-channel-number?) thread?)
; Send a note to the currently open port (if any) on the given
; channel for the given duration.
(thread (lambda()
(rtmidi-send-message out (list (+ channel 143) pitch velocity ))
(sleep (* (/ 1.0 1000) duration-ms))
(rtmidi-send-message out (list (+ channel 127) pitch velocity)))))
;; TODO figure out how to send MIDI cc messages and implement sending them.
(define/contract (rs-midi-core-send-cc! cc-no cc-val [channel 1])
; Send a MIDI CC message.
; TODO create a proper contract.
(->* (midi-value? midi-value?)
(midi-channel-number?)
thread?)
(thread (lambda()
(rtmidi-send-message out (list (+ channel 175) cc-no cc-val)))))
(module+ test
(define test-info #<<EOF
What should happen: a list of all available MIDI ports should be printed.
Then, if you have at least one port available on your
system, a middle C will be played for half a second at full velocity
on channel 1.
EOF
)
(displayln test-info)
(rs-midi-core-print-port-list)
(cond
[(length out-ports)
(displayln "At least one port available. Playing a note.")
(rs-midi-core-open-out-port! 0)
(rs-midi-core-send-note! 60 500)
(sleep 1) ; Necessary because otherwise the port may be closed before the note is played.
(rs-midi-core-close-output!)]
[else (displayln "No MIDI port available for testing.")]
)
;; (rs-midi-core-open-out-port! 0)
;; (let loop()
;; (rs-midi-core-send-cc! 7 100)
;; (sleep 0.01)
;; (rs-midi-core-send-note! 60 100)
;; (sleep 0.1)
;; (rs-midi-core-send-cc! 7 120)
;; (rs-midi-core-send-note! 60 100)
;; (sleep 0.2)
;; (printf "Sending CC\n")
;; (loop))
)