hArduino-1.2: System/Hardware/Arduino/Parts/Piezo.hs
-------------------------------------------------------------------------------------------------
-- |
-- Module : System.Hardware.Arduino.Parts.Piezo
-- Copyright : (c) Levent Erkok
-- License : BSD3
-- Maintainer : erkokl@gmail.com
-- Stability : experimental
--
-- Abstractions for piezo speakers.
-------------------------------------------------------------------------------------------------
module System.Hardware.Arduino.Parts.Piezo(
-- * Declaring a piezo speaker
Piezo, speaker
-- * Notes you can play, and durations
, Note(..), Duration(..)
-- * Playing a note, rest, or silencing
, playNote, rest, silence
-- * Play a sequence of notes:
, playNotes
) where
import Data.Bits (shiftR, (.&.))
import Data.Maybe (fromMaybe)
import System.Hardware.Arduino
import System.Hardware.Arduino.Comm
import System.Hardware.Arduino.Data
-- | A piezo speaker. Note that this type is abstract, use 'speaker' to
-- create an instance.
data Piezo = Piezo { piezoPin :: IPin -- ^ The internal-pin that controls the speaker
, tempo :: Int -- ^ Tempo for the melody
}
-- | Create a piezo speaker instance.
speaker :: Int -- ^ Tempo. Higher numbers mean faster melodies; in general.
-> Pin -- ^ Pin controlling the piezo. Should be a pin that supports PWM mode.
-> Arduino Piezo
speaker t p = do debug $ "Attaching speaker on pin: " ++ show p
setPinMode p PWM
(ip, _) <- convertAndCheckPin "Piezo.speaker" p PWM
return Piezo { piezoPin = ip, tempo = t }
-- | Musical notes, notes around middle-C
data Note = A | B | C | D | E | F | G | R deriving (Eq, Show) -- R is for rest
-- | Beat counts
data Duration = Whole | Half | Quarter | Eight deriving (Eq, Show)
-- | Convert a note to its frequency appropriate for Piezo
frequency :: Note -> Int
frequency n = fromMaybe 0 (n `lookup` fs)
where fs = [(A, 440), (B, 493), (C, 261), (D, 294), (E, 329), (F, 349), (G, 392), (R, 0)]
-- | Convert a duration to a delay amount
interval :: Piezo -> Duration -> Int
interval p Whole = 8 * interval p Eight
interval p Half = 4 * interval p Eight
interval p Quarter = 2 * interval p Eight
interval p Eight = tempo p
-- | Turn the speaker off
silence :: Piezo -> Arduino ()
silence (Piezo p _) = send $ AnalogPinWrite p 0 0
-- | Keep playing a given note on the piezo:
setNote :: Piezo -> Note -> Arduino ()
setNote (Piezo p _) n = send $ AnalogPinWrite p (fromIntegral lsb) (fromIntegral msb)
where f = frequency n
lsb = f .&. 0x7f
msb = (f `shiftR` 7) .&. 0x7f
-- | Play the given note for the duration
playNote :: Piezo -> (Note, Duration) -> Arduino ()
playNote pz (n, d) = do setNote pz n
delay (interval pz d)
silence pz
-- | Play a sequence of notes with given durations:
playNotes :: Piezo -> [(Note, Duration)] -> Arduino ()
playNotes pz = go
where go [] = silence pz
go (nd@(_, d):r) = do playNote pz nd
delay (interval pz d `div` 3) -- heuristically found.. :-)
go r
-- | Rest for a given duration:
rest :: Piezo -> Duration -> Arduino ()
rest pz d = delay (interval pz d)