Guidance
指路人
g.yi.org
Software / Reginald / Examples / rxmidi examples / asm.rex

Register 
注册
Search 搜索
首页 
Home Home
Software
Upload

  
/*
GUIBEGIN
WINDOW , 62, 149, 337, 41, POPUP|CAPTION|SYSMENU|MINBOX|MAXBOX|THICK, , MIDI Assembler
	FONT 8, 400, MS Shell Dlg
	TEXT 5, 29, 327, 8, GROUP, , Message, , Click on "Pick file" to convert a text file to MIDI.
	PUSH 145, 4, 40, 14, TABSTOP, , PickButton, ALT "P", &Pick file
DEND
GUIEND
*/

/* This is a REXX version of the MIDI File Assembler. It converts
 * a specially formatted text file (as output by disasm.rex) into
 * a MIDI file. It also uses REXX GUI for the file dialog and
 * main window.
 */

OPTIONS "C_CALL LABELCHECK NOSOURCE"

/* Load REXXGUI.DLL, RXMIDI.DLL, and register all their functions. */
LIBRARY rexxgui, rxmidi

/* Let RxMidi functions raise ERROR condition if they fail. So, we don't have
 * to check the return values from functions such as MIDISetEvent().
 */
midierr = "ERROR"

/* Let Reginald raise SYNTAX condition for any error in a Gui function call. */
guierr = "SYNTAX"
guiheading = 1

/* Create our window, and activate/show it */
guicreatewindow('NORMAL')

/* If the user supplied the MIDI filename when he ran this script, then load/convert it. */
tofilename = ""
fromfilename = ARG(1)
IF fromfilename \== "" THEN convertmidi()

again:
DO FOREVER

	guigetmsg()

	/* None of our handlers below calls GuiWake(). Also, we have no Child Window Layout
	 * scripts. So we omit further checks of GuiObject/GuiSignal.
	 */

	CATCH NOTREADY
		CONDITION('M')
		SIGNAL again

	CATCH ERROR
		CONDITION('M')
		SIGNAL again

	CATCH SYNTAX
		CONDITION('M')
		SIGNAL again

	FINALLY
		guidestroywindow()
END
RETURN





/* Called by Reginald when user clicks on the "Pick file" button. */

wm_click_pickbutton:

/* We call GuiGetMsg() below. If ToFilename isn't "" then, he has
 * clicked the "Pick file" button during a conversion. In that case,
 * we abort the conversion.
 */
IF tofilename \== "" THEN DO
	tofilename = ""
	guiaddctltext("PickButton", "Aborting...")
	RETURN
END

/* Let the user pick the text file. */
DO
	guifile('FromFilename', 'EXISTING', 'Select a text file to convert to MIDI', 'Text files (*.txt) | *.txt | All files (*.*) | *.*')

	CATCH SYNTAX
		IF CONDITION('D') \== "CANCEL" THEN guisay(CONDITION('D'))
		RETURN
END

convertmidi:

/* Create the MIDI file name from the text filename by chopping
 * off any .txt extension and replacing with .MID.
 */
tofilename = EDITNAME(fromfilename, "*.mid", "U")

/* No lines of the text file read yet. */
linenum = 0

