commit du code source

This commit is contained in:
nicolas 2026-02-04 11:51:22 +01:00
parent b92e43ca11
commit 31c19a595b
40 changed files with 11537 additions and 1 deletions

3
.gitignore vendored
View File

@ -32,3 +32,6 @@
*.out
*.app
JuceLibraryCode/**
/.vs

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

229
Makefile Normal file
View File

@ -0,0 +1,229 @@
# Automatically generated makefile, created by the Projucer
# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!
# build with "V=1" for verbose builds
ifeq ($(V), 1)
V_AT =
else
V_AT = @
endif
# (this disables dependency generation if multiple architectures are set)
DEPFLAGS := $(if $(word 2, $(TARGET_ARCH)), , -MMD)
ifndef PKG_CONFIG
PKG_CONFIG=pkg-config
endif
ifndef STRIP
STRIP=strip
endif
ifndef AR
AR=ar
endif
ifndef CONFIG
CONFIG=Debug
endif
JUCE_ARCH_LABEL := $(shell uname -m)
ifeq ($(CONFIG),Debug)
JUCE_BINDIR := build
JUCE_LIBDIR := build
JUCE_OBJDIR := build/intermediate/Debug
JUCE_OUTDIR := build
ifeq ($(TARGET_ARCH),)
TARGET_ARCH :=
endif
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=1" "-DJUCE_USE_DARK_SPLASH_SCREEN=0" "-DJUCE_PROJUCER_VERSION=0x70003" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_JACK=1" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_STANDALONE_APPLICATION=1" "-DJUCER_LINUX_MAKE_B28D=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell $(PKG_CONFIG) --cflags alsa freetype2 libcurl webkit2gtk-4.0 gtk+-x11-3.0) -pthread -IJuceLibraryCode -IJuceLibraryCode/modules $(CPPFLAGS)
JUCE_CPPFLAGS_APP := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=0"
JUCE_TARGET_APP := Tchick
JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0 $(CFLAGS)
JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++20 $(CXXFLAGS)
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) $(shell $(PKG_CONFIG) --libs alsa freetype2 libcurl) -fvisibility=hidden -lrt -ldl -lpthread $(LDFLAGS)
CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)
endif
ifeq ($(CONFIG),Release)
JUCE_BINDIR := build
JUCE_LIBDIR := build
JUCE_OBJDIR := build/intermediate/Release
JUCE_OUTDIR := build
ifeq ($(TARGET_ARCH),)
TARGET_ARCH :=
endif
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=1" "-DJUCE_USE_DARK_SPLASH_SCREEN=0" "-DJUCE_PROJUCER_VERSION=0x70003" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_JACK=1" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_STANDALONE_APPLICATION=1" "-DJUCER_LINUX_MAKE_B28D=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell $(PKG_CONFIG) --cflags alsa freetype2 libcurl webkit2gtk-4.0 gtk+-x11-3.0) -pthread -IJuceLibraryCode -IJuceLibraryCode/modules $(CPPFLAGS)
JUCE_CPPFLAGS_APP := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=0"
JUCE_TARGET_APP := Tchick
JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -O3 $(CFLAGS)
JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++20 $(CXXFLAGS)
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) $(shell $(PKG_CONFIG) --libs alsa freetype2 libcurl) -fvisibility=hidden -lrt -ldl -lpthread $(LDFLAGS)
CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)
endif
OBJECTS_APP := \
$(JUCE_OBJDIR)/OptionsPanel_74384d2f.o \
$(JUCE_OBJDIR)/TempoPanel_68242652.o \
$(JUCE_OBJDIR)/PlayPanel_f7a1c6f1.o \
$(JUCE_OBJDIR)/MesurePanel_621c6998.o \
$(JUCE_OBJDIR)/Config_df6c736b.o \
$(JUCE_OBJDIR)/BPMPanel_fcde4d0e.o \
$(JUCE_OBJDIR)/Main_b18eef62.o \
$(JUCE_OBJDIR)/Metronome_d2176e73.o \
$(JUCE_OBJDIR)/MainComponent_fec15f05.o \
$(JUCE_OBJDIR)/include_juce_audio_basics_854129ea.o \
$(JUCE_OBJDIR)/include_juce_audio_devices_c670bf62.o \
$(JUCE_OBJDIR)/include_juce_audio_formats_7957c261.o \
$(JUCE_OBJDIR)/include_juce_audio_processors_46d0f806.o \
$(JUCE_OBJDIR)/include_juce_audio_processors_ara_80e36097.o \
$(JUCE_OBJDIR)/include_juce_audio_processors_lv2_libs_badcdc68.o \
$(JUCE_OBJDIR)/include_juce_audio_utils_d9446d36.o \
$(JUCE_OBJDIR)/include_juce_core_f75b497b.o \
$(JUCE_OBJDIR)/include_juce_data_structures_7e723c43.o \
$(JUCE_OBJDIR)/include_juce_events_92002035.o \
$(JUCE_OBJDIR)/include_juce_graphics_915442e7.o \
$(JUCE_OBJDIR)/include_juce_gui_basics_1fa21125.o \
$(JUCE_OBJDIR)/include_juce_gui_extra_fc3dee7a.o \
.PHONY: clean all strip
all : $(JUCE_OUTDIR)/$(JUCE_TARGET_APP)
$(JUCE_OUTDIR)/$(JUCE_TARGET_APP) : $(OBJECTS_APP) $(RESOURCES)
@command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; }
@$(PKG_CONFIG) --print-errors alsa freetype2 libcurl
@echo Linking "Tchick - App"
-$(V_AT)mkdir -p $(JUCE_BINDIR)
-$(V_AT)mkdir -p $(JUCE_LIBDIR)
-$(V_AT)mkdir -p $(JUCE_OUTDIR)
$(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_APP) $(OBJECTS_APP) $(JUCE_LDFLAGS) $(JUCE_LDFLAGS_APP) $(RESOURCES) $(TARGET_ARCH)
$(JUCE_OBJDIR)/OptionsPanel_74384d2f.o: Source/OptionsPanel.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling OptionsPanel.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/TempoPanel_68242652.o: Source/TempoPanel.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling TempoPanel.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/PlayPanel_f7a1c6f1.o: Source/PlayPanel.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling PlayPanel.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/MesurePanel_621c6998.o: Source/MesurePanel.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling MesurePanel.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/Config_df6c736b.o: Source/Config.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling Config.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/BPMPanel_fcde4d0e.o: Source/BPMPanel.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling BPMPanel.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/Main_b18eef62.o: Source/Main.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling Main.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/Metronome_d2176e73.o: Source/Metronome.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling Metronome.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/MainComponent_fec15f05.o: Source/MainComponent.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling MainComponent.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_basics_854129ea.o: JuceLibraryCode/include_juce_audio_basics.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_basics.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_devices_c670bf62.o: JuceLibraryCode/include_juce_audio_devices.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_devices.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_formats_7957c261.o: JuceLibraryCode/include_juce_audio_formats.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_formats.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_processors_46d0f806.o: JuceLibraryCode/include_juce_audio_processors.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_processors.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_processors_ara_80e36097.o: JuceLibraryCode/include_juce_audio_processors_ara.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_processors_ara.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_processors_lv2_libs_badcdc68.o: JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_processors_lv2_libs.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_audio_utils_d9446d36.o: JuceLibraryCode/include_juce_audio_utils.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_audio_utils.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_core_f75b497b.o: JuceLibraryCode/include_juce_core.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_core.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_data_structures_7e723c43.o: JuceLibraryCode/include_juce_data_structures.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_data_structures.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_events_92002035.o: JuceLibraryCode/include_juce_events.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_events.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_graphics_915442e7.o: JuceLibraryCode/include_juce_graphics.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_graphics.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_gui_basics_1fa21125.o: JuceLibraryCode/include_juce_gui_basics.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_gui_basics.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_gui_extra_fc3dee7a.o: JuceLibraryCode/include_juce_gui_extra.cpp
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
@echo "Compiling include_juce_gui_extra.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
clean:
@echo Cleaning Tchick
$(V_AT)$(CLEANCMD)
strip:
@echo Stripping Tchick
-$(V_AT)$(STRIP) --strip-unneeded $(JUCE_OUTDIR)/$(TARGET)
-include $(OBJECTS_APP:%.o=%.d)

View File

@ -1,3 +1,12 @@
# Tchick
Simple metronome
A basic metronome application built to test capabilities of [JUCE](https://github.com/juce-framework/JUCE)
audio application framework.
## Build
Generate your prefered build chain via Projucer by opening Tchick.sln
Visual Studio 2022 and linux makefiles are provided as examples.
On linux, you have to copy Assets/Clicks folder in the output directory.
## Run
Run the executable. Click play button. Have fun.

View File

@ -0,0 +1,10 @@
name: New MCP server
version: 0.0.1
schema: v1
mcpServers:
- name: New MCP server
command: npx
args:
- -y
- <your-mcp-server>
env: {}

View File

@ -0,0 +1,5 @@
---
description: A description of your rule
---
Your rule content

5
Source/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"continue.enableConsole": true,
"continue.enableQuickActions": true,
"continue.telemetryEnabled": false
}

156
Source/BPMPanel.cpp Normal file
View File

@ -0,0 +1,156 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du tempo.
==============================================================================
*/
#include "BPMPanel.h"
BPMPanel::BPMPanel(juce::Colour c) : backgroundColour(c)
{
// initialisation de l'interface
addComponent(decrement);
addComponent(bpm);
addComponent(increment);
addComponent(twice);
addComponent(half);
addComponent(set60);
addComponent(set90);
addComponent(set120);
addComponent(set150);
decrement.setRepeatSpeed(300, 100); // On veux pouvoir laisser le bouton appuyé
increment.setRepeatSpeed(300, 100);
bpm.setText("72", juce::dontSendNotification);
bpm.getFont().setBold(true);
bpm.setColour(juce::Label::textColourId, juce::Colours::black);
bpm.setJustificationType(juce::Justification::centred);
bpm.setFont(50);
}
void BPMPanel::addComponent(juce::Component& c)
{
addAndMakeVisible(c);
c.setColour(juce::TextButton::buttonColourId, backgroundColour);
c.setColour(juce::TextButton::textColourOffId, juce::Colours::black);
}
void BPMPanel::mouseWheelMove(const juce::MouseEvent&,
const juce::MouseWheelDetails& wheel)
{
// capture des mouseWheel pour invoquer increment/decrement
float dir = wheel.deltaY * (wheel.isReversed ? -1.0f : 1.0f);
if (dir > 0)
{
increment.onClick();
}
else
{
decrement.onClick();
}
}
void BPMPanel::paint(juce::Graphics& g)
{
g.fillAll(backgroundColour);
}
void BPMPanel::resized()
{
// Layout à la mano avec une première ligne pour les raccourcis
auto area = getLocalBounds();
int margin = 3;
area.removeFromTop(3*margin);
{
// première ligne accès rapide aux speeds
int setLinHeigth = 20;
auto setSpeed = area.removeFromTop(setLinHeigth);
int butWidth = 64;
int marginH = (setSpeed.getWidth() - butWidth * 4) / 2;
setSpeed.removeFromLeft(marginH);
set60.setBounds(setSpeed.removeFromLeft(butWidth)); setSpeed.removeFromLeft(margin);
set90.setBounds(setSpeed.removeFromLeft(butWidth)); setSpeed.removeFromLeft(margin);
set120.setBounds(setSpeed.removeFromLeft(butWidth)); setSpeed.removeFromLeft(margin);
set150.setBounds(setSpeed.removeFromLeft(butWidth)); setSpeed.removeFromLeft(margin);
}
area.removeFromTop(margin);
// Et une deuxième ligne avec increment/decrement et le tempo courant
{
// deuxième ligne BPM inc + dec
int butHeigth = 20;
int butWidth = 64;
int labeSize = 5 * butHeigth;
int totalWidth = 2 * butWidth + labeSize;
int marginH = (area.getWidth() - totalWidth) / 2;
int marginV = (area.getHeight() - labeSize) / 2;
decrement.setBounds(area.getX() + marginH, area.getY() + marginV + butHeigth, butWidth, 2 * butHeigth);
half.setBounds(area.getX() + marginH, area.getY() + marginV + 3 * butHeigth + margin, butWidth, butHeigth);
bpm.setBounds(area.getX() + marginH + butWidth, area.getY() + marginV, labeSize, labeSize);
increment.setBounds(area.getX() + marginH + butWidth + labeSize, area.getY() + marginV + butHeigth, butWidth, 2 * butHeigth);
twice.setBounds(area.getX() + marginH + butWidth + labeSize, area.getY() + marginV + 3 * butHeigth + margin, butWidth, butHeigth);
}
}
void BPMPanel::init(Metronome & metro)
{
// On établi les liaisons entre le Metronome et les composants graphique
bpm.setText(juce::String(metro.getBPM()), juce::dontSendNotification); // On récupère la valeur initiale du tempo
increment.onClick = [&, this]() { inc(metro); };
decrement.onClick = [&, this]() { dec(metro); };
set60.onClick = [&, this]() { setBPM(metro, 60); };
set90.onClick = [&, this]() { setBPM(metro, 90); };
set120.onClick = [&, this]() { setBPM(metro, 120); };
set150.onClick = [&, this]() { setBPM(metro, 150); };
half.onClick = [&, this]() { setBPM(metro, metro.getBPM() / 2); };
twice.onClick = [&, this]() { setBPM(metro, metro.getBPM() * 2); };
}
void BPMPanel::inc(Metronome& metro)
{
// Ajoute 1 au tempo
metro.setBPM(metro.getBPM() + 1);
bpm.setText(juce::String(metro.getBPM()), juce::dontSendNotification);
}
void BPMPanel::dec(Metronome& metro)
{
// reduit le tempo de 1
metro.setBPM(metro.getBPM() - 1);
bpm.setText(juce::String(metro.getBPM()), juce::dontSendNotification);
}
void BPMPanel::setBPM(Metronome& metro, int value)
{
// défini la nouvelle valeur du tempo
metro.setBPM(value);
bpm.setText(juce::String(metro.getBPM()), juce::dontSendNotification);
}

