'-------------------------------------------------------------------------------------------------------------- ' ' This is a reworked version of my MIDI-player for the canvas-xterm context. ' ' To play sound files, both the 'openal.bac' and 'openal-plugin-mid.bac' contexts are required. They are based ' on OpenAL and libWildMidi. ' ' The GUI itself does not look fancy, but the intention of the program is: ' - test mouse interaction with the canvas, including scroll button ' - experiment with Immediate mode GUI approach ' ' The implementation follows the Immediate-Mode GUI approach, instead of the traditional event-driven approach. ' As a result, your system needs sufficient CPU power to render the GUI. ' ' August 2019, Peter van Eerten - MIT License. ' ' Version 1.0: -initial release '--------------------------------------------------------------------------------------------------------------- INCLUDE canvas-xterm INCLUDE openal INCLUDE openal-plugin-mid CONST Scroll_Width = 15 CONST Font_Height = 24 ' Needed for static strings in main loop DEF FN GetAscii(x) = x ' Create configuration directory IF NOT(FILEEXISTS(GETENVIRON$("HOME") & "/.canvas-midiplayer")) THEN MAKEDIR GETENVIRON$("HOME") & "/.canvas-midiplayer" ' Memorize the last directory with MIDI files CONST Song_Config$ = GETENVIRON$("HOME") & "/.canvas-midiplayer/songdir.cfg" ' Initialize OpenAL sound renderer OPENAL_INIT() '--------------------------------------------------------------------------------------------------------------- FUNCTION Draw_Dialog(msg$, x, y, rw, rh) LOCAL length, xpos, ypos, button, state, pressed, pos LOCAL clicked = 0 TYPE static int LOCAL line$ xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) ' Draw the dialog INK(255, 255, 255, 255) SQUARE(x, y, rw, rh, TRUE) INK(0, 0, 100, 255) SQUARE(x, y, rw, rh, FALSE) SCALE(0.8) msg$ = ALIGN$(msg$, rw/8, 2) FOR line$ IN msg$ STEP NL$ TEXT(line$, x-TEXTLEN(line$)/2, y-AMOUNT(msg$, NL$)*12+pos*24) INCR pos NEXT ' Mouse click event handling IF xpos BETWEEN x-rw AND x+rw AND ypos BETWEEN y-rh AND y+rh THEN IF state = 1 THEN IF button = 1 THEN INCR x, 2 INCR y, 2 clicked = TRUE ENDIF ELSE IF clicked = TRUE THEN pressed = TRUE clicked = FALSE ENDIF ENDIF ENDIF ' Draw the button SQUARE(x, y+rh-30, 60, 20, 0) TEXT("Close", x-TEXTLEN("Close")/2, y+rh-30) SCALE(1.0) RETURN pressed ENDFUNCTION '--------------------------------------------------------------------------------------------------------- SUB Draw_Label(caption$, x, y) INK(0,0,100,255) TEXT(caption$, x, y) ENDSUB '--------------------------------------------------------------------------------------------------------- SUB Draw_Frame(caption$, x, y, rw, rh) LOCAL length INK(0,0,100,255) LINE(x-rw, y-rh, x-rw, y+rh) LINE(x-rw, y+rh, x+rw, y+rh) LINE(x+rw, y+rh, x+rw, y-rh) IF LEN(caption$) THEN LINE(x-rw, y-rh, x-rw+10, y-rh) SCALE(0.75) length = TEXTLEN(caption$) TEXT(caption$, x-rw+15, y-rh) SCALE(1.0) LINE(x+rw, y-rh, x-rw+20+length, y-rh) ELSE LINE(x+rw, y-rh, x-rw, y-rh) ENDIF ENDSUB '--------------------------------------------------------------------------------------------------------- FUNCTION Draw_Slider(id, x, y, rw, rh) LOCAL length, xpos, ypos, button, state LOCAL value[16] = {[0 ... 15] = 80} TYPE static int xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) IF button = 1 AND state = 1 AND xpos BETWEEN x-rw AND x-rw+(rw*2*value[id])/100 AND ypos BETWEEN y-rh AND y+rh THEN DECR value[id],2 IF button = 1 AND state = 1 AND xpos BETWEEN x-rw+(rw*2*value[id])/100 AND x+rw AND ypos BETWEEN y-rh AND y+rh THEN INCR value[id],2 IF value[id] > 100 THEN value[id] = 100 IF value[id] < 0 THEN value[id] = 0 INK(0,0,100,255) CIRCLE(x-rw+(rw*2*value[id])/100, y, 15, 15, TRUE) SQUARE(x, y, rw, rh, 0) RETURN value[id] ENDFUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION Draw_Button(id, caption$, x, y, rw, rh) LOCAL length, xpos, ypos, button, state, pressed LOCAL clicked[16] = { 0 } TYPE static int xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) ' Mouse click event handling IF xpos BETWEEN x-rw AND x+rw AND ypos BETWEEN y-rh AND y+rh THEN IF state = 1 THEN IF button = 1 THEN INCR x, 2 INCR y, 2 clicked[id] = TRUE ENDIF ELSE IF clicked[id] = TRUE THEN pressed = TRUE clicked[id] = FALSE ENDIF ENDIF ENDIF ' Draw button here INK(0,0,100,255) SQUARE(x, y, rw, rh, 0) SCALE(1.0) length = TEXTLEN(caption$) TEXT(caption$, x-length/2, y) RETURN pressed ENDFUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION Draw_List$(id, x, y, float rw, float rh) LOCAL length, xpos, ypos, button, state, i, offset, visible, invisible, total, scroll_rh TYPE float LOCAL selected[16] = { 0 } TYPE static int LOCAL clicked[16] = { 0 } TYPE static int LOCAL pos[16] = {[0 ... 15] = 1} TYPE static int LOCAL file$, item$, result$, dir$ LOCAL Up_Event, Down_Event ' Get mouse status xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) ' Obtain dir and file list dir$ = SORT$(WALK$(".", 2, ".*", FALSE, NL$), NL$) file$ = ".." & NL$ & IIF$(LEN(dir$), dir$ & NL$, "") & SORT$(WALK$(".", 1, ".*", FALSE, NL$), NL$) ' Total amount of entries total = AMOUNT(file$, NL$) ' How many visible entries we have in the list visible = FLOOR((2.0*rh)/Font_Height) invisible = total-visible ' Draw the list box INK(0,0,100,255) SQUARE(x, y, rw, rh, FALSE) ' Mouse click event handling IF xpos BETWEEN x-rw AND x+rw-Scroll_Width AND ypos BETWEEN y-rh AND y+rh THEN IF state = 1 THEN IF button = 1 AND clicked[id] = FALSE THEN selected[id] = (ypos-(y-rh))/Font_Height+1 clicked[id] = TRUE ENDIF ELSE IF clicked[id] = TRUE THEN clicked[id] = FALSE ENDIF ENDIF ENDIF ' We need to draw a scrollbar? IF visible < total THEN ' Amount of pixels per entry offset = (2.0*rh)/(invisible+2.0) ' Check scroll button event IF button = 5 AND state = 1 AND xpos BETWEEN x-rw AND x+rw-Scroll_Width AND ypos BETWEEN y-rh AND y+rh THEN Up_Event = TRUE ' Check regular event on the scrollbar IF button = 1 AND state = 1 AND xpos BETWEEN x+rw-Scroll_Width AND x+rw+Scroll_Width AND ypos BETWEEN y-rh+offset*pos[id] AND y+rh THEN Up_Event = TRUE ' Check scroll button event IF button = 4 AND state = 1 AND xpos BETWEEN x-rw AND x+rw-Scroll_Width AND ypos BETWEEN y-rh AND y+rh THEN Down_Event = TRUE ' Check regular event on the scrollbar IF button = 1 AND state = 1 AND xpos BETWEEN x+rw-Scroll_Width AND x+rw+Scroll_Width AND ypos BETWEEN y-rh+offset*pos[id] AND y-rh THEN Down_Event = TRUE ' Process the scroll event IF Up_Event THEN INCR pos[id] IF pos[id] > total-visible+1 THEN pos[id] = total-visible+1 ELSE DECR selected[id] ENDIF ELIF Down_Event THEN DECR pos[id] IF pos[id] < 1 THEN pos[id] = 1 ELSE INCR selected[id] ENDIF ENDIF ' Draw the scrollbar LINE(x+rw-Scroll_Width, y-rh, x+rw-Scroll_Width, y+rh) SQUARE(x+rw-Scroll_Width/2, y-rh+offset*pos[id], Scroll_Width/2, offset, TRUE) ENDIF ' Put the text into the list FOR i = 1 TO visible item$ = TOKEN$(file$, i+pos[id]-1, NL$) SCALE(0.75) WHILE TEXTLEN(TOASCII$(item$)) > 2*rw-Scroll_Width item$ = LEFT$(item$, LEN(item$)-1) WEND TEXT(TOASCII$(item$), x-rw+1, y-rh-10+i*Font_Height) SCALE(1.0) IF selected[id] BETWEEN 1 AND visible AND i = selected[id] AND LEN(item$) THEN SQUARE(x-Scroll_Width/2, y-rh-10+i*Font_Height, rw-Scroll_Width/2-1, Font_Height/2, FALSE) NEXT ' Determine selection to return IF selected[id] THEN result$ = TOKEN$(file$, pos[id]+selected[id]-1, NL$) ' If result is a directory change working dir IF LEN(result$) AND FILETYPE(result$) = 2 THEN CHANGEDIR result$ pos[id] = 1 selected[id] = 0 ENDIF RETURN result$ ENDFUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION Get_Next$(title$) LOCAL dir$, file$, next$ ' Obtain dir and file list dir$ = SORT$(WALK$(".", 2, ".*", FALSE, NL$), NL$) file$ = ".." & NL$ & IIF$(LEN(dir$), dir$ & NL$, "") & SORT$(WALK$(".", 1, ".*", FALSE, NL$), NL$) idx = ISTOKEN(file$, BASENAME$(title$), NL$) INCR idx IF idx <= AMOUNT(file$, NL$) THEN next$ = TOKEN$(file$, idx, NL$) RETURN next$ ENDFUNCTION '--------------------------------------------------------------------------------------------------------- SUB Main_Loop LOCAL song[256] = { 0 } TYPE static char LOCAL song_dir[1024] = { 0 } TYPE static char LOCAL Current_Song = 0 TYPE static int LOCAL automatic = 0, paused = 0, nosong = 0 TYPE static int LOCAL song$, dir$ LOCAL volume ' Set background color INK(211,211,211,255) CLS ' Set default dirs IF song_dir[0] = 0 THEN IF FILEEXISTS(Song_Config$) THEN MAP CHOP$(LOAD$(Song_Config$)) BY GetAscii TO song_dir SIZE LEN(CHOP$(LOAD$(Song_Config$))) ELSE song_dir[0] = 46 ENDIF ENDIF ' Handle list for Midi file CHANGEDIR song_dir song$ = CURDIR$ & "/" & Draw_List$(1, 380, 50+4*Font_Height+5, 370, 4*Font_Height+5) ' Memorize path MAP CURDIR$ BY GetAscii TO song_dir SIZE LEN(CURDIR$) song_dir[LEN(CURDIR$)] = 0 ' If a MIDI file: memorize by putting characters into static array using MAP IF automatic = 0 AND LCASE$(RIGHT$(song$, 4)) = ".mid" THEN MAP song$ BY GetAscii TO song SIZE LEN(song$) song[LEN(song$)] = 0 nosong = 0 ENDIF Draw_Label("Current: ", 10, 30) SCALE(0.75) Draw_Label(BASENAME$(song), 40+TEXTLEN("Current: "), 31) SCALE(1.0) ' Volume slider Draw_Frame("Volume", 145, 330, 135, 45) volume = Draw_Slider(1, 145, 340, 110, 5) ' Draw controles Draw_Frame("Controls", 530, 330, 220, 45) IF Draw_Button(1, "Play", 390, 340, 60, 25) THEN IF Current_Song AND MID_BUSY(Current_Song) THEN MID_STOP(Current_Song) MID_CLOSE(Current_Song) ENDIF IF NOT(LEN(song)) THEN nosong = 1 ELSE Current_Song = MID_OPEN(song) MID_PLAY(Current_Song) automatic = 0 ENDIF ENDIF IF Draw_Button(2, "Pause", 530, 340, 60, 25) THEN IF Current_Song THEN IF MID_BUSY(Current_Song) THEN MID_PAUSE(Current_Song) paused = 1 ELSE MID_PLAY(Current_Song) paused = 0 ENDIF ENDIF ENDIF ' Try to start next song IF Current_Song AND NOT(MID_BUSY(Current_Song)) AND NOT(paused) THEN MID_CLOSE(Current_Song) Current_Song = 0 song$ = Get_Next$(song) IF LEN(song$) THEN MAP song$ BY GetAscii TO song SIZE LEN(song$) song[LEN(song$)] = 0 Current_Song = MID_OPEN(song) MID_PLAY(Current_Song) automatic = 1 ELSE automatic = 0 ENDIF ENDIF ' Quit button IF Draw_Button(3, "Quit", 670, 340, 60, 25) THEN IF Current_Song THEN MID_STOP(Current_Song) MID_CLOSE(Current_Song) ENDIF MID_FREE() OPENAL_FREE dir$ = song_dir SAVE dir$ TO Song_Config$ QUIT ENDIF ' Error if no song is selected IF nosong THEN nosong = NOT(Draw_Dialog("Select the MIDI file you like to play!", WIDTH/2, HEIGHT/2, 150, 100)) ' Set volume IF Current_Song THEN MID_VOLUME(Current_Song, volume/100.0) ' SYN events: Fill sound buffers IF Current_Song AND MID_BUSY(Current_Song) THEN MID_UPDATE(Current_Song) ENDSUB '--------------------------------------------------------------------------------------------------------- WINDOW("The Canvas Xterm Midi Player", 760, 390) FONTALIGN(1) CALLBACK(50, Main_Loop) WAITKEY