
commit
4136a6aed7
38 changed files with 9731 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||
*.out |
|||
*.gch |
|||
*.json |
|||
tgvs |
@ -0,0 +1,251 @@ |
|||
?<html> |
|||
<head> |
|||
<title>The Code Project Open License (CPOL)</title> |
|||
<Style> |
|||
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt } |
|||
H1,H2,H3,H4,H5 { color: #ff9900; font-weight: bold; } |
|||
H1 { font-size: 14pt;color:black } |
|||
H2 { font-size: 13pt; } |
|||
H3 { font-size: 12pt; } |
|||
H4 { font-size: 10pt; color: black; } |
|||
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; } |
|||
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; } |
|||
.SpacedList li { padding: 5px 0px 5px 0px;} |
|||
</style> |
|||
</head> |
|||
<body bgcolor="#FFFFFF" color=#000000> |
|||
|
|||
<h1>The Code Project Open License (CPOL) 1.02</h1> |
|||
<br /> |
|||
|
|||
<center> |
|||
<div style="text-align: left; border: 2px solid #000000; width: 660; background-color: #FFFFD9; padding: 20px;"> |
|||
|
|||
<h2>Preamble</h2> |
|||
<p> |
|||
This License governs Your use of the Work. This License is intended to allow developers |
|||
to use the Source Code and Executable Files provided as part of the Work in any |
|||
application in any form. |
|||
</p> |
|||
<p> |
|||
The main points subject to the terms of the License are:</p> |
|||
<ul> |
|||
<li>Source Code and Executable Files can be used in commercial applications;</li> |
|||
<li>Source Code and Executable Files can be redistributed; and</li> |
|||
<li>Source Code can be modified to create derivative works.</li> |
|||
<li>No claim of suitability, guarantee, or any warranty whatsoever is provided. The software is |
|||
provided "as-is".</li> |
|||
<li>The Article accompanying the Work may not be distributed or republished without the |
|||
Author's consent</li> |
|||
</ul> |
|||
|
|||
<p> |
|||
This License is entered between You, the individual or other entity reading or otherwise |
|||
making use of the Work licensed pursuant to this License and the individual or other |
|||
entity which offers the Work under the terms of this License ("Author").</p> |
|||
|
|||
<h2>License</h2> |
|||
<p> |
|||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CODE PROJECT OPEN |
|||
LICENSE ("LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE |
|||
LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT |
|||
LAW IS PROHIBITED.</p> |
|||
<p> |
|||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HEREIN, YOU ACCEPT AND AGREE TO BE |
|||
BOUND BY THE TERMS OF THIS LICENSE. THE AUTHOR GRANTS YOU THE RIGHTS CONTAINED HEREIN |
|||
IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. IF YOU DO NOT |
|||
AGREE TO ACCEPT AND BE BOUND BY THE TERMS OF THIS LICENSE, YOU CANNOT MAKE ANY |
|||
USE OF THE WORK.</p> |
|||
|
|||
<ol class="SpacedList"> |
|||
<li><strong>Definitions.</strong> |
|||
|
|||
<ol class="SpacedList" style="list-style-type: lower-alpha;"> |
|||
<li><strong>"Articles"</strong> means, collectively, all articles written by Author |
|||
which describes how the Source Code and Executable Files for the Work may be used |
|||
by a user.</li> |
|||
<li><b>"Author"</b> means the individual or entity that offers the Work under the terms |
|||
of this License.<strong></strong></li> |
|||
<li><strong>"Derivative Work"</strong> means a work based upon the Work or upon the |
|||
Work and other pre-existing works.</li> |
|||
<li><b>"Executable Files"</b> refer to the executables, binary files, configuration |
|||
and any required data files included in the Work.</li> |
|||
<li>"<b>Publisher</b>" means the provider of the website, magazine, CD-ROM, DVD or other |
|||
medium from or by which the Work is obtained by You.</li> |
|||
<li><b>"Source Code"</b> refers to the collection of source code and configuration files |
|||
used to create the Executable Files.</li> |
|||
<li><b>"Standard Version"</b> refers to such a Work if it has not been modified, or |
|||
has been modified in accordance with the consent of the Author, such consent being |
|||
in the full discretion of the Author. </li> |
|||
<li><b>"Work"</b> refers to the collection of files distributed by the Publisher, including |
|||
the Source Code, Executable Files, binaries, data files, documentation, whitepapers |
|||
and the Articles. </li> |
|||
<li><b>"You"</b> is you, an individual or entity wishing to use the Work and exercise |
|||
your rights under this License. |
|||
</li> |
|||
</ol> |
|||
</li> |
|||
|
|||
<li><strong>Fair Use/Fair Use Rights.</strong> Nothing in this License is intended to |
|||
reduce, limit, or restrict any rights arising from fair use, fair dealing, first |
|||
sale or other limitations on the exclusive rights of the copyright owner under copyright |
|||
law or other applicable laws. |
|||
</li> |
|||
|
|||
<li><strong>License Grant.</strong> Subject to the terms and conditions of this License, |
|||
the Author hereby grants You a worldwide, royalty-free, non-exclusive, perpetual |
|||
(for the duration of the applicable copyright) license to exercise the rights in |
|||
the Work as stated below: |
|||
|
|||
<ol class="SpacedList" style="list-style-type: lower-alpha;"> |
|||
<li>You may use the standard version of the Source Code or Executable Files in Your |
|||
own applications. </li> |
|||
<li>You may apply bug fixes, portability fixes and other modifications obtained from |
|||
the Public Domain or from the Author. A Work modified in such a way shall still |
|||
be considered the standard version and will be subject to this License.</li> |
|||
<li>You may otherwise modify Your copy of this Work (excluding the Articles) in any |
|||
way to create a Derivative Work, provided that You insert a prominent notice in |
|||
each changed file stating how, when and where You changed that file.</li> |
|||
<li>You may distribute the standard version of the Executable Files and Source Code |
|||
or Derivative Work in aggregate with other (possibly commercial) programs as part |
|||
of a larger (possibly commercial) software distribution. </li> |
|||
<li>The Articles discussing the Work published in any form by the author may not be |
|||
distributed or republished without the Author's consent. The author retains |
|||
copyright to any such Articles. You may use the Executable Files and Source Code |
|||
pursuant to this License but you may not repost or republish or otherwise distribute |
|||
or make available the Articles, without the prior written consent of the Author.</li> |
|||
</ol> |
|||
|
|||
Any subroutines or modules supplied by You and linked into the Source Code or Executable |
|||
Files of this Work shall not be considered part of this Work and will not be subject |
|||
to the terms of this License. |
|||
</li> |
|||
|
|||
<li><strong>Patent License.</strong> Subject to the terms and conditions of this License, |
|||
each Author hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, |
|||
irrevocable (except as stated in this section) patent license to make, have made, use, import, |
|||
and otherwise transfer the Work.</li> |
|||
|
|||
<li><strong>Restrictions.</strong> The license granted in Section 3 above is expressly |
|||
made subject to and limited by the following restrictions: |
|||
|
|||
<ol class="SpacedList" style="list-style-type: lower-alpha;"> |
|||
<li>You agree not to remove any of the original copyright, patent, trademark, and |
|||
attribution notices and associated disclaimers that may appear in the Source Code |
|||
or Executable Files. </li> |
|||
<li>You agree not to advertise or in any way imply that this Work is a product of Your |
|||
own. </li> |
|||
<li>The name of the Author may not be used to endorse or promote products derived from |
|||
the Work without the prior written consent of the Author.</li> |
|||
<li>You agree not to sell, lease, or rent any part of the Work. This does not restrict |
|||
you from including the Work or any part of the Work inside a larger software |
|||
distribution that itself is being sold. The Work by itself, though, cannot be sold, |
|||
leased or rented.</li> |
|||
<li>You may distribute the Executable Files and Source Code only under the terms of |
|||
this License, and You must include a copy of, or the Uniform Resource Identifier |
|||
for, this License with every copy of the Executable Files or Source Code You distribute |
|||
and ensure that anyone receiving such Executable Files and Source Code agrees that |
|||
the terms of this License apply to such Executable Files and/or Source Code. You |
|||
may not offer or impose any terms on the Work that alter or restrict the terms of |
|||
this License or the recipients' exercise of the rights granted hereunder. You |
|||
may not sublicense the Work. You must keep intact all notices that refer to this |
|||
License and to the disclaimer of warranties. You may not distribute the Executable |
|||
Files or Source Code with any technological measures that control access or use |
|||
of the Work in a manner inconsistent with the terms of this License. </li> |
|||
<li>You agree not to use the Work for illegal, immoral or improper purposes, or on pages |
|||
containing illegal, immoral or improper material. The Work is subject to applicable |
|||
export laws. You agree to comply with all such laws and regulations that may apply |
|||
to the Work after Your receipt of the Work. |
|||
</li> |
|||
</ol> |
|||
</li> |
|||
|
|||
<li><strong>Representations, Warranties and Disclaimer.</strong> THIS WORK IS PROVIDED |
|||
"AS IS", "WHERE IS" AND "AS AVAILABLE", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES |
|||
OR CONDITIONS OR GUARANTEES. YOU, THE USER, ASSUME ALL RISK IN ITS USE, INCLUDING |
|||
COPYRIGHT INFRINGEMENT, PATENT INFRINGEMENT, SUITABILITY, ETC. AUTHOR EXPRESSLY |
|||
DISCLAIMS ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS, INCLUDING |
|||
WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY |
|||
OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT, |
|||
OR THAT THE WORK (OR ANY PORTION THEREOF) IS CORRECT, USEFUL, BUG-FREE OR FREE OF |
|||
VIRUSES. YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE |
|||
WORKS. |
|||
</li> |
|||
|
|||
<li><b>Indemnity. </b>You agree to defend, indemnify and hold harmless the Author and |
|||
the Publisher from and against any claims, suits, losses, damages, liabilities, |
|||
costs, and expenses (including reasonable legal or attorneysโ fees) resulting from |
|||
or relating to any use of the Work by You. |
|||
</li> |
|||
|
|||
<li><strong>Limitation on Liability.</strong> EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE |
|||
LAW, IN NO EVENT WILL THE AUTHOR OR THE PUBLISHER BE LIABLE TO YOU ON ANY LEGAL |
|||
THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES |
|||
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK OR OTHERWISE, EVEN IF THE AUTHOR |
|||
OR THE PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
|||
</li> |
|||
|
|||
<li><strong>Termination.</strong> |
|||
|
|||
<ol style="list-style-type: lower-alpha;"> |
|||
<li>This License and the rights granted hereunder will terminate automatically upon |
|||
any breach by You of any term of this License. Individuals or entities who have |
|||
received Derivative Works from You under this License, however, will not have their |
|||
licenses terminated provided such individuals or entities remain in full compliance |
|||
with those licenses. Sections 1, 2, 6, 7, 8, 9, 10 and 11 will survive any termination |
|||
of this License. </li> |
|||
|
|||
<li>If You bring a copyright, trademark, patent or any other infringement claim against |
|||
any contributor over infringements You claim are made by the Work, your License |
|||
from such contributor to the Work ends automatically.</li> |
|||
|
|||
<li>Subject to the above terms and conditions, this License is perpetual (for the duration |
|||
of the applicable copyright in the Work). Notwithstanding the above, the Author |
|||
reserves the right to release the Work under different license terms or to stop |
|||
distributing the Work at any time; provided, however that any such election will |
|||
not serve to withdraw this License (or any other license that has been, or is required |
|||
to be, granted under the terms of this License), and this License will continue |
|||
in full force and effect unless terminated as stated above. |
|||
</li> |
|||
</ol> |
|||
</li> |
|||
|
|||
<li><strong>Publisher</strong>. The parties hereby confirm that the Publisher shall |
|||
not, under any circumstances, be responsible for and shall not have any liability |
|||
in respect of the subject matter of this License. The Publisher makes no warranty |
|||
whatsoever in connection with the Work and shall not be liable to You or any party |
|||
on any legal theory for any damages whatsoever, including without limitation any |
|||
general, special, incidental or consequential damages arising in connection to this |
|||
license. The Publisher reserves the right to cease making the Work available to |
|||
You at any time without notice</li> |
|||
|
|||
<li><strong>Miscellaneous</strong> |
|||
|
|||
<ol class="SpacedList" style="list-style-type: lower-alpha;"> |
|||
<li>This License shall be governed by the laws of the location of the head office of |
|||
the Author or if the Author is an individual, the laws of location of the principal |
|||
place of residence of the Author.</li> |
|||
<li>If any provision of this License is invalid or unenforceable under applicable law, |
|||
it shall not affect the validity or enforceability of the remainder of the terms |
|||
of this License, and without further action by the parties to this License, such |
|||
provision shall be reformed to the minimum extent necessary to make such provision |
|||
valid and enforceable. </li> |
|||
<li>No term or provision of this License shall be deemed waived and no breach consented |
|||
to unless such waiver or consent shall be in writing and signed by the party to |
|||
be charged with such waiver or consent. </li> |
|||
<li>This License constitutes the entire agreement between the parties with respect to |
|||
the Work licensed herein. There are no understandings, agreements or representations |
|||
with respect to the Work not specified herein. The Author shall not be bound by |
|||
any additional provisions that may appear in any communication from You. This License |
|||
may not be modified without the mutual written agreement of the Author and You. |
|||
</li> |
|||
</ol> |
|||
|
|||
</li> |
|||
</ol> |
|||
|
|||
</div> |
|||
</center> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,10 @@ |
|||
|
|||
.PHONY: clean |
|||
|
|||
all: |
|||
gcc -o tgvs main.c draw.c gui.c jackclient.c drawhelper.c draw_xpitches_yports.c draw_xports_ypitches.c draw_port_grids.c draw_meterbridge.c draw_activity.c programstate.c jRead.c jWrite.c camera.c effect_starfield.c -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -ljack -llo |
|||
|
|||
|
|||
clean: |
|||
rm tgvs |
|||
rm *.gch |
@ -0,0 +1,94 @@ |
|||
//Standard lib
|
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "camera.h" |
|||
|
|||
extern ProgramState programState; |
|||
|
|||
|
|||
// https://www.raylib.com/examples/web/core/loader.html?name=core_2d_camera
|
|||
//Camera controls, such as zoom and reset, are in gui.c
|
|||
|
|||
static int screenWidth; |
|||
static int screenHeight; |
|||
|
|||
void cameraMoveUpdate() { |
|||
programState.camera.offset = (Vector2){ screenWidth/2+programState.cameraCenterOnX, screenHeight/2+programState.cameraCenterOnY }; |
|||
} |
|||
|
|||
void cameraLeft() { |
|||
programState.cameraCenterOnX -= 10; |
|||
cameraMoveUpdate(); |
|||
} |
|||
void cameraRight() { |
|||
programState.cameraCenterOnX += 10; |
|||
cameraMoveUpdate(); |
|||
} |
|||
void cameraUp() { |
|||
programState.cameraCenterOnY -= 10; |
|||
cameraMoveUpdate(); |
|||
} |
|||
void cameraDown() { |
|||
programState.cameraCenterOnY += 10; |
|||
cameraMoveUpdate(); |
|||
} |
|||
|
|||
void cameraMaybeZoom() { |
|||
//float rememberZoom = programState.camera.zoom;
|
|||
if (!programState.lockInput) { |
|||
programState.camera.zoom += ((float)GetMouseWheelMove()*0.05f); |
|||
if (programState.camera.zoom < 0) |
|||
programState.camera.zoom = 0.0f; |
|||
//if (rememberZoom != programState.camera.zoom) {}
|
|||
} |
|||
} |
|||
|
|||
void cameraReset() { |
|||
//Only called through user action. Camera startup values are either loaded from file or set in programstate.c
|
|||
programState.camera.zoom = 1.0f; |
|||
programState.camera.rotation = 0.0f; |
|||
programState.cameraCenterOnX = 0; |
|||
programState.cameraCenterOnY = 0; |
|||
cameraMoveUpdate(); |
|||
} |
|||
|
|||
void cameraNormalizeRotation() { |
|||
if (programState.camera.rotation > 359) programState.camera.rotation -= 360; |
|||
else if (programState.camera.rotation < 0) programState.camera.rotation += 360; |
|||
} |
|||
|
|||
void cameraRotateLeft() { |
|||
programState.camera.rotation--; |
|||
cameraNormalizeRotation(); |
|||
} |
|||
|
|||
void cameraRotateRight() { |
|||
programState.camera.rotation++; |
|||
cameraNormalizeRotation(); |
|||
} |
|||
|
|||
void cameraRotate180() { |
|||
programState.camera.rotation += 180.0f; |
|||
cameraNormalizeRotation(); |
|||
} |
|||
|
|||
void cameraFlip() { |
|||
programState.camera.zoom *= -1.0f; //this is the same as rotation by 180, and not mirror
|
|||
//programState.camera.rotation += 180.0f;
|
|||
} |
|||
|
|||
void mainLoop_cameraReposition() { |
|||
if (programState.guiRedrawNeeded) { |
|||
screenWidth = GetScreenWidth(); //Changes on resize
|
|||
screenHeight = GetScreenHeight(); //Changes on resize
|
|||
programState.camera.target = (Vector2){ screenWidth/2, screenHeight/2 }; |
|||
programState.camera.offset = (Vector2){ screenWidth/2+programState.cameraCenterOnX, screenHeight/2+programState.cameraCenterOnY }; |
|||
} |
|||
} |
|||
|
|||
|
@ -0,0 +1,18 @@ |
|||
#ifndef CAMERA_H |
|||
#define CAMERA_H |
|||
|
|||
void cameraReset(); |
|||
void cameraMaybeZoom(); |
|||
void mainLoop_cameraReposition(); |
|||
void cameraRotateLeft(); |
|||
void cameraRotateRight(); |
|||
void cameraRotate180(); |
|||
//void cameraFlip();
|
|||
|
|||
void cameraLeft(); |
|||
void cameraRight(); |
|||
void cameraUp(); |
|||
void cameraDown(); |
|||
|
|||
|
|||
#endif // not defined CAMERA_H
|
@ -0,0 +1,52 @@ |
|||
#ifndef CONSTANTS_H |
|||
#define CONSTANTS_H |
|||
|
|||
#include "raylib.h" |
|||
|
|||
#ifndef MAX |
|||
#define MAX(a,b) ( (a) < (b) ? (b) : (a) ) |
|||
#endif |
|||
|
|||
#define VIS_PORTS 16 // Program is able to handle <100 ports. But we never intend anything different than 16 to be honest.
|
|||
#define VIS_PORTS_SQRT 4 //please choose by hand. If not possibe choose the next higher. e.g. 13 ports -> 4ยฒ
|
|||
|
|||
//Pixel values
|
|||
#define NOTE_SMALL_SIDE 14 |
|||
#define NOTE_LONG_SIDE 20 |
|||
#define NOTE_BORDER 4 //halved for left/right padding. the NOTE sides already include the border.
|
|||
#define NOTE_GAP 3 |
|||
//For GUI
|
|||
#define HEIGHT 25 |
|||
#define SPACING 35 |
|||
|
|||
|
|||
typedef struct { // C-N00b: using typedef lets us type Note nt; or (Note){...} instead of struct Note.
|
|||
int active; |
|||
int port; |
|||
int x; |
|||
int y; |
|||
int pitch; |
|||
int velocity; //converted to color, size etc. by raylib
|
|||
float countdown; //0.0f is not existent, 1 is permanently visible, everything between is the fade out process with auto-decrement. To start the timer set to 0.999 or so
|
|||
} Note; |
|||
|
|||
|
|||
enum { |
|||
//ports are 0-15
|
|||
background=VIS_PORTS+0, |
|||
backgroundLight=VIS_PORTS+1, |
|||
backgroundLighter=VIS_PORTS+2, |
|||
}; |
|||
|
|||
|
|||
enum { |
|||
//Layers in order.
|
|||
layerIndex_notes, |
|||
layerIndex_effects, |
|||
layerIndex_sprites, |
|||
layerIndex_count, //last one
|
|||
}; |
|||
|
|||
#endif // not defined CONSTANTS_H
|
|||
|
|||
|
@ -0,0 +1,144 @@ |
|||
//Standard lib
|
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
#include "draw.h" |
|||
|
|||
//Drawing Routines
|
|||
#include "draw_xpitches_yports.h" |
|||
#include "draw_xports_ypitches.h" |
|||
#include "draw_port_grids.h" |
|||
#include "draw_meterbridge.h" |
|||
#include "draw_activity.h" |
|||
|
|||
//Effect Routines
|
|||
#include "effect_starfield.h" |
|||
|
|||
#define GLSL_VERSION 330 |
|||
|
|||
|
|||
extern ProgramState programState; |
|||
|
|||
Effect effects[NR_OF_EFFECTS] = { |
|||
{ false, "Starfield", false, 1.0f, 1, 2, effect_starfield}, |
|||
{ false, "Snow Fall", false, 1.0f, 1, 3, effect_starfield}, |
|||
{ false, "Falling Leafs", false, 1.0f, 1, 4, effect_starfield}, |
|||
{ false, "Rain", false, 1.0f, 1, 5, effect_starfield}, |
|||
{ false, "Embers", false, 1.0f, 1, 5, effect_starfield}, |
|||
}; |
|||
|
|||
//This needs to be manually synced to gui.c combox
|
|||
//This order is permanent. No re-ordering after the first release!
|
|||
//The save file and the GUI depend on it.
|
|||
void (*drawFunctions[5])() = { |
|||
draw_xpitches_yports, |
|||
draw_xports_ypitches, |
|||
draw_port_grids, |
|||
draw_meterbridge, |
|||
draw_activity, |
|||
}; |
|||
|
|||
|
|||
static Texture2D backgroundTexture; |
|||
static RenderTexture2D renderTarget; |
|||
static Shader shader; |
|||
|
|||
void loadBackgroundImage(char * path, bool newImage) { |
|||
if (programState.nsm) { |
|||
char * temporaryAbsolutePath = malloc (sizeof(char) * 4096); //4096 is the max path length in linux
|
|||
snprintf(temporaryAbsolutePath, (sizeof(char) * 4096), "%s/%s", programState.nsmDirectory, "background.png"); |
|||
if (newImage) { //User clicked on the GUI button and chose a file
|
|||
unlink(temporaryAbsolutePath); |
|||
symlink(path, temporaryAbsolutePath); //source-file, link
|
|||
} |
|||
programState.pathBackgroundImage = "background.png"; |
|||
backgroundTexture = LoadTexture(temporaryAbsolutePath); |
|||
free(temporaryAbsolutePath); |
|||
} |
|||
else { |
|||
programState.pathBackgroundImage = path; |
|||
backgroundTexture = LoadTexture(programState.pathBackgroundImage); |
|||
} |
|||
|
|||
} |
|||
|
|||
void init_draw() { |
|||
renderTarget = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); |
|||
shader = LoadShader(0, FormatText("glsl%i/bloom.fs", GLSL_VERSION)); //vs file, fs file
|
|||
} |
|||
|
|||
|
|||
void mainLoop_draw(Note *allGUINotes) { |
|||
if (IsWindowResized()) { |
|||
//printf("Window resized to: %i x %i\n", GetScreenWidth(), GetScreenHeight());
|
|||
programState.guiRedrawNeeded = true; |
|||
} |
|||
|
|||
//Drawing Layers from bottom to top:
|
|||
//Background Image (or plain color)
|
|||
//Background Sprites (internal Z-Order = Y)
|
|||
//Background Effects (e.g. Starfield)
|
|||
//Main Visualization (Notes)
|
|||
//Foreground Sprites (internal Z-Order = Y)
|
|||
//Foreground Effects (e.g. Rain)
|
|||
|
|||
|
|||
|
|||
|
|||
if (programState.showBackgroundImageLayer) { |
|||
//void DrawTexture(Texture2D texture, int posX, int posY, Color tint);
|
|||
//Draw centered but not scaled.
|
|||
DrawTexture(backgroundTexture, GetScreenWidth()/2-backgroundTexture.width/2, GetScreenHeight()/2-backgroundTexture.height/2, WHITE); |
|||
} |
|||
|
|||
//BeginTextureMode(renderTarget); // Enable drawing to texture
|
|||
//ClearBackground(BLACK); //Texture background
|
|||
|
|||
//Background Effects Pass
|
|||
if (programState.showEffectLayer) { |
|||
for (int i=0; i<NR_OF_EFFECTS; i++) { |
|||
if (effects[i].enabled && !effects[i].foreground) { |
|||
effects[i].function(programState.guiRedrawNeeded, effects[i].opacity, effects[i].speed, effects[i].size); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//EndTextureMode(); // End drawing to texture (now we have a texture available for next passes)
|
|||
//BeginShaderMode(shader);
|
|||
// DrawTextureRec(renderTarget.texture, (Rectangle){ 0, 0, renderTarget.texture.width, -renderTarget.texture.height }, (Vector2){ 0, 0 }, WHITE);
|
|||
//EndShaderMode();
|
|||
|
|||
|
|||
if (programState.showDrawMode) { |
|||
BeginMode2D(programState.camera); |
|||
drawFunctions[programState.drawMode](allGUINotes, programState.guiRedrawNeeded); //One Mode at a time
|
|||
EndMode2D(); |
|||
} |
|||
|
|||
|
|||
|
|||
//Foreground Effects Pass
|
|||
if (programState.showEffectLayer) { |
|||
for (int i=0; i<NR_OF_EFFECTS; i++) { |
|||
if (effects[i].enabled && effects[i].foreground) { |
|||
effects[i].function(programState.guiRedrawNeeded, effects[i].opacity, effects[i].speed, effects[i].size); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
//DrawCircle(GetScreenWidth()/2, GetScreenHeight()/2, 10, RAYWHITE); //show center point
|
|||
|
|||
programState.guiRedrawNeeded = false; |
|||
} |
@ -0,0 +1,36 @@ |
|||
#ifndef DRAW_H |
|||
#define DRAW_H |
|||
|
|||
#define NR_OF_EFFECTS 5 |
|||
|
|||
typedef void (*effectFunction)(bool guiRedrawNeeded, float opacity, int speedModifier, int size); |
|||
typedef struct { |
|||
bool enabled; |
|||
char * name; |
|||
bool foreground; |
|||
float opacity; |
|||
int speed; |
|||
int size; // effect depends on the drawing mode. This could be everything
|
|||
effectFunction function; |
|||
} Effect; |
|||
|
|||
typedef struct { |
|||
bool enabled; |
|||
char * path; //NSM = subpath, Standalone: Absolute Path
|
|||
bool foreground; |
|||
float opacity; |
|||
Color tint; |
|||
int x; |
|||
int y; |
|||
float rotation; |
|||
float zoom; |
|||
//If you want animation or different versions you have to put them all in one image and then devide into tiles:
|
|||
int tileSize; //from the whole picture, how big is the square that should be displayed.
|
|||
int tileIndex; //When the image is divided through size which of the resulting tiles should be displayed. 1D, starting from 0, max is 127
|
|||
} Sprite; |
|||
|
|||
void mainLoop_draw(Note *allGUINotes); |
|||
void loadBackgroundImage(char * path, bool newImage); |
|||
void init_draw(); |
|||
|
|||
#endif // not defined DRAW_H
|
@ -0,0 +1,99 @@ |
|||
//Standard lib
|
|||
#include <stdio.h> |
|||
#include <math.h> |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
#include "draw_activity.h" |
|||
|
|||
|
|||
static int screenWidth; |
|||
static int screenHeight; |
|||
|
|||
static int radius = 80; |
|||
const float distanceFactor = 2.5; |
|||
static int xOffset; |
|||
static int yOffset; |
|||
|
|||
extern ProgramState programState; |
|||
|
|||
static float fadeout[VIS_PORTS]; |
|||
|
|||
|
|||
static void reposition(Note *allGUINotes) { |
|||
//This is both init and update. We only call it internally, the name of the function doesn't matter.
|
|||
//Once guaranteed called on program start and then each window resize
|
|||
screenWidth = GetScreenWidth(); |
|||
screenHeight = GetScreenHeight(); |
|||
|
|||
xOffset = (int)(screenWidth/2) - (VIS_PORTS_SQRT/2)*radius*distanceFactor; |
|||
yOffset = (int)(screenHeight/2) - (VIS_PORTS_SQRT/2)*radius*distanceFactor; |
|||
|
|||
programState.modeDescription = "Each port is one polygon.\nNumber of sites equals active notes.\nThere is no time-dimension."; |
|||
} |
|||
|
|||
|
|||
void drawShape(Vector2 center, int port, float rotation) { |
|||
//DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color);
|
|||
|
|||
//float fadeValue = pow(sinf(rotation / 180 * PI), 2); //sine wants radians, not degree
|
|||
//float fadeValue = (float)(GetRandomValue(1,10)-5.0f) / 100.0f + 1.0f; //sparkle
|
|||
//Color color = Fade(programState.colors[port], fadeValue);
|
|||
|
|||
Color border = Fade(BLACK, fadeout[port]); |
|||
Color color = Fade(programState.colors[port], fadeout[port]); |
|||
|
|||
if (programState.portActivity[port] < 3) { |
|||
//DrawCircleV(Vector2 center, float radius, Color color);
|
|||
DrawCircleV(center, radius*0.7+NOTE_BORDER, border); |
|||
DrawCircleV(center, radius*0.7, color); |
|||
} |
|||
else { |
|||
DrawPoly(center, programState.portActivity[port], radius+NOTE_BORDER*2, rotation, border); |
|||
DrawPoly(center, programState.portActivity[port], radius, rotation, color); |
|||
} |
|||
} |
|||
|
|||
void draw_activity(Note *allGUINotes, int redrawNeeded) { |
|||
float ft = GetFrameTime(); |
|||
int bpmFactor = programState.bpm/40 * programState.bpm/20; //exponential curve. The higher bpm the faster we get. Below 90 it should feel really slow
|
|||
float rotation = fmod(GetTime() * bpmFactor * 5, 360.0f); |
|||
|
|||
if (redrawNeeded) { |
|||
reposition(allGUINotes); |
|||
} |
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
|
|||
int portRow = port / VIS_PORTS_SQRT; |
|||
int portColumn = port % VIS_PORTS_SQRT; |
|||
int blockY = portRow * radius * distanceFactor; |
|||
int blockX = portColumn * radius * distanceFactor; |
|||
Vector2 center = (Vector2){ xOffset + blockX + radius, yOffset + blockY + radius }; |
|||
|
|||
if (programState.showPortBackground && portShouldDraw(port)) { |
|||
DrawCircleV(center, radius-NOTE_BORDER, programState.colors[backgroundLight]); |
|||
} |
|||
|
|||
if (programState.portActivity[port] > 0) { |
|||
//printf("port %i notes %i\n", port, programState.portActivity[port]);
|
|||
fadeout[port] = 1.0f; |
|||
drawShape(center, port, rotation); |
|||
|
|||
} |
|||
else if (fadeout[port] > 0.0f) { |
|||
if (fadeout[port] == 1.0f) { fadeout[port] = 0.999; } //begin countdown
|
|||
fadeout[port] = calculateCountdown(fadeout[port], ft); |
|||
drawShape(center, port, rotation); |
|||
} |
|||
|
|||
if (programState.showConnectedPortnames) { |
|||
drawJackPortName(programState.connectedPortNames[port], xOffset+blockX, yOffset+blockY, 18, RAYWHITE, false); //text, x, y, int fontsize, color, vertical
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
#ifndef DRAW_ACTIVITY_H |
|||
#define DRAW_ACTIVITY_H |
|||
|
|||
void draw_activity(Note *allGUINotes, int redrawNeeded); |
|||
|
|||
#endif // not defined DRAW_ACTIVITY_H
|
@ -0,0 +1,115 @@ |
|||
//Standard lib
|
|||
#include <stdio.h> |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
#include "raygui.h" // Required for GUI controls |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
#include "draw_meterbridge.h" |
|||
|
|||
extern ProgramState programState; |
|||
static Rectangle backgroundRect; |
|||
static bool guiGroupEditMode= false; |
|||
static int screenHeight; |
|||
static int bottom; |
|||
|
|||
static void reposition(Note *allGUINotes) { |
|||
//This is both init and update. We only call it internally, the name of the function doesn't matter.
|
|||
//Once guaranteed called on program start and then each window resize
|
|||
int screenWidth = GetScreenWidth(); |
|||
int screenHeight = GetScreenHeight(); |
|||
int xOffset = (int)(screenWidth/2) - (int)(60*(NOTE_LONG_SIDE+NOTE_GAP)); //we want all tracks around the center.
|
|||
int yOffset = (int)(screenHeight/2) - (int)(NOTE_SMALL_SIDE+NOTE_GAP)*2; //we want all tracks below the center.
|
|||
|
|||
programState.modeDescription = "All port are in one horizontal row.\
|
|||
\nPitches are left to right, low to high.\ |
|||
\n\ |
|||
\nVelocity 'shoots' notes upwards, then they fall back\ |
|||
\ninfluenced by the fade-out setting.\ |
|||
\n\ |
|||
\nYou can set the a 'grouping' parameter for this mode\ |
|||
\nwhich combines several pitches into a single indicator.\ |
|||
\nGrouping uses the pitch marker pitch as baseline.\ |
|||
\nThink of it like the tonic or root note.\ |
|||
\n\ |
|||
\n\ |
|||
\nNotes are only lit up when they are played.\ |
|||
\nThere is no time-dimension."; |
|||
|
|||
int rootNote = programState.pitchMarkerValue % 12; //factor out the octave. used to start groups on the pitchmarker as root note of the scale
|
|||
int xpos = xOffset; |
|||
int pitchToTheCenter = 64 - (128/programState.meterbridge_grouping/2); |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
for (int midiPitch=0; midiPitch<128; midiPitch++) { //We prepare the full range of pitches, but the main loop will only activate the current pitch range.
|
|||
int adjustedNotePitch = (int)((midiPitch-rootNote)/programState.meterbridge_grouping) + pitchToTheCenter; |
|||
xpos = xOffset + adjustedNotePitch*NOTE_LONG_SIDE + adjustedNotePitch*NOTE_GAP/2; |
|||
allGUINotes[port*128 + midiPitch].x = xpos; |
|||
allGUINotes[port*128 + midiPitch].y = yOffset + NOTE_SMALL_SIDE + NOTE_GAP*2; |
|||
} |
|||
} |
|||
|
|||
backgroundRect.x = allGUINotes[programState.pitchMin].x; //includes offset
|
|||
backgroundRect.y = (float)(yOffset + NOTE_SMALL_SIDE + NOTE_GAP*2 - NOTE_GAP); |
|||
backgroundRect.width = allGUINotes[programState.pitchMax].x - allGUINotes[programState.pitchMin].x; |
|||
backgroundRect.height = (float)NOTE_SMALL_SIDE; |
|||
} |
|||
|
|||
static void drawBackground() { |
|||
if (programState.showPortBackground) { |
|||
DrawRectangleRec(backgroundRect, programState.colors[backgroundLight]); |
|||
} |
|||
} |
|||
|
|||
void draw_meterbridge(Note *allGUINotes, int redrawNeeded) { |
|||
Note * nt; |
|||
float ft = GetFrameTime(); |
|||
|
|||
if (redrawNeeded) { |
|||
reposition(allGUINotes); |
|||
} |
|||
|
|||
//Z-Order is created by call-order. First is bottom.
|
|||
drawBackground(); |
|||
|
|||
if (programState.showPitchMarker) { |
|||
nt = &allGUINotes[programState.pitchMarkerValue]; |
|||
Rectangle rec = (Rectangle){nt->x, nt->y+NOTE_SMALL_SIDE, NOTE_LONG_SIDE-NOTE_BORDER, NOTE_SMALL_SIDE-NOTE_BORDER}; //TODO: Ha! :) This falls down as well, together with the note.
|
|||
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]); |
|||
} |
|||
|
|||
//Debug: Show 0 and 127
|
|||
//DrawLine(allGUINotes[0].x, 0, allGUINotes[0].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
|
|||
//DrawLine(allGUINotes[127].x, 0, allGUINotes[127].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
|
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
for (int midiPitch=programState.pitchMin; midiPitch<=programState.pitchMax; midiPitch++) { |
|||
nt = &allGUINotes[port*128 + midiPitch]; |
|||
if (nt->active) { |
|||
nt->y = backgroundRect.y - 2*(nt->countdown * nt->velocity); //Shift the notes upwards. Don't use -= or +=, that is exponential modifications
|
|||
reduceCountdown(nt, ft/2); //handles 0.0 and 1.0 as special cases
|
|||
drawNoteRect(nt, 1, 0); //note, rotated, square. Will not draw if not active/countdown == 0
|
|||
} |
|||
} |
|||
} |
|||
|
|||
//Our own GUI section
|
|||
if (programState.guiVisible) { |
|||
EndMode2D(); //We are already in camera mode because this is a draw_ function. GUI is not under camera control.
|
|||
int row = 0; |
|||
screenHeight = GetScreenHeight(); //Changes on resize
|
|||
bottom = screenHeight - 2*SPACING; |
|||
int remember = programState.meterbridge_grouping; |
|||
GuiLabel((Rectangle){ 34, bottom-row*SPACING, 105, HEIGHT }, "Meterbridge Note Grouping"); // void GuiLabel(Rectangle bounds, const char *text);
|
|||
if (GuiSpinner((Rectangle){ 250, bottom-row*SPACING, 80, HEIGHT }, "", &programState.meterbridge_grouping, 0, 127, guiGroupEditMode)) guiGroupEditMode = !guiGroupEditMode; // bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
|
|||
if (programState.meterbridge_grouping > 128) {programState.meterbridge_grouping=128;} |
|||
if (programState.meterbridge_grouping < 1) {programState.meterbridge_grouping=1;} |
|||
if (remember != programState.meterbridge_grouping) { reposition(allGUINotes); } //we cannot set guiRedrawNeeded to true here because parent draw() will reset it after we return
|
|||
|
|||
row++; |
|||
BeginMode2D(programState.camera); //Switch back to previously active camera mode
|
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
#ifndef DRAW_METERBRIDGE_H |
|||
#define DRAW_METERBRIDGE_H |
|||
|
|||
void draw_meterbridge(Note *allGUINotes, int redrawNeeded); |
|||
|
|||
#endif // not defined DRAW_METERBRIDGE_H
|
@ -0,0 +1,121 @@ |
|||
//Standard lib
|
|||
#include <stdio.h> |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
#include "draw_port_grids.h" |
|||
|
|||
/* Each port is a grid square, ordered in rows, like text
|
|||
* Pitches are put on a 12x12 square for octaves. |
|||
* The screen does not move or scroll. Notes fade out and make room for the next note-on. |
|||
*/ |
|||
|
|||
extern ProgramState programState; |
|||
|
|||
static Rectangle backgroundRects[VIS_PORTS]; // 4 floats: x, y, w, h
|
|||
|
|||
static void reposition(Note *allGUINotes) { |
|||
//This is both init and update. We only call it internally, the name of the function doesn't matter.
|
|||
//Once guaranteed called on program start and then each window resize
|
|||
int screenWidth = GetScreenWidth(); |
|||
int screenHeight = GetScreenHeight(); |
|||
int blockSize = 13 * (NOTE_SMALL_SIDE + NOTE_GAP); //space the blocks one row/column apart = 13
|
|||
int xOffset = (int)(screenWidth/2) - (VIS_PORTS_SQRT/2)*blockSize; //shift half of the grid blocks from the center to the left
|
|||
int yOffset = (int)(screenHeight/2) - (VIS_PORTS_SQRT/2)*blockSize; //shift half of the grid blocks from the center to the left
|
|||
|
|||
//top left corner of each block
|
|||
int blockX; |
|||
int blockY; |
|||
int portRow; |
|||
int portColumn; |
|||
int pitchRow; |
|||
int pitchColumn; |
|||
|
|||
programState.modeDescription = "Each port is one grid.\
|
|||
\nEach pitch is one square, from top left\nto bottom right.\ |
|||
\nPitch rows wrap, just like normal text.\n\n\ |
|||
\n\ |
|||
The higher the velocity of a note the more\nvibrant its color.\n\ |
|||
\n\ |
|||
Notes are only lit up when they are played.\n\ |
|||
There is no time-dimension."; |
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
//We loop in a way that is guaranteed to have >= grid blocks available for all notes
|
|||
//But we will never try to access more notes than exist in allGUINotes.
|
|||
portRow = port / VIS_PORTS_SQRT; |
|||
portColumn = port % VIS_PORTS_SQRT; |
|||
blockY = portRow * blockSize; |
|||
blockX = portColumn * blockSize; |
|||
|
|||
backgroundRects[port].x = (float)(xOffset + blockX); |
|||
backgroundRects[port].y = (float)(yOffset + blockY); |
|||
backgroundRects[port].width = (float)(12 * (NOTE_SMALL_SIDE + NOTE_GAP)); //blocksize without filling the extra gap
|
|||
backgroundRects[port].height = (float)(12 * (NOTE_SMALL_SIDE + NOTE_GAP)); |
|||
//Build a 12x12 Grid for octaves.
|
|||
for (int midiPitch=0; midiPitch<128; midiPitch++) { |
|||
pitchRow = midiPitch / 12 +1; //shift one down because it is prettier
|
|||
pitchColumn = midiPitch % 12; |
|||
allGUINotes[port*128 + midiPitch].x = xOffset + blockX + pitchColumn * (NOTE_SMALL_SIDE + NOTE_GAP); |
|||
allGUINotes[port*128 + midiPitch].y = yOffset + blockY + pitchRow * (NOTE_SMALL_SIDE + NOTE_GAP); |
|||
} |
|||
} |
|||
} |
|||
|
|||
static void drawBackground() { |
|||
//Each port is one track
|
|||
//The background rects already have their positions and dimensions set.
|
|||
if (programState.showPortBackground) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
if (portShouldDraw(port)) { |
|||
DrawRectangleRec(backgroundRects[port], programState.colors[backgroundLight]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void draw_port_grids(Note *allGUINotes, int redrawNeeded) { |
|||
Note * nt; |
|||
float ft = GetFrameTime(); |
|||
|
|||
if (redrawNeeded) { |
|||
reposition(allGUINotes); |
|||
} |
|||
|
|||
//Z-Order is created by call-order. First is bottom.
|
|||
drawBackground(); |
|||
|
|||
if (programState.showConnectedPortnames) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
nt = &allGUINotes[port*128]; // top left note of each port
|
|||
drawJackPortName(programState.connectedPortNames[port], nt->x, nt->y-1.5*(NOTE_SMALL_SIDE + NOTE_GAP), 18, RAYWHITE, false); //text, x, y, int fontsize, color, vertical
|
|||
} |
|||
} |
|||
|
|||
if (programState.showPitchMarker) { |
|||
//Each block has a pitch marker at one note that we draw with a different color.
|
|||
//We use the already set note positions but create a background note behind the real one.
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
if (portShouldDraw(port)) { |
|||
nt = &allGUINotes[port*128 + programState.pitchMarkerValue]; |
|||
Rectangle rec = (Rectangle){nt->x, nt->y, NOTE_SMALL_SIDE-NOTE_BORDER, NOTE_SMALL_SIDE-NOTE_BORDER}; |
|||
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
for (int midiPitch=0; midiPitch<128; midiPitch++) { //128 is a fixed midi value, no need to dynamically get the size
|
|||
nt = &allGUINotes[port*128 + midiPitch]; |
|||
if (nt->active) { |
|||
reduceCountdown(nt, ft); //handles 0.0 and 1.0 as special cases
|
|||
drawNoteRect(nt, 0, 1); //note, rotated, square. Will not draw if not active/countdown == 0
|
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
#ifndef DRAW_PORT_GRIDS_H |
|||
#define DRAW_PORT_GRIDS_H |
|||
|
|||
void draw_port_grids(Note *allGUINotes, int redrawNeeded); |
|||
|
|||
#endif // not defined DRAW_PORT_GRIDS_H
|
@ -0,0 +1,107 @@ |
|||
//Standard lib
|
|||
#include <stdio.h> |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
#include "draw_xpitches_yports.h" |
|||
|
|||
/* Each port is a track, which are ordered top to bottom
|
|||
* Each pitch is a rectangle in that track. midi note 0 on the left to 127 on the right. |
|||
* The screen does not move or scroll. Notes fade out and make room for the next note-on. |
|||
*/ |
|||
|
|||
extern ProgramState programState; |
|||
|
|||
static Rectangle backgroundRects[VIS_PORTS]; // 4 floats: x, y, w, h
|
|||
|
|||
static void reposition(Note *allGUINotes) { |
|||
//This is both init and update. We only call it internally, the name of the function doesn't matter.
|
|||
//Once guaranteed called on program start and then each window resize
|
|||
int screenWidth = GetScreenWidth(); |
|||
int screenHeight = GetScreenHeight(); |
|||
int xOffset = (int)(screenWidth/2) - (int)(60*(NOTE_SMALL_SIDE+NOTE_GAP)); //we want all tracks around the center.
|
|||
int yOffset = (int)(screenHeight/2) - (int)(VIS_PORTS*(NOTE_LONG_SIDE+NOTE_GAP))/2; //we want all tracks around the center.
|
|||
|
|||
programState.modeDescription = "Each port is a horizontal track.\n\
|
|||
Each pitch is one square, going from left lowest\nto right highest.\n\ |
|||
\n\ |
|||
The higher the velocity of a note the more\nvibrant its color.\n\ |
|||
\n\ |
|||
Notes are only lit up when they are played.\n\ |
|||
There is no time-dimension."; |
|||
|
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
backgroundRects[port].x = xOffset + programState.pitchMin * (NOTE_SMALL_SIDE + NOTE_GAP/2); |
|||
backgroundRects[port].y = (float)(yOffset + port*NOTE_LONG_SIDE + port*NOTE_GAP*2 - NOTE_GAP); |
|||
backgroundRects[port].width = (programState.pitchMax-programState.pitchMin+1)*(NOTE_SMALL_SIDE + NOTE_GAP/2); |
|||
backgroundRects[port].height = (float)NOTE_LONG_SIDE; |
|||
|
|||
for (int midiPitch=0; midiPitch<128; midiPitch++) { //We prepare the full range of pitches, but the main loop will only activate the current pitch range.
|
|||
allGUINotes[port*128 + midiPitch].x = xOffset + midiPitch*(NOTE_SMALL_SIDE + NOTE_GAP/2); |
|||
allGUINotes[port*128 + midiPitch].y = yOffset + port*NOTE_LONG_SIDE + port*NOTE_GAP*2; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static void drawBackground() { |
|||
//Each port is one track
|
|||
//The background rects already have their positions and dimensions set.
|
|||
|
|||
if (programState.showPortBackground) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
if (portShouldDraw(port)) |
|||
{ |
|||
DrawRectangleRec(backgroundRects[port], programState.colors[backgroundLight]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void draw_xpitches_yports(Note *allGUINotes, int redrawNeeded) { |
|||
Note * nt; |
|||
float ft = GetFrameTime(); |
|||
|
|||
if (redrawNeeded) { |
|||
reposition(allGUINotes); |
|||
} |
|||
|
|||
//Z-Order is created by call-order. First is bottom.
|
|||
drawBackground(); |
|||
|
|||
if (programState.showConnectedPortnames) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
nt = &allGUINotes[port*128+programState.pitchMin]; // choose note where to start writing
|
|||
drawJackPortName(programState.connectedPortNames[port], nt->x, nt->y, 18, RAYWHITE, false); //text, x, y, int fontsize, color, vertical
|
|||
} |
|||
} |
|||
|
|||
if (programState.showPitchMarker) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
if (portShouldDraw(port)) { |
|||
nt = &allGUINotes[port*128 + programState.pitchMarkerValue]; |
|||
Rectangle rec = (Rectangle){nt->x, nt->y, NOTE_SMALL_SIDE-NOTE_BORDER, NOTE_LONG_SIDE-NOTE_BORDER}; |
|||
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//Debug: Show 0 and 127
|
|||
//DrawLine(allGUINotes[0].x, 0, allGUINotes[0].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
|
|||
//DrawLine(allGUINotes[127].x, 0, allGUINotes[127].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
|
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
for (int midiPitch=programState.pitchMin; midiPitch<=programState.pitchMax; midiPitch++) { |
|||
nt = &allGUINotes[port*128 + midiPitch]; |
|||
if (nt->active) { |
|||
reduceCountdown(nt, ft); //handles 0.0 and 1.0 as special cases
|
|||
drawNoteRect(nt, 0, 0); //note, rotated, square. Will not draw if countdown == 0
|
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
#ifndef DRAW_XPITCHES_YPORTS_H |
|||
#define DRAW_XPITCHES_YPORTS_H |
|||
|
|||
void draw_xpitches_yports(Note *allGUINotes, int redrawNeeded); |
|||
|
|||
#endif // not defined DRAW_XPITCHES_YPORTS_H
|
@ -0,0 +1,103 @@ |
|||
//Standard lib
|
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
#include "draw_xports_ypitches.h" |
|||
|
|||
/* Each port is a column, ordered from left to right
|
|||
* Each pitch is a rectangle in that column. midi note 0 on the bottom to 127 on top |
|||
* The screen does not move or scroll. Notes fade out and make room for the next note-on. |
|||
*/ |
|||
|
|||
extern ProgramState programState; |
|||
|
|||
static Rectangle backgroundRects[VIS_PORTS]; // 4 floats: x, y, w, h
|
|||
|
|||
static void reposition(Note *allGUINotes) { |
|||
//This is both init and update. We only call it internally, the name of the function doesn't matter.
|
|||
//Once guaranteed called on program start and then each window resize
|
|||
int screenWidth = GetScreenWidth(); |
|||
int screenHeight = GetScreenHeight(); |
|||
int xOffset = (int)(screenWidth/2) - (int)((VIS_PORTS*NOTE_LONG_SIDE)/2); |
|||
//int yOffset = -1 * ((int)(screenWidth/2) - ((programState.pitchMax - programState.pitchMin +1 ) * NOTE_SMALL_SIDE))/2;
|
|||
int yOffset = -1 * ((int)(screenHeight/2) - (60 * NOTE_SMALL_SIDE)/2); //we want all tracks around the center.
|
|||
|
|||
programState.modeDescription = "Each port is a vertical column.\n\
|
|||
Each pitch is one square, going from top highest\nto bottomw lowest.\n\ |
|||
\n\ |
|||
The higher the velocity of a note the more\nvibrant its color.\n\ |
|||
\n\ |
|||
Notes are only lit up when they are played.\n\ |
|||
There is no time-dimension."; |
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
backgroundRects[port].x = xOffset + port*(NOTE_LONG_SIDE + NOTE_GAP) - NOTE_GAP/2; |
|||
backgroundRects[port].y = yOffset + (127-programState.pitchMax) * NOTE_SMALL_SIDE; |
|||
backgroundRects[port].width = (float)NOTE_LONG_SIDE; |
|||
backgroundRects[port].height = (programState.pitchMax - programState.pitchMin +1 ) * NOTE_SMALL_SIDE; |
|||
|
|||
for (int midiPitch=0; midiPitch<128; midiPitch++) { //We prepare the full range of pitches, but the main loop will only activate the current pitch range.
|
|||
allGUINotes[port*128 + midiPitch].x = xOffset + port*(NOTE_LONG_SIDE + NOTE_GAP); |
|||
allGUINotes[port*128 + midiPitch].y = yOffset + (127-midiPitch) * NOTE_SMALL_SIDE ; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static void drawBackground() { |
|||
//Each port is one track
|
|||
//The background rects already have their positions and dimensions set.
|
|||
if (programState.showPortBackground) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
if (portShouldDraw(port)) { |
|||
DrawRectangleRec(backgroundRects[port], programState.colors[backgroundLight]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void draw_xports_ypitches(Note *allGUINotes, int redrawNeeded) { |
|||
Note * nt; |
|||
float ft = GetFrameTime(); |
|||
|
|||
if (redrawNeeded) { |
|||
reposition(allGUINotes); |
|||
} |
|||
|
|||
//Z-Order is created by call-order. First is bottom.
|
|||
drawBackground(); |
|||
|
|||
|
|||
if (programState.showConnectedPortnames) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
nt = &allGUINotes[port*128+programState.pitchMin]; // bottom left note of each port
|
|||
drawJackPortName(programState.connectedPortNames[port], nt->x, nt->y+20, 18, RAYWHITE, true); //text, x, y, int fontsize, color, vertical
|
|||
} |
|||
} |
|||
|
|||
if (programState.showPitchMarker) { |
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
if (portShouldDraw(port)) { |
|||
nt = &allGUINotes[port*128 + programState.pitchMarkerValue]; |
|||
Rectangle rec = (Rectangle){nt->x, nt->y, NOTE_LONG_SIDE-NOTE_BORDER, NOTE_SMALL_SIDE-NOTE_BORDER}; |
|||
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
for (int port=0; port<VIS_PORTS; port++) { |
|||
for (int midiPitch=programState.pitchMin; midiPitch<=programState.pitchMax; midiPitch++) { |
|||
nt = &allGUINotes[port*128 + midiPitch]; |
|||
if (nt->active) { |
|||
reduceCountdown(nt, ft); //handles 0.0 and 1.0 as special cases
|
|||
drawNoteRect(nt, 1, 0); //note, rotated, square. Will not draw if countdown == 0
|
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
#ifndef DRAW_XPORTS_YPITCHES_H |
|||
#define DRAW_XPORTS_YPITCHES_H |
|||
|
|||
void draw_xports_ypitches(Note *allGUINotes, int redrawNeeded); |
|||
|
|||
#endif // not defined DRAW_XPORTS_YPITCHES_H
|
@ -0,0 +1,181 @@ |
|||
//Standard lib
|
|||
#include <string.h> //strchr |
|||
#include <stdio.h> //print error messages |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "drawhelper.h" |
|||
|
|||
extern ProgramState programState; |
|||
|
|||
|
|||
float intToHue(int number, int maxnumber) { |
|||
//previously used to auto-color ports this is now only used for the default port colors.
|
|||
|
|||
//portnum is 0-VIS_PORTS
|
|||
//hue is 0-360
|
|||
//return (input-inputLowest) / (inputHighest-inputLowest) * (outputHighest-outputLowest) + outputLowest
|
|||
return (float)number / (float)maxnumber * 360.0f; |
|||
} |
|||
|
|||
Color intToColor(int number, int maxnumber) { |
|||
Color result; |
|||
Vector3 hsv; // 3 floats: hue/color, saturation, value/brightness
|
|||
|
|||
hsv = (Vector3){intToHue(number, VIS_PORTS), 1.0f, (float)(127+40)/(float)167}; //Saturation is always 1, we tweak Value to never go into the black range.
|
|||
result = ColorFromHSV(hsv); |
|||
return result; |
|||
} |
|||
|
|||
Color midiToColor(Note * note) { |
|||
Color result; |
|||
Vector3 hsv; // 3 floats: hue/color, saturation, value/brightness
|
|||
|
|||
//hsv = (Vector3){intToHue(note->port, VIS_PORTS), 1.0f, (float)(note->velocity+40)/(float)167}; //Saturation is always 1, we tweak Value to never go into the black range.
|
|||
float hue=ColorToHSV(programState.colors[note->port]).x; |
|||
hsv = (Vector3){hue, 1.0f, (float)(note->velocity+40)/(float)167}; //Saturation is always 1, we tweak Value to never go into the black range.
|
|||
result = ColorFromHSV(hsv); |
|||
return result; |
|||
} |
|||
|
|||
void drawNoteRect(Note * note, int rotated, int square) { |
|||
if (note->active) { |
|||
//opacity is between 0.0f and 1.0f
|
|||
int w,h; |
|||
|
|||
if (square==1) { |
|||
w = NOTE_SMALL_SIDE - NOTE_BORDER; |
|||
h = NOTE_SMALL_SIDE - NOTE_BORDER; |
|||
} |
|||
else { //Not Square
|
|||
if (rotated==0) { |
|||
w = NOTE_SMALL_SIDE - NOTE_BORDER; |
|||
h = NOTE_LONG_SIDE - NOTE_BORDER; |
|||
} |
|||
else { |
|||
h = NOTE_SMALL_SIDE - NOTE_BORDER; |
|||
w = NOTE_LONG_SIDE - NOTE_BORDER; |
|||
} |
|||
} |
|||
|
|||
const Rectangle rec = (Rectangle){note->x, note->y, w,h}; |
|||
const Rectangle outer = (Rectangle){note->x-NOTE_BORDER/2, note->y-NOTE_BORDER/2, w+NOTE_BORDER, h+NOTE_BORDER}; |
|||
|
|||
Color bordercolor = Fade(BLACK, note->countdown); |
|||
Color color = Fade(programState.colors[note->port], note->countdown); |
|||
|
|||
//Drawing order is reverse Z-Order
|
|||
DrawRectangleRounded(outer, 0.5, 8, bordercolor); //float roundness, int segments/smoothness, Color
|
|||
DrawRectangleRounded(rec, 0.5, 8, color); |
|||
} |
|||
} |
|||
|
|||
bool portShouldDraw(int port) { |
|||
//Check various conditions if a port background, port name and pitch marker should be drawn
|
|||
if ( programState.showPortBackgroundForUnconnected || ( programState.connectedPortNames[port] && programState.connectedPortNames[port][0] != '\0' )) { |
|||
return true; |
|||
} |
|||
else { |
|||
return false; |
|||
} |
|||
|
|||
} |
|||
|
|||
void drawJackPortName(const char * portname, int x, int y, int fontsize, Color color, bool vertical) { |
|||
|
|||
if (portname && portname[0] != '\0') { |
|||
|
|||
const char * finalPortName; |
|||
if (programState.includeClientInPortNameDisplay) { |
|||
finalPortName = portname; //TODO: Pretty Names don't contain the client name. Add it ourselves?
|
|||
} |
|||
else { |
|||
if (strchr(portname, ':') == NULL ) //Pretty Names don't contain the client name.
|
|||
{ |
|||
finalPortName = portname; |
|||
} |
|||
else { |
|||
finalPortName = strchr(portname, ':')+1; |
|||
} |
|||
} //final port name is ready.
|
|||
|
|||
|
|||
//Vertical Mode
|
|||
//void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float scale, Color tint); // Draw one character (codepoint)
|
|||
if (vertical) { |
|||
//TODO: Wow, that is ugly!
|
|||
int counter = 0; |
|||
char ch = finalPortName[counter]; |
|||
Font defaultFont = GetFontDefault(); |
|||
while (ch != '\0') { |
|||
DrawTextCodepoint(defaultFont, ch, (Vector2){ x, y+counter*(fontsize/1.5) }, 1.8f, color); |
|||
counter++; |
|||
ch = finalPortName[counter]; |
|||
} |
|||
} |
|||
else { |
|||
// Horizontal Mode
|
|||
DrawText(finalPortName, x, y, fontsize, color); |
|||
} |
|||
} |
|||
} |
|||
|
|||
float calculateCountdown(float countdown, float ft) { |
|||
float bpmFactor; |
|||
|
|||
switch (programState.fadeOutMode) { |
|||
case 0: // framelegnth * user factor
|
|||
if (countdown < 1.0f) { countdown -= (float)programState.fadeOutFactor * ft; } //note off has been triggered. Fade out.
|
|||
if (countdown <= 0.0f) { countdown = 0; } // Is it now, or was already, below 0?
|
|||
break; |
|||
|
|||
case 1: // bpm
|
|||
bpmFactor = (float)(programState.bpm/40); |
|||
if (countdown < 1.0f) { countdown -= bpmFactor * ft; } //note off has been triggered. Fade out.
|
|||
if (countdown <= 0.0f) { countdown = 0.0f; } // Is it now, or was already, below 0?
|
|||
break; |
|||
|
|||
case 2: // no fadeout
|
|||
if (countdown < 1.0f) { countdown = 0.0f; } // As soon as note off arrives
|
|||
break; |
|||
|
|||
default: //just in case
|
|||
countdown = 0.0f; |
|||
break; |
|||
} |
|||
|
|||
return countdown; |
|||
} |
|||
|
|||
void reduceCountdown(Note * nt, float ft) { |
|||
//Notes countdown is set to 0 (note-off), 1 (note-on) or 0.999 (note-off) by our jack midi client.
|
|||
|
|||
float bpmFactor; |
|||
|
|||
if (nt->active == 1) { |
|||
switch (programState.fadeOutMode) { |
|||
case 0: // framelegnth * user factor
|
|||
if (nt->countdown < 1.0f) { nt->countdown -= (float)programState.fadeOutFactor * ft; } //note off has been triggered. Fade out.
|
|||
if (nt->countdown <= 0.0f) { nt->active = false; } // Is it now, or was already, below 0?
|
|||
break; |
|||
|
|||
case 1: // bpm
|
|||
bpmFactor = (float)(programState.bpm/40); |
|||
if (nt->countdown < 1.0f) { nt->countdown -= bpmFactor * ft; } //note off has been triggered. Fade out.
|
|||
if (nt->countdown <= 0.0f) { nt->active = false; } // Is it now, or was already, below 0?
|
|||
break; |
|||
|
|||
case 2: // no fadeout
|
|||
if (nt->countdown < 1.0f) { nt->active = false; } // As soon as note off arrives
|
|||
break; |
|||
|
|||
default: //just in case
|
|||
nt->active = false; |
|||
break; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
#ifndef DRAWHELPER_H |
|||
#define DRAWHELPER_H |
|||
|
|||
#include "constants.h" |
|||
|
|||
Color midiToColor(Note * note); |
|||
Color intToColor(int number, int maxnumber); |
|||
void drawNoteRect(Note * note, int rotated, int square); |
|||
void reduceCountdown(Note * nt, float ft); |
|||
float calculateCountdown(float countdown, float ft); |
|||
void drawJackPortName(const char * portname, int x, int y, int fontsize, Color color, bool vertical); |
|||
bool portShouldDraw(int port); |
|||
|
|||
#endif // not defined DRAWHELPER_H
|
@ -0,0 +1,100 @@ |
|||
//Standard lib
|
|||
#include <stdio.h> |
|||
|
|||
//Third party
|
|||
#include "raylib.h" |
|||
|
|||
//Our own files
|
|||
#include "constants.h" |
|||
#include "programstate.h" |
|||
#include "effect_starfield.h" |
|||
|
|||
extern ProgramState programState; |
|||
static int screenWidth; |
|||
static int screenHeight; |
|||
static const int numberOfStars=540; |
|||
|
|||
static const int starLayers = 3; |
|||
|
|||
typedef struct { |
|||
int x; |
|||
int y; |
|||
int z; |
|||
Color color; |
|||
float rand; |
|||
} Star; |
|||
|
|||
static Star stars[540]; //1080 * 0.5 Not quite one star per line
|
|||
|
|||
void reposition() { |
|||
//Recreate all stars and begin from blank.
|
|||
//We want a certain star density, no matter the screen resolution
|
|||
|
|||
screenWidth = GetScreenWidth(); |
|||
screenHeight = GetScreenHeight(); |
|||
Color color; |
|||
int rand; |
|||
int layer; |
|||
int partitionSize=(starLayers+1)*2 / 8; //8 partions
|
|||
for (int i=0; i<numberOfStars; i++) { |
|||
rand = GetRandomValue(1, (starLayers+1)*2); |
|||
if (rand<= 5*partitionSize) layer = 1; |
|||
else if (rand<= 7*partitionSize) layer = 2; |
|||
else if (rand<= 8*partitionSize) layer = 3; |
|||
|
|||
switch (layer) { |
|||
//0 does not exist
|
|||
case 1: |
|||
color = BROWN; |
|||
break; |
|||
case 2: |
|||
color = BEIGE; |
|||
break; |
|||
case 3: |
|||
color = PURPLE; |
|||
break; |
|||
default: |
|||
color = DARKBLUE; |
|||
} |
|||
|
|||
|
|||
float randomFactor = ((float)GetRandomValue(0, 20)-10.0f) / 100.0f; |
|||
stars[i] = (Star){ GetRandomValue(0, screenWidth), GetRandomValue(0, screenHeight), layer, color, randomFactor }; |
|||
} |
|||
} |
|||
|
|||
void effect_starfield(bool redrawNeeded, float opacity, int speedModifier, int size) { |
|||
|
|||
float ft = GetFrameTime(); |
|||
int bpmFactor = programState.bpm/40 * programState.bpm/20; //exponential curve. The higher bpm the faster we get. Below 90 it should feel really slow
|
|||
Star * star; |
|||
|
|||
if (redrawNeeded) { |
|||
reposition(); |
|||
} |
|||
|
|||
for (int i=0; i<numberOfStars; i++) { |
|||
star = &stars[i]; |
|||
|
|||
if (programState.transportRolling) { |
|||
//Move Forward
|
|||
star->x -= (star->z * ft * bpmFactor * 3 * speedModifier); //... the mysterious number 3.
|
|||
//wrap around
|
|||
if (star->x <= 0) { |
|||
star->x += screenWidth; |
|||
star->y = GetRandomValue(0, screenHeight); |
|||
} |
|||
else if (star->x > screenWidth) { |
|||
star->x -= screenWidth; |
|||
star->y = GetRandomValue(0, screenHeight); |
|||
} |
|||
} |
|||
|
|||
//Wobble wobble
|
|||
int rand = GetRandomValue(1, 200); |
|||
if (rand == 1) {star->y += 1;} |
|||
else if (rand == 2) {star->y -= 1;} |
|||
|
|||
DrawRectangle(star->x, star->y, size, size, Fade(star->color, opacity)); |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
#ifndef effect_starfield_H |
|||
#define effect_starfield_H |
|||
|
|||
void effect_starfield(bool redrawNeeded, float opacity, int speedModifier, int size); |
|||
|
|||
#endif // not defined effect_starfield_H
|
Binary file not shown.
@ -0,0 +1,40 @@ |
|||
#version 330 |
|||
|
|||
// Input vertex attributes (from vertex shader) |
|||
in vec2 fragTexCoord; |
|||
in vec4 fragColor; |
|||
|
|||
// Input uniform values |
|||
uniform sampler2D texture0; |
|||
uniform vec4 colDiffuse; |
|||
|
|||
// Output fragment color |
|||
out vec4 finalColor; |
|||
|
|||
// NOTE: Add here your custom variables |
|||
|
|||
const vec2 size = vec2(1920, 1080); // render size |
|||
const float samples = 5.0; // pixels per axis; higher = bigger glow, worse performance |
|||
const float quality = 2.5; // lower = smaller glow, better quality |
|||
|
|||
void main() |
|||
{ |
|||
vec4 sum = vec4(0); |
|||
vec2 sizeFactor = vec2(1)/size*quality; |
|||
|
|||
// Texel color fetching from texture sampler |
|||
vec4 source = texture(texture0, fragTexCoord); |
|||
|
|||
const int range = 2; // should be = (samples - 1)/2; |
|||
|
|||
for (int x = -range; x <= range; x++) |
|||
{ |
|||
for (int y = -range; y <= range; y++) |
|||
{ |
|||