66
Source/BPMPanel.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du tempo.
==============================================================================
*/
#include <JuceHeader.h>
#include "Metronome.h"
struct BPMPanel : public juce::Component
{
explicit BPMPanel(juce::Colour c);
// Méthode de surcharge JUCE
void mouseWheelMove(const juce::MouseEvent&,
const juce::MouseWheelDetails& wheel) override; // on prend en charge le mouseWheel pour changer le tempo
void paint(juce::Graphics& g) override;
void resized() override;
// utilitaire graphique
void addComponent(juce::Component& c);
// méthodes de gestion du tempo
void init(Metronome& metro);
void inc(Metronome& metro);
void dec(Metronome& metro);
void setBPM(Metronome& metro, int value);
// composants graphiques
juce::Colour backgroundColour;
juce::TextButton increment{ "+" };
juce::TextButton decrement{ "-" };
juce::TextButton twice{ "*2" };
juce::TextButton half{ "/2" };
juce::TextButton set60{ "60" };
juce::TextButton set90{ "90" };
juce::TextButton set120{ "120" };
juce::TextButton set150{ "150" };
juce::Label bpm;
};

104
Source/Config.cpp Normal file
View File

@ -0,0 +1,104 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion de la sauvegarde de la configuration courante.
==============================================================================
*/
#include "Config.h"
// petit singleton
Config* Config::instance = nullptr;
Config& Config::Load()
{
if (instance == nullptr)
instance = new Config();
// On cherche le fichier json dans le repertoire courant
juce::File curDir{ juce::File::getSpecialLocation(juce::File::SpecialLocationType::currentExecutableFile).getParentDirectory() };
juce::File confFile{ curDir.getChildFile("tchick.json") };
if (confFile.exists())
{
// s'il existe, on le charge
auto content = juce::JSON::parse(confFile);
instance->fromVar(content);
}
return *instance;
}
void Config::Save(Metronome& metro, OptionsPanel& options)
{
// On initialise les paramètres
instance->bpm = metro.getBPM();
instance->measures = metro.getMeasures();
instance->gain = metro.getGain();
instance->figure = (int)metro.getFigure();
instance->sound = options.getSoundName();
// on transforme en var
juce::var obj = instance->asVar();
// on met à jours le fichier
juce::File curDir{ juce::File::getSpecialLocation(juce::File::SpecialLocationType::currentExecutableFile).getParentDirectory() };
juce::File confFile{ curDir.getChildFile("tchick.json") };
juce::FileOutputStream stream(confFile);
if (stream.openedOk())
{
// méthode préconisé pour ecraser le fichier existant
stream.setPosition(0);
stream.truncate();
juce::JSON::writeToStream(stream, obj);
}
}
juce::var Config::asVar()
{
// création du var à partir de this
juce::DynamicObject* obj = new juce::DynamicObject();
juce::NamedValueSet& properties = obj->getProperties();
properties.set("bpm", bpm);
properties.set("measures", measures);
properties.set("gain", gain);
properties.set("sound", sound);
properties.set("figure", figure);
return juce::var(obj);
}
void Config::fromVar(juce::var var)
{
// chargement des paramètres depuis un var
juce::DynamicObject* obj = var.getDynamicObject();
juce::NamedValueSet& properties = obj->getProperties();
if(properties.contains("bpm")) bpm = properties["bpm"];
if(properties.contains("measures")) measures = properties["measures"];
if(properties.contains("gain")) gain = properties["gain"];
if(properties.contains("sound")) sound = properties["sound"];
if(properties.contains("figure")) figure = properties["figure"];
}

67
Source/Config.h Normal file
View File

@ -0,0 +1,67 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion de la sauvegarde de la configuration courante.
==============================================================================
*/
#include <JuceHeader.h>
#include "Metronome.h"
#include "OptionsPanel.h"
class Metronome;
struct OptionsPanel;
class Config
{
public:
~Config() {};
// charge la config depuis le fichier json ou les valeurs par defaut
static Config& Load();
// enregistre la config dans le fichier json
static void Save(Metronome& metro, OptionsPanel& options);
// paramètres sauvegardés
int bpm{ 72 }; // default 72
int measures{ 4 }; // default 4
float gain{ 1.0f }; // default 1.0
juce::String sound{ "" }; // default empty
int figure{ 1 }; // default 1 (noire)
private:
// je crois que c'est pas suffisant mais on devrait toujours passer par les méthodes statiques
Config() {};
// convertisseur paramètre <-> var
juce::var asVar();
void fromVar(juce::var var);
// seule instance de config
static Config* instance;
};

