ICE, sound and MIDI files

Discussions about SOFTIMAGEs© Interactive Creative Environment©
Bullit
Moderator
Posts: 2621
Joined: 24 May 2012, 07:44

ICE, sound and MIDI files

Post by Bullit » 08 Apr 2013, 19:06

There are some applications that make animations from MIDI files, example http://www.musanim.com/player/

I wonder if there is one that uses an animated virtual keyboard? To connect via texture map to ICE or if there are better alternatives. Ideally would be to connect a key to a sound but i don't think that is possible.

Letterbox
Posts: 391
Joined: 17 Jun 2009, 12:49

Re: ICE, sound and MIDI files

Post by Letterbox » 08 Apr 2013, 19:29

There was something old on xsibase, dont know if archive.org will have it.

Best solution, take a look at ... http://www.sidefx.com/index.php?option= ... &Itemid=66

It might inspire you...or others.

Bullit
Moderator
Posts: 2621
Joined: 24 May 2012, 07:44

Re: ICE, sound and MIDI files

Post by Bullit » 09 Apr 2013, 14:52

Thanks for the feedback. It seems an interesting book.

User avatar
rray
Moderator
Posts: 1810
Joined: 26 Sep 2009, 13:51
Location: Bonn, Germany

Re: ICE, sound and MIDI files

Post by rray » 11 Apr 2013, 23:19

Here's something that might help



Explained here (bottom of page)
softimage resources section updated Sep 26th 2026

User avatar
dwigfor
Posts: 395
Joined: 17 Nov 2009, 16:46

Re: ICE, sound and MIDI files

Post by dwigfor » 11 Apr 2013, 23:54

Interesting approach, using the piano roll as a texturemap. I wonder how huge that image is!! Thanks for the website - I'll have to take a look at the other tutorials he's got on there!

Thanks for the link to the Houdini ebook.. I'm tempted on buying this, cause I've been interested in this for a while.

Anyone else have midi instruments? I've got a really old Roland D5 synth (no power adapter), a really old Roland MT32 sound module (bought that for Sierra Online games!), an old StudioLogic 88 controller (again, no power adapter), a Roland JV 2080 sound module, Peavey 1600 controller (bought for XSI), MOTU 4x4 interface, and Roland Fantom G8 (my baby!). Recently bought an Akai pad controller for Ableton, but I haven't had much time to play with that..

Falam

Re: ICE, sound and MIDI files

Post by Falam » 12 Apr 2013, 01:06

dwigfor wrote:Interesting approach, using the piano roll as a texturemap. I wonder how huge that image is!! Thanks for the website - I'll have to take a look at the other tutorials he's got on there!

Thanks for the link to the Houdini ebook.. I'm tempted on buying this, cause I've been interested in this for a while.

Anyone else have midi instruments? I've got a really old Roland D5 synth (no power adapter), a really old Roland MT32 sound module (bought that for Sierra Online games!), an old StudioLogic 88 controller (again, no power adapter), a Roland JV 2080 sound module, Peavey 1600 controller (bought for XSI), MOTU 4x4 interface, and Roland Fantom G8 (my baby!). Recently bought an Akai pad controller for Ableton, but I haven't had much time to play with that..
You a music man ? :)

User avatar
dwigfor
Posts: 395
Joined: 17 Nov 2009, 16:46

Re: ICE, sound and MIDI files

Post by dwigfor » 12 Apr 2013, 07:17

Just a fun hobby on the side. :-

almoehi
Posts: 1
Joined: 31 Aug 2012, 13:35

Re: ICE, sound and MIDI files

Post by almoehi » 12 Apr 2013, 14:05

check this out:


You can download it here:
3d-pixel.de/softimage/sound/sound.zip
Helpfile is here:
3d-pixel.de/softimage/sound/
Or you click on the ? in the PPG

Letterbox
Posts: 391
Joined: 17 Jun 2009, 12:49

