;****************************************************************
;*								*
;*	Mod2Sample -						*
;*								*
;*		A program to convert a 1 pattern ProTracker	*
;*		module into a sound sample.			*
;*								*
;****************************************************************
;*								*
;*	Program History:					*
;*								*
;*	v1.00	18/12/92	Program created			*
;*	v1.01	20/12/92	Final debugging of main code,	*
;*				sorted out a few bugs in the	*
;*				sample mixing code.		*
;*	v1.02	24/12/92	Created a sound buffer for each	*
;*				track, then mixed the 4 tracks	*
;*				together (samples sound better).*
;*	v1.03	25/12/92	Fixed some stupid bugs, added	*
;*				better memory handeling for the	*
;*				track buffers.			*
;*								*
;****************************************************************

;Possibly the last DevPac2.15/AmigaDOS 1.3 compatible
;code I'll ever write

	include libraries/dos.i
	include libraries/dosextens.i
	include libraries/dos_lib.i
	include exec/exec_lib.i
	include	exec/memory.i

Start:
;The beginning ...
	move.l	d0,-(sp)	;Save d0
	move.l	a0,-(sp)	;and a0
	jsr	OpenLibs	;Open the required libraries
	move.l	(sp)+,a0	;Retrieve a0
	move.l	(sp)+,d0	;and d0
	jsr	ParseInputLine	;Get the input line
	cmp.l	#0,d0		;Any error codes?
	bne	CloseLibs	;Yes, just close DOS now
	jsr	LoadModule	;Load the module etc.
	cmp.l	#0,d0		;Any errors?
	bne	CloseLibs	;YES! Exit.
	jsr	ExamineSTMod	;Check that we have a module
	cmp.l	#0,d0		;Any problems?
	bne	CloseLibs	;Yeah, exit
	jsr	ProcessPattern	;Process the module
	jsr	AddTrack2Sample	;Merge the tracks
	jsr	RemoveWhiteSpace;Get effective length of sample
	jsr	WriteSample	;Write out the buffer
	jsr	FreeModMemory	;Free the module
	jsr	FreeSampleMem	;Free the sample buffer
	jsr	FreeTrackMem	;Free the track buffers
CloseLibs:
	move.l	4,a6		;Get ExecBase
	move.l	DosBase,a1	;The library to close
	jsr	_LVOCloseLibrary(a6);Close DOS
	moveq	#0,d0		;THE return code
	rts			;Exit program

LoadModule:
;Load in the SoundTracker module.  If this routine bombs out, it MUST
;clean up after its self!

;Allocate memory for the FileInfoBlock
	move.l	#0,d1		;Any type of memory
	move.l	#fib_SIZEOF,d0	;Size of a FileInfoBlock
	move.l	4,a6		;ExecBase
	jsr	_LVOAllocMem(a6);Allocate some RAM
	cmp.l	#0,d0		;Did I get some memory?
	beq	NoMemFIB	;No.  Print a message and exit
	move.l	d0,fibAddr	;and save the address

;Now, try and get a lock on the requested module
	move.l	#ModName,d1	;Point to the module name
	move.l	#ACCESS_READ,d2	;How to treat the module
	move.l	DosBase,a6	;Point to DOS
	jsr	_LVOLock(a6)	;Lock the file
	cmp.l	#0,d0		;Any problems?
	beq	LOFileErr	;Yes, exit
	move.l	d0,ModLock	;and save the lock.

;Examine the module for size
	move.l	d0,d1		;Copy our lock (from above)
	move.l	fibAddr,d2	;and the address of our FIB
	jsr	_LVOExamine(a6)	;'Examine' the file
	move.l	fibAddr,a5	;Reference the FIB off a5 ...
	move.l	fib_Size(a5),d0	;... and get the size of the module
	move.l	d0,ModSize	;Save it

;Allocate enough memory to hold the module
	moveq	#0,d1		;Any type of memory
	move.l	4,a6		;ExecBase (NOTE: Size of MemBlock in d0!)
	jsr	_LVOAllocMem(a6);Get the memory
	cmp.l	#0,d0		;Allocate OK?
	beq	ModNoMem	;NO!  Exit
	move.l	d0,ModBlock	;Save the address

