/// <summary> /// Event handler for individual miniGrid (holding 1-4 sounds) in mastergrid /// </summary> void OnMiniGridTapped(MiniGrid miniGrid) { Instrument selectedInstrument = selectedInstrButton.Instrument; // Changes UI represenation and returns new set of colors on this grid bool added = miniGrid.ToggleColor(selectedInstrument.color); // If sidebar color isn't part of button's new set of colors, remove it Instrument.Note toggledNote = selectedInstrument.AtPitch(miniGrid.semitoneShift); //If sidebar button color = clicked button color if (added) { song.AddNote(toggledNote, Grid.GetColumn(miniGrid)); // Add the note if (!player.IsPlaying) { SingleNotePlayer.PlayNote(selectedInstrument.AtPitch(miniGrid.semitoneShift)); // Play note so long as not already playing a song } } else { song.RemoveNote(toggledNote, Grid.GetColumn(miniGrid)); } //Undo clear stops woking when user adds stuff to grid so they don't accidentally undo clear clearedSong = null; loadedSongChanged = true; // If song is empty, no need to worry about changes, otherwise }
/// <summary> /// Plays a single note. Separate from the rest of the song playing code /// </summary> public static void PlayNote(Instrument.Note note) { lock (syncObj) { #if __ANDROID__ if (playingTrack != null) { //We use pause instead of stop because pause stops playing immediately playingTrack.Pause(); playingTrack.Release(); playingTrack.Dispose(); } #endif #if __IOS__ if (audioQueue != null) { //Pass true to stop immediately audioQueue.Stop(true); audioQueue.Dispose(); } #endif #if __ANDROID__ playingTrack = new AudioTrack( // Stream type Android.Media.Stream.Music, // Frequency SongPlayer.PLAYBACK_RATE, // Mono or stereo ChannelOut.Mono, // Audio encoding Android.Media.Encoding.Pcm16bit, // Length of the audio clip in bytes (note.data.Length * 2), // Mode. Stream or static. AudioTrackMode.Static); playingTrack.Write(note.data, 0, note.data.Length); playingTrack.Play(); #endif #if __IOS__ audioQueue = new OutputAudioQueue(AudioStreamBasicDescription.CreateLinearPCM(SongPlayer.PLAYBACK_RATE, 1, 16, false)); unsafe { AudioQueueBuffer *buffer; audioQueue.AllocateBuffer(note.data.Length * 2, out buffer); fixed(short *beatData = note.data) { buffer->CopyToAudioData((IntPtr)beatData, note.data.Length * 2); } audioQueue.EnqueueBuffer((IntPtr)buffer, note.data.Length * 2, null); } audioQueue.Start(); #endif } }
/// <summary> /// Remove a note from the song at a specific beat /// </summary> /// <param name="note">The note to remove from the song</param> /// <param name="beat">The beat to remove the note from</param> public void RemoveNote(Instrument.Note note, int beat) { //We use a lock so that the beat data is not modified while we are removing lock (beats) { beats[beat].Remove(note); } }
/// <summary> /// Add a note to the song at a specific beat /// </summary> /// <param name="note">The note to add to the song</param> /// <param name="beat">The beat to add the note at</param> public void AddNote(Instrument.Note note, int beat) { //We use a lock so that the beat data is not modified while we are adding lock (beats) { beats[beat].Add(note); } }
/// <summary> /// Generates a Song object from a stream containing a text representation of a song /// </summary> public static Song LoadSongFromStream(Stream stream) { //Use StreamReader because the songs are in text foma StreamReader file = new StreamReader(stream); String versionString = file.ReadLine(); if (versionString != "VERSION 1" && versionString != "VERSION 1.0.2") { throw new FileLoadException("Save file is in an invalid format"); } //The first line of a song file contains the number of beats //and the song's instruments. It usually looks something like //16 beats|Snare|BassDrum|Piano|Clap //Get total beats String[] firstLineParts = file.ReadLine().Split('|'); int totalBeats = int.Parse(firstLineParts[0].Split(' ')[0]); //Load instruments Instrument[] songInstruments = new Instrument[firstLineParts.Length - 1]; for (int i = 1; i < firstLineParts.Length; i++) { songInstruments[i - 1] = Instrument.GetByName(firstLineParts[i]); } //Tempo is stored at the end of the file, so for now set the tempo to be -1 Song loadedSong = new Song(totalBeats, songInstruments, -1); //Load notes for (int i = 0; i < totalBeats; i++) { //Each beat starts with a header of the form //Beat <beat number>|<number of notes> String header = file.ReadLine(); int numNotes = int.Parse(header.Split('|')[1]); for (int n = 0; n < numNotes; n++) { //Each note is stored like so: //<instrument name>:<semitone shift> String[] noteStringParts = file.ReadLine().Split(':'); String instrName = noteStringParts[0]; int semitoneShift = int.Parse(noteStringParts[1]); Instrument.Note note = Instrument.GetByName(instrName).AtPitch(semitoneShift); loadedSong.AddNote(note, i); } } //The last line of the file is the tempo loadedSong.Tempo = int.Parse(file.ReadLine()); return(loadedSong); }
/// <summary> /// Returns an array containing the notes at the given beat /// </summary> /// <param name="beat">The beat to get the notes from</param> /// <returns>The notes at <code>beat</code></returns> public Instrument.Note[] NotesAtBeat(int beat) { Instrument.Note[] notes; //Lock so that the notes do not change while they are being copied lock (beats) { notes = new Instrument.Note[beats[beat].Count]; beats[beat].CopyTo(notes); } return(notes); }
/// <summary> /// Replaces the instruments listed in oldInstruments with the respective instrument in newInstruments /// </summary> public void ReplaceInstruments(IList <Instrument> oldInstruments, IList <Instrument> newInstruments) { //Lock so that the beats do not change while they are being modified lock (beats) { foreach (HashSet <Instrument.Note> beat in beats) { List <Instrument.Note> oldNotes = beat.ToList(); beat.Clear(); foreach (Instrument.Note oldNote in oldNotes) { //If this note's instrument is in oldInstrument, replace it with a note //of the same pitch but from the new instrument int index = oldInstruments.IndexOf(oldNote.instrument); if (index < 0) { //Not supposed to be replaced, so just readd it. beat.Add(oldNote); } else { //Replace with new note Instrument.Note newNote = newInstruments[index].AtPitch(oldNote.semitoneShift); beat.Add(newNote); } } } //Update Instruments for (int i = 0; i < oldInstruments.Count; i++) { Instrument oldInstr = oldInstruments[i]; Instruments[Array.IndexOf(Instruments, oldInstr)] = newInstruments[i]; } } }
public PlayingNote(Instrument.Note note, int startSample) { this.note = note; this.startSample = startSample; }