Re: ICE, sound and MIDI files

Post by Letterbox » 12 Apr 2013, 16:00

dwigfor wrote:Interesting approach, using the piano roll as a texturemap. I wonder how huge that image is!!

Thanks for the link to the Houdini ebook.. I'm tempted on buying this, cause I've been interested in this for a while.

Since you've been interested for a while you might consider this alternative approach.

Realize that both sets of key's per note are uniform from an origin, as each key of the same width. Both sets (b/w) follow a physically consistent layout, for all keyboards. Further that each key (on a piano) rotates a fixed limited amount around a pivot where the key connects to lever to the hammer. Thus, no textures, no shapes is a possibility, as long as one realizes this when modeling the item, and it follows a physically correct layout.

Calculating a note's position now becomes effectively translating it's tonal value into moving a physical distance from one end of the keyboard to the required key (for both b/w). Which I believe is what your brain first does when you learn the piano, until the note's position is remembered. Which could translate programmatically into midi_note/object00... etc. Then simply rotating that key (object00) around it's (limited) pivot for the duration of the note. The same principle could be used too for the pedals, if the midi file contains that pedal data (depends on source). The duration of the note is down is too in the midi file (depends on source). In theory it should make the simulated ice tree much simpler.

I suppose too you could build they keyboard with ice modeling, procedurally creating the layout because of it's uniformity, which would aid in the integration and selection, as then you'd (maybe more easily) be able to use the midi note value into an object / Name / number, ie Key01, Key02 mapping table/array.

Further you'd probably need a python script to read the midi data set (which exist) and save it in an ice array for this mapping the simulation stack. If your not sure about that process it's a similar methodology to reading a csv file via python, which is explained somewhere - look in the usual places.

PS I hope you find some power supplies !! Good Luck.

User avatar
dwigfor
Posts: 395
Joined: 17 Nov 2009, 16:46

Re: ICE, sound and MIDI files

Post by dwigfor » 12 Apr 2013, 16:43

Letterbox wrote: I suppose too you could build they keyboard with ice modeling, procedurally creating the layout because of it's uniformity
I might try that. Sounds like a fun achievable project.

Letterbox wrote: Further you'd probably need a python script to read the midi data set (which exist) and save it in an ice array for this mapping the simulation stack. If your not sure about that process it's a similar methodology to reading a csv file via python, which is explained somewhere - look in the usual places.
Yeah, some way to translate standard midi file into a readable format, like xml. And then to somehow load that into SI/ICE. That's where I'm stuck... I did find a python midi project a couple of years ago when I was taking a python class at work, but after sending it to the teacher/coworker, I never heard anything back. Assumed he was busy with other stuff, and uninterested in midi.

Letterbox wrote: PS I hope you find some power supplies !!
I hate those things! :p

Bullit
Moderator
Posts: 2621
Joined: 24 May 2012, 07:44

Re: ICE, sound and MIDI files

Post by Bullit » 12 Apr 2013, 19:46

rray wrote:Here's something that might help

Explained here (bottom of page)
Aha! That is why i asked for an application that read midi to animated keyboard. He made a sequence of images in Propellerhead Reason via screen capture. Seems too convoluted.


dwigfor i found this when i was searching, maybe it will help you:



He has some python files here:
http://www.logicalside.com/MIDI_based_animation_3D

He also made an animation in Swish.

User avatar
dwigfor
Posts: 395
Joined: 17 Nov 2009, 16:46

Re: ICE, sound and MIDI files

Post by dwigfor » 16 Apr 2013, 20:56

Thanks for the link, Bullit. Here's the contents of the included midiparser.py file, if it could help someone bringing midi into SI.

http://www.logicalside.com/sites/defaul ... 20Maya.zip

Code: Select all


# Placed into Public Domain in June 2006 by Sean D. Spencer

# Sean D. Spencer
# sean_don4@lycos.com
# 2/19/2006
# Last Revision: 4/19/2007

