Skip to content

Commit

Permalink
Merge pull request #30 from olemb/revert-29-master
Browse files Browse the repository at this point in the history
Revert "Added the ability to read and write file objects as well as f…
  • Loading branch information
olemb committed Aug 26, 2015
2 parents e5b3df0 + 00c345a commit 25b083a
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 172 deletions.
12 changes: 1 addition & 11 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,10 @@ Changes
Release History
---------------

1.1.15 (2015-08-16)
^^^^^^^^^^^^^^^^^^^
* Added the ability to use file objects as well as filenames when reading,
writing and saving MIDI files. This allows you to create a MIDI file
dynamically, possibly *not* using mido, save it to an io.BytesIO, and
then play that in-memory file, without having to create an intermediate
external file. Of course the memory file (and/or the MidiFile) can still
be saved to an external file.
(Implemented by Brian O'Neill.)

1.1.14 (2015-06-09)
^^^^^^^^^^^^^^^^^^^

* bugfix: merge_tracks() concatenated the tracks instead of merging
* bugfix: merge_tracks() concatinated the tracks instead of merging
them. This caused tracks to be played back one by one. (Issue #28,
reported by Charles Gillingham.)

Expand Down
20 changes: 5 additions & 15 deletions docs/midi_files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ MIDI Files
==========

MidiFile objects can be used to read, write and play back MIDI
files.
files. (Writing is not yet implemented.)


Opening a File
Expand Down Expand Up @@ -74,15 +74,14 @@ You can create a new file by calling MidiFile without the ``filename``
argument. The file can then be saved by calling the ``save()`` method::

from mido.midifies import MidiTrack
from mido.messages import Messages

with MidiFile() as mid:
track = MidiTrack()
mid.tracks.append(track)
tracks.append(track)

track.append(Message('program_change', program=12, time=0))
track.append(Message('note_on', note=64, velocity=64, time=32)
track.append(Message('note_off', note=64, velocity=127, time=32)
tracks.append(midi.Message('program_change', program=12, time=0))
tracks.append(midi.Message('note_on', note=64, velocity=64, time=32)
tracks.append(midi.Message('note_off', note=64, velocity=127, time=32)

mid.save('new_song.mid')

Expand All @@ -97,15 +96,6 @@ be written anyway.

A complete example can be found in ``examples/midifiles/``.

The ``save`` method takes either a filename (``str``) or, using the ``file``
keyword parameter, a file object such as an in-memory binary file (an
``io.BytesIO``). If you pass a file object, ``save`` does not close it.
Similarly, the ``MidiFile`` constructor can take either a filename, or
a file object by using the ``file`` keyword parameter. if you pass a file
object to ``MidiFile`` as a context manager, the file is not closed when
the context manager exits. Examples can be found in ``test_midifiles2.py``.



File Types
----------
Expand Down
2 changes: 1 addition & 1 deletion mido/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
__email__ = 'ombdalen@gmail.com'
__url__ = 'http://mido.readthedocs.org/'
__license__ = 'MIT'
__version__ = '1.1.15'
__version__ = '1.1.14'

# Prevent splat import.
__all__ = []
Expand Down
8 changes: 4 additions & 4 deletions mido/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def check_channel(channel):
"""Check type and value of channel.
Raises TypeError if the value is not an integer, and ValueError if
it is outside range 0..15.
it is outside range 0..127.
"""
if not isinstance(channel, int):
raise TypeError('channel must be an integer')
Expand Down Expand Up @@ -268,7 +268,7 @@ class BaseMessage(object):
def copy(self, **overrides):
"""Return a copy of the message.
Attributes will be overridden by the passed keyword arguments.
Attributes will be overriden by the passed keyword arguments.
Only message specific attributes can be overridden. The message
type can not be changed.
Expand All @@ -286,7 +286,7 @@ def copy(self, **overrides):
for name, value in overrides.items():
try:
# setattr() is responsible for checking the
# name and type of the attribute.
# name and type of the atrribute.
setattr(message, name, value)
except AttributeError as err:
raise ValueError(*err.args)
Expand Down Expand Up @@ -386,7 +386,7 @@ def __setattr__(self, name, value):
'{} message has no attribute {}'.format(self.type, name))

def __delattr__(self, name):
raise AttributeError('attribute cannot be deleted')
raise AttributeError('attribute can not be deleted')

def bytes(self):
"""Encode message and return as a list of integers."""
Expand Down
76 changes: 25 additions & 51 deletions mido/midifiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
from .midifiles_meta import MetaMessage, _build_meta_message, meta_charset
from .midifiles_meta import MetaSpec, add_meta_spec, encode_variable_int
from . import midifiles_meta
import io

# The default tempo is 120 BPM.
# (500000 microseconds per beat (quarter note).)
Expand All @@ -44,18 +43,8 @@ class ByteReader(object):
"""
Reads bytes from a file.
"""
def __init__(self, filename_or_file):
"""
:param filename_or_file: a str (filename, as previously),
or an opened file handle,
possibly an in-memory file (an io.BytesIO stream)
"""
if isinstance(filename_or_file, str):
with open(filename_or_file, 'rb') as f:
values = f.read()
else:
values = filename_or_file.getvalue()
self._buffer = list(bytearray(values))
def __init__(self, filename):
self._buffer = list(bytearray(open(filename, 'rb').read()))
self.pos = 0
self._eof = EOFError('unexpected end of file')

Expand Down Expand Up @@ -109,12 +98,9 @@ def __exit__(self, type, value, traceback):


class ByteWriter(object):
def __init__(self, filename_or_file):
self.file_created = isinstance(filename_or_file, str)
if self.file_created:
filename_or_file = open(filename_or_file, 'wb')
self.file = filename_or_file

def __init__(self, filename):
self.file = open(filename, 'wb')

def write(self, bytes):
self.file.write(bytearray(bytes))

Expand All @@ -137,8 +123,7 @@ def __enter__(self):
return self

def __exit__(self, type, value, traceback):
if self.file_created:
self.file.close()
self.file.close()
return False

class MidiTrack(list):
Expand Down Expand Up @@ -208,19 +193,14 @@ def merge_tracks(tracks):


class MidiFile:
def __init__(self, filename=None, file=None,
type=1, ticks_per_beat=DEFAULT_TICKS_PER_BEAT,
def __init__(self, filename=None, type=1,
ticks_per_beat=DEFAULT_TICKS_PER_BEAT,
charset='latin1'):

assert filename is None or isinstance(filename, str)
assert file is None or isinstance(file, io.BufferedIOBase)

filename_or_file = filename or file # filename takes precedence
self.filename_or_file = filename_or_file
self.filename = filename
self.tracks = []
self.charset = charset

if filename_or_file is None:
if filename is None:
if type not in range(3):
raise ValueError(
'invalid format {} (must be 0, 1 or 2)'.format(format))
Expand All @@ -242,7 +222,7 @@ def add_track(self, name=None):
return track

def _load(self):
with ByteReader(self.filename_or_file) as self._file, \
with ByteReader(self.filename) as self._file, \
meta_charset(self.charset):
# Read header (16 bytes)
magic = self._file.peek_list(4)
Expand Down Expand Up @@ -438,32 +418,26 @@ def _has_end_of_track(self, track):
else:
return False

def save(self, filename=None, file=None):
"""Save to a file, given by filename (str) or file (file object),
or by filename or file already passed to constructor.
def save(self, filename=None):
"""Save to a file.
If filename or file is passed, self.filename_or_file will be set to
filename if given, else file if given, and the data will be saved to
the specified file. Otherwise the existing self.filename_or_file is
used (raising ValueError if that too is None).
If filename is passed, self.filename will be set to this
value, and the data will be saved to this file. Otherwise
self.filename is used.
Raises ValueError if both filename_or_file and self.filename_or_file are None,
or if a type 0 file has != one track.
Raises ValueError both filename and self.filename are None,
or if a type 1 file has != one track.
"""
if self.type == 0 and len(self.tracks) != 1:
raise ValueError('type 0 file must have exactly 1 track')

assert filename is None or isinstance(filename, str)
assert file is None or isinstance(file, io.BufferedIOBase)
raise ValueError('type 1 file must have exactly 1 track')

filename_or_file = filename or file # filename takes precedence
if filename_or_file is self.filename_or_file is None:
raise ValueError('no file name or file')
if filename is self.filename is None:
raise ValueError('no file name')

if filename_or_file is not None:
self.filename_or_file = filename_or_file
if filename is not None:
self.filename = filename

with ByteWriter(self.filename_or_file) as self._file, \
with ByteWriter(self.filename) as self._file, \
meta_charset(self.charset):
self._file.write(b'MThd')

Expand Down Expand Up @@ -529,7 +503,7 @@ def print_tracks(self, meta_only=False):

def __repr__(self):
return '<midi file {!r} type {}, {} tracks, {} messages>'.format(
self.filename_or_file, self.type, len(self.tracks),
self.filename, self.type, len(self.tracks),
sum([len(track) for track in self.tracks]))

def __enter__(self):
Expand Down
90 changes: 0 additions & 90 deletions mido/test_midifiles2.py

This file was deleted.

0 comments on commit 25b083a

Please sign in to comment.