43
Source/Figures.h Normal file

File diff suppressed because one or more lines are too long

118
Source/Main.cpp Normal file
View File

@ -0,0 +1,118 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
#include <JuceHeader.h>
#include "MainComponent.h"
//==============================================================================
class MetronomeApplication : public juce::JUCEApplication
{
public:
//==============================================================================
MetronomeApplication() {}
const juce::String getApplicationName() override { return ProjectInfo::projectName; }
const juce::String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return true; }
//==============================================================================
void initialise (const juce::String&) override
{
// This method is where you should put your application's initialisation code..
mainWindow.reset (new MainWindow (getApplicationName()));
}
void shutdown() override
{
// Add your application's shutdown code here..
mainWindow = nullptr; // (deletes our window)
}
//==============================================================================
void systemRequestedQuit() override
{
// This is called when the app is being asked to quit: you can ignore this
// request and let the app carry on running, or call quit() to allow the app to close.
quit();
}
void anotherInstanceStarted (const juce::String&) override
{
// When another instance of the app is launched while this one is running,
// this method is invoked, and the commandLine parameter tells you what
// the other instance's command-line arguments were.
}
//==============================================================================
/*
This class implements the desktop window that contains an instance of
our MainComponent class.
*/
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow (juce::String name)
: DocumentWindow (name,
juce::Desktop::getInstance().getDefaultLookAndFeel()
.findColour (juce::ResizableWindow::backgroundColourId),
DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new MainComponent(Config::Load()), true); // On charge la configuration json et on la passe au constructeur de MainComponent
#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
#else
setResizable (false, false);
centreWithSize (getWidth(), getHeight());
#endif
setVisible (true);
}
void closeButtonPressed() override
{
// This is called when the user tries to close this window. Here, we'll just
// ask the app to quit when this happens, but you can change this to do
// whatever you need.
((MainComponent*)getContentComponent())->saveConfig();
JUCEApplication::getInstance()->systemRequestedQuit();
}
/* Note: Be careful if you override any DocumentWindow methods - the base
class uses a lot of them, so by overriding you might break its functionality.
It's best to do all your work in your content component instead, but if
you really have to override any DocumentWindow methods, make sure your
subclass also calls the superclass's method.
*/
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
private:
std::unique_ptr<MainWindow> mainWindow;
};
//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (MetronomeApplication)

143
Source/MainComponent.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
#include "MainComponent.h"
//==============================================================================
MainComponent::MainComponent(Config& conf) :
metronome(conf),
measures(juce::Colours::whitesmoke),
bpm(juce::Colours::whitesmoke),
tempo(juce::Colours::whitesmoke),
playButton(juce::Colours::whitesmoke),
options(juce::Colours::whitesmoke, conf)
{
addAndMakeVisible (measures);
addAndMakeVisible (bpm);
bpm.init(metronome);
addAndMakeVisible (tempo);
tempo.init(metronome);
addAndMakeVisible(playButton);
playButton.init(metronome);
addAndMakeVisible (options);
options.init(metronome);
setSize (400, 600);
// Some platforms require permissions to open input channels so request that here
if (juce::RuntimePermissions::isRequired (juce::RuntimePermissions::recordAudio)
&& ! juce::RuntimePermissions::isGranted (juce::RuntimePermissions::recordAudio))
{
juce::RuntimePermissions::request (juce::RuntimePermissions::recordAudio,
[&] (bool granted) { if (granted) setAudioChannels (2, 2); });
}
else
{
// Specify the number of input and output channels that we want to open
setAudioChannels (0, 2);
}
startTimer(20); // timer pour aller relire les eventuels signaux du métronome
}
MainComponent::~MainComponent()
{
shutdownAudio();
}
//==============================================================================
void MainComponent::prepareToPlay (int , double sampleRate)
{
metronome.prepareToPlay(sampleRate); // passe le sampleRate au métronome
}
void MainComponent::getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill)
{
// netoyage de l'entrée
bufferToFill.clearActiveBufferRegion();
if (playButton.playState == PlayState::playing)
{
// boucle de lecture du métronome
metronome.getNextAudioBlock (bufferToFill);
}
}
void MainComponent::releaseResources()
{
}
//==============================================================================
void MainComponent::paint (juce::Graphics& g)
{
g.fillAll (juce::Colours::lightgrey);
}
void MainComponent::resized()
{
// Comme on à séparé tout en composants, on utilise un simple grille en ligne
juce::Grid grid;
using Track = juce::Grid::TrackInfo;
using Fr = juce::Grid::Fr;
grid.templateRows = { Track (Fr (5)), Track (Fr (12)), Track (Fr (8)),Track (Fr (20)), Track (Fr (4)) };
grid.templateColumns = { Track (Fr (1)) };
float margin = 1.0f;
grid.items = {
juce::GridItem (measures).withMargin(juce::GridItem::Margin(0.0f, 0.0f, margin, 0.0f)),
juce::GridItem (bpm).withMargin(juce::GridItem::Margin(margin, 0.0f, margin, 0.0f)),
juce::GridItem (tempo).withMargin(juce::GridItem::Margin(margin, 0.0f, margin, 0.0f)),
juce::GridItem(playButton).withMargin(juce::GridItem::Margin(margin, 0.0f, margin, 0.0f)),
juce::GridItem (options).withMargin(juce::GridItem::Margin(margin, 0.0f, 0.0f, 0.0f)),
};
grid.performLayout (getLocalBounds());
}
void MainComponent::timerCallback()
{
// On regarde si le metronome à des choses à nous dire (changement de mesure)
if (metronome.signal.get() == true)
{
int cur = metronome.getCurrentMeasure();
metronome.signal.set(false); // on ferme le signal
int meas = metronome.getMeasures();
cur = cur > 0 ? cur - 1 : cur;
measures.update(meas, cur % meas);
if (cur != lastcur)
{
// On à changé de mesure, on indique au playButton de s'allumer pendant 6 frames
playButton.highlightFrames = 6;
lastcur = cur;
}
}
playButton.highlight(); // le bouton va se redessiner et decrementer son highlightFrames
}
void MainComponent::saveConfig()
{
// sauvegarde la configuration du metronome à la fermeture
Config::Save(metronome, options);
}

79
Source/MainComponent.h Normal file
View File

