mercredi 18 avril 2012

Itérateur infini en PHP

Quand j'ai jeté un coup d'oeil dans la documentation de PHP au sujet du InfiniteIterator de la SPL, je trouvais que ça manquait cruellement d'exemples concrets. Boucler sur des chats et des chiens, hum ? Dans quel cas réel est-ce qu'on voudrait poursuivre au début d'une liste lorsqu'on atteint la fin ? Tiens, en musique la gamme chromatique est cyclique, c'est à dire que si on énumère les douze notes possibles et qu'on ajoute un demi-ton à la dernière, la note qui suit est celle qu'on a utilisé au départ, à la différence qu'on sera un octave plus élevé.

Voyons comment on pourrait traduire cet exemple en code, d'abord par une classe simple qui contiendra l'ensemble des notes. Pour ceux moins familiers avec la notation internationale : Do = C, Ré = D, Mi = E, Fa = F, Sol = G, La = A et Si = B. Remarquez, en aucun cas je demande à revenir explicitement au début de la liste, je ne fais que lire l'élément courant ainsi que me déplacer à l'élément suivant.
class ChromaticScale{
    private $_notes = array('C', 'C#/Db', 'D', 'D#/Eb', 'E', 'F', 'F#/Gb', 'G', 'G#/Ab', 'A', 'A#/Bb', 'B');
    private $_infiniteIterator;

    public function __construct($key){
        if(array_search($key, $this->_notes) === false){
            throw new Exception("Key $key does not exists");
        }
 
        $this->_infiniteIterator = new InfiniteIterator( new ArrayIterator($this->_notes) );

        while( $key != $this->_infiniteIterator->current() ){
            $this->_infiniteIterator->next();
        }
    }

    public function currentNote(){
        return $this->_infiniteIterator->current();
    }

    public function add($halfSteps){
        for($hs = 0; $hs  < $halfSteps ; $hs++){
            $this->_infiniteIterator->next();
        }
        return $this->_infiniteIterator->current();
    }
}
Maintenant qu'on peut faire une boucle infinie à l'intérieur de la liste de notes, ce serait intéressant de pouvoir générer les gammes majeures et mineures. Pour cela, on aura besoin de connaître les formules d'intervalles.
  • Pour la gamme majeure, on choisit une note de départ à laquelle on applique la formule : ton, ton, demi-ton, ton, ton, ton, demi-ton.
  • Pour la gamme mineure : ton, demi-ton, ton, ton, demi-ton, ton, ton.
Dans les livres de théorie musicale, vous verrez souvent la formule inscrite sous la forme WWHWWWH ou WHWWHWW où la lettre indique le ton (Whole step) ou le demi-ton (Half step). Voici un exemple simple qui calcule les notes de la gamme selon la clé ($key) et le mode ($mode) définis.
DEFINE('W', 2); // whole step = 2 half steps
DEFINE('H', 1); // half step

// initialisation
$key = 'C#/Db';
$mode = 'major';

$notes = new ChromaticScale($key);

$modes = array(
    'major' => array(W,W,H,W,W,W,H),
    'minor' => array(W,H,W,W,H,W,W)
);

$formula = $modes[$mode];

$nbNotes = count($formula);

$scale = array();
foreach($formula as $halfSteps){
    $scale[] = $notes->currentNote();
    $notes->add($halfSteps);
}

echo '<h1>' . $key . ' ' . $mode . ' scale</h1>';
echo implode(' - ', $scale);
Affichage:

C#/Db major scale
C#/Db - D#/Eb - F - F#/Gb - G#/Ab - A#/Bb - C

Aucun commentaire:

Publier un commentaire