/// <summary> /// Writes current <see cref="MidiFile"/> to the stream. /// </summary> /// <param name="stream">Stream to write file's data to.</param> /// <param name="format">Format of the file to be written.</param> /// <param name="settings">Settings according to which the file must be written. Specify <c>null</c> to use /// default settings.</param> /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException"><paramref name="stream"/> doesn't support writing.</exception> /// <exception cref="InvalidEnumArgumentException"><paramref name="format"/> specified an invalid value.</exception> /// <exception cref="InvalidOperationException">Time division is <c>null</c>.</exception> /// <exception cref="IOException">An I/O error occurred while writing to the stream.</exception> /// <exception cref="ObjectDisposedException"><paramref name="stream"/> is disposed.</exception> /// <exception cref="TooManyTrackChunksException">Count of track chunks presented in the file /// exceeds maximum value allowed for MIDI file.</exception> public void Write(Stream stream, MidiFileFormat format = MidiFileFormat.MultiTrack, WritingSettings settings = null) { ThrowIfArgument.IsNull(nameof(stream), stream); ThrowIfArgument.IsInvalidEnumValue(nameof(format), format); if (TimeDivision == null) { throw new InvalidOperationException("Time division is null."); } if (!stream.CanWrite) { throw new ArgumentException("Stream doesn't support writing.", nameof(stream)); } // if (settings == null) { settings = new WritingSettings(); } if (settings.WriterSettings == null) { settings.WriterSettings = new WriterSettings(); } using (var writer = new MidiWriter(stream, settings.WriterSettings)) { var chunksConverter = ChunksConverterFactory.GetConverter(format); var chunks = chunksConverter.Convert(Chunks); if (settings.WriteHeaderChunk) { var trackChunksCount = chunks.Count(c => c is TrackChunk); if (trackChunksCount > ushort.MaxValue) { throw new TooManyTrackChunksException(trackChunksCount); } var headerChunk = new HeaderChunk { FileFormat = (ushort)format, TimeDivision = TimeDivision, TracksNumber = (ushort)trackChunksCount }; headerChunk.Write(writer, settings); } foreach (var chunk in chunks) { if (chunk is UnknownChunk && settings.DeleteUnknownChunks) { continue; } chunk.Write(writer, settings); } } }
/// <summary> /// Writes current <see cref="MidiFile"/> to the stream. /// </summary> /// <param name="stream">Stream to write file's data to.</param> /// <param name="format">Format of the file to be written.</param> /// <param name="settings">Settings according to which the file must be written.</param> /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="stream"/> doesn't support writing.</exception> /// <exception cref="InvalidEnumArgumentException"><paramref name="format"/> specified an invalid value.</exception> /// <exception cref="InvalidOperationException">Time division is null.</exception> /// <exception cref="IOException">An I/O error occurred while writing to the stream.</exception> /// <exception cref="ObjectDisposedException"><paramref name="stream"/> is disposed. -or- /// Underlying stream writer is disposed.</exception> /// <exception cref="TooManyTrackChunksException">Count of track chunks presented in the file /// exceeds maximum value allowed for MIDI file.</exception> public void Write(Stream stream, MidiFileFormat format = MidiFileFormat.MultiTrack, WritingSettings settings = null) { ThrowIfArgument.IsNull(nameof(stream), stream); ThrowIfArgument.IsInvalidEnumValue(nameof(format), format); if (TimeDivision == null) { throw new InvalidOperationException("Time division is null."); } if (!stream.CanWrite) { throw new ArgumentException("Stream doesn't support writing.", nameof(stream)); } // if (settings == null) { settings = new WritingSettings(); } ValidateCustomMetaEventsStatusBytes(settings.CustomMetaEventTypes); ValidateCustomChunksIds(Chunks.Where(c => !(c is TrackChunk) && !(c is HeaderChunk))); using (var writer = new MidiWriter(stream)) { var chunksConverter = ChunksConverterFactory.GetConverter(format); var chunks = chunksConverter.Convert(Chunks); var trackChunksCount = chunks.Count(c => c is TrackChunk); if (trackChunksCount > ushort.MaxValue) { throw new TooManyTrackChunksException( $"Count of track chunks to be written ({trackChunksCount}) is greater than the valid maximum ({ushort.MaxValue}).", trackChunksCount); } var headerChunk = new HeaderChunk { FileFormat = (ushort)format, TimeDivision = TimeDivision, TracksNumber = (ushort)trackChunksCount }; headerChunk.Write(writer, settings); foreach (var chunk in chunks) { if (settings.CompressionPolicy.HasFlag(CompressionPolicy.DeleteUnknownChunks) && chunk is UnknownChunk) { continue; } chunk.Write(writer, settings); } } }
/// <summary> /// Reads a chunk from a MIDI-file. /// </summary> /// <param name="reader">Reader to read a chunk with.</param> /// <param name="settings">Settings according to which a chunk must be read.</param> /// <param name="actualTrackChunksCount">Actual count of track chunks at the moment.</param> /// <param name="expectedTrackChunksCount">Expected count of track chunks.</param> /// <returns>A MIDI-file chunk.</returns> /// <exception cref="ObjectDisposedException">Method was called after the reader was disposed.</exception> /// <exception cref="IOException">An I/O error occurred on the underlying stream.</exception> /// <exception cref="UnknownChunkException">Chunk to be read has unknown ID and that /// should be treated as error accordng to the specified <paramref name="settings"/>.</exception> /// <exception cref="UnexpectedTrackChunksCountException">Actual track chunks /// count is greater than expected one and that should be treated as error according to /// the specified <paramref name="settings"/>.</exception> /// <exception cref="InvalidChunkSizeException">Actual chunk's size differs from the one declared /// in its header and that should be treated as error according to the specified /// <paramref name="settings"/>.</exception> /// <exception cref="UnknownChannelEventException">Reader has encountered an unknown channel event.</exception> /// <exception cref="NotEnoughBytesException">Value cannot be read since the reader's underlying stream /// doesn't have enough bytes.</exception> /// <exception cref="UnexpectedRunningStatusException">Unexpected running status is encountered.</exception> /// <exception cref="MissedEndOfTrackEventException">Track chunk doesn't end with End Of Track event and that /// should be treated as error accordng to the specified <paramref name="settings"/>.</exception> /// <exception cref="InvalidChannelEventParameterValueException">Value of a channel event's parameter /// just read is invalid.</exception> /// <exception cref="InvalidMetaEventParameterValueException">Value of a meta event's parameter /// just read is invalid.</exception> private static MidiChunk ReadChunk(MidiReader reader, ReadingSettings settings, int actualTrackChunksCount, int?expectedTrackChunksCount) { MidiChunk chunk = null; try { var chunkId = reader.ReadString(MidiChunk.IdLength); if (chunkId.Length < MidiChunk.IdLength) { switch (settings.NotEnoughBytesPolicy) { case NotEnoughBytesPolicy.Abort: throw new NotEnoughBytesException("Chunk ID cannot be read since the reader's underlying stream doesn't have enough bytes.", MidiChunk.IdLength, chunkId.Length); case NotEnoughBytesPolicy.Ignore: return(null); } } // switch (chunkId) { case HeaderChunk.Id: chunk = new HeaderChunk(); break; case TrackChunk.Id: chunk = new TrackChunk(); break; default: chunk = TryCreateChunk(chunkId, settings.CustomChunkTypes); break; } // if (chunk == null) { switch (settings.UnknownChunkIdPolicy) { case UnknownChunkIdPolicy.ReadAsUnknownChunk: chunk = new UnknownChunk(chunkId); break; case UnknownChunkIdPolicy.Skip: var size = reader.ReadDword(); reader.Position += size; return(null); case UnknownChunkIdPolicy.Abort: throw new UnknownChunkException($"'{chunkId}' chunk ID is unknown.", chunkId); } } // if (chunk is TrackChunk && expectedTrackChunksCount != null && actualTrackChunksCount >= expectedTrackChunksCount) { ReactOnUnexpectedTrackChunksCount(settings.UnexpectedTrackChunksCountPolicy, actualTrackChunksCount, expectedTrackChunksCount.Value); switch (settings.ExtraTrackChunkPolicy) { case ExtraTrackChunkPolicy.Read: break; case ExtraTrackChunkPolicy.Skip: var size = reader.ReadDword(); reader.Position += size; return(null); } } // chunk.Read(reader, settings); } catch (NotEnoughBytesException ex) { ReactOnNotEnoughBytes(settings.NotEnoughBytesPolicy, ex); } catch (EndOfStreamException ex) { ReactOnNotEnoughBytes(settings.NotEnoughBytesPolicy, ex); } return(chunk); }