;Open the module
	move.l	#ModName,d1	;Point to the name
	move.l	#MODE_OLDFILE,d2;the mode
	move.l	DosBase,a6	;Get DOS
	jsr	_LVOOpen(a6)	;Open the file
	move.l	d0,ModHandle	;Save the file handle
	move.l	d0,d1		;and transfer it

;Read it in (finally)
	move.l	ModBlock,d2	;Where to put the module
	move.l	ModSize,d3	;How big it is
	jsr	_LVORead(a6)	;Read the whole thing in
	cmp.l	#-1,d0		;Any read errors?
	beq	FReadErr	;Yes, exit AND free the ram for the module

;Close file
	move.l	ModHandle,d1	;Which file handle
	jsr	_LVOClose(a6)	;and close it
	move.l	ModLock,d1	;The file lock
	jsr	_LVOUnLock(a6)	;and unlock it

;Free FileInfoBlock
	move.l	fibAddr,a1	;Point to the block to free
	move.l	#fib_SIZEOF,d0	;How many bytes to free
	move.l	4,a6		;ExecBase
	jsr	_LVOFreeMem(a6)	;Free the memory

;Exit LoadModule
	moveq	#0,d0		;No errors
	rts			;so exit clean

;DC's for LoadModule
fibAddr		dc.l	0	;Address for FileInfoBlock
ModLock		dc.l	0	;BCPL lock for DOS (bleah)
ModSize		dc.l	0	;The size of the module
ModBlock	dc.l	0	;Where the module actually is
ModHandle	dc.l	0	;The modules' FileHandle

NoMemFIB:
	move.l	#FIBNoMem,d2	;Point to the message
	move.l	#FIBNMLen-FIBNoMem,d3;get length
	jsr	WriteMessage	;Print the message
	moveq	#1,d0		;Set error code
	rts			;Exit to Main

FIBNoMem: dc.b	'ERROR: No memory for FileInfoBlock',10
FIBNMLen: dc.b 	0

	even

NoMemMod:
	move.l	#ModNoMem,d2	;Point to the message
	move.l	#MNMLen-ModNoMem,d3;get length
	jsr	WriteMessage	;Print the message
	moveq	#1,d0		;Set error code
	rts			;Exit to Main

ModNoMem: dc.b	'ERROR: Not enough memory for module',10
MNMLen:   dc.b 	0

	even

LOFileErr:
	move.l	#FileErr,d2	;Point to the message
	move.l	#FELen-FileErr,d3;get length
	jsr	WriteMessage	;Print the message
	moveq	#1,d0		;Set error code
	rts			;Exit to Main

FileErr: dc.b	'ERROR: Unable to open or lock module',10
FELen:   dc.b 	0

	even

FReadErr:
	move.l	#FileReadErr,d2	;Point to the message
	move.l	#FRLen-FileReadErr,d3;get length
	jsr	WriteMessage	;Print the message 
	jsr	FreeModMemory	;Free memory used by module
	moveq	#1,d0		;Set error code
	rts			;Exit to Main

FileReadErr: dc.b	'ERROR: Error during read',10
FRLen:   dc.b 	0

	even

ErNoSMem:
	move.l	#NoSmpMem,d2	;Point to the message
	move.l	#NSMLen-NoSmpMem,d3;get length
	jsr	WriteMessage	;Print the message
	jsr	FreeModMemory	;Free memory used by module
	moveq	#1,d0		;Set error code
	rts			;Exit to Main

NoSmpMem: dc.b	'ERROR: Not enough memory to make sample',10
NSMLen:   dc.b 	0

	even

ErNoTMem:
	move.l	#NoTrkMem,d2	;Point to the message
	move.l	#NTMLen-NoTrkMem,d3;get length
	jsr	WriteMessage	;Print the message
	jsr	FreeModMemory	;Free memory used by module
	jsr	FreeSampleMem	;Free the memory needed by the sample
	moveq	#1,d0		;Set error code
	rts			;Exit to Main

NoTrkMem: dc.b	'ERROR: Not enough memory for tracks (256k req''d)',10
NTMLen:   dc.b 	0

	even