@ -0,0 +1,79 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
#include <JuceHeader.h>
#include "MesurePanel.h"
#include "BPMPanel.h"
#include "TempoPanel.h"
#include "PlayPanel.h"
#include "OptionsPanel.h"
#include "Metronome.h"
//==============================================================================
/*
This component lives inside our window, and this is where you should put all
your controls and content.
*/
class MainComponent : private juce::Timer, public juce::AudioAppComponent
{
public:
//==============================================================================
MainComponent(Config& conf);
~MainComponent();
//==============================================================================
void prepareToPlay (int , double sampleRate) override;
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override;
void releaseResources() override;
//==============================================================================
void paint (juce::Graphics& g) override;
void resized() override;
void saveConfig();
private:
MesurePanel measures;
BPMPanel bpm;
PlayPanel playButton;
TempoPanel tempo;
OptionsPanel options;
Metronome metronome;
// conserve la dernière mesure pour déclencher le highlight en cas de changement
int lastcur;
// Utilisation d'un timer pour relire les signaux du metronome
// timer de 20ms, largement suffisant pour un retour graphique sans surcharger le processeur (~50fps)
void timerCallback() override;
// permet de faire clignoter le bouton play sur le temps
void highlight();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

133
Source/MesurePanel.cpp Normal file
View File

@ -0,0 +1,133 @@
#include "MesurePanel.h"
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du nombre de temps par mesure.
==============================================================================
*/
void RoundCBLookAndFeel::drawToggleButton(juce::Graphics& g, juce::ToggleButton& button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
{
// dessine un cercle au lieux d'un bouton
auto fontSize = juce::jmin(15.0f, (float)button.getHeight() * 0.75f);
auto tickWidth = fontSize * 1.1f;
drawTickBox(g, button, 4.0f, ((float)button.getHeight() - tickWidth) * 0.5f,
tickWidth, tickWidth,
button.getToggleState(),
button.isEnabled(),
shouldDrawButtonAsHighlighted,
shouldDrawButtonAsDown);
}
void RoundCBLookAndFeel::drawTickBox(juce::Graphics& g, juce::Component& component,
float x, float y, float w, float h,
bool ticked, bool isEnabled,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
{
// dessine un cercle au lieux d'un bouton
juce::ignoreUnused(isEnabled, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
g.setColour(component.findColour(juce::ToggleButton::tickDisabledColourId));
g.drawEllipse(x, y, w, h, 1.0f);
if (ticked)
{
g.setColour(component.findColour(juce::ToggleButton::tickColourId));
g.fillEllipse(x, y, w, h);
}
}
MesurePanel::MesurePanel(juce::Colour c) : backgroundColour(c)
{
lef.reset(new RoundCBLookAndFeel());
update(4, 0);
}
MesurePanel::~MesurePanel()
{
// evite une fuite mémoire à la fermeture due au L&F
for (auto& tb : buttons)
{
removeChildComponent(tb);
tb->setLookAndFeel(nullptr);
}
}
void MesurePanel::paint(juce::Graphics& g)
{
g.fillAll(backgroundColour);
}
void MesurePanel::update(int nb, int cur)
{
// Construit autant de bouton qu'il y a de temps
if (buttons.size() != nb)
{
// Si la mesure à changé on reinitialise
for (auto& tb : buttons)
{
removeChildComponent(tb);
tb->setVisible(false);
}
buttons.clear();
for (int i = 0; i < nb; ++i)
{
auto* tb = new juce::ToggleButton(juce::String(i));
addAndMakeVisible(buttons.add(tb));
tb->setRadioGroupId(1234);
tb->setColour(juce::ToggleButton::tickDisabledColourId, juce::Colours::black);
tb->setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
tb->setLookAndFeel(lef.get());
}
}
// On sélectionne le bouton correpondant au temps courant
for (int i = 0; i < nb; ++i)
{
buttons[i]->setToggleState(i == cur, juce::dontSendNotification);
}
resized();
}
void MesurePanel::resized()
{
// Layout via une flexbox pour répartir les boutons en ligne
juce::FlexBox fb;
fb.flexWrap = juce::FlexBox::Wrap::wrap;
fb.justifyContent = juce::FlexBox::JustifyContent::center;
fb.alignContent = juce::FlexBox::AlignContent::center;
for (auto* b : buttons)
fb.items.add(juce::FlexItem(*b).withMinWidth(40.0f).withMinHeight(40.0f));
fb.performLayout(getLocalBounds().toFloat());
}

62
Source/MesurePanel.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du nombre de temps par mesure.
==============================================================================
*/
#include <JuceHeader.h>
// Surcharge les boutons pour un rond plein ou vide
struct RoundCBLookAndFeel : public juce::LookAndFeel_V4
{
void drawToggleButton(juce::Graphics& g, juce::ToggleButton& button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override;
void drawTickBox(juce::Graphics& g, juce::Component& component,
float x, float y, float w, float h,
bool ticked, bool isEnabled,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override;
};
// Panneau d'affichage de la mesure en cours
struct MesurePanel : public juce::Component
{
explicit MesurePanel(juce::Colour c);
~MesurePanel();
// Méthode de surcharge JUCE
void paint(juce::Graphics& g) override;
void resized() override;
// méthodes internes
void update(int nb, int cur);
// composants graphiques
juce::Colour backgroundColour;
juce::OwnedArray<juce::ToggleButton> buttons;
std::unique_ptr <RoundCBLookAndFeel> lef;
};

246
Source/Metronome.cpp Normal file
View File

@ -0,0 +1,246 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Moteur de l'application. Gère la lecture des samples Callé sur le bon tempo.
==============================================================================
*/
#include "Metronome.h"
Metronome::Metronome(Config& conf)
{
// on récupère les valeurs de la configuration
bpm.set(conf.bpm);
measures.set(conf.measures);
gain.set(conf.gain);
setFigure(conf.figure);
// on initialise le lecteur wav
formatManager.registerBasicFormats();
// On crée 2 voie sur le synthé
for (auto i = 0; i < 2; ++i)
synth.addVoice(new juce::SamplerVoice());
}
Metronome::~Metronome()
{
// libère les sons
synth.clearSounds();
}
void Metronome::loadSounds(std::string high, std::string low)
{
// chargement des sons high et low
auto mySamplesHigh = juce::File{high};
auto mySamplesLow = juce::File{ low };
jassert(mySamplesHigh.exists());
jassert(mySamplesLow.exists());
synth.clearSounds();
juce::BigInteger usedNotes;
usedNotes.setRange(0, 1, true);
auto formatReaderHigh = formatManager.createReaderFor(mySamplesHigh);
synth.addSound(new juce::SamplerSound("High", *formatReaderHigh, usedNotes, 0, 0.0f, 0.0f, 999.0));
delete formatReaderHigh;
juce::BigInteger usedNotes2;
usedNotes2.setRange(1, 1, true);
auto formatReaderLow = formatManager.createReaderFor(mySamplesLow);
synth.addSound(new juce::SamplerSound("Low", *formatReaderLow, usedNotes2, 1, 0.0f, 0.0f, 999.0));
delete formatReaderLow;
}
void Metronome::prepareToPlay (double sr)
{
// Appelé avant de démaré la lecture, on récupère la sr et on initialise le reste du moteur
sampleRate = sr;
updateInterval = (int)(60.0 / bpm.get() * sampleRate);
synth.setCurrentPlaybackSampleRate(sampleRate);
initMeasure();
}
void Metronome::getNextAudioBlock(juce::AudioSourceChannelInfo const& bufferToFill)
{
// Méthode invoqué par le moteur audio. Le but est de remplir bufferToFill avec la waveform que l'on souhaite jouer
int const numSamples = bufferToFill.numSamples;
juce::MidiBuffer midi;
int measCount = measures.get();
int measLength = updateInterval * measCount; // nombre de sample total dans une mesure
int start = lastPos; // Le premier index du buffer correspond au lastPos
int stop = lastPos + numSamples; // et la dernière position du buffer
for (auto cur : measStruct)
{
// On cherche Si dans notre mesure on a un click entre start et stop
int nextTick = (int)(cur.measPos * updateInterval); // le measPos est en relatif, on convertit en équivalent samples
if (stop > measLength)
{
// Dans le cas ou stop se situe sur la mesure suivante, on décale le nextTick d'une mesure
nextTick += measLength;
}
if (nextTick >= start && nextTick < stop)
{
// On à trouvé un tick
if (cur.type != NoteType::mute)
{
// qui est pas mute
// on sélectionne la bone note, la bonne vélocité et on génère un événement midi à nextTick - start
int note = cur.type == NoteType::high ? 0 : 1;
float vel = cur.type == NoteType::lowLow ? 0.2f : 1.0f;
midi.addEvent(juce::MidiMessage::noteOn(1, note, vel), nextTick - start);
}
// on signale l'interface (pour marquer le temps même s'il est mute)
signal.set(true);
}
}
// On incrémente lastPos
lastPos = stop;
if (lastPos > measLength)
{
// Et on le remet dans la mesure suivante
lastPos -= measLength;
}
// On demande au synthé de remplir le buffer
synth.renderNextBlock(*bufferToFill.buffer,
midi,
0,
bufferToFill.numSamples);
// On reprend la main sur le buffer pour appliquer le gain manuellement
float mult = gain.get();
for (auto channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
{
auto* buffer = bufferToFill.buffer->getWritePointer(channel, bufferToFill.startSample);
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
buffer[sample] = buffer[sample] * mult;
}
}
void Metronome::reset()
{
// raz
lastPos = 0;
signal.set(true);
}
void Metronome::setBPM(int value)
{
// contraint les BPM entre 10 et 400
int coerce = juce::jlimit(10, 400, value);
bpm.set(coerce);
int lastInterval = updateInterval;
updateInterval = (int)(60.0 / bpm.get() * sampleRate);
// En cas de changement de BPM brutal on constate un décalage dans la lecture
// Pour remédier à ca on recalibre le lastPos en proportion du nouvel intervale
lastPos = (int)(lastPos * (float)updateInterval / (float)lastInterval);
}
int Metronome::getCurrentMeasure() const
{
// permet à l'UI de savoir sur quel temps de la mesure on se trouve
int numTemps = (lastPos + updateInterval - 1) / updateInterval; // nombre de temps depuis le start
return numTemps;
}
void Metronome::initMeasure()
{
// construit la structure de la mesure
measStruct.clear();
int measCount = measures.get();
switch (figure.get())
{
case FigureType::blanche:
// On alterne click/Mute
for (size_t i = 0; i < measCount; ++i)
{
if (i % 2 == 0)
{
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
}
else
{
measStruct.push_back(MeasureTick((float)i, NoteType::mute));
}
}
break;
case FigureType::noire:
// Un click par temps
for (size_t i = 0; i < measCount; ++i)
{
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
}
break;
case FigureType::croche:
// On alterne click/lowlow
for (size_t i = 0; i < measCount; ++i)
{
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
measStruct.push_back(MeasureTick((float)i + 0.5f, NoteType::lowLow));
}
break;
case FigureType::triolet:
// On alterne click/lowlow/lowlow
for (size_t i = 0; i < measCount; ++i)
{
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
measStruct.push_back(MeasureTick((float)i + 1.f / 3.f, NoteType::lowLow));
measStruct.push_back(MeasureTick((float)i + 2.f / 3.f, NoteType::lowLow));
}
break;
case FigureType::doubleCroche:
// On alterne click/lowlow/lowlow/lowlow
for (size_t i = 0; i < measCount; ++i)
{
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
measStruct.push_back(MeasureTick((float)i + 0.25f, NoteType::lowLow));
measStruct.push_back(MeasureTick((float)i + 0.5f, NoteType::lowLow));
measStruct.push_back(MeasureTick((float)i + 0.75f, NoteType::lowLow));
}
break;
case FigureType::swing:
// On alterne click/lowlow sur les 2/3 du temps
for (size_t i = 0; i < measCount; ++i)
{
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
measStruct.push_back(MeasureTick((float)i + 2.f / 3.f, NoteType::lowLow));
}
break;
default:
break;
}
}

134
Source/Metronome.h Normal file
View File

@ -0,0 +1,134 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Moteur de l'application. Gère la lecture des samples Callé sur le bon tempo.
==============================================================================
*/
#include <JuceHeader.h>
#include "Config.h"
class Config;
// Défini les 3 niveaux de clic utilisés par le métronome
// high marque le premier temps de la mesure. correspond au sample High*.wav
// low marque les autres temps de la mesure. correspond au sample Low*.wav
// lowlow est utilisé pour les clics intermédiares (croches,...). correspond au sample Low*.wav avec une vélocité réduite
// mute permet de marquer un silence sur le temps lorsqu'on bat la mesure à la blanche
enum class NoteType { high = 0, low = 1, lowLow = 2, mute = 3 };
// Les figures rythmiques disponibles : blanche, noire, croche, triolet, double croche
// la figure swing (ou shuffle) est construite sur un triolet avec un click sur le temps et un sur le 2/3 de temps.
// suivant l'interprétation, le swing devrait su situer entre le triolet et la double croche mais c'est moi qui décide.
enum class FigureType { blanche = 0, noire = 1, croche = 2, triolet = 3, doubleCroche = 4, swing = 5 };
// structure de base pour définir la structure rythmique de la mesure
struct MeasureTick
{
// position du click dans la mesure en valeur absolu
// pour une mesure de 5 temps measPos est dans l'intervale [0, 5[
float measPos;
// type de click
NoteType type;
};
// Bat la mesure
class Metronome
{
public:
Metronome(Config& configuration);
~Metronome();
juce::Atomic<bool> signal;
// méthodes de gestion du lecteur
void prepareToPlay (double sampleRate);
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill);
// reinitialise le lecteur sur le stop
void reset();
// recharge les son high et low
void loadSounds(std::string high, std::string low);
// accesseurs
int getBPM() const noexcept
{
return bpm.get();
}
void setBPM(int value);
int getMeasures() const noexcept
{
return measures.get();
}
void setMeasures(int value) {
int coerce = juce::jlimit(1, 9, value); // On limite le nombre de mesure à 9
measures.set(coerce);
initMeasure(); // reconstruit la structure de la mesure
signal.set(true); // signale à la GUI de se metre à jours
}
int getCurrentMeasure() const;
void setGain(float value) {
gain.set(value);
}
float getGain() {
return gain.get();
}
void setFigure(int value) {
figure.set((FigureType)value);
initMeasure(); // reconstruit la structure de la mesure
}
FigureType getFigure() {
return figure.get();
}
private:
// variables réglables depuis l'UI (d'ou le Atomic)
juce::Atomic<int> measures{ 4 }; // nombre de temps par mesure
juce::Atomic<int> bpm{ 72 }; // tempo
juce::Atomic<float> gain{ 1.0f }; // volume sonore
juce::Atomic<FigureType> figure{ FigureType::noire }; // type de figure utilisé pour construire la mesure
// variables internes
double sampleRate { 0 }; // nombre de sample par secondes
int updateInterval{ 0 }; // nombre de sample entre 2 temps : 60/bpm*sr
int lastPos{ 0 }; // indice du dernier sample traité lors de l'appel getNextAudioBlock. Valeur réinitialisé à chaque mesure modulo la taille de la mesure
std::vector<MeasureTick> measStruct; // structure de la mesure, contient tout les clicks en fonction du nombre de mesure et de la figure courante
juce::AudioFormatManager formatManager; // chargement des fichier wav
juce::Synthesiser synth; // la gestion direct du remplissage du buffer via getNextAudioBlock est trop compliqué
// On passe par un Synthé avec un fichier par note, on déclenche la lecture par un evenement midi déclenché précisément
void initMeasure(); // reconstruit la structure de la mesure
};

319
Source/OptionsPanel.cpp Normal file
View File

@ -0,0 +1,319 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion des options figure rythmique et fichier son du tick.
==============================================================================
*/
#include "OptionsPanel.h"
#include "Figures.h"
void ClickSounds::init()
{
// on récupère tout les sous dossiers dans App/Clicks
juce::File curDir{ juce::File::getSpecialLocation(juce::File::SpecialLocationType::currentExecutableFile).getParentDirectory() };
juce::File clickDir{ curDir.getChildFile("Clicks")};
auto it = clickDir.findChildFiles(juce::File::findDirectories, false);
for (int i = 0; i < it.size(); ++i)
{
juce::File sub = it[i];
bool highFile = !sub.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "High*.wav").isEmpty();
bool lowFile = !sub.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "Low*.wav").isEmpty();
if (highFile && lowFile)
{
// On n'ajoute que si on a bien les fichiers High et Low
juce::String path = sub.getFullPathName();
std::string ss = path.toStdString();
sounds.push_back(ss);
}
}
}
std::string ClickSounds::getSoundName(int i) const
{
// Valeur affiché dans la combo, on récupère le nom du sous dossier
juce::File path{ sounds[i] };
return path.getFileNameWithoutExtension().toStdString();
}
std::string ClickSounds::getSoundPathHigh(int i) const
{
// Chemin vers le fichier High
juce::File path{ sounds[i] };
return path.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "High*.wav")[0].getFullPathName().toStdString();
}
std::string ClickSounds::getSoundPathLow(int i) const
{
// Chemin vers le fichier Low
juce::File path{ sounds[i] };
return path.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "Low*.wav")[0].getFullPathName().toStdString();
}
int ClickSounds::getIndex(std::string soundName) const
{
// récupère l'index du fichier à partir de son nom
// Utilisé pour restaurer la configuration
// Du coup si le son n'est pas trouvé on retourne le premier son
for (int i = 0; i < sounds.size(); ++i)
{
if (getSoundName(i) == soundName)
{
return i;
}
}
return 0;
}
void ComboLookAndFeel::drawPopupMenuBackground(juce::Graphics& g, int width, int height)
{
// Permet de setter le background du PopupMenu
g.fillAll(juce::Colours::whitesmoke);
juce::ignoreUnused(width, height);
#if ! JUCE_MAC
g.setColour(juce::Colours::darkgrey.withAlpha(0.6f));
g.drawRect(0, 0, width, height);
#endif
}
void ComboLookAndFeel::drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle<int>& area,
const bool isSeparator, const bool isActive,
const bool isHighlighted, const bool isTicked,
const bool hasSubMenu, const juce::String& text,
const juce::String& shortcutKeyText,
const juce::Drawable* icon, const juce::Colour* const textColourToUse)
{
// Permet de setter le background du PopupMenuItem
if (isSeparator)
{
auto r = area.reduced(5, 0);
r.removeFromTop(juce::roundToInt(((float)r.getHeight() * 0.5f) - 0.5f));
g.setColour(juce::Colours::darkgrey.withAlpha(0.3f));
g.fillRect(r.removeFromTop(1));
}
else
{
auto textColour = (textColourToUse == nullptr ? juce::Colours::darkgrey
: *textColourToUse);
auto r = area.reduced(1);
if (isHighlighted && isActive)
{
g.setColour(juce::Colours::lightgrey);
g.fillRect(r);
g.setColour(juce::Colours::darkgrey);
}
else
{
g.setColour(textColour.withMultipliedAlpha(isActive ? 1.0f : 0.5f));
}
r.reduce(juce::jmin(5, area.getWidth() / 20), 0);
auto font = getPopupMenuFont();
auto maxFontHeight = (float)r.getHeight() / 1.3f;
if (font.getHeight() > maxFontHeight)
font.setHeight(maxFontHeight);
g.setFont(font);
auto iconArea = r.removeFromLeft(juce::roundToInt(maxFontHeight)).toFloat();
if (icon != nullptr)
{
icon->drawWithin(g, iconArea, juce::RectanglePlacement::centred | juce::RectanglePlacement::onlyReduceInSize, 1.0f);
r.removeFromLeft(juce::roundToInt(maxFontHeight * 0.5f));
}
else if (isTicked)
{
auto tick = getTickShape(1.0f);
g.fillPath(tick, tick.getTransformToScaleToFit(iconArea.reduced(iconArea.getWidth() / 5, 0).toFloat(), true));
}
if (hasSubMenu)
{
auto arrowH = 0.6f * getPopupMenuFont().getAscent();
auto x = static_cast<float> (r.removeFromRight((int)arrowH).getX());
auto halfH = static_cast<float> (r.getCentreY());
juce::Path path;
path.startNewSubPath(x, halfH - arrowH * 0.5f);
path.lineTo(x + arrowH * 0.6f, halfH);
path.lineTo(x, halfH + arrowH * 0.5f);
g.strokePath(path, juce::PathStrokeType(2.0f));
}
r.removeFromRight(3);
g.drawFittedText(text, r, juce::Justification::centredLeft, 1);
if (shortcutKeyText.isNotEmpty())
{
auto f2 = font;
f2.setHeight(f2.getHeight() * 0.75f);
f2.setHorizontalScale(0.95f);
g.setFont(f2);
g.drawText(shortcutKeyText, r, juce::Justification::centredRight, true);
}
}
}
OptionsPanel::OptionsPanel(juce::Colour c, Config& configuration) : backgroundColour(c)
{
// initialisation de l'interface
clef.reset(new ComboLookAndFeel());
addAndMakeVisible(figures);
auto doc = juce::XmlDocument::parse(SVGFigures::noire); // on charge la noire par defaut, va être re-initialisé par la configuration
auto note = juce::Drawable::createFromSVG(*doc);
figures.setImages(note.get());
figures.setToggleState(false, juce::NotificationType::dontSendNotification);
figures.setColour(juce::TextButton::buttonColourId, juce::Colours::whitesmoke);
figures.setColour(juce::TextButton::textColourOffId, juce::Colours::black);
figures.onClick = [&]
{
// génère le dropdown menu des figures
juce::PopupMenu menu;
int width = 70;
int height = 48;
menu.addCustomItem(1, std::make_unique<FigureButton>(SVGFigures::blanche, width, height, juce::Colours::whitesmoke));
menu.addCustomItem(4, std::make_unique<FigureButton>(SVGFigures::triolet, width, height, juce::Colours::whitesmoke));
menu.addCustomItem(2, std::make_unique<FigureButton>(SVGFigures::noire, width, height, juce::Colours::whitesmoke));
menu.addCustomItem(5, std::make_unique<FigureButton>(SVGFigures::doubles, width, height, juce::Colours::whitesmoke));
menu.addCustomItem(3, std::make_unique<FigureButton>(SVGFigures::croches, width, height, juce::Colours::whitesmoke));
menu.addCustomItem(6, std::make_unique<FigureButton>(SVGFigures::swing, width, height, juce::Colours::whitesmoke));
menu.showMenuAsync(juce::PopupMenu::Options{}.withTargetComponent(figures)
.withMinimumNumColumns(3), [&](int resultId) { if(resultId > 0) setFigure(resultId - 1); });
};
addAndMakeVisible(sounds);
sounds.setLookAndFeel(clef.get());
sounds.setColour(juce::ComboBox::backgroundColourId, juce::Colours::whitesmoke);
sounds.setColour(juce::ComboBox::outlineColourId, juce::Colours::darkgrey);
sounds.setColour(juce::ComboBox::arrowColourId, juce::Colours::darkgrey);
sounds.setColour(juce::ComboBox::textColourId, juce::Colours::darkgrey);
clicksounds.init(); // charge la liste des sons
curSoundID = clicksounds.getIndex(configuration.sound.toStdString()); // récupère le son courant dans la configuration
}
juce::String OptionsPanel::getSoundName()
{
// Récupère le nom du son courant pour sauver la config
return clicksounds.getSoundName(sounds.getSelectedId() - 1);
}
void OptionsPanel::setFigure(int figure)
{
// MàJ de l'icone de la figure courante
juce::String newSVG;
switch (figure)
{
case 0:
newSVG = SVGFigures::blanche;
break;
case 2:
newSVG = SVGFigures::croches;
break;
case 3:
newSVG = SVGFigures::triolet;
break;
case 4:
newSVG = SVGFigures::doubles;
break;
case 5:
newSVG = SVGFigures::swing;
break;
case 1:
default:
newSVG = SVGFigures::noire;
break;
}
auto doc = juce::XmlDocument::parse(newSVG);
auto note = juce::Drawable::createFromSVG(*doc);
figures.setImages(note.get());
setMetroFigure(figure);
}
OptionsPanel::~OptionsPanel()
{
// evite une fuite mémoire à la fermeture due au L&F
sounds.setLookAndFeel(nullptr);
figures.setLookAndFeel(nullptr);
}
void OptionsPanel::paint(juce::Graphics& g)
{
g.fillAll(backgroundColour);
}
void OptionsPanel::resized()
{
// On utilise un layout grille en 2 colonnes
int margin = 3;
juce::Grid grid;
using Track = juce::Grid::TrackInfo;
using Fr = juce::Grid::Fr;
grid.templateRows = { Track(Fr(1)) };
grid.templateColumns = { Track(Fr(1)), Track(Fr(1)) };
grid.items = {
juce::GridItem(figures).withMargin(juce::GridItem::Margin(margin)),
juce::GridItem(sounds).withMargin(juce::GridItem::Margin(margin)),
};
grid.performLayout(getLocalBounds());
}
void OptionsPanel::init(Metronome& metro)
{
// On établi les liaisons entre le Metronome et les composants graphique
for (int i = 0; i < clicksounds.getSoundCount(); ++i)
{
sounds.addItem(clicksounds.getSoundName(i), i + 1); // La combo n'autorise pas 0 comme Id, d'ou le + 1
}
sounds.setSelectedId(curSoundID + 1); // toujours ce + 1
selectSound(metro); // charge le son courant dans le metronome
sounds.onChange = [&, this]() { selectSound(metro); };
setMetroFigure = [&](int figure) { metro.setFigure(figure); };
setFigure((int)metro.getFigure());
}
void OptionsPanel::selectSound(Metronome& metro)
{
// charge le son courant dans le metronome
std::string high = clicksounds.getSoundPathHigh(sounds.getSelectedId() - 1);
std::string low = clicksounds.getSoundPathLow(sounds.getSelectedId() - 1);
metro.loadSounds(high, low);
}

145
Source/OptionsPanel.h Normal file
View File

@ -0,0 +1,145 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion des options figure rythmique et fichier son du tick.
==============================================================================
*/
#include <JuceHeader.h>
#include "Metronome.h"
#include "Config.h"
class Config;
class Metronome;
// Surcharge les boutons pour afficher l'image de la figure rythmique
struct FigureButton : public juce::PopupMenu::CustomComponent
{
FigureButton(juce::String image, int widthIn, int heightIn, juce::Colour backgroundIn)
: juce::PopupMenu::CustomComponent(false),
idealWidth(widthIn),
idealHeight(heightIn),
background(backgroundIn)
{
// initialisation de l'interface
addAndMakeVisible(button);
// récupère l'image de la figure
auto doc = juce::XmlDocument::parse(image);
auto note = juce::Drawable::createFromSVG(*doc);
button.setImages(note.get());
button.setToggleState(false, juce::NotificationType::dontSendNotification);
button.setColour(juce::TextButton::buttonColourId, juce::Colours::whitesmoke);
button.setColour(juce::TextButton::textColourOffId, juce::Colours::black);
button.onClick = [&, this]
{
// notifie la "Combo"
this->triggerMenuItem();
};
}
void getIdealSize(int& width, int& height) override
{
width = idealWidth;
height = idealHeight;
}
void paint(juce::Graphics& g) override { g.fillAll(background); }
void resized() override { button.setBounds(getBounds()); }
// composants graphiques
juce::Colour background;
juce::DrawableButton button{ "Figures", juce::DrawableButton::ImageOnButtonBackground };
// taille du bouton
int idealWidth = 0;
int idealHeight = 0;
};
// L&F pour la combo des sons, permet de setter le background du Popup
struct ComboLookAndFeel : public juce::LookAndFeel_V4
{
void drawPopupMenuBackground(juce::Graphics& g, int width, int height) override;
void drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle<int>& area,
const bool isSeparator, const bool isActive,
const bool isHighlighted, const bool isTicked,
const bool hasSubMenu, const juce::String& text,
const juce::String& shortcutKeyText,
const juce::Drawable* icon, const juce::Colour* const textColourToUse) override;
};
// structure dédiée à la gestion des sons
struct ClickSounds
{
ClickSounds() {};
// Charge la liste de sons
void init();
// Accesseurs
int getSoundCount() const { return (int)sounds.size(); };
std::string getSoundName(int i) const;
std::string getSoundPathHigh(int i) const;
std::string getSoundPathLow(int i) const;
int getIndex(std::string soundName) const;
// Liste des sons
std::vector<std::string> sounds;
};
// Panneau d'affichage des options
struct OptionsPanel : public juce::Component
{
explicit OptionsPanel(juce::Colour c, Config& configuration);
~OptionsPanel();
// Méthode de surcharge JUCE
void paint(juce::Graphics& g) override;
void resized() override;
// méthodes internes
void init(Metronome& metro);
void selectSound(Metronome& metro);
void setFigure(int figure);
juce::String getSoundName();
// composants graphiques
juce::DrawableButton figures{ "Figures", juce::DrawableButton::ImageOnButtonBackground };
juce::ComboBox sounds;
juce::Colour backgroundColour;
std::unique_ptr <ComboLookAndFeel> clef;
// variables internes
// liste des sons
ClickSounds clicksounds;
// pointeur sur la méthode qui permet de setter le metronome
std::function< void(int)> setMetroFigure {nullptr};
// permet de stocker l'id du son selectionné dans la configuration entre le constructeur et le init()
int curSoundID;
};

