note
	description: "chocolate doom mus2mid, originally by Ben Ryves"
	license: "[
				Copyright (C) 1993-1996 by id Software, Inc.
				Copyright (C) 2005-2014 Simon Howard
				Copyright (C) 2006 Ben Ryves 2006
				Copyright (C) 2021 Ilgiz Mustafin
		
				This program is free software; you can redistribute it and/or modify
				it under the terms of the GNU General Public License as published by
				the Free Software Foundation; either version 2 of the License, or
				(at your option) any later version.
		
				This program is distributed in the hope that it will be useful,
				but WITHOUT ANY WARRANTY; without even the implied warranty of
				MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
				GNU General Public License for more details.
		
				You should have received a copy of the GNU General Public License along
				with this program; if not, write to the Free Software Foundation, Inc.,
				51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
	]"

class 
	MUS2MID

create 
	make

feature 

	Num_channels: INTEGER_32 = 16

	Midi_percussion_chan: NATURAL_8 = 6

	Mus_percussion_chan: NATURAL_8 = 15
	
feature -- musevent, MUS event codes

	Mus_releasekey: NATURAL_8 = 0

	Mus_presskey: NATURAL_8 = 16

	Mus_pitchwheel: NATURAL_8 = 32

	Mus_systemevent: NATURAL_8 = 48

	Mus_changecontroller: NATURAL_8 = 64

	Mus_scoreend: NATURAL_8 = 96
	
feature -- midievent, MIDI event codes

	Midi_releasekey: NATURAL_8 = 128

	Midi_presskey: NATURAL_8 = 144

	Midi_aftertouchkey: NATURAL_8 = 160

	Midi_changecontroller: NATURAL_8 = 176

	Midi_changepatch: NATURAL_8 = 192

	Midi_aftertouchchannel: NATURAL_8 = 208

	Midi_pitchwheel: NATURAL_8 = 224
	
feature 

	write_midiheader
		do
			output.extend (('M').to_character_8.code.to_natural_8)
			output.extend (('T').to_character_8.code.to_natural_8)
			output.extend (('h').to_character_8.code.to_natural_8)
			output.extend (('d').to_character_8.code.to_natural_8)
			output.extend (0)
			output.extend (0)
			output.extend (0)
			output.extend (6)
			output.extend (0)
			output.extend (0)
			output.extend (0)
			output.extend (1)
			output.extend (0)
			output.extend (70)
			output.extend (('M').to_character_8.code.to_natural_8)
			output.extend (('T').to_character_8.code.to_natural_8)
			output.extend (('r').to_character_8.code.to_natural_8)
			output.extend (('k').to_character_8.code.to_natural_8)
			output.extend (0)
			output.extend (0)
			output.extend (0)
			output.extend (0)
		end

	channelvelocities: ARRAY [NATURAL_8]
			-- Cached channel velocities
		do
			create Result.make_filled (127, 0, 15)
		end

	queuedtime: NATURAL_32
			-- Timestamps between sequences of MUS events

	Controller_map: ARRAY [NATURAL_8]
		once
			create Result.make_filled (0, 0, 15 - 1)
			Result [0] := 0
			Result [1] := 32
			Result [2] := 1
			Result [3] := 7
			Result [4] := 10
			Result [5] := 11
			Result [6] := 91
			Result [7] := 93
			Result [8] := 64
			Result [9] := 67
			Result [10] := 120
			Result [11] := 123
			Result [12] := 126
			Result [13] := 127
			Result [14] := 121
		end

	Channel_map: ARRAY [INTEGER_32]
		once
			create Result.make_filled (-1, 0, Num_channels - 1)
		end
	
feature 

	writetime (a_time: NATURAL_32)
			-- Write timestamp to a MIDI file
		local
			buffer: NATURAL_32
			writeval: NATURAL_8
			done: BOOLEAN
			time: NATURAL_32
		do
			time := a_time
			from
				buffer := time & 127
				time := time |>> 7
			until
				time = 0
			loop
				buffer := buffer |<< 8
				buffer := buffer | ((time & 127) | 128)
				time := time |>> 7
			end
			from
				done := False
			until
				done
			loop
				writeval := buffer.to_natural_8 & 255
				output.extend (writeval)
				if buffer & 128 /= 0 then
					buffer := buffer |>> 8
				else
					queuedtime := 0
					done := True
				end
			end
		end

	writeendtrack
			-- Write the end of track marker
		do
			writetime (queuedtime)
			output.extend (255)
			output.extend (47)
			output.extend (0)
		end

	writepresskey (channel, key, velocity: NATURAL_8)
			-- Write a key press event
		do
			writetime (queuedtime)
			output.extend (Midi_presskey | channel)
			output.extend (key & 127)
			output.extend (velocity & 127)
		end

	writereleasekey (channel, key: NATURAL_8)
			-- Write a key release event
		do
			writetime (queuedtime)
			output.extend (Midi_releasekey | channel)
			output.extend (key & 127)
			output.extend (0)
		end

	writepitchwheel (channel: NATURAL_8; wheel: INTEGER_16)
			-- Write a pitch/bend event
		do
			writetime (queuedtime)
			output.extend (Midi_pitchwheel | channel)
			output.extend ((wheel).to_natural_8 & 127)
			output.extend ((wheel |>> 7).to_natural_8 & 127)
		end

	writechangepatch (channel, patch: NATURAL_8)
			-- Write a patch change event
		do
			writetime (queuedtime)
			output.extend (Midi_changepatch | channel)
			output.extend (patch & 127)
		end

	writechangecontroller_valued (channel, control, value: NATURAL_8)
			-- Write a valued controller change event
		local
			working: NATURAL_8
		do
			writetime (queuedtime)
			output.extend (Midi_changecontroller | channel)
			output.extend (control & 127)
			working := value
			if (working & 128) /= 0 then
				working := 127
			end
			output.extend (working)
		end

	writechangecontroller_valueless (channel, control: NATURAL_8)
			-- Write a valueless controller change event
		do
			writechangecontroller_valued (channel, control, 0)
		end
	