# MIDI Parsing Library for Python.

import sys

TRUE = -1
FALSE = 0

class format:
    SingleTrack = 0
    MultipleTracksSync = 1
    MultipleTracksAsync = 2

class voice:
    NoteOff = 0x80
    NoteOn = 0x90
    PolyphonicKeyPressure = 0xA0 # note aftertouch
    ControllerChange = 0xB0
    ProgramChange = 0xC0
    ChannelPressure = 0xD0
    PitchBend = 0xE0

class meta:
    FileMetaEvent = 0xFF
    SMPTEOffsetMetaEvent = 0x54
    SystemExclusive = 0xF0
    SystemExclusivePacket = 0xF7
    SequenceNumber = 0x00
    TextMetaEvent = 0x01
    CopyrightMetaEvent = 0x02
    TrackName = 0x03
    InstrumentName = 0x04
    Lyric = 0x05
    Marker = 0x06
    CuePoint = 0x07
    ChannelPrefix = 0x20
    MidiPort = 0x21
    EndTrack = 0x2F
    SetTempo = 0x51
    TimeSignature = 0x58
    KeySignature = 0x59
    SequencerSpecificMetaEvent = 0x7F

class EventNote:
    def __init__(self):
	self.note_no = None
	self.velocity = None

class EventValue:
    def __init__(self):
	self.type = None
	self.value = None

class EventAmount:
    def __init__(self):
	self.amount = None

class MetaEventKeySignature:
    def __init__(self):
	self.fifths = None
	self.mode = None

class MetaEventTimeSignature:
    def __init__(self):
	self.numerator = None
	self.log_denominator = None
	self.midi_clocks = None
	self.thirty_seconds = None

class MetaEventText:
    def __init__(self):
	self.length = None
	self.text = None

class MetaEventSMPTEOffset:
    def __init__(self):
	self.hour = None
	self.minute = None
	self.second = None
	self.frame = None
	self.sub_frame = None

class MetaValues:
    def __init__(self):
	self.length = None
	self.values = None

def getNumber(str, length):
    # MIDI uses big-endian for everything
    sum = 0
    for i in range(length):
        #sum = (sum *256) + int(str[i])
	sum = (sum << 8) + ord(str[i])
    return sum, str[length:]

def getVariableLengthNumber(str):
    sum = 0
    i = 0
    while 1:
        x = ord(str[i])
        i = i + 1
	# sum = (sum * 127) + (x (mask) 127) # mask off the 7th bit
        sum = (sum << 7) + (x & 0x7F)
	# Is 7th bit clear?
        if not (x & 0x80):
            return sum, str[i:]

def getValues(str, n=16):
    temp = []
    for x in str[:n]:
	temp.append(repr(ord(x)))
    return temp

class File:
    def __init__(self, file):
        self.file = file
        self.format = None
	self.num_tracks = None
	self.division = None
        self.tracks = []

	self.file = open(self.file, 'rb')
	str = self.file.read()
	self.file.close()

	self.read(str)

    def read(self, str):
	assert str[:4] == "MThd"
	str = str[4:]

	length, str = getNumber(str, 4)
	assert length == 6

	self.format, str = getNumber(str, 2)

	self.num_tracks, str = getNumber(str, 2)
	self.division, str = getNumber(str, 2)

	for i in range(self.num_tracks):
	    track = Track(i+1)
	    str = track.read(str)
	    self.tracks.append(track)

class Track:
    def __init__(self, index):
	self.number = index
	self.length = None
	self.events = []

    def read(self, str):
	self.length, str = getNumber(str[4:], 4)
	track_str = str[:self.length]

	prev_absolute = 0
	prev_status = 0

	i = 0
	while track_str:
	    event = Event(self.number, i+1)
	    track_str = event.read(prev_absolute, prev_status, track_str)

	    prev_absolute += event.delta
	    prev_status = event.status
	    self.events.append(event)
	    i += 1

	return str[self.length:]