FreeModMemory:
;Free the memory used by the module
	move.l	4,a6		;Get ExecBase
	move.l	ModBlock,a1	;the address of the module
	move.l	ModSize,d0	;and the size
	jsr	_LVOFreeMem(a6)	;Free the memory
	rts			;Exit

SvSmp	dc.b	'Saving sample ... '
SSLen	dc.b	0
	even

DMess	dc.b	'Done',10
DMLen	dc.b	0
	even

WriteSample:
;Writes the sample buffer out to disk

	move.l	#SvSmp,d2	;Point to the Saving message
	move.l	#SSLen-SvSmp,d3	;and its length
	jsr	WriteMessage	;Print it

;Open a new file on the disk
	move.l	#SampleName,d1	;The name of the file
	move.l	#MODE_NEWFILE,d2;we will open a NEW file
	move.l	DosBase,a6	;Get the DOS
	jsr	_LVOOpen(a6)	;Open this file!
	move.l	d0,SamHandle	;Save the file handle
	move.l	d0,d1		;and move it into d1 as well

;Actually write the sample out
	move.l	SampleAddr,d2	;Where we've put the sample¸
	move.l	SampleLen,d3	;How long the sample is
	jsr	_LVOWrite(a6)	;Write out the data

;Close the file
	move.l	SamHandle,d1	;Get back my file handle
	jsr	_LVOClose(a6)	;Close the file

	move.l	#DMess,d2	;Point to the Saving message
	move.l	#DMLen-DMess,d3	;and its length
	jsr	WriteMessage	;Print it

;Exit WriteSample
	rts

;DC's for WriteSample
SamHandle	dc.l	0	;The samples' FileHandle

ParseInputLine:
;Extract the source module name and the output sample name

;Quick check for no arguments
	cmp.l	#0,d0		;d0= number of characters on command line
	beq	ShowUsage	;Tell user how to used program

;Null terminate input string
	clr.b	-1(a0,d0)	;Quick and dirty way
	move.l	a0,CmdLine	;Save address of Comand Line args

;Load module name into buffer
	move.l	#ModName,a1	;a1 points to buffer

MoveModName:
	move.b	(a0)+,d0	;Get a byte
	cmp.b	#0,d0		;A null byte?
	beq	ShowUsage	;Yes, error in line!
	cmp.b	#32,d0		;A space?
	beq	TermModName	;Yes, terminate name
	move.b	d0,(a1)+	;Save the byte
	bra	MoveModName	;and get another

TermModName:
	move.b	#0,(a1)		;Null terminate the module name

;Load sample name into buffer
	move.l	#SampleName,a1	;a1 points to buffer

MoveSampleName:
	move.b	(a0)+,d0	;Get a byte
	cmp.b	#0,d0		;The null byte?
	beq	TermSampleName	;Yes, end sample name
	move.b	d0,(a1)+	;Write the byte
	bra	MoveSampleName	;and get another

TermSampleName:
	move.b	#0,(a1)		;Null terminate name
	moveq	#0,d0		;Set a return code
	rts			;and exit.

;DC's for ParseInputLine
ModName		dcb.b	80,0	;80 characters for source name
SampleName	dcb.b	80,0	;80 characters for sample name
CmdLine		dc.l	0	;Address of input line

ShowUsage:
;Shows usage of this program

	move.l	#Usage,d2	;Point to text
	move.l	#UseLen-Usage,d3;Number of characters
	jsr	WriteMessage	;Write it
	moveq	#1,d0		;Set a fail code
	rts			;Exit

Usage:	dc.b	'USAGE: Mod2Sample <module name> <sample name>',10
UseLen	dc.b	0
	even
OutHandle	dc.l	0	;Output handle

WriteMessage:
	move.l	OutHandle,d1	;Get the output file handle
	move.l	DosBase,a6	;And DOS
	jsr	_LVOWrite(a6)	;Write the message
	rts			;and exit

ExamineSTMod:
;Examine the ST module, extract and build a table of data

;Is it a 31 instrument ProTracker module?
	move.l	ModBlock,a1	;Get address of module
	move.l	MK,d0		;Get the letters "M.K." into d0
	move.l	1080(a1),d1	;Get ^^^ from the module
	cmp.l	d0,d1		;Compare
	beq	ExtractData	;OK - get the inst. data

