Browse Source

Update the BLOCKS SDK and its documentation

tags/2021-05-28
hogliux 7 years ago
parent
commit
f5db5faff6
41 changed files with 2183 additions and 238 deletions
  1. +2
    -0
      examples/BLOCKS/BlocksDrawingDemo.h
  2. +11
    -10
      examples/BLOCKS/BlocksMonitorDemo.h
  3. +2
    -2
      examples/BLOCKS/BlocksSynthDemo.h
  4. +14
    -0
      extras/BLOCKS/doxygen/Doxyfile
  5. +2
    -1
      extras/BLOCKS/doxygen/DoxygenLayout.xml
  6. BIN
      extras/BLOCKS/doxygen/images/BlocksMonitor.png
  7. BIN
      extras/BLOCKS/doxygen/images/blocks_code_interface.png
  8. BIN
      extras/BLOCKS/doxygen/images/blocks_code_tabs.png
  9. +5
    -3
      extras/BLOCKS/doxygen/pages/juce_connecting_blocks.dox
  10. +42
    -6
      extras/BLOCKS/doxygen/pages/juce_controlling_control_buttons.dox
  11. +52
    -18
      extras/BLOCKS/doxygen/pages/juce_controlling_led_grids.dox
  12. +43
    -6
      extras/BLOCKS/doxygen/pages/juce_controlling_led_strips.dox
  13. +11
    -6
      extras/BLOCKS/doxygen/pages/juce_discovering_blocks.dox
  14. +16
    -5
      extras/BLOCKS/doxygen/pages/juce_downloading_the_sdk.dox
  15. +5
    -7
      extras/BLOCKS/doxygen/pages/juce_example_applications.dox
  16. +19
    -0
      extras/BLOCKS/doxygen/pages/juce_example_bitmap_led_program.dox
  17. +29
    -0
      extras/BLOCKS/doxygen/pages/juce_example_block_finder.dox
  18. +227
    -10
      extras/BLOCKS/doxygen/pages/juce_example_blocks_drawing.dox
  19. +297
    -9
      extras/BLOCKS/doxygen/pages/juce_example_blocks_monitor.dox
  20. +211
    -9
      extras/BLOCKS/doxygen/pages/juce_example_blocks_synth.dox
  21. +115
    -0
      extras/BLOCKS/doxygen/pages/juce_example_colour_pressure_map.dox
  22. +65
    -0
      extras/BLOCKS/doxygen/pages/juce_example_dynamic_parameters.dox
  23. +18
    -0
      extras/BLOCKS/doxygen/pages/juce_example_integrations.dox
  24. +411
    -0
      extras/BLOCKS/doxygen/pages/juce_example_music_gen.dox
  25. +34
    -0
      extras/BLOCKS/doxygen/pages/juce_example_scripts.dox
  26. +284
    -0
      extras/BLOCKS/doxygen/pages/juce_example_tic_tac_toe.dox
  27. +59
    -7
      extras/BLOCKS/doxygen/pages/juce_getting_control_button_events.dox
  28. +40
    -0
      extras/BLOCKS/doxygen/pages/juce_getting_started_with_blocks_code.dox
  29. +55
    -8
      extras/BLOCKS/doxygen/pages/juce_getting_touch_events.dox
  30. +40
    -20
      extras/BLOCKS/doxygen/pages/juce_main.dox
  31. +2
    -14
      extras/BLOCKS/doxygen/pages/juce_the_littlefoot_language.dox
  32. +10
    -31
      extras/BLOCKS/doxygen/pages/juce_the_standalone_blocks_sdk.dox
  33. +6
    -0
      extras/BLOCKS/doxygen/stylesheet.css
  34. +8
    -8
      extras/BLOCKS/standalone_sdk/README.md
  35. +2
    -2
      extras/BLOCKS/standalone_sdk/SDK/BlocksHeader.h
  36. +11
    -13
      extras/BLOCKS/standalone_sdk/examples/BlockFinder/BlockFinder.cpp
  37. +4
    -7
      extras/BLOCKS/standalone_sdk/examples/BlockFinder/BlockFinder.h
  38. +13
    -14
      extras/BLOCKS/standalone_sdk/examples/BlockFinder/Linux/main.cpp
  39. +1
    -1
      extras/BLOCKS/standalone_sdk/examples/BlockFinder/MacOS/Makefile
  40. +4
    -7
      extras/BLOCKS/standalone_sdk/examples/BlockFinder/MacOS/main.mm
  41. +13
    -14
      extras/BLOCKS/standalone_sdk/examples/BlockFinder/Windows/main.cpp

+ 2
- 0
examples/BLOCKS/BlocksDrawingDemo.h View File

@@ -648,6 +648,7 @@ private:
return xPos == x && yPos == y;
}
};
Array<ActiveLED> activeLeds;
int getLEDAt (uint32 x, uint32 y) const
@@ -665,6 +666,7 @@ private:
colourPalette = 0,
canvas
};
DisplayMode currentMode = colourPalette;
//==============================================================================


+ 11
- 10
examples/BLOCKS/BlocksMonitorDemo.h View File

@@ -136,15 +136,15 @@ public:
static Array<ControlButton::ButtonFunction> map[] =
{
{ CB::mode, CB::button0 },
{ CB::volume, CB::button1 },
{ CB::scale, CB::button2, CB::click },
{ CB::chord, CB::button3, CB::snap },
{ CB::arp, CB::button4, CB::back },
{ CB::sustain, CB::button5, CB::playOrPause },
{ CB::octave, CB::button6, CB::record },
{ CB::love, CB::button7, CB::learn },
{ CB::up },
{ CB::mode, CB::button0, CB::velocitySensitivity },
{ CB::volume, CB::button1, CB::glideSensitivity },
{ CB::scale, CB::button2, CB::slideSensitivity, CB::click },
{ CB::chord, CB::button3, CB::pressSensitivity, CB::snap },
{ CB::arp, CB::button4, CB::liftSensitivity, CB::back },
{ CB::sustain, CB::button5, CB::fixedVelocity, CB::playOrPause },
{ CB::octave, CB::button6, CB::glideLock, CB::record },
{ CB::love, CB::button7, CB::pianoMode, CB::learn },
{ CB::up },
{ CB::down }
};
@@ -748,7 +748,8 @@ private:
if (type == Block::lightPadBlock)
return new LightpadComponent (newBlock);
if (type == Block::loopBlock || type == Block::liveBlock)
if (type == Block::loopBlock || type == Block::liveBlock
|| type == Block::touchBlock || type == Block::developerControlBlock)
return new ControlBlockComponent (newBlock);
// Should only be connecting a Lightpad or Control Block!


+ 2
- 2
examples/BLOCKS/BlocksSynthDemo.h View File

@@ -751,7 +751,7 @@ private:
void clearOldTouchTimes (const Time now)
{
for (auto i = touchMessageTimesInLastSecond.size(); --i >= 0;)
if (touchMessageTimesInLastSecond.getReference(i) < now - juce::RelativeTime::seconds (0.33))
if (touchMessageTimesInLastSecond.getReference(i) < now - RelativeTime::seconds (0.33))
touchMessageTimesInLastSecond.remove (i);
}
@@ -835,7 +835,7 @@ private:
PhysicalTopologySource topologySource;
Block::Ptr activeBlock;
Array<juce::Time> touchMessageTimesInLastSecond;
Array<Time> touchMessageTimesInLastSecond;
int waveshapeMode = 0;


+ 14
- 0
extras/BLOCKS/doxygen/Doxyfile View File

@@ -18,3 +18,17 @@ DISABLE_INDEX = YES

GENERATE_TREEVIEW = YES

ALIASES += "tags{1}="

ALIASES += "s_break=<br>"
ALIASES += "s_file{1}=<code class=\"filename\">\1</code>"
ALIASES += "s_code{1}=<code class=\"code\">\1</code>"
ALIASES += "s_projcode{1}=<code class=\"project_code\">\1</code>"
ALIASES += "s_item{1}=<span class=\"comment\">\1</span>"

ALIASES += "jucelink{1}=<a href=\"https://www.juce.com/\">\1</a>"
ALIASES += "jucegithub{1}=<a href=\"https://github.com/WeAreROLI/JUCE\">\1</a>"
ALIASES += "blocksgithub{1}=<a href=\"https://github.com/WeAreROLI/BLOCKS-SDK\">\1</a>"
ALIASES += "blockscode{1}=<a href=\"https://juce.com/blocks/code\">\1</a>"
ALIASES += "littlefootgithub{1}=<a href=\"https://github.com/WeAreROLI/Littlefoot-Examples\">\1</a>"
ALIASES += "jucetutorials{1}=<a href=\"https://juce.com/learn/tutorials\">\1</a>"

+ 2
- 1
extras/BLOCKS/doxygen/DoxygenLayout.xml View File

@@ -3,8 +3,9 @@
<navindex>
<tab type="pages" visible="yes" title="Documentation" intro="How to use the BLOCKS SDK"/>
<tab type="modules" visible="yes" title="JUCE Modules" intro="A list of the JUCE modules included in the SDK"/>
<tab type="user" visible="yes" title="Get the JUCE framework" url="https://github.com/julianstorer/JUCE"/>
<tab type="user" visible="yes" title="Get the BLOCKS CODE IDE" url="https://juce.com/blocks/code"/>
<tab type="user" visible="yes" title="Get the standalone BLOCKS SDK" url="https://github.com/WeAreROLI/BLOCKS-SDK"/>
<tab type="user" visible="yes" title="Get the JUCE framework" url="https://github.com/WeAreROLI/JUCE"/>
</navindex>

<class>


BIN
extras/BLOCKS/doxygen/images/BlocksMonitor.png View File

Before After
Width: 1424  |  Height: 1468  |  Size: 248KB Width: 1424  |  Height: 1468  |  Size: 225KB

BIN
extras/BLOCKS/doxygen/images/blocks_code_interface.png View File

Before After
Width: 1300  |  Height: 961  |  Size: 309KB

BIN
extras/BLOCKS/doxygen/images/blocks_code_tabs.png View File

Before After
Width: 401  |  Height: 238  |  Size: 21KB

+ 5
- 3
extras/BLOCKS/doxygen/pages/juce_connecting_blocks.dox View File

@@ -13,11 +13,11 @@ When powered on by pressing the power button on the bottom edge, you will be abl
The power button also functions as a toggle for the Bluetooth connection - when the blue light on the button is illuminated, the device is able to connect via Bluetooth and send and receive MIDI data.
Pressing this button will turn the light off and disable the Bluetooth functionality.
Currently MIDI over Bluetooth is only supported on Mac OS.
Currently MIDI over Bluetooth is only supported on macOS.
@subsection mac_bluetooth MacOS
@subsection mac_bluetooth macOS
To connect a BLOCKS device via Bluetooth on MacOS, follow these steps:
To connect a BLOCKS device via Bluetooth on macOS, follow these steps:
- Open the "Audio Midi Setup" application (found in Applications/Utilities)
- Click on the menu item: Window -> Show MIDI Studio
@@ -29,4 +29,6 @@ To connect a BLOCKS device via Bluetooth on MacOS, follow these steps:
Lightpad and Control Blocks can be connected together in any number of combinations via their DNA edge connectors, sharing a common connection to your computer.
To do this, simply snap your devices together and the magnetic connectors will handle the rest.
Learn how to discover BLOCKS in your applications in the @ref discovering_blocks section.
*/

+ 42
- 6
extras/BLOCKS/doxygen/pages/juce_controlling_control_buttons.dox View File

@@ -3,15 +3,51 @@
In addition to sending button pressed and button released events, ControlButton objects can allow your application code to change the colour of the LED behind the corresponding physical button on a BLOCKS device.
An array of pointers to the available %ControlButton objects can be obtained from the Block::getButtons method of a Block---see the @ref discovering_blocks section for details of how to obtain a %Block object.
Once you have a %ControlButton, the functions involving the LED are ControlButton::hasLight and ControlButton::setLightColour, which are descriptively named.
An array of pointers to the available %ControlButton objects can be obtained from the Block::getButtons() method of a Block --- see the @ref discovering_blocks section for details of how to obtain a %Block object.
Once you have a %ControlButton, the functions involving the LED are ControlButton::hasLight() and ControlButton::setLightColour(), which are descriptively named.
A code snippet showing how to turn all the available buttons of a %Block red is shown below.
@code{.cpp}
class BlockButtonExample
{
void setAllButtonsRed (Block& block)
{
for (auto button : block.getButtons())
if (button->hasLight())
button->setLightColour (LEDColour (0xffff0000));
}
};
@endcode
@section controlling_control_buttons_example_usage Example usage
To add this functionality to the BlockFinder example project, add the above function to the BlockFinder class implementation. Then in the @s_projcode{topologyChanged()} callback, check if the connected %Block is a Control %Block and call the above function as shown below:
@code{.cpp}
void setAllButtonsRed (Block& block)
void topologyChanged() override
{
for (auto button : block->getButtons())
if (button->hasLight())
button->setLightColour (Colours::red);
//...
for (auto& block : currentTopology.blocks)
{
//...
if (block->getType() == Block::liveBlock || block->getType() == Block::loopBlock
|| block->getType() == Block::developerControlBlock || block->getType() == Block::touchBlock)
{
setAllButtonsRed (*block);
}
}
}
@endcode
If you run the application now and connect a Control %Block, you should see the control buttons turn red.
Learn more about other Block methods from the following pages:
@ref getting_touch_events
@ref getting_control_button_events
@ref controlling_led_grids
@ref controlling_led_strips
*/

+ 52
- 18
extras/BLOCKS/doxygen/pages/juce_controlling_led_grids.dox View File

@@ -1,36 +1,70 @@
/**
@page controlling_led_grids Controlling LED grids
@section basic_usage Basic usage
@section controlling_led_grids_basic_usage Basic usage
An LED grid on a BLOCKS device can be controlled via an LEDGrid object, which can be obtained from the Block::getLEDGrid function of a Block---see the @ref discovering_blocks section for details of how to obtain a %Block object.
An LED grid on a BLOCKS device can be controlled via an LEDGrid object, which can be obtained from the Block::getLEDGrid() function of a Block --- see the @ref discovering_blocks section for details of how to obtain a %Block object.
Using an LED grid requires an LEDGrid::Program to operate the LEDs.
This program specifies some code to run on the device, and can also provide methods to be called from your application which can comminucate with the code running on the device via a block of shared memory.
Using a LED grid requires a Block::Program to operate the LEDs.
This program specifies some code to run on the device, and can also provide methods to be called from your application which can communicate with the code running on the device via a block of shared memory.
The code which runs on the device must be specified using @ref the_littlefoot_language, which is described in the corresponding section.
However, for a very wide range of applications, the BitmapLEDProgram provided with the BLOCKS SDK is sufficient and you will not need to create your own.
Using a %BitmapLEDProgram to change the colour of LEDs is demonstated below.
@code{.cpp}
// This should be called when doing the initial configuration of your application.
void setBitmapLEDProgram (Block& block)
class BlockProgramExample
{
if (auto grid = block->getLEDGrid())
grid->setProgram (new BitmapLEDProgram (*grid));
}
public:
// This should be called when doing the initial configuration of your application.
void setBitmapLEDProgram (Block& block)
{
block.setProgram (new BitmapLEDProgram (block));
}
// Once a BitmapLEDProgram is loaded we can use its setLED method to change the
// colour of LEDs on the corresponding device.
void setLED (Block& block, int x, int y, Colour c)
{
if (auto grid = block->getLEDGrid())
if (auto program = dynamic_cast<BitmapLEDProgram*> (grid->getProgram()))
// Once a BitmapLEDProgram is loaded we can use its setLED() method to change the
// colour of LEDs on the corresponding device.
void setLED (Block& block, int x, int y, LEDColour c)
{
if (auto program = dynamic_cast<BitmapLEDProgram*> (block.getProgram()))
program->setLED (x, y, c);
}
};
@endcode
@section controlling_led_grids_example_usage Example usage
To add this functionality to the BlockFinder example project, add the above functions to the BlockFinder class implementation. Then in the @s_projcode{topologyChanged()} callback, check if the connected %Block is a Lightpad and call the above functions as shown below:
@code{.cpp}
void topologyChanged() override
{
//...
for (auto& block : currentTopology.blocks)
{
//...
if (block->getType() == Block::lightPadBlock)
{
setBitmapLEDProgram (*block);
setLED (*block, 7, 7, LEDColour (0xff00ff00));
}
}
}
@endcode
@section advanced_usage Advanced Usage
If you run the application now and connect a Lightpad, you should see a single green dot displayed in the centre of the surface.
@section controlling_led_grids_advanced_usage Advanced Usage
Using a custom %Block::Program allows more precise control over the operation of the LEDs.
The code which will actually execute on the device, returned by your overriden Block::Program::getLittleFootProgram() function, must be specified in the LittleFoot language.
Learn more about other Block methods from the following pages:
@ref getting_touch_events
@ref getting_control_button_events
@ref controlling_led_strips
Using a custom %LEDGrid::Program allows more precise control over the operation of the LEDs.
The code which will actually execute on the device, returned by your overriden LEDGrid::Program::getLittleFootProgram() function, must be specified in the LittleFoot language.
@ref controlling_control_buttons
*/

+ 43
- 6
extras/BLOCKS/doxygen/pages/juce_controlling_led_strips.dox View File

