Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
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 provided559
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 • 103 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.
Contact

Comments

Please log in or sign up to comment.