Beispiel #1
0
 /// <summary>
 /// Checks if a given <see cref="MelodyTrackIndex"/> is valid within the given midi file.
 /// </summary>
 /// <param name="melodyTrackIndex">The one-based ordinal track index number to be checked. </param>
 /// <param name="midiFile"> The midi file that the track is checked against.</param>
 /// <param name="errorMessage"> Returned error message in case the track is invalid. </param>
 /// <returns> True if the track number is null or valid, false otherwise. </returns>
 public static bool IsMelodyTrackIndexValid(int?melodyTrackIndex, IMidiFile midiFile, out string errorMessage)
 {
     if (melodyTrackIndex.HasValue && melodyTrackIndex >= midiFile?.Tracks.Count)
     {
         errorMessage = $"Error: Invalid index for {nameof(melodyTrackIndex)}." +
                        $"The given MIDI file has only {midiFile.Tracks.Count - 1} tracks.";
         return(false);
     }
     errorMessage = null;
     return(true);
 }
Beispiel #2
0
        /// <summary>
        /// Creates a midi playback file by copying an existing midi file or stream
        /// and removing it's melody track, if such exists (according to the specified
        /// track number in the <paramref name="MelodyTrackNumber"/> parameter.
        /// <para> If the given midi is already a pure playback with no melody tracks,
        /// then a new copy of this midi would be returned.
        /// </para>
        /// </summary>
        /// <param name="midiStream"> The midi input that the playback should be created from. </param>
        /// <param name="MelodyTrackNumber">Number of the melody track in the given midi, if such a track exists.</param>
        /// <returns></returns>
        public static IMidiFile CreateMidiPlayback(Stream midiStream, MelodyTrackIndex?melodyTrackNumber)
        {
            // create a new midi file instance
            IMidiFile midifile = MidiFactory.CreateMidiFile(midiStream);

            // remove melody track is such exists on this midi file
            if (melodyTrackNumber.HasValue && melodyTrackNumber != MelodyTrackIndex.NoMelodyTrackInFile)
            {
                midifile.ExtractMelodyTrack((byte)melodyTrackNumber);
            }

            // return the resulted playback midi file
            return(midifile);
        }
Beispiel #3
0
 public static bool IsMidiFileValid(HttpPostedFileBase midiFileHandler, out IMidiFile midiFile, out string errorMessage)
 {
     errorMessage = null;
     try
     {   // fetch midi content from stream
         midiFile = CompositionContext.ReadMidiFile(midiFileHandler.InputStream);
         return(true);
     }
     catch (Exception ex)
     {
         midiFile     = null;
         errorMessage = ex.Message;
         return(false);
     }
 }
Beispiel #4
0
        private void btnUploadChords_Click(object sender, EventArgs e)
        {
            if (_chordsFileDialog.ShowDialog() == DialogResult.OK)
            {
                _chordsFilePath = _chordsFileDialog.FileName;
                _chordsFileName = _chordsFileDialog.SafeFileName;

                lblChordsFileName.Text = _chordsFileName;

                if (!string.IsNullOrWhiteSpace(_chordsFilePath) && !string.IsNullOrWhiteSpace(_midiFilePath))
                {
                    _composition               = new CompositionContext(_chordsFilePath, _midiFilePath, melodyTrackIndex: MelodyTrackIndex.TrackNumber1);
                    _midiFile                  = _composition.Compose()[0];
                    lblMidiTitle.Text         += ": " + _midiFile.Title;
                    lblMidiBpm.Text           += ": " + _midiFile.BeatsPerMinute.ToString();
                    lblMidiTimeSignature.Text += ": " + _midiFile.KeySignature.ToString();
                    lblMidiBars.Text          += ": " + _midiFile.NumberOfBars.ToString();
                }
            }
        }