@@ -3,15 +3,52 @@
Control Blocks have a strip of LEDs which can be controlled via an LEDRow object.
A pointer to an %LEDRow object can be obtained from the Block::getLEDRow method of a Block---see the @ref discovering_blocks section for details of how to obtain a %Block object.
A pointer to an %LEDRow object can be obtained from the Block::getLEDRow() method of a Block --- see the @ref discovering_blocks section for details of how to obtain a %Block object.
Once you have an %LEDRow there are a few functions that you can use to interact with the strip of LEDs on a device.
A code snippet showing how to turn the whole strip of LEDs on a %Block orange is shown below.
A code snippet showing how to turn the whole strip of LEDs on a %Block yellow is shown below.
@code{.cpp}
class BlockLEDExample
{
public:
void setWholeLEDRowYellow (Block& block)
{
if (auto ledRow = block.getLEDRow())
for (int i = 0; i < ledRow->getNumLEDs(); ++i)
ledRow->setLEDColour (i, LEDColour (0xffffff00));
}
};
@endcode
@section controlling_led_strips_example_usage Example usage
To add this functionality to the BlockFinder example project, add the above function to the BlockFinder class implementation. Then in the @s_projcode{topologyChanged()} callback, check if the connected %Block is a Control %Block and call the above function as shown below:
@code{.cpp}
void setWholeLEDRowOrange (Block& block)
void topologyChanged() override
{
if (auto ledRow = block->getLEDRow())
for (int i = 0; i < ledRow.getNumLEDs(); ++i)
ledRow.setLEDColour (i, Colours::orange);
//...
for (auto& block : currentTopology.blocks)
{
//...
if (block->getType() == Block::liveBlock || block->getType() == Block::loopBlock
|| block->getType() == Block::developerControlBlock || block->getType() == Block::touchBlock)
{
setWholeLEDRowYellow (*block);
}
}
}
@endcode
If you run the application now and connect a Control %Block, you should see the LEDs in the strip turn yellow.
Learn more about other Block methods from the following pages:
@ref getting_touch_events
@ref getting_control_button_events
@ref controlling_led_grids
@ref controlling_control_buttons
*/

+ 11
- 6
extras/BLOCKS/doxygen/pages/juce_discovering_blocks.dox View File

@@ -10,19 +10,20 @@ Groups of connected Lightpad and Control Blocks are described by a BlockTopology
A %BlockTopology contains an array of references to Block objects, which provide access to Lightpad and Control %Block functionality, and an array of BlockDeviceConnection objects, which describe the connections between devices.
Once you have a %BlockTopology you have all the information required to visualise and interact with your Lightpads and Control Blocks.
For more information about using %Block objects see the @ref the_block_object section.
For more information about using %Block objects see @ref the_block_object section.
For Lightpads and Control Blocks a %BlockTopology can be obtained from a PhysicalTopologySource.
For Lightpads and Control Blocks, a %BlockTopology can be obtained from a PhysicalTopologySource.
@section the_physical_topology_source_object The PhysicalTopologySource object
The current topology is provided by a %PhysicalTopologySource.
When instantiated, a %PhysicalTopologySource monitors for any connections from your computer to any Lightpad and Control Blocks and the PhysicalTopologySource::getCurrentTopology() method returns the current %BlockTopology.
In an environment where Lightpad and Control can be connected and disconnected dynamically it is convenient to register your code for <code>topologyChanged()</code> callbacks from a %PhysicalTopologySource.
In an environment where Lightpad and Control Blocks can be connected and disconnected dynamically it is convenient to register your code for @s_projcode{topologyChanged()} callbacks from a %PhysicalTopologySource.
Then, when the current %BlockTopology changes, your application is able to react to the new configuration.
You can do this by inheriting from the TopologySource::Listener class and registering as a listener to a %PhysicalTopologySource object.
When you inherit from %TopologySource::Listener you must override the pure virtual method TopologySource::Listener::topologyChanged(), which is then called by a %PhysicalTopologySource on topology changes when you register as a listener.
A simple example is shown below.
BlockFinder.h:
@@ -31,15 +32,17 @@ BlockFinder.h:
BlockFinder.cpp:
@include BlockFinder/BlockFinder.cpp
When instantiated this class simply monitors for changes to the connected Lightpad and Control Blocks and prints some information about them to stdout.
When instantiated this class simply monitors for changes to the connected Lightpad and Control Blocks and prints some information about them to @s_code{stdout}.
Once you have the current %BlockTopology object you have access to the available %Block objects and can start to interact with them.
A more complex application would probably do much more in the <tt>topologyChanged()</tt> method---see the @ref example_applications page.
A more complex application would probably do much more in the @s_projcode{topologyChanged()} method---see the @ref example_applications page.
You can find this simple example in the @s_file{examples/BlockFinder/} directory of the BLOCKS-SDK and the following sections will build on top of this project. If you need help with downloading and installing the BLOCKS-SDK, please refer to @ref the_standalone_blocks_sdk section.
@section the_block_object The Block object
A Block object is the main entry point for communicating between your application and any Lightpad and Control Blocks that are connected to your computer.
All the different %Block types are subclasses of %Block so they provide the same interface (see the %Block class documentation).
All the different %Block types are subclasses of %Block so they provide the same interface (see the Block class documentation).
About half of the %Block public member functions return information about the physical device it represents.
In the example code above you can see that we use some of these methods to query each %Block about its current status.
The more interesting %Block methods return pointers to objects you can use to control and receive events from individual BLOCKS.
@@ -52,4 +55,6 @@ More detail about these methods can be obtained from the following pages:
@ref controlling_led_grids
@ref controlling_led_strips
@ref controlling_control_buttons
*/

+ 16
- 5
extras/BLOCKS/doxygen/pages/juce_downloading_the_sdk.dox View File

@@ -1,12 +1,23 @@
/**
@page downloading_the_sdk Downloading the SDK
The BLOCKS SDK is distributed as part of the <a href="https://www.juce.com/">JUCE framework</a>, which can be obtained from GitHub <a href="https://github.com/julianstorer/JUCE">here</a>.
The JUCE repository also contains the code for the @ref example_applications, which require the JUCE framework to compile.
Whilst you don't need to know anything about JUCE to build the examples using the supplied Visual Studio/Xcode/etc projects, it will probably be worthwhile reading a brief introduction to JUCE, which can be found <a href="https://www.juce.com/learn/getting-started">here</a>.
@section writing_littlefoot_scripts Writing scripts using the LittleFoot language
If your goal is to develop simple scripts that can be loaded onto the BLOCKS hardware and run independently from a host application, you should read about @ref the_littlefoot_language. To get started quickly, you can download the BLOCKS CODE IDE @blockscode{here} and write all your LittleFoot code in there. BLOCKS CODE is designed to work seemlessly with BLOCKS hardware and allow you to compile and upload LittleFoot scripts to BLOCKS instantly.
More details are provided in @ref the_littlefoot_language and @ref example_scripts sections.
You can also download the standalone BLOCKS SDK from GitHub <a href="https://github.com/WeAreROLI/BLOCKS-SDK">here</a>.
@section integrating_blocks_sdk Integrating BLOCKS SDK into existing applications
You can also download the standalone BLOCKS SDK from GitHub @blocksgithub{here}.
This is a stripped down version of what JUCE provides, including only the features required for the SDK.
Using this version of the SDK is much more complicated but may be more suitable for integrating BLOCKS into an existing application.
More details are provided in the @ref the_standalone_blocks_sdk section.
More details are provided in @ref the_standalone_blocks_sdk and @ref example_integrations sections.
@section building_blocks_applications Building BLOCKS applications using JUCE
The BLOCKS SDK is distributed as part of the @jucelink{JUCE framework}, which can be obtained from GitHub @jucegithub{here}.
The JUCE repository also contains the code for the @ref example_applications, which require the JUCE framework to compile.
Whilst you don't need to know anything about JUCE to build the examples using the supplied Visual Studio/Xcode/Makefile projects, it will probably be worthwhile reading some JUCE tutorials, which can be found @jucetutorials{here}.
Learn how to connect BLOCKS in the @ref connecting_blocks section or start discovering BLOCKS in your applications by jumping to the @ref discovering_blocks section.
*/

+ 5
- 7
extras/BLOCKS/doxygen/pages/juce_example_applications.dox View File

@@ -1,17 +1,15 @@
/**
@page example_applications Example Applications
@page example_applications Example JUCE Applications
@section downloading_the_example_code Downloading the example code
These example applications demonstrate the functionality of the BLOCKS SDK.
The example applications are all distributed as part of the <a href="https://www.juce.com/">JUCE framework</a>, which can be obtained from GitHub <a href="https://github.com/julianstorer/JUCE">here</a>.
You will find the examples in the <tt>JUCE/examples/BLOCKS/</tt> directory.
Each example comes with projects for Visual Studio, Xcode, etc in the <tt>Builds</tt> subdirectory or each example directory, and these should open, compile and run without any trouble in the respective IDEs.
The example applications are all distributed as part of the @jucelink{JUCE framework}, which can be obtained from GitHub @jucegithub{here}.
You will find the examples in the @s_file{JUCE/examples/BLOCKS/} directory.
First, you need to generate a Projucer project from the corresponding PIP file of each example project. If you don't know how to do this, please refer to the JUCE tutorials @jucetutorials{here}. This will create projects for Visual Studio, Xcode and Makefile in the @s_file{Builds} subdirectory of each example directory, and these should open, compile and run without any trouble in the respective IDEs.
A quick guide to getting up and running with JUCE can be found <a href="https://www.juce.com/learn/getting-started">here</a>.
<h1>Overview</h1>
@section example_applications_overview Overview
@subpage example_blocks_monitor


+ 19
- 0
extras/BLOCKS/doxygen/pages/juce_example_bitmap_led_program.dox View File

@@ -0,0 +1,19 @@
/**
@page example_bitmap_led_program The %BitmapLEDProgram class
@section littlefoot_example A LittleFoot example
The %BitmapLEDProgram class is a simple example of a LittleFoot program.
@s_file{%juce_blocks_basics/visualisers/juce_BitmapLEDProgram.h}
@include juce_blocks_basics/visualisers/juce_BitmapLEDProgram.h
@s_file{juce_blocks_basics/visualisers/juce_BitmapLEDProgram.cpp}
@include juce_blocks_basics/visualisers/juce_BitmapLEDProgram.cpp
The repaint() method of the LittleFoot program is called at approximately 25 Hz, and each time it simply inspects the heap (the shared area of memory used to communicate between your application code and your LittleFoot program) and sets the LEDs based on the heap's content.
To update the heap, and hence the LEDs, your application code calls BitmapLEDProgram::setLED().
A more advanced example can be found in the source code of the DrumPadGridProgram class or in the @ref example_blocks_synth example.
*/

+ 29
- 0
extras/BLOCKS/doxygen/pages/juce_example_block_finder.dox View File

@@ -0,0 +1,29 @@
/**
@page example_block_finder BlockFinder
In order to compile and run this application you need to first download and compile the BLOCKS-SDK, which can be obtained from GitHub @blocksgithub{here}. If you need help with this step, please refer to @ref the_standalone_blocks_sdk section.
@section standalone_example An example application
The source code for this example can be found in the @blocksgithub{BLOCKS-SDK repository} at @s_file{examples/BlockFinder/}, with the parts that are specific to different operating systems in the corresponding subdirectories.
The main functionality of the application is contained within the following class:
@s_file{BlockFinder/BlockFinder.h}:
@include BlockFinder/BlockFinder.h
@s_file{BlockFinder/BlockFinder.cpp}:
@include BlockFinder/BlockFinder.cpp
All this class does is create a PhysicalTopologySource and register for TopologySource::Listener::topologyChanged() callbacks --- for more information about how this works you should see the @ref discovering_blocks section.
When the topology changes we print some information about the available BLOCKS.
The @s_projcode{main} function of the macOS application is the easiest to understand.
@s_file{BlockFinder/MacOS/main.mm}:
@include BlockFinder/MacOS/main.mm
Here we simply perform some JUCE initialisation, instantiate a BlockFinder class, then run the event loop.
Whilst in the event loop, the @s_projcode{finder} object receives TopologySource::Listener::topologyChanged() callbacks and we see output printed to @s_code{stdout} when BLOCKS are connected or disconnected.
*/

+ 227
- 10
extras/BLOCKS/doxygen/pages/juce_example_blocks_drawing.dox View File