class Event:
    def __init__(self, track, index):
	self.number = index
	self.type = None
	self.delta = None
	self.absolute = None
	self.status = None
	self.channel = None

    def read(self, prev_time, prev_status, str):
	self.delta, str = getVariableLengthNumber(str)
	self.absolute = prev_time + self.delta

	# use running status?
	if not (ord(str[0]) & 0x80):
	    # squeeze a duplication of the running status into the data string
	    str = prev_status + str

	self.status = str[0]
	self.channel = ord(self.status) & 0xF

	# increment one byte, past the status
	str = str[1:]

	has_channel = has_meta = TRUE

	# handle voice events
	channel_msg = ord(self.status) & 0xF0
	if channel_msg == voice.NoteOn or \
		channel_msg == voice.NoteOff or \
		channel_msg == voice.PolyphonicKeyPressure:
	    self.detail = EventNote()
	    self.detail.note_no = ord(str[0])
	    self.detail.velocity = ord(str[1])
	    str = str[2:]

	elif channel_msg == voice.ControllerChange:
	    self.detail = EventValue()
	    self.detail.type = ord(str[0])
	    self.detail.value = ord(str[1])
	    str = str[2:]

	elif channel_msg == voice.ProgramChange or \
		channel_msg == voice.ChannelPressure:

	    self.detail = EventAmount()
	    self.detail.amount = ord(str[0])
	    str = str[1:]

	elif channel_msg == voice.PitchBend:
	    # Pitch bend uses high accuracy 14 bit unsigned integer.
	    self.detail = EventAmount()
	    self.detail.amount = (ord(str[0]) << 7) | ord(str[1])
	    str = str[2:]

	else: has_channel = FALSE

	# handle meta events
	meta_msg = ord(self.status)
	if meta_msg == meta.FileMetaEvent:

	    meta_msg = type = ord(str[0])
	    length, str = getVariableLengthNumber(str[1:])

	    if type == meta.SetTempo or \
		    type == meta.ChannelPrefix:

		self.detail = EventAmount()
		self.detail.tempo, str  = getNumber(str, length)

	    elif type == meta.KeySignature:
		self.detail = MetaEventKeySignature()
		self.detail.fifths = ord(str[0])

		if ord(str[1]): self.detail.mode = "minor"
		else: self.detail.mode = "major"

		str = str[length:]

	    elif type == meta.TimeSignature:
		self.detail = MetaEventTimeSignature()
		self.detail.numerator = ord(str[0])
		self.detail.log_denominator = ord(str[1])
		self.detail.midi_clocks = ord(str[2])
		self.detail.thirty_seconds = ord(str[3])
		str = str[length:]

	    elif type == meta.TrackName or \
		    type == meta.TextMetaEvent or \
		    type == meta.Lyric or \
		    type == meta.CuePoint or \
		    type == meta.CopyrightMetaEvent:

		self.detail = MetaEventText()
		self.detail.length = length
		self.detail.text = str[:length]
		str = str[length:]

	    elif type == meta.SMPTEOffsetMetaEvent:
		self.detail = MetaEventSMPTEOffset()
		self.detail.hour = ord(str[0])
		self.detail.minute = ord(str[1])
		self.detail.second = ord(str[2])
		self.detail.frame = ord(str[3])
		self.detail.sub_frame = ord(str[4])
		str = str[length:]

	    elif type == meta.EndTrack:
		str = str[length:] # pass on to next track

	    else: has_meta = FALSE

	elif meta_msg == meta.SystemExclusive or \
		meta_msg == meta.SystemExclusivePacket:
	    self.detail = MetaValues()
	    self.detail.length, str = getVariableLengthNumber(str)
	    self.detail.values = getValues(str, self.detail.length)
	    str = str[self.detail.length:]

	else: has_meta = FALSE

	if has_channel:
	    self.type = channel_msg
	elif has_meta:
	    self.type = meta_msg
	else:
	    raise "Unknown event."

	return str


