Small steps towards using OpenAI-compatible text-to-speech services with speechd-el or emacspeak

Speech synthesis has come a long way since I first tried out Emacspeak in 2002. Kokoro TTS and Piper offer more natural-sounding voices now, although the initial delay in loading the models and generating speech mean that they aren't quite ready to completely replace espeak, which is faster but more robotic. I've been using the Kokoro FastAPI through my own functions for working with various speech systems. I wanted to see if I could get Kokoro and other OpenAI-compatible text-to-speech services to work with either speechd-el or Emacspeak just in case I could take advantage of the rich functionality either provides for speech-synthesized Emacs use. speechd-el is easier to layer on top of an existing Emacs if you only want occasional speech, while emacspeak voice-enables many packages to an extent beyond speaking simply what's on the screen.

Speech synthesis is particularly helpful when I'm learning French because I can use it as a reference for what a paragraph or sentence should sound like. It's not perfect. Sometimes it uses liaisons that my tutor and Google Translate don't use. But it's a decent enough starting point. I also used it before to read out IRC mentions and compile notifications so that I could hear them even if I was paying attention to a different activity.

Here's a demonstration of speechd reading out the following lines using the code I've just uploaded to https://codeberg.org/sachac/speechd-ai:

  • The quick brown fox jumps over the lazy dog.
  • Now let's set the language to French so we can read the next line.
  • Bonjour, je m'appelle Emacs.

Screencast showing speechd-el

There's about a 2-second delay between the command and the start of the audio for the sentence.

Note that speechd-speak-read-sentence fails in some cases where (forward-sentence 1) isn't the same place as (backward-sentence 1) (forward-sentence 1), which can happen when you're in an Org Mode list. I've submitted a patch upstream.

Aside from that, speechd-speak-set-language, speechd-speak-read-paragraph and speechd-speak-read-region are also useful commands. I think the latency makes this best-suited for reading paragraphs, or for shadowing sentences for language learning.

I'm still trying to figure out how to get speechd-speak to work as smoothly as I'd like. I think I've got it set up so that the server falls back to espeak for short texts so that it can handle words or characters better, and uses the specified server for longer ones. I'd like to get to the point where it can handle all the things that speechd usually does, like saying lines as I navigate through them or giving me feedback as I'm typing. Maybe it can use espeak for fast feedback character by character and word by word, and then use Kokoro TTS for the full sentence when I finish. Then it will be possible to use it to type things without looking at the screen.

After putting this together, I still find myself leaning towards my own functions because they make it easy to see the generated speech output to a file, which is handy for saving reference audio that I can play on my phone and for making replays almost instant. That could also be useful for pre-generating the next paragraph to make it flow more smoothly. Still, it was interesting making something that is compatible with existing protocols and libraries.

Posting it in case anyone else wants to use it as a starting point. The repository also contains the starting point for an Emacspeak-compatible speech server. See See speechd-ai/README.org for more details.

https://codeberg.org/sachac/speechd-ai

View Org source for this post

French pronunciation assessment with Azure and my Emacs setup

| speech

I'm working on learning French because it's fun and because I want to help my daughter with her French classes. Some sounds are particularly difficult because they don't exist in English, so they involve moving my tongue and my lips in ways that I'm not used to. I've been working on some sentences my tutor assigned me to help me with various sounds. I practice:

  • during the 45-minute virtual sessions with my tutor twice a week: I record this with OBS
  • recording on my phone when I find myself with some spare time away from my computer, like when I'm waiting for my daughter
  • recording on my computer with Emacs, subed-record, and ffmpeg
  • just out loud, like when I'm skating or doing the dishes

I use WhisperX to get the transcript and word timing data with speaker identification (diarization). I use subed-record in Emacs to correct the transcript, cut/split/trim audio segments, and compile them

I've been working on my processes for reviewing reference examples either from text-to-speech engines or my meeting audio, recording and reviewing my attempts, and comparing them.

I want to be able to quickly listen to my best attempts from a session, compare different versions, and keep track of my progress over time. I also want to figure out how to practice pronunciation in between sessions with my tutor while minimizing the risk of reinforcing mistakes. To segment the audio for review, I use WhisperX to get the transcript and word timing data with speaker identification (diarization). I use subed-record in Emacs to correct the transcript, cut/split/trim audio segments, and compile them. I want to make this process even easier.

Some of the sounds I'm working on are:

  • /​y/ as in mule and bu
  • /​ʁ/ as in trottoir: making it with less air, but without it feeling like an "h" instead; also, transitioning to /​y/ as in brume
  • /​œ/ as in cœur
  • distinguishing between roue and rue

Microsoft Azure pronunciation assessment

I've been thinking about how to practise more effectively in between my twice-weekly tutoring sessions. There's been a fair amount of research into computer-aided language training and pronunciation assessment, and I wonder how I can tweak my processes and interfaces to take advantage of what other people have learned. I think many apps use the confidence scores of Whisper-based speech recognition engines. Other services try to be more detailed. For example, Microsoft Azure offers a pronunciation assessment service that scores audio samples on:

  • accuracy: how closely phonemes match a native speaker's pronunciation
  • fluency: how closely it matches a native speaker's silent breaks between words
  • completeness: whether all the words were said
  • pronunciation: the total score
  • confidence

Azure's syllable and phoneme analyses only work for the en-US locale, so I can't use them for fr-FR. That's okay, LLM pronunciation evaluation might work better for sentences than for words or phonemes anyway.1

I'll share my results first, and then I'll describe my workflow.

For example, here are some Azure scores for "Trois très grands trains traversent trois trop grandes rues", compared with a Kokoro TTS sample and its results when analyzing an audio segment of my teacher speaking (not included).

  Mar 3 Bad Kokoro Tutor
Overall 94 44 97 93
Accuracy 93 63 96 100
Fluency 93 62 100 89
Completeness 100 33 100 100
Confidence 90 80 92 02

I'm not entirely sure how useful these numbers will be. It does distinguish between my current attempts and intentionally bad pronunciation, but I'm not sure how much I can trust it yet, or how useful it will be for guiding my attempts in between tutoring sessions. I can imagine a workflow where I listen to a reference, record my attempt, and replay the reference and the recording while displaying the scores, a waveform, and a spectrogram.

Let's look at another example of the scores. The tongue-twister "La mule sûre court plus vite que le loup fou." might offer a more useful comparison because I have a hard time with the u sound in "mule".

  Mar 3 with tutor Kokoro Tutor
Overall 84 94 100 95
Accuracy 96 95 100 100
Fluency 75 92 100 93
Completeness 100 100 100 100
Confidence 84 85 89 87

Phonemes

What about trying to get the IPA and then doing some kind of comparison? I tried using Allosaurus and Montreal Forced Aligner for my audio samples for "La mule sûre court plus vite que le loup fou." I've also included IPA output from Kokoro TTS and espeak, which generate them from text, although I've removed the stress marks for easier comparison.

Mar 3 naɛmis̪yʁkɔpluzitɑləkœnədufu
with tutor (Allosaurus) lɛns̪ikɑlovitkɛlədufu
with tutor (MFA) lamylsyʁkuʁplyvitkəlølufu
Tutor lamilks̪yuəs̪ikətəlufu
Kokoro TTS reference la myl syʁ kuʁ ply vit kə lə lu fu.
Espeak TTS reference la mjul suɹə kɔt plʌs vaɪt kwɛ lə lup fu.

The IPA for a sentence is hard to read and compare. Maybe I can get Allosaurus to do word breaks, or maybe I can try a different tool. The phonemes from Montreal Forced Aligner look like they might be a little more manageable; I can see that the main difference is that I said instead of . I can get timing data from MFA, so I might be able to use that to break it up into words. It would be nice to get confidence data, though, since I'm pretty sure that y isn't solid yet.

Analyzing multiple tongue-twisters from a single session

Here are some tongue-twister attempts from my March 6 session, annotated with the comments from my tutor. First, the list of tongue-twisters in this analysis.

  1. Maman peint un grand lapin blanc.
  2. Un enfant intelligent mange lentement.
  3. Le roi croit voir trois noix.
  4. Il est loin mais moins loin que ce coin.
  5. Le témoin voit le chemin loin.
  6. Moins de foin au loin ce matin.
  7. La laine beige sèche près du collège.
  8. La croquette sèche dans l’assiette.
  9. Elle mène son frère à l’hôtel.
  10. Le verre vert est très clair.
  11. Elle aimait manger et rêver.
  12. Le cœur seul pleure doucement.
  13. Le beurre fond dans le cœur chaud.
  14. Tu es sûr du futur ?
  15. Un mur dur bloque la rue.

Then I can extract audio segments, transcribe the IPA, and send it to Azure for pronunciation analysis all in one go.

(my-subed-record-analyze-file-with-azure "~/proj/french/analysis/virelangues/2026-03-06-raphael-script.vtt")
File ID Comments All Acc Flu Comp Conf Phonemes
01-01   85 92 78 100 88 lɒmɒaɒʁɔnlat̪ɒlɒ
01-02 Mm hmm 88 86 86 100 87 mamakɔaɒkul̪ɒlatalɒ
01-03 Ouais 68 73 69 67 86 lɒmɒaɒpzɒlɒ
01-04 Ouais 81 78 89 83 86 ɒenɒmɒt̪ɒʁɔlɒt̪alɒŋ
02-01 Ouais, c'est bien 92 93 90 100 90 ɒjnɒwsɒnɛnt̪eleʒɔnmomuʃlɔns̪mɒ
03-01 Uh huh 98 100 97 100 88 jal̪aʁwaɒll̪awandwa
04-01 Ouais, parfait 91 92 100 89 89 ijelwamenmwdoakesekwaŋ
05-01 X: témoin 83 82 90 83 88 etemwawaəʃemɒdwaŋ
05-02 Ouais 92 88 96 100 89 et̪timwawaeʃemɒlwa
06-01 Mm hmm, parfait 89 93 94 86 89 wɒt̪iəwozwasənmɒtɒŋ
07-01 X: près du collège 78 79 99 71 87 ɒmnteʁalol̪e
07-02 X: près 85 86 85 86 88 ɒjteʁatkœl̪es
07-03 Mm hmm 90 93 99 86 89 ɒl̪mnteadəkol̪en
08-01 Ouais, c'est mieux 99 99 99 100 90 laokes̪estɒnlas̪iə
08-02 Mm hmm 97 96 100 100 90 laokes̪est̪ɒvɒnləs̪iən
09-01 Ouais, c'est bien 99 99 100 100 89 ɛnmɛnsɑnsʁɛajlot̪ɛn
10-01 Mm hmm 87 88 99 83 87 nɛzaʁbɛʁejklə
11-01 Mm hmm 100 100 100 100 89 elɒnmimaŋzeʁɒze
12-01 X: doucement 81 86 81 80 87 ikaʁapjobil̪ədimɒ
12-02 Ouais 82 83 76 100 86 ikɑsuntius̪əmɑn
12-03 Ouais, c'est mieux 70 75 65 80 84 lɛkɑsødius̪əmən
13-01 Ouais 85 85 85 86 84 lidəfɔdɑŋlikɑʁʃəl
14-01 Mm hmm 97 96 98 100 87 kel̪eid̪jefit̪joəʁ
15-01 X: rue 84 90 78 100 84 ɒwl̪midijəlɔʁlɒʁs̪u
15-02 X: rue 84 87 88 83 83 ɒwmjd̪il̪ɔʁnɒku