@@ -1,35 +1,252 @@
/**
@page example_blocks_drawing BlocksDrawing
In order to compile and run this application you need to first download the <a href="https://www.juce.com/">JUCE framework</a>, which can be obtained from GitHub <a href="https://github.com/julianstorer/JUCE">here</a>.
In order to compile and run this application you need to first download the @jucelink{JUCE framework}, which can be obtained from GitHub @jucegithub{here}.
@section blocks_drawing_introduction Introduction
BlocksDrawing is a JUCE application that allows you to use your Lightpad as a drawing surface. You can choose from a palette of 9 base colours and paint them on the 15x15 LED grid, blending between colours using touch pressure.
Navigate to the <tt>JUCE/examples/BLOCKS/BlocksDrawing/Builds/</tt> directory and open the code project in your IDE of choice. Run the application and connect your Lightpad (if you do not know how to do this, see @ref connecting_blocks) - it should now display a 3x3 grid of colours to choose from. Touch a colour to set it as the current brush colour and then press the mode button to switch to canvas mode where you will be presented with a blank touch surface. Touch anywhere on the LED grid to start painting and use the pressure of your touch to control how bright the colour is. Try painting over an already painted LED to increase its brightness and blend between different colours by doing this with a different brush colour. To clear the canvas and start over, double-click the mode button.
Generate a Projucer project from the PIP file located in the @s_file{JUCE/examples/BLOCKS/} folder, then navigate to the @s_file{BlocksDrawingDemo/Builds/} directory and open the code project in your IDE of choice. Run the application and connect your Lightpad (if you do not know how to do this, see @ref connecting_blocks) - it should now display a 3x3 grid of colours to choose from. Touch a colour to set it as the current brush colour and then press the mode button to switch to canvas mode where you will be presented with a blank touch surface. Touch anywhere on the LED grid to start painting and use the pressure of your touch to control how bright the colour is. Try painting over an already painted LED to increase its brightness and blend between different colours by doing this with a different brush colour. To clear the canvas and start over, double-click the mode button.
The concept of a BLOCKS topology and the methods for receiving callbacks from a Block object are covered in the @ref example_blocks_monitor example and this tutorial will cover the methods in the API for displaying grids and setting LEDs on the Lightpad.
@section blocks_drawing_led_grid The LEDGrid Object
Lightpads have a 15x15 LED grid which can be accessed and controlled through the LEDGrid object, a pointer to which is returned by the Block::getLEDGrid() method (for more details on how the %LEDGrid object operates, see @ref controlling_led_grids). In the <code>topologyChanged()</code> method of <code>MainComponent</code> this %LEDGrid pointer is passed to the <code>setLEDProgram()</code> method, which sets the LEDGrid::Program to either a DrumPadGridProgram or BitmapLEDProgram, depending on the selected mode.
Lightpads have a 15x15 LED grid which can be accessed and controlled through the LEDGrid object, a pointer to which is returned by the Block::getLEDGrid() method @s_item{[1]} (for more details on how the %LEDGrid object operates, see @ref controlling_led_grids).
@code{.cpp}
void topologyChanged() override
{
//...
auto blocks = topologySource.getCurrentTopology().blocks;
for (auto b : blocks)
{
if (b->getType() == Block::Type::lightPadBlock)
{
activeBlock = b;
//...
if (auto grid = activeBlock->getLEDGrid()) // [1]
{
//...
setLEDProgram (*activeBlock); // [2]
}
//...
}
}
}
@endcode
In the @s_projcode{topologyChanged()} method of @s_projcode{BlocksDrawingDemo} this %LEDGrid pointer is passed to the @s_projcode{setLEDProgram()} method @s_item{[2]}, which sets the Block::Program to either a DrumPadGridProgram @s_item{[3]} or a BitmapLEDProgram @s_item{[4]}, depending on the selected mode.
@code{.cpp}
void setLEDProgram (Block& block)
{
if (currentMode == canvas)
{
block.setProgram (new BitmapLEDProgram (block)); // [4]
//...
}
else if (currentMode == colourPalette)
{
block.setProgram (new DrumPadGridProgram (block)); // [3]
//...
}
}
@endcode
@section blocks_drawing_colour_palette Colour Palette
In the colour palette mode the Lightpad displays a 3x3 grid of colours, constructed using the %DrumPadGridProgram class. A %DrumPadGridProgram pointer called <code>colourPaletteProgram</code> is declared as a private member variable of <code>MainComponent</code> and in the <code>MainComponent::setLEDProgram()</code> method this is set to point to a new %DrumPadGridProgram object and is passed the %LEDGrid object of the Lightpad in its constructor. After the program has been initialised, it is passed to the LEDGrid to display using the LEDGrid::setProgram() method and the layout of the grid is set up using the DrumPadGridProgram::setGridFills() method. This function takes 3 arguments: the number of rows, number of columns and an array of DrumPadGridProgram::GridFill objects containing a <code>GridFill</code> for each pad that controls its colour and fill type. The <code>ColourGrid</code> struct in MainComponent.h contains all of this information and handles the construction of the <code>GridFill</code> array in the <code>ColourGrid::constructGridFillArray()</code> method. An instance of this object called <code>layout</code> is declared as a member variable of <code>MainComponent</code> to easily change how the grid looks. The <code>ColourGrid::setActiveColourForTouch()</code> method is called in the <code>MainComponent::touchChanged()</code> callback and is used to determine which brush colour has been selected based on a Touch coordinate from the Lightpad.
In the colour palette mode the Lightpad displays a 3x3 grid of colours, constructed using the %DrumPadGridProgram class. A %DrumPadGridProgram pointer is retrieved by calling the @s_projcode{getPaletteProgram()} helper function of @s_projcode{BlocksDrawingDemo} and in the @s_projcode{BlocksDrawingDemo::setLEDProgram()} method the %Block::Program is set to point to a new %DrumPadGridProgram object and is passed the %Block object of the Lightpad in its constructor.
\image html BlocksDrawing_palette.JPG "Colour palette mode"
@code{.cpp}
DrumPadGridProgram* getPaletteProgram()
{
if (activeBlock != nullptr)
return dynamic_cast<DrumPadGridProgram*> (activeBlock->getProgram());
@section blocks_drawing_canvas Canvas
return nullptr;
}
@endcode
After the program has been initialised, it is passed to the LEDGrid to display using the Block::setProgram() method and the layout of the grid is set up using the DrumPadGridProgram::setGridFills() method. This function takes 3 arguments: the number of rows, number of columns and an array of DrumPadGridProgram::GridFill objects containing a @s_projcode{GridFill} for each pad that controls its colour and fill type.
@code{.cpp}
void setLEDProgram (Block& block)
{
//...
else if (currentMode == colourPalette)
{
//...
if (auto* program = getPaletteProgram())
program->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray);
}
}
@endcode
The @s_projcode{ColourGrid} struct contains all of this information and handles the construction of the @s_projcode{GridFill} array in the @s_projcode{ColourGrid::constructGridFillArray()} method.
@code{.cpp}
void constructGridFillArray()
{
gridFillArray.clear();
auto counter = 0;
for (auto i = 0; i < numColumns; ++i)
{
for (auto j = 0; j < numRows; ++j)
{
DrumPadGridProgram::GridFill fill;
Colour colourToUse = colourArray.getUnchecked (counter);
fill.colour = colourToUse.withBrightness (colourToUse == currentColour ? 1.0f : 0.1f);
if (colourToUse == Colours::black)
fill.fillType = DrumPadGridProgram::GridFill::FillType::hollow;
else
fill.fillType = DrumPadGridProgram::GridFill::FillType::filled;
gridFillArray.add (fill);
if (++counter == colourArray.size())
counter = 0;
}
}
}
@endcode
An instance of this object called @s_projcode{layout} is declared as a member variable of @s_projcode{BlocksDrawingDemo} to easily change how the grid looks.
In canvas mode, the %LEDGrid program is set to an instance of %BitmapLEDProgram and uses the BitmapLEDProgram::setLED() method to set individual LEDs on the Lightpad to a particular colour. The <code>ActiveLED</code> struct declared in the private section of <code>MainComponent</code> is used to keep track of which LEDs are on and their colour and brightness. <code>MainComponent</code> contains an %Array of these objects called <code>activeLeds</code>.
In the <code>MainComponent::setLEDProgram()</code> method the program is set up and passed to the %LEDGrid object the same way as in the colour palette mode but the <code>MainComponent::redrawLEDs()</code> method is also called which iterates over the <code>activeLeds</code> array and sets the appropriate LEDs on the Lightpad so the LED states persist between mode switches. When a Touch is received in the <code>MainComponent::touchChanged()</code> callback the <code>MainComponent::drawLEDs()</code> method is called with 4 arguments: x and y coordinates, touch pressure and brush colour. This method iterates over the <code>activeLed</code> array and checks to see if there is an active LED at the given coordinate. If it is blank, an <code>ActiveLED</code> object is created and added to the array with the given coordinates and colour using touch pressure for brightness. If there is already an active LED at the coordinate, the colour of that LED will be blended with the current brush colour, the proportion of which is determined by the touch pressure.
@code{.cpp}
ColourGrid layout { 3, 3 };
@endcode
\image html BlocksDrawing_canvas.JPG "Unleash your inner Picasso!"
The @s_projcode{ColourGrid::setActiveColourForTouch()} method is called in the @s_projcode{BlocksDrawingDemo::touchChanged()} callback and is used to determine which brush colour has been selected based on a Touch coordinate from the Lightpad.
@code{.cpp}
bool setActiveColourForTouch (int x, int y)
{
auto colourHasChanged = false;
auto xindex = x / 5;
auto yindex = y / 5;
auto newColour = colourArray.getUnchecked ((yindex * 3) + xindex);
if (currentColour != newColour)
{
currentColour = newColour;
constructGridFillArray();
colourHasChanged = true;
}
return colourHasChanged;
}
@endcode
When the application is run, the colour palette mode would look like this:
@image html BlocksDrawing_palette.JPG "Colour palette mode"
@section blocks_drawing_canvas Canvas
In canvas mode, the %Block program is set to an instance of %BitmapLEDProgram and uses the BitmapLEDProgram::setLED() method to set individual LEDs on the Lightpad to a particular colour. The @s_projcode{ActiveLED} struct declared in the private section of @s_projcode{BlocksDrawingDemo} is used to keep track of which LEDs are on and their colour and brightness. @s_projcode{BlocksDrawingDemo} contains an %Array of these objects called @s_projcode{activeLeds}.
@code{.cpp}
struct ActiveLED
{
uint32 x, y;
Colour colour;
float brightness;
//...
};
Array<ActiveLED> activeLeds;
@endcode
In the @s_projcode{BlocksDrawingDemo::setLEDProgram()} method the program is set up and passed to the %LEDGrid object the same way as in the colour palette mode but the @s_projcode{BlocksDrawingDemo::redrawLEDs()} method is also called which iterates over the @s_projcode{activeLeds} array and sets the appropriate LEDs on the Lightpad so the LED states persist between mode switches.
@code{.cpp}
void redrawLEDs()
{
if (auto* canvasProgram = getCanvasProgram())
{
for (auto led : activeLeds)
{
canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
lightpadComponent.setLEDColour (led.x, led.y, led.colour.withBrightness (led.brightness));
}
}
}
@endcode
When a Touch is received in the @s_projcode{BlocksDrawingDemo::touchChanged()} callback the @s_projcode{BlocksDrawingDemo::drawLEDs()} method is called with 4 arguments: x and y coordinates, touch pressure and brush colour. This method iterates over the @s_projcode{activeLed} array and checks to see if there is an active LED at the given coordinate. If it is blank, an @s_projcode{ActiveLED} object is created and added to the array with the given coordinates and colour using touch pressure for brightness. If there is already an active LED at the coordinate, the colour of that LED will be blended with the current brush colour, the proportion of which is determined by the touch pressure.
@code{.cpp}
void drawLED (uint32 x0, uint32 y0, float z, Colour drawColour)
{
if (auto* canvasProgram = getCanvasProgram())
{
auto index = getLEDAt (x0, y0);
if (drawColour == Colours::black)
{
if (index >= 0)
{
canvasProgram->setLED (x0, y0, Colours::black);
lightpadComponent.setLEDColour (x0, y0, Colours::black);
activeLeds.remove (index);
}
return;
}
if (index < 0)
{
ActiveLED led;
led.x = x0;
led.y = y0;
led.colour = drawColour;
led.brightness = z;
activeLeds.add (led);
canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
lightpadComponent.setLEDColour (led.x, led.y, led.colour.withBrightness (led.brightness));
return;
}
auto currentLed = activeLeds.getReference (index);
if (currentLed.colour == drawColour)
currentLed.brightness = jmin (currentLed.brightness + z, 1.0f);
else
currentLed.colour = currentLed.colour.interpolatedWith (drawColour, z);
if (canvasProgram != nullptr)
canvasProgram->setLED (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
lightpadComponent.setLEDColour (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
activeLeds.set (index, currentLed);
}
}
@endcode
When the application is run, the canvas mode would look like this:
@image html BlocksDrawing_canvas.JPG "Unleash your inner Picasso!"
@section blocks_drawing_summary Summary
This tutorial and the accompanying code project have introduced the %LEDGrid object and shown how to use the %LEDGrid::Program object to display basic grids and set individual LEDs on the Lightpad.
This tutorial and the accompanying code project have introduced the %LEDGrid object and shown how to use the %Block::Program object to display basic grids and set individual LEDs on the Lightpad.
@section blocks_drawing_see_also See also
- @ref example_blocks_monitor
- @ref example_blocks_synth
*/

+ 297
- 9
extras/BLOCKS/doxygen/pages/juce_example_blocks_monitor.dox View File

@@ -1,40 +1,328 @@
/**
@page example_blocks_monitor BlocksMonitor
In order to compile and run this application you need to first download the <a href="https://www.juce.com/">JUCE framework</a>, which can be obtained from GitHub <a href="https://github.com/julianstorer/JUCE">here</a>.
In order to compile and run this application you need to first download the @jucelink{JUCE framework}, which can be obtained from GitHub @jucegithub{here}.
@section blocks_monitor_introduction Introduction
BlocksMonitor is a simple JUCE application that shows currently connected Lightpad and Control %Block devices and visualises touches and button presses. It also displays some basic information about the Blocks.
Navigate to the <tt>JUCE/examples/BLOCKS/BlocksMonitor/Builds/</tt> directory and open the code project in your IDE of choice. Run the application and connect your Blocks (if you do not know how to do this, see @ref connecting_blocks). Any devices that you have connected should now show up in the application window and this display will be updated as you add and remove Blocks. Lightpads are represented as a black square and will display the current touches as coloured circles, the size of which depend on the touch pressure, and Control Blocks are shown as rectangles containing the LED row and clickable buttons on the hardware. If you hover the mouse cursor over a %Block, a tooltip will appear displaying the name, UID, serial number and current battery level.
Generate a Projucer project from the PIP file located in the @s_file{JUCE/examples/BLOCKS/} folder, then navigate to the @s_file{BlocksMonitorDemo/Builds/} directory and open the code project in your IDE of choice. Run the application and connect your Blocks (if you do not know how to do this, see @ref connecting_blocks). Any devices that you have connected should now show up in the application window and this display will be updated as you add and remove Blocks. Lightpads are represented as a black square and will display the current touches as coloured circles, the size of which depend on the touch pressure, and Control Blocks are shown as rectangles containing the LED row and clickable buttons on the hardware. If you hover the mouse cursor over a %Block, a tooltip will appear displaying the name, UID, serial number and current battery level.
\image html BlocksMonitor.png "The BlocksMonitor application with a Lightpad and 3 Control Blocks connected"
@image html BlocksMonitor.png "The BlocksMonitor application with a Lightpad and 3 Control Blocks connected"
@section blocks_monitor_topology Topology
One of the fundamental concepts of the BLOCKS API is topology - a topology is a set of physically connected Blocks and the connections between them. Knowing when the topology has changed and accessing a data structure containing the current topology is the basis of any Blocks application.
To access the current topology, <code>MainComponent</code> inherits from the TopologySource::Listener base class and implements the TopologySource::Listener::topologyChanged() method, a callback which is used to inform listeners when any physical devices have been added or removed. In order to receive these callbacks, <code>MainComponent</code> contains an instance of the PhysicalTopologySource class and registers itself as a listener to this object in its constructor. When the <code>topologyChanged()</code> method is called, this object can be used to access the updated topology through the PhysicalTopologySource::getCurrentTopology() method which returns a BlockTopology struct containing an array of currently connected Block objects and an array of BlockDeviceConnection structs representing the connections between them.
To access the current topology, @s_projcode{BlocksMonitorDemo} inherits from the TopologySource::Listener base class @s_item{[1]} and implements the TopologySource::Listener::topologyChanged() method @s_item{[2]}, a callback which is used to inform listeners when any physical devices have been added or removed.
@code{.cpp}
class BlocksMonitorDemo : public Component,
public TopologySource::Listener, // [1]
private Timer
{
public:
//...
void topologyChanged() override // [2]
{
//...
@endcode
In order to receive these callbacks, @s_projcode{BlocksMonitorDemo} contains an instance of the PhysicalTopologySource class @s_item{[3]} and registers itself as a listener to this object in its constructor @s_item{[4]}.
@code{.cpp}
BlocksMonitorDemo()
{
//...
topologySource.addListener (this); // [4]
//...
}
private:
//...
PhysicalTopologySource topologySource; // [3]
OwnedArray<BlockComponent> blockComponents;
BlockComponent* masterBlockComponent = nullptr;
//...
@endcode
When the @s_projcode{topologyChanged()} method is called, this object can be used to access the updated topology through the PhysicalTopologySource::getCurrentTopology() method @s_item{[5]} which returns a BlockTopology struct containing an array of currently connected Block objects and an array of BlockDeviceConnection structs representing the connections between them.
@code{.cpp}
void topologyChanged() override
{
//...
auto topology = topologySource.getCurrentTopology(); // [5]
//...
}
@endcode
@section blocks_monitor_block_object The Block Object
The array of %Block objects contained in the %BlockTopology struct can be used to access individual %Block objects and determine their type using the Block::getType() method. The application uses this information to construct an on-screen representation of the currently connected Blocks by creating either a <code>LightpadComponent</code> or <code>ControlBlockComponent</code> object for each %Block in the current topology. Both of these classes derive from <code>BlockComponent</code>, a relatively simple base class that contains some virtual functions for painting the %Block on screen and handling callbacks from the touch surface and/or buttons on the %Block. In its constructor, <code>BlockComponent</code> takes a pointer to the %Block object that it represents and adds itself as a listener to the touch surface (if it is a Lightpad) and buttons using the Block::getTouchSurface() and Block::getButtons() methods, respectively. It inherits from the TouchSurface::Listener and ControlButton::Listener classes and overrides the TouchSurface::Listener::touchChanged(), ControlButton::Listener::buttonPressed() and ControlButton::Listener::buttonReleased() methods to call its own virtual methods, which are implemented by the <code>LightpadComponent</code> and <code>ControlBlockComponent</code> classes to update the on-screen components.
The array of %Block objects contained in the %BlockTopology struct can be used to access individual %Block objects @s_item{[1]} and determine their type using the Block::getType() method @s_item{[2]}.
@code{.cpp}
for (auto& block : topology.blocks) // [1]
{
if (auto* blockComponent = createBlockComponent (block))
{
//...
}
}
@endcode
The application uses this information to construct an on-screen representation of the currently connected Blocks by creating either a @s_projcode{LightpadComponent} object @s_item{[3]} or a @s_projcode{ControlBlockComponent} object @s_item{[4]} for each %Block in the current topology.
@code{.cpp}
BlockComponent* createBlockComponent (Block::Ptr newBlock)
{
auto type = newBlock->getType(); // [2]
if (type == Block::lightPadBlock)
return new LightpadComponent (newBlock); // [3]
if (type == Block::loopBlock || type == Block::liveBlock
|| type == Block::touchBlock || type == Block::developerControlBlock)
return new ControlBlockComponent (newBlock); // [4]
jassertfalse;
return nullptr;
}
@endcode
Both of these classes derive from @s_projcode{BlockComponent}, a relatively simple base class that contains some virtual functions for painting the %Block on screen and handling callbacks from the touch surface and/or buttons on the %Block.
@code{.cpp}
class BlockComponent : public Component,
//...
{
public:
//...
virtual void paint (Graphics&) override = 0;
virtual void handleButtonPressed (ControlButton::ButtonFunction, uint32) {}
virtual void handleButtonReleased (ControlButton::ButtonFunction, uint32) {}
virtual void handleTouchChange (TouchSurface::Touch) {}
virtual void handleBatteryLevelUpdate (float) {}
//...
@endcode
In its constructor, @s_projcode{BlockComponent} takes a pointer to the %Block object that it represents and adds itself as a listener to the touch surface (if it is a Lightpad) and buttons using the Block::getTouchSurface() and Block::getButtons() methods, respectively.
@code{.cpp}
BlockComponent (Block::Ptr blockToUse)
: block (blockToUse)
{
//...
if (auto touchSurface = block->getTouchSurface())
touchSurface->addListener (this);
for (auto button : block->getButtons())
button->addListener (this);
//...
}
@endcode
It inherits from the TouchSurface::Listener and ControlButton::Listener classes and overrides the TouchSurface::Listener::touchChanged(), ControlButton::Listener::buttonPressed() and ControlButton::Listener::buttonReleased() methods to call its own virtual methods, which are implemented by the @s_projcode{LightpadComponent} and @s_projcode{ControlBlockComponent} classes to update the on-screen components.
@code{.cpp}
class BlockComponent : public Component,
public SettableTooltipClient,
private TouchSurface::Listener,
private ControlButton::Listener,
private Timer
{
private:
//...
void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); }
void buttonPressed (ControlButton& b, Block::Timestamp t) override { handleButtonPressed (b.getType(), t); }
void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); }
//...
@endcode
To visualise touches on the Lightpad, @s_projcode{LightpadComponent} contains an instance of the TouchList class called @s_projcode{touches} and calls the TouchList::updateTouch() method whenever it receives a touch surface listener callback in the @s_projcode{LightpadComponent::handleTouchChange()} method.
@code{.cpp}
class LightpadComponent : public BlockComponent
{
public:
//...
void handleTouchChange (TouchSurface::Touch touch) override { touches.updateTouch (touch); }
private:
//...
TouchList<TouchSurface::Touch> touches;
//...
@endcode
The @s_projcode{LightpadBlock::paint()} method then iterates over the current TouchSurface::Touch objects in the %TouchList and visualises them on the component at 25Hz.
To visualise touches on the Lightpad, <code>LightpadComponent</code> contains an instance of the TouchList class called <code>touches</code> and calls the TouchList::updateTouch() method whenever it receives a touch surface listener callback in the <code>LightpadComponent::handleTouchChange()</code> method. The <code>LightpadBlock::paint()</code> method then iterates over the current TouchSurface::Touch objects in the %TouchList and visualises them on the component at 25Hz.
@code{.cpp}
void paint (Graphics& g) override
{
//...
for (auto touch : touches)
{
//...
}
}
@endcode
The <code>ControlBlockComponent</code> class represents a generic Control %Block with 15 LEDs, 8 circular buttons and 1 rounded rectangular button. When a button is pressed on the physical Control %Block, the <code>BlockComponent::handleButtonPressed()</code> function is called and this class uses the ControlButton::ButtonFunction variable to determine which button was pressed and should be activated on the on-screen component. The same process is repeated for when the button is released. This class also overrides the <code>BlockComponent::handleBatteryLevelUpdate()</code> method to update which LEDs should be on based on the battery level, which is accessed in the <code>BlockComponent</code> base class using the Block::getBatteryLevel() and Block::isBatteryCharging() methods.
The @s_projcode{ControlBlockComponent} class represents a generic Control %Block with 15 LEDs, 8 circular buttons and 1 rounded rectangular button. When a button is pressed on the physical Control %Block, the @s_projcode{BlockComponent::handleButtonPressed()} function is called and this class uses the ControlButton::ButtonFunction variable to determine which button was pressed and should be activated on the on-screen component.
@code{.cpp}
void handleButtonPressed (ControlButton::ButtonFunction function, uint32) override
{
displayButtonInteraction (controlButtonFunctionToIndex (function), true);
}
@endcode
The same process is repeated for when the button is released.
@code{.cpp}
void handleButtonReleased (ControlButton::ButtonFunction function, uint32) override
{
displayButtonInteraction (controlButtonFunctionToIndex (function), false);
}
@endcode
This class also overrides the @s_projcode{BlockComponent::handleBatteryLevelUpdate()} method to update which LEDs should be on based on the battery level, which is accessed in the @s_projcode{BlockComponent} base class using the Block::getBatteryLevel() and Block::isBatteryCharging() methods.
@code{.cpp}
void handleBatteryLevelUpdate (float batteryLevel) override
{
auto numLedsOn = static_cast<int> (numLeds * batteryLevel);
if (numLedsOn != previousNumLedsOn)
for (auto i = 0; i < numLeds; ++i)
leds.getUnchecked (i)->setOnState (i < numLedsOn);
previousNumLedsOn = numLedsOn;
repaint();
}
@endcode
These callback methods are a simple and powerful way to get user input from the Blocks and use this data to drive some process in your application. In this example, the input is simply mirrored on the screen but it could be used to control any number of things such as audio synthesis (see the @ref example_blocks_synth example) and graphics (see the @ref example_blocks_drawing example).
@section blocks_monitor_connections Blocks Connections
The %BlockTopology struct returned by the <code>%PhysicalTopologySource::getCurrentTopology()</code> method also contains an array of BlockDeviceConnection objects representing all the current DNA port connections between Blocks in the topology. A single %BlockDeviceConnection struct describes a physical connection between two ports on two Blocks and contains a Block::UID and Block::ConnectionPort object for each of the two devices.
The %BlockTopology struct returned by the @s_projcode{%PhysicalTopologySource::getCurrentTopology()} method also contains an array of BlockDeviceConnection objects representing all the current DNA port connections between Blocks in the topology. A single %BlockDeviceConnection struct describes a physical connection between two ports on two Blocks and contains a Block::UID and Block::ConnectionPort object for each of the two devices.
This information is used to calculate the position and rotation of each connected %Block and update the corresponding @s_projcode{topLeft} and @s_projcode{rotation} member variables of its @s_projcode{BlockComponent} so that the correct topology is displayed on the screen. The @s_projcode{topLeft} variable is a Point that describes the position of the top left of the @s_projcode{BlockComponent} in terms of logical device units relative to the top left of the master %Block at Point (0, 0).
@code{.cpp}
int rotation = 0;
Point<float> topLeft = { 0.0f, 0.0f };
@endcode
Initially, all @s_projcode{BlockComponent} instances have the @s_projcode{topLeft} position (0, 0) and the @s_projcode{BlocksMonitorDemo::positionBlocks()} method iterates first over all of the Blocks connected to the master %Block and then any remaining Blocks and calculates the correct @s_projcode{topLeft} %Point and @s_projcode{rotation} for each using the array of %BlockDeviceConnection objects.
@code{.cpp}
void positionBlocks (BlockTopology topology)
{
//...
Array<BlockDeviceConnection> masterBlockConnections;
for (auto connection : topology.connections)
if (connection.device1 == masterBlockComponent->block->uid || connection.device2 == masterBlockComponent->block->uid)
masterBlockConnections.add (connection);
while (maxDelta > 0.001f && --maxLoops)
{
//...
for (auto connection : masterBlockConnections)
{
//...
for (auto otherBlockComponent : blockComponents)
{
//...
}
}
}
This information is used to calculate the position and rotation of each connected %Block and update the corresponding <code>topLeft</code> and <code>rotation</code> member variables of its <code>BlockComponent</code> so that the correct topology is displayed on the screen. The <code>topLeft</code> variable is a Point that describes the position of the top left of the <code>BlockComponent</code> in terms of logical device units relative to the top left of the master %Block at Point (0, 0). Initially, all <code>BlockComponent</code> instances have the <code>topLeft</code> position (0, 0) and the <code>MainComponent::positionBlocks()</code> method iterates first over all of the Blocks connected to the master %Block and then any remaining Blocks and calculates the correct <code>topLeft</code> %Point and <code>rotation</code> for each using the array of %BlockDeviceConnection objects. Then, in the <code>MainComponent::resized()</code> method these attributes are used to correctly position the components.
Array<BlockComponent*> unpositionedBlocks;
for (auto blockComponent : blockComponents)
if (blockComponent != masterBlockComponent && ! blocksConnectedToMaster.contains (blockComponent))
unpositionedBlocks.add (blockComponent);
if (unpositionedBlocks.size() > 0)
{
//...
while (maxDelta > 0.001f && --maxLoops)
{
//...
for (auto blockComponent : unpositionedBlocks)
{
Array<BlockDeviceConnection> blockConnections;
for (auto connection : topology.connections)
if (connection.device1 == blockComponent->block->uid || connection.device2 == blockComponent->block->uid)
blockConnections.add (connection);
for (auto connection : blockConnections)
{
//...
for (auto otherBlockComponent : blockComponents)
{
//...
}
}
}
}
}
}
@endcode
Then, in the @s_projcode{BlocksMonitorDemo::resized()} method these attributes are used to correctly position the components.
@code{.cpp}
void resized() override
{
//...
if (numBlockComponents == 0)
{
//...
return;
}
//...
if (isInitialResized)
{
//...
for (auto blockComponent : blockComponents)
{
auto topLeft = blockComponent->topLeft;
auto rotation = blockComponent->rotation;
//...
}
//...
masterBlockComponent->centreWithSize (...);
//...
}
else
{
masterBlockComponent->setSize (...);
}
for (auto blockComponent : blockComponents)
{
if (blockComponent == masterBlockComponent)
continue;
blockComponent->setBounds (...);
if (blockComponent->rotation != 0)
blockComponent->setTransform (AffineTransform::rotation (...));
}
}
@endcode
@section blocks_monitor_summary Summary
This tutorial and the accompanying code has introduced the %BlockTopology and %Block objects, and demonstrated how to receive callbacks from connected Blocks when the touch surface or buttons are pressed, allowing you to use this input in your own applications.
@section blocks_monitor_see_also See also
- @ref example_blocks_drawing
- @ref example_blocks_synth
*/