;This is NOT a protracker module!  Inform user
	move.l	#FError,d2	;Address of message
	move.l	#FLen-FError,d3	;Length
	jsr	WriteMessage	;Print it
	moveq	#1,d0		;Set error code
	rts			;And exit

FError:	dc.b	'ERROR: File is not a ProTracker2.1 module',10
FLen	dc.b 	0
	even
MK:	dc.b	'M.K.'		;Code for a 31 instr module

OpenLibs:
;Open required libraries etc

	move.l	4,a6		;ExecBase
	lea	DosName,a1	;Point to DOS name
	moveq	#0,d0		;any version will do (for now)
	jsr	_LVOOpenLibrary(a6);Open it
	move.l	d0,DosBase	;Save it

;Get the output file handle and save
	move.l	DosBase,a6	;Get DOS
	jsr	_LVOOutput(a6)	;and the file handle
	move.l	d0,OutHandle	;Save it
	rts			;And exit

DosName	dc.b	'dos.library',0
	even
DosBase	dc.l	0

ExtractData:
;Build the table

;Determine start of the samples
	move.l	ModBlock,a1	;Get the address of the module
	move.b	950(a1),d0	;and the number of patterns
	cmp.b	#1,d0		;Only 1 pattern?
	bne	TooManyPatterns	;NO! Bomb out and inform user
	adda.l	#2108,a1	;Point to the actual samples

;Fill in the data table
	move.l	ModBlock,a2	;a2= address of module
	adda.l	#42,a2		;a2 now is the module sample data
	move.l	#SampleData,a3	;a3 is address of our table
	adda.l	#8,a3		;Correct for Sample 0
	move.l	#31,d0		;31 instruments to process

InstDataLoop:
	moveq	#0,d1		;Clear d1
	move.w	(a2),d1		;Get the sample length in words
	lsl.l	#1,d1		;Multiply by 2 (to get length in bytes)
	move.l	a1,(a3)+	;Save start address
	move.l	d1,(a3)+	;and the length
	adda.l	d1,a1		;Get the address of NEXT sample
	adda.l	#30,a2		;and address of next sample data
	subi.l	#1,d0
	cmpi.l	#0,d0
	bne	InstDataLoop	;Process the next instrument
	moveq	#0,d0		;Set return code
	rts			;Done, so exit.

TooManyPatterns:
	move.l	#PattErr,d2	;Address of message
	move.l	#PELen-PattErr,d3;Length
	jsr	WriteMessage	;Write it
	jsr	FreeModMemory	;Free the module
	moveq	#1,d0		;Set a return code
	rts			;and exit

PattErr	dc.b	'ERROR: Number of patterns exceeds 1',10
PELen	dc.b	0
	even

ProcOK	dc.b	'Processing module "',0
POLen	dc.b	0
	even

NoFCom	dc.b	'WARNING: No Fxx command, F06 assumed',10
NFCLen	dc.b	0
	even

;This is the SampleData storage area.
;FORMAT:	Sample1Start	dc.l	0
;		Sample1Length	dc.l	0
;		  :   :  :       :      :
;		Sample31Start	dc.l	0
;		Sample31Length	dc.l	0
SampleData: dcb.l	64,0	;Allocatee 64 longs

	even
CR:	dc.b	'"',10		;A quote and a CR

ProcessPattern:
;Get all the data out of the pattern, process and build sample

;Get speed of pattern
	move.l	ModBlock,a1	;Get address of module
	adda.l	#1084,a1	;Point a1 to pattern data
	move.l	(a1),d0		;Get 1st note of 1st channel
	jsr	GetNoteData	;Process the note
	cmp.w	#$0f,d1		;IS there an Fxx command?
	beq	CalcSpeed	;Yes, work it out

	move.l	#NoFCom,d2	;Point to a message
	move.l	#NFCLen-NoFCom,d3;and its length
	jsr	WriteMessage	;Print it
	bra	ScanPattern	;continue...

CalcSpeed:
;Actual speed in d2, calculate ByteSkip
;(ByteSkip = 170 bytes per position per unit of song speed)
	mulu.w	#170,d2		;d2 is ByteSkip value
	move.l	d2,ByteSkip	;Store it