The play buttons even work in Org Mode because I use a custom link type for audio links.

It would be useful to analyze one tongue-twister across multiple sessions to get a sense of my progress.

Focusing on one word

Here's a deep-dive on the word "mule" from my March 3 session. I exported the words using the WhisperX JSON. Both Azure speech recognition results and Allosaurus phonemes are all over the place when I try to run them on audio segments with individual words.

(my-subed-record-extract-words "mule"  "/home/sacha/sync/recordings/processed/2026-03-03-raphael.json" "/home/sacha/proj/french/analysis/mule/index.vtt")

I manually adjusted some timestamps, removed some segments, and added a reference sample from Wiktionary (source, public domain). Here's the WebVTT file with the directives: file:///home/sacha/proj/french/analysis/mule/index.vtt

(my-subed-record-analyze-file-with-azure "~/proj/french/analysis/mule/index.vtt")
File ID Comments WhisperX All Acc Flu Comp Conf Phonemes
01 Wiktionary - France   97 95 100 100 77 mel
02   92 80 68 100 100 78 mi
03   91 80 68 100 100 79 nol̪ɒj
04 Bit of a y 90 95 93 100 100 88 nyjɔl̪ɒ
05 Bit of a y 89 88 80 100 100 83 j
06   89 88 80 100 100 82 muə
07 Bit of a y 88 10 50 0 0 78 niə
08   88 76 60 100 100 81 mio
09 Bit of a y 84 91 86 100 100 84 mj
10 Bit of a y 84 11 56 0 0 77 nj
11 Bit of a y 83 6 31 0 0 79 jn
12   83 80 68 100 100 80 mijə
13   82 0 0 0 0 88 ɒ
14 Got a small "oui" 80 1 5 0 0 84 ija
15   78 4 21 0 0 57 mia
16   78 10 53 0 0 80 miə
17   75 10 52 0 0 77 we
18 Got a "non!" 75 8 44 0 0 85 mjən
19   72 79 66 100 100 78
20 Bit of a y 72 79 66 100 100 79 miə

At the word level, the Azure pronunciation scores are all over the place, and so are the Allosaurus phonemes. For now, I think it might be better to tweak my interface so that I can more easily refer to the samples (text to speech or recordings extracted from my tutoring session) and compare new recordings, maybe with waveforms, spectrograms, and other plots.

I like the progress I've made on a workflow for extracting and evaluating sentences or words. I'm looking forward to seeing what I can do once I understand a bit more of the research. Let me describe the workflow I have so far.

Analyzing one tongue-twister across multiple sessions

I can also compile different versions of one tongue-twister into one file to get a sense of my progress.