Beispiel #5
0
        /// <summary>
        /// Checks that bar structure of the musical peace is compatible between it's description in the
        /// MIDI file and it's description in the chord progression read from the chords file.
        /// </summary>
        /// <param name="chordProgression"> Bar sequence which contains the chord progressionn from chords file. </param>
        /// <param name="midiFile"> MIDI file instance containning the bars music peace description. </param>
        /// <param name="errorMessage"> Returned error message in case the bars are not compatible. </param>
        /// <returns></returns>
        public static bool AreBarsCompatible(IList <IBar> chordProgression, IMidiFile midiFile, out string errorMessage)
        {
            // validate that total num of bars in MIDI matches num of bars in chord progression
            if (chordProgression?.Count != midiFile?.NumberOfBars)
            {
                errorMessage = $"Error: Number of bars mismatch: \n" +
                               $"chord file has {chordProgression?.Count} bars," +
                               $" while midi file has {midiFile?.NumberOfBars}!";
                return(false);
            }

            // validate that each bar duration from CHORD progression matches the duration in MIDI
            IDuration midiDuration   = null;
            byte      barNumerator   = 0;
            byte      barDenominator = 0;

            for (int barIndex = 0; barIndex < chordProgression.Count; barIndex++)
            {
                // get bar's duration from chord progression
                barNumerator   = chordProgression[barIndex].TimeSignature.Numerator;
                barDenominator = chordProgression[barIndex].TimeSignature.Denominator;

                // get bar's duration from midi file
                midiDuration = midiFile.GetBarDuration(barIndex);

                // validate equality
                if (barNumerator != midiDuration.Numerator || barDenominator != midiDuration.Denominator)
                {
                    errorMessage = $"Error: Time signature '{barNumerator}/{barDenominator}' " +
                                   $"of bar number {barIndex + 1} in the chord progression " +
                                   $"does not match the corresponding time signature " +
                                   $"'{midiDuration.Numerator}/{midiDuration.Denominator}' " +
                                   $"in the midi file.";
                }
            }

            errorMessage = null;
            return(true);
        }
Beispiel #6
0
        /// <summary>
        /// Construct a composition from a chord progression, midi file handler,
        /// and an indicator determinning if this midi file is a pure playback or whether
        /// it contains a melody track that should be replaced when composing a new melody.
        /// </summary>
        /// <remarks> Number of bars in midi file and chord progression must match exactly, and so do their durations. </remarks>
        /// <param name="chordProgression"> The bar sequence which contains the chord progression.</param>
        /// <param name="midiFile"> The MIDI file handler. </param>
        /// <param name="melodyTrackIndex"> Index of the existing melody track in the midi file, if one exists.
        /// <para>
        /// When composing, the track with this index would be replaced by the new composed
        /// melody track. If the current midi file is a pure playback that contains no existing
        /// melody track, then this property should be set to null. </para></param>
        /// <exception cref="InvalidDataException"> Thrown when there is a mismatch
        /// between the chord progression and the midi file, either in the total number
        /// of bars, or the duration of individual bars. </exception>
        /// <exception cref="IndexOutOfRangeException"> Throw when <paramref name="melodyTrackIndex"/>
        /// has a value which equal or greater than the number of track in the given midi file. </exception>
        public CompositionContext(IList <IBar> chordProgression, IMidiFile midiFile, MelodyTrackIndex?melodyTrackIndex = null)
        {
            // set chord progression and midi properties
            MidiInputFile      = midiFile;
            ChordProgression   = chordProgression;
            _melodyTrackIndex  = melodyTrackIndex;
            _midiInputFilePath = MidiInputFile.FilePath;
            _midiInputFileName = Path.GetFileNameWithoutExtension(MidiInputFile.FilePath);

            // place holder for an error message if validtions fail
            string errorMessage;

            // validate melody track index is not out of bounds
            if (!CompositionContext.IsMelodyTrackIndexValid((int?)melodyTrackIndex, MidiInputFile, out errorMessage))
            {
                throw new IndexOutOfRangeException(errorMessage);
            }

            // validate that bars in CHORD progression are compatible with MIDI file
            if (!AreBarsCompatible(chordProgression, MidiInputFile, out errorMessage))
            {
                throw new InvalidDataException(errorMessage);
            }
        }
