'From Squeak2.8 of 13 June 2000 [latest update: #2345] on 14 January 2001 at 8:50:15 pm'!
"Change Set:		Fronkenshteen
Date:			1 November 2000
Author:			hh

Patches Siren's MIDIPort to use the builtin (enhanced) MIDI primitives."

MIDIioctls at: #sqMIDIDoUseSemaphore put: 13.
MIDIioctls at: #sqMIDIFilterActiveSense put: 14
!

PositionableStream subclass: #MIDIPort
	instanceVariableNames: 'readProcess rData hasDurs hasBuffer interfaces timeOffset readSemaphore readSemaphoreIndex error channelMap defaultInterface inPort driver ports '
	classVariableNames: 'Debug Instance '
	poolDictionaries: 'MIDICommands MIDIioctls GeneralMIDIMap GeneralMIDIDrums '
	category: 'Music-MIDI-IO'!

!MIDIPort methodsFor: 'control commands' stamp: 'hh 1/14/2001 20:38'!
readController: index
	"Read the given controller value."

	| array |
	array := ByteArray new: 1.
	self primReadControllers: inPort from: index to: index into: array.
	^array first! !

!MIDIPort methodsFor: 'control commands' stamp: 'hh 1/14/2001 20:38'!
readControllersFrom: lo to: hi
	"Read the given controller value."

	| array |
	array := ByteArray new: (hi-lo+1).
	self primReadControllers: inPort from: lo to: hi into: array.
	^array! !

!MIDIPort methodsFor: 'open/close' stamp: 'hh 11/1/2000 18:34'!
close
	"Close MIDI--shut the primitive scheduler down."
	"MIDIPort instance close"

	readProcess ifNotNil: [readProcess terminate].
	readProcess release.
	readProcess := nil.
	readSemaphore ifNotNil:
		[Smalltalk unregisterExternalObject: readSemaphore.
		readSemaphore := nil].
	self breakDependents.
	error := nil.
	[self primCloseMIDI]
		on: Error
		do: [ :ex | Transcript cr; show: 'MIDI close failed.,..'; cr]! !

!MIDIPort methodsFor: 'open/close' stamp: 'hh 1/14/2001 16:20'!
open
	"Open the MIDI driver -- start the primitive scheduler up."
	"MIDIPort default"

	| ints |
	self isActive ifTrue: [^0].
	error := 0 asValue.
	rData := OrderedCollection new: 16.	"available data read from MIDI"
	readSemaphore ifNil:
		[readSemaphore := Semaphore new.
		readSemaphoreIndex := Smalltalk registerExternalObject: readSemaphore].
"Create and start the reader process."
	readProcess := [self readLoop] newProcess.
	readProcess priority: Processor userInterruptPriority.
	self filterActiveSense: true.
	readProcess resume.
"Call the primitive."
	ints := self primOpenMIDI: readSemaphoreIndex socket: 0.
	ints = 0
		ifFalse: ["Transcript show: 'Found ', ints printString; cr."
			self loadInterfaceData: ints].
	hasDurs := self hasDurs.
	hasBuffer := self hasBuffer.
	self hasClock
		ifTrue: [timeOffset := Time millisecondClockValue - self getClock]
		ifFalse: [timeOffset := 0].
	^ints! !

!MIDIPort methodsFor: 'read/write' stamp: 'hh 11/1/2000 19:24'!
get: packet
	"Read the data from the receiver into the argument (a MIDIPacket). Answer the number of data bytes read."

	| len data |
	data := ByteArray new: packet data size + 4.
	len := self primReadPacket: packet size: data size data: data.
	len < 0
		ifTrue: [error value: len].
	len = 0
		ifTrue: [^0].
	packet data
		replaceFrom: 1
		to: packet data size
		with: data
		startingAt: 5.
	packet time: (data longAt: 1 bigEndian: false).
	^len-4! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 1/14/2001 16:36'!
eventsAvailable
	"Answer the number of events in the input Q."
	"MIDIPort instance eventsAvailable"

	^self primMIDIIoctl: sqMIDIEventsAvailable do: sqMIDIQuery! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 1/14/2001 16:44'!
filterActiveSense
	"Answer whether port is filtering Active Sense bytes (16rFE) from the input stream."

	^1 == (self primMIDIIoctl: sqMIDIFilterActiveSense do: sqMIDIQuery)! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 01/11/2000 17:17'!
hasBuffer
	"Answer whether the MIDI driver has a time-stamped output buffer."

	^(self primMIDIIoctl: sqMIDIHasBuffer do: sqMIDIQuery) ~= 0! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 01/11/2000 17:17'!
hasClock
	"Answer whether the MIDI driver has its own clock."

	^(self primMIDIIoctl: sqMIDIHasClock do: sqMIDIQuery) ~= 0! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 01/11/2000 17:17'!
hasDurs
	"Answer whether the MIDI driver supports a 1-call note-on/off command."

	^(self primMIDIIoctl: sqMIDIHasDurs do: sqMIDIQuery) ~= 0! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 01/11/2000 17:17'!