feature 

	allocatemidichannel: NATURAL_8
			-- Allocate a free MIDI channel
		local
			max: INTEGER_32
			i: INTEGER_32
		do
			from
				max := -1
				i := 0
			until
				i >= Num_channels
			loop
				if Channel_map [i] > max then
					max := Channel_map [i]
				end
				i := i + 1
			end
			Result := (max + 1).as_natural_8
			if Result = Midi_percussion_chan then
				Result := Result + 1
			end
		ensure
				Result >= 0
				Result.to_integer_32 < Num_channels
		end

	getmidichannel (mus_channel: INTEGER_32): NATURAL_8
			-- Given a MUS channel number, get the MIDI channel number to use
			-- in the outputted file
		do
			if mus_channel = Mus_percussion_chan.to_integer_32 then
				Result := Midi_percussion_chan
			else
				if Channel_map [mus_channel] = -1 then
					Channel_map [mus_channel] := allocatemidichannel.to_integer_32
					writechangecontroller_valueless (Channel_map [mus_channel].as_natural_8, 123)
				end
				Result := Channel_map [mus_channel].as_natural_8
			end
		end
	
feature 

	readmusheader (header: MUSHEADER)
		do
			header.Id [0] := read_natural_8
			header.Id [1] := read_natural_8
			header.Id [2] := read_natural_8
			header.Id [3] := read_natural_8
			header.scorelength := read_natural_16
			header.scorestart := read_natural_16
			header.primarychannels := read_natural_16
			header.secondarychannels := read_natural_16
			header.instrumentcount := read_natural_16
		end

	mus2mid
			-- Read a MUS file from a stream (musinput) and output a MIDI file to
			-- a stream (midioutput)
		local
			musfileheader: MUSHEADER
			eventdescriptor: NATURAL_8
			hitscoreend: BOOLEAN
			working: NATURAL_8
			timedelay: NATURAL_32
			eventdone: BOOLEAN
			timedone: BOOLEAN
		do
			create musfileheader
			readmusheader (musfileheader)
			pos := musfileheader.scorestart.to_integer_32
			write_midiheader
			from
			until
				hitscoreend
			loop
				from
					eventdone := False
				until
					hitscoreend or eventdone
				loop
					eventdescriptor := read_natural_8
					hitscoreend := read_event (eventdescriptor)
					if (eventdescriptor & 128) /= 0 then
						eventdone := True
					end
				end
				if not hitscoreend then
					from
						timedelay := 0
						timedone := False
					until
						timedone
					loop
						working := read_natural_8
						timedelay := timedelay * 128 + (working & 127).to_natural_32
						if working & 128 = 0 then
							timedone := True
						end
					end
					queuedtime := queuedtime + timedelay
				end
			end
			writeendtrack
			output [19] := (tracksize |>> 24).to_natural_8 & 255
			output [20] := (tracksize |>> 16).to_natural_8 & 255
			output [21] := (tracksize |>> 8).to_natural_8 & 255
			output [22] := tracksize.to_natural_8 & 255
		end

	tracksize: NATURAL_32
		do
			Result := output.count.as_natural_32 - 22
		end

	read_event (eventdescriptor: NATURAL_8): BOOLEAN
			-- Read one event, return true if it was score end
		local
			channel: NATURAL_8
			event: NATURAL_8
			key: NATURAL_8
			controllernumber: NATURAL_8
			controllervalue: NATURAL_8
		do
			channel := getmidichannel (eventdescriptor & 15.to_integer_32)
			event := eventdescriptor & 112
			if event = Mus_releasekey then
				key := read_natural_8
				writereleasekey (channel, key)
			elseif event = Mus_presskey then
				key := read_natural_8
				if key & 128 /= 0 then
					channelvelocities [channel] := read_natural_8 & 127
				end
				writepresskey (channel, key, channelvelocities [channel.to_integer_32])
			elseif event = Mus_pitchwheel then
				key := read_natural_8
				writepitchwheel (channel, key.to_integer_16 * 64)
			elseif event = Mus_systemevent then
				controllernumber := read_natural_8
				check
						controllernumber >= 10 and controllernumber <= 14
				end
				writechangecontroller_valueless (channel, Controller_map [controllernumber.to_integer_32])
			elseif event = Mus_changecontroller then
				controllernumber := read_natural_8
				controllervalue := read_natural_8
				if controllernumber = 0 then
					writechangepatch (channel, controllervalue)
				else
					check
							controllernumber >= 1 and controllernumber <= 9
					end
					writechangecontroller_valued (channel, Controller_map [controllernumber.to_integer_32], controllervalue)
				end
			elseif event = Mus_scoreend then
				Result := True
			else
				{I_MAIN}.i_error ("Unknown event " + event.out)
			end
		end
	
feature 

	make (a_input: MANAGED_POINTER)
		do
			create output.make (0)
			input := a_input
			pos := 0
		end

	fill_output
		do
			mus2mid
		end

	output: ARRAYED_LIST [NATURAL_8]

	input: MANAGED_POINTER
	
feature 

	pos: INTEGER_32

	read_natural_16: NATURAL_16
		do
			Result := input.read_natural_16_le (pos)
			pos := pos + {PLATFORM}.integer_16_bytes
		end

	read_natural_8: NATURAL_8
		do
			Result := input.read_natural_8_le (pos)
			pos := pos + {PLATFORM}.character_8_bytes
		end
	
end -- class MUS2MID

Generated by ISE EiffelStudio