(my-subed-record-analyze-file-with-azure
 (my-subed-record-collect-matching-subtitles
 "Le roi croit voir trois noix"
 '(("~/sync/recordings/processed/2026-02-20-raphael-tongue-twisters.vtt" . "Feb 20")
   ("~/sync/recordings/processed/2026-02-22-virelangues-single.vtt" . "Feb 22")
   ("~/proj/french/recordings/2026-02-26-virelangues-script.vtt" . "Feb 26")
   ("~/proj/french/recordings/2026-02-27-virelangues-script.vtt" . "Feb 27")
   ("~/proj/french/recordings/2026-03-03-virelangues.vtt" . "Mar 3")
   ("~/proj/french/analysis/virelangues/2026-03-06-raphael-script.vtt" . "Mar 6"))
 "~/proj/french/analysis/virelangues/Le-roi-croit-voir-trois-noix.vtt"
 nil 'my-subed-simplify))
File ID Comments All Acc Flu Comp Conf Phonemes
01 Feb 20 76 78 73 83 85 ɛmiwəkɔwɑtwɑsmɑs
02 Feb 22 97 98 97 100 88 jusɒl̪vwatlɒnwa
03 Feb 26 97 100 96 100 89 əkwɒwɑtwanwɑ
04 Feb 27 96 95 97 100 87 jɛʀwɑl̪ɛvwɑtwɛnwɑ
05 Mar 3 94 96 92 100 88 juwakwɒl̪dwɑwanwɑ
06 Mar 6: : Uh huh 98 100 97 100 88 jal̪aʁwaɒll̪awandwa
Emacs Lisp code for collecting matching subtitles
(defun my-subed-record-collect-matching-subtitles (text files output-file &optional match-fn transform-fn)
  "Find subtitles that match TEXT in FILES and write to OUTPUT-FILE.
FILES can be a list of filenames or a list of (FILE . NOTE) pairs.
By default, TEXT is an approximate match based on
`subed-word-data-compare-normalized-string-distance'.
If TRANSFORM-FN is specified, use that on SUBTITLE-TEXT before comparing.
If MATCH-FN is specified, use that to match instead. It will be called
with the arguments input-text and subtitle-text.
Return OUTPUT-FILE."
  (subed-create-file
   output-file
   (seq-mapcat
    (lambda (o)
      (let ((media-file (subed-guess-media-file nil (expand-file-name (if (consp o) (car o) o)))))
        (seq-keep
         (lambda (sub)
           (when transform-fn
             (setf (elt sub 3) (funcall transform-fn (elt sub 3))))
           (when (funcall (or match-fn 'subed-word-data-compare-normalized-string-distance) text (elt sub 3))
             ;; Add audio note if missing
             (unless (or (subed-record-get-directive "#+AUDIO" (elt sub 4))
                         (null media-file))
               (setf (elt sub 4)
                     (subed-record-set-directive "#+AUDIO" media-file (or (elt sub 4) ""))))
             ;; Prepend note if specified
             (when (and (consp o) (cdr o))
               (let ((current-note (subed-record-get-directive "#+NOTE" (elt sub 4))))
                 (setf (elt sub 4)
                       (subed-record-set-directive
                        "#+NOTE"
                        (if current-note
                            (concat (cdr o) ": " current-note)
                          (cdr o))
                        (or (elt sub 4) "")))))
             sub))
         (subed-parse-file (car o)))))
    files)
   t)
  output-file)

Workflow

Extracting parts of the recording

I use OBS to record both my microphone and the tutor's voice. I use WhisperX to transcribe my recording with speaker diarization.

Shell script
#!/bin/zsh
WHISPER_ARGS=(${(z)WHISPER_FLAGS})
MAX_LINE_WIDTH="${MAX_LINE_WIDTH:-50}"
MODEL="${MODEL:-large-v2}"
for FILE in "$@"; do
    text="${FILE%.*}.txt"
    if [  -f "$text" ]; then
       echo "Skipping $FILE as it's already been transcribed."
    else
        ~/vendor/whisperx/.venv/bin/whisperx --model "$MODEL" --diarize --hf_token $HUGGING_FACE_API_KEY --language fr --align_model WAV2VEC2_ASR_LARGE_LV60K_960H --compute_type int8 --print_progress True --max_line_width $MAX_LINE_WIDTH --segment_resolution chunk --max_line_count 1 --initial_prompt "Emacs et Org Mode sont d'excellents outils. J'utilise Org-roam pour prendre des notes. Today I am recording a braindump about technical setups. C'est vraiment utile pour la productivité." "$FILE" $WHISPER_FLAGS
        rm -f "${FILE%.*}.srt"
    fi
done

I can review the VTT manually, but it's also useful to be able to quickly extract different attempts at the phrases or words. I added subed-record-extract-all-approximately-matching-phrases to subed-record.el so that I can generate a starting point with something like this:

(subed-record-extract-all-approximately-matching-phrases
   phrases
   "/home/sacha/sync/recordings/processed/2026-03-06-raphael.json"
   "/home/sacha/proj/french/analysis/virelangues/2026-03-06-raphael-script.vtt")

Ideas:

  • Group by speaker ID to make it possible to extract phrases even with interstitial corrections.
  • Add an optional parameter that lets me append to an existing file.

Here's a copy of its output: file:///home/sacha/proj/french/analysis/virelangues/2026-03-06-raphael-script-original.vtt

2026-03-06_13-49-52.png
Figure 1: Screenshot with subed-waveform showing the waveforms for each segment

Based on the waveforms, I can see that some timestamps need to be adjusted, and some phrases may need to be duplicated, trimmed, or split. This is the file that I ended up with.

file:///home/sacha/proj/french/analysis/virelangues/2026-03-06-raphael-script.vtt

Sometimes I want to do a deep dive on a specific word. Here's another function that uses the WhisperX JSON data to extract just single words, like in my analysis of "mule".

Extracting words
(defun my-subed-record-extract-words (word word-data-file output-file)
  (let ((media-file (subed-guess-media-file nil word-data-file)))
    (subed-create-file
     output-file
     (mapcar (lambda (o)
               (list nil
                     (alist-get 'start o)
                     (alist-get 'end o)
                     (alist-get 'text o)
                     (format "#+AUDIO: %s\n#+WHISPER_SCORE: %d\n#+SPEAKER: %s"
                             media-file
                             (* (alist-get 'score o) 100)
                             (or (alist-get 'speaker o) ""))))
             (sort
              (seq-filter
               (lambda (o)
                 (subed-word-data-compare-normalized-string-distance
                   word
                   (alist-get 'text o)))
               (subed-word-data-parse-file
                word-data-file))
              :key (lambda (o) (alist-get 'score o))
              :reverse t))
     t)))

Azure pronunciation assessment

I can use the following code from any subtitle of 30 seconds or less to automatically extract the audio for that subtitle and add a comment with the scores from Microsoft Azure pronunciation assessment. It needs an API key and region. I think the free tier includes 5 hours of speech each month, with additional hours priced at USD 0.66 per hour for short files less than 30 seconds (billed in 1-second increments). There's another API that can handle longer segments for USD 1.32 per hour (also included in the 5 hours free), but I'll probably need a Python or NodeJS program. Since I'm working with words and short sentences for now, I can use the REST API.

(defvar my-subed-record-azure-assessment nil)
(defvar my-subed-record-azure-assess-pronunciation-lang "fr-FR")
;; (my-subed-record-azure-assess-pronunciation "~/proj/french/analysis/mule/ref-france-sample.wav" "mule")
(defun my-azure-assess-pronunciation (audio-file reference-text)
  "Send AUDIO-FILE to Azure for pronunciation assessment against REFERENCE-TEXT.
Needs the AZURE_SPEECH_REGION and AZURE_SPEECH_KEY environment variables."
  (interactive (list (read-file-name "Audio: ")
                     (read-string "Text: ")))
  (let* ((url (format "https://%s.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=%s&format=detailed"
                      (getenv "AZURE_SPEECH_REGION")
                      (url-hexify-string my-subed-record-azure-assess-pronunciation-lang)))
         ;; 1. Create the Configuration JSON
         (config-json (json-encode `(("ReferenceText" . ,reference-text)
                                     ("GradingSystem" . "HundredMark")
                                     ("Granularity"   . "Phoneme")
                                     ("Dimension"     . "Comprehensive"))))
         ;; 2. Base64 encode the config
         (config-base64 (base64-encode-string (encode-coding-string config-json 'utf-8) t))
         ;; 3. Prepare headers
         (headers `(("Ocp-Apim-Subscription-Key" . ,(getenv "AZURE_SPEECH_KEY"))
                    ("Pronunciation-Assessment"  . ,config-base64)
                    ("Content-Type"              . "audio/wav; codecs=audio/pcm; samplerate=16000")))
         (data (plz 'post url
                 :headers headers
                 :body `(file ,audio-file)
                 :as #'json-read)))
    (when (called-interactively-p 'any)
      (kill-new (my-subed-record-azure-format-assessment data))
      (message "%s" (my-subed-record-azure-format-assessment data)))
    data))

(defun my-subed-record-azure-assess-pronunciation (&optional beg end)
  "Assess the pronunciation of the current cue."
  (interactive (if (region-active-p)
                   (list (region-beginning)
                         (region-end))
                 (list (subed-subtitle-start-pos)
                       (save-excursion
                         (subed-jump-to-subtitle-end)
                         (point)))))
  (subed-for-each-subtitle beg end t
    (let* ((temp-file (make-temp-file "subed-record-azure" nil ".wav"))
           (text (my-subed-simplify (subed-subtitle-text)))
           data
           score)
      (subed-record-extract-audio-for-current-subtitle-to-file temp-file)
      (setq data
            (my-azure-assess-pronunciation
             temp-file
             text))
      (when data
        (subed-record-set-directive
         "#+SCORE"
         (my-subed-record-azure-format-assessment data))
        (setq my-subed-record-azure-assessment data))
      (delete-file temp-file)
      data)))

(defun my-subed-record-azure-format-assessment (&optional data)
  (setq data (or data my-subed-record-azure-assessment))
  (let-alist (car (alist-get 'NBest data))
    (format "%d; A: %d, F: %d, C: %d, Conf: %d"
            (or .PronunciationAssessment.PronScore .PronScore)
            (or .PronunciationAssessment.AccuracyScore .AccuracyScore)
            (or .PronunciationAssessment.FluencyScore .FluencyScore)
            (or .PronunciationAssessment.CompletenessScore .CompletenessScore)
            (* 100.0 (or .PronunciationAssessment.Confidence
                         .Confidence)))))

Phonemes with Allosaurus, Espeak, or Kokoro FastAPI

(defvar my-subed-record-allosaurus-command '("/home/sacha/proj/french/.venv/bin/python3" "-m" "allosaurus.run" "--lang" "fra" "-i"))
(defun my-subed-record-allosaurus-phonemes (&optional beg end)
  (interactive (if (region-active-p)
                   (list (region-beginning)
                         (region-end))
                 (list (subed-subtitle-start-pos)
                       (save-excursion
                         (subed-jump-to-subtitle-end)
                         (point)))))
  (subed-for-each-subtitle beg end t
    (let* ((temp-file (make-temp-file "subed-record-allosaurus" nil ".wav")))
      (subed-record-extract-audio-for-current-subtitle-to-file temp-file)
      (subed-record-set-directive
       "#+PHONEMES"
       (with-temp-buffer
         (apply #'call-process (car my-subed-record-allosaurus-command)
                nil t nil
                (append
                 (cdr my-subed-record-allosaurus-command)
                 (list temp-file)))
         (delete-file temp-file)
         (replace-regexp-in-string " " "" (buffer-string)))))))

(defun my-lang-espeak-ng-phonemes (text)
  (interactive "MText: ")
  (let ((data
         (with-temp-buffer
           (call-process "espeak" nil t nil "-q" "--ipa" text)
           (string-trim (buffer-string)))))
    (when (called-interactively-p 'any)
      (kill-new data)
      (message "%s" data))
    text))
(defun my-french-kokoro-fastapi-phonemes (s)
  (interactive "MText: ")
  (my-kokoro-fastapi-ensure)
  (let ((data (alist-get 'phonemes
                         (plz 'post "http://localhost:8880/dev/phonemize"
                           :headers '(("Content-Type" . "application/json"))
                           :body (json-encode `((text . ,s)
                                                (language . "fr-fr")))
                           :as #'json-read))))
    (when (called-interactively-p 'any)
      (kill-new data)
      (message "%s" data))
    data))

Splitting into segments and making a table

(defun my-subed-record-make-groups (subtitles)
  "Come up with a good ID for attempts, grouping by cue text."
  (let* ((group-num 0)
         (sub-num 0)
         (grouped ; ((text . (start start start)) ...)
          (mapcar
           (lambda (o)
             (cons (car o)
                   (mapcar
                    (lambda (sub) (elt sub 1))
                    (cdr o))))
           (seq-group-by
            (lambda (o) (elt o 3))
            (seq-remove (lambda (o)
                          (and (elt o 4)
                               (string-match "#\\+SKIP" (elt o 4)))) subtitles))))
         (one-group (= (length grouped) 1)))
    (seq-mapcat
     (lambda (o)
       (setq group-num (1+ group-num))
       (unless one-group (setq sub-num 0))
       (seq-map
        (lambda (sub-start)
          (setq sub-num (1+ sub-num))
          (cons sub-start (if one-group
                              (format "%02d" sub-num)
                            (format "%02d-%02d" group-num sub-num))))
        (cdr o)))
     grouped)))

(defun my-subed-record-analyze-file-with-azure (vtt &optional always-create filter)
  (with-current-buffer (find-file-noselect vtt)
    (let* (results
           filename
           (ids (my-subed-record-make-groups
                 (seq-filter
                  (lambda (o)
                    (if filter
                        (string-match filter (elt o 3))
                      'identity))
                  (subed-subtitle-list))))
           id)
      (subed-for-each-subtitle (point-min) (point-max) t
        (unless (and (subed-subtitle-comment)
                     (string-match "#\\+SKIP" (subed-subtitle-comment))
                     (or (null filter)
                         (string-match filter (subed-subtitle-text))))
          (setq id (alist-get (subed-subtitle-msecs-start) ids))
          (setq filename (expand-file-name
                          (format "%s-%s.opus"
                                  (file-name-base vtt)
                                  id)
                          (file-name-directory vtt)))
          (when (or always-create (not (file-exists-p filename))) (subed-record-extract-audio-for-current-subtitle-to-file filename))
          (unless (string-match (regexp-quote "#+SCORE") (subed-subtitle-comment))
            (my-subed-record-azure-assess-pronunciation))
          (unless (string-match (regexp-quote "#+PHONEMES") (subed-subtitle-comment))
            (my-subed-record-allosaurus-phonemes))
          (let* ((comment (subed-subtitle-comment))
                 (scores (mapcar (lambda (o)
                                   (if (string-match (concat (regexp-quote o) ": \\([0-9]+\\)") comment)
                                       (match-string 1 comment)
                                     ""))
                                 '("#+WHISPER_SCORE" "#+SCORE" "A" "F" "C" "Conf")))
                 (phonemes (when (string-match "#\\+PHONEMES: \\(.+\\)" comment)
                             (match-string 1 comment)))
                 (text (subed-subtitle-text))
                 (extra-comment (when (string-match "#\\+NOTE: \\(.+\\)" comment) (match-string 1 comment))))
            (push
             (append
              (list (org-link-make-string (format "audio:%s?icon=t" filename) "▶️")
                    id
                    (or extra-comment ""))
              scores
              (list
               (org-link-make-string (concat "abbr:" text) phonemes)))
             results))))
      (setq results
            (cons
             '("File" "ID" "Comments" "WhisperX" "All" "Acc" "Flu" "Comp" "Conf" "Phonemes")
             results))
      (my-org-table-remove-blank-columns results t))))

(defun my-org-table-remove-blank-columns (data &optional has-header)
  "Remove blank columns from DATA.
Skip the first line if HAS-HEADER is non-nil."
  (cl-loop
   for i from (1- (length (car results))) downto 1
   do (unless (delq nil
                    (mapcar
                     (lambda (o)
                       (and (elt o i)
                            (not (string= (elt o i) ""))))
                     (if has-header
                         (cdr data)
                       data)))
        (setq data
              (mapcar
               (lambda (o)
                 (seq-remove-at-position o i))
               data))))
  data)

Collecting segments from multiple sessions

First I collect a sample from different files

View Org source for this post

La semaine du 2 mars au 8 mars

| french

lundi 2 mars

J'ai préparé ma newsletter sur Emacs et j'ai écrit un article sur l'affichage d'indices pour des raccourcis clavier. J'ai aussi essayé l'expansion des snippets par commande vocale. Je pense que l'expansion des snippets est utile parce que quand j'insère un snippet à partir d'initiales, je dois penser à l'expression et puis penser aux lettres initiales, mais quand j'insère un snippet par commande vocale, je peux utiliser l'expression naturelle. Bien sûr, il y a un bref délai pour la transcription, mais c'est suffisamment court pour ne pas couper le fil de mes pensées.

Ma fille était trop fatiguée pour son cours de gymnastique, donc je l'ai emmenée chez la dentiste pour un examen à cause de sa douleur dentaire. La dentiste a dit que ses gencives sont un peu enflées. Elle nous a recommandé de ramollir sa brosse à dents sous l'eau chaude avant de se brosser les dents et peut-être d'utiliser un bain de bouche salin. Ma fille s'est plainte que ses dents semblent trop serrées. La dentiste a dit que c'est acceptable pour le moment, et si nous voulons, elle peut nous orienter vers un orthodontiste. Quand j'étais plus jeune, je ne supportais pas l'appareil dentaire, mais c'est possible que ma fille puisse le supporter. Je pense que c'est mieux que nous attendions que le pic de concentration virale dans les eaux usées soit passé.

Après la vaisselle et ma routine du soir, ma fille et moi avons cousu à la main notre projet de petit sac avec quelques poches.

mardi 3 mars

J'ai travaillé sur les virelangues pendant le rendez-vous avec mon tuteur. Les sons « r » et « u » ont continué à me poser des difficultés. Je vais travailler sur la différence entre « roue » et « rue », le mot « brume », et quelques autres. Il a dit que le « r » a besoin de moins d'air.

Les résultats aujourd'hui :

Je me demande quel serait une bonne méthode et une bonne interface pour m'entraîner seule à la prononciation entre les rendez-vous avec mon tuteur. Je pense que le processus comprend les étapes suivantes :

  1. Apprendre à écouter la différence entre l'exemple et un énoncé incorrect : il s'agit d'abord de distinguer qu'ils sont différents, puis de comprendre pourquoi.
    • Si j'extrais les énoncés de mes enregistrements et que je les annote avec les classifications de mon tuteur, je peux les utiliser pour l'apprentissage supervisé afin d'exercer mon oreille. Ces enregistrements seront trop ennuyeux pour d'autres, mais pour moi, il vaut peut-être mieux que je les écoute pour mieux apprendre.
  2. Identifier lequel des deux énoncés est le meilleur.
    • Je peux randomiser les courts enregistrements de l'étape précédente pour créer un jeu.
  3. Essayer de produire des sons variés. Il faut m'entraîner, il n'y a évidemment pas d'autre solution.
  4. Écouter la différence entre l'exemple et le son que j'ai produit. Déterminer si le son est assez bon. Réfléchir à la connexion entre les mouvements de la bouche et le son qu'ils produisent.
  5. Produire le son de manière isolée. Connecter la sensation interne de produire le son avec le son que je veux produire, parce que le son que j'enregistre diffère du son que j'écoute en parlant.
  6. Produire le son systématiquement.
  7. Produire le son même si je n'écoute pas de modèle et je ne viens pas de le répéter.
  8. Utiliser le son dans le contexte d'une expression avec des pauses.
  9. Dire l'expression plus fluidement.
  10. Dire l'expression sans exemple.

Si c'était un problème résolu facilement, tout le monde utiliserait et recommanderait la solution. Je pense qu'il n'y a pas de bonne solution sur le marché à l'exception de la méthode que j'ai utilisée pour la formation de mon petit projet d'intelligence humaine générale (qui a 10 ans maintenant, comme elle me le dit souvent) : une quantité massive de données. Mais bien sûr, il y a beaucoup de recherches dont je peux profiter.

Oooh, j'ai hâte d'essayer des spectrogrammes en plus des formes d'onde. Il y a quelques logiciels qui peuvent afficher les spectrogrammes même en temps réel. C'est possible que ça facilite l'analyse des voyelles.

Donc, je peux utiliser les horodatages par mot de WhisperX pour segmenter l'enregistrement. Mais je dois les écouter dans le contexte du rendez-vous pour les associer avec les commentaires de mon tuteur, sauf si la segmentation par locuteur est fiable pour identifier quels énoncés ont obtenu un « oui » ou « c'est mieux » de mon tuteur et quels énoncés lui font dire « non ». Pour le moment, je pense que c'est plus fiable si j'écoute la conversation et annote les segments moi-même, donc une interface qui affiche les formes d'onde segmentées et me permet de faire des sélections par raccourcis clavier serait utile. Si les scores sont disponibles, les afficher sous forme de graphique à barres est peut-être plus précis et plus facile à comparer que les afficher à l'aide d'un dégradé de couleurs. Je peux aller voir du côté de Label Studio ou Praat pour des idées à implémenter sur Emacs. Ou bien, si j'utilise Audino 2.0 ou d'autres projets similaires sur le web, je peux les annoter pendant mes moments perdus.

Pendant la pratique, je pense que mon interface doit lancer l'enregistrement de mon tuteur et peut-être afficher la forme d'onde ou le spectrogramme. Elle doit enregistrer ma voix, puisqu'elle doit lancer la lecture de l'exemple du tuteur et l'enregistrement de ma voix pour comparaison avec le score de confiance de WhisperX. Des raccourcis clavier lancent l'un ou l'autre.

Notre réseau

Mon tuteur a une question sur les réseaux informatiques, donc je vais profiter de cette occasion pour expliquer notre réseau en français afin d'apprendre plusieurs mots techniques en cours de route. Mon mari est principalement responsable de l'entretien de notre réseau, mais je devrais également m'y former.

Mon mari a recommandé des ressources pour les gens intéressés :

  • Jim's Garage : recommandé vivement, mais le Homelab 2.0 dont il a discuté dans les vidéos récentes commence à coûter cher.
  • Serve the Home
  • Reddit, bien sûr

Notre réseau :

  • Notre modem fibre optique du FAI se connecte à un mini-ordinateur Lenovo M920q qui fait fonctionner Proxmox pour la gestion de pare-feu et quelques machines virtuelles. Une des machines virtuelles est OPNSense, qui gère les adresses réseau, le pare-feu, le lissage du trafic réseau (y compris la règle consistant à couper l'accès à internet de notre enfant tard le soir) et divers réseaux virtuels (VLAN) pour isoler les différents appareils via l'adaptateur réseau Gigabit Intel 893647. L'Internet des objets manque souvent de mises à jour, donc mon mari veut les isoler de nos autres ordinateurs. OPNSense lui-même reçoit des mises à jour. En fait, mon mari l'a mis à jour récemment, et il est passé de 16 à 32 gigaoctets de RAM. Mon mari a dit qu'il apprécie que le Lenovo M920q soit assez silencieux.
  • Le M920Q se connecte à un commutateur réseau ASUS GS108Tv2, qui se connecte au Synology DS718+ pour le stockage réseau et à l'Odroid-XU4 qui fait aussi fonctionner PiHole pour réduire les publicités. Proxmox sur le M920q a aussi une machine virtuelle qui est responsable de sauvegarder les fichiers sur le Synology DS718+.
  • Le commutateur réseau ASUS GS108Tv2 se connecte au routeur wifi ASUS RT-AC66U qui utilise FreshTomato pour avoir plus de contrôle qu'avec le modem fibre optique. Il est capable de wifi 5 GHz et il peut traiter les réseaux wifi virtuels (deux ou plus de SSIDs dans la même bande 2,4 GHz ou 5 GHz) pour isoler les appareils comme le thermostat. De cette façon, les appareils fiables comme nos ordinateurs ne sont pas visibles par les appareils non sécurisés.
  • Le routeur wifi se connecte à un commutateur réseau non géré qui se connecte à un Odroid-C4 qui utilise OpenELEC et à notre vieille Sony PS3.

Nous utilisions le routeur wifi ASUS RT-AC66U avec FreshTomato pour notre réseau, mais mon mari a mis à niveau vers le Lenovo M920q pour faciliter la gestion des réseaux virtuels et pour optimiser le débit. Il a dit qu'il avait choisi les composants pour minimiser l'espace, la consommation d'énergie et le bruit. Rien n'est neuf et tout peut être acheté sur Ebay ou le marché de l'occasion. Pour le moment, la RAM et le stockage coûtent très cher, et nous n'avons pas besoin de haute disponibilité ou réplication.

network.png

Après l'école, ma fille a eu de l'énergie, donc je l'ai emmenée à un cours de rattrapage de gymnastique. C'était un cours collectif de tissu aérien. Pendant que ma fille participait en classe, j'ai étudié mes cartes Anki. Elle a globalement aimé le cours à l'exception de ses chaussettes perdues. Malheureusement, quelqu'un a pris les chaussettes de ma fille au lieu des siennes. Je me suis retenue de dire qu'elle aurait dû me donner ses affaires à garder.

mercredi 4 mars

J'ai écrit un article sur l'expansion de snippets par la reconnaissance vocale sur Emacs et sur d'autres applications.

J'ai essayé le bilan de prononciation d'Azure et la transcription des phonèmes par la bibliothèque Allosaurus, mais je pense que ceux-ci ne sont ni fiables ni adaptés à mes objectifs. Je ne sais pas si les scores d'Azure sont utiles. Allosaurus ne me donne pas l'API que je veux, même si j'analyse l'enregistrement de mon tuteur. (Je dois le vérifier avec le résultat de la synthèse vocale…)

Le cours phonologique de FSI contraste deux exemples courts similaires pour développer la compétence d'identification des différences. Pour le moment, mieux vaut améliorer mon processus pour extraire et écouter les segments vocaux de mon rendez-vous que de s'entraîner d'une façon peu fiable et probablement incorrecte mais avec assurance.

Ma fille et moi avons fait des courses. Après une pause, ma fille et moi sommes allées au parc pour jouer à Pokémon Go avec beaucoup d'autres dresseurs. Nous avons gagné quelques raids, mais ma fille n'a pas attrapé les Pokémons qu'elle voulait. Elle était un peu déçue, mais elle a dit que c'était une bonne promenade de toute façon.

Ma fille était de mauvaise humeur à l'heure du coucher à cause de mon conseil pendant le brossage. Je suis restée calme et je lui ai donné de l'espace.

jeudi 5 mars

Ma fille s'est réveillée toute seule ce matin et elle a pris son petit-déjeuner, mais elle n'a pas voulu assister à ses cours en ligne. La harceler n'est pas utile, donc je l'ai laissée gérer ses propres émotions. J'ai travaillé sur le piano. J'ai aussi amélioré l'automatisation pour rassembler les jalons de distribution pour la Bike Brigade en utilisant Spookfox. J'ai découvert que la clé est d'utiliser le code

document.querySelector('form[phx-change="update_options"]')
  .dispatchEvent(new Event('submit', {bubbles: true, cancelable:true}))

pour mettre le tableau à jour après avoir changé les dates. Spookfox ne me permet pas d'attendre le résultat s'il prend du temps, donc je dois attendre dans Emacs Lisp comme ça :

(let (result)
  (dolist (block-name '("milestone-this-month-set"
                        "milestone-this-month-get"
                        "milestone-before-month-set"
                        "milestone-before-month-get"
                        "milestone-after-month-set"
                        "milestone-after-month-get"
                        "milestone-summary"))
    (setq result
           (org-babel-execute-src-block
            nil
            (org-babel-lob--src-info block-name)
            nil 'babel-call))
    (when (string-match "-set" block-name)
      (message "Waiting after %s..." block-name)
      (sit-for 3)))
  (kill-new result)
  (message "Copied."))

De cette façon, j'ai simplifié le processus pour réduire le nombre de clics. Le code complet est ici.

vendredi 6 mars

J'ai adoré travailler sur ma prononciation via mes notes sur notre réseau sur lequel mon tuteur m'avait interrogée mardi et mon mari m'avait aidée. J'ai besoin de travailler encore sur l'alphabet, qui est nécessaire pour lire les noms de modèles à voix haute. Mon tuteur a aussi des questions sur les LLM. J'ai hâte d'écrire plus de notes.

Nous avons réarrangé des meubles parce que le nouveau lit arrive demain pour notre fille. Nous avons déplacé les étagères dans la chambre de ma fille dans un coin qui est mon nouvel espace bureau.

Ma fille était trop frustrée par l'école aujourd'hui. Elle a séché ses cours, et elle a voulu rentrer plus tôt de sa sortie avec son amie. Je pense que cette journée était un peu difficile pour elle. Je me suis rappelé de penser sur le long terme, sans harcèlement.

samedi 7 mars

Ma fille et moi avons joué à Donjons et Dragons avec mes sœurs et mes nièces. Nous avons bien aimé la partie. Dans l'histoire, il y avait des kobolds qui habitent dans une des Cavernes du Chaos et qui regrettent d'avoir attrapé un ours. L'ours avait très faim et les kobolds aussi, parce que les kobolds lui donnent leur nourriture pour éviter d'avoir mal. La clerc (ma fille) et la guerrière (une de mes nièces) ont réussi à attirer l'ours dehors avec des bleuets. Ma sœur la magicienne a mené la charge contre des maraudeurs qui habitaient dans une autre caverne, et nous les avons vaincus. Dans une chambre, nous avons vu deux coffres, mais nous avons trouvé qu'un coffre était en fait un imitateur. Après un autre combat, nous avons trouvé 150 pièces d'or, des bottes et une potion mystérieuse.

Après le déjeuner, ma fille et moi avons fait une promenade au parc pendant que nous jouions à Pokémon Go. Il faisait beau avec beaucoup de brume qui semblait un peu magique.

Puis, mon mari et moi avons démonté l'ancien lit de ma fille et quelques autres meubles dans sa chambre pour créer de l'espace pour son nouveau lit.

dimanche 8 mars

Ma fille a réussi à éviter de tomber de son nouveau lit mezzanine. Succès ! Mon mari a fini de poncer et de vernir le garde-corps qu'il fabriquait en bois, donc il l'a installé pour nous permettre d'utiliser le matelas qui est trop épais pour le garde-corps original.

J'ai commencé à externaliser mon code dans un nouveau package d'apprentissage des langues. Je ne sais pas s'il est utile aux autres, mais si je veux aider les autres à essayer, il a besoin d'un peu de travail.

Il faisait très beau. Mon mari, ma fille et moi sommes allés à IKEA pour acheter des coussins, des lumières et un tapis de gym pour le petit coin jeu sous le nouveau lit de ma fille. Pendant ce temps-là, ma fille a vu un couteau qu'elle a aimé, donc nous l'avons acheté aussi. À la maison, elle a installé le tapis et les coussins elle-même. Elle a décidé de rapporter les lumières pour se faire rembourser la semaine prochaine.

Pour le dîner, nous avons préparé des nuggets de poulet, des frites et du brocoli.

Sur l'intelligence artificielle

Dans le rendez-vous précédent, mon tuteur m'a posé des questions sur l'intelligence artificielle. Je veux réfléchir sur l'IA pour travailler ma prononciation en utilisant un sujet qui nous intéresse également, et pour trouver des points d'amélioration.

D'abord, du contexte pour expliquer ma perspective :

  • Je laisse de côté les questions sur l'impact environnemental ou l'éthique des données entrantes.
  • Jusqu'à présent, j'ai essayé l'IA pour mes centres d'intérêt comme la parentalité, l'apprentissage du français et la programmation en Emacs Lisp, en Python et en Javascript. Je l'ai aussi utilisée pour faire des recherches.
  • Je travaille seulement un peu comme consultante, mais en fait, c'est juste pour le plaisir. Je ne veux pas augmenter ma charge de travail parce que je me concentre sur ma fille et mes intérêts personnels. Rien ne me presse d'utiliser l'IA (comme un chef, des clients ou des concurrents). L'IA ne me menace pas. Je peux l'utiliser ou ne pas l'utiliser, à mon gré. Je peux me focaliser sur mon bonheur.
  • Je peux consacrer une petite partie de mon budget à des essais, mais je ne veux pas travailler davantage pour rentabiliser une dépense plus importante. Pour le moment, les limites d'utilisation gratuite de Gemini, de Claude et d'Azure suffisent pour mes idées et mon temps limité. Je n'ai pas le temps de concentration nécessaire pour justifier l'investissement dans mon propre matériel, et sinon, les progrès sont trop rapides pour m'engager dans une configuration spécifique.
  • J'ai une conscience aiguë des limites cognitives ou physiques à cause des difficultés de santé de ma mère et de ma sœur, et de mes expériences avec mes limitations à cause du fait que je suis la personne principalement en charge de ma fille.
  • Je lis très vite, mais je n'ai pas assez de patience pour les longs contenus vidéo ou audio. Je n'aime pas les textes qui contiennent beaucoup de remplissage.
  • J'aime la programmation, donc je comprends un peu comment l'IA fonctionne et je ne peux pas lui attribuer une vraie intelligence. Je n'aime pas non plus les résultats imprévisibles.
  • De mon côté, c'est facile de lancer beaucoup d'idées. C'est difficile de les mener à terme. Je peine à finaliser mes tâches parce que de nouvelles idées arrivent sans cesse. Mais presque aucune de mes tâches n'est vraiment nécessaire, donc ce n'est pas grave.
  • J'aime bien l'amélioration incrémentale. Je préfère les petites étapes, les petites fonctions, les petits logiciels.
  • Beaucoup de gens ont une réaction forte contre l'IA pour plusieurs raisons qui incluent le battage médiatique excessif dont elle fait l'objet, son utilisation à mauvais escient, et l'inondation de banalité qu'elle produit.
La programmation

Pour la programmation, je trouve qu'elle fonctionne mieux pour les logiciels courts que pour les logiciels longs. Je réécris souvent la majorité du logiciel à l'exception d'un ou deux morceaux parce que ce code ne me convient pas. De temps en temps, j'utilise l'IA pour parfaire ou vérifier une idée rapidement avant de travailler sur l'idée moi-même. Je ne veux pas l'utiliser pour les correctifs que je veux soumettre à d'autres projets parce que le code ne me semble pas correct et je ne veux pas gaspiller le temps d'autres bénévoles.

Quelques exemples concrets :

  • C'était utile pour implémenter une fonction qui compare deux listes et renvoie les éléments ajoutés, enlevés, ou modifiés via un algorithme classique que je comprends un peu mais pas suffisamment pour l'implémenter moi-même.
  • C'était utile pour tester l'idée d'un serveur de Kokoro TTS qui est compatible avec le serveur speechd parce que je ne sais pas encore comment faire un serveur multithread en Python. J'aime pouvoir lui donner trois dépôts git et des instructions pour générer un logiciel à partir d'un dépôt pour un autre via le troisième dépôt. Mais je ne veux pas le publier avant de réécrire et tout comprendre.
  • C'était utile pour générer des interfaces web pour mes idées personnelles.
  • Ce n'était pas très utile pour bricoler ma configuration (à l'exception d'identifier parfois des commandes ou des variables que je ne connais pas), parce que j'aime bien le bricolage. Spécifier mes objectifs demande souvent autant de travail que de les implémenter moi-même.

Mon mari a son propre abonnement à Claude IA. Il a dit qu'il l'apprécie parce que l'IA peut gérer plusieurs petites tâches qui autrement nécessitent beaucoup de recherches. De mon côté, j'utilise souvent Gemini IA parce que sa limite d'utilisation gratuite est généreuse. J'ai aussi essayé Claude Code, mais mes connaissances sont limitées. Il semble utile, mais je préfère l'isoler dans une machine virtuelle, donc c'est peu pratique pour moi en ce moment.

L'IA est très utile pour utiliser des commandes qui ont beaucoup d'options comme ffmpeg ou gnuplot.

Je ne trouve pas l'IA assez fiable pour la laisser agir complètement indépendamment. Peut-être un jour, mais pour moi, pas encore.

L'apprentissage du français

J'aime utiliser l'IA pour me donner des retours sur mes textes. Si j'utilise seulement le dictionnaire, je ferai beaucoup d'anglicismes à cause de la traduction littérale. Les sujets qui m'intéressent sont un peu rares, donc ce sera peut-être difficile de trouver un tuteur qui se concentre exactement sur ceux-là. C'est un peu inefficace de corriger mon écriture mot à mot avec un professionnel. Mon journal et mes pensées ne sont pas si importants. Avec l'IA, je n'ai pas à perdre de temps avec mon tuteur pour corriger beaucoup d'erreurs comme l'accord du nom et du verbe ou les mots maladroits, et je découvre de nouveaux mots et expressions. Les suggestions de l'IA sont de temps en temps bizarres, donc c'est toujours une bonne idée de vérifier avec de vraies personnes. Sans l'IA, je pourrais peut-être apprendre plus lentement avec l'aide d'Internet, qui a beaucoup de ressources comme Vitrine linguistique.

J'ai essayé l'IA pour faire des commentaires sur ma prononciation, mais je pense que ce n'est pas encore fiable et je n'ai pas l'expérience pour bien juger. Je peux peut-être vérifier mes résultats avec un tuteur, mais c'est peut-être difficile à cause des objectifs contradictoires, comme les personnes à qui l'on demande de former leurs remplaçants. En fait, je ne veux pas remplacer la connexion humaine. Je veux profiter davantage, apprendre davantage avec l'aide de vraies personnes, complétée par l'aide de l'IA. Il y a des chercheurs qui étudient les applications de l'IA à l'apprentissage des langues. Je peux attendre leurs découvertes. En attendant, je pense qu'il vaut mieux utiliser l'IA pour comprendre d'autres manières d'analyser la prononciation moi-même, et pour construire des outils personnalisés peut-être comme les résumés et les extraits de nos rendez-vous, les visualisations de mes tentatives, ou une interface pour enregistrer et écouter en temps réel.

De temps en temps, j'essaye de générer des histoires ou des articles compréhensibles de mon niveau (ou presque). Pour le moment, je préfère d'autres ressources pour la lecture, comme les sous-titres d'émissions. Néanmoins, les traductions automatiques sur Reddit m'intéressent, donc j'ai réussi à remplacer mon fil d'actualité par un flux en français.

Je ne suis pas encore prête à converser avec des IA par la voix. J'ai essayé la conversation libre et le dialogue presque scénarisé. J'adore les sous-titres simultanés, mais je n'ai pas toujours trouvé une méthode ou un système qui me convienne. Dans la conversation libre, je sais que l'interlocuteur est une IA, donc je n'ai pas une vraie curiosité pour ses «intérêts ou pensées». La conversation semblait très artificielle. En plus, je pense que je préférerais en construire un moi-même pour plus de contrôle. De toute façon, ma prononciation, ma grammaire et mon vocabulaire ont besoin de travail. Dans le dialogue scénarisé, je n'ai pas encore un vocabulaire assez riche pour discuter des sujets dans les exercices généraux. Si je répète simplement, je n'ai pas besoin d'IA pour ça.

La parentalité

J'ai parfois utilisé Claude IA pour générer des histoires interactives sur les centres d'intérêt de ma fille. Les histoires incluent les mots que ma fille doit apprendre pour sa classe. Elles permettent de taper sur un mot pour l'écouter par la synthèse vocale et pour voir la traduction. Elle aime bien ce format. L'enseignant de ma fille n'a pas le temps de personnaliser l'apprentissage du vocabulaire à ce point, et elle est trop imprévisible pour planifier ses propres rendez-vous avec un tuteur.

Elle aime générer d'autres histoires interactives avec l'IA elle-même, comme des petits jeux sur KPop Demon Hunters ou Pokémon. Je pense que c'est une bonne façon de s'entraîner à réfléchir à ce qu'elle veut, comment l'expliquer et comment le peaufiner.

Elle a 10 ans. Personne ne sait à quoi ressemblera vraiment le monde quand elle sera grande. Je pense que c'est mieux que mon mari et moi montrions comment approcher, comment apprendre, comment décider ce que nous pensons, sans peur ni battage publicitaire.

Sans l'IA, nous pourrions improviser nos propres histoires. Mais je pense que la capacité de lui donner plus de contrôle dans une boucle de rétroaction1 rapide est une bonne chose.

Je n'aime pas l'utiliser pour essayer de résoudre mes dilemmes de parentalité parce que l'IA confirme toujours quoi qu'on lui donne. De temps en temps, je l'utilise pour générer des questions pour réfléchir, ce qui est un peu plus utile.

Mélanges

J'aime bien la reconnaissance vocale parce qu'elle me permet de saisir plus d'idées plus vite (avant de les oublier) et d'analyser les transcriptions sans avoir à réécouter tous les enregistrements. Beaucoup de raisons peuvent empêcher une personne de taper. J'aime bien la programmation et l'écriture, et je veux continuer longtemps. J'ai hâte d'explorer des interfaces vocales.

Je pense que la manière probabiliste que l'IA utilise est prometteuse pour chercher des choses que je ne sais pas exactement, ce qui sera très utile quand on a un brouillard cérébral. Je n'aime pas les résumés qui sont souvent mauvais et qui enlèvent l'expérience de rencontrer d'autres personnes qui pensent elles aussi des choses similaires. J'aime suivre les liens où je peux en apprendre davantage. J'aime aussi poser quelques questions à l'IA avant ou au lieu de demander à une vraie personne.

Les étapes prochaines pour moi

Je vais continuer à essayer l'IA dans mes centres d'intérêt. Je veux extraire mes fonctions personnelles dans des bibliothèques de reconnaissance vocale et d'apprentissage des langues pour aider les autres, mais j'avance lentement parce que mon attention est facile à détourner. Petit à petit.

Je veux essayer les bibliothèques d'IA sous Emacs comme agent-shell. Si je peux approuver manuellement chaque commande, je pense que ce n'est pas grave.

Footnotes

1

Feedback loop? My tutor was not sure about the wording.

View Org source for this post

Emacs Lisp and NodeJS: Getting the bolded words from a section of a Google Document

Posted: - Modified: | french, js, emacs

: Simplified getting a section or finding the bolded text by using the Org Mode format instead.

During the sessions with my French tutor, I share a Google document so that we can mark the words where I need to practice my pronunciation some more or tweak the wording. Using Ctrl+B to make the word as bold is an easy way to make it jump out.

I used to copy these changes into my Org Mode notes manually, but today I thought I'd try automating some of it.

First, I need a script to download the HTML for a specified Google document. This is probably easier to do with the NodeJS library rather than with oauth2.el and url-retrieve-synchronously because of various authentication things.

require('dotenv').config();
const { google } = require('googleapis');

async function download(fileId) {
  const auth = new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/drive.readonly'],
  });
  const drive = google.drive({ version: 'v3', auth });
  const htmlRes = await drive.files.export({
    fileId: fileId,
    mimeType: 'text/html'
  });
  return htmlRes.data;
}

async function main() {
  console.log(await download(process.argv.length > 2 ? process.argv[2] : process.env['DOC_ID']));
}

main();

Then I can wrap a little bit of Emacs Lisp around it.

(defvar my-google-doc-download-command
  (list "nodejs" (expand-file-name "~/bin/download-google-doc-html.cjs")))

(defun my-google-doc-html (doc-id)
  (when (string-match "https://docs\\.google\\.com/document/d/\\(.+?\\)/" doc-id)
    (setq doc-id (match-string 1 doc-id)))
  (with-temp-buffer
    (apply #'call-process (car my-google-doc-download-command)
           nil t nil (append (cdr my-google-doc-download-command) (list doc-id)))
    (buffer-string)))

(defun my-google-doc-org (doc-id)
  "Return DOC-ID in Org Mode format."
   (let ((dom (with-temp-buffer
               (insert (my-google-doc-html doc-id))
               (libxml-parse-html-region))))
    ;; bold text is actually represented as font-weight:700 instead
    (dom-search
     dom
     (lambda (o)
       (when (and
              (string-match "font-weight:700" (or (dom-attr o 'style) ""))
              (not (string-match "font-style:normal" (or (dom-attr o 'style) ""))))
         (setf (car o) 'strong))
       (when (dom-attr o 'style)
         (dom-remove-attribute o 'style))))
    (with-temp-buffer
      (svg-print dom)
      (pandoc-convert-stdio (buffer-string) "html" "org"))))

I have lots of sections in that document, including past journal entries, so I want to get a specific section by name.

(defun my-org-get-subtree-by-name (org-text heading-name)
  "Return ORG-TEXT subtree for HEADING-NAME."
  (with-temp-buffer
    (insert org-text)
    (org-mode)
    (goto-char (point-min))
    (let ((org-trust-scanner-tags t))
      (car (delq nil
                 (org-map-entries
                  (lambda ()
                    (when (string= (org-entry-get (point) "ITEM") heading-name)
                      (buffer-substring (point) (org-end-of-subtree))))))))))

Now I can get the bolded words from a section of my notes, with just a sentence for context. I use pandoc to convert it to Org Mode syntax.

(defvar my-lang-words-for-review-context-function 'sentence-at-point)

(defun my-lang-tutor-notes (section-name)
  (my-org-get-subtree-by-name
   (my-google-doc-org my-lang-tutor-notes-url)
   section-name))

(defun my-lang-words-for-review (section)
  "List the bolded words for review in SECTION."
  (let* ((section (my-lang-tutor-notes section))
         results)
    (with-temp-buffer
      (insert section)
      (org-mode)
      (goto-char (point-min))
      (org-map-entries
       (lambda ()
         (org-end-of-meta-data t)
         (while (re-search-forward "\\*[^* ].*?\\*" nil t)
           (cl-pushnew
            (replace-regexp-in-string
             "[ \n ]+" " "
             (funcall my-lang-words-for-review-context-function))
            results
            :test 'string=)))))
    (nreverse results)))

For example, when I run it on my notes on artificial intelligence, this is the list of bolded words and the sentences that contain them.

(my-lang-words-for-review "Sur l'intelligence artificielle")

I can then go into the WhisperX transcription JSON file and replay those parts for closer review.

I can also tweak the context function to give me less information. For example, to limit it to the containing phrase, I can do this:

(defun my-split-string-keep-delimiters (string delimiter)
  (when string
    (let (results pos)
      (with-temp-buffer
        (insert string)
        (goto-char (point-min))
        (setq pos (point-min))
        (while (re-search-forward delimiter nil t)
          (push (buffer-substring pos (match-beginning 0)) results)
          (setq pos (match-beginning 0)))
        (push (buffer-substring pos (point-max)) results)
        (nreverse results)))))

(ert-deftest my-split-string-keep-delimiters ()
 (should
  (equal (my-split-string-keep-delimiters
          "Beaucoup de gens ont une réaction forte contre l'IA pour plusieurs raisons qui *incluent* le battage médiatique excessif dont elle fait l'objet, son utilisation à mauvais escient, et *l'inondation de banalité* qu'elle produit."
          ", \\| que \\| qui \\| qu'ils? \\| qu'elles? \\| qu'on "
          )
 )))

(defun my-lang-words-for-review-phrase-context (&optional s)
  (setq s (replace-regexp-in-string " " " " (or s (sentence-at-point))))
  (string-join
   (seq-filter (lambda (s) (string-match "\\*" s))
               (my-split-string-keep-delimiters s ", \\| parce que \\| que \\| qui \\| qu'ils? \\| qu'elles? \\| qu'on \\| pour "))
   " ... "))

(ert-deftest my-lang-words-for-review-phrase-context ()
  (should
   (equal (my-lang-words-for-review-phrase-context
           "Je peux consacrer une petite partie de mon *budget* à des essais, mais je ne veux pas travailler davantage pour rentabiliser une dépense plus importante.")
          "Je peux consacrer une petite partie de mon *budget* à des essais")))
(let ((my-lang-words-for-review-context-function 'my-lang-words-for-review-phrase-context))
  (my-lang-words-for-review "Sur l'intelligence artificielle"))

Now that I have a function for retrieving the HTML or Org Mode for a section, I can use that to wdiff against my current text to more easily spot wording changes.

(defun my-lang-tutor-notes-wdiff-org ()
  (interactive)
  (let ((section (org-entry-get (point) "ITEM")))
    (my-wdiff-strings
     (replace-regexp-in-string
      " " " "
      (my-org-subtree-text-without-blocks))
     (replace-regexp-in-string
      " " " "
      (my-lang-tutor-notes section)))))

Related:

Screenshot:

2026-03-12_11-28-24.png
Figure 1: wdiff
This is part of my Emacs configuration.
View Org source for this post

2026-03-09 Emacs news

| emacs, emacs-news

If you use kubernetes-el, don't update for now, and you might want to check your installation if you updated it recently. The repo was compromised a few days ago.

I've occasionally wanted to tangle a single Org Mode source block to multiple places, so I'm glad to hear that ob-tangle has just added support for multiple targets. Niche, but could be handy. I'm also curious about using clime to write command-line tools in Emacs Lisp that handle argument parsing and all the usual stuff.

If you're looking for something to write about, why not try this month's Emacs Carnival theme of mistakes and misconceptions?

Enjoy!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

Expanding yasnippets by voice in Emacs and other applications

| emacs, audio, speech-recognition

Yasnippet is a template system for Emacs. I want to use it by voice. I'd like to be able to say things like "Okay, define interactive function" and have that expand to a matching snippet in Emacs or other applications. Here's a quick demonstration of expanding simple snippets:

Screencast of expanding snippets by voice in Emacs and in other applications

Transcript
  • 00:00 So I've defined some yasnippets with names that I can say. Here, for example, in this menu, you can see I've got "define interactive function" and "with a buffer that I'll display." And in fundamental mode, I have some other things too. Let's give it a try.
  • 00:19 I press my shortcut. "Okay, define an interactive function." You can see that this is a yasnippet. Tab navigation still works.
  • 00:33 I can say, "OK, with a buffer that I'll display," and it expands that also.
  • 00:45 I can expand snippets in other applications as well, thanks to a global keyboard shortcut.
  • 00:50 Here, for example, I can say, "OK, my email." It inserts my email address.
  • 01:02 Yasnippet definitions can also execute Emacs Lisp. So I can say, "OK, date today," and have that evaluated to the actual date.
  • 01:21 So that's an example of using voice to expand snippets.

This is handled by the following code:

(defun my-whisper-maybe-expand-snippet (text)
  "Add to `whisper-insert-text-at-point'."
  (if (and text
           (string-match
            "^ok\\(?:ay\\)?[,\\.]? \\(.+\\)" text))
    (let* ((name
            (downcase
             (string-trim
              (replace-regexp-in-string "[,\\.]" "" (match-string 1 text)))))
           (matching
            (seq-find (lambda (o)
                        (subed-word-data-compare-normalized-string-distance
                         name
                         (downcase (yas--template-name o))))
                      (yas--all-templates (yas--get-snippet-tables)))))
      (if matching
          (progn
            (if (frame-focus-state)
                (progn
                  (yas-expand-snippet matching)
                  nil)
              ;; In another application
              (with-temp-buffer
                (yas-minor-mode)
                (yas-expand-snippet matching)
                (buffer-string))))
        text))
    text))

This code relies on my fork of whisper.el, which lets me specify a list of functions for whisper-insert-text-at-point. (I haven't asked for upstream review yet because I'm still testing things, and I don't know if it actually works for anyone else yet.) It does approximate matching on the snippet name using a function from subed-word-data.el which just uses string-distance. I could probably duplicate the function in my config, but then I'd have to update it in two places if I come up with more ideas.

The code for inserting into other functions is defined in my-whisper-maybe-type, which is very simple:

(defun my-whisper-maybe-type (text)
  "If Emacs is not the focused app, simulate typing TEXT.
Add this function to `whisper-insert-text-at-point'."
  (when text
    (if (frame-focus-state)
        text
      (make-process :name "xdotool" :command
                    (list "xdotool" "type"
                          text))
      nil)))

Someday I'd like to provide alternative names for snippets. I also want to make it easy to fill in snippet fields by voice. I'd love to be able to answer minibuffer questions from yas-choose-value, yas-completing-read, and other functions by voice too. Could be fun!

Related:

This is part of my Emacs configuration.
View Org source for this post

La semaine du 23 février au premier mar

| french

lundi 23 février

J'ai demandé si ses amis pourraient venir à sa fête demain. Nous avons appris qu'ils étaient malades depuis quelques semaines et ils ne pouvaient pas venir.

J'ai emmené ma fille à son cours de gymnastique à vélo parce que les rues étaient praticables. Elle s'est entraînée à faire la roue. Après ça, nous avons livré des pochettes surprises et des petits gâteaux pour ses amis qui sont malheureusement malades.

Ma fille a voulu faire des biscuits en meringues. Elle a séparé les œufs et les a battus elle-même jusqu'à ce qu'elle soit fatiguée. La première fournée n'a pas marché, mais la deuxième était acceptable. Nous les avons laissés dans le mini-four toute la nuit.

Nous avons découvert que l'axolotl en peluche qui passe au micro-ondes est une façon parfaite de chauffer nos orteils sous les couvertures. Ma fille ne s'habitue pas à l'odeur (c'est probablement la graine de lin avec la lavande), mais si c'est sous les couvertures, ça ne la dérange pas.

mardi 24 février

Les biscuits meringues sont encore trop collants ce matin. Il se trouve que j'ai oublié de les faire cuire au four pendant une heure hier soir. J'ai jeté la moitié de la fournée avant de rechercher une façon de réparer le reste. Heureusement, après les avoir cuits au four pendant une heure à basse température, les biscuits étaient acceptables.

J'ai travaillé sur la prononciation avec mon tuteur. J'ai réessayé les virelangues du rendez-vous précédent, ainsi que de nouveaux :

  • 00:00 Maman peint un grand lapin blanc.
  • 00:04 Un enfant intelligent mange lentement.
  • 00:08 Le roi croit voir trois noix.
  • 00:12 Le témoin voit le chemin loin.
  • 00:16 Moins de foin au loin ce matin.
  • 00:21 La laine beige sèche près du collège.
  • 00:25 La croquette sèche dans l'assiette.
  • 00:28 Elle mène son frère à l'hôtel.
  • 00:31 Le verre vert est très clair.
  • 00:35 Elle aimait manger et rêver.
  • 00:38 Le jeu bleu me plaît peu.
  • 00:41 Ce neveu veut un jeu.
  • 00:44 Le feu bleu est dangereux.
  • 00:47 Le beurre fond dans le cœur chaud.
  • 00:50 Les fleurs de ma sœur sentent bon.
  • 00:54 Le hibou sait où il va.
  • 00:56 L'homme fort mord la pomme.
  • 01:00 Le sombre col tombe.
  • 01:02 L'auto saute au trottoir chaud.
  • 01:07 Le château d'en haut est beau.
  • 01:09 Le cœur seul pleure doucement.
  • 01:14 Tu es sûr du futur.
  • 01:17 Trois très grands trains traversent trois trop grandes rues.
    {tʁwˈa tʁɛ ɡʁˈɑ̃ tʁˈɛ̃ tʁavˈɛʁs tʁwˈa tʁo ɡʁˈɑ̃d ʁˈy.}
  • 01:29 Je veux deux feux bleus, mais la reine préfère la laine beige.
    {ʒə vˈø dˈø fˈø blˈø, mɛ la ʁˈɛn pʁefˈɛʁ la lˈɛn bˈɛʒ.}
  • 01:37 Vincent prend un bain en chantant lentement.
    {vɛ̃sˈɑ̃ pʁˈɑ̃t œ̃ bˈɛ̃ ɑ̃ ʃɑ̃tˈɑ̃ lɑ̃tmˈɑ̃.}
  • 01:44 La mule sûre court plus vite que le loup fou.
    {la mˈyl sˈyːʁ kˈuʁ ply vˈit kə lə lˈu fˈu.}
  • 01:50 Luc a bu du jus sous le pont où coule la boue.
    {lˈyk a bˈy dy ʒˈy su lə pˈɔ̃t u kˈul la bˈu.}

Je n'ai pas enregistré de bonne tentative pour :

  • Le frère de Robert prépare un rare rôti rouge.
    {lə fʁˈɛʁ də ʁobˈɛʁ pʁepˈaʁ œ̃ ʁˈaʁ ʁotˈi ʁˈuʒ.}
  • La mule court autour du mur où hurle le loup.
    {la mˈyl kˈuʁ otˌuʁ dy mˈyʁ u ˈyʁl lə lˈu.}

Si je comprends bien, mon tuteur m'a dit que les sons dans « Maman peint un grand lapin blanc. » sont plus proches les uns des autres que dans la version de la synthèse vocale. Il a aussi prononcé « doucement » avec trois syllabes au lieu de deux. Je me demande si c'est l'accent du Midi. C'est tout à fait acceptable. Maintenant, mon objectif de prononciation est juste d'être compréhensible, pas d'atteindre un accent métropolitain ou canadien. Si j'apprends la prononciation des voyelles nasales et du «r», et que j'apprends les liaisons et les lettres muettes, je pense qu'il me sera facile de prendre un accent acceptable même si ce n'est pas parfait.

Écouter mes enregistrements n'était pas très utile. Il valait mieux lire les virelangues en voix haute pendant le rendez-vous. Peut-être que je dois modifier mon interface pour écouter les courtes parties d'enregistrements. Mais je pense que la préparation des enregistrements avant le rendez-vous a été utile.

J'ai ajouté la fonctionnalité pour couper une partie au milieu de l'enregistrement dans ma bibliothèque compile-media.

Nous avons reporté la fête d'anniversaire de ma fille à cause des maladies de ses amis. Elle a invité deux familles, mais tous les enfants étaient malades. Selon la surveillance des pathogènes dans les eaux usées, quelques maladies sont très fréquentes pour le moment. Nous leur avons donné une pochette surprise et des gâteaux.

Bien que nous n'ayons pas eu de fête, nous avons acheté la pizza que nous avions prévue.

Elle a des crampes, pauvre chérie. L'axolotl réchauffé était une source de réconfort.

mercredi 25 février

Pour une fois, ma fille s'est réveillée à temps pour le petit-déjeuner. Mais l'école virtuelle a une remplaçante aujourd'hui, donc ma fille n'a pas voulu participer à la classe. C'est la vie. Je l'ai laissée décider parce que c'est sa responsabilité.

J'ai soumis le rapport annuel de mon entreprise. C'était simple.

J'ai acheté des fleurs LEGO pour l'anniversaire de ma sœur qui habite aux Pays-Bas. Nous avons les mêmes fleurs et ma fille les adore.

J'ai participé à la réunion virtuelle Emacs Berlin. Quelqu'un nous a demandé comment trier les candidats de saisie, donc j'ai expliqué le mécanisme et j'ai créé un exemple qui trie les candidats différemment. J'ai aussi démontré consult-org-headings et edebug, et j'ai discuté d'Embark et de Consult.

J'ai emmené ma fille à la patinoire pour jouer avec son amie et la troupe de scouts de son amie. J'ai apporté 2 litres de chocolat chaud, qui est plus que suffisant pour tous les enfants. Le père de son amie leur a appris à tourner plus vite. Ils ont aussi joué au loup. Même si quatre filles ont poursuivi le père, elles ne l'ont pas attrapé.

jeudi 26 février

Une fois de plus, ma fille s'est encore réveillée à l'heure du petit-déjeuner. Elle a participé à la classe. Tout allait bien. Après l'école, elle a voulu faire des courses elle-même. Elle a emprunté deux livres à la bibliothèque et elle a acheté quelques collations au supermarché. Je l'ai suivie d'un peu loin pour partager mon Internet. Elle a envie de l'indépendance, mais elle voulait aussi jouer à Pokémon Go.

J'ai modifié le mécanisme de saisie Orderless pour traiter des lettres accentuées. J'ai aussi amélioré mes fonctions qui trient les candidats de saisie par niveau au lieu de par position. Puis j'ai écrit trois articles sur mon blog : deux sur la saisie pour le Carnaval d'Emacs et un sur les intérêts convergents pour le Carnaval IndieWeb. Je suis ravie d'écrire les fonctions et les notes.

En préparation d'un autre article, j'ai rassemblé plus de 300 liens sur la saisie tirés de mon infolettre depuis quelques années. J'ai mis à jour ma fonction pour vérifier les liens et je l'ai utilisée pour identifier les liens morts. J'ai aussi commencé à en catégoriser.

J'ai créé des fonctions pour ma bibliothèque subed-record pour écouter des références audio comme celles que j'avais extraites du rendez-vous avec mon tuteur.

J'ai dû renouveler mes certificats SSL, ce qui a nécessité de mettre à jour mon logiciel pour arrêter et redémarrer le serveur web.

vendredi 27 février

J'ai créé une fonction pour utiliser la synthèse vocale pour générer un fichier de référence audio avec les sous-titres. En la combinant avec les fonctions que j'avais écrites hier, je peux probablement suivre ma progression au fil de plusieurs essais. Je dois penser à une bonne interface pour la comparaison sur Emacs et sur Google Chrome pour faciliter le partage.

Pendant le rendez-vous avec mon tuteur, j'ai encore travaillé sur tous les virelangues. Il a dit que je m'améliorais. Progrès ! Bien sûr, j'ai besoin de plus de travail pour que ce soit plus fluide, particulièrement le « r ». Mais je construis un bon flux de travail pour enregistrer mes tentatives et les réécouter, et j'ai hâte de l'améliorer.

Le soleil brillait l'après-midi. Je me suis assise sur la terrasse de bois et j'ai profité du soleil pendant que j'écrivais mon journal. C'était merveilleux que je puisse me détendre vendredi après-midi. Quand il fait beau, je veux être dehors. Je n'ai fait que taper sur mon smartphone, mais je peux aussi lire sur ma tablette. Regarder des émissions est un peu difficile à cause de la lumière vive. Je pense que ce sera meilleur si je configure finalement une synthèse vocale et Emacspeak sur mon smartphone.

L'article de Christian Tietze m'a fait penser à la façon dont l'éditeur Emacs me permet de faire beaucoup de choses parce qu'il gère bien tous les textes. Il a utilisé Tmux pour capturer l'output et diriger vers l'IA pour fermer la boucle de rétroaction. C'est prometteur.

Après mes rendez-vous avec mon tuteur, j'utilise la reconnaissance vocale pour transcrire l'enregistrement. Maintenant que c'est du texte, je peux utiliser subed.el pour écouter certains moments. Puis je peux utiliser subed-record.el pour extraire des passages dans un fichier audio avec les sous-titres corrigés. Je peux donc les écouter, enregistrer de nouvelles tentatives, et les comparer.

J'ai modifié ma configuration pour la reconnaissance vocale. Maintenant, une fois que je dis « okay, … in French », elle le traduit et affiche le résultat comme une suggestion de saisie au lieu d'insérer directement. Cette façon m'aide à me souvenir.

Ma fille était fatiguée après l'école, donc nous sommes allées jusqu'au parc au lieu de patiner.

samedi 28 février

Les résultats de l'examen médical de ma fille sont arrivés. Son ECG était normal. Elle a dit que ses palpitations sont un peu meilleures. Selon son analyse de sang, son niveau de fer était un peu bas, comme nous tous. Il faut ajuster notre nourriture. Elle me demande si les petits pains aux haricots rouges contiennent du fer. Quelle surprise, ils ont une quantité respectable. Nous sommes tous allés à la pâtisserie chinoise à vélo pour en acheter. En cours de route, nous avons participé aux raids Pokémon et nous avons attrapé quelques mega-Pokémon avec l'aide d'autres dresseurs.

Nous avons fait du lèche-vitrines à IKEA pour réfléchir à des meubles qui conviendraient à notre fille. Elle a envie du lit en mezzanine qui crée un espace pour jouer en dessous. Elle a aussi envie d'une table à abattant avec des étagères. Avant de les acheter, il faut que nous désencombrions sa chambre et que nous mesurions l'espace.

J'ai ajouté des contributions au Carnaval Emacs sur la saisie. J'ai aussi ajouté environ 300 liens issus des archives de l'infolettre Emacs News. C'était une bonne occasion pour apprendre ensemble.

J'ai commencé à regarder les émissions de Pokémon en français sur YouTube. Ma fille adore Pokémon pour le moment, donc si j'en regarde aussi, nous pouvons bavarder. Dans le premier épisode, notre protagoniste Sacha a dormi trop tard et il a reçu le dernier Pokémon, Pikachu, qui n'a pas voulu devenir ami avec lui. Mais une fois que Pikachu a vu comment Sacha a voulu le protéger contre beaucoup de Piafabecs, Pikachu l'a aidé.

J'ai essayé Claude CLI pour générer quelques serveurs MCP pour interroger mon journal en anglais et en français, mes articles sur mon blog, et mes dessins.

dimanche premier mars

J'ai désencombré l'ensemble de tiroirs dans ma chambre et la commode dans la chambre de ma fille. J'ai rempli un sac de choses à donner et j'ai jeté des choses qui étaient cassées ou trop usées.

J'ai relu mon journal pour travailler sur mes dessins quotidiens. Je veux résumer mes revues mensuelles que j'ai perdu l'habitude de faire depuis que j'ai appris le français.

Ma fille a pleuré à cause d'une rage de dents, donc je dois prendre un rendez-vous chez la dentiste bientôt. Elle a dit que ses dents sont trop serrées. Peut-être qu'elle a besoin d'un appareil orthodontique. C'est aussi possible que je ne lui aie pas assez bien brossé les dents. Je vais essayer de faire mieux, et elle doit aussi apprendre à prendre soin d'elle-même.

L'après-midi, ma fille et moi sommes allées au parc pour jouer à Pokémon Go. Nous avons raté l'événement avec des cadeaux, mais nous avons réussi à attraper deux Pokémon légendaires avec l'aide de plusieurs autres dresseurs. Il faisait froid, donc nous sommes rentrées après une heure.

Mon mari a essayé les kits électroniques micro:bit que j'avais achetés pour apprendre l'électronique avec notre fille. Il était un peu frustré par Bluetooth, mais il a finalement réussi avec un câble direct. Je veux toujours bricoler avec le kit, mais je veux aussi apprendre beaucoup d'autres choses. On va voir.

Prononciation

  • 00:00 … les rues étaient praticables
  • 00:03 Elle s'est entraînée à faire la roue.
  • 00:07 Ma fille a voulu faire des biscuits en meringues.
View Org source for this post