# Ali Rashidi
# t.me/WriteYourWay
import machine, utime, random, uasyncio as asyncio # وارد کردن ماژولهای مورد نیاز:
# machine برای کنترل سختافزار، utime برای مدیریت زمان،
# random برای تولید اعداد تصادفی و uasyncio برای برنامهنویسی غیرهمزمان.
from i2c_lcd import I2cLcd # وارد کردن کلاس I2cLcd از کتابخانه i2c_lcd جهت کنترل LCD از طریق I2C
# ---------------------------------------------------------------------------
# تعریف دیکشنری نتهای موسیقی: کلیدهای کیپد به نام و فرکانس نتهای مربوطه
# ---------------------------------------------------------------------------
note_mapping = {
"1": ("C4", 262), # کلید "1": نت C4 با فرکانس تقریبی 262 هرتز
"2": ("D4", 294), # کلید "2": نت D4 با فرکانس تقریبی 294 هرتز
"3": ("E4", 330), # کلید "3": نت E4 با فرکانس تقریبی 330 هرتز
"A": ("F4", 349), # کلید "A": نت F4 با فرکانس تقریبی 349 هرتز
"4": ("G4", 392), # کلید "4": نت G4 با فرکانس تقریبی 392 هرتز
"5": ("A4", 440), # کلید "5": نت A4 با فرکانس 440 هرتز
"6": ("B4", 494), # کلید "6": نت B4 با فرکانس تقریبی 494 هرتز
"B": ("C5", 523), # کلید "B": نت C5 با فرکانس تقریبی 523 هرتز
"7": ("D5", 587), # کلید "7": نت D5 با فرکانس تقریبی 587 هرتز
"8": ("E5", 659), # کلید "8": نت E5 با فرکانس تقریبی 659 هرتز
"9": ("F5", 698), # کلید "9": نت F5 با فرکانس تقریبی 698 هرتز
"C": ("G5", 784), # کلید "C": نت G5 با فرکانس تقریبی 784 هرتز
"0": ("A5", 880), # کلید "0": نت A5 با فرکانس 880 هرتز
"D": ("B5", 988) # کلید "D": نت B5 با فرکانس تقریبی 988 هرتز
}
# کلیدهای "*" و "#" به ترتیب برای ضبط (Record) و پخش (Playback) در نظر گرفته شدهاند.
# ---------------------------------------------------------------------------
# تنظیمات اولیه
# ---------------------------------------------------------------------------
record_mode = False # حالت ضبط؛ در ابتدا فعال نیست
recording = [] # لیستی برای ذخیره نتهای ضبط شده؛ هر ورودی شامل (کلید, فاصله زمانی) است
last_record_time = 0 # زمان آخرین نت ضبط شده (برای محاسبه فاصله زمانی)
tempo = 1.0 # ضریب تمپو (سرعت پخش)؛ پیشفرض 1.0
pitch_multiplier = 1.0 # ضریب تغییر تن صدا؛ پیشفرض 1.0
# ============================================================
# کلاس Keypad: کنترل کیپد 4×4 جهت دریافت ورودیهای موسیقی
# ============================================================
class Keypad:
def __init__(self, row_pins, col_pins, keys):
# ایجاد پینهای ردیف (خروجی)
self.rows = [machine.Pin(pin, machine.Pin.OUT) for pin in row_pins]
# ایجاد پینهای ستون (ورودی با pull-down)
self.cols = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_DOWN) for pin in col_pins]
# ذخیره ماتریس کلیدها
self.keys = keys
async def scan(self):
"""
اسکن کیپد جهت تشخیص کلید فشرده شده
"""
for i, row in enumerate(self.rows):
row.value(1) # فعال کردن ردیف
for j, col in enumerate(self.cols):
if col.value() == 1:
await asyncio.sleep_ms(50) # تأخیر جهت کاهش نویز
if col.value() == 1:
key = self.keys[i][j] # دریافت کلید فشرده
start_release = utime.ticks_ms() # ثبت زمان شروع آزادسازی
while col.value() == 1:
await asyncio.sleep_ms(10)
if utime.ticks_diff(utime.ticks_ms(), start_release) > 500:
break
row.value(0) # غیرفعال کردن ردیف
return key
row.value(0)
return None
# ============================================================
# کلاس Joystick: (اختیاری) در این پروژه میتوان از آن برای تنظیم تمپو یا تن صدا استفاده کرد
# ============================================================
class Joystick:
def __init__(self, vrx_pin, vry_pin, sw_pin):
self.vrx = machine.ADC(machine.Pin(vrx_pin))
self.vry = machine.ADC(machine.Pin(vry_pin))
self.sw = machine.Pin(sw_pin, machine.Pin.IN, machine.Pin.PULL_UP)
async def read(self):
if not self.sw.value():
await asyncio.sleep_ms(150)
return "CENTER"
x = self.vrx.read_u16()
y = self.vry.read_u16()
if x < 20000:
return "RIGHT"
elif x > 45000:
return "LEFT"
elif y < 20000:
return "DOWN"
elif y > 45000:
return "UP"
return None
# ============================================================
# کلاس Buzzer: پخش نتهای موسیقی از طریق بوزر با استفاده از PWM
# ============================================================
class Buzzer:
def __init__(self, pin_num):
self.pin = machine.Pin(pin_num)
self.pwm = machine.PWM(self.pin)
self.pwm.freq(1000)
self.pwm.duty_u16(0)
async def play_tone(self, frequency, duration):
self.pwm.freq(int(frequency)) # تنظیم فرکانس (با اعمال pitch_multiplier)
self.pwm.duty_u16(32768) # تنظیم duty به 50%
await asyncio.sleep_ms(duration)
self.pwm.duty_u16(0) # خاموش کردن بوزر
async def beep(self, duration=100):
await self.play_tone(1000, duration)
# ============================================================
# تابع replay_pattern: پخش دنباله ضبط شده روی LCD به همراه صداها
# ============================================================
async def replay_pattern(seq, lcd, buzzer, extra_delay):
lcd.clear()
lcd.putstr("Playing Record")
await asyncio.sleep_ms(1000)
# پیمایش نتهای ضبط شده؛ هر ورودی شامل (کلید, تأخیر) است
for note, delay in seq:
lcd.clear()
# در هنگام پخش، نام نت نمایش داده میشود
if note in note_mapping:
note_name = note_mapping[note][0]
lcd.putstr("Note: {}".format(note_name))
freq = note_mapping[note][1] * pitch_multiplier # اعمال تغییر تن (pitch)
await buzzer.play_tone(freq, 150)
await asyncio.sleep_ms(int(delay * tempo)) # صبر به مدت تأخیر ضربدر تمپو
lcd.clear()
lcd.putstr("Playback Done")
await asyncio.sleep_ms(1000)
# ============================================================
# تابع play_note: پخش نت موسیقی و نمایش نام آن
# ============================================================
async def play_note(key, lcd, buzzer):
if key in note_mapping:
note_name, base_freq = note_mapping[key]
freq = base_freq * pitch_multiplier # اعمال تغییر تن (pitch)
lcd.clear()
lcd.putstr("Note: {}".format(note_name)) # نمایش نام نت روی LCD
await buzzer.play_tone(freq, 150) # پخش صدای نت به مدت 150 میلیثانیه
# ============================================================
# تابع main_loop: حلقه اصلی پروژه برای دریافت ورودیهای کیپد و مدیریت حالتهای ضبط/پخش
# ============================================================
async def main_loop(keypad, joystick, buzzer, lcd):
global record_mode, recording, last_record_time
last_record_time = utime.ticks_ms() # ثبت زمان اولیه
while True:
key = await keypad.scan() # دریافت ورودی از کیپد
if key is None:
await asyncio.sleep_ms(20)
continue
# اگر کلید "*" فشرده شود: تغییر حالت ضبط (Record)
if key == "*":
record_mode = not record_mode # تغییر وضعیت ضبط
if record_mode:
recording = [] # پاکسازی ضبط قبلی
last_record_time = utime.ticks_ms() # ثبت زمان شروع ضبط جدید
lcd.clear()
lcd.putstr("Recording...") # نمایش پیام ضبط
else:
lcd.clear()
lcd.putstr("Recording Stopped")
await asyncio.sleep_ms(500)
continue
# اگر کلید "#" فشرده شود: پخش ضبط
if key == "#":
if recording:
lcd.clear()
lcd.putstr("Playback...")
await asyncio.sleep_ms(500)
await replay_pattern(recording, lcd, buzzer, 0)
else:
lcd.clear()
lcd.putstr("No Record!")
await asyncio.sleep_ms(500)
continue
# اگر کلید فشرده شده متعلق به نتهای موسیقی باشد
if key in note_mapping:
# پخش نت و نمایش نام آن
await play_note(key, lcd, buzzer)
# اگر حالت ضبط فعال است، ثبت نت و فاصله زمانی از آخرین نت
if record_mode:
current_time = utime.ticks_ms()
delay = utime.ticks_diff(current_time, last_record_time)
last_record_time = current_time
recording.append((key, delay))
await asyncio.sleep_ms(20) # تأخیر کوتاه جهت کاهش مصرف CPU
# ============================================================
# تابع setup: تنظیمات اولیه سختافزاری (کیپد، جویاستیک، بوزر و LCD)
# ============================================================
def setup():
# تنظیم پینهای کیپد
keypad_rows = [2, 3, 4, 5]
keypad_cols = [6, 7, 8, 9]
keypad_keys = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"]
]
kp = Keypad(keypad_rows, keypad_cols, keypad_keys)
# تنظیم جویاستیک (در صورت نیاز به تنظیمات بیشتر برای تن/تمپو میتوان استفاده کرد)
js = Joystick(vrx_pin=26, vry_pin=27, sw_pin=22)
# تنظیم بوزر بر روی پین 15
bz = Buzzer(15)
# تنظیم I2C و LCD (آدرس 0x27، ابعاد 2×16)
i2c = machine.I2C(0, scl=machine.Pin(17), sda=machine.Pin(16), freq=400000)
lcd = I2cLcd(i2c, 0x27, 2, 16)
return kp, js, bz, lcd
# ============================================================
# تابع main: تابع اصلی جهت اجرای پروژه
# ============================================================
async def main():
kp, js, bz, lcd = setup() # دریافت تنظیمات اولیه سختافزاری
lcd.clear()
lcd.putstr("Digital Piano") # نمایش عنوان پروژه روی LCD
await asyncio.sleep_ms(2000)
# نمایش داستان آغازین پروژه
lcd.clear()
lcd.putstr("Welcome, Arya!\nUnlock the magic")
await asyncio.sleep_ms(3000)
# اجرای حلقه اصلی پیانو دیجیتال
await main_loop(kp, js, bz, lcd)
# اجرای تابع main به صورت غیرهمزمان
asyncio.run(main())