DO
	/* Change "Pick file" to "Loading..." */
	guiaddctltext("PickButton", "Loading...")

	/* Open the text file. */
	STREAM(fromfilename, 'C', 'OPEN READ')

	/* Read in the MThd line. */
	DO UNTIL POS("MThd |", line) \== 0 | POS("MThd|", line) \== 0
		linenum = linenum + 1
		line = LINEIN(fromfilename)

		/* If we can't find the MThd, get out of here. */
		CATCH NOTREADY
			guisay("No MThd line!")
			RETURN
	END

	/* Get the format, number of tracks, and division. */
	PARSE VAR line . "Format=" FORMAT "|" "# of Tracks=" tracks "|" "Division=" division

	/* Make sure that we have numeric values for them. If not, bomb out and let caller handle ERROR */
	IF DATATYPE(format) \== 'NUM' THEN DO
		guisay("MThd Format must be numeric!")
		RETURN
	END
	IF DATATYPE(tracks) \== 'NUM' THEN DO
		guisay("# of Tracks must be numeric!")
		RETURN
	END
	IF DATATYPE(division) \== 'NUM' THEN DO
		guisay("MThd Division must be numeric!")
		RETURN
	END

	/* If tracks > 1 then make sure format is 1 or 2 */
	IF tracks > 1 & FORMAT == 0 THEN DO
		guisay("Format 0 can't have more than 1 track!")
		RETURN
	END

	/* Create a new MIDI file in RAM */
	MIDIOpenFile(, division)

	/* Do all tracks */
	DO tracknum = 1 TO tracks

		/* Find the next track heading. */
		DO UNTIL POS("Track #", line) \== 0
			linenum = linenum + 1
			line = LINEIN(fromfilename)

			CATCH NOTREADY
				guisay('Contains less than' tracks 'tracks!')
				RETURN
		END

		/* Can't omit any time components for the first event in this track */
		measure = ""
		beat = ""
		clock = ""

		/* Default note pitch/velocity, MIDI channel, Poly/Aftertouch pressure, Controller number,
		 * SMPTE hour/min/sec/frame/subs */
		pitch = 60
		velocity = 0
		midievent.!channel = 0
		press = 0
		ctlnum = 0
		hour = 0
		MIN = 0
		frame = 0
		sec = 0
		subs = 0

		/* We need to parse for the first event line */
		PARSE = 1

		/* Check if the CLOSE BOX has been clicked. We do this only at the beginning of
		 * each track being disassembled. We could put it inside the DO UNTIL loop below
		 * to allow a quicker response to the user wanting to abort (ie, abort during
		 * writing out a track), but that can slow down the loop too.
		 */
		guigetmsg('CLEAR')
		IF tofilename == "" THEN RETURN

		/* Do all events until an "End of Track" event. */
		DO UNTIL midievent.!type == 47

			/* Get the next event line. */
			IF PARSE == 1 THEN DO
				DO UNTIL POS("|", line) \== 0
					linenum = linenum + 1
					line = LINEIN(fromfilename)
				END

				CATCH NOTREADY
					guisay('Missing End of Track event at line' linenum)
					RETURN
			END
			PARSE = 1

			/* Replace TABs with spaces. */
			line = TRANSLATE(line, ' ', '09'x)

			/* Break off the time, event type, and any remaining text. */
			PARSE VAR line data '|' type '|' remaining

			/* Break off Measure:Beat:Clock. */
			PARSE VAR data midievent.!measure ':' midievent.!beat ':' midievent.!clock

			midievent.!measure = STRIP(midievent.!measure)
			midievent.!beat = STRIP(midievent.!beat)
			midievent.!clock = STRIP(midievent.!clock)

			/* If any time component was omitted, use the previous event's component. */
			IF midievent.!clock == "" THEN DO
				midievent.!clock = midievent.!beat
				midievent.!beat = midievent.!measure
				midievent.!measure = measure
			END
			IF midievent.!clock == "" THEN DO
				midievent.!clock = midievent.!beat
				midievent.!beat = beat
			END
			IF midievent.!clock == "" THEN midievent.!clock = clock

			/* Save them for the next time through. */
			clock = midievent.!clock
			beat = midievent.!beat
			measure = midievent.!measure

			/* Now make sure that we have all 3 components. */
			IF clock == "" | beat == "" | measure == "" THEN
				RAISE ERROR 100 DESCRIPTION 'Missing time component'

			/* Make sure that we have an event type. */
			IF type = "" THEN
				RAISE ERROR 100 DESCRIPTION 'Missing event type'

			/* Convert to a status if the type was specified as an event name. */
			IF DATATYPE(type) \== 'NUM' THEN midievent.!type = MIDIEventProp(, type)

			/* Event type was specified as a status. */
			ELSE midievent.!type = type

			/* An event type with a MIDI channel? */
			IF MIDIEventProp('CHAN', midievent.!type) == "YES" THEN DO

				/* Is there a channel specified? If so, break it off. Otherwise,
				 * just use the previous value of 'chan'. MIDISetEvent() will error
				 * check it for a numeric value from 1 to 16.
				 */
				IF POS("chan=", remaining) \= 0 THEN PARSE VAR remaining 'chan=' midievent.!channel .

				/* An event type with a pitch (ie, Notes and Aftertouch)? */
				IF MIDIEventProp('FREQ', midievent.!type) == "YES" THEN DO

					/* Is there a pitch specified? If so, get it. Otherwise,
					 * just use the previous value of 'pitch'.
					 */
					offset = POS("pitch=", remaining)
					IF offset \== 0 THEN DO

						/* Break off the pitch. */
						pitch = SUBSTR(remaining, offset + 6)
						offset = POS("|", pitch)
						IF offset > 1 THEN pitch = LEFT(pitch, offset - 1)

						/* If he specified it as a note name, convert to a note number. */
						IF DATATYPE(pitch) \== 'NUM' THEN pitch = MIDINoteNum(pitch)
					END

					/* Store pitch as Data1 */
					midievent.!data1 = pitch

					/* If it's a note event type, get the velocity */
					IF MIDIEventProp('NOTE', midievent.!type) == "YES" THEN DO

						/* Is there a velocity specified? If so, get it. Otherwise,
						 * just use the previous value of 'velocity'
						 */
						IF POS("vol=", remaining) \= 0 THEN DO

							/* Break off the pitch */
							PARSE VAR remaining 'vol=' velocity .

							/* Store velocity as Data2 */
							midievent.!data2 = velocity
						END

						/* velocity not specified */
						ELSE DO
							/* If he specified an "(Off) Note" type, then the velocity is always 0 */
							IF LEFT(type, 4) == "(Off" THEN midievent.!data2 = 0

							/* Otherwise, use previous value of 'velocity' */
							ELSE midievent.!data2 = velocity
						END
					END /* Note Events */

					/* Must be Aftertouch */
					ELSE DO
						/* Is there a press specified? If so, get it. Otherwise,
						 * just use the previous value of 'press'
						 */
						IF POS("press=", remaining) \== 0 THEN

							/* Break off the press */
							PARSE VAR remaining 'press=' press .


						/* Store pressure value as Data2 */
						midievent.!data2 = press

					END /* Aftertouch */

				END /* Events with a pitch */

				/* Non-pitch events (ie, Program, Poly Press, Controller, Pitch Wheel) */
				ELSE SELECT midievent.!type

					/* Controller */
					WHEN 176 THEN DO

						/* Is there a value specified? */
						IF POS("value=", remaining) == 0 THEN RAISE ERROR 100 DESCRIPTION 'Missing value'

						/* Break off the value */
						PARSE VAR remaining 'value=' midievent.!data2 '|' .

						/* Is there a controller specified? */
						offset = POS("contr=", remaining)
						IF offset \== 0 THEN DO

							/* Break off the pitch. */
							ctlnum = SUBSTR(remaining, offset + 6)
							offset = POS("|", ctlnum)
							IF offset > 1 THEN ctlnum = LEFT(ctlnum, offset - 1)

							/* If he specified it as a controller name, convert to a number */
							IF DATATYPE(ctlnum) \== 'NUM' THEN ctlnum = MIDICtlNum(ctlnum)

						END

						/* Store controller number as Data1 */
						midievent.!data1 = ctlnum

					END /* Controller */

					/* Program */
					WHEN 192 THEN DO

						/* Is there a program specified? */
						offset = POS("pgm #=", remaining)
						IF offset == 0 THEN RAISE ERROR 100 DESCRIPTION 'Missing program number'

						/* Break off the program # */
						midievent.!data1 = SUBSTR(remaining, offset + 6)
						offset = POS("|", midievent.!data1)
						IF offset > 1 THEN midievent.!data1 = LEFT(midievent.!data1, offset - 1)

						/* If he specified it as a GM program name, convert to a program number */
						IF DATATYPE(midievent.!data1) \== 'NUM' THEN midievent.!data1 = MIDIGetGMPgm(STRIP(midievent.!data1))

						/* Reference program number from 0 */
						ELSE midievent.!data1 = midievent.!data1 - 1

					END /* Program */

					/* Poly Press */
					WHEN 208 THEN DO
						/* Is there a press specified? If so, get it. Otherwise,
						 * just use the previous value of 'press'
						 */
						IF POS("press=", remaining) \== 0 THEN

							/* Break off the press */
							PARSE VAR remaining 'press=' press .

						/* Store pressure value as Data1 */
						midievent.!data1 = press

					END /* Poly Press */

					/* Pitch Wheel */
					WHEN 224 THEN DO

						/* Is there a bend value specified? */
						IF POS("bend=", remaining) = 0 THEN RAISE ERROR 100 DESCRIPTION 'Missing bend value'

						/* Break off the value */
						PARSE VAR remaining 'bend=' midievent.!data3 .

					END /* Pitch Wheel */

				END /* SELECT for Non-pitch events */

			END /* Events with a channel (ie, voice category) */

			/* Events that do not have a MIDI channel */
			ELSE DO

				/* All System Common and Realtime */
				IF midievent.!type >= 240 THEN DO

					/* All realtime have no data bytes, so only process System Common (except for
					 * Tune, which also has no data byte).
					 */
					IF (midievent.!type < 248 & midievent.!type \= 246) | midievent.!type = 255 | midievent.!type = 257 THEN DO

						/* MTC, Song Position Pointer, Song Select */
						IF midievent.!type <= 243 & midievent.!type \= 240 THEN DO

							/* Break off the data value. MIDISetEvent() will
							 * error check it for a proper numeric value
							 */
							PARSE VAR remaining midievent.!data1 .

						END /* MTC, Song Position Pointer, Song Select. */
	
						/* Sysex (including First Packet, Packet, and Last Packet), and all other undefined MIDI status. */
						ELSE parsetext()

					END /* System Common. */
	
				END /* System Realtime/Common. */

				/* Events other than MIDI Realtime/Common (ie, Meta-Events). */
				ELSE SELECT midievent.!type

					/* Seq # */
					WHEN 0 THEN

						/* Break off the Seq #/Chan #/Port #. MIDISetEvent() will
						 * error check it for a proper numeric value
						 */
						PARSE VAR remaining midievent.!data1 .

					/* Chan # */
					WHEN 32 THEN

						/* Break off the Seq #/Chan #/Port #. MIDISetEvent() will
						 * error check it for a proper numeric value
						 */
						PARSE VAR remaining midievent.!data1 .

					/* Port # */
					WHEN 33 THEN

						/* Break off the Seq #/Chan #/Port #. MIDISetEvent() will
						 * error check it for a proper numeric value
						 */
						PARSE VAR remaining midievent.!data1 .

					/* End of Track */
					WHEN 47 THEN NOP

					/* Tempo */
					WHEN 81 THEN DO

						/* Break off any micros\quarter */
						PARSE VAR remaining 'micros\quarter=' midievent.!data1 .
						IF midievent.!data1 = "" THEN DO

							/* Break off any BPM */
							PARSE VAR remaining 'BPM=' midievent.!data2 .
							IF midievent.!data2 = "" THEN RAISE ERROR 100 DESCRIPTION 'Missing tempo'

						END

					END /* Tempo */

					/* SMPTE */
					WHEN 84 THEN DO

						PARSE VAR remaining 'hour=' midievent.!data1 .
						IF midievent.!data1 = "" THEN midievent.!data1 = hour
						PARSE VAR remaining 'min=' midievent.!data2 .
						IF midievent.!data2 = "" THEN midievent.!data2 = MIN
						PARSE VAR remaining 'sec=' midievent.!data3 .
						IF midievent.!data3 = "" THEN midievent.!data3 = sec
						PARSE VAR remaining 'frame=' midievent.!data4 .
						IF midievent.!data4 = "" THEN midievent.!data4 = frame
						PARSE VAR remaining 'subs=' midievent.!data5 .
						IF midievent.!data5 = "" THEN midievent.!data5 = subs

					END /* SMPTE */

					/* Time Sig */
					WHEN 88 THEN DO

						PARSE VAR remaining midievent.!data1 '/' midievent.!data2 .
						DO UNTIL offset = 0
							offset = POS("|", midievent.!data1)
							IF offset \= 0 THEN PARSE VAR midievent.!data1 '|' midievent.!data1
						END

						IF DATATYPE(midievent.!data1) \= "NUM" | DATATYPE(midievent.!data2) \== "NUM" THEN RAISE ERROR 100 DESCRIPTION 'Missing time signature'

						PARSE VAR remaining 'MIDI-clocks\click=' midievent.!data3 .
						PARSE VAR remaining '32nds\quarter=' midievent.!data4 .
	
					END /* Time Sig */

					/* Key Sig */
					WHEN 89 THEN DO

						offset = POS("|", remaining)
						IF offset \= 0 THEN PARSE VAR remaining remaining '|' .
						midievent.!data3 = remaining

					END /* Key Sig */

					/* Text type of events, Specific */
					OTHERWISE parsetext()

				END /* SELECT */

			END /* Events without a channel */

			/* Add the event */
			MIDISetEvent('INS|TIME|CHAN|DATA', tracknum)

		END /* DO UNTIL type */

		CATCH ERROR
			guisay("MIDI ERROR at line" linenum || ':' CONDITION('D'))
			RETURN

	END /* DO tracknum */

	FINALLY
		/* Close the text file. */
		STREAM(fromfilename, 'C', 'CLOSE')

		/* Save the MIDI file */
		MIDISaveFile(tofilename)

		/* Change back to "Pick file". */
		guiaddctltext("PickButton", "Pick file")

		/* Indicate that the assembly is done. */
		guiaddctltext('Message', 'Done converting' TRANSLATE(tofilename, "/", "\"))

		/* No longer converting a file. */
		tofilename = ""
END

RETURN








/* A function to parse the remaining line of an event that
 * may span several lines, because it has any amount of
 * data characters.
 */
parsetext:
	/* Upon return, we won't need to parse the next event line,
	 * since ParseText ends up parsing one extra line.
	 */
	PARSE = 0

	origlinenum = linenum

	/* No data characters so far. */
	midievent.!data1 = ""

	/* We need to parse all lines until we encounter a new event line. A
	 * new event's line will have a '|' character outside of any < and >
	 * characters.
	 */
	DO FOREVER

		/* Get the next line, stripped of leading/trailing spaces */
		linenum = linenum + 1
		line = STRIP(LINEIN(fromfilename))

		offset2 = POS("<", line)

		/* Check if this is the start of the next event, Is there a '|' in it? */
		offset = POS("|", line)
		IF offset \== 0 THEN DO

			/* Is there a '<' in it, and is this before the '|'? If not,
			 * then we've got a new event line.
			 */
			IF offset2 == 0 | offset2 > offset THEN

				/* Ok, we've now encountered the next event. We
				 * need to finish this text type of event by
				 * returning and letting MIDISetEvent() store it.
				 * We've already accumulated all the data characters
				 * in MIDIEvent.Data1
				 */
				RETURN

		END /* Checking for start of next event. */

		/* More characters for this text type of event. */

		/* If first character is '<' then the text is specified verbatim */
		IF offset2 == 1 THEN DO

			/* Find the closing '>'. If omitted, use the remainder of the line */
			offset = LASTPOS(">", line)
			IF offset == 0 THEN offset = LENGTH(line)
			IF offset < 2 THEN offset = 2

			/* Append the data to previously collected data */
			midievent.!data1 = midievent.!data1 || SUBSTR(line, 2, offset - 2)

		END /* text specified verbatim */

		/* Values are specified for each text character */
		ELSE DO

			/* Break off the first value */
			PARSE VAR line data line
			data = STRIP(data)

			/* No more values, or verbatim translation on end of the line? If so, we're done parsing the line. */
			DO WHILE data \== "" & LEFT(data, 1) \== '<'

				/* Another value to parse */

				/* Is it specified in hexadecimal? */
				IF LEFT(data, 2) == '0x' THEN

					/* Convert value to a character, and append to previously collected data. */
					midievent.!data1 = midievent.!data1 || X2C(DELSTR(data, 1, 2))

				/* Decimal */
				ELSE midievent.!data1 = midievent.!data1 || D2C(data)

				/* Break off the next value */
				PARSE VAR line data line
				data = STRIP(data)

			END /* More values? */

		END /* Values for each text character */

		CATCH NOTREADY
			linenum = origlinenum
			RAISE ERROR 100 DESCRIPTION 'Possible run-on event'

	END /* FOREVER */

	RETURN
掌柜推荐
 
 
 
 
 
 
 
 
 
 
 
 
© Fri 2024-3-29  Guidance Laboratory Inc.
Email:webmaster1g.yi.org Hits:0 Last modified:2010-07-16 20:49:22