ScanPattern:
;Allocate 64k for Sample
	move.l	#MEMF_CLEAR|MEMF_PUBLIC,d1;Any sort of ram, zeroed
	move.l	#$10000,d0	;64k required
	move.l	4,a6		;ExecBase
	jsr	_LVOAllocMem(a6);Allocate the memory
	cmp.l	#0,d0		;Did we get it?
	beq	ErNoSMem	;NO!  Exit
	move.l	d0,SampleAddr	;Save the address

;Allocate 64k for each track (256k in total)
	move.l	#MEMF_CLEAR|MEMF_PUBLIC,d1;Any sort of ram, zeroed (again)
	move.l	#$40000,d0	;256k required (!!!)
	jsr	_LVOAllocMem(a6);Try to allocate the memory
	cmp.l	#0,d0		;Did we get it?
	beq	ErNoTMem	;NO!  Exit
	move.l	d0,TrackAddr	;Save the address

	move.l	#ProcOK,d2	;Point to "Processing"  message
	move.l	#POLen-ProcOK,d3;and its length
	jsr	WriteMessage	;Write it
	move.l	ModBlock,d2	;Point to name of module
	move.l	#20,d3		;Its length
	jsr	WriteMessage	;Print it
	move.l	#CR,d2		;Point to a Carriage Return/LF
	move.l	#2,d3		;Its length
	jsr	WriteMessage	;And do it

;Go thru the pattern on a note by note basis
	move.l	ModBlock,a0	;Start of module
	adda.l	#1084,a0	;Point to the patterns
	move.l	#0,d6		;Position counter
	move.l	#0,d3		;Current position (bytes) in a track

PosLoop:
	move.l	#0,d5		;Track counter
NoteLoop:
	move.l	(a0)+,d0	;Get a note
	jsr	GetNoteData	;Process it
	cmp.b	#0,d0		;Is a note triggered?
	beq	NextChannel	;No, so do next channel
	jsr	AddSampleData	;Add the current sample to the main sample
NextChannel:
	addi.l	#1,d5
	cmpi.l	#4,d5		;All tracks (0-3) done?
	bne	NoteLoop	;No, do next note

	move.l	ByteSkip,d5	;Prepare for next position
	add.l	d5,d3		;Point to it
	addi.l	#1,d6
	cmpi.l	#64,d6		;All 64 positions done?
	bne	PosLoop		;Process next position

	rts			;Done!  Exit

ByteSkip	dc.l	1020	;Default ByteSkip
SampleAddr	dc.l	0
SampleLen	dc.l	0
TrackAddr	dc.l	0

GetNoteData
;Get the note data
;FORMAT:	GetNoteData(note)
;			     d0
;Returns:	d0 = Sample triggered
;		d1 = Command
;		d2 = Command Data

	move.l	d5,-(sp)	;Save d5
	move.l	d0,d5		;Save the note

;Get Command data
	moveq	#0,d2		;Clear d2
	move.b	d0,d2		;move effect data

;Get Command
	move.l	d5,d0		;Just in case
	andi.w	#$0f00,d0	;Mask out all but command
	lsr.w	#8,d0		;Shift it over
	moveq	#0,d1		;Clear d1
	move.w	d0,d1		;Move effect command

;Get instrument number
	move.l	d5,d0		;Restore data
	andi.l	#$f000f000,d0	;Mask out all but sample number
	lsr.w	#8,d0		;
	lsr.w	#4,d0		;Move lower 4 bits into position
	move.l	d0,d5		;Copy
	swap	d0		;Exchange upper and lower word
	lsr.w	#8,d0		;Move upper 4 bits into position
	add.w	d5,d0		;Create sample number
	move.l	(sp)+,d5	;restore d5
	andi.l	#$000000ff,d0	;Mask out any garbage
	rts			;and return

AddSampleData:
;Adds a sample to a buffer
;FORMAT:	AddSampleData(SampleNo,Position,Volume,Track)
;				d0        d3      d2	 d5
;NOTE:  Volume not yet implemented!!!

;Save registers
	movem.l	d0-d7/a0-a6,-(sp);stack 'em