+ 211
- 9
extras/BLOCKS/doxygen/pages/juce_example_blocks_synth.dox View File

@@ -1,37 +1,239 @@
/**
@page example_blocks_synth BlocksSynth
In order to compile and run this application you need to first download the <a href="https://www.juce.com/">JUCE framework</a>, which can be obtained from GitHub <a href="https://github.com/julianstorer/JUCE">here</a>.
In order to compile and run this application you need to first download the @jucelink{JUCE framework}, which can be obtained from GitHub @jucegithub{here}.
@section blocks_synth_introduction Introduction
BlocksSynth is a JUCE application that turns your Lightpad into a simple monophonic synthesiser capable of playing 4 different waveshapes - sine, square, sawtooth and triangle.
Navigate to the <tt>JUCE/examples/BLOCKS/BlocksSynth/Builds/</tt> directory and open the code project in your IDE of choice. Run the application and connect your Lightpad (if you do not know how to do this, see @ref connecting_blocks) - it should now display a simple 5x5 grid where each pad plays a note in the chromatic scale using a sine wave starting from the bottom-left (C3). It is possible to play any of the 25 notes but for ease of use tonics (the root note of the scale) are highlighted in white and notes in the C-major scale are highlighted in green. When a note has been played it is possible to change the amplitude using touch pressure and to pitch bend between adjacent notes by sliding left and right. Pressing the mode button on the Lightpad will change to the waveshape selection screen where the currently selected waveshape is rendered on the LEDs and you can switch between the 4 different waveshapes by touching anywhere on the %Block surface.
Generate a Projucer project from the PIP file located in the @s_file{JUCE/examples/BLOCKS/} folder, then navigate to the @s_file{BlocksSynthDemo/Builds/} directory and open the code project in your IDE of choice. Run the application and connect your Lightpad (if you do not know how to do this, see @ref connecting_blocks) - it should now display a simple 5x5 grid where each pad plays a note in the chromatic scale using a sine wave starting from the bottom-left (C3). It is possible to play any of the 25 notes but for ease of use tonics (the root note of the scale) are highlighted in white and notes in the C-major scale are highlighted in green. When a note has been played it is possible to change the amplitude using touch pressure and to pitch bend between adjacent notes by sliding left and right. Pressing the mode button on the Lightpad will change to the waveshape selection screen where the currently selected waveshape is rendered on the LEDs and you can switch between the 4 different waveshapes by touching anywhere on the %Block surface.
The concept of a BLOCKS topology and the methods for receiving callbacks from the Block object are covered in the @ref example_blocks_monitor example and the basic methods for displaying grids and setting LEDs on the %Block are covered in the @ref example_blocks_drawing example. This example will cover how to render custom programs on the LEDGrid using the Littlefoot language and how to do some simple audio synthesis using data from the Lightpad.
@section blocks_synth_note_grid Note Grid
In the synthesiser mode the Lightpad displays a 5x5 grid constructed using the DrumPadGridProgram class. The <code>SynthGrid</code> struct in <tt>MainComponent.h</tt> handles the setup and layout of this grid and sets the colours of the pads to white for tonics, green for notes in the C major scale and black for notes that are not in the C major scale. The <code>ColourGrid::getNoteNumberForPad()</code> method is called in the <code>MainComponent::touchChanged()</code> callback and returns the corresponding MIDI note number for a Touch coordinate on the Lightpad. This note number is then passed to the <code>Audio</code> class to be played on the synthesiser.
In the synthesiser mode the Lightpad displays a 5x5 grid constructed using the DrumPadGridProgram class. The @s_projcode{SynthGrid} struct handles the setup and layout of this grid and sets the colours of the pads to white for tonics, green for notes in the C major scale and black for notes that are not in the C major scale.
\image html BlocksSynth_grid.JPG "Synthesiser note grid"
@code{.cpp}
void constructGridFillArray()
{
gridFillArray.clear();
for (auto i = 0; i < numRows; ++i)
{
for (auto j = 0; j < numColumns; ++j)
{
DrumPadGridProgram::GridFill fill;
auto padNum = (i * 5) + j;
fill.colour = notes.contains (padNum) ? baseGridColour
: tonics.contains (padNum) ? Colours::white
: Colours::black;
fill.fillType = DrumPadGridProgram::GridFill::FillType::gradient;
gridFillArray.add (fill);
}
}
}
@endcode
The @s_projcode{SynthGrid::getNoteNumberForPad()} method is called in the @s_projcode{BlocksSynthDemo::touchChanged()} callback and returns the corresponding MIDI note number for a Touch coordinate on the Lightpad. This note number is then passed to the @s_projcode{Audio} class to be played on the synthesiser.
@code{.cpp}
int getNoteNumberForPad (int x, int y) const
{
auto xIndex = x / 3;
auto yIndex = y / 3;
return 60 + ((4 - yIndex) * 5) + xIndex;
}
@endcode
When the application is run, the synthesiser note grid would look like this:
@image html BlocksSynth_grid.JPG "Synthesiser note grid"
@section blocks_synth_waveshape_display Waveshape Display
In the waveshape selection mode the LEDGrid::Program is set to an instance of the WaveshapeProgram class, which is contained in the <code>WaveshapeProgram.h</code> file. This class inherits from %LEDGrid::Program so that it can be loaded onto the %LEDGrid and its LittleFoot program can be executed on the Lightpad. The class itself is relatively simple and contains a method to set which waveshape should be displayed, a method to load the coordinates for each of the four waveshapes into the heap and two pure virtual methods overridden from %LEDGrid::Program - LEDGrid::Program::getLittleFootProgram() and LEDGrid::Program::getHeapSize(). The heap is the area of shared memory that is used by the program to communicate with the host computer and the size of this memory is set using the <code>getHeapSize()</code> method. In the private section of <code>WaveshapeProgram</code> the structure of the shared data heap is laid out with variables containing the offsets for each section and the <code>totalDataSize</code> variable contains the total size (in bytes) that is required and is returned by the <code>WaveshapeProgram::getHeapSize()</code> method. The heap contains space for a variable that determines which waveshape type to display and the Y coordinates for 1.5 cycles of each of the four waveshapes.
In the waveshape selection mode the Block::Program is set to an instance of the WaveshapeProgram class @s_item{[1]}. This class inherits from %Block::Program so that it can be loaded onto the %LEDGrid and its LittleFoot program can be executed on the Lightpad.
@code{.cpp}
void setLEDProgram (Block& block)
{
if (currentMode == waveformSelectionMode)
{
block.setProgram (new WaveshapeProgram (block)); // [1]
if (auto* waveshapeProgram = getWaveshapeProgram())
{
//...
}
}
//...
}
@endcode
The class itself is relatively simple and contains a method to set which waveshape should be displayed @s_item{[2]}, a method to load the coordinates for each of the four waveshapes into the heap @s_item{[3]} and one pure virtual method overridden from %Block::Program, the Block::Program::getLittleFootProgram() method @s_item{[4]}. The heap is the area of shared memory that is used by the program to communicate with the host computer and the size of this memory is set using the @s_projcode{\#heapsize: XXX} directive where XXX is the number of bytes required @s_item{[5]}.
@code{.cpp}
class WaveshapeProgram : public Block::Program
{
public:
WaveshapeProgram (Block& b) : Program (b) {}
void setWaveshapeType (uint8 type) {...} // [2]
void generateWaveshapes() {...} // [3]
String getLittleFootProgram() override // [4]
{
return R"littlefoot(
#heapsize: 256 // [5]
The <code>WaveshapeProgram::getLittleFootProgram()</code> method returns the LittleFoot program that will be executed on the BLOCKS device. The <code>repaint()</code> method of this program is called at approximately 25Hz and is used to draw the moving waveshape on the LEDs of the Lightpad. Each time this method is called, it clears the LEDs by setting them all to black then calculates the heap offset based on the waveshape type that has been set and uses a <code>for</code> loop to iterate over the 15 LEDs on the X-axis and draw an LED 'circle' using the <code>drawLEDCircle()</code> method at the corresponding Y coordinate for the selected waveshape. The read position of the heap is offset using the <code>yOffset</code> variable which is incremented each <code>repaint()</code> call and wraps back around when the end of the heap section for the selected waveshape is reached to draw a 'moving' waveshape.
//...
\image html BlocksSynth_waveshape.gif "A sine wave dispayed in the waveshape selection mode"
)littlefoot";
}
//...
@endcode
The string literal returned by the @s_projcode{getLittleFootProgram()} function needs to be preceeded by the "R" prefix and enclosed between "littlefoot" delimiters in order to prevent the characters to be escaped in the program.
In the private section of @s_projcode{WaveshapeProgram} the structure of the shared data heap is laid out with variables containing the offsets for each section and the total size (in bytes) that is required can be determined by adding the last set of bytes required to the last offset, which in this case is 136 + 45 = 181. The heap contains space for a variable that determines which waveshape type to display and the Y coordinates for 1.5 cycles of each of the four waveshapes.
@code{.cpp}
static constexpr uint32 waveshapeType = 0; // 1 byte
static constexpr uint32 sineWaveOffset = 1; // 1 byte * 45
static constexpr uint32 squareWaveOffset = 46; // 1 byte * 45
static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45
static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45
@endcode
The @s_projcode{WaveshapeProgram::getLittleFootProgram()} method returns the LittleFoot program that will be executed on the BLOCKS device. The @s_projcode{repaint()} method of this program is called at approximately 25Hz and is used to draw the moving waveshape on the LEDs of the Lightpad.
@code{.cpp}
void repaint()
{
fillRect (0xff000000, 0, 0, 15, 15); // [6]
int type = getHeapByte (0);
int offset = 1 + (type * 45) + yOffset; // [7]
for (int x = 0; x < 15; ++x)
{
int y = getHeapByte (offset + x);
if (y == 255)
{
for (int i = 0; i < 15; ++i)
drawLEDCircle (x, i);
}
else if (x % 2 == 0)
{
drawLEDCircle (x, y); // [8]
}
}
if (++yOffset == 30) // [9]
yOffset = 0;
}
@endcode
Each time this method is called, it clears the LEDs by setting them all to black @s_item{[6]} then calculates the heap offset based on the waveshape type that has been set @s_item{[7]} and uses a @s_code{for()} loop to iterate over the 15 LEDs on the X-axis and draw an LED 'circle' using the @s_projcode{drawLEDCircle()} method at the corresponding Y coordinate for the selected waveshape @s_item{[8]}. The read position of the heap is offset using the @s_projcode{yOffset} variable which is incremented each @s_projcode{repaint()} call and wraps back around when the end of the heap section for the selected waveshape is reached to draw a 'moving' waveshape @s_item{[9]}.
@image html BlocksSynth_waveshape.gif "A sine wave dispayed in the waveshape selection mode"
@section blocks_synth_audio Audio
The <code>Audio</code> class handles the audio synthesis for this application and overrides the AudioIODeviceCallback::audioDeviceIOCallback() method to call the Synthesiser::renderNextBlock() method of a Synthesiser object. This object is initialised to be capable of rendering sine, square, sawtooth and triangle waves on separate MIDI channels in the constructor of <code>Audio</code>, and <code>Audio</code> contains methods for sending note on, note off, channel pressure and pitch wheel messages to the Synthesiser. When a note is triggered on the Lightpad, the <code>Audio::noteOn()</code> method is called with 3 arguments: a MIDI channel corresponding to the waveshape that should be generated, a MIDI note number and an initial velocity. Whilst the note is playing, the amplitude and pitch are modulated by calling the <code>Audio::pressureChange()</code> and <code>Audio::pitchChange()</code> methods from the <code>MainComponent::touchChanged()</code> callback. The pressure value of the Touch instance is used to directly control the Synthesiser amplitude and the distance from the initial note trigger on the X-axis of the Lightpad is scaled to +/-1.0 and used to modulate the frequency of the currently playing note.
The <tt>Oscillators.h</tt> file contains the waveshape rendering code. It contains an <code>Oscillator</code> base class which inherits from SynthesiserVoice and has a pure virtual <code>Oscillator::renderWaveShape()</code> method that is overridden by subclasses to render the 4 different waveshapes.
The @s_projcode{Audio} class handles the audio synthesis for this application and overrides the AudioIODeviceCallback::audioDeviceIOCallback() method to call the Synthesiser::renderNextBlock() method of a Synthesiser object.
@code{.cpp}
void audioDeviceIOCallback (const float** /*inputChannelData*/, int /*numInputChannels*/,
float** outputChannelData, int numOutputChannels, int numSamples) override
{
AudioBuffer<float> sampleBuffer (outputChannelData, numOutputChannels, numSamples);
sampleBuffer.clear();
synthesiser.renderNextBlock (sampleBuffer, MidiBuffer(), 0, numSamples);
}
@endcode
This object is initialised to be capable of rendering sine, square, sawtooth and triangle waves on separate MIDI channels in the constructor of @s_projcode{Audio}, and @s_projcode{Audio} contains methods for sending note on, note off, channel pressure and pitch wheel messages to the Synthesiser.
@code{.cpp}
Audio()
{
//...
synthesiser.clearVoices();
synthesiser.clearSounds();
synthesiser.addVoice (new SineVoice());
synthesiser.addVoice (new SquareVoice());
synthesiser.addVoice (new SawVoice());
synthesiser.addVoice (new TriangleVoice());
synthesiser.addSound (new SineSound());
synthesiser.addSound (new SquareSound());
synthesiser.addSound (new SawSound());
synthesiser.addSound (new TriangleSound());
}
@endcode
When a note is triggered on the Lightpad, the @s_projcode{Audio::noteOn()} method is called with 3 arguments: a MIDI channel corresponding to the waveshape that should be generated, a MIDI note number and an initial velocity.
@code{.cpp}
void noteOn (int channel, int noteNum, float velocity)
{
synthesiser.noteOn (channel, noteNum, velocity);
}
void noteOff (int channel, int noteNum, float velocity)
{
synthesiser.noteOff (channel, noteNum, velocity, false);
}
void allNotesOff()
{
for (auto i = 1; i < 5; ++i)
synthesiser.allNotesOff (i, false);
}
@endcode
Whilst the note is playing, the amplitude and pitch are modulated by calling the @s_projcode{Audio::pressureChange()} and @s_projcode{Audio::pitchChange()} methods from the @s_projcode{BlocksSynthDemo::touchChanged()} callback. The pressure value of the Touch instance is used to directly control the Synthesiser amplitude and the distance from the initial note trigger on the X-axis of the Lightpad is scaled to +/-1.0 and used to modulate the frequency of the currently playing note.
@code{.cpp}
void pressureChange (int channel, float newPressure)
{
synthesiser.handleChannelPressure (channel, static_cast<int> (newPressure * 127));
}
void pitchChange (int channel, float pitchChange)
{
synthesiser.handlePitchWheel (channel, static_cast<int> (pitchChange * 127));
}
@endcode
The @s_projcode{Oscillator} base class contains the waveshape rendering code which inherits from SynthesiserVoice and has a pure virtual @s_projcode{Oscillator::renderWaveShape()} method that is overridden by subclasses to render the 4 different waveshapes.
@code{.cpp}
class OscillatorBase : public SynthesiserVoice
{
public:
//...
virtual bool canPlaySound (SynthesiserSound*) override = 0;
virtual double renderWaveShape (const double currentPhase) = 0;
//...
@endcode
@section blocks_synth_summary Summary
This tutorial and the accompanying code project have expanded on the topics covered by previous tutorials, showing you how to display more complex, custom programs on the %LEDGrid using the LittleFoot language and how to control simple audio synthesis parameters using the Lightpad.
@section blocks_synth_see_also See also
- @ref example_blocks_monitor
- @ref example_blocks_drawing
*/

+ 115
- 0
extras/BLOCKS/doxygen/pages/juce_example_colour_pressure_map.dox View File

@@ -0,0 +1,115 @@
/**
@page example_colour_pressure_map Colour Pressure Map
@section colour_pressure_map_introduction Introduction
Learn how to display coloured pressure maps on the Lightpad Block.
Launch the BLOCKS CODE application and open the script called @s_file{ColourPressureMap.littlefoot}. You can find the script in the Littlefoot-Examples repository, which you can download from GitHub @littlefootgithub{here}. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section @ref getting_started_with_blocks_code for help.
@section colour_pressure_map_drawing_pressure_maps Drawing Pressure Maps
Let's start by drawing white pressure map points onto the screen whenever pressure is applied onto the touch surface of a Lightpad.
We start by clearing the display in the repaint() function using the clearDisplay() function to reset the state of our LEDs. We than call two handy functions that are defined in the littlefoot language that allow us to draw and fade pressure points automatically. These are respectively the drawPressureMap() and fadePressureMap() functions.
@code{.cpp}
void repaint()
{
clearDisplay();
drawPressureMap();
fadePressureMap();
}
@endcode
However, if we run the script at the moment no pressure points are shown on the screen because we need to tell the program where to draw them by calling the addPressurePoint() function with the colour and coordinates of the point.
The littlefoot language has several callback functions that are called when a special event happens. In our case we are interested in the start and movement of a touch gesture and therefore we can implement these as follows:
@code{.cpp}
void touchStart (int index, float x, float y, float z, float vz)
{
addPressurePoint (0xffffff, x, y, z * 100.0);
}
@endcode
When the callback functions are called, we add a pressure point to the pressure map by specifying a colour in hexadecimal (in this case white is 0xffffff) and the coordinates of the touch event.
@code{.cpp}
void touchMove (int index, float x, float y, float z, float vz)
{
addPressurePoint (0xffffff, x, y, z * 20.0);
}
@endcode
Notice here that we multiply the depth z by a scaler in order to make the surface of the pressure point bigger and easier to see.
@section colour_pressure_map_adding_colours Adding Colours
Now let's try to add different colours to the pressure points depending on the number of fingers on the screen. To make these variables accessible in realtime to the Parameters tab of the IDE, we create some variables in the metadata section of the code.
@code{.xml}
<metadata description="Colour Pressure Map">
<variables>
<variable name="pressureColour0" displayName="Heat Colour 0" type="colour" value="0xFF0000" />
<variable name="pressureColour1" displayName="Heat Colour 1" type="colour" value="0x00FF00" />
<variable name="pressureColour2" displayName="Heat Colour 2" type="colour" value="0x0000FF" />
<variable name="pressureColour3" displayName="Heat Colour 3" type="colour" value="0xFF00FF" />
<variable name="scaling" displayName="How hot!" type="float" value="1" min="1" max="200" />
</variables>
</metadata>
@endcode
We also implement a helper function called getPressureColour() that takes an index of the finger from the touch event and returns the desired colour as shown below:
@code{.cpp}
int getPressureColour (int index)
{
int col = pressureColour3;
if (index == 1)
{
col = pressureColour0;
}
else if (index == 2)
{
col = pressureColour1;
}
else if (index == 3)
{
col = pressureColour2;
}
return col;
}
@endcode
Finally we need to modify the callbacks to incorporate this helper function instead of hardcoding the colour white.
@code{.cpp}
void touchStart (int index, float x, float y, float z, float vz)
{
addPressurePoint (getPressureColour (index), x, y, z * float (scaling));
}
@endcode
Notice we also allow the scaling of the pressure point to be controlled by a slider in the Parameters tab in order to change its size in realtime.
@code{.cpp}
void touchMove (int index, float x, float y, float z, float vz)
{
addPressurePoint (getPressureColour (index), x, y, z * float (scaling));
}
@endcode
@section colour_pressure_map_summary Summary
In this example, we learnt how to display pressure maps onto the Lightpad and add colours depending on the number of fingers pressed onto the touch surface.
@section colour_pressure_map_see_also See also
- @ref example_dynamic_parameters
- @ref example_tic_tac_toe
- @ref example_music_gen
*/

+ 65
- 0
extras/BLOCKS/doxygen/pages/juce_example_dynamic_parameters.dox View File

@@ -0,0 +1,65 @@
/**
@page example_dynamic_parameters Dynamic Parameters
@section dynamic_parameters_introduction Introduction
Learn how to use BLOCKS CODE metadata to interact with your BLOCKS using dynamic parameters.
Launch the BLOCKS CODE application and open the script called @s_file{DynamicParameters.littlefoot}. You can find the script in the Littlefoot-Examples repository, which you can download from GitHub @littlefootgithub{here}. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section @ref getting_started_with_blocks_code for help.
@section dynamic_parameters_adding_parameters Adding Parameters
Let's start by adding parameters to the side panel of BLOCKS CODE that will appear under the Parameters tab.
These are created using an XML format enclosed between multiline comment blocks and @s_code{\<metadata\>} tags at the top of the file. Variables are created between @s_code{\<variables\>} tags and they can also be grouped using @s_code{\<groups\>} tags as follows:
@code{.xml}
<metadata description="Dynamic Parameters">
<groups>
<group name="pos" displayName="Position" />
<group name="size" displayName="Size" />
<group name="colour" displayName="Colour" />
</groups>
<variables>
<variable group="pos" name="xPos" type="int" min="0" max="15" value="6" />
<variable group="pos" name="yPos" type="int" min="0" max="15" value="6" />
<variable group="size" name="width" type="int" min="1" max="15" value="3" />
<variable group="size" name="height" type="int" min="1" max="15" value="3" />
<variable group="colour" name="alpha" type="float" min="0" max="255" value="255" />
<variable group="colour" name="red" type="int" min="0" max="255" value="255" />
<variable group="colour" name="green" type="int" min="0" max="255" value="0" />
<variable group="colour" name="blue" type="int" min="0" max="255" value="0" />
<variable name="draw" type="bool" value="true" />
</variables>
</metadata>
@endcode
Within the variable declaration, the string given to the "name" attribute becomes the variable name in the subsequent littlefoot code.
Let's now implement the repaint() function of the littlefoot program. This function is called periodically at approximately 25Hz on the device and is used to control the LEDs on a Lightpad.
In the following piece of code, we first call the clearDisplay() function which will reset the screen to a blank state so that any LEDs that were turned on in the previous iteration of the repaint() function are turned off.
@code{.cpp}
void repaint()
{
clearDisplay();
if (draw)
fillRect (makeARGB (int (alpha), red, green, blue), xPos, yPos, width, height);
}
@endcode
Then we use the fillRect() function to draw a rectangle on the screen using the variables defined before, only if the draw button is checked. The fillRect() function takes as argument the colour retrieved by the makeARGB() helper function and the position and size of the rectangle we wish to draw.
@section dynamic_parameters_summary Summary
In this example, we learnt how to add parameters that can be tweaked at runtime to control the behaviour of a program in realtime.
@section dynamic_parameters_see_also See also
- @ref example_colour_pressure_map
- @ref example_tic_tac_toe
- @ref example_music_gen
*/

+ 18
- 0
extras/BLOCKS/doxygen/pages/juce_example_integrations.dox View File

@@ -0,0 +1,18 @@
/**
@page example_integrations Example BLOCKS Integrations
@section downloading_the_example_integration Downloading the example integration
These example integrations demonstrate the functionality of the standalone BLOCKS SDK.
The example integrations are all distributed as part of the standalone BLOCKS SDK, which can be obtained from GitHub @blocksgithub{here}.
You will find the examples in the @s_file{BLOCKS-SDK/examples/} directory.
Each example comes with projects for Visual Studio, Xcode and Makefile in their respective subdirectory, and these should open, compile and run without any trouble in the respective IDEs.
@section example_integrations_overview Overview
@subpage example_block_finder
BlockFinder is a basic JUCE application that shows currently connected Lightpad and Control %Block devices. It also displays some basic information about the Blocks.
*/

+ 411
- 0
extras/BLOCKS/doxygen/pages/juce_example_music_gen.dox View File

@@ -0,0 +1,411 @@
/**
@page example_music_gen Music Gen
@section music_gen_introduction Introduction
Develop a MIDI note music generator with interesting visuals on the Lightpad Block.
Launch the BLOCKS CODE application and open the script called @s_file{MusicGen.littlefoot}. You can find the script in the Littlefoot-Examples repository, which you can download from GitHub @littlefootgithub{here}. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section @ref getting_started_with_blocks_code for help.
@section music_gen_initial_setup Initial Setup
Let's first start by defining global parameters for our app such as the speed, root note and chord for our music generator like so:
@code{.xml}
<metadata description="Music Gen - Tap to create an emitter, tap again to change its direction. Mode button resets.">
<variables>
<variable name="speed" displayName="Speed" type="float" min="0" max="5" value="1" />
<variable name="rootNote" displayName="Root Note" type="midiNote" value="C1" />
<variable name="chord" displayName="Chord" type="option" value="Major" options="Major;Minor" />
</variables>
</metadata>
@endcode
We also need to define global variables such as colours, bullet direction and coordinates, blob direction and coordinates as well as the last note played and the number of note generators on the screen.
@code{.cpp}
int green;
int red;
int blue;
int yellow;
int count;
float bullet1x;
float bullet1y;
//...
int bullet1d;
//...
int blob1x;
int blob1y;
//...
int blob1d;
//...
int lastNote1;
//...
@endcode
In the initialise() function of our script, we first clear the display and send a MIDI CC message to turn all MIDI notes off thus resetting both the visual and audio states of the app. We also initialise all the variables to default values and set the colours we want to use.
@code{.cpp}
void initialise()
{
clearDisplay();
sendCC (0, 120, 127);
blob1x = -99;
//...
blob1y = -99;
//...
blob1d = 3;
//...
bullet1x = -99;
//...
bullet1y = -99;
//...
green = 0x2200FF00;
red = 0x22FF0000;
blue = 0x220000FF;
yellow = 0x22FFFF00;
count = 0;
}
@endcode
In order to reset the state of the music generator at anytime, we implement the handleButtonDown() callback to initialise the state of the app when the side button of the Lightpad is pressed.
@code{.cpp}
void handleButtonDown (int index)
{
initialise();
}
@endcode
In the repaint() function we first clear the display and perform 4 sequential operations every time the screen is refreshed: paint blobs, draw bullets, update bullets and detect bullets. These functions are each defined later on.
@code{.cpp}
void repaint()
{
clearDisplay();
paintBlob (blob1x, blob1y, blob1d);
//...
drawBullet (bullet1x, bullet1y, blob1d);
//...
updateBullet1();
//...
detectBullet();
}
@endcode
@section music_gen_drawing_blobs_bullets Drawing Blobs and Bullets
Now let's take a look at drawing various elements on the screen. The blobs that start shooting the note bullets are drawn using the following @s_projcode{paintBlob()} function:
@code{.cpp}
void paintBlob (int x, int y, int type)
{
if (type == 0)
{
fillRect (green, x, y - 1, 1, 2);
blendRect (green, x - 1, y, 3, 1);
}
else if (type == 1)
{
fillRect (red, x, y, 2, 1);
blendRect (red, x, y - 1, 1, 3);
}
else if (type == 2)
{
fillRect (blue, x, y, 1, 2);
blendRect (blue, x - 1, y, 3, 1);
}
else if (type == 3)
{
fillRect (yellow, x - 1, y, 2, 1);
blendRect (yellow, x, y - 1, 1, 3);
}
}
@endcode
Depending on the direction of the blob we decide to draw the shapes using different colours and we use the blendRect() function to blend the pixel of the overlapping coordinate. Similarly, we draw the bullets with the @s_projcode{drawBullet()} function depending on the direction of the corresponding blob.
@code{.cpp}
void drawBullet (float x, float y, int d)
{
fillPixel (0xFF222222, int (x), int (y));
if (d == 0)
{
fillPixel (0xFFFFFF, int (x), int (y - 1));
}
else if (d == 1)
{
fillPixel (0xFFFFFF, int (x + 1), int (y));
}
else if (d == 2)
{
fillPixel (0xFFFFFF, int (x), int (y + 1));
}
else
{
fillPixel (0xFFFFFF, int (x - 1), int (y));
}
}
@endcode
@section music_gen_handling_touch_events Handling Touch Events
Up until now, the script would not draw anything on the screen as touch events are not handled yet and default coordinates are set to be out of bounds with the screen coordinates. Let's implement the @s_projcode{touchStart()} callback to process touch events.
@code{.cpp}
void touchStart (int touchIndex, float x, float y, float z, float vz)
{
int intX = int (x * 7);
int intY = int (y * 7);
int touch = touchBlob (intX, intY);
if (touch >= 1)
{
changeBlob (touch);
}
else if (count < 5)
{
if (z < 0.05)
{
assignBlob (intX, intY, count, 0);
}
else if (z < 0.2)
{
assignBlob (intX, intY, count, 1);
}
else if (z < 0.5)
{
assignBlob (intX, intY, count, 2);
}
else
{
assignBlob (intX, intY, count, 3);
}
++count;
}
}
@endcode
In the above function, we first convert the device coordinates into LED grid coordinates by multiplying both x and y variables by 7. Device coordinates are defined using the number of DNA connectors on the side of the device so for example in the case of a Lightpad %Block, the device has a size of 2x2 and therefore the device coordinates will range from 0.0 to 2.0 on each x and y dimensions. Multiplying this range by 7 gives us the LED grid coordinates ranging from 0 to 14 inclusive.
Now using these grid coordinates, we use the @s_projcode{touchBlob()} helper function defined below to check whether the touch event was performed on a previously drawn blob and return its index. If no previous blobs were touched, we return 0 to indicate the creation of a new one.
@code{.cpp}
int touchBlob (int x, int y)
{
int touch = 0;
if (x >= (blob1x - 1) && x <= (blob1x + 1) && y >= (blob1y - 1) && y <= (blob1y + 1))
{
touch = 1;
}
//...
return touch;
}
@endcode
The @s_projcode{changeBlob()} function is called in the touchStart() callback when an existing blob is touched and updates the index for its direction to update the orientation.
@code{.cpp}
void changeBlob (int blob)
{
if (blob == 1)
{
if (blob1d < 3)
{
++blob1d;
}
else
{
blob1d = 0;
}
}
//...
}
@endcode
If the creation of a new blob was requested from the touchStart() callback, depending on the pressure of the touch event we spawn a different type of blob at the specified coordinate and call the corresponding function to spawn a bullet.
@code{.cpp}
void assignBlob (int x, int y, int index, int type)
{
if (index == 0)
{
blob1x = x;
blob1y = y;
blob1d = type;
spawnBullet1();
}
//...
}
@endcode
To spawn a bullet we simply assign the coordinates and direction of the blob to the bullet which overwrites the default values and makes the bullet appear within the screen coordinates.
@code{.cpp}
void spawnBullet1()
{
bullet1x = blob1x;
bullet1y = blob1y;
bullet1d = blob1d;
}
//...
@endcode
The position of the bullet is updated from the repaint() function by incrementing or decrementing the corresponding x or y coordinate by the speed variable defined as an IDE parameter which consequently moves the bullet on the screen.
@code{.cpp}
void updateBullet1()
{
if (blob1d == 0)
{
bullet1y -= speed;
}
else if (blob1d == 1)
{
bullet1x += speed;
}
else if (blob1d == 2)
{
bullet1y += speed;
}
else if (blob1d == 3)
{
bullet1x -= speed;
}
}
//...
@endcode
@section music_gen_generating_midi_messages Generating MIDI Messages
Now that all the visuals are implemented we have to generate some MIDI messages to trigger sounds from the host. The @s_projcode{detectBullet()} function is called periodically in the repaint() function and performs some basic collision detection.
@code{.cpp}
void detectBullet()
{
if (bullet1x > 15)
{
spawnBullet1();
midiNote (0, 0);
}
else if (bullet1x < 0 && bullet1x > -90)
{
spawnBullet1();
midiNote (0, 1);
}
else if (bullet1y > 15)
{
spawnBullet1();
midiNote (0, 2);
}
else if (bullet1y < 0 && bullet1y > -90)
{
spawnBullet1();
midiNote (0, 3);
}
//...
}
@endcode
Here we check if any of the bullets have crossed the screen boundaries and spawn a new bullet when the old bullet becomes off-screen. Notice here we make sure the default value of -99 defined in the initialise() function does not trigger the spawning of a bullet. We also generate a MIDI note by calling the helper function @s_projcode{midiNote()} defined hereafter:
@code{.cpp}
void midiNote(int note1, int note2)
{
note2 *= 12;
int note = rootNote;
if (note1 == 0)
{
note += note2;
note1 (note);
}
else if (note1 == 1)
{
if (chord == 0)
{
note += 4;
}
else
{
note += 3;
}
note += note2;
note2 (note);
}
else if (note1 == 2)
{
note += 7;
note += note2;
note3 (note);
}
else if (note1 == 3)
{
if (chord == 0)
{
note += 11;
}
else
{
note += 10;
}
note += note2;
note4 (note);
}
else if (note1 == 4)
{
note += 14;
note += note2;
note5 (note);
}
}
@endcode
In order to play a harmonious set of MIDI notes, the above function generates specific notes that form the chord with a root note and major/minor quality as defined in the parameters. It follows a simple set of rules as follows:
- The index of the blob defines the note in the scale in ascending order: tonic, major or minor third, fifth, major or minor seventh, ninth.
- The direction of the blob defines the octave of the note in ascending order: east, west, south, north.
- If the chord parameter is set to major, the major third and major seventh are selected forming a major seventh chord.
- If the chord parameter is set to minor, the minor third and minor seventh are selected forming a minor seventh chord.
The selected note is then passed to the following helper function in order to stop the previously ringing note and start a new one using respectively the sendNoteOff() and sendNoteOn() functions with the channel number, the note number and the note velocity as arguments.
@code{.cpp}
void note1 (int note)
{
sendNoteOff (0, lastNote1, 80);
sendNoteOn (0, note, 80);
lastNote1 = note;
}
//...
@endcode
@section music_gen_summary Summary
In this example, we learnt how to create a music generator app that sends MIDI messages to a host.
@section music_gen_see_also See also
- @ref example_dynamic_parameters
- @ref example_colour_pressure_map
- @ref example_tic_tac_toe
*/

+ 34
- 0
extras/BLOCKS/doxygen/pages/juce_example_scripts.dox View File

@@ -0,0 +1,34 @@
/**
@page example_scripts Example LittleFoot Scripts
@section downloading_the_example_script Downloading the example script
These example scripts demonstrate the functionality of the LittleFoot language.
The example scripts are self-contained and do not rely on the standalone BLOCKS SDK or the JUCE library.
You will find the examples in the Littlefoot-Examples repository, which you can download from GitHub @littlefootgithub{here}.
Each example comes with a @s_file{.littlefoot} file that can be loaded into the BLOCKS CODE IDE and uploaded onto BLOCKS.
@section example_scripts_overview Overview
@subpage example_bitmap_led_program
The %BitmapLEDProgram class contains a simple LittleFoot program that sets the LEDs of a Lightpad from the heap's content.
@subpage example_dynamic_parameters
Learn how to use BLOCKS CODE metadata to interact with your BLOCKS using dynamic parameters.
@subpage example_colour_pressure_map
Learn how to display coloured pressure maps on the Lightpad %Block.
@subpage example_tic_tac_toe
Implement the classic game of Tic Tac Toe on the Lightpad %Block.
@subpage example_music_gen
Develop a MIDI note music generator with interesting visuals on the Lightpad %Block.
*/

+ 284
- 0
extras/BLOCKS/doxygen/pages/juce_example_tic_tac_toe.dox View File

@@ -0,0 +1,284 @@
/**
@page example_tic_tac_toe Tic Tac Toe
@section tic_tac_toe_introduction Introduction
Implement the classic game of Tic Tac Toe on the Lightpad Block.
Launch the BLOCKS CODE application and open the script called @s_file{TicTacToe.littlefoot}. You can find the script in the Littlefoot-Examples repository, which you can download from GitHub @littlefootgithub{here}. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section @ref getting_started_with_blocks_code for help.
@section tic_tac_toe_drawing_grid Drawing the Grid
Let's start by defining global variables to save the current state of the game. To define global variables in LittleFoot simply place your variables outside any function declaration and at the top of the file for convenience. We first have 9 variables for the state of each cell in the grid, a @s_projcode{currentTurn} variable for the current player's turn and a @s_projcode{winner} variable to determine which player is the winner.
@code{.cpp}
int tl, t, tr;
int ml, m, mr;
int bl, b, br;
int currentTurn;
int winner;
@endcode
The initialise() function in the littlefoot language is useful to initialise variables and the state of your script as the function is called once when the program is loaded onto the device. Here we set all the cells to 0, a number chosen to define the blank state of a cell, the current turn to player 1 and the winner to 0, again a number chosen to define the ongoing state of the game.
@code{.cpp}
void initialise()
{
tl = 0;
t = 0;
tr = 0;
ml = 0;
m = 0;
mr = 0;
bl = 0;
b = 0;
br = 0;
currentTurn = 1;
winner = 0;
}
@endcode
In the repaint() function, we first clear the display as usual and check if a winner has been selected. If not, this means the game is still ongoing and we proceed to draw a grid and signs if there are any. Otherwise we draw the winner screen.
@code{.cpp}
void repaint()
{
clearDisplay();
if (winner == 0)
{
drawGrid();
drawSigns();
}
else
{
drawWinner();
}
}
@endcode
In order to draw the default grid, we have created a helper function called drawGrid() which is called in the previously defined repaint() function. We first define a variable to the colour white and proceed to draw a rectangle if the cell is still unoccupied.
@code{.cpp}
void drawGrid()
{
int white = makeARGB (255, 200, 200, 200);
if (tl == 0) drawRect (white, 0, 0, 5, 5);
if (t == 0) drawRect (white, 5, 0, 5, 5);
if (tr == 0) drawRect (white, 10, 0, 5, 5);
if (ml == 0) drawRect (white, 0, 5, 5, 5);
if (m == 0) drawRect (white, 5, 5, 5, 5);
if (mr == 0) drawRect (white, 10, 5, 5, 5);
if (bl == 0) drawRect (white, 0, 10, 5, 5);
if (b == 0) drawRect (white, 5, 10, 5, 5);
if (br == 0) drawRect (white, 10, 10, 5, 5);
}
@endcode
This is performed on each cell using the helper function drawRect() defined below which takes as argument a colour, the coordinates and the size of the rectangle.
@code{.cpp}
void drawRect (int colour, int x, int y, int w, int h)
{
fillRect (colour, x, y, 1, h);
fillRect (colour, x, y, w, 1);
fillRect (colour, x + w - 1, y, 1, h);
fillRect (colour, x, y + h - 1, w, 1);
}
@endcode
Notice here we use the built-in fillRect() function with either a width or height of 1 to draw lines for the four sides of the rectangle because we don't want the rectangle to be filled.
@section tic_tac_toe_drawing_signs Drawing the Signs
Now let's see how we can draw the player signs if any of the cells are occupied. The drawSigns() function called in the repaint() loop checks every cell using the evaluate() helper function defined after. It passes the cell variables along with the coordinates in case we need to draw the signs.
@code{.cpp}
void drawSigns()
{
evaluate (tl, 0, 0);
evaluate (t , 5, 0);
evaluate (tr, 10, 0);
evaluate (ml, 0, 5);
evaluate (m, 5, 5);
evaluate (mr, 10, 5);
evaluate (bl, 0, 10);
evaluate (b, 5, 10);
evaluate (br, 10, 10);
}
@endcode
The evaluate() function below checks if the cell variable has been switched to a player index of 1 or 2 in which case we need to draw the corresponding player sign. If the cell variable is still set to 0 this means that the cell is still blank and we keep the white default square.
@code{.cpp}
void evaluate (int value, int x, int y)
{
if (value == 1)
drawX (x, y);
else if (value == 2)
drawO (x, y);
}
@endcode
To draw player 1's "X" sign, we first define the player colour and draw each point in the two diagonals as follows:
@code{.cpp}
void drawX (int x, int y)
{
int xColour = makeARGB (255, 0, 255, 0);
fillRect (xColour, x, y, 1, 1);
fillRect (xColour, x + 1, y + 1, 1, 1);
fillRect (xColour, x + 2, y + 2, 1, 1);
fillRect (xColour, x + 3, y + 3, 1, 1);
fillRect (xColour, x + 4, y + 4, 1, 1);
fillRect (xColour, x + 4, y, 1, 1);
fillRect (xColour, x + 3, y + 1, 1, 1);
fillRect (xColour, x + 2, y + 2, 1, 1);
fillRect (xColour, x + 1, y + 3, 1, 1);
fillRect (xColour, x, y + 4, 1, 1);
}
@endcode
To draw player 2's "O" sign, we first define the player colour and draw the four sides like so:
@code{.cpp}
void drawO (int x, int y)
{
int oColour = makeARGB (255, 0, 0, 255);
fillRect (oColour, x, y, 5, 1);
fillRect (oColour, x, y + 4, 5, 1);
fillRect (oColour, x, y, 1, 5);
fillRect (oColour, x + 4, y, 1, 5);
}
@endcode
@section tic_tac_toe_handling_touch_events Handling Touch Events
In its current implementation state, our script won't receive any touch events and therefore the game will stay in the blank state with a white grid showing. Let's implement the touchStart() callback to receive player moves.
@code{.cpp}
void touchStart (int index, float x, float y, float z, float vz)
{
int xPos = int (x * 7);
int yPos = int (y * 7);
int index = getIndex (xPos, yPos);
if (setValueForIndex (index, currentTurn))
{
currentTurn = currentTurn == 1 ? 2 : 1;
}
if (xHasWon()) winner = 1;
else if (oHasWon()) winner = 2;
}
@endcode
In the above function, we first convert the device coordinates into LED grid coordinates by multiplying both x and y variables by 7. Device coordinates are defined using the number of DNA connectors on the side of the device so for example in the case of a Lightpad %Block, the device has a size of 2x2 and therefore the device coordinates will range from 0.0 to 2.0 on each x and y dimensions. Multiplying this range by 7 gives us the LED grid coordinates ranging from 0 to 14 inclusive.
Now using these grid coordinates, we use the @s_projcode{getIndex()} helper function defined below to retrieve the cell index ranging from 0 to 8 from our 3x3 game grid.
@code{.cpp}
int getIndex (int x, int y)
{
int xInd = x / 5;
int yInd = y / 5;
return yInd * 3 + xInd;
}
@endcode
When the index is retrieved we attempt to set a cell variable value only if the touched cell is unoccupied. If a value has been successfully set for the current player, the function returns true and the @s_projcode{currentTurn} variable is switched to the other player.
@code{.cpp}
bool setValueForIndex (int index, int value)
{
if (index == 0) { tl = tl == 0 ? value : tl; return true; }
if (index == 1) { t = t == 0 ? value : t ; return true; }
if (index == 2) { tr = tr == 0 ? value : tr; return true; }
if (index == 3) { ml = ml == 0 ? value : ml; return true; }
if (index == 4) { m = m == 0 ? value : m ; return true; }
if (index == 5) { mr = mr == 0 ? value : mr; return true; }
if (index == 6) { bl = bl == 0 ? value : bl; return true; }
if (index == 7) { b = b == 0 ? value : b ; return true; }
if (index == 8) { br = br == 0 ? value : br; return true; }
return false;
}
@endcode
@section tic_tac_toe_determining_winner Determining a Winner
We were able to implement the basic gameplay but we don't have a mechanism to select a winner yet. As a last step to the touchStart() callback, we check if a winner has been selected by calling respectively the @s_projcode{xHasWon()} and @s_projcode{oHasWon()} helper functions defined as follows:
@code{.cpp}
bool xHasWon() { return hasWon (1); }
bool oHasWon() { return hasWon (2); }
@endcode
These in turn call the @s_projcode{hasWon()} function with the player number which checks all 8 possible combination of winning lines (3 horizontal, 3 vertical and 2 diagonal).
@code{.cpp}
bool hasWon (int player)
{
return (tl == player && t == player && tr == player)
|| (ml == player && m == player && mr == player)
|| (bl == player && b == player && br == player)
|| (tl == player && ml == player && bl == player)
|| (t == player && m == player && b == player)
|| (tr == player && mr == player && br == player)
|| (tl == player && m == player && br == player)
|| (tr == player && m == player && bl == player);
}
@endcode
If a winner is selected the repaint() function will call the @s_projcode{drawWinner()} function defined below and fill the screen with the appropriate winner's colour:
@code{.cpp}
void drawWinner()
{
if (winner == 1)
{
int xColour = makeARGB (255, 0, 255, 0);
fillRect (xColour, 0, 0, 15, 15);
}
else if (winner == 2)
{
int oColour = makeARGB (255, 0, 0, 255);
fillRect (oColour, 0, 0, 15, 15);
}
}
@endcode
As a final step we can implement a simple way to reset the game back to the beginning by calling the initialise() function ourselves when the side button of the device is pressed. This is achieved by implementing the handleButtonDown() callback function like this:
@code{.cpp}
void handleButtonDown (int index)
{
initialise();
}
@endcode
@section tic_tac_toe_summary Summary
In this example, we learnt how to recreate the classic game of Tic Tac Toe on the Lightpad %Block.
@section tic_tac_toe_see_also See also
- @ref example_dynamic_parameters
- @ref example_colour_pressure_map
- @ref example_music_gen
*/

+ 59
- 7
extras/BLOCKS/doxygen/pages/juce_getting_control_button_events.dox View File

@@ -3,37 +3,89 @@
Control button events are communicated from Lightpad and Control Blocks to your application via ControlButton objects.
You can obtain an array of %ControlButton pointers associated with a specific Lightpad or Control %Block from its corresponding Block object using the Block::getButtons method---see the @ref discovering_blocks page for an example of how to obtain %Block objects.
You can obtain an array of %ControlButton pointers associated with a specific Lightpad or Control %Block from its corresponding Block object using the Block::getButtons() method --- see the @ref discovering_blocks page for an example of how to obtain %Block objects.
Each pointer to a %ControlButton will be valid for the lifetime of the %Block object.
Once you have a %ControlButton you must register as a ControlButton::Listener to receive button pressed and button released callbacks.
The process for doing this is to have one of your application's classes inherit from %ControlButton::Listener and override the pure virtual methods ControlButton::Listener::buttonPressed and ControlButton::Listener::buttonReleased.
The process for doing this is to have one of your application's classes inherit from %ControlButton::Listener and override the pure virtual methods ControlButton::Listener::buttonPressed() and ControlButton::Listener::buttonReleased().
Then, when you register your derived class as a listener to a particular %ControlButton, your overriden methods will be called when the corresponding button is pressed and released.
Registering a class derived from %ControlButton::Listener with multiple %ControlButton objects is done as follows:
@code{.cpp}
class ControlButtonListenerExample : public ControlButton::Listener
{
public:
ControlButtonListenerExample (Block& block)
{
for (auto button : block->getButtons())
for (auto button : block.getButtons())
button->addListener (this);
}
virtual void buttonPressed (ControlButton&, sourceControlButton, Block::Timestamp timestamp) override
void buttonPressed (ControlButton& sourceControlButton, Block::Timestamp timestamp) override
{
// Do something when the sourceControlButton is pressed!
}
virtual void buttonReleased (ControlButton&, sourceControlButton, Block::Timestamp timestamp) override
void buttonReleased (ControlButton& sourceControlButton, Block::Timestamp timestamp) override
{
// Do something when the sourceControlButton is released!
}
};
@endcode
When your overriden <code>buttonPressed</code> or <code>buttonReleased</code> methods are called you have access to two paramters: a reference to the %ControlButton that generated this event and timestamp for the event.
When your overriden @s_projcode{buttonPressed()} or @s_projcode{buttonReleased()} methods are called you have access to two parameters: a reference to the %ControlButton that generated this event and timestamp for the event.
@section getting_control_button_events_example_usage Example usage
To add this functionality to the BlockFinder example project, add %ControlButton::Listener as a base class to the BlockFinder class and override the @s_projcode{buttonPressed()} and @s_projcode{buttonReleased()} functions as follows:
@code{.cpp}
class BlockFinder : private TopologySource::Listener,
private ControlButton::Listener
{
//...
private:
//...
void buttonPressed (ControlButton& sourceControlButton, Block::Timestamp timestamp) override
{
Logger::writeToLog ("Button Pressed!");
}
void buttonReleased (ControlButton& sourceControlButton, Block::Timestamp timestamp) override
{
Logger::writeToLog ("Button Released!");
}
//...
};
@endcode
Then in the @s_projcode{topologyChanged()} callback, add the BlockFinder class as a listener to all the buttons on connected Blocks in the current topology to receive button pressed and button released callbacks as shown below:
@code{.cpp}
void topologyChanged() override
{
//...
for (auto& block : currentTopology.blocks)
{
//...
for (auto button : block->getButtons())
button->addListener (this);
}
}
@endcode
If you run the application now and connect a Lightpad or Control %Block, you should see text in the logger whenever buttons are pressed or released.
You can also find multiple examples of control button listeners in the @ref example_applications pages.
Learn more about other Block methods from the following pages:
@ref getting_touch_events
@ref controlling_led_grids
@ref controlling_led_strips
You will find multiple examples of control button listeners in the @ref example_applications pages.
@ref controlling_control_buttons
*/

+ 40
- 0
extras/BLOCKS/doxygen/pages/juce_getting_started_with_blocks_code.dox View File

@@ -0,0 +1,40 @@
/**
@page getting_started_with_blocks_code Getting started with BLOCKS CODE
@section downloading_blocks_code Downloading BLOCKS CODE
In order to facilitate the development of simple BLOCKS applications using the LittleFoot language, BLOCKS CODE IDE was created to provide a self-contained solution that compiles and uploads your code automatically onto BLOCKS devices.
BLOCK CODE is available for macOS and Windows and you can download the IDE @blockscode{here}.
@section ide_interface The IDE interface
After having installed the software, launch the application and you should see a main window pop up, similar to the one shown here:
@image html blocks_code_interface.png "The BLOCKS CODE interface"
BLOCKS CODE presents a simple interface with a code editor that highlights the syntax of the LittleFoot language and provides a side panel to browse properties relating to the project. It is divided into four tabs as follows:
@image html blocks_code_tabs.png "Side panel tabs"
@subsection ide_interface_project The Project tab
The Project tab lists all the LittleFoot programs that are currently open and by clicking on any of them, you can instantly upload the code to the device (if there is a device connected). This also selects the code that can be edited in the code editor and displays any unsaved file with a red file icon.
@subsection ide_interface_targets The Targets tab
The Targets tab lists all the connected BLOCKS devices to the computer, whether it be via USB or Bluetooth. For every device connected, you have the option to rename the device name, choose it as a target or interact with it. When a program is opened, it is automatically uploaded to all the BLOCKS designated as targets and multiple devices can be selected as targets. However, only one device can be selected to be interacted with and this allows you to change the parameters displayed in the next tab.
@subsection ide_interface_parameters The Parameters tab
The Parameters tab displays all the available parameters for the selected %Block in the Targets tab that can be interacted with. This includes parameters created via the \<metadata\> tag or any configuration flag enabled at runtime. These parameters can be tweaked at runtime in order to dynamically control variables in the program uploaded onto the %Block.
@subsection ide_interface_logging The Logging tab
The Logging tab displays values that are logged using the @s_projcode{log()} function in the LittleFoot program. If multiple %Block devices are logging values at the same time, the logs are prefixed by the four-character device ID.
@section blocks_code_language The LittleFoot language
For a brief introduction to the LittleFoot language, please refer to @ref the_littlefoot_language page or jump straight into the @ref example_scripts to get coding.
*/

+ 55
- 8
extras/BLOCKS/doxygen/pages/juce_getting_touch_events.dox View File

@@ -3,33 +3,80 @@
Touch events are communicated from BLOCKS devices to your application code via TouchSurface objects.
You can obtain a pointer to the %TouchSurface associated with a specific BLOCKS device from its corresponding Block object using the Block::getTouchSurface() method---see the @ref discovering_blocks page for an example of how to obtain %Block objects.
For devices without a touch surface (such as the Control Block) this method will return <code>nullptr</code>, but if the device is capable of sending touch events then the pointer to the %TouchSurface will be valid for the lifetime of the %Block object.
You can obtain a pointer to the %TouchSurface associated with a specific BLOCKS device from its corresponding Block object using the Block::getTouchSurface() method --- see the @ref discovering_blocks page for an example of how to obtain %Block objects.
For devices without a touch surface (such as the Control %Block) this method will return @s_code{nullptr}, but if the device is capable of sending touch events then the pointer to the %TouchSurface will be valid for the lifetime of the %Block object.
Once you have a %TouchSurface you must register as a TouchSurface::Listener to get touch events.
The process for doing this is to have one of your application's classes inherit from %TouchSurface::Listener and override the pure virtual method TouchSurface::Listener::touchChanged.
The process for doing this is to have one of your application's classes inherit from %TouchSurface::Listener and override the pure virtual method TouchSurface::Listener::touchChanged().
Then, when you register your derived class as a listener to a particular %TouchSurface, your overriden method will be called when the corresponding device is touched.
A safe way of registering a class derived from %TouchSurface::Listener with a %TouchSurface is as follows.
@code{.cpp}
class TouchSurfaceListenerExample : public TouchSurface::Listener
{
public:
TouchSurfaceListenerExample (Block& block)
{
if (auto touchSurface = block->getTouchSurface())
if (auto touchSurface = block.getTouchSurface())
touchSurface->addListener (this);
}
virtual void touchChanged (TouchSurface& sourceTouchSurface, const TouchSurface::Touch& touchEvent) override
void touchChanged (TouchSurface& sourceTouchSurface, const TouchSurface::Touch& touchEvent) override
{
// Do something with touchEvent here!
}
};
@endcode
When your overriden <code>touchChanged</code> method is called you have access to two paramters: a reference to the %TouchSurface that generated this event and a reference to a TouchSurface::Touch.
The %TouchSurface::Touch class contains member variables describing the postion, pressure, velocity, timestamp and more.
When your overriden @s_projcode{touchChanged()} method is called you have access to two parameters: a reference to the %TouchSurface that generated this event and a reference to a TouchSurface::Touch.
The %TouchSurface::Touch class contains member variables describing the position, pressure, velocity, timestamp and more.
@section getting_touch_events_example_usage Example usage
To add this functionality to the BlockFinder example project, add %TouchSurface::Listener as a base class to the BlockFinder class and override the @s_projcode{touchChanged()} function as follows:
@code{.cpp}
class BlockFinder : private TopologySource::Listener,
private TouchSurface::Listener
{
//...
private:
//...
void touchChanged (TouchSurface& sourceTouchSurface, const TouchSurface::Touch& touchEvent) override
{
Logger::writeToLog ("Touch Changed!");
}
//...
};
@endcode
Then in the @s_projcode{topologyChanged()} callback, add the BlockFinder class as a listener to the connected Blocks in the current topology to receive touch change callbacks as shown below:
@code{.cpp}
void topologyChanged() override
{
//...
for (auto& block : currentTopology.blocks)
{
//...
if (auto touchSurface = block->getTouchSurface())
touchSurface->addListener (this);
}
}
@endcode
If you run the application now and connect a Lightpad, you should see text in the logger whenever the surface is touched.
You can also find multiple examples of using TouchSurfaces in the @ref example_applications pages.
Learn more about other Block methods from the following pages:
@ref getting_control_button_events
@ref controlling_led_grids
@ref controlling_led_strips
You will find multiple examples of using TouchSurfaces in the @ref example_applications pages.
@ref controlling_control_buttons
*/

+ 40
- 20
extras/BLOCKS/doxygen/pages/juce_main.dox View File

@@ -2,64 +2,84 @@
@mainpage Documentation
Welcome to the BLOCKS SDK documentation.
<br>
@s_break
Here you will find all the information required to start creating BLOCKS applications.
A brief summary of the main sections is below.
<br>
@s_break
@subpage downloading_the_sdk
This section describes how to obtain the SDK source code, either via the <a href="https://github.com/julianstorer/JUCE">JUCE framework</a> or @ref the_standalone_blocks_sdk.
This section describes how to get started writing LittleFoot scripts and how to obtain the SDK source code, either via the @jucegithub{JUCE framework} or @ref the_standalone_blocks_sdk.
<br>
@s_break
@subpage connecting_blocks
Lightpad and Control Blocks communicate with a computer over USB-C or Bluetooth, and communicate with each other via magnetic connections on their sides.
This section contains instructions for configuring your setup so that all the components can communicate with each other.
<br>
@s_break
@subpage discovering_blocks
Once you have connected your device to your computer you need to be able to discover it from your application.
This section outlines the procedure for Lightpad and Control %Block discovery and provides some simple example code which monitors for new connections.
<br>
@s_break
@subpage getting_touch_events
This section explains how to capture touch events from a compatible device and, building on the @ref discovering_blocks section, displays some example code.
<br>
@s_break
@subpage getting_control_button_events
Lightpad and Control Blocks have control buttons, either a mode button on their side or labelled buttons on top, and this section shows you how to obtain button pressed and button released events.
<br>
@s_break
@subpage controlling_led_grids
This section explains how to control the LED grid on a Lightpad.
<br>
@subpage the_littlefoot_language
Advanced SDK users can specify specialised programs to run on Lightpad Blocks.
These programs must be written in the LittleFoot language, which is described
in this section.
<br>
@s_break
@subpage controlling_led_strips
Control Blocks have a strip of lights running along one side and this section provides instructions for controling the individual LEDs.
Control Blocks have a strip of lights running along one side and this section provides instructions for controlling the individual LEDs.
<br>
@s_break
@subpage controlling_control_buttons
As well as providing button pressed and button released events, control buttons also have LEDs.
This section explains how to change the colour of different buttons.
<br>
@s_break
@subpage getting_started_with_blocks_code
Learn how to use the BLOCKS CODE IDE to develop LittleFoot programs that run on BLOCKS devices.
@s_break
@subpage the_littlefoot_language
Advanced SDK users can specify specialised programs to run on Lightpad Blocks.
These programs must be written in the LittleFoot language, which is described
in this section.
@s_break
@subpage the_standalone_blocks_sdk
The easiest way to get started using the SDK is via the <a href="https://github.com/julianstorer/JUCE">JUCE framework</a>, but if you want to integrate BLOCKS functionality into your existing application then it may be more convenient to use @ref the_standalone_blocks_sdk.
The easiest way to get started using the SDK is via the @jucegithub{JUCE framework}, but if you want to integrate BLOCKS functionality into your existing application then it may be more convenient to use @ref the_standalone_blocks_sdk.
This section gives an overview of building and using the BLOCKS SDK as a library.
@s_break
@subpage example_scripts
This section provides examples of LittleFoot scripts that can be loaded onto the BLOCKS hardware.
@s_break
@subpage example_integrations
This section gives an example of how to integrate BLOCKS features into an existing application.
@s_break
@subpage example_applications
This section contains examples of BLOCKS applications that make use of the full potential of the JUCE library.
*/

+ 2
- 14
extras/BLOCKS/doxygen/pages/juce_the_littlefoot_language.dox View File

@@ -3,21 +3,9 @@
@section littlefoot_description A description of the LittleFoot language
A description of the LittleFoot language is contained in the SDK source code at <tt>juce_blocks_basics/littlefoot/LittleFoot Language README.txt</tt>:
A description of the LittleFoot language is contained in the SDK source code at @s_file{juce_blocks_basics/littlefoot/LittleFoot Language README.txt}:
@includedoc "LittleFoot Language README.txt"
@section littlefoot_example A LittleFoot example
You can find example scripts written using the LittleFoot language in the @ref example_scripts section and a more comprehensive list of @ref LittleFootFunctions in the JUCE library documentation.
The %BitmapLEDProgram class is a simple example of a LittleFoot program.
<tt>%juce_blocks_basics/visualisers/juce_BitmapLEDProgram.h</tt>
@include juce_blocks_basics/visualisers/juce_BitmapLEDProgram.h
<tt>juce_blocks_basics/visualisers/juce_BitmapLEDProgram.cpp</tt>
@include juce_blocks_basics/visualisers/juce_BitmapLEDProgram.cpp
The repaint() method of the LittleFoot program is called at approximately 25 Hz, and each time it simply inspects the heap (the shared area of memory used to communicate between your application code and your LittleFoot program) and sets the LEDs based on the heap's content.
To update the heap, and hence the LEDS, your application code calls BitmapLEDProgram::setLED.
A more advanced example can be found in the source code of the DrumPadGridProgram class or in the @ref example_blocks_synth example.
*/

+ 10
- 31
extras/BLOCKS/doxygen/pages/juce_the_standalone_blocks_sdk.dox View File

@@ -1,7 +1,7 @@
/**
@page the_standalone_blocks_sdk The standalone BLOCKS SDK
The easiest way to get started developing BLOCKS applications is to use <a href="https://github.com/julianstorer/JUCE">the JUCE framework</a>, but if you would prefer not to use JUCE directly the standalone BLOCKS SDK can be obtained from the <a href="https://github.com/WeAreROLI/BLOCKS-SDK">BLOCKS-SDK repository</a>.
The easiest way to get started developing BLOCKS applications is to use the @jucegithub{JUCE framework}, but if you would prefer not to use JUCE directly the standalone BLOCKS SDK can be obtained from the @blocksgithub{BLOCKS-SDK repository}.
The most straightforward way to use the SDK is to compile the SDK source code into a static library.
Then, in your BLOCKS application code, you can use the header files in the SDK to give you access to the BLOCKS classes and functions.
@@ -9,51 +9,30 @@ Finally, when you want to compile your application, you must link against the st
@section standalone_building_library Building the SDK library
The source code for the BLOCKS SDK library is contained within the <tt>SDK</tt> directory of the BLOCKS-SDK repository.
Here you will find header files that you can include in your own projects and the <tt>Build</tt> subdirectory contains an Xcode project, a Visual Studio project and a Linux Makefile for compiling the SDK source code into a static library.
The source code for the BLOCKS SDK library is contained within the @s_file{SDK} directory of the BLOCKS-SDK repository.
Here you will find header files that you can include in your own projects and the @s_file{Build} subdirectory contains an Xcode project, a Visual Studio project and a Linux Makefile for compiling the SDK source code into a static library.
Use the appropriate choice for your platform, select either the "Debug" or "Release" configuration, and build the project.
For MacOS this will produce <tt>libBLOCKS-SDK.a</tt> in either the <tt>SDK/Build/MacOS/Debug/</tt> or <tt>SDK/Build/MacOS/Release/</tt> directory, for Linux this will produce <tt>libBLOCKS-SDK.a</tt> in either the <tt>SDK/Build/Linux/Debug/</tt> or <tt>SDK/Build/Linux/Release/</tt> directory, and for Windows this will produce <tt>BLOCKS-SDK.lib</tt> in either the <tt>SDK\\Build\\Windows\\x64\\Debug</tt> or <tt>SDK\\Build\\Windows\\x64\\Release</tt> folder.
For macOS this will produce @s_file{libBLOCKS-SDK.a} in either the @s_file{SDK/Build/MacOS/Debug/} or @s_file{SDK/Build/MacOS/Release/} directory, for Linux this will produce @s_file{libBLOCKS-SDK.a} in either the @s_file{SDK/Build/Linux/Debug/} or @s_file{SDK/Build/Linux/Release/} directory, and for Windows this will produce @s_file{BLOCKS-SDK.lib} in either the @s_file{SDK\\Build\\Windows\\x64\\Debug} or @s_file{SDK\\Build\\Windows\\x64\\Release} folder.
@section standalone_using_header Using the SDK header file
To use BLOCKS classes and functions in your application you must include the <tt>BlocksHeader.h</tt> file in your source code.
You also need to tell the compiler to look in the <tt>SDK</tt> directory for additional header files, which you can configure inside your Xcode or Visual Studio project.
If you are using the command line to compile your application then you can see an example of how to do this in <tt>examples/BLOCKS-SDK/BlockFinder/Linux/Makefile</tt> (which is also configured for MacOS, despite being located inside the Linux directory).
To use BLOCKS classes and functions in your application you must include the @s_file{BlocksHeader.h} file in your source code.
You also need to tell the compiler to look in the @s_file{SDK} directory for additional header files, which you can configure inside your Xcode or Visual Studio project.
If you are using the command line to compile your application then you can see an example of how to do this in @s_file{examples/BlockFinder/Linux/Makefile} or @s_file{examples/BlockFinder/MacOS/Makefile}.
@section standalone_linking Linking against the SDK library
You must also tell your compiler where to find the SDK static library before your BLOCKS application will compile, and include all of the dependencies for your platform, which are listed in the @ref standalone_dependencies section.
Again, this is configured in your Xcode or Visual Studio project, but if you are using the command line you can see an example of how to do this in <tt>examples/BLOCKS-SDK/BlockFinder/Linux/Makefile</tt> (which, again, is also configured for MacOS).
Again, this is configured in your Xcode or Visual Studio project, but if you are using the command line you can see an example of how to do this in @s_file{examples/BlockFinder/Linux/Makefile} or @s_file{examples/BlockFinder/MacOS/Makefile}.
@section standalone_example An example application
The source code for this example can be found in the <a href="https://github.com/WeAreROLI/BLOCKS-SDK">BLOCKS-SDK repository</a> at <tt>examples/BlockFinder/</tt>, with the parts that are specific to different operating systems in the corresonding subdirectories.
The main functionality of the application is contained within the following class:
<tt>BlockFinder/BlockFinder.h</tt>:
@include BlockFinder/BlockFinder.h
<tt>BlockFinder/BlockFinder.cpp</tt>:
@include BlockFinder/BlockFinder.cpp
All this class does is create a PhysicalTopologySource and register for TopologySource::Listener::topologyChanged() callbacks---for more information about how this works you should see the @ref discovering_blocks section.
When the topology changes we print some information about the available BLOCKS.
The <tt>main</tt> function of the MacOS application is the easiest to understand.
<tt>BlockFinder/MacOS/main.mm</tt>:
@include BlockFinder/MacOS/main.mm
Here we simply perform some JUCE initialisation, instantiate a BlockFinder class, then run the event loop.
Whilst in the event loop, the <tt>finder</tt> object receives TopologySource::Listener::topologyChanged() callbacks and we see output printed to stdout when BLOCKS are connected or disconnected.
You can find example code of how to integrate the BLOCKS SDK into existing applications in the @ref example_integrations section.
@section standalone_dependencies Standalone SDK dependencies
- A C++11 compatible compiler
@subsection standalone_libraries_macos MacOS frameworks
@subsection standalone_libraries_macos macOS frameworks
- Accelerate
- AudioToolbox


+ 6
- 0
extras/BLOCKS/doxygen/stylesheet.css View File

@@ -2176,3 +2176,9 @@ div.image img[src="BlocksSynth_grid.JPG"] {
div.image img[src="BlocksSynth_waveshape.gif"] {
width: 320px;
}
div.image img[src="blocks_code_interface.png"] {
width: 750px;
}
div.image img[src="blocks_code_tabs.png"] {
width: 320px;
}

+ 8
- 8
extras/BLOCKS/standalone_sdk/README.md View File

@@ -6,19 +6,19 @@ This repository contains the source code of the BLOCKS SDK, which is licensed un

Much more comprehensive documentation can be found at http://developer.roli.com/documentation/the_standalone_blocks_sdk.html.

The `SDK` directory contains the source code required to compile a library containing the BLOCKS SDK functionality. In the `SDK/Build` directory you will find an Xcode project, a Visual Studio solution and a Linux Makefile which are configured to create a static library on the MacOS, Windows and Linux platforms respectively. Simply use the one appropriate for your system. Once you have created this library you can access BLOCKS functionality by linking this library into your application and `#include`-ing the provided header file, `BlocksHeader.h`.
The `SDK` directory contains the source code required to compile a library containing the BLOCKS SDK functionality. In the `SDK/Build` directory you will find an Xcode project, a Visual Studio solution and a Linux Makefile which are configured to create a static library on the macOS, Windows and Linux platforms respectively. Simply use the one appropriate for your system. Once you have created this library you can access BLOCKS functionality by linking this library into your application and `#include`-ing the provided header file, `BlocksHeader.h`.

The `examples` directory contains some sample code which uses the BLOCKS SDK library. Here we again provide an Xcode project, a Visual Studio solution and a Linux Makefile to allow the examples to be built on the corresponding platform. The Xcode project and Visual Studio solution compile the BLOCKS SDK library automatically, whereas the Linux Makefile requires the BLOCKS SDK library to be manually build first.
The `examples` directory contains some sample code which uses the BLOCKS SDK library. Here we again provide an Xcode project, a Visual Studio solution and a Linux Makefile to allow the examples to be built on the corresponding platform. The Xcode project and Visual Studio solution compile the BLOCKS SDK library automatically, whereas the Linux Makefile requires the BLOCKS SDK library to be manually built first.

## Quick start guide

### MacOS with Xcode
### macOS with Xcode

Open `examples/BlockFinder/MacOS/BlockFinder.xcodeproj` with Xcode and compile and execute the example application. This will both create the BLOCKS SDK library and provide a very simple demonstration of how to use it. Use this as a base to develop your own BLOCKS creation!

### Windows with Visual Studio

Use the same procedure as MacOS, but instead open `examples/BlockFinder/Windows/BlockFinder.vcxproj`.
Use the same procedure as macOS, but instead open `examples/BlockFinder/Windows/BlockFinder.vcxproj`.

### Linux

@@ -39,21 +39,21 @@ This will produce the executable `BlockFinder` in the `Debug` directory.

The source code for the BLOCKS SDK library is contained within the `SDK` directory of this repository. Here you will find header files that you can include in your own projects and the `Build` subdirectory contains an Xcode project, a Visual Studio project and a Linux Makefile for compiling the SDK source code into a static library. Use the appropriate choice for your platform, select either the "Debug" or "Release" configuration, and build the project.

For MacOS this will produce `libBLOCKS-SDK.a` in either the `SDK/Build/MacOS/Debug/` or `SDK/Build/MacOS/Release/` directory, for Linux this will produce `libBLOCKS-SDK.a` in either the `SDK/Build/Linux/Debug/` or `SDK/Build/Linux/Release/` directory, and for Windows this will produce `BLOCKS-SDK.lib` in either the `SDK\Build\Windows\x64\Debug` or `SDK\Build\Windows\x64\Release` folder.
For macOS this will produce `libBLOCKS-SDK.a` in either the `SDK/Build/MacOS/Debug/` or `SDK/Build/MacOS/Release/` directory, for Linux this will produce `libBLOCKS-SDK.a` in either the `SDK/Build/Linux/Debug/` or `SDK/Build/Linux/Release/` directory, and for Windows this will produce `BLOCKS-SDK.lib` in either the `SDK\Build\Windows\x64\Debug` or `SDK\Build\Windows\x64\Release` folder.

### Using the SDK header file

To use BLOCKS classes and functions in your application you must include the `BlocksHeader.h` file in your source code. You also need to tell the compiler to look in the `SDK` directory for additional header files, which you can configure inside your Xcode or Visual Studio project. If you are using the command line to compile your application then you can see an example of how to do this in `examples/BLOCKS-SDK/BlockFinder/Linux/Makefile` (which is also configured for MacOS, despite being located inside the Linux directory).
To use BLOCKS classes and functions in your application you must include the `BlocksHeader.h` file in your source code. You also need to tell the compiler to look in the `SDK` directory for additional header files, which you can configure inside your Xcode or Visual Studio project. If you are using the command line to compile your application then you can see an example of how to do this in `examples/BlockFinder/Linux/Makefile` or `examples/BlockFinder/MacOS/Makefile`.

### Linking against the SDK library

You must also tell your compiler where to find the SDK static library before your BLOCKS application will compile, and include all of the dependencies for your platform, which are listed in the Dependencies section of this README. Again, this is configured in your Xcode or Visual Studio project, but if you are using the command line you can see an example of how to do this in `examples/BLOCKS-SDK/BlockFinder/Linux/Makefile` (which, again, is also configured for MacOS).
You must also tell your compiler where to find the SDK static library before your BLOCKS application will compile, and include all of the dependencies for your platform, which are listed in the Dependencies section of this README. Again, this is configured in your Xcode or Visual Studio project, but if you are using the command line you can see an example of how to do this in `examples/BlockFinder/Linux/Makefile` or `examples/BlockFinder/MacOS/Makefile`.

## Dependencies

- A C++11 compatible compiler

### MacOS frameworks
### macOS frameworks

- Accelerate
- AudioToolbox


+ 2
- 2
extras/BLOCKS/standalone_sdk/SDK/BlocksHeader.h View File

@@ -1,11 +1,11 @@
#pragma once
#ifndef JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED
#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1
#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1
#endif
#ifndef JUCE_DONT_DECLARE_PROJECTINFO
#define JUCE_DONT_DECLARE_PROJECTINFO 1
#define JUCE_DONT_DECLARE_PROJECTINFO 1
#endif
#include <juce_audio_basics/juce_audio_basics.h>


+ 11
- 13
extras/BLOCKS/standalone_sdk/examples/BlockFinder/BlockFinder.cpp View File

@@ -1,21 +1,18 @@
/*
==============================================================================
This file is part of the JUCE library.
This file is part of the JUCE examples.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
@@ -34,18 +31,19 @@ void BlockFinder::topologyChanged()
{
// We have a new topology, so find out what it isand store it in a local
// variable.
BlockTopology currentTopology = pts.getCurrentTopology();
auto currentTopology = pts.getCurrentTopology();
Logger::writeToLog ("\nNew BLOCKS topology.");
// The blocks member of a BlockTopology contains an array of blocks. Here we
// loop over them and print some information.
Logger::writeToLog (String ("Detected ") + String (currentTopology.blocks.size()) + " blocks:");
Logger::writeToLog ("Detected " + String (currentTopology.blocks.size()) + " blocks:");
for (auto& block : currentTopology.blocks)
{
Logger::writeToLog ("");
Logger::writeToLog (String(" Description: ") + block->getDeviceDescription());
Logger::writeToLog (String(" Battery level: ") + String (block->getBatteryLevel()));
Logger::writeToLog (String(" UID: ") + String (block->uid));
Logger::writeToLog (String(" Serial number: ") + block->serialNumber);
Logger::writeToLog (" Description: " + block->getDeviceDescription());
Logger::writeToLog (" Battery level: " + String (block->getBatteryLevel()));
Logger::writeToLog (" UID: " + String (block->uid));
Logger::writeToLog (" Serial number: " + block->serialNumber);
}
}

+ 4
- 7
extras/BLOCKS/standalone_sdk/examples/BlockFinder/BlockFinder.h View File

@@ -1,21 +1,18 @@
/*
==============================================================================
This file is part of the JUCE library.
This file is part of the JUCE examples.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/


+ 13
- 14
extras/BLOCKS/standalone_sdk/examples/BlockFinder/Linux/main.cpp View File

@@ -1,21 +1,18 @@
/*
==============================================================================
This file is part of the JUCE library.
This file is part of the JUCE examples.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
@@ -29,20 +26,22 @@ class MyJUCEApp : public juce::JUCEApplicationBase
public:
MyJUCEApp() {}
~MyJUCEApp() {}
void initialise (const juce::String&) override {}
void shutdown() override {}
const juce::String getApplicationName() override { return "BlockFinder"; }
const juce::String getApplicationVersion() override { return "1.0.0"; }
bool moreThanOneInstanceAllowed() override { return true; }
void shutdown() override {}
const juce::String getApplicationName() override { return "BlockFinder"; }
const juce::String getApplicationVersion() override { return "1.0.0"; }
bool moreThanOneInstanceAllowed() override { return true; }
void anotherInstanceStarted (const juce::String&) override {}
void suspended() override {}
void resumed() override {}
void suspended() override {}
void resumed() override {}
void systemRequestedQuit() override {}
void unhandledException(const std::exception*, const juce::String&,
int lineNumber) override {}
private:
// Our BLOCKS class.
BlockFinder finder;
};


+ 1
- 1
extras/BLOCKS/standalone_sdk/examples/BlockFinder/MacOS/Makefile View File

@@ -42,7 +42,7 @@ INCLUDES := -I$(SDK_PATH)
ifeq ($(PLATFORM),MacOS)
LIBS := -framework Accelerate -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreMIDI -framework IOKit -framework OpenGL -framework QuartzCore
else
LIBS := -L/usr/X11R6/lib/ $(shell pkg-config --libs alsa freetype2 libcurl x11 xext xinerama) -lGL -ldl -lpthread -lrt
LIBS := -L/usr/X11R6/lib/ $(shell pkg-config --libs alsa libcurl x11) -ldl -lpthread -lrt
CXXFLAGS += -DLINUX=1
endif



+ 4
- 7
extras/BLOCKS/standalone_sdk/examples/BlockFinder/MacOS/main.mm View File

@@ -1,21 +1,18 @@
/*
==============================================================================
This file is part of the JUCE library.
This file is part of the JUCE examples.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/


+ 13
- 14
extras/BLOCKS/standalone_sdk/examples/BlockFinder/Windows/main.cpp View File

@@ -1,21 +1,18 @@
/*
==============================================================================
This file is part of the JUCE library.
This file is part of the JUCE examples.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
@@ -29,20 +26,22 @@ class MyJUCEApp : public juce::JUCEApplicationBase
public:
MyJUCEApp() {}
~MyJUCEApp() {}
void initialise (const juce::String&) override {}
void shutdown() override {}
const juce::String getApplicationName() override { return "BlockFinder"; }
const juce::String getApplicationVersion() override { return "1.0.0"; }
bool moreThanOneInstanceAllowed() override { return true; }
void shutdown() override {}
const juce::String getApplicationName() override { return "BlockFinder"; }
const juce::String getApplicationVersion() override { return "1.0.0"; }
bool moreThanOneInstanceAllowed() override { return true; }
void anotherInstanceStarted (const juce::String&) override {}
void suspended() override {}
void resumed() override {}
void suspended() override {}
void resumed() override {}
void systemRequestedQuit() override {}
void unhandledException(const std::exception*, const juce::String&,
int lineNumber) override {}
private:
// Our BLOCKS class.
BlockFinder finder;
};


Loading…
Cancel
Save