public void SerializeChart(Chart chart, Stream outStream)
        {
            var writer = new BinaryWriter(outStream);

            writer.WriteUInt32BE(MAGIC);
            writer.WriteUInt8(VERSION);

            writer.WriteUInt16BE((ushort)chart.StreamCount);
            Logger.Log($"chart.binwrite stream count { chart.StreamCount }");

            var effectTable = new ChartEffectTable();

            for (int s = 0; s < chart.StreamCount; s++)
            {
                var stream = chart[s];
                foreach (var obj in stream)
                {
                    if (obj is IHasEffectDef e)
                    {
                        var effect = e.Effect;
                        if (effect == null || effect.Type == EffectType.None)
                        {
                            continue;
                        }
                        effectTable.Add(effect);
                    }
                }
            }

            writer.WriteUInt16BE((ushort)effectTable.Count);
            Logger.Log($"chart.binwrite effect count { effectTable.Count }");

            for (int i = 0; i < effectTable.Count; i++)
            {
                var effect = effectTable[i];
                Logger.Log($"chart.binwrite   effect { effect.Type }");
                SerializeEffectDef(effect, writer);
            }

            int controlPointCount = chart.ControlPoints.Count;

            writer.WriteUInt16BE((ushort)controlPointCount);
            Logger.Log($"chart.binwrite control point count { controlPointCount }");

            for (int i = 0; i < controlPointCount; i++)
            {
                var cp = chart.ControlPoints[i];
                Logger.Log($"chart.binwrite   control point { cp.Position } { cp.BeatsPerMinute } { cp.BeatCount }/{ cp.BeatKind } { cp.SpeedMultiplier }");

                writer.WriteDoubleBE((double)cp.Position);
                writer.WriteDoubleBE(cp.BeatsPerMinute);
                writer.WriteUInt8((byte)cp.BeatCount);
                writer.WriteUInt8((byte)cp.BeatKind);
                writer.WriteDoubleBE(cp.SpeedMultiplier);
            }

            for (int s = 0; s < chart.StreamCount; s++)
            {
                var stream = chart[s];

                int count = stream.Count;
                writer.WriteUInt32BE((uint)count);

                for (int i = 0; i < count; i++)
                {
                    var obj = stream[i];

                    var  serializer = GetSerializerForType(obj);
                    byte objId      = (byte)(serializer?.ID ?? 0);

                    writer.WriteUInt8(objId);
                    if (obj.IsInstant)
                    {
                        writer.WriteUInt8(0);
                    }
                    else
                    {
                        writer.WriteUInt8(0x01);
                    }
                    writer.WriteDoubleBE((double)obj.Position);
                    if (!obj.IsInstant)
                    {
                        writer.WriteDoubleBE((double)obj.Duration);
                    }

                    serializer?.SerializeSubclass(obj, writer, effectTable);
                }
            }
        }
 public abstract ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects);
        public Chart DeserializeChart(ChartInfo chartInfo, Stream inStream)
        {
            var reader = new BinaryReader(inStream, Encoding.UTF8);

            uint magicCheck = reader.ReadUInt32BE();

            if (magicCheck != MAGIC)
            {
                throw new ChartFormatException($"Invalid input stream given.");
            }

            uint versionCheck = reader.ReadUInt8();

            if (versionCheck > VERSION)
            {
                throw new ChartFormatException($"Input stream cannot be read by this serializer: the version is too high.");
            }

            ushort streamCount = reader.ReadUInt16BE();
            var    chart       = new Chart(streamCount)
            {
                Info = chartInfo
            };

            chart.Offset = chartInfo.ChartOffset;

            int effectCount = reader.ReadUInt16BE();
            var effectTable = new ChartEffectTable();

            for (int i = 0; i < effectCount; i++)
            {
                var effect = DeserializeEffectDef(reader);
                effectTable.Add(effect);
            }

            ushort controlPointCount = reader.ReadUInt16BE();

            for (int i = 0; i < controlPointCount; i++)
            {
                tick_t position  = reader.ReadDoubleBE();
                double bpm       = reader.ReadDoubleBE();
                int    beatCount = reader.ReadUInt8();
                int    beatKind  = reader.ReadUInt8();
                double mult      = reader.ReadDoubleBE();

                var cp = chart.ControlPoints.GetOrCreate(position, false);
                cp.BeatsPerMinute  = bpm;
                cp.BeatCount       = beatCount;
                cp.BeatKind        = beatKind;
                cp.SpeedMultiplier = mult;
            }

            for (int s = 0; s < streamCount; s++)
            {
                var stream = chart[s];

                uint ucount = reader.ReadUInt32BE();
                if (ucount > int.MaxValue)
                {
                    throw new ChartFormatException($"Too many objects declared in stream { s }.");
                }
                int count = (int)ucount;

                for (int i = 0; i < count; i++)
                {
                    byte objId      = reader.ReadUInt8();
                    var  serializer = GetSerializerByID(objId);

                    byte flags       = reader.ReadUInt8();
                    bool hasDuration = (flags & 0x01) != 0;

                    tick_t position = reader.ReadDoubleBE();
                    tick_t duration = hasDuration ? reader.ReadDoubleBE() : 0;

                    ChartObject obj;
                    if (serializer != null)
                    {
                        obj = serializer.DeserializeSubclass(position, duration, reader, effectTable);
                    }
                    else
                    {
                        obj = new ChartObject()
                        {
                            Position = position, Duration = duration,
                        }
                    };

                    stream.Add(obj);
                }
            }

            return(chart);
        }
 public abstract void SerializeSubclass(ChartObject obj, BinaryWriter writer, ChartEffectTable effects);