;Get sample data
	lea	SampleData,a4	;Reference off a4
	mulu	#8,d0		;Offset to the required data
	adda.l	d0,a4		;Get my sample data location
	move.l	(a4),a0		;Get address of sample
	move.l	4(a4),d0	;and the number of bytes

;Get what track to write the sample to
	mulu.w	#$1000,d5	;Convert track no to offset
	lsl.l	#4,d5		;Correct for word size of mulu
	move.l	TrackAddr,a1	;Get the address of the track
	adda.l	d5,a1		;Point to the correct track
	adda.l	d3,a1		;Correct for ByteSkip

;Error check!  Ensure that sample doesn't extend beyond 
;the 64k buffer
	move.l	d3,d6		;Copy current position
	add.l	d0,d6		;d6 is now the endpoint of the copy
	cmpi.l	#$10000,d6	;Does d6 extend beyond the track
	ble	MoveInstrument	;No, do the copy.

;At this point, CopyMem will go beyond the end of the track by
;d6 - $10000 bytes.  We now correct for this
	subi.l	#$10000,d6	;Get the number of bytes to NOT copy
	sub.l	d6,d0		;Take this off the copy count
				;and proceed as normal

;Move the sample into the buffer
MoveInstrument:
	move.l	4,a6		;Get ExecBase
	jsr	_LVOCopyMem(a6)	;Copy the instrument

;Restore registers and exit
	movem.l	(sp)+,a0-a6/d0-d7;Unstack the registers
	rts			;Exit

AddTrack2Sample:
;Takes the four tracks created earlier, and merges them into
;the one sample.

;Get addresses
	move.l	SampleAddr,a0	;Dest buffer
	move.l	TrackAddr,a1	;Track 0 data
	move.l	TrackAddr,a2
	adda.l	#$10000,a2	;Track 1 data
	move.l	TrackAddr,a3	
	adda.l	#$20000,a3	;Track 2 data
	move.l	TrackAddr,a4
	adda.l	#$30000,a4	;Track 3 data

	move.l	#0,d0		;Byte counter

;Add each track, byte by byte
AdTrk2Smp:
	move.b	(a1)+,d1	;Get a byte of Track 1
	move.b	(a2)+,d2	;and a byte of Track 2
	add.b	d2,d1		;Mix ...
	move.b	(a3)+,d2	;and get Track 3
	add.b	d2,d1		;Mix again ...
	move.b	(a4)+,d2	;and at last Track 4
	add.b	d2,d1		;Mix for the last time
	move.b	d1,(a0)+	;And place in the Sample buffer
	addi.l	#1,d0		;Increment our counter
	cmpi.l	#$10000,d0	;Done the whole pattern?
	ble	AdTrk2Smp	;No, so get the next byte

	rts			;Exit, a job well done.

RemoveWhiteSpace:
;Returns the useable length of the sample!
	move.l	SampleAddr,a1	;Get address of sample
	move.l	#$10000,d0	;and length
	adda.l	d0,a1		;Point a1 at END of sample

CheckLoop:
	move.b	-(a1),d2	;Get a byte
	cmp.b	#0,d2		;A zero?
	bne	FoundSample	;No - This is the end of the sample.
	subi.l	#1,d0		;decrease the counter
	bra	CheckLoop	;and check again

FoundSample:
	move.l	d0,SampleLen	;Save value
	rts			;Exit

FreeSampleMem
;Frees the memory occupied by the sample
	move.l	SampleAddr,a1	;Address of sample
	move.l	#$10000,d0	;length
	move.l	4,a6		;ExecBase
	jsr	_LVOFreeMem(a6)	;Free it
	rts			;And exit

FreeTrackMem
;Frees the memory occupied by the tracks
	move.l	TrackAddr,a1	;Address of sample
	move.l	#$40000,d0	;length
	move.l	4,a6		;ExecBase
	jsr	_LVOFreeMem(a6)	;Free it
	rts			;And exit

Copyright:
	dc.b	'Module2Sample:  © Neil O''Rourke 1992',0
Version:
	dc.b	'Version: 1.03!',0
;NOTE: Included as data in final code, NEVER PRINTED!

