Download Now
Forums

    Synthwave

    For the past few weeks I've been working on writing and testing an object oriented encapsulation of the free open source SDL and NanoVG libraries.
    Below is a short video clip of one of my demos, the Synthwave demo. This demo evokes the style of the 1980s synthwave music culture using Free Pascal. The user interface elements are also rendered using vector graphics.

    Video

    Here is a video capture of the Synthwave vector graphics demo.
    Video: Synthwave


    Source Code

    The following is a complete listing of the Sythwave demo.
    unit Demo.Synthwave;
    
    {$mode delphi}
    
    interface
    
    uses
      Tiny.System, Tiny.Types, Tiny.Geometry, Tiny.Application, Tiny.Graphics,
      Demo.Scene;
    
    { TSynthwave }
    
    type
      TPoints = TArrayList<TVec3>;
      TPointList = TArrayList<TPoints>;
    
      TSynthwave = class(TDemoScene)
      private
        FPoints: TPointList;
        FLast: Double;
        FSpeed: Float;
      protected
        function GetTitle: string; override;
        function GetDescription: string; override;
        function GetGlyph: string; override;
        function RangeGenerate: TRangeInfo; override;
        procedure RangeChanged(NewPosition: Float); override;
        procedure Load; override;
        procedure Render(Width, Height: Integer; const Time: Double); override;
      end;
    
    implementation
    
    const
      ListLength = 30;
      PointsLength = 60;
      Spacing = 40;
      Offset = PointsLength * Spacing / 2;
      Depth = 300;
    
    function TSynthwave.GetTitle: string;
    begin
      Result := 'Synthwave';
    end;
    
    function TSynthwave.GetDescription: string;
    begin
      Result := 'Synthwave stems from a microgenre of electronic music drawing predominantly from 1980s culture.';
    end;
    
    function TSynthwave.GetGlyph: string;
    begin
      Result := '󱑽';
    end;
    
    function TSynthwave.RangeGenerate: TRangeInfo;
    begin
      Result := TRangeInfo.Create('Driving speed', FSpeed, 0.001, 0, 10);
    end;
    
    procedure TSynthwave.RangeChanged(NewPosition: Float);
    begin
      FSpeed := NewPosition;
    end;
    
    procedure TSynthwave.Load;
    var
      PointData: TPoints;
      A, I, J: Integer;
    begin
      inherited Load;
      { Reset the speed timer }
      FLast := 0;
      if FPoints.Length = 0 then
        FSpeed := 1;
      FPoints.Length := ListLength;
      { Generate our ground geometry }
      for I := 0 to ListLength - 1 do
      begin
        PointData := nil;
        PointData.Length := PointsLength;
        for J := 0 to PointsLength - 1 do
        begin
          A := Abs(J - ListLength);
          PointData[J] := Vec3(J * Spacing, Random * -(A * A) + ListLength, I * -10);
        end;
        FPoints[I] := PointData;
      end;
    end;
    
    procedure TSynthwave.Render(Width, Height: Integer; const Time: Double);
    const
    { Skip allows for reduction in overall scene geometry }
      Skip = 6;
      colorDusk = $FF6E073A;
      colorCorona = $00FF0000;
    var
      Brush: IGradientBrush;
      Rect: TRectF;
      Delta: Double;
      Reset: Boolean;
      A, B: Float;
      I, J: Integer;
    begin
      inherited Render(Width, Height, Time);
      { Draw background }
      Brush := NewBrush(NewPointF(0, Height * 0.6), NewPointF(0, 0));
      Brush.NearStop.Color := colorDusk;
      Brush.FarStop.Color := colorBlack;
      Canvas.Rect(ClientRect);
      Canvas.Fill(Brush);
      { Draw corona }
      Rect := NewRectF(350, 350);
      Rect.X := Width / 2 - Rect.Width / 2;
      Rect.Y := Height / 2 - Rect.Height / 2;
      Brush := NewBrush(Rect);
      Brush.FarStop.Offset := 0.9;
      Brush.NearStop.Color := colorOrange;
      Brush.NearStop.Offset := 0.1;
      Brush.FarStop.Color := colorCorona;
      Canvas.Circle(Width / 2, Height / 2, 350);
      Canvas.Fill(Brush);
      { Draw sun }
      Brush := NewBrush(NewPointF(0, Height * 0.7), NewPointF(0, Height * 0.3));
      Brush.NearStop.Color := colorRed;
      Brush.FarStop.Color := colorYellow;
      Canvas.Circle(Width / 2, Height / 2, 225);
      Canvas.Fill(Brush);
      { Adjust for speed }
      Delta := (Time - FLast) * 50 * FSpeed;
      FLast := Time;
      { Check if we need to rebuild next row }
      for I := 0 to ListLength - 1 do
      begin
        Reset := False;
        for J := Skip to PointsLength - (Skip + 1) do
        begin
          FPoints[I].Items[J].Z := FPoints[I].Items[J].Z - 0.5 * Delta;
          if FPoints[I].Items[J].Z < -Depth then
          begin
            Reset := True;
            Break;
          end;
        end;
        { Rebuild row }
        if Reset then
        begin
          FPoints.Move(ListLength - 1, 0);
          for J := Skip to PointsLength - (Skip + 1) do
          begin
            A := Abs(J - ListLength);
            FPoints[0].Items[J].Z := 0;
            FPoints[0].Items[J].Y := Random * -(A * A) + ListLength;
          end;
        end;
      end;
      { Draw the ground grid }
      Canvas.Matrix.Translate(Width / 2, Height / 2);
      for I := 0 to ListLength - 2 do
        for J := Skip to PointsLength - (Skip + 2) do
        begin
          A := Depth / (Depth + FPoints[I][J].Z);
          B := Depth / (Depth + FPoints[I + 1][J].Z);
          Canvas.MoveTo((FPoints[I][J].X - Offset) * A, FPoints[I][J].Y * A);
          Canvas.LineTo((FPoints[I][J + 1].X - Offset) * A, FPoints[I][J + 1].Y * A);
          Canvas.LineTo((FPoints[I + 1][J + 1].X - Offset) * B, FPoints[I + 1][J + 1].Y * B);
          Canvas.LineTo((FPoints[I + 1][J].X - Offset) * B, FPoints[I + 1][J].Y * B);
          { Fill and stroke }
          Canvas.Fill(NewColorF(0, 0, 0, -FPoints[I][J].Z / 100), True);
          A := Depth + FPoints[I][J].Z;
          Canvas.Stroke(NewColorF((250 - A) / 255, 0, (50 + A) / 255, 1 - A / 300), 2);
        end;
    end;
    
    end.