-
Notifications
You must be signed in to change notification settings - Fork 32
/
org-fc-audio.el
211 lines (178 loc) · 6.79 KB
/
org-fc-audio.el
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
;;; org-fc-audio.el --- Audio playback during review -*- lexical-binding: t; -*-
;; Copyright (C) 2020-2024 Leon Rische
;; Author: Leon Rische <[email protected]>
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Adds audio playback during review.
;;
;; Audio files can be played at different times:
;; - before the card is set up
;; - after the card is set up
;;
;; This distinction is relevant for text-input cards where the card
;; setup ends only after the user has entered their answer.
;;
;; The `mpv` media player needs to be installed for this to work.
;;
;;; Code:
(require 'cl-lib)
(require 'org-fc-core)
(require 'org-fc-review)
(require 'org-fc-review-data)
(defcustom org-fc-audio-before-setup-property "FC_AUDIO_BEFORE_SETUP"
"Name of the property to use for storing before-setup audio files."
:type 'string
:group 'org-fc)
(defcustom org-fc-audio-after-setup-property "FC_AUDIO_AFTER_SETUP"
"Name of the property to use for storing after-setup audio files."
:type 'string
:group 'org-fc)
(defcustom org-fc-audio-after-flip-property "FC_AUDIO_AFTER_FLIP"
"Name of the property to use for storing after-flip audio files."
:type 'string
:group 'org-fc)
(defcustom org-fc-audio-before-setup-prefix "FC_AUDIO_BEFORE_SETUP"
"Prefix of the property to use for storing before-setup audio files."
:type 'string
:group 'org-fc)
(defcustom org-fc-audio-after-setup-prefix "FC_AUDIO_AFTER_SETUP"
"Prefix of the property to use for storing after-setup audio files."
:type 'string
:group 'org-fc)
(defcustom org-fc-audio-after-flip-prefix "FC_AUDIO_AFTER_FLIP"
"Prefix of the property to use for storing after-flip audio files."
:type 'string
:group 'org-fc)
(defvar org-fc-audio-last-file nil)
(defvar org-fc-audio--process nil)
(defun org-fc-audio--read-position-name ()
(completing-read
"Position: "
(org-fc-review-data-names (org-fc-review-data-parse '()))))
(defun org-fc-audio-set-before-setup (file &optional position)
"Set the before-setup audio property of POSITION of the current card to FILE.
When POSITION is nil, the file will be used for all positions of
the card."
(interactive
(list
(read-file-name "File: ")
(org-fc-audio--read-position-name)))
(when (org-fc-entry-p)
(if (or (null position) (string= position ""))
(org-set-property org-fc-audio-before-setup-property file)
(org-set-property
(format "%s_%s" org-fc-audio-before-setup-prefix (upcase position))
file))))
(defun org-fc-audio-set-after-setup (file &optional position)
"Set the after-setup audio of POSITION of the current card to FILE.
When POSITION is nil, the file will be used for all positions of
the card."
(interactive
(list
(read-file-name "File: ")
(org-fc-audio--read-position-name)))
(when (org-fc-entry-p)
(if (or (null position) (string= position ""))
(org-set-property org-fc-audio-after-setup-property file)
(org-set-property
(format "%s_%s" org-fc-audio-after-setup-prefix (upcase position))
file))))
(defun org-fc-audio-set-after-flip (file &optional position)
"Set the after-flip audio of POSITION of the current card to FILE.
When POSITION is nil, the file will be used for all positions of
the card."
(interactive
(list
(read-file-name "File: ")
(org-fc-audio--read-position-name)))
(when (org-fc-entry-p)
(if (or (null position) (string= position ""))
(org-set-property org-fc-audio-after-flip-property file)
(org-set-property
(format "%s_%s" org-fc-audio-after-flip-prefix (upcase position))
file))))
(defun org-fc-audio-play (property &optional speed)
"Play the audio of the current card.
Look up the file from PROPERTY. If SPEED is non-nil, play back
the file at the given speed. When used interactively, the user
is prompted for one of he audio files attached to the current
flashcard.
"
(interactive
(list
(completing-read
"Type: "
(let ((props (mapcar #'car (org-entry-properties))))
(cl-remove-if-not
(lambda (prop)
(or
(string= org-fc-audio-before-setup-property prop)
(string= org-fc-audio-after-setup-property prop)
(string= org-fc-audio-after-flip-property prop)
(string-prefix-p org-fc-audio-before-setup-prefix prop)
(string-prefix-p org-fc-audio-after-setup-prefix prop)
(string-prefix-p org-fc-audio-after-flip-prefix prop)))
props)))))
(if-let ((file (org-entry-get (point) property)))
(org-fc-audio-play-file file (or speed 1.0))))
(defun org-fc-audio-play-position (prefix)
"Play the audio file for PREFIX and the current position."
(org-fc-review-with-current-item current-item
(when current-item
(let* ((pos (oref current-item name))
(property (format "%s_%s" prefix (upcase pos))))
(org-fc-audio-play property)))))
(defun org-fc-audio-play-file (file speed)
"Play the audio FILE at SPEED."
(org-fc-audio-stop)
(setq org-fc-audio-last-file file)
(setq org-fc-audio--process
(start-process-shell-command
"org-fc audio"
nil
(format "mpv %s --speed=%f" file speed))))
(defun org-fc-audio-stop ()
"Stop org-fc audio playback."
(interactive)
(when (process-live-p org-fc-audio--process)
(kill-process org-fc-audio--process)))
(add-hook
'org-fc-before-setup-hook
(lambda ()
(org-fc-audio-stop)
(org-fc-audio-play org-fc-audio-before-setup-property)
(org-fc-audio-play-position org-fc-audio-before-setup-prefix)))
(add-hook
'org-fc-after-setup-hook
(lambda ()
(org-fc-audio-stop)
(org-fc-audio-play org-fc-audio-after-setup-property)
(org-fc-audio-play-position org-fc-audio-after-setup-prefix)))
(add-hook
'org-fc-after-flip-hook
(lambda ()
(org-fc-audio-stop)
(org-fc-audio-play org-fc-audio-after-flip-property)
(org-fc-audio-play-position org-fc-audio-after-flip-prefix)))
(add-hook 'org-fc-after-review-hook #'org-fc-audio-stop)
(defun org-fc-audio-replay ()
(interactive)
(when org-fc-audio-last-file
(org-fc-audio-play-file org-fc-audio-last-file 1.0)))
(defun org-fc-audio-replay-slow ()
(interactive)
(when org-fc-audio-last-file
(org-fc-audio-play-file org-fc-audio-last-file 0.7)))
;;; Footer
(provide 'org-fc-audio)
;;; org-fc-audio.el ends here