public void Draw(Rect displayRect, WaveDisplayState displayState) { if (displayState.displayType != WaveDisplayType.Line && displayState.samplesPerPixel != cachedSamplesPerPixel) { // New list instead? Clear takes O(n) time, whereas creating a new list may be faster. // Would probably thrash memory more, though. System.Array.Clear(cachedData, 0, cachedData.Length); cachedSamplesPerPixel = displayState.samplesPerPixel; } switch (displayState.displayType) { case WaveDisplayType.Line: DrawWaves(displayRect, displayState); break; case WaveDisplayType.MinMax: DrawMinMax(displayRect, displayState); break; case WaveDisplayType.RMS: DrawRMS(displayRect, displayState); break; case WaveDisplayType.Both: DrawMinMax(displayRect, displayState); DrawRMS(displayRect, displayState); break; } }
void DrawMinMax(Rect waveArea, WaveDisplayState displayState) { UnityEditor.Handles.color = new Color(1f, 104f / 255f, 0f, KoreographerColors.HandleFullAlpha); int amplitude = (int)(channelAmplitudePercent * (waveArea.height / 2f)); Vector2 startPoint = Vector2.zero; Vector2 endPoint = Vector2.zero; float minSample, maxSample, curSample; int sampleIdxForPixel; int cacheIdxOffset = displayState.firstSamplePackToDraw / displayState.samplesPerPixel; for (int i = 0; i < waveArea.width && displayState.firstSamplePackToDraw + (i * displayState.samplesPerPixel) < sampleData.Length; ++i) { WaveformCacheEntry entry; if (cachedData[cacheIdxOffset + i] == null) { entry = new WaveformCacheEntry(); cachedData[cacheIdxOffset + i] = entry; } else { entry = cachedData[cacheIdxOffset + i]; } if (!entry.minMaxValid) { minSample = 1f; maxSample = -1f; sampleIdxForPixel = i * displayState.samplesPerPixel; for (int j = 0; j < displayState.samplesPerPixel && displayState.firstSamplePackToDraw + sampleIdxForPixel + j < sampleData.Length; j++) { curSample = sampleData[displayState.firstSamplePackToDraw + sampleIdxForPixel + j]; minSample = Mathf.Min(minSample, curSample); maxSample = Mathf.Max(maxSample, curSample); } // Subtract because positive is down! entry.minMaxValues.x = waveArea.center.y - (maxSample * amplitude); entry.minMaxValues.y = waveArea.center.y - (minSample * amplitude); // Update the cache entry! entry.minMaxValid = true; } // Draw a vertical line. startPoint.x = waveArea.x + i; endPoint.x = startPoint.x; startPoint.y = entry.minMaxValues.x; endPoint.y = entry.minMaxValues.y; UnityEditor.Handles.DrawLine(startPoint, endPoint); } }
void DrawRMS(Rect waveArea, WaveDisplayState displayState) { UnityEditor.Handles.color = new Color(1f, 0.58431f, 0f, KoreographerColors.HandleFullAlpha); int amplitude = (int)(channelAmplitudePercent * (waveArea.height / 2f)); Vector2 startPoint = Vector2.zero; Vector2 endPoint = Vector2.zero; float curSample, peak; int sampleIdxForPixel; int cacheIdxOffset = displayState.firstSamplePackToDraw / displayState.samplesPerPixel; // Calculate the waveform via RMS! for (int i = 0; i < waveArea.width && displayState.firstSamplePackToDraw + (i * displayState.samplesPerPixel) < sampleData.Length; ++i) { WaveformCacheEntry entry; if (cachedData[cacheIdxOffset + i] == null) { entry = new WaveformCacheEntry(); cachedData[cacheIdxOffset + i] = entry; } else { entry = cachedData[cacheIdxOffset + i]; } if (!entry.rmsValid) { peak = 0; sampleIdxForPixel = i * displayState.samplesPerPixel; for (int j = 0; j < displayState.samplesPerPixel && displayState.firstSamplePackToDraw + sampleIdxForPixel + j < sampleData.Length; j++) { curSample = sampleData[displayState.firstSamplePackToDraw + sampleIdxForPixel + j]; peak += (curSample * curSample); } peak = Mathf.Sqrt(peak / (float)displayState.samplesPerPixel); entry.rmsValues.x = waveArea.center.y - (peak * amplitude); entry.rmsValues.y = waveArea.center.y + (peak * amplitude); // Update the cache entry! entry.rmsValid = true; } // Draw vertical lines. startPoint.x = waveArea.x + i; endPoint.x = startPoint.x; startPoint.y = entry.rmsValues.x; endPoint.y = entry.rmsValues.y; UnityEditor.Handles.DrawLine(startPoint, endPoint); } }
void DrawWaves(Rect waveArea, WaveDisplayState displayState) { UnityEditor.Handles.color = new Color(1f, 149f / 255f, 0f, KoreographerColors.HandleFullAlpha); int startSample = displayState.firstSamplePackToDraw; int amplitude = (int)(channelAmplitudePercent * (waveArea.height / 2f)); Vector2 startPoint = Vector2.zero; Vector2 endPoint = Vector2.zero; float lastY = waveArea.center.y + (sampleData[startSample] * amplitude); for (int i = 1; i < waveArea.width && i + startSample < sampleData.Length; ++i) { endPoint.x = waveArea.x + i; startPoint.x = endPoint.x - 1; // Back us up by one! // Get y's for left channel. startPoint.y = lastY; endPoint.y = waveArea.center.y + (sampleData[startSample + i] * amplitude); UnityEditor.Handles.DrawLine(startPoint, endPoint); // Store previous y for next time! lastY = endPoint.y; } }
float GetDrawStart(WaveDisplayState displayState) { // TODO: Embed this info into the skin! This was reconstructed from Draw(). return GUI.skin.box.padding.left + GUI.skin.box.margin.left + displayState.drawStartOffsetInPixels; }
public void Draw(Rect displayRect, WaveDisplayState displayState, Koreography koreo, bool bShowPlayhead, List<KoreographyEvent> selectedEvents) { // Draw background. Color originalBG = GUI.backgroundColor; GUI.backgroundColor = mainBGColor; GUI.Box(displayRect, ""); GUI.backgroundColor = originalBG; GUI.BeginGroup(displayRect); // Calculate drawing metrics for channels. float left = GUI.skin.box.padding.left + 1f; float top = GUI.skin.box.padding.top; float width = GetChannelPixelWidthForWindow((int)displayRect.width); float height = (displayRect.height - GUI.skin.box.padding.vertical) / MAX_CHANNELS_TO_DRAW; Rect contentRect = new Rect(left + displayState.drawStartOffsetInPixels, top, width - displayState.drawStartOffsetInPixels, displayRect.height - GUI.skin.box.padding.vertical); // Adjust for start offset! Rect channelRect = new Rect(left + displayState.drawStartOffsetInPixels, top, width - displayState.drawStartOffsetInPixels, height); // Draw the beat markers before the actual audio content. // NOTE: This contains GUI code. EveytType.Repaint optimizations handled internally. DrawBeatLines(contentRect, displayState, koreo); // Only process this drawing if we're repainting (DOES NOT USE GUI SYSTEM). if (Event.current.type.Equals(EventType.Repaint)) { // Draw Channels (waveforms) for (int i = 0; i < channelDisplays.Count; ++i) { channelRect.y += i * height; // Draw ZERO Line Handles.color = new Color(0f, 0f, 0f, KoreographerColors.HandleFullAlpha); Handles.DrawLine(new Vector2(channelRect.x, channelRect.center.y), new Vector2(channelRect.x + channelRect.width, channelRect.center.y)); // Draw Channel Content channelDisplays[i].Draw(channelRect, displayState); } } // Draw Tracks (events) if (trackDisplay.EventTrack != null) { Rect trackRect = new Rect(channelRect.x, contentRect.center.y - 12f, channelRect.width, 24f); trackDisplay.Draw(trackRect, displayState, selectedEvents); } // Only process this drawing if we're repainting (DOES NOT USE GUI SYSTEM). if (Event.current.type.Equals(EventType.Repaint) && bShowPlayhead) { // Draw overlays if (displayState.playheadSamplePosition >= displayState.firstSamplePackToDraw && displayState.playheadSamplePosition <= displayState.firstSamplePackToDraw + (width * displayState.samplesPerPixel)) { // Make the playhead position flexible to allow for scrolling (while maintaining playhead position). int position = displayState.drawStartOffsetInPixels + ((displayState.playheadSamplePosition - displayState.firstSamplePackToDraw) / displayState.samplesPerPixel); DrawPlayheadLine((int)left + position, (int)top, (int)(top + (2f * height))); } } if (Event.current.type == EventType.Repaint) { // Store rect. This must be done during Repaint as the values are not properly handled on Layout. waveContentRect = displayRect; } GUI.EndGroup(); }
void DrawBeatLinesForSection(Rect contentRect, WaveDisplayState displayState, TempoSectionDef tempoSection, int startSample, int endSample, Color sectionColor) { // Only draw the lines if our current zoom level is reasonable. if (tempoSection.SamplesPerBeat >= displayState.samplesPerPixel * 2) // Check that we will not just be drawing a line (or multiple lines!) for each pixel. Require at least one gap. { // Draw our background box. { Color bgColor = GUI.backgroundColor; GUI.backgroundColor = sectionColor; Rect boxRect = new Rect(contentRect); boxRect.xMin += (float)(startSample - displayState.firstSamplePackToDraw) / displayState.samplesPerPixel; boxRect.xMax -= contentRect.width - (float)(endSample - displayState.firstSamplePackToDraw) / displayState.samplesPerPixel; GUI.Box(boxRect, ""); GUI.backgroundColor = bgColor; } if (Event.current.type.Equals(EventType.Repaint)) { // Set us up to draw the first beat. Initially, assume we start somewhere within the view. float lineLoc = (float)tempoSection.StartSample - displayState.firstSamplePackToDraw; int beatNum = 0; // Get us onto the current beat boundary if our content begins beyond that first beat. if (startSample > tempoSection.StartSample) { lineLoc %= tempoSection.SamplesPerBeat; beatNum = ((int)((float)(startSample - tempoSection.StartSample) / tempoSection.SamplesPerBeat)); } float grayValue = 170f / 255f; Color firstBeatColor = new Color(grayValue, grayValue, grayValue, KoreographerColors.HandleFullAlpha); grayValue = 96f / 255f; Color normalBeatColor = new Color(grayValue, grayValue, grayValue, KoreographerColors.HandleFullAlpha); // Draw all the beat lines! for (; lineLoc < (float)endSample - displayState.firstSamplePackToDraw; lineLoc += tempoSection.SamplesPerBeat) { int x = (int)(contentRect.x + (lineLoc / displayState.samplesPerPixel)); Handles.color = (beatNum % tempoSection.BeatsPerMeasure == 0) ? firstBeatColor : normalBeatColor; Handles.DrawLine(new Vector2(x, contentRect.yMin), new Vector2(x, contentRect.yMax)); // Increment the beat count! beatNum++; } } } }
void DrawBeatLines(Rect contentRect, WaveDisplayState displayState, Koreography koreo) { int startSample = displayState.firstSamplePackToDraw; int endSample = startSample + displayState.samplesPerPixel * (int)contentRect.width; int startSectionIdx = koreo.GetTempoSectionIndexForSample(startSample); int endSectionIdx = koreo.GetTempoSectionIndexForSample(endSample); TempoSectionDef drawSection = koreo.GetTempoSectionAtIndex(startSectionIdx); if (startSectionIdx < endSectionIdx) { // Multiple sections to draw! for (int i = startSectionIdx + 1; i <= endSectionIdx; ++i) { TempoSectionDef nextSection = koreo.GetTempoSectionAtIndex(i); DrawBeatLinesForSection(contentRect, displayState, drawSection, startSample, nextSection.StartSample, sectionBGColors[(i - 1) % sectionBGColors.Length]); // Set up for the next section! startSample = nextSection.StartSample; drawSection = nextSection; } } // Draw the lines for the final (or only) section. DrawBeatLinesForSection(contentRect, displayState, drawSection, startSample, endSample, sectionBGColors[endSectionIdx % sectionBGColors.Length]); }
public int GetSamplePositionOfPoint(Vector2 loc, WaveDisplayState displayState) { float drawStart = GetDrawStart(displayState); float distFromContentStart = loc.x - waveContentRect.x; int samplePos = displayState.firstSamplePackToDraw + displayState.samplesPerPixel * (int)(distFromContentStart - drawStart); // Disallow negative numbers! return Mathf.Max(samplePos, 0); }
public float GetHorizontalLocOfSample(int samplePos, WaveDisplayState displayState) { float pixelsIn = (float)(samplePos - displayState.firstSamplePackToDraw) / (float)displayState.samplesPerPixel; return pixelsIn + GetDrawStart(displayState); }
public void Draw(Rect displayRect, WaveDisplayState displayState, List<KoreographyEvent> selectedEvents) { if (eventTrack != null) { int rangeStart = displayState.firstSamplePackToDraw; int rangeEnd = rangeStart + ((int)displayRect.width * displayState.samplesPerPixel); List<KoreographyEvent> drawEvents = eventTrack.GetEventsInRange(rangeStart, rangeEnd); // In case we want to change things later. Rect eventRect = new Rect(displayRect); int xStart, xEnd; // Range [0, displayRect.width]. These are offsets from displayRect.x! if (Event.current.type == EventType.Repaint) { eventDisplays.Clear(); } foreach (KoreographyEvent e in drawEvents) { // Sample-space to pixel-space. xStart = (e.StartSample - rangeStart) / displayState.samplesPerPixel; xEnd = (e.EndSample - rangeStart) / displayState.samplesPerPixel; eventRect.xMin = displayRect.x + xStart; eventRect.xMax = displayRect.x + xEnd; EventDisplay.ValidateDisplayRect(ref eventRect); EventDisplay.Draw(eventRect, eventTrack, e, selectedEvents.Contains(e)); // Do this only during Repaint to cut down on extra processing. if (Event.current.type == EventType.Repaint) { // Add a little bit to either side. eventRect.width += 3f; eventRect.x -= 1.5f; Rect[] rectSet; if (e.IsOneOff() || eventRect.width <= 12f) { // Switch between resize and move modes. if (Event.current.alt) { Rect leftRect = new Rect(eventRect); Rect rightRect = new Rect(eventRect); leftRect.xMax = leftRect.center.x; rightRect.xMin = rightRect.center.x; EditorGUIUtility.AddCursorRect(leftRect, MouseCursor.ResizeHorizontal); EditorGUIUtility.AddCursorRect(rightRect, MouseCursor.ResizeHorizontal); rectSet = new Rect[4]{eventRect, leftRect, rightRect, new Rect()}; } else { // Default to move only. EditorGUIUtility.AddCursorRect(eventRect, MouseCursor.MoveArrow); rectSet = new Rect[4]{eventRect, new Rect(), new Rect(), eventRect}; } } else { // Cursor Left: Rect leftRect = new Rect(eventRect); Rect centRect = new Rect(eventRect); Rect rightRect = new Rect(eventRect); float resizeRectWidth = 3f; leftRect.xMax = leftRect.xMin + resizeRectWidth; rightRect.xMin = rightRect.xMax - resizeRectWidth; // Etc. centRect.xMin = leftRect.xMax; centRect.xMax = rightRect.xMin; EditorGUIUtility.AddCursorRect(leftRect, MouseCursor.ResizeHorizontal); EditorGUIUtility.AddCursorRect(rightRect, MouseCursor.ResizeHorizontal); EditorGUIUtility.AddCursorRect(centRect, MouseCursor.MoveArrow); // Store the rects! rectSet = new Rect[4]{eventRect, leftRect, rightRect, centRect}; } eventDisplays[e] = rectSet; } } if (Event.current.type == EventType.Repaint) { // Store our rect. trackContentRect = displayRect; } } }