5353from .devices import GPIODevice , Device , CompositeDevice
5454from .mixins import SourceMixin
5555from .threads import GPIOThread
56+ from .tones import Tone
5657
5758
5859class OutputDevice (SourceMixin , GPIODevice ):
@@ -626,74 +627,75 @@ class TonalBuzzer(SourceMixin, CompositeDevice):
626627 :class:`~gpiozero.pins.pigpio.PiGPIOFactory`.
627628 """
628629
629- def __init__ (self , pin = None , initial_value = None , mid_note = 'A4' , octaves = 1 ,
630- pin_factory = None ):
631- self ._validate_note_range ( mid_note , octaves )
630+ def __init__ (self , pin = None , initial_value = None , mid_tone = Tone ( "A4" ) ,
631+ octaves = 1 , pin_factory = None ):
632+ self ._mid_tone = None
632633 super (TonalBuzzer , self ).__init__ (
633634 pwm_device = PWMOutputDevice (
634635 pin = pin , pin_factory = pin_factory
635636 ), pin_factory = pin_factory )
636637 try :
638+ self ._mid_tone = Tone (mid_tone )
639+ if not (0 < octaves <= 9 ):
640+ raise ValueError ('octaves must be between 1 and 9' )
637641 self ._octaves = octaves
642+ try :
643+ self .min_tone .note
644+ except ValueError :
645+ raise ValueError (
646+ '%r is too low for %d octaves' %
647+ (self ._mid_tone , self ._octaves ))
648+ try :
649+ self .max_tone .note
650+ except ValueError :
651+ raise ValueError (
652+ '%r is too high for %d octaves' %
653+ (self ._mid_tone , self ._octaves ))
638654 self .value = initial_value
639655 except :
640656 self .close ()
641657 raise
642658
643659 def __repr__ (self ):
644660 try :
645- return '<gpiozero.%s object on pin %r, is_active=%s>' % (
646- self .__class__ .__name__ , self .pwm_device .pin , self .is_active )
661+ if self .value is None :
662+ return '<gpiozero.TonalBuzzer object on pin %r, silent>' % (
663+ self .pwm_device .pin ,)
664+ else :
665+ return '<gpiozero.TonalBuzzer object on pin %r, playing %s>' % (
666+ self .pwm_device .pin , self .tone .note )
647667 except :
648- return super (OutputDevice , self ).__repr__ ()
649-
650- def _note_to_midi (self , note ):
651- if isinstance (note , bytes ):
652- note = note .decode ('ascii' )
653- if isinstance (note , str ):
654- nt , num = note [:- 1 ].replace ('#' , 'S' ), int (note [- 1 ])
655- notes = 'C CS D DS E F FS G GS A AS B' .split (' ' )
656- note = (num + 1 ) * 12 + notes .index (nt )
657- return note
658-
659- def _note_to_freq (self , note ):
660- midi = self ._note_to_midi (note )
661- return 2 ** ((midi - 69 )/ 12 )* 440
662-
663- def _validate_note_range (self , mid_note , octaves ):
664- self ._mid_note = self ._note_to_midi (mid_note )
665- self ._octaves = octaves
666- if octaves < 1 :
667- raise ValueError ('octaves must be at least 1' )
668- if self .min_note < 1 :
669- plural = 's' if octaves > 1 else ''
670- raise ValueError (
671- 'mid_note too low for {} octave{}' .format (octaves , plural ))
672-
673- def note_value (self , note ):
674- """
675- Convert the given note to a normalized value scaled -1 to 1 relative to
676- the buzzer's range. If note is :data:`None`, :data:`None` is returned.
677- """
678- if note is None :
679- return None
680- note = self ._note_to_midi (note )
681- return (note - self .mid_note ) / (self .max_note - self .mid_note )
668+ return super (TonalBuzzer , self ).__repr__ ()
682669
683- def play (self , note ):
670+ def play (self , tone ):
684671 """
685- Play the given note e.g. ``play(60)`` (MIDI) or ``play('C4')`` (note).
686- ``play(None)`` turns the buzzer off.
672+ Play the given *tone*. This can either be an instance of
673+ :class:`~gpiozero.tones.Tone` or can be anything that could be used to
674+ construct an instance of :class:`~gpiozero.tones.Tone`.
675+
676+ For example::
677+
678+ >>> from gpiozero import TonalBuzzer
679+ >>> from gpiozero.tones import Tone
680+ >>> b = TonalBuzzer(17)
681+ >>> b.play(Tone("A4"))
682+ >>> b.play(Tone(220.0)) # Hz
683+ >>> b.play(Tone(60)) # middle C in MIDI notation
684+ >>> b.play("A4")
685+ >>> b.play(220.0)
686+ >>> b.play(60)
687687 """
688- if note is None :
688+ if tone is None :
689689 self .value = None
690690 else :
691- freq = self ._note_to_freq (note )
692- if self .min_frequency <= freq <= self .max_frequency :
691+ if not isinstance (tone , Tone ):
692+ tone = Tone (tone )
693+ freq = tone .frequency
694+ if self .min_tone .frequency <= tone <= self .max_tone .frequency :
693695 self .pwm_device .pin .frequency = freq
694696 self .pwm_device .value = 0.5
695697 else :
696- raise ValueError ("note out of the device's range" )
698+ raise ValueError ("tone is out of the device's range" )
697699
698700 def stop (self ):
699701 """
@@ -702,33 +704,46 @@ def stop(self):
702704 """
703705 self .value = None
704706
707+ @property
708+ def tone (self ):
709+ """
710+ Returns the :class:`~gpiozero.tones.Tone` that the buzzer is currently
711+ playing, or :data:`None` if the buzzer is silent. This property can
712+ also be set to play the specified tone.
713+ """
714+ if self .pwm_device .pin .frequency is None :
715+ return None
716+ else :
717+ return Tone .from_frequency (self .pwm_device .pin .frequency )
718+
719+ @tone .setter
720+ def tone (self , value ):
721+ self .play (value )
722+
705723 @property
706724 def value (self ):
707725 """
708- Represents the state of the buzzer as a value between 0 (representing
726+ Represents the state of the buzzer as a value between -1 (representing
709727 the minimum note) and 1 (representing the maximum note). This can also
710728 be the special value :data:`None` indicating that the buzzer is
711729 currently silent.
712730 """
713731 if self .pwm_device .pin .frequency is None :
714732 return None
715733 else :
716- freq = self .pwm_device .pin .frequency
717- mid_freq = self .mid_frequency
718734 try :
719- return log2 (freq / mid_freq ) / self .octaves
735+ return log2 (
736+ self .pwm_device .pin .frequency / self .mid_tone .frequency
737+ ) / self .octaves
720738 except ZeroDivisionError :
721- return 0
739+ return 0.0
722740
723741 @value .setter
724742 def value (self , value ):
725743 if value is None :
726744 self .pwm_device .pin .frequency = None
727745 elif - 1 <= value <= 1 :
728- if value == 0 :
729- freq = self .mid_frequency
730- else :
731- freq = self .mid_frequency * 2 ** (self .octaves * value )
746+ freq = self .mid_tone .frequency * 2 ** (self .octaves * value )
732747 self .pwm_device .pin .frequency = freq
733748 self .pwm_device .value = 0.5
734749 else :
@@ -751,49 +766,28 @@ def octaves(self):
751766 return self ._octaves
752767
753768 @property
754- def min_note (self ):
755- """
756- The minimum note available (i.e. the MIDI note represented when value
757- is -1).
758- """
759- return self .mid_note - 12 * self .octaves
760-
761- @property
762- def mid_note (self ):
763- """
764- The middle note available (i.e. the MIDI note represented when value is
765- 0).
766- """
767- return self ._mid_note
768-
769- @property
770- def max_note (self ):
771- """
772- The maximum note available (i.e. the MIDI note represented when value
773- is 1).
774- """
775- return self .mid_note + 12 * self .octaves
776-
777- @property
778- def min_frequency (self ):
769+ def min_tone (self ):
779770 """
780- The frequency of the minimum note.
771+ The lowest tone that the buzzer can play, i.e. the tone played
772+ when :attr:`value` is -1.
781773 """
782- return self ._note_to_freq ( self .min_note )
774+ return self ._mid_tone . down ( 12 * self .octaves )
783775
784776 @property
785- def mid_frequency (self ):
777+ def mid_tone (self ):
786778 """
787- The frequency of the middle note.
779+ The middle tone available, i.e. the tone played when :attr:`value` is
780+ 0.
788781 """
789- return self ._note_to_freq ( self . mid_note )
782+ return self ._mid_tone
790783
791784 @property
792- def max_frequency (self ):
785+ def max_tone (self ):
793786 """
794- The frequency of the maximum note.
787+ The highest tone that the buzzer can play, i.e. the tone played when
788+ :attr:`value` is 1.
795789 """
796- return self ._note_to_freq ( self .max_note )
790+ return self ._mid_tone . up ( 12 * self .octaves )
797791
798792
799793class PWMLED (PWMOutputDevice ):
0 commit comments