161
Source/PlayPanel.cpp Normal file
View File

@ -0,0 +1,161 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du bouton play et du volume.
==============================================================================
*/
#include "PlayPanel.h"
#include "Metronome.h"
#include "Figures.h"
void RoundButtonLookAndFeel::drawButtonBackground(juce::Graphics& g, juce::Button& button, const juce::Colour& backgroundColour,
bool isMouseOverButton, bool isButtonDown)
{
// version addapté du LookAndFeel_V4
auto baseColour = backgroundColour.withMultipliedSaturation(button.hasKeyboardFocus(true) ? 1.3f : 0.9f)
.withMultipliedAlpha(button.isEnabled() ? 0.9f : 0.5f);
if (isButtonDown || isMouseOverButton)
baseColour = baseColour.contrasting(isButtonDown ? 0.2f : 0.1f);
auto width = (float)button.getWidth() - 1.0f;
auto height = (float)button.getHeight() - 1.0f;
if (width > 0 && height > 0)
{
// on viens dessiner un cercle
auto rect = button.getLocalBounds();
float rwidth = rect.getWidth() < rect.getHeight() ? (float)rect.getWidth() - 25.0f : (float)rect.getHeight() - 25.0f;
g.setGradientFill(juce::ColourGradient::vertical(juce::Colours::lightgrey, 0.0f,
juce::Colours::lightgrey.darker(0.1f), height));
g.fillEllipse(rect.getCentreX() - rwidth / 2, rect.getCentreY() - rwidth / 2, rwidth, rwidth);
g.setGradientFill(juce::ColourGradient::vertical(baseColour, 0.0f,
baseColour.darker(0.1f), height));
g.drawEllipse(rect.getCentreX() - rwidth / 2, rect.getCentreY() - rwidth / 2, rwidth, rwidth, 4);
}
}
PlayPanel::PlayPanel(juce::Colour c) : backgroundColour(c)
{
// initialisation de l'interface
lef.reset(new RoundButtonLookAndFeel());
// Le dessin du bouton play est dans un svg
auto doc = juce::XmlDocument::parse(SVGFigures::play);
auto image = juce::Drawable::createFromSVG(*doc);
addComponent(volume);
addComponent(playButton);
playButton.setRadioGroupId(1);
playButton.setImages(image.get());
playButton.setToggleState(false, juce::NotificationType::dontSendNotification);
playButton.setColour(juce::TextButton::buttonColourId, juce::Colours::lightgreen);
playButton.setColour(juce::TextButton::textColourOffId, juce::Colours::black);
playButton.setLookAndFeel(lef.get());
volume.setRange(-100, 6); // on prend -100db en min, et on s'autorise à amplifier le sample à +6db
volume.setValue(0); // valeur initiale à 0db
volume.setSliderStyle(juce::Slider::SliderStyle::LinearVertical);
volume.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, true, 0, 0);
volume.setColour(juce::Slider::thumbColourId, backgroundColour);
volume.setColour(juce::Slider::trackColourId, juce::Colours::green);
}
PlayPanel::~PlayPanel()
{
// evite une fuite mémoire à la fermeture due au L&F
playButton.setLookAndFeel(nullptr);
}
void PlayPanel::addComponent(juce::Component& c)
{
addAndMakeVisible(c);
c.setColour(juce::TextButton::buttonColourId, backgroundColour);
c.setColour(juce::TextButton::textColourOffId, juce::Colours::black);
}
void PlayPanel::paint(juce::Graphics& g)
{
g.fillAll(backgroundColour);
}
void PlayPanel::resized()
{
// On gêre le placement via une grille 3 collones
// la première colonne est vide et sert de "marge"
juce::Grid vpgrid;
using Track = juce::Grid::TrackInfo;
using Fr = juce::Grid::Fr;
vpgrid.templateRows = { Track(Fr(1)) };
vpgrid.templateColumns = { Track(Fr(1)), Track(Fr(5)), Track(Fr(1)) };
vpgrid.items = { juce::GridItem(), juce::GridItem(playButton), juce::GridItem(volume) };
vpgrid.performLayout(getLocalBounds());
}
void PlayPanel::init(Metronome& metro)
{
// On établi les liaisons entre le Metronome et les composants graphique
playButton.onClick = [&, this]() { play(metro); };
volume.setValue(juce::Decibels::gainToDecibels(metro.getGain())); // On récupère la valeur initiale du gain
volume.onValueChange = [&, this]
{
double db = volume.getValue();
metro.setGain((float)juce::Decibels::decibelsToGain(db));
};
}
void PlayPanel::play(Metronome& metro)
{
// lance la lecture ou met en pause
playState = playState == PlayState::playing ? PlayState::stopped : PlayState::playing;
if (playState == PlayState::stopped)
{
// en cas de stop, on remet les compteurs à zero sur le metronome
metro.reset();
}
// On modifie la couleur du bouton en fonction de l'état
playButton.setColour(juce::TextButton::buttonColourId, playState == PlayState::playing ? juce::Colours::green : juce::Colours::lightgreen);
}
void PlayPanel::highlight()
{
// Met le bouton en surbrillance sur le temps
// la variable highlightFrames decompte 6 frames sur un timer de 20ms soit 120ms
if (playState == PlayState::playing)
{
if (highlightFrames > 0)
{
playButton.setColour(juce::TextButton::buttonColourId, juce::Colours::orangered);
highlightFrames--;
}
else
{
playButton.setColour(juce::TextButton::buttonColourId, juce::Colours::green);
}
}
}