User avatar
dwigfor
Posts: 395
Joined: 17 Nov 2009, 16:46

Re: ICE, sound and MIDI files

Post by dwigfor » 16 Apr 2013, 21:03

I found another script to parse a midi file with Python. I got it to output into the cmd prompt window, but I'm not sure how to use that in SI.

First off, could someone explain how to save the output into a .txt file?

Code: Select all

#!/usr/bin/env python
"""
midi.py -- MIDI classes and parser in Python
Placed into the public domain in December 2001 by Will Ware

Python MIDI classes: meaningful data structures that represent MIDI
events
and other objects. You can read MIDI files to create such objects, or
generate a collection of objects and use them to write a MIDI file.

Helpful MIDI info:
http://crystal.apana.org.au/ghansper/midi_introduction/midi_file_format.html
http://www.argonet.co.uk/users/lenny/midi/mfile.html
"""

import sys, string, types, exceptions

debugflag = 0

def showstr(str, n=16):
    for x in str[:n]:
        print ('%02x' % ord(x)),
    print

def getNumber(str, length):
    # MIDI uses big-endian for everything
    sum = 0
    for i in range(length):
        sum = (sum << 8) + ord(str[i])
    return sum, str[length:]

def getVariableLengthNumber(str):
    sum = 0
    i = 0
    while 1:
        x = ord(str[i])
        i = i + 1
        sum = (sum << 7) + (x & 0x7F)
        if not (x & 0x80):
            return sum, str[i:]

def putNumber(num, length):
    # MIDI uses big-endian for everything
    lst = [ ]
    for i in range(length):
        n = 8 * (length - 1 - i)
        lst.append(chr((num >> n) & 0xFF))
    return string.join(lst, "")

def putVariableLengthNumber(x):
    lst = [ ]
    while 1:
        y, x = x & 0x7F, x >> 7
        lst.append(chr(y + 0x80))
        if x == 0:
            break
    lst.reverse()
    lst[-1] = chr(ord(lst[-1]) & 0x7f)
    return string.join(lst, "")


class EnumException(exceptions.Exception):
    pass

class Enumeration:
    def __init__(self, enumList):
        lookup = { }
        reverseLookup = { }
        i = 0
        uniqueNames = [ ]
        uniqueValues = [ ]
        for x in enumList:
            if type(x) == types.TupleType:
                x, i = x
            if type(x) != types.StringType:
                raise EnumException, "enum name is not a string: " + x
            if type(i) != types.IntType:
                raise EnumException, "enum value is not an integer: " + i
            if x in uniqueNames:
                raise EnumException, "enum name is not unique: " + x
            if i in uniqueValues:
                raise EnumException, "enum value is not unique for " + x
            uniqueNames.append(x)
            uniqueValues.append(i)
            lookup[x] = i
            reverseLookup[i] = x
            i = i + 1
        self.lookup = lookup
        self.reverseLookup = reverseLookup
    def __add__(self, other):
        lst = [ ]
        for k in self.lookup.keys():
            lst.append((k, self.lookup[k]))
        for k in other.lookup.keys():
            lst.append((k, other.lookup[k]))
        return Enumeration(lst)
    def hasattr(self, attr):
        return self.lookup.has_key(attr)
    def has_value(self, attr):
        return self.reverseLookup.has_key(attr)
    def __getattr__(self, attr):
        if not self.lookup.has_key(attr):
            raise AttributeError
        return self.lookup[attr]
    def whatis(self, value):
        return self.reverseLookup[value]


channelVoiceMessages = Enumeration([("NOTE_OFF", 0x80),
                                    ("NOTE_ON", 0x90),
                                    ("POLYPHONIC_KEY_PRESSURE", 0xA0),
                                    ("CONTROLLER_CHANGE", 0xB0),
                                    ("PROGRAM_CHANGE", 0xC0),
                                    ("CHANNEL_KEY_PRESSURE", 0xD0),
                                    ("PITCH_BEND", 0xE0)])

