Journal TapTempo Fortran

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
30
1
nov.
2021

Sommaire

TapTempo Fortran

Difficile d'échapper à l'engouement planétaire pour TapTempo… Voici donc enfin une version en Fortran moderne (ce qui pour moi désigne le langage à partir de la norme Fortran 90).

Pourquoi Fortran ?

  1. Le calcul éminemment scientifique du tempo me semble mériter de recourir au King du calcul, le mathematical FORmula TRANslating system de John Backus, prix Turing 1977.
  2. Fortran a commencé sa carrière en même temps qu'Elvis Presley, en 1954 (année de début du projet, le premier compilateur ayant été déployé chez les clients d'IBM en 1957).

Le code source

Le projet est disponible sous licence GNU GPLv3 ici :
https://github.com/vmagnin/TapTempo-Fortran

Il utilise pour sa construction le Fortran Package Manager fpm (https://github.com/fortran-lang/fpm) développé depuis deux ans par la communauté Fortran-lang, mais vous pouvez aussi le compiler simplement avec la commande :

$ gfortran -o taptempo src/taptempo.f90 app/main.f90

ou si vous êtes pressé, copier/coller le code source suivant dans un unique fichier taptempo.f90 et le compiler avec gfortran -o taptempo taptempo.f90 (gfortran fait partie de la GCC) :

module taptempo
    ! Reals working precision and 64 bits integers:
    use, intrinsic :: iso_fortran_env, only: wp=>real64, int64

    implicit none

    ! Tap Tempo Version:
    character(len=*), parameter :: version = "1.0.0"

    ! Default values of the command options:
    integer :: s = 5                ! Stack size
    integer :: p = 0                ! Precision
    integer :: r = 5                ! Reset time in seconds
    logical :: out_flag = .false.   ! Flag for the ouput file

    private

    public :: manage_command_line, measure_tempo

contains

    subroutine print_version
        print '(A)', "TapTempo Fortran "//version
        print '(A)', "Copyright (C) 2021 Vincent Magnin and the Fortran-lang community"
        print '(A)', "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>"
        print '(A)', "This is free software: you are free to change and redistribute it."
        print '(A)', "There is NO WARRANTY, to the extent permitted by law."
    end subroutine


    subroutine print_options
        print '(A)'
        print '(A)', "Usage: taptempo [options]"
        print '(A)'
        print '(A)', "Options :"
        print '(A)', "  -h, --help            display this help message"
        print '(A)', "  -o, --output          save the results in the taptempo.txt file"
        print '(A)', "  -p, --precision       change the number of decimals for the tempo,"
        print '(A)', "                        the default is 0 decimal places, the max is 5 decimals"
        print '(A)', "  -r, --reset-time      change the time in seconds to reset the calculation,"
        print '(A)', "                        the default is 5 seconds"
        print '(A)', "  -s, --sample-size     change the number of samples needed to calculate the tempo,"
        print '(A)', "                        the default is 5 samples, the minimum is 2"
        print '(A)', "  -v, --version         print the version number"
        print '(A)'
        print '(A)', "Home page: <https://github.com/vmagnin/TapTempo-Fortran>"
    end subroutine


    subroutine manage_command_line
        integer :: i, nb, status
        character(len=100) :: args

        nb = command_argument_count()
        if (nb == 0) return

        i = 0
        do while (i < nb)
            i = i + 1
            call get_command_argument(i, value=args)

            select case(trim(args))
                case("-h", "--help")
                    call print_version()
                    call print_options()
                    stop
                case("-o", "--output")
                    out_flag = .true.
                case("-p", "--precision")
                    i = i + 1
                    call get_command_argument(i, value=args)
                    read(args, *, iostat=status) p
                    if (status /= 0) print '(A)', "Problem with -p: the default value will be used"
                    p = max(0, min(p, 5))   ! 0 <= p <= 5
                case("-r", "--reset-time")
                    i = i + 1
                    call get_command_argument(i, value=args)
                    read(args, *, iostat=status) r
                    if (status /= 0) print '(A)', "Problem with -r: the default value will be used"
                case("-s", "--sample-size")
                    i = i + 1
                    call get_command_argument(i, value=args)
                    read(args, *, iostat=status) s
                    if (status /= 0) print '(A)', "Problem with -s: the default value will be used"
                    s = max(2, s)
                case("-v", "--version")
                    call print_version()
                    stop
                case default
                    print '(2A)', "Unknown option ignored: ", trim(args)
            end select
        end do
    end subroutine manage_command_line


    subroutine measure_tempo
        character(len=1) :: key
        integer(int64) :: count       ! Count of the processor clock
        real(wp) :: rate              ! Number of clock ticks per second
        integer :: i
        integer :: my_file            ! Unit of the output file
        real(wp), dimension(s) :: t   ! Time FIFO stack
        real(wp) :: t0                ! Time origin
        real(wp) :: bpm               ! Beats Per Minute
        integer :: oldest
        character(len=28) :: fmt

        ! Format used for printing the tempo:
        write(fmt, '(A, I1, A)') '("Tempo: ", f10.', p, ', " BPM ")'
        ! Stack initialization:
        t = 0

        if (out_flag) then
            open(newunit=my_file, file="taptempo.txt", status="replace")
            write(my_file, '(A)') "#    t         bpm"
        end if

        print '(A)', "Hit Enter key for each beat (q to quit)."

        ! Infinite loop:
        i = 0
        do
            ! Reading the standard input:
            read '(a1)', key

            if (key == 'q') exit

            call system_clock(count, rate)
            ! Updating the time FIFO stack:
            t(2:s) = t(1:s-1)
            t(1) = count / rate

            i = i + 1

            if (i == 1) then
                print '(A)', "[Hit enter key one more time to start BPM computation...]"
                t0 = t(1)
            else
                ! Verify that the user is actively tapping:
                if (t(1) - t(2) <= r) then
                    ! Oldest time in the stack:
                    oldest = min(i, s)
                    ! Computes and prints the beats per minute:
                    bpm = 60 / ((t(1) - t(oldest)) / (oldest - 1))
                    write(*, fmt, advance="no") bpm
                    if (out_flag) write(my_file, '(F9.3, F12.5)') t(1)-t0, bpm
                else
                    print '(A)', "Time reset"
                    i = 1
                    t(2:s) = 0
                end if
            end if
        end do

        print '(A)', "I don't know why you say goodbye..."
        if (out_flag) close(my_file)

    end subroutine measure_tempo

end module taptempo

program main
    use taptempo, only: manage_command_line, measure_tempo

    implicit none

    call manage_command_line()
    call measure_tempo()
end program main

Commentaires sur le code source

Le programme principal se contente d'appeler la procédure qui gère la ligne de commandes et la procédure qui mesure le tempo. Ces procédures sont placées dans le module taptempo : les modules ont été introduits par la norme Fortran 90 (la norme Fortran 2008 a même introduit les submodules pour les grands codes).

Fortran utilise pour les types de données son système de KIND (un numéro de type de données) : avant Fortran 2008, on aurait par exemple écrit real(8) pour espérer avoir un réel sur 8 octets, malheureusement ce n'était pas normalisé et le nombre entre parenthèses n'était pas forcément le nombre d'octets dans certains compilateurs… Le module intrinsèque iso_fortran_env définit donc désormais des constantes pour les KINDs de chaque grand type de données, par exemple real64 (renommé wp dans notre programme) et int64.

Depuis Fortran 2003, on peut gérer les arguments de la ligne de commandes avec command_argument_count() et get_command_argument(). Les chaînes de caractères Fortran étant de taille fixe (c'est plus rapide !), on utilise la commande trim() qui supprime les espaces excédentaires. La commande read(args, *, iostat=status) p utilise la chaîne args au lieu de l'entrée standard pour écrire la valeur du paramètre dans l'entier p. La variable status reçoit un code d'erreur non nul en cas de problème.

La ligne write(fmt, '(A, I1, A)') '("Tempo: ", f10.', p, ', " BPM ")' permet au contraire d'écrire des données dans la chaîne fmt en respectant le format '(A, I1, A)' (une chaîne, un entier sur un caractère, une chaîne). Pour l'écriture des résultats dans l'éventuel fichier de sortie, on a besoin de réserver une unité non utilisée (newunit=my_file). Traditionnellement, les unités d'entrées/sorties standards occupent les unités 5 et 6 et peuvent être représentées par une étoile, mais on peut désormais aussi utiliser les constantes input_unit et output_unit définies dans le module iso_fortran_env.

Depuis Fortran 90, le langage permet de manipuler facilement des tableaux : t = 0 permet d'initialiser à zéro tous les éléments du tableau. Faire avancer la pile FIFO représentée par ce tableau se fait simplement : t(2:s) = t(1:s-1). Enfin, la commande call system_clock(count, rate) disponible depuis Fortran 90 permet de récupérer le nombre de tics de l'horloge système et son nombre de tics par secondes. A noter que Fortran dispose aussi de la fonction cpu_time(), mais pendant que l'instruction read attend, le processus est idle et la valeur de cette fonction est donc figée. cpu_time() est avant tout conçue pour déterminer le temps de calcul, cheval de bataille du langage.

Ces particularités du langage étant présentées, le reste du code devrait être relativement lisible pour un développeur qui n'est pas habitué au Fortran.

Limitations

  • Il n'y a pas d'internationalisation, il parle la langue de John Backus et d'Elvis Presley. A noter que pour afficher les réels avec une virgule au lieu d'un point décimal, il suffirait d'ajouter dans le format dc, (decimal comma) avant le f10. Seul inconvénient, si p vaut zéro, ça affichera la virgule quand même, ce qui ne passe pas en Français.
  • Si on garde la touche Entrée enfoncée, ça plafonne à 1500 BPM… Probablement une limitation du terminal puisque la version C++ plafonne à la même valeur. Dommage, on aurait pu espérer s'approcher du GBPM.
  • Si le système reste allumé très longtemps, le nombre de tics de l'horloge finira par revenir à zéro. Mais avec des entiers 64 bits on pourra aller jusque 2**63-1 = +9223372036854775807. Avec GFortran sur un PC actuel, l'horloge génère un milliard de tics par seconde. On devrait tenir 9223372036 secondes ~ 106751 jours ~ 292 ans. De quoi nous prémunir du bug des missiles Patriot. Vous pouvez donc même utiliser TapTempo sur un serveur !

Perspectives

  • Vous pouvez utiliser TapTempo pour mesurer votre rythme cardiaque : mettez une main autour de votre gorge et tapez en rythme sur Entrée. Ne serrez pas ! Rappelons la clause 16 de la GNU GPL v3 : _16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM…)
  • On pourrait créer une interface graphique, par exemple avec gtk-fortran, pour remplacer la touche Entrée par un clic de souris sur un bouton. Pour rester minimaliste, le tempo pourrait être affiché comme texte du bouton.
  • # Erratum

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

    Attention, si vous copiez-collez le code source dans un fichier unique, il faut mettre le program main tout à la fin, après le module. Mais je ne peux pas corriger mon message initial (c'est curieux, d'après mes souvenirs il y avait un bouton Modifier avant, qui permettait en tout cas d'éditer son message dans les 5 minutes).

    • [^] # Re: Erratum

      Posté par  (Mastodon) . Évalué à 6.

      J'ai corrigé, ça va comme ça ?

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

  • # Qui dit mieux ?

    Posté par  . Évalué à 5. Dernière modification le 01 novembre 2021 à 14:50.

    Si on garde la touche Entrée enfoncée, ça plafonne à 1500 BPM… Probablement une limitation du terminal puisque la version C++ plafonne à la même valeur. Dommage, on aurait pu espérer s'approcher du GBPM.

    Quand je maintiens sur la touche entrée j'ai :

    Tempo: 1989. BPM
    Tempo: 1995. BPM
    Tempo: 1991. BPM
    Tempo: 1986. BPM
    Tempo: 1997. BPM
    Tempo: 1973. BPM
    Tempo: 1959. BPM
    Tempo: 1959. BPM
    Tempo: 1978. BPM
    Tempo: 1973. BPM
    Tempo: 1998. BPM
    Tempo: 1997. BPM
    Tempo: 1980. BPM
    Tempo: 1989. BPM
    Tempo: 1969. BPM
    Tempo: 1976. BPM
    Tempo: 1972. BPM
    Tempo: 1976. BPM
    Tempo: 1982. BPM
    Tempo: 1982. BPM
    Tempo: 1985. BPM
    Tempo: 1984. BPM
    Tempo: 1986. BPM
    Tempo: 1976. BPM
    Tempo: 1983. BPM
    Tempo: 1951. BPM

    Qui peut faire plus haut ?

    • [^] # Re: Qui dit mieux ?

      Posté par  . Évalué à 10. Dernière modification le 01 novembre 2021 à 15:02.

      J'imagine que ça dépend surtout de l'intervalle de répétition du clavier.

      Avec le clavier :

      Tempo: 2851. BPM
      Tempo: 2990. BPM
      Tempo: 2798. BPM
      Tempo: 2913. BPM
      Tempo: 2814. BPM
      Tempo: 2718. BPM
      Tempo: 2841. BPM
      Tempo: 2815. BPM

      Sans le clavier :

      yes | ./taptempo
      BPM Tempo: 8507623. BPM Tempo: 8539410. BPM Tempo: 8566537. BPM Tempo: 8583997. BPM Tempo: 8649581. BPM Tempo: 8641496. BPM Tempo: 8702597.

      • [^] # Re: Qui dit mieux ?

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

        Bien vu, j'arrive à 6000 BPM en réglant la répétition du clavier à 100 rép/s dans la configuration de KDE. La valeur par défaut y est de 25 rép/s (et on a bien 25*60=1500).

        La science autour de TapTempo avance !

  • # Compétition internationale

    Posté par  (site web personnel, Mastodon) . Évalué à 10.

    Bon, pour le moment la France a une longueur d’avance avec toutes ces recherches sur TapTempo publiées dans ce journal et les précédents, mais il commence à y avoir de la compétition internationale : https://lobste.rs/s/dyxogq/bpm_is_tap_along_with_beat_song_discover

    Si quelqu’un a un compte sur lobster.rs, ce serait marrant de montrer à ce type toutes les implémentations déjà postées dans des journaux ici.

    • [^] # Re: Compétition internationale

      Posté par  (site web personnel) . Évalué à 10. Dernière modification le 01 novembre 2021 à 19:29.

      Il est clairement URGENT de relancer la TapTempo Federation ! Elle semble à l'abandon, il y a 26 Issues ouvertes, beaucoup datant même de mars 2018 :
      https://github.com/TapTempo-Federation/TapTempo-Federation/issues

      Voilà qui explique les nombreuses difficultés rencontrées par la France au niveau international ces derniers temps. Il est encore temps d'inverser la vapeur.

      Rappelons d'ailleurs l'article 1 de son manifeste :

      The Taptempo-Federation Manifesto
      1. The Taptempo-Federation promotes the use and adoption of Taptempo throughout the world.

      • [^] # Re: Compétition internationale

        Posté par  . Évalué à 3.

        Effectivement il y a urgence, mettre en avant l'excellence linuxfrienne en matière de rythme, ca n'arrive pas tous les siècles.

        Je plaide donc pour la création d'une internationale du tap tempo avec charte éthique, hymne et drapeau.

Suivre le flux des commentaires

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