利用Switch上的Smile Basic软件控制手柄播放音乐

利用Switch上的Smile Basic软件控制手柄播放音乐(以春日影这首曲子为例)
文中关键部分已经使用中文进行注释。因为Smile Basic不支持中文的输入,所以里面的中文注释是我后面补充的。
本代码是基于B站 @自觉放齐 大佬的代码上进行修改的,感谢大佬的辛勤付出。
如果你要修改成其他的曲目,主要修改

  1. BEAT_DURATION 控制歌曲一拍的时长
  2. KEY_OFFSET 控制歌曲是什么调(简谱只需要修改这个就可以控制整首歌调,不需要调整后面的音符)
  3. SING()函数,里面定义了具体的每一拍的音符并播放音符,你需要找到对应歌曲的简谱来进行修改(如果是五线谱,建议转成简谱再编写进去,因为下面的代码对简谱更友好,乐理大佬请无视)

SMILE BASIC需要购买正版才能保存工程文件,上传工程则需要购买服务器券,试玩版可以浏览别人的工程和进行编程,但是不能保存文件也不能上传。
另外,没有破解switch的话,SMILE BASIC的工程文件无法从外部导入也无法导出到外部,所以以下代码需要你手敲(本人代码也是手敲到电脑里的),写电脑上主要是为了方便询问大模型,因为本人的乐理也是靠大模型现学的。
SMILE_BASIC的使用教程,后面会给出详细的教程(可能会出视频),尽请期待!如果你会英文也可以自学,首页有programer's guide,里面介绍了smile basic的简要使用(入门很快,里面用的英文都很简单)。建议连接键盘和鼠标后使用。
SMILE_BASIC的简要快捷键说明(都可以在指引中找到)

  1. CTRL+S 保存 CTRL+L(载入代码)
  2. CTRL+C/V 复制/粘贴(可以用鼠标选择代码,也可以按shift+方向键选择)
  3. CTRL+F/CTRL+H 查找、替换(按ESC退出)
  4. F4(进入工程后,按F4切换为代码模式,或者手柄上的X键)
  5. F5(运行代码,默认运行MAIN.PRG)
  6. F12(回到主界面)
' 播放春日影(利用HD震动).
'
'彩蛋:
' 按住十字键上键和左摇杆保持三秒钟解除音高限制
'这个彩蛋看大佬评论区都没人提到,估计就我这种会看代码的人才会看到了吧233

OPTION STRICT
OPTION DEFINT

'Master volume 总音量: 0 to 100
CONST #VOLUME=95
'Main melody volume 主旋律音量: 0 to 10
CONST #MVOL=10
'Cord Volume 和弦音量: 0 to 10
CONST #CVOL=3
'Duration of a beat(1/10 sec)
CONST #BEAT_DURATION=1.6

'中央C的频率
CONST #MIDDLE_C = 262

'Change the pitch of a not by some half tones
DEF PITCH(NOTE,HALE_TONES)
    'Equal temperament
    'Each half tone is 1.059 higher than the previous one
    RETURN NOTE*POW(1.059,HALE_TONES)
END


CONST #MAX_P = 27
CONST #MIN_P = -25

VAR P=0 'Current pitch,controlled by Up and Down button
VAR FP=0.0 'Current fine pitch,controlled by LSTICK up and down
VAR KEY_OFFSET=11 '控制大调,这里11就是B大调,7就是G大调,不懂乐理可以问大模型


