Visualizing and managing Pipewire audio graphs from Emacs

| emacs

I want to be able to record, stream, screen share, and do speech recognition, possibly all at the same time. If I just try having those processes read directly from my microphone, I find that the audio skips. I'm on Linux, so it turns out that I can set up Pipewire with a virtual audio cable (loopback device) connecting my microphone to a virtual output (null sink) with some latency (100ms seems good) so that multiple applications listening to the null sink can get the audio packets smoothly.

I was getting a little confused connecting things to other things, though. qpwgraph was helpful for starting to understand how everything was actually connected to each other, and also for manually changing the connections on the fly.

2026-01-13_10-06-59.png
Figure 1: qpwgraph screenshot

Like with other graphical applications, I found myself wondering: could I do this in Emacs instead? I wanted to just focus on a small set of the nodes. For example, I didn't need all of the lines connecting to the volume control apps. I also wanted the ability to focus on whichever nodes were connected to my microphone.

Unsurprisingly, there is a pipewire package in MELPA.

2026-01-14_16-39-37.png
Figure 2: Screenshot of M-x pipewire from the pipewire package

I want to see and manage the connections between devices, though, so I started working on sachac/epwgraph: Emacs Pipewire graph visualization. This is what epwgraph-show looks like with everything in it:

2026-01-14_16-50-39.png
Figure 3: epwgraph-show

Let's call it with C-u, which prompts for a regexp of nodes to focus on and another regexp for nodes to exclude. Then I can ignore the volume control:

2026-01-14_16-51-16.png
Figure 4: Ignoring the volume control

I can focus on just the things that are connected to my microphone:

2026-01-14_16-51-56.png
Figure 5: Focusing on a regular expression

This also lets me disconnect things with d (epwgraph-disconnect-logical-nodes):

2026-01-14_16-52-35.png
Figure 6: Disconnecting a link

and connect them with c (epwgraph-connect-logical-nodes).

2026-01-14_16-52-57.png
Figure 7: Connecting links

I don't have a fancy 5.1 sound systems, so the logic for connecting nodes just maps L and R if possible.

Most of the time I just care about the logical devices instead of the specific left and right channels, but I can toggle the display with t so that I can see specific ports:

2026-01-14_17-17-34.png
Figure 8: Showing specific ports

and I can use C and D to work with specific ports as well.

2026-01-14_18-10-55.png
Figure 9: Connecting specific ports

I usually just want to quickly rewire a node so that it gets its input from a specified device, which I can do with i (epwgraph-rewire-inputs-for-logical-node).

output-2026-01-14-17:30:18.gif
Figure 10: Animated GIF showing how to change the input for a node.

I think this will help me stay sane when I try to scale up my audio configuration to having four or five web conferences going on at the same time, possibly with streaming speech recognition.

Ideas for next steps:

  • I want to be able to set the left/right balance of audio, probably using pactl set-sink-volume <index> left% right%
  • I'd love to be able to click on the graph in order to work with it, like dragging from one box to another in order to create a connection, right-drag to disconnect, or shift-drag to rewire the inputs.

In case this is useful for anyone else:

sachac/epwgraph: Emacs Pipewire graph visualization

View org source for this post

Emacs Lisp: Editing one file twice at the same time

| emacs

@HaraldKi@nrw.social said:

Emacs can do everything. Except the most simple thing ever as I learned after 40 years in which I never needed it: Edit one file twice at the same time.

I can open a new Emacs "window" and re-open the file. But Emacs notices and this and shows the file's buffer in the new window, not a new buffer.

But why? Well, when editing and SVG file, you can switch between the XML and the rendered image with C-c C-c, but I would like to see the XML and the rendered next to each other.😀

You might think this is easy, just use M-x clone-indirect-buffer-other-window. But image-mode adds a wrinkle. It uses text properties to display the image, so even if you have two views of the same buffer thanks to clone-indirect-buffer, C-c C-c will toggle both of them. If we want to edit a file as both text and an SVG at the same time, we need to actually have two separate file buffers.

I started off by looking at how find-file works. From there, I went to find-file-noselect. Normally, find-file-no-select reuses any existing buffers visiting the same file. If it doesn't find any, it calls find-file-noselect-1. That lets me write this short function to jump straight to that step.

