Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Alan Wang
Published © CC BY-NC-SA

X'mas Music Box - Super Simple CircuitPython MP3 Player

Let's build a Christmas decoration that plays music from a SD card with some lights!

BeginnerFull instructions provided542
X'mas Music Box - Super Simple CircuitPython MP3 Player

Things used in this project

Hardware components

Adafruit ItsyBitsy M4 Express
×1
Development Board, Class D Audio Amplifier Module
Development Board, Class D Audio Amplifier Module
×1
Speaker (8 ohms 0.5 watt)
×1
SD Card Module
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×1
NeoPixel Ring: WS2812 5050 RGB LED
Adafruit NeoPixel Ring: WS2812 5050 RGB LED
×1

Software apps and online services

CircuitPython

Story

Read more

Schematics

Xmas Music Box

Code

Xmas Music Box

MicroPython
Using CircuitPython 7.x
# ----------------------------------------

import board, busio

# button
BTN_PIN = board.D4
QUICK_PRESS_THRESHOLD = 0.4  # second; pause/resume
MID_PRESS_THRESHOLD = 1.2  # second; play next track
# long press = back to start

# audio out (DAC or PWM)
AUDIO_PIN = board.A0

# SPI SD card module
SDCARD_MOUNT = 'SD'
SDCARD_CS_PIN = board.D2
try:
    SPI = board.SPI()
except:
    # for RP2040s
    SPI = busio.SPI(clock=board.GP18, MOSI=board.GP19, MISO=board.GP16)

# on board RGB LED (neopixel or dotstar)
BOARD_HAS_LED = hasattr(board, 'DOTSTAR_CLOCK') or hasattr(board, 'APA102_MOSI') or hasattr(board, 'NEOPIXEL')
BOARD_LEDS_NUM = 1
BOARD_LED_BRIGHTNESS = 1.0

# external RGB LEDs (neopixel)
EXT_LEDS_NUM = 13  # set to 0 if you have none
EXT_LEDS_PIN = board.D5
EXT_LEDS_BRIGHTNESS = 0.2

# ----------------------------------------

import time, gc

gc.enable()

def openMp3(filename):
    return open(f'/{SDCARD_MOUNT}/{filename}', 'rb')

try:
    from rainbowio import colorwheel
    
    # https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel/releases
    from neopixel import NeoPixel
    
    if BOARD_HAS_LED:
        if hasattr(board, 'DOTSTAR_CLOCK') or hasattr(board, 'APA102_MOSI'):
            # https://github.com/adafruit/Adafruit_CircuitPython_DotStar/releases
            from adafruit_dotstar import DotStar
            try:
                board_led = DotStar(board.DOTSTAR_CLOCK, board.DOTSTAR_DATA, BOARD_LEDS_NUM)
            except:
                board_led = DotStar(board.APA102_SCK, board.APA102_MOSI, BOARD_LEDS_NUM)
        else:
            board_led = NeoPixel(board.NEOPIXEL, BOARD_LEDS_NUM)
        board_led.brightness = BOARD_LED_BRIGHTNESS
        board_led.fill((255, 255, 255))
        board_led.show()
    
    if EXT_LEDS_NUM > 0:
        ext_leds = NeoPixel(EXT_LEDS_PIN, EXT_LEDS_NUM)
        ext_leds.brightness = EXT_LEDS_BRIGHTNESS
        ext_leds.fill((0, 0, 0))
        ext_leds.show()

    from digitalio import DigitalInOut, Pull
    button = DigitalInOut(BTN_PIN)
    button.switch_to_input(pull=Pull.UP)
    
    import storage
    from sdcardio import SDCard
    sdcard = SDCard(SPI, SDCARD_CS_PIN)
    vfs = storage.VfsFat(sdcard)
    storage.mount(vfs, f'/{SDCARD_MOUNT}')

    mp3files = [name for name, _, _, _ in vfs.ilistdir('/')
                if name.endswith('.mp3') and not name.startswith('.')]
    
    if not mp3files:  # empty list
        raise Exception('No mp3 files found on sd card')
    
    mp3files.sort()
    
    print('List of found mp3 files:')
    for filename in mp3files:
        print(f'* {filename}')
    print(f'Total: {len(mp3files)} tracks')
    
    try:
        from audioio import AudioOut
    except ImportError:
        from audiopwmio import PWMAudioOut as AudioOut
    from audiomp3 import MP3Decoder

    audio = AudioOut(AUDIO_PIN)
    mp3 = openMp3(mp3files[0])
    decoder = MP3Decoder(mp3)

except Exception as e:
    print('Error occurred:', e)
    try:
        if BOARD_HAS_LED:
            board_led.fill((255, 0, 0))
            board_led.show()
    finally:
        while True:
            pass

startPress = 0
pressDuration = 0
isPressing = False
mp3Index = 0
ledIndex = 0

def playMp3():
    print(f'Playing track: {mp3files[mp3Index]}')
    time.sleep(0.5)
    decoder.file = openMp3(mp3files[mp3Index])
    audio.play(decoder)

def forwardMp3():
    global mp3Index
    mp3Index = (mp3Index + 1) % len(mp3files)

time.sleep(1)
playMp3()

while True:
    if not audio.playing:
        forwardMp3()
        playMp3()
        
    if not button.value and not isPressing:
        isPressing = True
        startPress = time.monotonic_ns()
    
    if button.value and isPressing:
        isPressing = False
        pressDuration = (time.monotonic_ns() - startPress) / 1000000000
        if pressDuration <= QUICK_PRESS_THRESHOLD:
            if audio.paused and audio.playing:
                audio.resume()
                print('Audio resumed')
            elif not audio.paused and audio.playing:
                audio.pause()
                print('Audio paused')
        elif pressDuration <= MID_PRESS_THRESHOLD:
            audio.stop()
            print('Skip to next track...')
            forwardMp3()
            playMp3()
        else:  # long press
            audio.stop()
            print('Back to first track...')
            mp3Index = 0
            playMp3()
    
    ledIndex = (ledIndex + 1) % 255
    color = colorwheel(ledIndex)
    if BOARD_HAS_LED:
        board_led.fill(color)
        board_led.show()
    if EXT_LEDS_NUM > 0:
        ext_leds.fill(color)
        ext_leds.show()
    
    time.sleep(0.1)

Credits

Alan Wang
32 projects • 102 followers
Please do not ask me for free help for school or company projects. My time is not open sourced and you cannot buy it with free compliments.

Comments