Beispiel #7
0
        public async Task <ActionResult> Create(SongViewModel songViewModel)
        {
            // if model is invalid return back the view again
            if (!ModelState.IsValid)
            {
                return(View(songViewModel));
            }

            // get current timestamp
            DateTime timestamp = DateTime.Now;

            // Create a new song instance based on the form data from the view model
            Song song = new Song
            {
                Created          = timestamp,
                Modified         = timestamp,
                Title            = songViewModel.Title,
                Artist           = songViewModel.Artist,
                MidiFileName     = songViewModel.MidiFileHandler.FileName,
                ChordsFileName   = songViewModel.ChordsFileHandler.FileName,
                MelodyTrackIndex = songViewModel.MelodyTrackIndex,
                IsPublic         = songViewModel.IsPublic && User.IsInRole(RoleName.Admin),
                UserId           = this.User.Identity.GetUserId(),
            };

            // set name for the playback file to be created based on the midi file
            song.SetPlaybackName();

            // save the new song in the database
            _databaseGateway.Songs.Add(song);
            await _databaseGateway.CommitChangesAsync();

            // save the files of the new song on the file server
            IMidiFile playbackFile = null;

            try
            {
                // create a new directory for the new song
                string directoryPath = await GetSongPath(song.Id);

                Directory.CreateDirectory(directoryPath);

                // save the midi file in the new directory
                string midiFileFullPath = directoryPath + song.MidiFileName;
                songViewModel.MidiFileHandler.SaveAs(midiFileFullPath);

                // save the midi playback file in the new directory
                string midiPlaybackFullPath = directoryPath + song.MidiPlaybackFileName;
                playbackFile = CompositionContext.CreateMidiPlayback(songViewModel.MidiFileHandler.InputStream, song.MelodyTrackIndex);
                playbackFile.SaveFile(outputPath: midiPlaybackFullPath, pathIncludesFileName: true);

                // save the chord progression file in the new directory
                string chordsFilefullPath = directoryPath + song.ChordsFileName;
                songViewModel.ChordsFileHandler.SaveAs(chordsFilefullPath);
            }
            catch (Exception ex)
            {
                // in case of failure, rollback DB changes and log error message
                _databaseGateway.Songs.Remove(song);
                await _databaseGateway.CommitChangesAsync();

                HomeController.WriteErrorToLog(HttpContext, ex.Message);
            }
            finally
            {
                // release open unmanaged resources
                songViewModel.ChordsFileHandler?.InputStream?.Dispose();
                songViewModel.MidiFileHandler?.InputStream?.Dispose();
                playbackFile?.Stream?.Dispose();
            }

            // If creation was successful, redirect to new song details page
            string successMessage = $"The Song '{song.Title}' by '{song.Artist}' was successfully uploaded.";

            return(RedirectToAction(nameof(Details), new { Id = song.Id, message = successMessage }));
        }