(defun my-find-file-always (filename &optional buffer-name)
  (interactive (list (read-file-name "File: ")))
  (setq buffer-name (or (create-file-buffer filename)))
  (let* ((truename (abbreviate-file-name (file-truename filename)))
         (attributes (file-attributes truename))
         (number (file-attribute-file-identifier attributes)))
    (with-current-buffer
        (find-file-noselect-1
         (get-buffer-create buffer-name)
         truename
         t nil truename number)
      (when (called-interactively-p 'any)
        (switch-to-buffer (current-buffer)))
      (current-buffer))))

(defun my-clone-file-other-window ()
  (interactive)
  (display-buffer-other-window (my-find-file-always (buffer-file-name))))

This code unconditionally opens a buffer visiting a file, so you could have multiple buffers, looking at the same file independently. With global-auto-revert-mode, editing the file in one buffer and saving it will result in changes in the other.

I sometimes play around with SVGs, and it might be helpful to be able to experiment with the source code of the SVG while seeing the changes refreshed automatically.

I really like how in Emacs, you can follow the trail of the functions to find out how they actually work.

Screencast demonstrating my-find-file-always

Transcript

00:00:00 The problem: clone-indirect-buffer-other-window and image-mode
@HaraldKi@nrw.social said, "Emacs can do everything except the most simple thing ever, as I learned after 40 years in which I never needed it: edit one file twice at the same time." You might think this is easy, just use M-x clone-indirect-buffer-other-window, but image mode adds a wrinkle. So let's show you how that works. I've got my test SVG here. We can say clone-indirect-buffer-other-window. But if I use C-c C-c, you'll notice that both of the windows change. That's because image mode uses text properties instead of some other kind of display. I mean, it's the same buffer that's being reused for the clone. So that doesn't work.
00:00:48 A quick tour of find-file
What I did was I looked at how find-file works. And then from there, I went to find-file-noselect. So this is find-file over here. If you look at the source code, you'll see how it uses find-file... It's a very short function, actually. It uses find-file-noselect. And find-file-noselect reuses a buffer if it can. Let's show you where we're looking for this. Ah, yes. So here's another buffer here. And what we want to do is we want to open a new file buffer no matter what. The way that find-file-noselect actually works is it calls this find-file-noselect1. And by taking a look at how it figured out the raw file and the true name and the number to send to it, I was able to write this short function, my-find-file-always, and a my-clone-file-other-window.
00:01:46 Demonstration of my-find-file-always
So if I say my-find-file-always, then it will always open that file, even if it's already open elsewhere.
00:01:57 Cloning it into the other window
Let's show you how it works when I clone it in the other window. All right, so if I switch this one to text mode, I can make changes to it. More stuff goes here. And as you can see, that added this over here. I have global-auto-revert mode on, so it just refreshes automatically. So yeah, that's this function.

View org source for this post

La semaine du 5 janvier au 11 janvier

| french

Lundi, le cinq janvier

Ma fille est devenue grincheuse probablement à cause d'une mauvaise communication ce matin. Elle n'a pas continué à l'école aujourd'hui. Elle n'est pas allée au cours de gym parce qu'elle était de mauvaise humeur. Elle est restée dans sa chambre toute la journée. Alors, s'inquiéter, ça ne sert à rien. Au lieu de stresser, j'ai déneigé. La neige était légère, et le fait de déneiger m'a changé les idées. Ensuite, je me suis préparée pour ma session avec ma tutrice. En addition des entrées de cette semaine, nous avons aussi révisé une entrée de la semaine dernière.

Elle n'a pas besoin d'un câlin, mais elle est revenue pour le déjeuner. Elle m'a permis de lui brosser les dents et de lui nettoyer ses piercings.

J'ai écrit mon bulletin Emacs pendant une diffusion en direct. Quelques spectateurs ont fait des commentaires. ( Merci ! ) J'ai besoin d'ajuster la configuration de l'audio. Un jour, ce sera plus facile.

Maintenant, c'est le moment de l'inscription pour l'école. Je vais me renseigner sur le processus pour l'année prochaine, au cas où nous déciderons d'expérimenter l'instruction en famille pour le reste de l'année. Je vais aussi me renseigner sur le processus au cas où nous l'inscririons en cours d'année. Nous allons recevoir le carnet de notes en février. Alors que ma fille semble passer son temps à ne rien faire, cela reste instructif. Je pense que ma fille peut trouver son propre chemin. Malgré notre grande liberté et les nombreuses tentations de flâner, mon mari et moi continuons d'apprendre chaque jour. Ma fille aime proposer sans cesse des idées d'amélioration. Des familles que nous connaissons et qui vivent selon les principes de l'apprentissage libre semblent développer des passions intéressantes.

Pour le souper, j'ai préparé du bœuf salé en boîte. Ma fille et moi l'avons mangé avec du riz et du pak-choï.

Mardi, le six janvier

Nous nous sommes levés très tôt et nous sommes allés à l'hôpital pour l'examen médical de ma fille. Elle a dû jeûner et boire seulement de l'eau et du jus de pomme. Nous sommes arrivés exactement à l'heure (trente minutes avant le rendez-vous). Après environ trente minutes d'attente au-delà de l'heure prévue du rendez-vous, elle a eu l'échographie. C'est correct, donc je l'ai emmenée dehors pour manger sur le pouce avant son deuxième rendez-vous. Le médecin a dit que tout allait bien et nous n'avons pas besoin de plus de rendez-vous avec eux l'année prochaine. Après l'examen, elle voulait aller à l'espace de jeux de l'hôpital. Elle a colorié un dessin, elle a joué dans un petit café, et elle a joué aux trains.

Sur le chemin du retour, nous sommes passés chez Nella Cucina pour voir un couteau adapté aux enfants. Ma fille voulait un couteau comme un santoku parce que les aliments ne collent pas dessus. Tous les couteaux sont trop grands ou trop petits ou ne sont pas comme un santoku. Peut-être qu'elle devrait le chercher en ligne.

Elle avait si faim. Elle avait hâte de manger des nouilles instantanées avec plus de gâteau au poisson. Elle a utilisé la moitié du sachet d'épices et m'a donné l'autre moitié. J'ai préparé des nouilles conventionnelles. J'ai ajouté des algues et du gâteau au poisson, qu'elle m'a presque tout piqué. Elle avait très faim. Ce n'était pas grave, j'ai mangé du pain avec du beurre.

Après tout ça, j'ai appelé son école pour connaître la procédure à suivre au cas où nous la retirerions de l'école et voudrions la réinscrire ensuite. La secrétaire n'était pas sûre, donc je me suis retrouvée à parler au directeur, ce qui était un peu embarrassant. Toutefois, j'ai appris que :

  • Presque tous les étudiants de l'école virtuelle vont rester, à l'exception de quelques personnes qui vont rentrer à l'école en présentiel.
  • Ils reçoivent une ou deux demandes chaque jour à l'extérieur de la période normale d'inscription, et doivent rejeter presque toutes les demandes à l'exception de cas graves, comme des raisons médicales.
  • Si notre fille continue, sa place est assurée pour l'année prochaine.
  • Si je l'inscris pour l'année prochaine avant le 23 janvier :
    • S'ils nous offrent une place en février, si notre fille se retire, elle pourrait revenir à l'école virtuelle.
    • S'ils ne nous offrent pas une place et que notre fille se retire et ensuite change d'avis, elle ne bénéficiera peut-être pas d'une exception et nous devrions attendre l'année suivante.
  • Si je ne l'inscris pas pour l'année prochaine, et si notre fille se retire et ensuite change d'avis, peut-être que nous devrions attendre l'année suivante.

Pouvons-nous accepter son mécontentement face à la situation actuelle ? Pouvons-nous accepter l'incertitude pour plus d'une année ? Je pense que nous pouvons attendre jusqu'à ce que la situation s'éclaircisse.

Mercredi, le sept janvier

J'étais un peu fatiguée quand je me suis levée. Au lieu d'exercice, j'ai déneigé autour des drains et j'ai essayé d'écoper la flaque sur le trottoir. C'était trop difficile parce que la neige est devenue glace. Tant pis.

Une réflexion sur l'apprentissage de ma fille :

Ma fille aime bien lire. Elle lit pour le plaisir et discute des livres avec nous un peu comme ses devoirs que l'enseignant veut qu'elle fasse mais qu'elle ne les fait pas. Nous apprécions les mots intéressants qu'elle accumule en lisant et peut utiliser au moment opportun. "I'm ravenously hungry," dit-elle. Grâce à mon apprentissage en français, je peux partager ma propre exultation quand je trouve un nouveau mot magnifique.

Ma fille aime bien les mathématiques. Elle adore exercer son cerveau en calculant et en résolvant des problèmes que je lui pose. Elle trouve que le cours est ennuyeux parce qu'il est trop lent. Elle trouve que la programmation simple dans Minecraft Education est trop simple. Elle me demande de plus de challenges et essaie d'autres exercices elle-même, grâce aux livres Beast Academy.

Ma fille aime bien lancer des idées variées d'amélioration ou d'entreprise. Par exemple, quand elle joue à la marchande, elle prépare ses propres collations en vrai et me les vendent contre cinq tapes dans la main.

Je pense que l'école peut nous aider au développement de ses compétences. Les enseignants peuvent fournir l'évaluation extérieure que je ne peux fournir parce que je n'ai pas l'expérience. C'est aussi une occasion pour pratiquer la gestion de ses propres tâches. Après la résistance initiale, ma fille est fière quand elle accomplit ses tâches.

Mon mari pense que l'évaluation extérieure est importante. Ce n'est pas grave si notre fille reçoit des notes basses maintenant. Peut-être que il est encore utile pour la renseigner. Je suis d'accord, donc je peux attendre, particulièrement puisque je n'ai pas trouvé d'alternative qui nous inspire confiance. C'est possible que ses œuvres suffisent. Si elle est grincheuse contre le système, l'enseignant ou moi, je préfère qu'elle soit grincheuse contre le système et qu'elle choisisse de réussir malgré ces limitations. On va voir.

D'ailleurs, la plupart du temps, c'est acceptable, et elle est fière qu'elle puisse faire les choses elle-même. Ça va.

J'ai finalement créé un logiciel pour créer ou télécharger le brouillon du prochain bulletin de la Bike Brigade sur Google Drive. J'ai aussi fait une fonction dans Emacs pour créer ou modifier la campagne sur MailChimp grâce au modèle. Ça signifie que je peux mettre à jour le bulletin de brouillon avec moins de clics. Ça simplifie mon flux de travail. Un jour je veux automatiser tout le processus à part l'écriture, que les autres bénévoles pourront faire.

Ma fille essayait Pokémon sur le GameBoy Advance de mon mari pendant que je l'interrogeais sur ses devoirs et je tapais ses réponses. Parfois je faisais la secrétaire pour elle, ce qui l'aide beaucoup.

Jeudi, le huit janvier

Au lieu de suivre une vidéo d'exercice, j'ai pris une pelle et creusé une rigole dans la glace pour drainer la flaque sur le trottoir plus bas dans la rue. La météo a annoncé que la journée de demain serait plus chaude, donc peut-être que le canal aidera à éviter que le trottoir ne soit inondé (ou du moins, moins d'inondations). C'était un bon exercice. Ensuite mes mains étaient très fatiguées, donc je me suis concentrée sur les tâches qui ne nécessitent pas de trop taper. J'ai revu mes cartes Anki, j'ai amélioré ma configuration d'audio, et j'ai enregistré une courte vidéo qui démontre ma fonction pour dicter une note à la tâche actuelle.

Quand mes mains allaient mieux, j'ai fait plus de programmation. Je veux automatiser l'exécution des logiciels chaque jour et chaque semaine, par exemple le renouvellement de nos livres empruntés à la bibliothèque ou mon nouveau logiciel pour créer un brouillon du bulletin d'information de la Bike Brigade. Cependant, mon ordinateur n'est pas allumé sans cesse. Je me suis renseignée sur comment configurer des tâches persistantes dans systemd. De cette façon, ces tâches vont s'exécuter un peu de temps après le démarrage de mon ordinateur, pourvu que je les aie configurées correctement. On va voir demain si les tâches se lanceront.

Ma fille et moi avons fait une promenade pendant la pause déjeuner, mais nous avons oublié que la bibliothèque ouvre à 12h30 (midi et demi) jeudi. Elle est devenue un peu grincheuse parce qu'elle a hâte d'avoir plus de livres, mais le soleil brillait et je maintenais une conversation légère, donc ce n'était pas grave. Elle est rentrée à l'école virtuelle juste à l'heure.

Après l'école, j'ai emmené ma fille au parc pour jouer avec son amie. Bon, en vrai, je pense qu'elle jouait plus avec la chienne de son amie qu'avec son amie. Je suppose que c'est juste une de ces journées.

Un fil sur Reddit m'a inspiré à penser aux buffers d'Emacs. J'ai revu ma configuration pour beaucoup d'exemples d'usage en dictant des notes avec ma nouvelle fonction pour les rassembler avec les liens, ce qui était très utile. Je pense que je vais faire une diffusion en direct avant d'écrire un billet sur le sujet. Quand… Peut-être que vendredi après-midi est un bon moment pour ça.

Ma fille était curieuse au sujet de Pokémon parce que mon mari y jouait sur sa GameBoy Advance. Elle m'a demandé si je pouvais trouver Pokémon Blanc. Je l'ai installé dans un émulateur sur mon ordinateur. Parce que mon ordinateur était occupé, j'ai ressuscité mon ancienne machine et je l'ai utilisée pour me connecter à mon ordinateur actuel via SSH pour continuer à écrire mon journal, grâce à Linux qui est orienté serveur.

Vendredi, le neuf janvier

Succès ! Selon ma fille, elle peut préparer son propre déjeuner mieux que moi. Ce matin, elle l'a refait parce que j'ai déchiré le prosciutto en morceaux trop petits. Maintenant elle peut emporter son propre déjeuner et son propre collation. Elle survivra.

J'ai essayé de prévoir une diffusion en direct ce matin sur la configuration d'Emacs. En tout cas, je veux le faire, alors autant essayer de partager en direct. J'ai amélioré mon processus pour dicter des notes. Maintenant je peux faire une capture d'écran automatiquement, et tous les audios sont enregistrés avec un horodatage.

L'après-midi, le temps était beau, donc j'ai fait du vélo aux Stockyards pour chercher les pyjamas que j'avais achetés pour ma fille. J'ai oublié de lui brosser les dents pendant la pause déjeuner. Je l'ai fait pendant la récré de l'après-midi.

J'ai aussi essayé la reconnaissance vocale sur Google Chrome. J'ai modifié un logiciel pour la reconnaissance vocale en continu pour montrer les énoncés précédents et envoyer des messages au serveur, et j'ai créé un serveur qui retransmet les messages à Emacs. Dans Emacs, j'ai affiché les messages dans un buffer. La reconnaissance vocale sur Google Chrome est plus rapide que sur WhisperX et elle peut supporter un flux, mais elle n'utilise pas de ponctuation et la qualité est moindre. Je pense qu'elle sera utile pour la reconnaissance vocale des flux multiples simultanés pendant la prochaine conférence, ou en solution de secours pendant que je fais une diffusion en direct.

Le vent était fort, donc nous sommes restés à la maison. Après l'école, ma fille a exploré un kit de gemmes cachées en forme de volcan. Ma fille aime bien ces kits. Une fois les gemmes découvertes, je lui ai donné des fils de fer et lui ai montré comment les sertir avec du fil de fer.

Nous avons mangé des petits pains chinois pour le souper.

Ma fille voulait faire une robe à volants qui forme des manches courtes. (Peut-être comme cette robe ?) Nous avons regardé des tissus en ligne jusqu'à ce que je sois surstimulée entre les nombreux choix et son jacassement. J'ai besoin de calme.

Elle est allée dans sa chambre pour probablement jouer à Minecraft. Mon mari a travaillé sur l'installation de RetroPie sur la Raspberry Pi pour que ma fille puisse jouer à Pokémon plutôt que sur mon ordinateur ou sur un ancien ordinateur qui a besoin d'un câble vidéo différent.

Samedi, le dix janvier

Ma fille m'a demandé des petites crêpes pour le petit-déjeuner, donc je lui ai fait de minuscules crêpes et quelques crêpes en forme de cœur. Après la routine matinale, ma fille a joué au courrier des choses. Elle a distribué les couverts dans le tiroir de la cuisine, la lessive dans les tiroirs de sa chambre, les ordures à la poubelle, et d'autres choses partout dans la maison.

J'ai préparé le bulletin de la Bike Brigade. Cette fois, j'ai créé une fonction pour envoyer un message de test via Mailchimp sous Emacs. Quand j'ai reçu la confirmation de l'autre bénévole, j'ai créé une fonction pour planifier la campagne sur Mailchimp sous Emacs. Maintenant, je peux surtout gérer la campagne sous Emacs sans clics, ce qui me rend heureuse.

L'après-midi, j'ai emmené ma fille à la patinoire pour jouer avec son amie. Le père de son amie a proposé une petite course, mais son amie paraissait triste et je sais que ma fille n'aime pas les jeux de compétition, donc nous avons trouvé d'autres activités plus coopératives entre elles. Le père était déjà trop rapide pour être attrapé, mais les amies se sont amusées à m'attraper quelques fois. Je pense qu'elles se sont plus amusées à courir après son père.

J'ai créé une interface pour la visualisation et la gestion du routage audio de Pipewire sous Emacs. Je veux enregistrer, diffuser en direct et faire de la reconnaissance vocale de ma propre voix ou du flux audio, possiblement en même temps, ce qui nécessite un loopback avec un peu de latence pour éviter les coupures audio. J'étais si préoccupée par ça que j'ai oublié de faire la vaisselle. Mon mari a cuisiné du gyudon et des pak-choïs, a fait la vaisselle et en a mangé seul, ce qui n'est pas comme d'habitude. Bref, la prochaine fois je ferai la vaisselle.

Après mon souper, je suis allée voir comment elle allait. Elle était grincheuse. Probablement qu'elle avait faim et froid, et qu'elle se sentait seule en m'attendant. J'ai réussi à désamorcer la situation en lui offrant le souper et des jeux de billes, ce qu'elle a accepté. Elle a bien aimé le gyudon. Comme promis, nous avons joué aux billes. Après ça, elle était de bonne humeur. Elle a joué à Pokémon. Pendant que je l'aidais, elle a travaillé sur ses devoirs.

À l'heure du coucher, elle et moi lisions un livre en alternance.

Elle a dit que ses dents lui faisaient mal, mais elle n'a pas voulu d'analgésique ou de compresse froide. Je lui ai offert une paille en silicone pour mordiller au cas où ça l'aiderait.

Dimanche, le onze janvier

Ma fille s'est levée plus tôt que moi ce matin, et elle était un peu impatiente en attendant. Je me suis finalement levée, mais j'étais encore un peu fatiguée.

Ma fille a joué à Pokémon Yellow. Elle a dit que ce n'est pas juste que le jeu ne permet pas de jouer en tant qu'une fille au lieu d'un garçon. Mon mari a trouvé un autre ROM qui permet ça, donc elle a recommencé le jeu vidéo. Je l'ai aidée pendant qu'elle faisait de l'exercice.

J'ai emmené ma fille à son premier cours de patinage à la patinoire au parc. En voyant les autres étudiants lutter pour se lever sur la glace, ma fille m'a dit que je l'avais inscrite au mauvais cours. Elle a trouvé que Learn to Skate 1 était trop facile. Heureusement, la professeure a pu l'amener de l'autre côté de la patinoire où elle participait à Learn to Skate 2, qui était en même temps. Le cours lui a plu. Sur l'autre patinoire, j'ai imité les exercices que les professeurs enseignaient aux enfants. Après le cours, nous avons passé du temps en patinant jusqu'à ce qu'elle ait trop froid. Quand la surfaceuse venait à la patinoire, tout le monde quittait la glace à l'exception d'une personne qui a emmené son enfant à la patinoire. Ma fille et moi nous sommes amusées parce que le superviseur les a poursuivis tellement rapidement.

Après être rentrée à la maison, j'ai fini mon entrée d'hier.

Notes

  • Prononciation
    • … au cas (silent s) où nous déciderons d'expérimenter l'instruction en famille pour le reste de l'année.
    • Je pense que ma fille peut trouver son propre chemin. (sheuh mehn)
    • Ma fille aime proposer sans cesse des idées d'amélioration. (dah mee lee oh rah seon)
    • … nous sommes passés chez Nella Cucina pour voir un couteau adapté aux enfants. (ehn fehn)
    • Elle avait hâte de manger des nouilles (nwee) instantanées (ein stahn tah nay) avec plus de gâteau au poisson.
    • Elle a utilisé la moitié du sachet d'épices et m'a donné l'autre moitié. (mwah teay)
    • … pendant que je l'interrogeais (lein tey roh jeas) sur ses devoirs et je tapais ses réponses.
    • … ou du moins, moins d'inondations (dein non dah seons)
    • … je me suis concentrée sur les tâches qui ne nécessitent (neh sess seet)
    • Succès ! (suk say)
    • … ce qui nécessite un loopback avec un peu de latence pour éviter les coupures (coo puurs) audio.
  • Wording
    en addition de
    in addition to
    bref
    anyway, in short
    alors que
    while
    (no term)
    en … - gérondif: use present participle (ex: patinant)
    (no term)
    plus-que-parfait for things that happened before the passé composé
View org source for this post

2026-01-12 Emacs news

| emacs, emacs-news

If you want to review packages before upgrading them, check out the new package.el feature for reviewing diffs (Reddit, Irreal).

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

2026-01-05 Emacs news

Posted: - Modified: | emacs, emacs-news

Looking for something to write about? Christian Tietze is hosting the January Emacs Carnival on the theme "This Year, I'll…". Check out last month's carnival on The People of Emacs for other entries.

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

La semaine du 29 décembre

| french

Lundi, le vingt-neuf décembre

Ma fille et moi avons pris nos pelles et son grand bâton et nous sommes allées au coin de la rue que j'avais un peu déneigée hier. Ma fille a pilé la glace avec son grand bâton, et nous avons pelleté.

J'ai modifié ma fonction pour surligner les nouveaux mots pour qu'elle affiche la prononciation à partir de la base de données Lexique après. J'ai créé d'autres fonctions qui utilisent la bibliothèque gtts-cli pour un outil de synthèse vocale. J'ai combiné ces fonctions pour créer des raccourcis clavier qui me permettent de sauter au prochain nouveau mot et de l'écouter, ou répéter le mot actuel. Je m'en suis servi avant ma session avec ma tutrice pour réviser rapidement parce que je n'ai pas eu le temps d'écouter toutes les phrases. Ça semble très utile jusqu'à présent. L'implémenter dans Emacs était plus facile que l'implémenter dans Google Chrome parce que je peux utiliser les bases de données. J'ai hâte de l'essayer sur mon journal cette semaine.

En raison de mes très longues entrées, je n'ai dit que l'entrée de jeudi dans mon rendez-vous avec ma tutrice. C'est correct. En corriger une partie est mieux que se précipiter pour tout finir. Je peux pratiquer l'expression orale grâce à la synthèse vocale et utiliser les rendez-vous pour identifier les mots que je prononce mal souvent.

J'ai écrit mon bulletin sur Emacs. De plus, j'ai extrait le passage sur les gens d'Emacs de mon journal pour en faire un billet séparé pour le Carnaval d'Emacs. J'ai mis à jour GoToSocial parce que l'ancienne version ne m'a pas permis de publier. La mise à jour a pris du temps à s'installer, donc je l'ai laissée de côté pour la nuit.

À l'heure du coucher, j'avais du mal à dormir parce que mon cerveau continuait à penser à quelques conversations. J'ai ressenti une petite douleur à cause de COVID et ses restrictions de voyage, mais je ne veux pas accepter de risquer le syndrome post-COVID pour des câlins et des moments partagés. Mon cerveau a aussi sauté à l'autre conversation sur les difficultés de communication. Toutefois, ce ne sont pas des problèmes que je peux résoudre toute seule. J'ai essayé l'exercice de respiration que ma thérapeute a suggéré, ce qui m'a aidée après un certain temps.

Mardi, le trente décembre

Ma fille était d'humeur grincheuse aujourd'hui, donc je me suis concentrée sur mes tâches. Premièrement, j'ai créé une fonction pour le publipostage destiné aux bénévoles par étiquette. Puis, j'ai utilisé cette fonction pour envoyer mes lettres de remerciements aux intervenants et aux bénévoles. Enfin, j'ai commencé à écrire ma réflexion sur l'organisation de la conférence.

Ma fille a travaillé sur ses devoirs. Mon journal l'a aidée pour ses devoirs d'écriture sur ses week-ends.

Le nouveau robot pâtissier de mon mari est arrivé. C'était fascinant à regarder. Mon mari l'a utilisé pour préparer de la pâte à pain. Il pense que le nouveau robot pâtissier est meilleur pour la pâte que le KitchenAid grâce à son mécanisme. J'ai hâte de goûter le pain demain.

Quand il se lance dans un nouveau loisir, mon mari fait beaucoup de recherches sur les techniques et les outils. De mon côté, je me renseigne souvent sur les techniques, mais je n'achète pas beaucoup d'équipement tout de suite parce que je sais que mes nouveaux centres d'intérêt ont tendance à ne pas durer très longtemps. Mes passe-temps sont souvent immatériels, comme la programmation, la musique, ou le français.

Les nouveaux vêtements de ma fille sont aussi arrivés. Elle a aimé presque tout à l'exception de quelques pinces à cheveux. Après tout, c'est son argent, son choix.

Mercredi, le trente-et-un décembre

Aujourd'hui, c'était merveilleux. Nous avons commencé par aller au parc avec nos amis. Nous sommes tombés sur eux en sortant de leur bâtiment, donc nous sommes allés tous ensemble. Avant d'aller à la patinoire, nous avons joué sur les flaques gelées. Les enfants ont glissadé. Elles ont aussi brisé la glace avec des gros bâtons. Le temps était très froid, mais grâce à l'amusement, c'était tolérable.

Nous avons patiné. Il n'y avait pas grand monde. J'étais contente de voir que ma fille et son amie ont pu patiner si vite déjà. Ensuite, nous avons bu du chocolat chaud qu'ils avaient préparé, et ma fille a partagé ses craquelins de riz. Ils ont bien aimé ça. Ils étaient étonnés de voir qu'on pouvait en acheter au No Frills.

Finalement, il a fallu rentrer. Pendant que nous rentrions, nous avons remarqué que la chienne boitait. Nous avons pensé que le sel l'avait blessée. J'ai offert notre landau pour transporter la chienne. C'était très utile.

J'ai continué à écrire mon billet sur l'organisation de la conférence. Je dois attendre les factures avant que je puisse faire le total des frais, mais je pense que c'est raisonnable.

J'ai choisi des raccourcis clavier pour rechercher les mots et les conjugaisons dans le dictionnaire sous Emacs.

Pour le souper, mon mari a préparé du saumon, j'ai fait sauter des blettes et du chou cavalier, et ma fille a préparé de la purée de pommes de terre. Elle a aussi créé un menu. Notre souper était très sophistiqué.

Jeudi, le jour de l'an

Bonne année !

Ma fille a fait la grasse matinée, donc j'ai le temps de revoir mes cartes d'Anki. Grâce au fil sur Reddit, j'ai découvert la série Extr@ French. J'ai regardé les trois premiers épisodes sur YouTube. À mon grand étonnement, je pouvais les comprendre. J'ai aussi regardé le premier épisode de Parlez-Moi, qui est plus lent et facile. Si je trouve le temps de les regarder (peut-être au réveil), je pense que je vais m'amuser pendant que j'apprends le français.

J'ai fini le cours de Michel Thomas pour les débutants sur YouTube. J'aime ce cours parce qu'il se concentre sur la grammaire et il construit les phrases de plus en plus complexes petit à petit. Par exemple, l'instructeur leur a demandé : Now, how would you say "It is not very logical, but it is practical that way." ? (Ce n'est pas très logique, mais c'est pratique comme ça.) La bibliothèque propose d'autres cours de Michel Thomas, donc je les leur ai demandés.

Pour le petit-déjeuner, j'ai préparé des petites crêpes épaisses. Nous devons le faire souvent sinon le levain va envahir notre réfrigérateur. Maintenant, je pense que nous sommes passés de trois grands bocaux à un grand bocal et un moyen bocal. La recette pour les petites crêpes épaisses est très utile parce qu'elle utilise seulement le levain, un peu de sel, un peu de bicarbonate de soude, et un peu de sucre. Nous n'avons pas besoin de blé, de lait, ou d'œuf.

Aujourd'hui, le temps était très froid. Il faisait moins treize degrés. Malgré le froid, je me suis promenée au parc. Le soleil brillait, le temps était calme, et j'étais bien emmitouflée, donc c'était agréable.

Ma fille a fait une grigne en forme d'épi sur le pain, et mon mari l'a fait cuire. Cette fois, la poussée au four était meilleure. Le résultat était beau (et délicieux, naturellement). J'ai coupé une moitié en tranches et je l'ai congelée parce que mon mari a commencé une autre pâte à pain pour la faire cuire demain.

J'ai continué à écrire mon billet sur l'organisation de la conférence. J'ai ajouté quelques statistiques et beaucoup d'idées d'amélioration. Il y a eu moins de participants que l'année dernière, mais ça valait le coup. Peut-être que les gens sont plus occupés. S'ils veulent regarder les vidéos ou qu'ils veulent lire les discussions, c'est disponible à tout moment.

Je pense que le billet n'a besoin que d'une petite révision avant la publication. C'est ma réflexion personnelle, donc il peut être moins soutenu. Puis je vais le réviser pour publier un compte-rendu sur le wiki d'EmacsConf.

Pour le souper, mon mari a préparé du poulet teriyaki. Il a déglacé la poêle avec du chou et du vin. J'ai préparé des edamames.

Les vacances vont bientôt finir. Quand ma fille retournera à l'école virtuelle, j'aurai plus de temps pour me concentrer, mais j'aurai plus de stress. Je me demande pourquoi… Peut-être que je m'inquiète pour l'interaction entre ma fille et l'école. Ma fille a toujours dit qu'elle s'ennuyait tellement. Je peux voir que c'est difficile pour elle. Nous pouvons envisager de l'instruction en famille, mais je crains qu'elle ne veuille pas écouter mes consignes ou celles des tuteurs. En fait, elle n'en fait qu'à sa tête, mais c'est normalement raisonnable. Si elle trouve un objectif précis, c'est plus facile de dire "d'accord". Pour l'instant, nous continuons. L'école lui a donné des occasions de faire des choses en autonomie et avec les camarades, ce qui est plus difficile si je l'éduque à la maison. Un jour, elle mûrira assez pour trouver sa propre façon, ou elle trouvera des façons de s'adapter aux autres. Peut-être que je m'inquiète pour rien.

Ma fille a dit qu'elle veut se coucher toute seule après avoir lu encore un peu. Elle a lu son livre, puis elle a éteint sa lampe et elle s'est couchée. En vrai ! Il est possible qu'elle mûrisse petit à petit…

Je souhaite que cette année apporte la santé et le bonheur à nous tous.

Vendredi, le deux janvier

J'ai commencé ma journée en révisant mes cartes Anki et en regardant quelques épisodes d'extr@. Je suis maintenant à la moitié du septième épisode. L'humour repose sur les stéréotypes, mais peut-être que je ne peux pas comprendre un humour plus sophistiqué pour l'instant, et les exagérations rendent la compréhension plus facile. Je me demande quels autres programmes amusants pour l'apprentissage du français je peux trouver sur YouTube. Je pense qu'il y en a beaucoup.

Comme d'habitude, j'ai cuisiné des petites crêpes épaisses pour le petit-déjeuner.

Ma fille a dit que ses cicatrices faisaient mal. J'ai envoyé un message à l'infirmière. Ma fille a lu longtemps dans sa chambre.

J'ai finalement publié mes notes à propos de la conférence sur mon blog. Puis, je les ai récapitulées pour le site et j'ai envoyé un message à la liste de diffusion.

Ma fille et moi avons finalement construit le modèle du biplan que mon père lui a donné. Malheureusement, ma fille a cassé la queue du biplan, mais heureusement, la dérive de l'avion était fixée de telle sorte qu'elle maintenait l'ensemble. C'est une leçon sur la persistance malgré les erreurs.

Ma fille voulait un jouet comme la machine à espresso. Nous avons recherché toutes les options, mais nous ne pouvons pas choisir un produit. Elle voulait un simple jouet en bois. Nous avons trouvé un plan pour en construire un, donc peut-être que nous pouvons le modifier. C'est l'occasion d'apprendre la menuiserie. Nous avons un peu d'expérience avec ça et quelques outils dans l'atelier.

À l'heure du coucher, ma fille et moi avons discuté de l'éducation. Elle ne veut pas aller à l'école parce qu'elle pense qu'elle s'ennuie avec son maître et ses devoirs. Je comprends. J'ai aussi eu des difficultés à l'école. J'ai expliqué quelques considérations. Pour l'instant, c'est une occasion gratuite de développer des compétences pour accomplir des tâches et apprendre avec les autres. Si le carnet de notes montre que c'est trop difficile, nous devrons nous adapter. Si elle veut que je lui enseigne, elle doit devenir plus réceptive à l'enseignement et aux remarques. Si elle veut apprendre avec les autres tuteurs ou apprendre elle-même, elle doit développer sa propre volonté. Si elle veut un rythme tranquille à l'école et obtenir des notes autour de B pour consacrer du temps à d'autres loisirs, c'est aussi possible, donc nous pouvons voir ce dont elle a besoin.

Samedi, le trois janvier

Mon mari a emmené notre fille à l'Eaton Centre pour faire du shopping, tandis que je suis restée à la maison. C'est une bonne occasion pour les tâches qui demandent de l'attention. J'ai corrigé quelques bogues dans mon logiciel de révision de sous-titrage et j'ai publié une nouvelle version. J'ai aussi configuré mon Emacs pour dicter mes notes sur la tâche sur laquelle je pointe en ce moment. Je veux enregistrer une vidéo pour l'expliquer, mais il faut que je résolve un problème avec l'audio d'abord.

Quand mon mari est rentré avec notre fille, il a dit qu'ils avaient fait du lèche-vitrines, et puis ils ont passé beaucoup de temps à la librairie en lisant les nouveaux livres absents de la bibliothèque. Notre fille souffrait un peu, pauvre chérie.

À l'heure du coucher, ma fille a encore dit qu'elle ne voulait pas aller à l'école. Au début, j'ai dit qu'il fallait pratiquer des choses ennuyeuses. Elle est devenue grincheuse et elle m'a demandé de sortir. Je l'ai laissée pendant que je réfléchissais. J'ai réalisé que j'ai mal géré la conversation. Mon rôle de parent n'est pas de lui faire la morale, mais de la soutenir. J'ai présenté mes excuses à ma fille et j'ai engagé une conversation légère, comme l'humour dans ses livres préférés. Heureusement, j'ai assez lu ses livres pour en parler. Après avoir passé un peu de temps, elle s'est déridée. Elle a dit que jouer aux jeux vidéo ensemble lui manquait. Elle s'intéressait également aux alternatives à l'école publique. Pour le moment, je pense que c'est mieux de poursuivre avec notre expérience à l'école publique, mais nous avons le temps pour les autres expériences.

Comment me préparer aux incertitudes si nous expérimentons une des alternatives ? Nous connaissons beaucoup de familles qui choisissent des alternatives variées, donc nous n'avons pas besoin de tout résoudre tout seuls. Je sais que c'est un long voyage d'exploration et je ne peux pas tout savoir immédiatement.

Dimanche, le quatre janvier

J'ai regardé les onzième et douzième épisodes de l'extr@. Le onzième épisode est sur les vacances et le douzième épisode est sur le fou de foot. J'ai presque fini la série. Je ne comprends pas tous les mots, donc la revoir est une bonne idée.

Pour le petit-déjeuner, j'ai cuisiné des saucisses et j'en ai mangé avec des œufs et du riz.

Le temps était agréable, donc nous avons fait du vélo au centre-ville pour manger des petits pains. Nous sommes aussi allés au musée, où nous avons créé des tampons et nous les avons utilisés sur du papier. Nous avons aussi examiné les maquettes de bateaux et les expositions de Yayoi Kusama (pour la deuxième fois) et de Ranbir Sidhu. Ces deux expositions utilisent beaucoup de miroirs pour créer l'illusion de l'infini. Je préfère l'effet de Ranbir Sidhu parce que le scintillement a l'air d'étoiles, alors que l'effet de Yayoi Kusama répète nos images.

Dans la maison, ma fille et moi avons joué aux billes. J'étais un peu stressée parce qu'elle n'aime pas l'école, mais j'ai déclaré que ce n'est pas à moi de gérer ça, mais je suis juste là pour faire des câlins.

Pour le souper, j'ai mangé de la chaudrée de palourdes en boîte que j'ai achetée il y a longtemps.

Nous avons fait les courses avant l'examen médical de ma fille mardi. Elle a besoin d'une échographie, donc elle doit jeûner mardi matin. Nous avons acheté du Jello, du jus de pomme, et de l'eau de noix de coco sans pulpe. Je dois appeler l'hôpital demain pour confirmer ce qui est acceptable.

Reflexion

  • I started looking up words in Emacs instead of switching to another browser. It's pretty useful. It would be great to also add example searches, maybe from a subtitle corpus?
  • I like my function for highlighting new words. It's interesting to see where I've sprinkled in new words as well as spans of text. I'm highlighting masculine words in blue, feminine words in pink, and neutral words in green.

    2026-01-05_15-10-09.png

    This is a little similar to @jiewawa@masto.ai colouring pinyin based on tone in Org Mode.

  • I've been using il faut and a besoin de a bit more so that it's not all dois this and dois that.
View org source for this post

Using whisper.el to convert speech to text and save it to the currently clocked task in Org Mode or elsewhere

Posted: - Modified: | emacs, audio, speech
  • : Change main function to my-whisper-run, use seq-reduce to go through the functions.
  • : Added code for automatically capturing screenshots, saving text, working with a list of functions.
  • : Added demo, fixed some bugs.
  • : Added note about difference from MELPA package, fixed :vc

I want to get my thoughts into the computer quickly, and talking might be a good way to do some of that. OpenAI Whisper is reasonably good at recognizing my speech now and whisper.el gives me a convenient way to call whisper.cpp from Emacs with a single keybinding. (Note: This is not the same whisper package as the one on MELPA.) Here is how I have it set up for reasonable performance on my Lenovo P52 with just the CPU, no GPU.

I've bound <f9> to the command whisper-run. I press <f9> to start recording, talk, and then press <f9> to stop recording. By default, it inserts the text into the buffer at the current point. I've set whisper-return-cursor-to-start to nil so that I can keep going.

(use-package whisper
  :vc (:url "https://github.com/natrys/whisper.el")
  :load-path "~/vendor/whisper.el"
  :config
  (setq whisper-quantize "q4_0")
  (setq whisper-install-directory "~/vendor")
  (setq whisper--install-path (concat
     (expand-file-name (file-name-as-directory whisper-install-directory))
     "whisper.cpp/"))
  ;; Get it running with whisper-server-mode set to nil first before you switch to 'local.
  ;; If you change models,
  ;; (whisper-install-whispercpp (whisper--check-install-and-run nil "whisper-start"))
  (setq whisper-server-mode 'local)
  (setq whisper-model "base")
  (setq whisper-return-cursor-to-start nil)
  ;(setq whisper--ffmpeg-input-device "alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo")
  (setq whisper--ffmpeg-input-device "VirtualMicSink.monitor")
  (setq whisper-language "en")
  (setq whisper-recording-timeout 3000)
  (setq whisper-before-transcription-hook nil)
  (setq whisper-use-threads (1- (num-processors)))
  (setq whisper-transcription-buffer-name-function 'whisper--simple-transcription-buffer-name)
  (add-hook 'whisper-after-transcription-hook 'my-subed-fix-common-errors-from-start -100)
  :bind
  (("<f9>" . whisper-run)
   ("C-<f9>" . my-whisper-run)
   ("S-<f9>" . my-whisper-replay)
   ("M-<f9>" . my-whisper-toggle-language)))

Disk space is inexpensive and backups are great, so let's save each file using the timestamp.

(defvar my-whisper-dir "~/recordings/whisper/")
(defun my-whisper-set-temp-filename ()
  (setq whisper--temp-file (expand-file-name
                            (format-time-string "%Y-%m-%d-%H-%M-%S.wav")
                            my-whisper-dir)))

(with-eval-after-load 'whisper
  (add-hook 'whisper-before-transcription-hook #'my-whisper-set-temp-filename))

The technology isn't quite there yet to do real-time audio transcription so that I can see what it understands while I'm saying things, but that might be distracting anyway. If I do it in short segments, it might still be okay. I can replay the most recently recorded snippet in case it's missed something and I've forgotten what I just said.

(defun my-whisper-replay ()
  "Replay the last temporary recording."
  (interactive)
  (mpv-play whisper--temp-file))

Il peut aussi comprendre le français.

(defun my-whisper-toggle-language ()
  "Set the language explicitly, since sometimes auto doesn't figure out the right one."
  (interactive)
  (setq whisper-language (if (string= whisper-language "en") "fr" "en"))
  ;; If using a server, we need to restart for the language
  (when (process-live-p whisper--server-process) (kill-process whisper--server-process))
  (message "%s" whisper-language))

I could use this with org-capture, but that's a lot of keystrokes. My shortcut for org-capture is C-c r. I need to press at least one key to set the template, <f9> to start recording, <f9> to stop recording, and C-c C-c to save it. I want to be able to capture notes to my currently clocked in task without having an Org capture buffer interrupt my display.

To clock in, I can use C-c C-x i or my ! speed command. Bonus: the modeline displays the current task to keep me on track, and I can use org-clock-goto (which I've bound to C-c j) to jump to it.

Then, when I'm looking at something else and I want to record a note, I can press <f9> to start the recording, and then C-<f9> to save it to my currently clocked task along with a link to whatever I'm looking at. (Update: Ooh, now I can save a screenshot too.)

(defvar my-whisper-targets
  '(my-whisper-save-text
    my-whisper-org-save-to-clocked-task
    my-whisper-save-to-file)
  "*Where to save the target.

Nil means jump to the current clocked-in entry and insert it along with
a link, or prompt for a capture template if nothing is clocked in.

If this is set to a string, it should specify a key from
`org-capture-templates'. The text will be in %i, and you can use %a for the link.
For example, you could have a template entry like this:
\(\"c\" \"Contents to current clocked task\" plain (clock) \"%i%?\n%a\" :empty-lines 1)

If this is set to a function, the function will be called from the
original marker with the text as the argument. Note that the window
configuration and message will not be preserved after this function is
run, so if you want to change the window configuration or display a
message, add a timer.

If this is set to a list of functions, the functions are called in
sequence.  The first function is called with the text. The second
function is called with the result from the first function, and so on.")

(defun my-whisper-process ()
  "Process the transcription."
  (remove-hook 'whisper-after-transcription-hook #'my-whisper-process)
  (let ((text (string-trim (buffer-string))))
    (erase-buffer)      ; stops further processing
    (my-whisper-process-text text)
    (setq my-whisper-skip-annotation nil)))

(defun my-whisper-process-text (text)
  (save-window-excursion
    (with-current-buffer (if (markerp whisper--marker) (marker-buffer whisper--marker) (current-buffer))
      (when (markerp whisper--marker) (goto-char whisper--marker))
      (cond
       ((and my-whisper-targets (listp my-whisper-targets))
        (seq-reduce
         (lambda (prev cur)
           (funcall cur prev))
         my-whisper-targets
         text))
       ((functionp my-whisper-targets)
        (funcall my-whisper-targets text))
       (my-whisper-targets
        (setq org-capture-initial text)
        (org-capture nil my-whisper-targets)
        (org-capture-finalize)
        ;; Delay the display of the message because whisper--cleanup-transcription clears it
        (run-at-time 0.5 nil (lambda (text) (message "Captured: %s" text)) text))
       (t (my-whisper-org-save-to-clocked-task text))))))

(defvar my-whisper-last-annotation nil "Last annotation so we can skip duplicates.")
(defvar my-whisper-skip-annotation nil)

(defun my-whisper-run (&optional skip-annotation)
  (interactive (list current-prefix-arg))
  (require 'whisper)
  (add-hook 'whisper-after-transcription-hook #'my-whisper-process -50)
  (whisper-run)
  (when skip-annotation
    (setq my-whisper-skip-annotation t)))

(defun my-whisper-save-text (text)
  "Save TEXT beside `whisper--temp-file'."
  (when text
    (let ((link (org-store-link nil)))
      (with-temp-file (concat (file-name-sans-extension whisper--temp-file) ".txt")
        (when link
          (insert link "\n"))
        (insert text)))
    text))

(defun my-whisper-org-save-to-clocked-task (text)
  (when text
    (save-window-excursion
      (with-current-buffer (if (markerp whisper--marker) (marker-buffer whisper--marker) (current-buffer))
        (when (markerp whisper--marker) (goto-char whisper--marker))
        ;; Take a screenshot maybe
        (let* ((link (and (not my-whisper-skip-annotation)
                          (org-store-link nil)))
               (region (and (region-active-p) (buffer-substring (region-beginning) (region-end))))
               (screenshot-filename
                (when (or
                       (null link)
                       (not (string= my-whisper-last-annotation link)))
                  (my-screenshot-svg (concat (file-name-sans-extension whisper--temp-file) ".svg")))))
          (if (org-clocking-p)
              (progn
                (let ()
                  (org-clock-goto)
                  (org-end-of-subtree)
                  (unless (bolp)
                    (insert "\n"))
                  (insert "\n")
                  (when screenshot-filename
                    (insert (org-link-make-string
                             (concat "file:" screenshot-filename))
                            "\n"))
                  (when (and link (not (string= my-whisper-last-annotation link)))
                    (insert link "\n"))
                  (when region
                    (insert "#+begin_example\n" region "\n#+end_example\n"))
                  (insert text "\n")
                  (setq my-whisper-last-annotation link))
                (run-at-time 0.5 nil (lambda (text) (message "Added clock note: %s" text)) text))
            ;; No clocked task, prompt for a place to capture it
            (kill-new text)
            (setq org-capture-initial text)
            (call-interactively 'org-capture)
            ;; Delay the window configuration
            (let ((config (current-window-configuration)))
              (run-at-time 0.5 nil
                           (lambda (text config)
                             (set-window-configuration config)
                             (message "Copied: %s" text))
                           text config))))))
    text))

(with-eval-after-load 'org
  (add-hook 'org-clock-in-hook #'my-whisper-org-clear-saved-annotation))

(defun my-whisper-org-clear-saved-annotation ()
  (setq my-whisper-org-last-annotation nil))

Here's an idea for a my-whisper-targets function that saves the recognized text with a timestamp.

(defvar my-whisper-notes "~/sync/stream/narration.org")
(defun my-whisper-save-to-file (text)
  (when text
    (let ((link (org-store-link nil)))
      (with-current-buffer (find-file-noselect my-whisper-notes)
        (goto-char (point-max))
        (insert "\n\n" (format-time-string "%H:%M ") text "\n" link "\n")
        (save-buffer)
        (run-at-time 0.5 nil (lambda (text) (message "Saved to file: %s" text)) text)))
    text))
; (setq my-whisper-targets #'my-whisper-save-to-file)
; (setq my-whisper-targets '(my-whisper-save-to-file my-whisper-org-save-to-clocked-task))

And now I can redo things if needed:

(defun my-whisper-redo ()
  (interactive)
  (setq whisper--marker (point-marker))
  (whisper--transcribe-audio))

I think I've just figured out my Pipewire setup so that I can record audio in OBS while also being able to do speech to text, without the audio stuttering. qpwgraph was super helpful for visualizing the Pipewire connections and fixing them.

systemctl --user restart pipewire
sleep 2
pactl load-module module-null-sink \
  sink_name="VirtualMicSink" sink_properties=device.description=VirtualMicSink
pactl load-module module-null-sink \
  sink_name="CombinedSink" sink_properties=device.description=CombinedSink
if pactl list short sources | grep -i pci-0000; then
  pactl load-module module-loopback \
    source="alsa_input.pci-0000_00_1f.3.analog-stereo" \
    sink="VirtualMicSink" \
    latency_msec=100 \
    adjust_time=1 \
    source_output_properties="node.name='SysToVMic'" \
    sink_input_properties="node.name='SysToVMic' media.role='filter'"
    sink_input_properties=media.role=filter
  pactl load-module module-loopback \    source="alsa_output.pci-0000_00_1f.3.analog-stereo.monitor" \
    sink="CombinedSink" \
    node_name="SystemOutToCombined" \
    source_output_properties="node.name='SysOutToCombined' node.description='SysOutToCombined'" \
    sink_input_properties="node.name='SysOutToCombined' node.description='SysOutToCombined' media.role='filter'" \
    latency_msec=100 adjust_time=1
fi
if pactl list short sources | grep -i yeti; then
  pactl load-module module-loopback \
    source="alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo" \
    sink="VirtualMicSink" \
    latency_msec=100 \
    adjust_time=1 \
    source_output_properties="node.description='YetiToVMic' node.name='YetiToVMic' media.name='YetiToVMic'" \
    sink_input_properties="node.description='YetiToVMic' node.name='YetiToVMic' media.role='filter'"
  pactl load-module module-loopback \    source="alsa_output.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo.monitor" \
    sink="CombinedSink" \
    source_output_properties="node.description='YetiOutToCombined' node.name='YetiOutToCombined' media.name='YetiOutToCombined' " \
    sink_input_properties="node.description='YetiOutToCombined' node.name='YetiOutToCombined' media.role='filter'" \
    latency_msec=100 adjust_time=1
fi
pactl load-module module-loopback \
  source="VirtualMicSink.monitor" \
  sink="CombinedSink" \
  source_output_properties="node.description='VMicToCombined' node.name='VMicToCombined' media.name='VMicToCombined'" \
  sink_input_properties="node.description='VMicToCombined' node.name='VMicToCombined' media.role='filter'" \
  latency_msec=100 adjust_time=1

pactl load-module module-null-sink \
  sink_name="ExtraSink1" sink_properties=device.description=ExtraSink1

pactl load-module module-loopback \
  source="ExtraSink1.monitor" \
  sink="CombinedSink" \
  source_output_properties="node.description='ExtraSink1ToCombined' node.name='ExtraSink1ToCombined' media.name='ExtraSink1ToCombined'" \
  sink_input_properties="node.description='ExtraSink1ToCombined' node.name='ExtraSink1ToCombined' media.role='filter'" \
  latency_msec=100 adjust_time=1

Here's a demo:

Screencast of using whisper.el to do speech-to-text into the current buffer, clocked-in task, or other function

Transcript

00:00:00 Inserting into the current buffer
Here's a quick demonstration of using whisper.el to log notes.
00:00:13 Inserting text and moving on
I can insert text into the current buffer one after the other.
00:00:31 Clocking in
If I clock into a task, I can add to the end of that clocked in task using my custom code by pressing C-<f9> or whatever my shortcut was. I can do that multiple times.
00:01:05 Logging a note from a different file
I can do that while looking at a different file.
00:01:15 I can look at an info page
I can do it looking at an info page, for example, and annotations will include a link back to whatever I was looking at.
00:01:33 Adding without an annotation (C-u)
I just added an optional argument so that I can also capture a note without saving an annotation. That way, if I'm going to say a lot of things about the same buffer, I don't have to have a lot of links that I need to edit out.
00:02:42 Saving to a different function
I can also have it save to a different function.

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