private void ComputeChannelsScroll(VideoFrameMetadata[] frames, int channelMask, int numVisibleNotes) { var numFrames = frames.Length; var numChannels = frames[0].channelNotes.Length; for (int c = 0; c < numChannels; c++) { if ((channelMask & (1 << c)) == 0) { continue; } // Go through all the frames and split them in segments. // A segment is a section of the song where all the notes fit in the view. var segments = new List <ScrollSegment>(); var currentSegment = (ScrollSegment)null; var minOverallNote = int.MaxValue; var maxOverallNote = int.MinValue; for (int f = 0; f < numFrames; f++) { var frame = frames[f]; var note = frame.channelNotes[c]; if (frame.scroll == null) { frame.scroll = new float[numChannels]; } if (note.IsMusical) { if (currentSegment == null) { currentSegment = new ScrollSegment(); segments.Add(currentSegment); } // If its the start of a new pattern and we've been not moving for ~10 sec, let's start a new segment. bool forceNewSegment = frame.playNote == 0 && (f - currentSegment.startFrame) > 600; var minNoteValue = note.Value - 1; var maxNoteValue = note.Value + 1; // Only consider slides if they arent too large. if (note.IsSlideNote && Math.Abs(note.SlideNoteTarget - note.Value) < numVisibleNotes / 2) { minNoteValue = Math.Min(note.Value, note.SlideNoteTarget) - 1; maxNoteValue = Math.Max(note.Value, note.SlideNoteTarget) + 1; } // Only consider arpeggios if they are not too big. if (note.IsArpeggio && note.Arpeggio.GetChordMinMaxOffset(out var minArp, out var maxArp) && maxArp - minArp < numVisibleNotes / 2) { minNoteValue = note.Value + minArp; maxNoteValue = note.Value + maxArp; } minOverallNote = Math.Min(minOverallNote, minNoteValue); maxOverallNote = Math.Max(maxOverallNote, maxNoteValue); var newMinNote = Math.Min(currentSegment.minNote, minNoteValue); var newMaxNote = Math.Max(currentSegment.maxNote, maxNoteValue); // If we cant fit the next note in the view, start a new segment. if (forceNewSegment || newMaxNote - newMinNote + 1 > numVisibleNotes) { currentSegment.endFrame = f; currentSegment = new ScrollSegment(); currentSegment.startFrame = f; segments.Add(currentSegment); currentSegment.minNote = minNoteValue; currentSegment.maxNote = maxNoteValue; } else { currentSegment.minNote = newMinNote; currentSegment.maxNote = newMaxNote; } } } // Not a single notes in this channel... if (currentSegment == null) { currentSegment = new ScrollSegment(); currentSegment.minNote = Note.FromFriendlyName("C4"); currentSegment.maxNote = currentSegment.minNote; segments.Add(currentSegment); } currentSegment.endFrame = numFrames; // Remove very small segments, these make the camera move too fast, looks bad. var shortestAllowedSegment = SegmentTransitionNumFrames * 2; bool removed = false; do { var sortedSegment = new List <ScrollSegment>(segments); sortedSegment.Sort((s1, s2) => s1.NumFrames.CompareTo(s2.NumFrames)); if (sortedSegment[0].NumFrames >= shortestAllowedSegment) { break; } for (int s = 0; s < sortedSegment.Count; s++) { var seg = sortedSegment[s]; if (seg.NumFrames >= shortestAllowedSegment) { break; } var thisSegmentIndex = segments.IndexOf(seg); // Segment is too short, see if we can merge with previous/next one. var mergeSegmentIndex = -1; var mergeSegmentLength = -1; if (thisSegmentIndex > 0) { mergeSegmentIndex = thisSegmentIndex - 1; mergeSegmentLength = segments[thisSegmentIndex - 1].NumFrames; } if (thisSegmentIndex != segments.Count - 1 && segments[thisSegmentIndex + 1].NumFrames > mergeSegmentLength) { mergeSegmentIndex = thisSegmentIndex + 1; mergeSegmentLength = segments[thisSegmentIndex + 1].NumFrames; } if (mergeSegmentIndex >= 0) { // Merge. var mergeSeg = segments[mergeSegmentIndex]; mergeSeg.startFrame = Math.Min(mergeSeg.startFrame, seg.startFrame); mergeSeg.endFrame = Math.Max(mergeSeg.endFrame, seg.endFrame); segments.RemoveAt(thisSegmentIndex); removed = true; break; } } }while (removed); // Build the actually scrolling data. var minScroll = (float)Math.Ceiling(Note.MusicalNoteMin + numVisibleNotes * 0.5f); var maxScroll = (float)Math.Floor(Note.MusicalNoteMax - numVisibleNotes * 0.5f); Debug.Assert(maxScroll >= minScroll); foreach (var segment in segments) { segment.scroll = Utils.Clamp(segment.minNote + (segment.maxNote - segment.minNote) * 0.5f, minScroll, maxScroll); } for (var s = 0; s < segments.Count; s++) { var segment0 = segments[s + 0]; var segment1 = s == segments.Count - 1 ? null : segments[s + 1]; for (int f = segment0.startFrame; f < segment0.endFrame - (segment1 == null ? 0 : SegmentTransitionNumFrames); f++) { frames[f].scroll[c] = segment0.scroll; } if (segment1 != null) { // Smooth transition to next segment. for (int f = segment0.endFrame - SegmentTransitionNumFrames, a = 0; f < segment0.endFrame; f++, a++) { var lerp = a / (float)SegmentTransitionNumFrames; frames[f].scroll[c] = Utils.Lerp(segment0.scroll, segment1.scroll, Utils.SmootherStep(lerp)); } } } } }
private void ReadInstrument2A03(Instrument instrument, int instIdx, ref int idx) { ReadCommonEnvelopes(instrument, instIdx, ref idx, envelopes); for (int i = 0; i < OctaveRange; ++i) { for (int j = 0; j < 12; ++j) { var index = bytes[idx++]; var pitch = bytes[idx++]; if (blockVersion > 5) { idx++; // sample delta } if (index > 0 && pitch != 0) { var sample = samples[index - 1]; var note = i * 12 + j + 1; if (sample != null && sample.ProcessedData != null) { if (project.NoteSupportsDPCM(note)) { if (project.GetDPCMMapping(note) == null) { project.MapDPCMSample(note, sample, pitch & 0x0f, (pitch & 0x80) != 0); } else { Log.LogMessage(LogSeverity.Warning, $"Multiple instruments assigning DPCM samples to key {Note.GetFriendlyName(note)}. Only the first one will be assigned, others will be loaded, but unassigned."); } } else { Log.LogMessage(LogSeverity.Warning, $"DPCM sample assigned to key {Note.GetFriendlyName(note)}. FamiStudio only supports DPCM samples on keys {Note.GetFriendlyName(Note.DPCMNoteMin + 1)} to {Note.GetFriendlyName(Note.DPCMNoteMax)}."); } } } } } }
public void PlayNote(Note newNote) { if (!newNote.HasFinePitch) { newNote.FinePitch = 0; } if (newNote.IsRelease) { // Channels with custom release code will do their own thing. if (customRelease) { note = newNote; } else { if (note.Instrument != null) { for (int j = 0; j < Envelope.Count; j++) { if (envelopes[j] != null && envelopes[j].Release >= 0) { envelopeIdx[j] = envelopes[j].Release; } } } } } else { bool instrumentChanged = note.Instrument != newNote.Instrument; bool arpeggioChanged = note.Arpeggio != newNote.Arpeggio; note = newNote; if (note.IsMusical) { // Set/clear override when changing arpeggio if (arpeggioChanged) { if (note.Arpeggio != null) { envelopes[Envelope.Arpeggio] = note.Arpeggio.Envelope; arpeggioEnvelopeOverride = true; } else { envelopes[Envelope.Arpeggio] = null; arpeggioEnvelopeOverride = false; } envelopeIdx[Envelope.Arpeggio] = 0; envelopeValues[Envelope.Arpeggio] = 0; } // If same arpeggio, but note has an attack, reset it. else if (note.HasAttack && arpeggioEnvelopeOverride) { envelopeIdx[Envelope.Arpeggio] = 0; envelopeValues[Envelope.Arpeggio] = 0; } } if (instrumentChanged || note.HasAttack && !note.IsStop) { for (int j = 0; j < Envelope.Count; j++) { if ((j != Envelope.Pitch || !pitchEnvelopeOverride) && (j != Envelope.Arpeggio || !arpeggioEnvelopeOverride)) { envelopes[j] = note.Instrument == null ? null : note.Instrument.Envelopes[j]; } envelopeIdx[j] = 0; } envelopeValues[Envelope.Pitch] = 0; // In case we use relative envelopes. noteTriggered = true; } if (instrumentChanged) { LoadInstrument(note.Instrument); } } }
private unsafe RenderBitmap GetPatternBitmapFromCache(RenderGraphics g, Pattern p) { int patternSizeX = Song.PatternLength - 1; int patternSizeY = trackSizeY - patternHeaderSizeY - 1; RenderBitmap bmp; if (patternBitmapCache.TryGetValue(p.Id, out bmp)) { if (bmp.Size.Width == patternSizeX) { return(bmp); } else { patternBitmapCache.Remove(p.Id); bmp.Dispose(); bmp = null; } } uint[] data = new uint[patternSizeX * patternSizeY]; Note minNote; Note maxNote; if (p.GetMinMaxNote(out minNote, out maxNote)) { if (maxNote.Value == minNote.Value) { minNote.Value = (byte)(minNote.Value - 5); maxNote.Value = (byte)(maxNote.Value + 5); } else { minNote.Value = (byte)(minNote.Value - 2); maxNote.Value = (byte)(maxNote.Value + 2); } Note lastValid = new Note { Value = Note.NoteInvalid }; for (int i = 0; i < Song.PatternLength - 1; i++) // TODO: We always skip the last note. { var n = p.Notes[i]; if (n.IsValid && !n.IsStop) { lastValid = p.Notes[i]; } if (lastValid.IsValid) { float scaleY = (patternSizeY - noteSizeY) / (float)patternSizeY; int x = i; int y = Math.Min((int)Math.Round((lastValid.Value - minNote.Value) / (float)(maxNote.Value - minNote.Value) * scaleY * patternSizeY), patternSizeY - noteSizeY); var instrument = lastValid.Instrument; var color = instrument == null ? ThemeBase.LightGreyFillColor1 : instrument.Color; for (int j = 0; j < noteSizeY; j++) { data[(patternSizeY - 1 - (y + j)) * patternSizeX + x] = (uint)color.ToArgb(); } } //if (n.HasEffect) //{ // for (int y = 0; y < patternSizeY; y++) // { // data[y * patternSizeX + i] = 0xff000000; // } //} } } bmp = g.CreateBitmap(patternSizeX, patternSizeY, data); patternBitmapCache[p.Id] = bmp; return(bmp); }