73
Source/PlayPanel.h Normal file
View File

@ -0,0 +1,73 @@
#pragma once
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du bouton play et du volume.
==============================================================================
*/
#include <JuceHeader.h>
#include "Metronome.h"
// Etat de lecture
enum class PlayState
{
playing,
stopped
};
// surcharge du look&feel pour dessiner le bouton play en cercle
struct RoundButtonLookAndFeel : public juce::LookAndFeel_V4
{
void drawButtonBackground(juce::Graphics& g, juce::Button& button, const juce::Colour& backgroundColour,
bool isMouseOverButton, bool isButtonDown) override;
};
// classe principale
struct PlayPanel : public juce::Component
{
explicit PlayPanel(juce::Colour c);
~PlayPanel();
// Méthode de surcharge JUCE
void paint(juce::Graphics& g) override;
void resized() override;
// utilitaire graphique
void addComponent(juce::Component& c);
// méthodes de gestion du lecteur
void init(Metronome& metro);
void play(Metronome& metro);
void highlight();
// composants graphiques
juce::Colour backgroundColour;
juce::Slider volume;
juce::DrawableButton playButton { "Play", juce::DrawableButton::ImageOnButtonBackground };
std::unique_ptr <RoundButtonLookAndFeel> lef;
// variables internes
int highlightFrames;
PlayState playState{ PlayState::stopped };
};