channelModeMessages = Enumeration([("ALL_SOUND_OFF", 0x78),
                                   ("RESET_ALL_CONTROLLERS", 0x79),
                                   ("LOCAL_CONTROL", 0x7A),
                                   ("ALL_NOTES_OFF", 0x7B),
                                   ("OMNI_MODE_OFF", 0x7C),
                                   ("OMNI_MODE_ON", 0x7D),
                                   ("MONO_MODE_ON", 0x7E),
                                   ("POLY_MODE_ON", 0x7F)])

metaEvents = Enumeration([("SEQUENCE_NUMBER", 0x00),
                          ("TEXT_EVENT", 0x01),
                          ("COPYRIGHT_NOTICE", 0x02),
                          ("SEQUENCE_TRACK_NAME", 0x03),
                          ("INSTRUMENT_NAME", 0x04),
                          ("LYRIC", 0x05),
                          ("MARKER", 0x06),
                          ("CUE_POINT", 0x07),
                          ("MIDI_CHANNEL_PREFIX", 0x20),
                          ("MIDI_PORT", 0x21),
                          ("END_OF_TRACK", 0x2F),
                          ("SET_TEMPO", 0x51),
                          ("SMTPE_OFFSET", 0x54),
                          ("TIME_SIGNATURE", 0x58),
                          ("KEY_SIGNATURE", 0x59),
                          ("SEQUENCER_SPECIFIC_META_EVENT", 0x7F)])


# runningStatus appears to want to be an attribute of a MidiTrack. But
# it doesn't seem to do any harm to implement it as a global.
runningStatus = None

class MidiEvent:

    def __init__(self, track):
        self.track = track
        self.time = None
        self.channel = self.pitch = self.velocity = self.data = None

    def __cmp__(self, other):
        # assert self.time != None and other.time != None
        return cmp(self.time, other.time)

    def __repr__(self):
        r = ("<MidiEvent %s, t=%s, track=%s, channel=%s" %
             (self.type,
              repr(self.time),
              self.track.index,
              repr(self.channel)))
        for attrib in ["pitch", "data", "velocity"]:
            if getattr(self, attrib) != None:
                r = r + ", " + attrib + "=" + repr(getattr(self,
attrib))
        return r + ">"

    def read(self, time, str):
        global runningStatus
        self.time = time
        # do we need to use running status?
        if not (ord(str[0]) & 0x80):
            str = runningStatus + str
        runningStatus = x = str[0]
        x = ord(x)
        y = x & 0xF0
        z = ord(str[1])

        if channelVoiceMessages.has_value(y):
            self.channel = (x & 0x0F) + 1
            self.type = channelVoiceMessages.whatis(y)
            if (self.type == "PROGRAM_CHANGE" or
                self.type == "CHANNEL_KEY_PRESSURE"):
                self.data = z
                return str[2:]
            else:
                self.pitch = z
                self.velocity = ord(str[2])
                channel = self.track.channels[self.channel - 1]
                if (self.type == "NOTE_OFF" or
                    (self.velocity == 0 and self.type == "NOTE_ON")):
                    channel.noteOff(self.pitch, self.time)
                elif self.type == "NOTE_ON":
                    channel.noteOn(self.pitch, self.time, self.velocity)
                return str[3:]

        elif y == 0xB0 and channelModeMessages.has_value(z):
            self.channel = (x & 0x0F) + 1
            self.type = channelModeMessages.whatis(z)
            if self.type == "LOCAL_CONTROL":
                self.data = (ord(str[2]) == 0x7F)
            elif self.type == "MONO_MODE_ON":
                self.data = ord(str[2])
            return str[3:]

        elif x == 0xF0 or x == 0xF7:
            self.type = {0xF0: "F0_SYSEX_EVENT",
                         0xF7: "F7_SYSEX_EVENT"}[x]
            length, str = getVariableLengthNumber(str[1:])
            self.data = str[:length]
            return str[length:]

        elif x == 0xFF:
            if not metaEvents.has_value(z):
                print "Unknown meta event: FF %02X" % z
                sys.stdout.flush()
                raise "Unknown midi event type"
            self.type = metaEvents.whatis(z)
            length, str = getVariableLengthNumber(str[2:])
            self.data = str[:length]
            return str[length:]

        raise "Unknown midi event type"

    def write(self):
        sysex_event_dict = {"F0_SYSEX_EVENT": 0xF0,
                            "F7_SYSEX_EVENT": 0xF7}
        if channelVoiceMessages.hasattr(self.type):
            x = chr((self.channel - 1) +
                    getattr(channelVoiceMessages, self.type))
            if (self.type != "PROGRAM_CHANGE" and
                self.type != "CHANNEL_KEY_PRESSURE"):
                data = chr(self.pitch) + chr(self.velocity)
            else:
                data = chr(self.data)
            return x + data

        elif channelModeMessages.hasattr(self.type):
            x = getattr(channelModeMessages, self.type)
            x = (chr(0xB0 + (self.channel - 1)) +
                 chr(x) +
                 chr(self.data))
            return x

        elif sysex_event_dict.has_key(self.type):
            str = chr(sysex_event_dict[self.type])
            str = str + putVariableLengthNumber(len(self.data))
            return str + self.data

        elif metaEvents.hasattr(self.type):
            str = chr(0xFF) + chr(getattr(metaEvents, self.type))
            str = str + putVariableLengthNumber(len(self.data))
            return str + self.data

        else:
            raise "unknown midi event type: " + self.type

