Journal TapTempo en CMake

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
43
24
oct.
2021

Cher journal,

voilà un certains temps qu’il n’y a pas eu de nouveau portage de TapTempo alors je propose aujourd’hui une version en CMake.

CMake n’est pas vraiment un langage de programmation, c’est plutôt un système de construction logicielle multiplateforme, dixit Wikipédia. Cependant, il intègre un langage de script qui permet de piloter la configuration logicielle. J’ai donc essayé de reproduire le comportement de TapTempo avec ce DSL.

Les prérequis pour un portage de TapTempo sont :

  1. afficher du texte à l’écran
  2. réagir à des entrées utilisateur
  3. avoir accès à une horloge monotone précise à la milliseconde
  4. pouvoir sauvegarder des valeurs dans des variables
  5. pouvoir effectuer des calculs mathématiques simples sur des variables

Hélas, CMake ne remplit pas tous ces critères, mais on peut les contourner. Commençons par ceux qui sont présents nativement.

En CMake, c’est la commande message qui permet d’afficher du texte à l’écran. Pour effectuer des calculs mathématiques simples on utilise math et on stocke des valeurs arbitraires dans des variables avec les commandes set et list.

Ça se corse avec l’horloge. CMake possède bien la commande string(TIMESTAMP, ...) pour récupérer le temps courant mais son implémentation n’est précise qu’à la seconde. J’ai retrouvé un ticket demandant une précision à la milliseconde mais qui n’a pas bougé depuis 2 ans. Tant pis pour la précision, ce sera un TapTempoGrossoModo !

Pour pouvoir gérer les tempi au-delà de 60 bpm qui comportent plusieurs frappes par secondes on ne peut pas utiliser l’algorithme original qui se base sur les intervalles entre deux frappes. Pour contourner cette limitation, on va compter le nombre d’entrées utilisateur par seconde puis effectuer le calcul du tempo sur les N dernières secondes. Ça fonctionne, mais il faut que l’utilisateur garde le même rythme pendant ces N secondes pour être précis, donc impossible de détecter des changements rapides de tempo à moins d’avoir un N petit mais avec une précision médiocre. D’ailleurs la formule pour le nombre de secondes nécessaires pour une précision p donnée est N = 60 / p. Ainsi, avec 10 secondes on a une précision de 6 bpm et pour une précision de 1 bpm il faut enregistrer 60 secondes de frappes. Dans le code ci-dessous j’ai limité à 5 secondes pour une précision de 12 bpm, un peu grossier mais suffisant pour l’exercice.

Plus embêtant, CMake ne permet pas d’interagir pendant son exécution. C’est un comportement tout à fait logique pour un logiciel de configuration de construction, mais cela ne m’arrange pas pour ce portage. Encore une solution de contournement : utiliser la commande execute_process pour lancer un mini script bash avec la commande :

read var
echo "$var"

On récupère alors l’entrée utilisateur dans la variable CMake pointée dans la commande execute_process. Avec ce mécanisme on pourrait même invoquer directement le programme taptempo original, mais ce serait de la triche !

Voilà, tout cela mis ensemble ça donne le script ci-dessous. Pour le lancer, il faut le copier dans un fichier CMakeLists.txt et exécuter la commande cmake avec comme argument le répertoire qui contient ce fichier.

cmake_minimum_required(VERSION 3.15)

project(CMakeTapTempo)

find_program(BASH NAMES bash)

message("Hit enter key for each beat (q to quit).")

set(current_keystroke_count 0)
set(keystroke_count_history 0 0 0 0 0)
set(last_timestamp 0)
set(should_stop "")

while(NOT should_stop)
    execute_process(COMMAND "${BASH}" "-c"
[=[
read var
echo "$var"
]=]
        WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
        OUTPUT_VARIABLE user_input
        RESULT_VARIABLE ret_code
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_STRIP_TRAILING_WHITESPACE
        )

    if(user_input)
        set(should_stop "1")
    endif()

    if(NOT should_stop)

        string(TIMESTAMP current_timestamp "%s")

        if(current_timestamp EQUAL last_timestamp)
            math(EXPR current_keystroke_count "${current_keystroke_count} + 1")
        else()
            set(last_timestamp ${current_timestamp})
            list(PREPEND keystroke_count_history ${current_keystroke_count})
            list(POP_BACK keystroke_count_history)
            set(current_keystroke_count 1)
        endif()

        set(total_keystroke_count 0)
        foreach(keystroke_count ${keystroke_count_history})
            math(EXPR total_keystroke_count "${total_keystroke_count}+${keystroke_count}")
        endforeach()
        list(LENGTH keystroke_count_history history_length)
        math(EXPR tempo "(${total_keystroke_count} * 60) / ${history_length}")

        message("${tempo} bpm")
    endif()

endwhile()

message("Bye Bye!")

Sûrement que des experts CMake trouveront à redire et leurs commentaires sont les bienvenus !

  • # sans bash

    Posté par  . Évalué à 8.

    Utiliser execute_process c'est un peu triché même s'il n'y a pas le choix. Mais utiliser un interpréteur de script c'est pire. On pourrait remplacer l'appel de bash par un simple commande équivalente comme head -n1 (ça reste un programme externe mais au moins ce n'est pas un langage de programmation).

    • [^] # Re: sans bash

      Posté par  (site web personnel) . Évalué à 5.

      Bien vu, effectivement ça marche !

      Il faut donc remplacer l'appel à execute_process par :

      execute_process(COMMAND "head" "-n1"
              WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
              OUTPUT_VARIABLE user_input
              RESULT_VARIABLE ret_code
              OUTPUT_STRIP_TRAILING_WHITESPACE
              ERROR_STRIP_TRAILING_WHITESPACE
              )
      

      Merci pour la suggestion d'amélioration !

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.