91
Source/TempoPanel.cpp Normal file
View File

@ -0,0 +1,91 @@
/*
==============================================================================
Copyright 2022 Nicolas Chambert
This program is free software : you can redistribute itand /or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see < http://www.gnu.org/licenses/>.
==============================================================================
*/
/*
==============================================================================
Gestion du nombre de temps par mesure.
==============================================================================
*/
#include "TempoPanel.h"
#include "Metronome.h"
TempoPanel::TempoPanel(juce::Colour c) : backgroundColour(c)
{
// initialisation de l'interface
addComponent(decrement);
addComponent(tempo);
addComponent(increment);
tempo.setText("4", juce::dontSendNotification); // On met une valeur mais elle va être RaZ dans le init()
tempo.getFont().setBold(true);
tempo.setColour(juce::Label::textColourId, juce::Colours::black);
tempo.setJustificationType(juce::Justification::centred);
tempo.setFont(30);
}
void TempoPanel::addComponent(juce::Component& c)
{
addAndMakeVisible(c);
c.setColour(juce::TextButton::buttonColourId, backgroundColour);
c.setColour(juce::TextButton::textColourOffId, juce::Colours::black);
}
void TempoPanel::paint(juce::Graphics& g)
{
g.fillAll(backgroundColour);
}
void TempoPanel::resized()
{
// On gêre le layout à la main
auto area = getLocalBounds();
int butHeigth = 20;
int butWidth = 48;
int labeSize = 3 * butHeigth;
int totalWidth = 2 * butWidth + labeSize;
int marginH = (area.getWidth() - totalWidth) / 2;
int marginV = (area.getHeight() - labeSize) / 2;
decrement.setBounds(area.getX() + marginH, area.getY() + marginV + butHeigth, butWidth, butHeigth);
tempo.setBounds(area.getX() + marginH + butWidth, area.getY() + marginV, labeSize, labeSize);
increment.setBounds(area.getX() + marginH + butWidth + labeSize, area.getY() + marginV + butHeigth, butWidth, butHeigth);
}
void TempoPanel::init(Metronome& metro)
{
// On établi les liaisons entre le Metronome et les composants graphique
tempo.setText(juce::String(metro.getMeasures()), juce::dontSendNotification);
increment.onClick = [&, this]() { inc(metro); };
decrement.onClick = [&, this]() { dec(metro); };
}
void TempoPanel::inc(Metronome& metro)
{
// incrémente le tempo
metro.setMeasures(metro.getMeasures() + 1);
tempo.setText(juce::String(metro.getMeasures()), juce::dontSendNotification); // MàJ du label
}
void TempoPanel::dec(Metronome& metro)
{
// decremente le tempo
metro.setMeasures(metro.getMeasures() - 1);
tempo.setText(juce::String(metro.getMeasures()), juce::dontSendNotification); // MàJ du label
}