Beispiel #8
0
        public async Task <ActionResult> Compose(CompositionViewModel model)
        {
            // validate that sum of weights is equal to 100
            double weightSum = model.WeightSum;

            if (weightSum != 100)
            {
                string errorMessage =
                    $"The total weights of all fitness function " +
                    $"evaluators must sum up to 100.\n" +
                    $"The current sum is {weightSum}";
                this.ModelState.AddModelError(nameof(model.AccentedBeats), errorMessage);
            }

            // assure all other validations passed okay
            if (!ModelState.IsValid)
            {
                CompositionViewModel viewModel = new CompositionViewModel
                {
                    SongSelectList  = new SelectList(_databaseGateway.Songs.GetAll().OrderBy(s => s.Title), nameof(Song.Id), nameof(Song.Title)),
                    PitchSelectList = new SelectList(_pitchSelectList, nameof(PitchRecord.Pitch), nameof(PitchRecord.Description)),
                };
                return(View(viewModel));
            }

            // fetch song from datasource
            Song song = _databaseGateway.Songs.Get(model.SongId);

            // get the chord and file paths on the file server
            string chordFilePath = await SongsController.GetSongPath(song.Id, _databaseGateway, User, SongFileType.ChordProgressionFile);

            string midiFilePath = await SongsController.GetSongPath(song.Id, _databaseGateway, User, SongFileType.MidiOriginalFile);

            // create a compositon instance
            CompositionContext composition = new CompositionContext(
                chordProgressionFilePath: chordFilePath,
                midiFilePath: midiFilePath,
                melodyTrackIndex: song.MelodyTrackIndex);

            // build evaluators weights
            MelodyEvaluatorsWeights weights = new MelodyEvaluatorsWeights
            {
                AccentedBeats    = model.AccentedBeats,
                ContourDirection = model.ContourDirection,
                ContourStability = model.ContourStability,
                DensityBalance   = model.DensityBalance,
                ExtremeIntervals = model.ExtremeIntervals,
                PitchRange       = model.PitchRange,
                PitchVariety     = model.PitchVariety,
                SmoothMovement   = model.SmoothMovement,
                Syncopation      = model.Syncopation
            };

            // Compose some melodies and fetch the best one
            IMidiFile midiFile = composition.Compose(
                strategy: CompositionStrategy.GeneticAlgorithmStrategy,
                overallNoteDurationFeel: model.OverallNoteDurationFeel,
                musicalInstrument: model.MusicalInstrument,
                minPitch: model.MinPitch,
                maxPitch: model.MaxPitch,
                useExistingMelodyAsSeed: model.useExistingMelodyAsSeed,
                customParams: weights)
                                 .FirstOrDefault();

            // save the midifile output internally
            _midiFile        = midiFile;
            ViewBag.MidiFile = midiFile;

            // save file on the file server
            string directoryPath = HomeController.GetFileServerPath() + "Outputs" +
                                   Path.DirectorySeparatorChar + song.Id + Path.DirectorySeparatorChar;

            Directory.CreateDirectory(directoryPath);
            string filePath = _midiFile.SaveFile(outputPath: directoryPath);

            // return the file to the client client for downloading
            string fileName = Path.GetFileName(filePath);

            byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
            return(File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName));
        }