"""
register_note() is a hook that can be overloaded from a script that
imports this module. Here is how you might do that, if you wanted to
store the notes as tuples in a list. Including the distinction
between track and channel offers more flexibility in assigning voices.

import midi
notelist = [ ]
def register_note(t, c, p, v, t1, t2):
    notelist.append((t, c, p, v, t1, t2))
midi.register_note = register_note
"""

def register_note(track_index, channel_index, pitch, velocity,
                  keyDownTime, keyUpTime):
    pass


class MidiChannel:

    """A channel (together with a track) provides the continuity
connecting
    a NOTE_ON event with its corresponding NOTE_OFF event. Together,
those
    define the beginning and ending times for a Note."""

    def __init__(self, track, index):
        self.index = index
        self.track = track
        self.pitches = { }

    def __repr__(self):
        return "<MIDI channel %d>" % self.index

    def noteOn(self, pitch, time, velocity):
        self.pitches[pitch] = (time, velocity)

    def noteOff(self, pitch, time):
        if self.pitches.has_key(pitch):
            keyDownTime, velocity = self.pitches[pitch]
            register_note(self.track.index, self.index, pitch, velocity,
                          keyDownTime, time)
            del self.pitches[pitch]
        # The case where the pitch isn't in the dictionary is illegal,
        # I think, but we probably better just ignore it.


class DeltaTime(MidiEvent):

    type = "DeltaTime"

    def read(self, oldstr):
        self.time, newstr = getVariableLengthNumber(oldstr)
        return self.time, newstr

    def write(self):
        str = putVariableLengthNumber(self.time)
        return str


class MidiTrack:

    def __init__(self, index):
        self.index = index
        self.events = [ ]
        self.channels = [ ]
        self.length = 0
        for i in range(16):
            self.channels.append(MidiChannel(self, i+1))

    def read(self, str):
        time = 0
        assert str[:4] == "MTrk"
        length, str = getNumber(str[4:], 4)
        self.length = length
        mystr = str[:length]
        remainder = str[length:]
        while mystr:
            delta_t = DeltaTime(self)
            dt, mystr = delta_t.read(mystr)
            time = time + dt
            self.events.append(delta_t)
            e = MidiEvent(self)
            mystr = e.read(time, mystr)
            self.events.append(e)
        return remainder

    def write(self):
        time = self.events[0].time
        # build str using MidiEvents
        str = ""
        for e in self.events:
            str = str + e.write()
        return "MTrk" + putNumber(len(str), 4) + str

    def __repr__(self):
        r = "<MidiTrack %d -- %d events\n" % (self.index,
len(self.events))
        for e in self.events:
            r = r + "    " + `e` + "\n"
        return r + "  >"