34
Source/TempoPanel.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
/*
==============================================================================
Gestion du nombre de temps par mesure.
==============================================================================
*/
#include <JuceHeader.h>
#include "Metronome.h"
struct TempoPanel : public juce::Component
{
explicit TempoPanel(juce::Colour c);
// Méthode de surcharge JUCE
void paint(juce::Graphics& g) override;
void resized() override;
// utilitaire graphique
void addComponent(juce::Component& c);
// méthodes de gestion du tempo
void init(Metronome& metro);
void inc(Metronome& metro);
void dec(Metronome& metro);
// composants graphiques
juce::Colour backgroundColour;
juce::TextButton increment{ "+" };
juce::TextButton decrement{ "-" };
juce::Label tempo;
};

114
Tchick.jucer Normal file
View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="eEbCxS" name="Tchick" projectType="guiapp" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" displaySplashScreen="1"
cppLanguageStandard="latest" splashScreenColour="Light" includeBinaryInJuceHeader="0">
<MAINGROUP id="gPzZc8" name="Tchick">
<GROUP id="{EDCE3033-ED90-D340-9E42-D702C9B82B08}" name="Assets">
<GROUP id="{A07629F7-9E5A-559D-9755-2EB795077D4C}" name="Clicks">
<GROUP id="{778AD36A-ADB9-BCE2-49CC-77E288129AEA}" name="01 SeikoSQ50">
<FILE id="C9jq8N" name="High Seiko SQ50.wav" compile="0" resource="0"
file="Assets/Clicks/01 SeikoSQ50/High Seiko SQ50.wav"/>
<FILE id="uQbK7f" name="Low Seiko SQ50.wav" compile="0" resource="0"
file="Assets/Clicks/01 SeikoSQ50/Low Seiko SQ50.wav"/>
</GROUP>
<GROUP id="{AE41D956-97BE-1D29-5301-286FB4B56301}" name="02 Stick">
<FILE id="CM52vr" name="High Stick.wav" compile="0" resource="0" file="Assets/Clicks/02 Stick/High Stick.wav"/>
<FILE id="zawixh" name="Low Stick.wav" compile="0" resource="0" file="Assets/Clicks/02 Stick/Low Stick.wav"/>
</GROUP>
<GROUP id="{D75593D1-F482-E1B6-CA27-988E2524442D}" name="03 Cowbell">
<FILE id="TjWHWd" name="High Cowbell.wav" compile="0" resource="0"
file="Assets/Clicks/03 Cowbell/High Cowbell.wav"/>
<FILE id="U48pfg" name="Low Cowbell.wav" compile="0" resource="0" file="Assets/Clicks/03 Cowbell/Low Cowbell.wav"/>
</GROUP>
<GROUP id="{A40DDBDF-ED54-59C0-CFCD-43194EE8C07A}" name="04 vChip Wood">
<FILE id="j6eFYY" name="High Wood.wav" compile="0" resource="0" file="Assets/Clicks/04 vChip Wood/High Wood.wav"/>
<FILE id="TNRBe5" name="Low Wood.wav" compile="0" resource="0" file="Assets/Clicks/04 vChip Wood/Low Wood.wav"/>
</GROUP>
<GROUP id="{157DA364-7F83-712F-1AFC-FB74BC4B3E23}" name="05 vChip Claves">
<FILE id="she6pj" name="HIgh Claves.wav" compile="0" resource="0" file="Assets/Clicks/05 vChip Claves/HIgh Claves.wav"/>
<FILE id="EC66R0" name="Low Claves.wav" compile="0" resource="0" file="Assets/Clicks/05 vChip Claves/Low Claves.wav"/>
</GROUP>
</GROUP>
</GROUP>
<GROUP id="{1F26F6FE-63EA-7E00-50D6-9178F972F8B2}" name="Source">
<FILE id="GSXFyS" name="OptionsPanel.cpp" compile="1" resource="0"
file="Source/OptionsPanel.cpp"/>
<FILE id="rNkF5a" name="TempoPanel.h" compile="0" resource="0" file="Source/TempoPanel.h"/>
<FILE id="EoRTt3" name="TempoPanel.cpp" compile="1" resource="0" file="Source/TempoPanel.cpp"/>
<FILE id="sl7izB" name="PlayPanel.h" compile="0" resource="0" file="Source/PlayPanel.h"/>
<FILE id="aswza3" name="PlayPanel.cpp" compile="1" resource="0" file="Source/PlayPanel.cpp"/>
<FILE id="TPOq96" name="OptionsPanel.h" compile="0" resource="0" file="Source/OptionsPanel.h"/>
<FILE id="bEvzCW" name="MesurePanel.h" compile="0" resource="0" file="Source/MesurePanel.h"/>
<FILE id="s74lBs" name="MesurePanel.cpp" compile="1" resource="0" file="Source/MesurePanel.cpp"/>
<FILE id="Ww74sz" name="Figures.h" compile="0" resource="0" file="Source/Figures.h"/>
<FILE id="QS0xaD" name="Config.h" compile="0" resource="0" file="Source/Config.h"/>
<FILE id="JbnJt4" name="Config.cpp" compile="1" resource="0" file="Source/Config.cpp"/>
<FILE id="PAwDsp" name="BPMPanel.h" compile="0" resource="0" file="Source/BPMPanel.h"/>
<FILE id="fz3AGQ" name="BPMPanel.cpp" compile="1" resource="0" file="Source/BPMPanel.cpp"/>
<FILE id="RqMHaF" name="Main.cpp" compile="1" resource="0" file="Source/Main.cpp"/>
<FILE id="lKOoJh" name="Metronome.h" compile="0" resource="0" file="Source/Metronome.h"/>
<FILE id="A0wYoj" name="Metronome.cpp" compile="1" resource="0" file="Source/Metronome.cpp"/>
<FILE id="WW9a1R" name="MainComponent.h" compile="0" resource="0" file="Source/MainComponent.h"/>
<FILE id="qf5GwL" name="MainComponent.cpp" compile="1" resource="0"
file="Source/MainComponent.cpp"/>
</GROUP>
</MAINGROUP>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_JACK="1"/>
<EXPORTFORMATS>
<VS2022 targetFolder="./.">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" prebuildCommand="xcopy /s /y &quot;$(SolutionDir)Assets\Clicks&quot; &quot;$(OutDir)\Clicks\&quot;"/>
<CONFIGURATION isDebug="0" name="Release" prebuildCommand="xcopy /s /y &quot;$(SolutionDir)Assets\Clicks&quot; &quot;$(OutDir)\Clicks\&quot;"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../juce"/>
<MODULEPATH id="juce_audio_devices" path="../../juce"/>
<MODULEPATH id="juce_audio_formats" path="../../juce"/>
<MODULEPATH id="juce_core" path="../../juce"/>
<MODULEPATH id="juce_data_structures" path="../../juce"/>
<MODULEPATH id="juce_events" path="../../juce"/>
<MODULEPATH id="juce_graphics" path="../../juce"/>
<MODULEPATH id="juce_gui_basics" path="../../juce"/>
<MODULEPATH id="juce_audio_utils" path="C:/Temp/JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="C:/Temp/JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="C:/Temp/JUCE/modules"/>
</MODULEPATHS>
</VS2022>
<LINUX_MAKE targetFolder="./.">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug"/>
<CONFIGURATION isDebug="0" name="Release"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../juce"/>
<MODULEPATH id="juce_audio_devices" path="../../juce"/>
<MODULEPATH id="juce_audio_formats" path="../../juce"/>
<MODULEPATH id="juce_core" path="../../juce"/>
<MODULEPATH id="juce_data_structures" path="../../juce"/>
<MODULEPATH id="juce_events" path="../../juce"/>
<MODULEPATH id="juce_graphics" path="../../juce"/>
<MODULEPATH id="juce_gui_basics" path="../../juce"/>
<MODULEPATH id="juce_audio_utils" path="C:/Temp/JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="C:/Temp/JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="C:/Temp/JUCE/modules"/>
</MODULEPATHS>
</LINUX_MAKE>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="1" useGlobalPath="0"/>
</MODULES>
<LIVE_SETTINGS>
<LINUX/>
</LIVE_SETTINGS>
</JUCERPROJECT>

21
Tchick.sln Normal file
View File

@ -0,0 +1,21 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio Version 17
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tchick - App", "Tchick_App.vcxproj", "{F9D7600C-859C-26DC-FE32-2EB5A98F1734}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F9D7600C-859C-26DC-FE32-2EB5A98F1734}.Debug|x64.ActiveCfg = Debug|x64
{F9D7600C-859C-26DC-FE32-2EB5A98F1734}.Debug|x64.Build.0 = Debug|x64
{F9D7600C-859C-26DC-FE32-2EB5A98F1734}.Release|x64.ActiveCfg = Release|x64
{F9D7600C-859C-26DC-FE32-2EB5A98F1734}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

3235
Tchick_App.vcxproj Normal file

File diff suppressed because it is too large Load Diff

5696
Tchick_App.vcxproj.filters Normal file

File diff suppressed because it is too large Load Diff

4
Tchick_App.vcxproj.user Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>

31
resources.rc Normal file
View File

@ -0,0 +1,31 @@
#pragma code_page(65001)
#ifdef JUCE_USER_DEFINED_RC_FILE
#include JUCE_USER_DEFINED_RC_FILE
#else
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "Tchick\0"
VALUE "FileVersion", "1.0.0\0"
VALUE "ProductName", "Tchick\0"
VALUE "ProductVersion", "1.0.0\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif