public IEnumerable <MidiChunk> Convert(IEnumerable <MidiChunk> chunks) { ThrowIfArgument.IsNull(nameof(chunks), chunks); // var trackChunks = chunks.OfType <TrackChunk>().ToArray(); if (trackChunks.Length == 1) { return(chunks); } // var eventsDescriptors = trackChunks .SelectMany(trackChunk => { var absoluteTime = 0L; var channel = -1; return(trackChunk.Events .Select(midiEvent => { var channelPrefixEvent = midiEvent as ChannelPrefixEvent; if (channelPrefixEvent != null) { channel = channelPrefixEvent.Channel; } if (!(midiEvent is MetaEvent)) { channel = -1; } return new EventDescriptor(midiEvent, (absoluteTime += midiEvent.DeltaTime), channel); })); }) .OrderBy(d => d, new EventDescriptorComparer()); // var resultTrackChunk = new TrackChunk(); var time = 0L; foreach (var eventDescriptor in eventsDescriptors) { MidiEvent midiEvent = eventDescriptor.Event.Clone(); midiEvent.DeltaTime = eventDescriptor.AbsoluteTime - time; resultTrackChunk.Events.Add(midiEvent); time = eventDescriptor.AbsoluteTime; } // return(new[] { resultTrackChunk }.Concat(chunks.Where(c => !(c is TrackChunk)))); }
private static ushort?GetSequenceNumber(TrackChunk trackChunk) { ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); return(trackChunk.Events .TakeWhile(m => m.DeltaTime == 0) .OfType <SequenceNumberEvent>() .FirstOrDefault() ?.Number); }
/// <summary> /// Handles finish of track chunk reading. Called after track chunk is read. /// </summary> /// <param name="trackChunk">Track chunk read.</param> /// <remarks> /// This method called within <see cref="TargetScope.TrackChunk"/> scope. /// </remarks> public virtual void OnFinishTrackChunkReading(TrackChunk trackChunk) { }
/// <summary> /// Handles start of track chunk's content reading. Called after track chunk header is read. /// </summary> /// <param name="trackChunk">Track chunk is being read.</param> /// <remarks> /// This method called within <see cref="TargetScope.TrackChunk"/> scope. /// </remarks> public virtual void OnStartTrackChunkContentReading(TrackChunk trackChunk) { }
/// <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); }
/// <summary> /// Gets all channel numbers presented in the specified <see cref="TrackChunk"/>. /// </summary> /// <param name="trackChunk"><see cref="TrackChunk"/> to get channels of.</param> /// <returns>Collection of channel numbers presented in the <paramref name="trackChunk"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="trackChunk"/> is <c>null</c>.</exception> public static IEnumerable <FourBitNumber> GetChannels(this TrackChunk trackChunk) { ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); return(trackChunk.Events.OfType <ChannelEvent>().Select(e => e.Channel).Distinct().ToArray()); }
/// <summary> /// Splits a track chunk into multiple ones that correspond to <see cref="MidiFileFormat.MultiTrack"/>. /// </summary> /// <param name="trackChunk">Track chunk to split into multiple ones.</param> /// <returns>Multiple track chunks that represent <paramref name="trackChunk"/>.</returns> /// <remarks> /// Note that events will be cloned so events in the result track chunks will not be equal /// by reference to events in the <paramref name="trackChunk"/>. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="trackChunk"/> is <c>null</c>.</exception> public static IEnumerable <TrackChunk> Explode(this TrackChunk trackChunk) { ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); return(ConvertTrackChunks(new[] { trackChunk }, MidiFileFormat.MultiTrack)); }