Beispiel #9
0
        /// <summary>
        /// Composes a solo-melody over this composition's midi playback file and chord progression,
        /// using the additional preferences and constraints parameters.
        /// </summary>
        /// <returns> A new midi file containing the composed solo-melody. </returns>
        /// <param name="strategy">Requested composition strategy algorithm for composing the melody. </param>
        /// <param name="overallNoteDurationFeel">Requested generic density overall feeling of the outcome melody.</param>
        /// <param name="musicalInstrument">Requested virtual music instrumen tha would play the output melody.</param>
        /// <param name="pitchRangeSource">Determins whether to set the pitch range from the original melody in the
        ///                                midi file, or from the custom values mentiones in <paramref name="minPitch"/>
        ///                                and <paramref name="maxPitch"/>.</param>
        /// <param name="minPitch">Requested lowest available pitch that would can be used for the composition.
        ///                        This is relevant only if <paramref name="pitchRangeSource"/> was set to
        ///                        <see cref="PitchRangeSource.Custom"/>.</param>
        /// <param name="maxPitch">Requested highest available pitch that would can be used for the composition.
        ///                        This is relevant only if <paramref name="pitchRangeSource"/> was set to
        ///                        <see cref="PitchRangeSource.Custom"/>.</param>
        /// <param name="useExistingMelodyAsSeed">If set, then the original melody might be used in initialization phase
        ///                                       of the compositon strategy algorithm. This is relevant only if the
        ///                                       midi file contains an existing melody to begin with.
        ///                                       Moreover, this feature is implementation dependent: some strategies
        ///                                       might make high use of original melody as the main initialization seed
        ///                                       and some may not use it at all.</param>
        /// <param name="customParams"> Currently unused. This a placeholder for additional parameters that could be used
        ///                             in the future to extened existing implementations and developing new ones without
        ///                             breaking the existing interface. </param>
        /// <returns></returns>
        public IMidiFile[] Compose(
            CompositionStrategy strategy = CompositionStrategy.GeneticAlgorithmStrategy,
            OverallNoteDurationFeel overallNoteDurationFeel = OverallNoteDurationFeel.Medium,
            MusicalInstrument musicalInstrument             = MusicalInstrument.AcousticGrandPiano,
            PitchRangeSource pitchRangeSource = PitchRangeSource.Custom,
            NotePitch minPitch           = DefaultMinPitch,
            NotePitch maxPitch           = DefaultMaxPitch,
            bool useExistingMelodyAsSeed = true,
            params object[] customParams)
        {
            // set compositor according to composition strategy
            _composer = ComposerFactory.CreateComposer(strategy);

            // make a copy of the input midi file for the output file
            MidiOutputFile = MidiFactory.CreateMidiFile(_midiInputFilePath);

            /* if the midi file already contains a melody track,
             * extract it out of the intended midi output file
             * and if requested, save it in a separate bar sequence for further usage
             * as a melody initialization seed for the composition if needed. */
            if (_melodyTrackIndex.HasValue && _melodyTrackIndex.Value != MelodyTrackIndex.NoMelodyTrackInFile)
            {
                /* if the existing melody should serve as a seed,
                 * initialize a bar-sequence place-holder for it,
                 * based on the chord progression structure */
                _melodySeed = useExistingMelodyAsSeed
                    ? CompositionContext.CloneChordProgressionBars(ChordProgression)
                    : null;

                /* extract/remove the existing melody from midi file and
                 * save it in the place holder if it was initialized */
                MidiOutputFile.ExtractMelodyTrack((byte)_melodyTrackIndex, _melodySeed);
            }

            // initialize pitch range from midi file if requested
            if (pitchRangeSource == PitchRangeSource.MidiFile && _melodyTrackIndex.HasValue)
            {
                NotePitch?lowestPitch, highestPitch;
                MidiInputFile.GetPitchRangeForTrack((int)_melodyTrackIndex, out lowestPitch, out highestPitch);
                if (lowestPitch.HasValue && highestPitch.HasValue)
                {
                    minPitch = (NotePitch)lowestPitch;
                    maxPitch = (NotePitch)highestPitch;
                }
            }

            // validate pitch range is at least one octave long (12 semi-tones)
            if (!IsPitchRangeValid((int)minPitch, (int)maxPitch, out string errorMessage))
            {
                throw new ArgumentException(errorMessage);
            }

            // compose a new melody
            IList <IBar>[] composedMelodies = _composer.Compose(
                chordProgression: ChordProgression,
                melodyInitializationSeed: _melodySeed,
                overallNoteDurationFeel: overallNoteDurationFeel,
                minPitch: minPitch,
                maxPitch: maxPitch,
                customParams: customParams)
                                              .ToArray();

            // Embed each new generated melody into a new separate midi file
            IMidiFile[] midiOutputs = new IMidiFile[composedMelodies.Length];
            for (int i = 0; i < composedMelodies.Length; i++)
            {
                midiOutputs[i] = MidiFactory.CreateMidiFile(_midiInputFilePath);
                midiOutputs[i].ExtractMelodyTrack((byte)_melodyTrackIndex);
                midiOutputs[i].EmbedMelody(composedMelodies[i], musicalInstrument);
                midiOutputs[i].FadeOut();
            }

            // save first output in dedicated placeholder
            MidiOutputFile = midiOutputs[0];

            // return the first composed melody from the output
            return(midiOutputs);
        }