Example #1
0
        /// <summary>
        /// Converts a Malody object to Qua
        /// </summary>
        /// <returns>Qua object</returns>
        public Qua ToQua()
        {
            var audioFile = Hitobjects.FirstOrDefault(x => x.Type == 1).Sound;
            var audioOffset = -Hitobjects.FirstOrDefault(x => x.Type == 1).Offset;

            var qua = new Qua()
            {
                AudioFile = audioFile,
                SongPreviewTime = Meta.PreviewTime,
                BackgroundFile = Meta.Background,
                MapId = -1,
                MapSetId = -1,
                Title = string.IsNullOrEmpty(Meta.Song.Title) ? Meta.Song.TitleOriginal : Meta.Song.Title,
                Artist = string.IsNullOrEmpty(Meta.Song.Artist) ? Meta.Song.ArtistOriginal : Meta.Song.Artist,
                Source = "Malody",
                Tags = "",
                Creator = Meta.Creator,
                DifficultyName = Meta.Version,
                Description = $"This is a Quaver converted version of {Meta.Creator}'s map."
            };

            if (Meta.Mode != 0)
                throw new ArgumentException("Only the 'Key' Malody game mode can be converted to Qua");

            switch (Meta.Keymode.Keymode)
            {
                case 4:
                    qua.Mode = GameMode.Keys4;
                    break;
                case 7:
                    qua.Mode = GameMode.Keys7;
                    break;
                default:
                    throw new InvalidEnumArgumentException();
            }

            foreach (var tp in TimingPoints)
            {
                qua.TimingPoints.Add(new TimingPointInfo()
                {
                    StartTime = GetMilliSeconds(GetBeat(tp.Beat), audioOffset),
                    Bpm = tp.Bpm,
                    Signature = TimeSignature.Quadruple
                });
            }

            if (SvPoints != null)
            {
                foreach (var sv in SvPoints)
                {
                    qua.SliderVelocities.Add(new SliderVelocityInfo
                    {
                        StartTime = GetMilliSeconds(GetBeat(sv.Beat), audioOffset),
                        Multiplier = sv.Scroll
                    });
                }
            }

            foreach (var ho in Hitobjects)
            {
                KeySoundInfo keySound;

                if (ho.Type == 1) // The song itself, doesn't have a note representation
                    continue;

                if (string.IsNullOrEmpty(ho.Sound))
                    keySound = null;
                else
                {
                    var cas = new CustomAudioSampleInfo
                    {
                        Path = ho.Sound,
                        UnaffectedByRate = false
                    };

                    if (!qua.CustomAudioSamples.Contains(cas))
                        qua.CustomAudioSamples.Add(cas);

                    keySound = new KeySoundInfo
                    {
                        Sample = qua.CustomAudioSamples.IndexOf(cas),
                        Volume = ho.Volume
                    };
                }

                qua.HitObjects.Add(new HitObjectInfo
                {
                    StartTime = GetMilliSeconds(GetBeat(ho.Beat), audioOffset),
                    EndTime = ho.BeatEnd == null ? 0 : GetMilliSeconds(GetBeat(ho.BeatEnd), audioOffset),
                    Lane = ho.Column + 1,
                    KeySounds = keySound == null ? new List<KeySoundInfo>() : new List<KeySoundInfo> { keySound }
                });
            }

            qua.Sort();

            if (!qua.IsValid())
                throw new ArgumentException("The .qua file is invalid. It does not have HitObjects, TimingPoints, its Mode is invalid or some hit objects are invalid.");

            return qua;
        }
Example #2
0
        /// <summary>
        ///     Converts an .osu file into a Qua object
        /// </summary>
        /// <returns></returns>
        public Qua ToQua()
        {
            // Init Qua with general information
            var qua = new Qua()
            {
                AudioFile       = AudioFilename,
                SongPreviewTime = PreviewTime,
                BackgroundFile  = Background,
                MapId           = -1,
                MapSetId        = -1,
                Title           = Title,
                Artist          = Artist,
                Source          = Source,
                Tags            = Tags,
                Creator         = Creator,
                DifficultyName  = Version,
                Description     = $"This is a Quaver converted version of {Creator}'s map."
            };

            // Get the correct game mode based on the amount of keys the map has.
            switch (KeyCount)
            {
            case 4:
                qua.Mode = GameMode.Keys4;
                break;

            case 7:
                qua.Mode = GameMode.Keys7;
                break;

            case 8:
                qua.Mode          = GameMode.Keys7;
                qua.HasScratchKey = true;
                break;

            default:
                qua.Mode = (GameMode)(-1);
                break;
            }

            foreach (var path in CustomAudioSamples)
            {
                qua.CustomAudioSamples.Add(new CustomAudioSampleInfo()
                {
                    Path             = path,
                    UnaffectedByRate = false
                });
            }

            // Get custom audio samples and sound effects.
            foreach (var info in SoundEffects)
            {
                // Skip sound effects with zero volume. In Qua 0 is the default value which in this case equals to 100.
                if (info.Volume == 0)
                {
                    continue;
                }

                qua.SoundEffects.Add(new SoundEffectInfo()
                {
                    StartTime = info.StartTime,
                    Sample    = info.Sample + 1,
                    Volume    = info.Volume
                });
            }

            // Get timing points and slider velocities.
            foreach (var tp in TimingPoints)
            {
                // WARNING: As far as I can tell, BPM changes with BPM < 0 behave as SVs for all intents and purposes,
                //          except that they also participate in the common BPM computation (as negative values).
                //          However, I don't think there are any maps that have enough negative BPM for the common BPM
                //          to become negative, and I am also not sure that negative common BPM wouldn't just break
                //          everything in osu! itself, so instead of inserting a ton of hacks throughout the rest of
                //          Quaver's code base, I'm making negative BPM changes behave fully like SVs (so they are never
                //          taken into account in common BPM computation). If there happens to be an actually working
                //          map with negative common BPM, this is the place to revisit.
                //          -- YaLTeR
                var isSV = tp.Inherited == 0 || tp.MillisecondsPerBeat < 0;

                if (isSV)
                {
                    qua.SliderVelocities.Add(new SliderVelocityInfo
                    {
                        StartTime  = tp.Offset,
                        Multiplier = (-100 / tp.MillisecondsPerBeat).Clamp(0.1f, 10)
                    });
                }
                else
                {
                    qua.TimingPoints.Add(new TimingPointInfo
                    {
                        StartTime = tp.Offset,
                        Bpm       = 60000 / tp.MillisecondsPerBeat,
                        Signature = tp.Signature
                    });
                }
            }

            // Get HitObject Info
            foreach (var hitObject in HitObjects)
            {
                // Get the keyLane the hitObject is in
                var keyLane = (int)(hitObject.X / (512d / KeyCount)).Clamp(0, KeyCount - 1) + 1;

                // osu! considers objects in lane 1 to be the special key, Quaver considers it to be the last lane.
                // Lane 8 on 7K+1
                if (qua.HasScratchKey)
                {
                    if (keyLane == 1)
                    {
                        keyLane = KeyCount;
                    }
                    else
                    {
                        keyLane--;
                    }
                }

                // Add HitObjects to the list depending on the object type
                if (hitObject.Type.HasFlag(HitObjectType.Circle))
                {
                    qua.HitObjects.Add(new HitObjectInfo
                    {
                        StartTime = hitObject.StartTime,
                        Lane      = keyLane,
                        EndTime   = 0,
                        HitSound  = hitObject.HitSound.ToQuaverHitSounds(),
                        KeySounds = hitObject.KeySound == -1 ? new List <KeySoundInfo>() : new List <KeySoundInfo>
                        {
                            new KeySoundInfo {
                                Sample = hitObject.KeySound + 1, Volume = hitObject.Volume
                            }
                        }
                    });
                }
                else if (hitObject.Type.HasFlag(HitObjectType.Hold))
                {
                    qua.HitObjects.Add(new HitObjectInfo
                    {
                        StartTime = hitObject.StartTime,
                        Lane      = keyLane,
                        EndTime   = hitObject.EndTime,
                        HitSound  = hitObject.HitSound.ToQuaverHitSounds(),
                        KeySounds = hitObject.KeySound == -1 ? new List <KeySoundInfo>() : new List <KeySoundInfo>
                        {
                            new KeySoundInfo {
                                Sample = hitObject.KeySound + 1, Volume = hitObject.Volume
                            }
                        }
                    });
                }
            }

            // Sort the various lists.
            qua.Sort();

            if (TimingPoints.Count > 0)
            {
                // If the individual object key sound volume is zero, we need to set it to the timing point sample volume.
                var timingPointIndex = 0;
                var volume           = TimingPoints[timingPointIndex].Volume;

                // Assumption: hit objects and timing points are sorted by start time (enforced by qua.Sort() above).
                foreach (var hitObject in qua.HitObjects)
                {
                    // Advance the current timing point index as necessary.
                    while (timingPointIndex < TimingPoints.Count - 1 &&
                           TimingPoints[timingPointIndex + 1].Offset <= hitObject.StartTime)
                    {
                        timingPointIndex++;
                        volume = TimingPoints[timingPointIndex].Volume;
                    }

                    for (var i = hitObject.KeySounds.Count - 1; i >= 0; i--)
                    {
                        var keySound = hitObject.KeySounds[i];
                        if (keySound.Volume == 0)
                        {
                            keySound.Volume = volume;
                        }

                        // If the volume is still zero, remove this key sound.
                        // In Qua 0 is the default value which equals to 100.
                        if (keySound.Volume == 0)
                        {
                            hitObject.KeySounds.RemoveAt(i);
                        }
                    }
                }
            }

            // Do a validity check.
            if (!qua.IsValid())
            {
                throw new ArgumentException("The .qua file is invalid. It does not have HitObjects, TimingPoints, its Mode is invalid or some hit objects are invalid.");
            }

            return(qua);
        }