DEF C()
    RETURN PITCH(#MIDDLE_C,12+P+FP+KEY_OFFSET)
END
'Do
DEF N1()
    RETURN ROUND(C())
END
'Lower Do
DEF _N1()
    RETURN ROUND(PITCH(C(),-12))
END
'Re
DEF N2()
    RETURN ROUND(PITCH(C(),2))
END
'Lower Re
DEF _N2()
    RETURN ROUND(PITCH(C(),-10))
END
'Mi
DEF N3()
    RETURN ROUND(PITCH(C(),4))
END
'Lower Mi
DEF _N3()
    RETURN ROUND(PITCH(C(),-8))
END
'Fa
DEF N4()
    RETURN ROUND(PITCH(C(),5))
END
'Lower Fa
DEF _N4()
    RETURN ROUND(PITCH(C(),-7))
END
'So
DEF N5()
    RETURN ROUND(PITCH(C(),7))
END
'Lower So
DEF _N5()
    RETURN ROUND(PITCH(C(),-5))
END
'La
DEF N6()
    RETURN ROUND(PITCH(C(),9))
END
'Lower La
DEF _N6()
    RETURN ROUND(PITCH(C(),-3))
END
'Ti
DEF N7()
    RETURN ROUND(PITCH(C(),11))
END
'Lower Ti
DEF _N7()
    RETURN ROUND(PITCH(C(),-1))
END

'======== higher notes function ========
'higher Do
DEF N1H()
    RETURN ROUND(PITCH(C(), 12)) 'higher an octave
END
'higher Re
DEF N2H()
    RETURN ROUND(PITCH(C(), 14))
END
'higher Mi
DEF N3H()
    RETURN ROUND(PITCH(C(), 16))
END
'higher Fa
DEF N4H()
    RETURN ROUND(PITCH(C(), 17))
END
'higher Sol
DEF N5H()
    RETURN ROUND(PITCH(C(), 19))
END
'higher La
DEF N6H()
    RETURN ROUND(PITCH(C(), 21))
END
'higher Ti
DEF N7H()
    RETURN ROUND(PITCH(C(), 23))
END

'======== Double lower notes function ========
DEF _N1L()
    RETURN ROUND(PITCH(C(), -24))
END
DEF _N2L()
    RETURN ROUND(PITCH(C(), -22))
END
DEF _N3L()
    RETURN ROUND(PITCH(C(), -20))
END
DEF _N4L()
    RETURN ROUND(PITCH(C(), -19))
END
DEF _N5L()
    RETURN ROUND(PITCH(C(), -17))
END
DEF _N6L()
    RETURN ROUND(PITCH(C(), -15))
END
DEF _N7L()
    RETURN ROUND(PITCH(C(), -13))
END



LOOP
    CLS
    IF !MENU() THEN
        BREAK
    ENDIF
    ? "--------------------"
    ? "     HARUHIKAGE     "
    ? "--------------------"
    IF !SLEEP(200) THEN
        BREAK
    ENDIF

    IF !SING() THEN
        BREAK
    ENDIF
    FIN
ENDLOOP
CLS
FIN


'Separator of left and right
CONST #R=-1

'Notes used in SING
CONST #1 = 1
CONST #2 = 2
CONST #3 = 3
CONST #4 = 4
CONST #5 = 5
CONST #6 = 6
CONST #7 = 7

CONST #_1 = -11
CONST #_2 = -12
CONST #_3 = -13
CONST #_4 = -14
CONST #_5 = -15
CONST #_6 = -16
CONST #_7 = -17

CONST #1H = 11  'higher Do
CONST #2H = 12  'higher Re
CONST #3H = 13  'higher Mi
CONST #4H = 14  'higher Fa
CONST #5H = 15  'higher Sol
CONST #6H = 16  'higher La
CONST #7H = 17  'higher Si

CONST #_1L = -21
CONST #_2L = -22
CONST #_3L = -23
CONST #_4L = -24
CONST #_5L = -25
CONST #_6L = -26
CONST #_7L = -27


'播放春日影.
'说明 春日影是B大调 6/8拍的曲子,一小节总共6个八分音符的时长,以下内容结合春日影简谱
'在春日影的前奏中,最多用到了16分音符,因此代码中能处理的最小单位也是16分音符
'前面的beat_duration也根据这边最小的音符单位进行微调
'在N()函数中,一个参数代表一个16分音符,而俩个16分音符相当于一个八分,同理,俩个八分相当于一个四分
'春日影前奏只用到了四分 八分 十六分音符,四分音符就是下面没有下划线,右边也没加点的音符 比如第一小节的3
'八分音符下面有一条下划线,十六分音符下面有俩条
'右边如果加点,代表在这个音符的基础上加半拍,例如,第二小节的第一个3右边有一个点,下面有一条下划线,代表这个3是一个八分音符加半拍,就是一个八分音符+一个十六分音符
'体现在代码里就是连续三个#3
'顶上有一个点代表高音,春日影前奏全部是高音
'但是由于switch手柄限制无法发出N1H以上的高音(也许你的手柄能发出来),因此这里全部降了八度使用,请注意
DEF SING()
    ? "Haruhikage - Intro"
'第一小节:3 2 1 2 #R左边控制左马达,右边控制右马达,#MVOL 和 #CVOL 表示左右俩边分别使用主旋律音量和和弦音量
    IF !N(#MVOL, #3,#3,#3 #3, #2,#2, #1,#1,#1,#1,#2,#2, #R, #CVOL, #3,#3,#3 #3, #2,#2, #1,#1,#1,#1,#2,#2) THEN
        RETURN 0
    ENDIF

'第二小节:3 4 3 2
    IF !N(#CVOL, #3, #3, #3, #4,#3,#3,#2,#2,#2,#2,#2,#2, #R, #MVOL, #3, #3, #3, #4,#3,#3,#2,#2,#2,#2,#2,#2) THEN
        RETURN 0
    ENDIF
    RETURN 1
END


'N plays some notes each 1 beat
'Usage: N leftVol, LeftNotes.. #R,RightVol,RightNotes..
DEF N(*)
    VAR L=DEFARGC() 'Count of arguments
    'Left volume
    VAR VL=DEFARG(0)
    'Find separator
    VAR SEP=L
    VAR I
    FOR I=1 TO L-1
        IF DEFARG(I)==#R THEN
            SEP=I
            BREAK
        ENDIF
    NEXT

    'Right volume
    VAR VR=0
    IF SEP<L-1 THEN
        VR=DEFARG(SEP+1)
    ENDIF


    VAR LL=SEP-1
    VAR LR=L-SEP-2
    IF LR<0 THEN LR=0
    VAR N_NOTES=LL
    IF LR>N_NOTES THEN N_NOTES=LR

    FOR I=1 TO N_NOTES
        'Left frequency
        VAR FL=0
        IF I<=LL THEN FL=DEFARG(I)
        'Right frequency
        VAR FR=0
        VAR J=I+SEP+1
        IF J<=L-1 THEN
            FR = DEFARG(J)
        ENDIF
        IF ! PLAY(FNOTE(FL),VL,FNOTE(FR),VR,#BEAT_DURATION) THEN
            RETURN 0
        ENDIF
    NEXT
    RETURN 1
END

'FNOTE returns the frequency of a note N
DEF FNOTE(N)
    CASE N
        WHEN 0: RETURN 0
        WHEN #1: RETURN N1()
        WHEN #2: RETURN N2()
        WHEN #3: RETURN N3()
        WHEN #4: RETURN N4()
        WHEN #5: RETURN N5()
        WHEN #6: RETURN N6()
        WHEN #7: RETURN N7()
        WHEN #_1: RETURN _N1()
        WHEN #_2: RETURN _N2()
        WHEN #_3: RETURN _N3()
        WHEN #_4: RETURN _N4()
        WHEN #_5: RETURN _N5()
        WHEN #_6: RETURN _N6()
        WHEN #_7: RETURN _N7()
        '======== add higher note check ========
        WHEN #1H: RETURN N1H()
        WHEN #2H: RETURN N2H()
        WHEN #3H: RETURN N3H()
        WHEN #4H: RETURN N4H()
        WHEN #5H: RETURN N5H()
        WHEN #6H: RETURN N6H()
        WHEN #7H: RETURN N7H()

        WHEN #_1L: RETURN _N1L()
        WHEN #_2L: RETURN _N2L()
        WHEN #_3L: RETURN _N3L()
        WHEN #_4L: RETURN _N4L()
        WHEN #_5L: RETURN _N5L()
        WHEN #_6L: RETURN _N6L()
        WHEN #_7L: RETURN _N7L()
        OTHERWISE:PRINT "INVALID NOTE", N
    ENDCASE
    STOP
END

'Play vibrates the left and right vibrators of controller N with
'frequencies FL, FR(hz) and volume VL, VR for a duration T(1/10 sec).
DEF PLAY(FL,VL,FR,VR,T)
   VAR AMP=127*#VOLUME/100
   VAR AMPL=AMP*VL/10
   'BUG? The vibration won't stop vibrating(it vibrates very slightly)
   'if frequency is 0 but amplitude is greater than 0
   IF FL<=0 THEN AMPL=0
   VAR AMPR=AMP*VR/10
   IF FR<=0 THEN AMPR=0

   'Left vibrator
   IF FL>=#MIDDLE_C THEN
    'Use the high freq band
    VIBRATE 1,0,0,0,FL,AMPL
   ELSE
    'Use the low freq band
    VIBRATE 1,0,FL,AMPL,0,0
   ENDIF

    'Right vibrator
   IF FR>=#MIDDLE_C THEN
    VIBRATE 1,1,0,0,FR,AMPR
   ELSE
    VIBRATE 1,1,FR,AMPR,0,0
   ENDIF
   RETURN SLEEP(T*100)
END

'FIN stops the vibration
DEF FIN()
   VIBRATE 1,0
END

'MENU displays the command menu
'Returns 0 if user selected exit or 1 if play
DEF MENU()
    LOCATE 9, 1
    PRINT "MADE WITHOUT SOYORIN BY 6543x1"
    LOCATE 13, 3
    PRINT "               v1.0.0"
    LOCATE 18, 7
    PRINT "↑↓: Change Pitch"
    LOCATE 18, 8
    PRINT "A: Play"
    LOCATE 18, 9
    PRINT "B: Exit" 

    LOOP
        VAR BTN= HANDLE_BUTTONS()
        IF BTN == #B_RRIGHT THEN 'A key
            LOCATE 18,5
            PRINT "Pitch:        "
            LOCATE 18,5
            PRINT "Pitch: "+ STR$(P)
            LOCATE 18,7
            PRINT "↑↓: Change Pitch"
            LOCATE 18,8
            PRINT "B: Exit"
            LOCATE 18,9
            PRINT "              "
            LOCATE 0, 10
            RETURN 1
        ELSEIF BTN == #B_RDOWN THEN 'B key
            RETURN 0
        ENDIF
        LOCATE 18,5
        PRINT "Pitch:        "
        LOCATE 18,5
        PRINT "Pitch: "+ STR$(P)
        VSYNC
    ENDLOOP
END



'SLEEP pauses the execution but processing key(up, down, B) pressing.
'Return 1 when B key is not pressed and timed out
'Return 0 when B key is pressed when sleeping
DEF SLEEP(MILLS)
    VAR T = MILLISEC()
    VAR X, Y
    LOCATE OUT X,Y
    LOOP
        IF MILLISEC()-T>=MILLS THEN
            LOCATE X,Y
            RETURN 1
        ENDIF

        IF HANDLE_BUTTONS()== #B_RDOWN THEN 'B key
            RETURN 0
        ENDIF
        
        VAR _
        STICK 1,0 OUT _,fp 'lSTICK
        FP = FP*-0.8
        LOCATE 18, 5
        PRINT "Pitch:           "
        VAR PV = STR$(P)
        IF FP !=0 THEN
            PV = FORMAT$("%.1F",P+FP)
        ENDIF
        LOCATE 18, 5
        PRINT "Pitch: "+ PV
        VSYNC
    ENDLOOP
END


VAR P_EASTER = 0

'HANDLE_BUTTONS handles button pressing
'Returns #B_RRIGHT(A button), #B_RDOWN(B button) or 0.
DEF HANDLE_BUTTONS()
   IF BUTTON(1, #B_RRIGHT, 1) THEN 'A key
       RETURN #B_RRIGHT
   ELSEIF BUTTON(1, #B_RDOWN, 1) THEN 'B key
       RETURN #B_RDOWN
   ENDIF
   
   'Easter egg:Pressing UP and LSTICK for 3 seconds
   'to remove the pitch limits.
   VAR START = MILLISEC()
   WHILE !P_EASTER && BUTTON(1,#B_LUP, 0) && BUTTON(1,#B_LSTICK, 0)
        IF MILLISEC()-START >=3000 THEN
            P_EASTER = 1
            P_LIMIT_VIBRATE
            BREAK
        ENDIF
        VSYNC
   WEND

   IF BUTTON(1,#B_LUP, 1) THEN 'Up key
       P = P + 1
       IF !P_EASTER && P>#MAX_P THEN
           P_LIMIT_VIBRATE
           P = #MAX_P
       ENDIF
    ENDIF
    IF BUTTON(1,#B_LDOWN, 1) THEN 'Down key
       P = P - 1
       IF !P_EASTER && P<#MIN_P THEN
           P_LIMIT_VIBRATE
           P = #MIN_P
       ENDIF
    ENDIF
    RETURN 0
END

VAR LAST_LIMIT_VIBRATE = 0

'Vibration used when reaching the limits of P.
DEF P_LIMIT_VIBRATE
    IF MILLISEC()-LAST_LIMIT_VIBRATE>500 THEN
        VIBRATE 1, 4
        LAST_LIMIT_VIBRATE = MILLISEC()
    ENDIF
END

posted @ 2025-08-31 19:52  JessieLin  阅读(37)  评论(1)    收藏  举报