hasMIDI
	"Answer whether the MIDI driver is installed."

	^(self primMIDIIoctl: sqMIDIInstalled do: sqMIDIQuery) ~= 0! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 1/14/2001 15:14'!
startMIDIInput
	"Start signalling the read semaphore on MIDI input."

	"flush first?"
	self filterActiveSense: true.
	^self primMIDIIoctl: sqMIDIDoUseSemaphore do: sqMIDITurnOn! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 11/01/2000 16:14'!
stopMIDIInput
	"Stop signalling the read semaphore on MIDI input."

	^self primMIDIIoctl: sqMIDIDoUseSemaphore do: sqMIDITurnOff! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 01/11/2000 17:03'!
version
	"Answer the version of the MIDI driver."

	^self primMIDIIoctl: sqMIDIVersion do: sqMIDIQuery! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/1/2000 18:03'!
ensureDriverOpen
	"Lazily initialize the MIDI primitives adapter."
	| numUniPorts |
	driver ifNil: [driver := MidiPrimTester new].
	"testVersion expects interfaces to be set up early"
	interfaces ifNil:
		[numUniPorts := driver primMIDIGetPortCount.
		self loadInterfaceData: numUniPorts]	"does *not* merge I/O ports"! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/01/2000 16:28'!
ensurePortOpen: portNum
	"If the given port isn't yet open, open it now."

	(ports at: portNum+1) ifNil:
		[driver
			primMIDIOpenPort: portNum
			readSemaIndex: 0
			interfaceClockRate: 0.
		ports at: portNum+1 put: true]! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/1/2000 18:05'!
inputPortNumber
	"Answer the number of the first port that supports input, or nil if none is found."
	| n |
	n := driver primMIDIGetPortCount.
	^(0 to: n-1)
		detect: [:i | ((driver primMIDIGetPortDirectionality: i) bitAnd: 1) = 1]
		ifNone: [nil]! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/01/2000 16:16'!
primCloseMIDI
	"Close down the MIDI driver."

	ports withIndexDo:
		[:port :index |
			port ifNotNil:
				[driver primMIDIClosePort: index-1.
				ports at: index put: nil]]! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/01/2000 16:18'!
primMIDIIoctl: selector do: argument
	"Call the general-purpose ioctl() primitive."

	self ensureDriverOpen.
	^argument = 0
		ifTrue: [driver primMIDIParameterGet: selector]
		ifFalse: [driver primMIDIParameterSet: selector to: (argument bitAnd: 1)]! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/01/2000 16:43'!
primOpenMIDI: theReadSemaphore socket: inputSocket
	"Open the Siren MIDI driver."
	"MIDIPort testOpenClose"
	"MIDIPort cleanUp"
	| n |
	self ensureDriverOpen.
	ports ifNil:
		[n _ driver primMIDIGetPortCount.
		ports _ Array new: n.
		inPort _ self inputPortNumber.
		inPort ifNotNil:
			[self stopMIDIInput.	"don't signal yet"
			driver
				primMIDIOpenPort: inPort
				readSemaIndex: theReadSemaphore
				interfaceClockRate: 0.
			ports at: inPort+1 put: true]].
	"trick >>open to believe that interfaces are set up (which they are)"
	^0! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/01/2000 16:37'!
primPut: buffer size: len on: interface
	"Send the given data out the MIDI driver."

	self ensurePortOpen: interface.
	^driver primMIDIWritePort: interface from: buffer at: 0 "now"! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/1/2000 18:47'!
primPut: buffer size: len on: interface at: time
	"Send the given data out the MIDI driver."

	self ensurePortOpen: interface.
	^driver primMIDIWritePort: interface from: buffer at: time! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 1/14/2001 20:37'!
primReadControllers: portNum from: from to: to into: array
	"Read a range of controller values from the MIDI driver."
	"Write data into the 4th argument; answer the number of bytes read."

	<primitive: 'primitiveMIDIReadControllers' module: 'MIDIPlugin'>
	self primError: 'MIDI read failed.'! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/01/2000 16:31'!
primReadPacket: packet size: size data: data
	"Read a packet from the MIDI driver."
	"Write data into the argument; answer the number of bytes read."

	^driver primMIDIReadPort: inPort into: data! !

!MIDIPort methodsFor: 'primitives' stamp: 'hh 11/1/2000 18:13'!
primitiveGetMIDIInterfaceName: index into: nameString
	"Get the name of the interface with the given index number."

	| name |
	name := driver primMIDIGetPortName: index.
	nameString
		replaceFrom: 1
		to: name size
		with: name
		startingAt: 1.
	^(driver primMIDIGetPortDirectionality: index) - 1! !

!MIDIPort methodsFor: 'ioctl' stamp: 'hh 1/14/2001 15:13'!
filterActiveSense: aBoolean
	"Filter Active Sense bytes (16rFE) from the input stream."

	^self
		primMIDIIoctl: sqMIDIFilterActiveSense
		do: (aBoolean ifTrue: [sqMIDITurnOn] ifFalse: [sqMIDITurnOff])! !

MIDIPort removeSelector: #primReadControllersFrom:to:into:!