class MidiFile:

    def __init__(self):
        self.file = None
        self.format = 1
        self.tracks = [ ]
        self.ticksPerQuarterNote = None
        self.ticksPerSecond = None

    def open(self, filename, attrib="rb"):
        if filename == None:
            if attrib in ["r", "rb"]:
                self.file = sys.stdin
            else:
                self.file = sys.stdout
        else:
            self.file = open(filename, attrib)

    def __repr__(self):
        r = "<MidiFile %d tracks\n" % len(self.tracks)
        for t in self.tracks:
            r = r + "  " + `t` + "\n"
        return r + ">"

    def close(self):
        self.file.close()

    def read(self):
        self.readstr(self.file.read())

    def readstr(self, str):
        assert str[:4] == "MThd"
        length, str = getNumber(str[4:], 4)
        assert length == 6
        format, str = getNumber(str, 2)
        self.format = format
        assert format == 0 or format == 1   # dunno how to handle 2
        numTracks, str = getNumber(str, 2)
        division, str = getNumber(str, 2)
        if division & 0x8000:
            framesPerSecond = -((division >> 8) | -128)
            ticksPerFrame = division & 0xFF
            assert ticksPerFrame == 24 or ticksPerFrame == 25 or \
                   ticksPerFrame == 29 or ticksPerFrame == 30
            if ticksPerFrame == 29: ticksPerFrame = 30  # drop frame
            self.ticksPerSecond = ticksPerFrame * framesPerSecond
        else:
            self.ticksPerQuarterNote = division & 0x7FFF
        for i in range(numTracks):
            trk = MidiTrack(i)
            str = trk.read(str)
            self.tracks.append(trk)

    def write(self):
        self.file.write(self.writestr())

    def writestr(self):
        division = self.ticksPerQuarterNote
        # Don't handle ticksPerSecond yet, too confusing
        assert (division & 0x8000) == 0
        str = "MThd" + putNumber(6, 4) + putNumber(self.format, 2)
        str = str + putNumber(len(self.tracks), 2)
        str = str + putNumber(division, 2)
        for trk in self.tracks:
            str = str + trk.write()
        return str


def main(argv):
    global debugflag
    import getopt
    infile = None
    outfile = None
    printflag = 0
    optlist, args = getopt.getopt(argv[1:], "i:o:pd")
    for (option, value) in optlist:
        if option == '-i':
            infile = value
        elif option == '-o':
            outfile = value
        elif option == '-p':
            printflag = 1
        elif option == '-d':
            debugflag = 1

    m = MidiFile()
    m.open(infile)
    m.read()
    m.close()

    if printflag:
        print m
    else:
        m.open(outfile, "wb")
        m.write()
        m.close()

if __name__ == "__main__":
    main(sys.argv)
To display contents, you can type in cmd prompt: c:\python27\python -p -d -i twentieth_century_fox.mid

(upload wouldn't accept .mid files, so you can get example midi file @ http://files.ifnimidi.com/rock/doors/Tw ... ry_Fox.mid

Stoecklem
Posts: 10
Joined: 12 Apr 2013, 04:53

Re: ICE, sound and MIDI files

Post by Stoecklem » 17 Apr 2013, 05:20

This is interesting. I imagine with midi and ice talking directly there are some cool things you could do that I don't understand yet. But the built in realtime midi-in support through the device driver is excellent. One of my favorite things about softimage