Compare commits
20 Commits
Author | SHA1 | Date |
---|---|---|
Neale Pickett | 195c02748b | |
Neale Pickett | 13825855d3 | |
Neale Pickett | e54595e630 | |
Neale Pickett | 63bd067250 | |
Neale Pickett | 9f0c0711a8 | |
Neale Pickett | efc67e9fe5 | |
Neale Pickett | ce7d6107cf | |
Neale Pickett | 48d2450514 | |
Neale Pickett | 5f44832151 | |
Neale Pickett | da45584955 | |
Neale Pickett | 50ce7c245a | |
Neale Pickett | f767eb6e23 | |
Neale Pickett | 7495bfd20f | |
Neale Pickett | 5fa607d6b0 | |
Neale Pickett | 855ad3ba60 | |
Neale Pickett | 25a917dc81 | |
Neale Pickett | a8d4918e05 | |
Neale Pickett | 544ffa8ced | |
Neale Pickett | a3d8262e01 | |
Neale Pickett | 0ba9c0957a |
|
@ -7,23 +7,21 @@ VERSION=$GITHUB_REF_NAME
|
|||
|
||||
BASE=$GITHUB_SERVER_URL/api/packages/$GITHUB_ACTOR/generic/$PACKAGE/$VERSION
|
||||
|
||||
echo "=== Delete $VERSION"
|
||||
curl \
|
||||
--user "$GITHUB_ACTOR:$PACKAGE_API_TOKEN" \
|
||||
--request DELETE \
|
||||
$BASE
|
||||
echo
|
||||
|
||||
for path in "$@"; do
|
||||
fn=$(basename "$path")
|
||||
echo "=== Upload $VERSION/$fn"
|
||||
|
||||
curl \
|
||||
--user "$GITHUB_ACTOR:$PACKAGE_API_TOKEN" \
|
||||
--request DELETE \
|
||||
$BASE/$fn
|
||||
|
||||
curl \
|
||||
--fail \
|
||||
--user "$GITHUB_ACTOR:$PACKAGE_API_TOKEN" \
|
||||
--upload-file "$path" \
|
||||
$BASE/$fn
|
||||
echo
|
||||
done
|
||||
|
|
|
@ -16,4 +16,4 @@ jobs:
|
|||
- name: publish
|
||||
env:
|
||||
PACKAGE_API_TOKEN: ${{ secrets.PACKAGE_API_TOKEN }}
|
||||
run: .gitea/publish.sh *.hex *.zip
|
||||
run: .gitea/publish.sh build/*.zip build/*.hex
|
||||
|
|
42
Makefile
42
Makefile
|
@ -2,29 +2,27 @@ DEVICE=/dev/ttyACM0
|
|||
|
||||
all: firmwares
|
||||
|
||||
firmwares: MockBand.guitar-wammy.hex
|
||||
MockBand.guitar-wammy.hex: VID=0x1bad
|
||||
MockBand.guitar-wammy.hex: PID=0x0004
|
||||
MockBand.guitar-wammy.hex: FLAGS=-DWAMMY
|
||||
firmwares: build/MockBand.guitar-wammy.hex
|
||||
build/MockBand.guitar-wammy.hex: VID=0x1bad
|
||||
build/MockBand.guitar-wammy.hex: PID=0x0004
|
||||
build/MockBand.guitar-wammy.hex: FLAGS=-DWAMMY
|
||||
|
||||
firmwares: MockBand.guitar.hex
|
||||
MockBand.guitar.hex: VID=0x1bad
|
||||
MockBand.guitar.hex: PID=0x0004
|
||||
firmwares: build/MockBand.guitar.hex
|
||||
build/MockBand.guitar.hex: VID=0x1bad
|
||||
build/MockBand.guitar.hex: PID=0x0004
|
||||
|
||||
firmwares: MockBand.drums.hex
|
||||
MockBand.drums.hex: VID=0x1bad
|
||||
MockBand.drums.hex: PID=0x3110
|
||||
firmwares: build/MockBand.drums.hex
|
||||
build/MockBand.drums.hex: VID=0x1bad
|
||||
build/MockBand.drums.hex: PID=0x3110
|
||||
|
||||
MockBand.drums-xbox.hex: VID=0x1bad
|
||||
MockBand.drums-xbox.hex: PID=0x0003
|
||||
|
||||
MockBand.drums-rb1.hex: VID=0x1bad
|
||||
MockBand.drums-rb1.hex: PID=00005
|
||||
firmwares: build/MockBand.drums1.hex
|
||||
build/MockBand.drums1.hex: VID=0x1bad
|
||||
build/MockBand.drums1.hex: PID=00005
|
||||
|
||||
|
||||
MockBand.%.hex: MockBand.ino
|
||||
rm -rf build
|
||||
build/MockBand.%.hex: MockBand.ino
|
||||
mkdir -p build/cache
|
||||
rm -f build/build.options.json
|
||||
arduino-builder \
|
||||
-build-path $(abspath build/) \
|
||||
-build-cache $(abspath build/cache/) \
|
||||
|
@ -36,16 +34,16 @@ MockBand.%.hex: MockBand.ino
|
|||
mv build/MockBand.ino.hex $@
|
||||
|
||||
|
||||
flash-%: MockBand.%.hex
|
||||
flash-%: build/MockBand.%.hex
|
||||
echo -n "Waiting for $(DEVICE)..."; while [ ! -e $(DEVICE) ]; do echo -n .; sleep 1; done
|
||||
avrdude -v -patmega32u4 -cavr109 -P$(DEVICE) -b57600 -D -Uflash:w:$<:i
|
||||
|
||||
dist: MockBand.zip
|
||||
dist: build/MockBand.zip
|
||||
|
||||
MockBand.zip: firmwares
|
||||
build/MockBand.zip: firmwares
|
||||
rm -f $@
|
||||
zip -r $@ README.md *.hex docs/
|
||||
zip -r $@ README.md build/*.hex docs/
|
||||
|
||||
clean:
|
||||
rm -rf build MockBand.*.hex MockBand.zip
|
||||
rm -rf build
|
||||
|
||||
|
|
52
MockBand.ino
52
MockBand.ino
|
@ -2,6 +2,8 @@
|
|||
#include <Arduino.h>
|
||||
#include <PluggableUSB.h>
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#include "hid.hh" // Modified HID library: doesn't prefix each packet with ID
|
||||
#include "instrument.hh"
|
||||
#include "standard.hh" // Standard pins
|
||||
|
@ -26,9 +28,7 @@
|
|||
#error USB_VID must be set to 0x1bad: see INSTALL.md
|
||||
#endif
|
||||
|
||||
#if USB_PID == 0x0003
|
||||
#define DRUM // XBox
|
||||
#elif USB_PID == 0x0004
|
||||
#if USB_PID == 0x0004
|
||||
#define GUITAR
|
||||
#elif USB_PID == 0x0005
|
||||
#define DRUM // Wii RB1
|
||||
|
@ -127,58 +127,42 @@ void loop() {
|
|||
//
|
||||
// Calculate and send an HID update
|
||||
//
|
||||
uint16_t vbuttons = buttons; // We're going to mess with the button state
|
||||
|
||||
buttonState.buttons = (vbuttons & 0b1100111111); // +-..!OYRGB
|
||||
buttonState.buttons = (buttons & 0b1100111111); // +-..!OYRGB
|
||||
#ifdef GUITAR
|
||||
buttonState.buttons |= (vbuttons >> 10) & 0b11111; // Solo keys
|
||||
bitWrite(buttonState.buttons, 6, (vbuttons >> 10) & 0b11111); // Solo modifier
|
||||
buttonState.buttons |= (buttons >> 10) & 0b11111; // Solo keys
|
||||
bitWrite(buttonState.buttons, 6, buttons & (0b11111 << 10)); // Solo modifier
|
||||
|
||||
if (bitRead(vbuttons, 6)) {
|
||||
if (bitRead(buttons, 6)) {
|
||||
buttonState.hatAndConstant = 0; // up
|
||||
} else if bitRead(vbuttons, 7) {
|
||||
} else if bitRead(buttons, 7) { //
|
||||
buttonState.hatAndConstant = 4; // down
|
||||
} else {
|
||||
buttonState.hatAndConstant = 8; // nothing
|
||||
}
|
||||
#else // DRUMS
|
||||
buttonState.buttons |= (buttons >> 10) & 0b01011; // Cymbals
|
||||
bitWrite(buttonState.buttons, 10, (buttons >> 0) & 0b01111); // Drum pad modifier
|
||||
bitWrite(buttonState.buttons, 11, (buttons >> 10) & 0b01011); // Cymbals modifier
|
||||
buttonState.axis[3] = bitRead(buttons, 12)?255:0; // High hat
|
||||
|
||||
// Hi hat pedal (SOLO_RED) makes yellow cymbal strike a blue cymbal strike
|
||||
if (bitRead(vbuttons, 12) && bitRead(vbuttons, 13)) {
|
||||
bitClear(vbuttons, 13);
|
||||
bitSet(vbuttons, 10);
|
||||
}
|
||||
|
||||
buttonState.buttons |= (vbuttons >> 10) & 0b01011; // Cymbals
|
||||
bitWrite(buttonState.buttons, 10, (vbuttons >> 0) & 0b01111); // Drum pad modifier
|
||||
bitWrite(buttonState.buttons, 11, (vbuttons >> 10) & 0b01011); // Cymbals modifier
|
||||
|
||||
|
||||
// rbdrum2midi wants these set: it ignores the button states.
|
||||
// rbdrum2midi wants these set
|
||||
buttonState.velocity[0] = bitRead(buttonState.buttons, 3)?127:0; // Y
|
||||
buttonState.velocity[1] = bitRead(buttonState.buttons, 2)?127:0; // R
|
||||
buttonState.velocity[2] = bitRead(buttonState.buttons, 1)?127:0; // G
|
||||
buttonState.velocity[3] = bitRead(buttonState.buttons, 0)?127:0; // B
|
||||
|
||||
// Clone Hero 1.0.0.4080-final needs blue and yellow cymbals to send up and down on d-pad.
|
||||
// This is what the mysterous CymExt1 and CymExt2 mappings mean.
|
||||
// If these aren't set, all pads (except red) register as simultaneous drum and cymbal hits.
|
||||
if (bitRead(vbuttons, 13)) {
|
||||
buttonState.hatAndConstant = 0; // up
|
||||
} else if (bitRead(vbuttons, 10)) {
|
||||
buttonState.hatAndConstant = 4; // down
|
||||
} else {
|
||||
buttonState.hatAndConstant = 8; // nothing
|
||||
}
|
||||
// Say the D-pad is centered
|
||||
buttonState.hatAndConstant = 8;
|
||||
#endif
|
||||
|
||||
#ifdef DPAD
|
||||
#error DPAD isn't implemented yet
|
||||
#endif
|
||||
|
||||
#ifdef DEBUGY0
|
||||
// Log sample rate to the first Y axis
|
||||
buttonState.axis[1] = samples & 0xff;
|
||||
#ifdef DEBUG
|
||||
// Log sample rate to the first X axis
|
||||
buttonState.axis[0] = samples & 0xff;
|
||||
#endif
|
||||
|
||||
// Send an update
|
||||
|
|
76
README.md
76
README.md
|
@ -1,9 +1,4 @@
|
|||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
# Introduction
|
||||
# Mock Band
|
||||
|
||||
Microcontroller Firmware to emulate guitar and drum kit controllers from the
|
||||
Wii version of the Rock Band games.
|
||||
|
@ -23,23 +18,6 @@ Thanks, Nicholas!
|
|||
* A physical controller
|
||||
|
||||
|
||||
# Skills Needed
|
||||
|
||||
This is a research project:
|
||||
it's assumed you already have a skillset that includes:
|
||||
|
||||
* Disassembling consumer electronics
|
||||
* Using a multimeter to perform continuity checks
|
||||
* Building electronics projects using a microcontroller
|
||||
* Soldering
|
||||
* Running the Arduino IDE *or* using `avrdude` to flash a firmware
|
||||
|
||||
If you're not comfortable with the above list,
|
||||
your best option right now (Jan 2024) is to either buy a used kit,
|
||||
or wait for the Polybar project to finish their work
|
||||
producing a beginner-friendly kit with assembly manual.
|
||||
|
||||
|
||||
# Controllers
|
||||
|
||||
## Guitar
|
||||
|
@ -71,30 +49,10 @@ I'm sorry I didn't photograph or record any of this,
|
|||
but it was pretty straightforward.
|
||||
|
||||
|
||||
# Building
|
||||
# Compiling
|
||||
|
||||
This will compile in the Arduino IDE,
|
||||
or on the commandline using `make`.
|
||||
|
||||
## Command Line
|
||||
|
||||
Just run `make` on a Unix system.
|
||||
I set up paths for my Debian install of Arduino 1.8;
|
||||
you may need to adjust them if your setup has different paths.
|
||||
|
||||
There is a `flash-%` target that will upload the built firmware to a Pro Micro.
|
||||
The following targets exist:
|
||||
|
||||
make flash-guitar # Guitar firmware
|
||||
make flash-guitar-wammy # Guitar firmware with wammy bar
|
||||
make flash-drums # Drums firmware
|
||||
|
||||
## Arduino
|
||||
|
||||
Mockband has no library dependencies,
|
||||
and as far as I can tell,
|
||||
will work with the built-in Leonardo profile,
|
||||
even though you're uploading to a Pro Micro.
|
||||
This compiles in the Arduino IDE.
|
||||
It doesn't have any library dependencies.
|
||||
|
||||
You need to make two edits to `boards.txt`.
|
||||
Instructions for this are all over the place.
|
||||
|
@ -105,7 +63,7 @@ Don't edit anything that doesn't say `build` on the line! I can't help you if yo
|
|||
In my examples, I'm editing the lines for the leonardo build. Yours might be different:
|
||||
it should match the board you're using.
|
||||
|
||||
### Build flags
|
||||
## Build flags
|
||||
|
||||
This disables serial communications on the board.
|
||||
|
||||
|
@ -114,7 +72,7 @@ This disables serial communications on the board.
|
|||
Hat tip to Nicholas Angle for figuring this out
|
||||
so I didn't have to.
|
||||
|
||||
### VID and PID
|
||||
## VID and PID
|
||||
|
||||
These set the USB identifiers.
|
||||
VID is the Vendor ID,
|
||||
|
@ -123,7 +81,7 @@ and PID is the Product ID.
|
|||
`0x1bad` means "Harmonix Music",
|
||||
at least, it does to the Linux kernel.
|
||||
|
||||
#### For guitar
|
||||
### For guitar
|
||||
|
||||
PID `0x004` means "Guitar controller".
|
||||
|
||||
|
@ -132,7 +90,7 @@ PID `0x004` means "Guitar controller".
|
|||
leonardo.build.usb_product="Mockband Guitar"
|
||||
|
||||
|
||||
#### For drums
|
||||
### For drums
|
||||
|
||||
PID `0x3110` means "Rock Band 2 drums".
|
||||
This works better with all versions of Rock Band on my wii,
|
||||
|
@ -228,19 +186,6 @@ will use both the 2x kick and the hi hat.
|
|||
Maybe there is some other game that uses this too.
|
||||
|
||||
|
||||
# Related Projects
|
||||
|
||||
Mockband is a research project.
|
||||
You can use it to build a fully working controller,
|
||||
but the main goal of this project is to inform other developers.
|
||||
|
||||
If you're looking for a user-friendly way to get a Rock Band drum controller,
|
||||
consider the following alternatives:
|
||||
|
||||
* Buying a used Harmonix (official Rock Band) drum kit
|
||||
* Mad Catz Rock Band 3 MIDI PRO-Adapter and a MIDI e-drum kit
|
||||
* [Santroller](https://santroller.tangentmc.net/wiring_guides/drum.html)
|
||||
wired to a used Harmonix drum kit
|
||||
|
||||
|
||||
# Bugs / Not Yet Implemented
|
||||
|
@ -255,11 +200,6 @@ just
|
|||
and I'll open an issue.
|
||||
|
||||
|
||||
# License
|
||||
|
||||
You may use this under the terms of the [MIT License](docs/COPYING.md).
|
||||
|
||||
|
||||
# Need help?
|
||||
|
||||
[Email me](mailto:neale@woozle.org),
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
## [1.0-beta2] - 2024-01-07
|
||||
### Added
|
||||
- CI/CD build
|
||||
- Make-based build, but it still works with the Arduino IDE!
|
||||
- XBox controller
|
||||
|
||||
### Fixed
|
||||
- Blue and Yellow cymbal strikes send up and down on the D-pad.
|
||||
- This fixes a bug with Clone Hero where Blue, Yellow, and Green pads,
|
||||
both cymbal and drum pads,
|
||||
triggered a drum and cymbal hit at the same time.
|
||||
- Clone Hero's mysterious CymExt1 and CymExt2 are read on Yellow and Blue
|
||||
cymbal hits, and must map to dpad up and down. In Windows this might be called "POV Hat";
|
||||
in Linux it's called "Hat 0"
|
||||
|
||||
### Changed
|
||||
- The "high hat" pin now causes the yellow cymbal to send a blue cymbal hit,
|
||||
similar to devices sold in the past for this express purpose.
|
||||
Previously it changed a hat axis,
|
||||
because I thought "hat" meant "high hat". Heh.
|
||||
|
||||
### Removed
|
||||
- No more DEBUG option
|
||||
|
||||
## [1.0-beta1] - 2024-01-04
|
|
@ -1,22 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright © 2023 Nicholas Angle
|
||||
Copyright © 2023-2024 Neale Pickett <neale@woozle.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
**The software is provided "as is", without warranty of any kind, express or
|
||||
implied, including but not limited to the warranties of merchantability,
|
||||
fitness for a particular purpose and noninfringement. In no event shall the
|
||||
authors or copyright holders be liable for any claim, damages or other
|
||||
liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
out of or in connection with the software or the use or other dealings in the
|
||||
software.**
|
|
@ -7,44 +7,16 @@ so that when original sources fall off the net,
|
|||
hopefully at least my notes will still be around.
|
||||
|
||||
|
||||
USB data rate
|
||||
-------------
|
||||
|
||||
Unlike the Santroller
|
||||
(which is much easier to install for beginners),
|
||||
this firmware does not send continuous updates over USB,
|
||||
unless it has to.
|
||||
|
||||
Because the only analog input is the wammy bar,
|
||||
if you don't include that input,
|
||||
the controller can stay silent until a button is pressed or released.
|
||||
This allows 4 controllers to connect to a USB hub,
|
||||
with no concern for overwhelming the hub.
|
||||
Since the amount of data is about the same as a keyboard,
|
||||
and a little less than a mouse,
|
||||
I would expect that a 7-port USB 2 hub would also handle 7 instruments,
|
||||
no problem.
|
||||
I just don't know of any games that can use mor than 4 instruments.
|
||||
|
||||
If you do use the wammy bar,
|
||||
an update is sent every 20 milliseconds.
|
||||
The firmware still attempts to reduce lag
|
||||
by sending button change events as soon as possible.
|
||||
I haven't played with this much,
|
||||
so if you have a wammy bar set up,
|
||||
please reach out to me and let me know how it works.
|
||||
|
||||
|
||||
|
||||
Sample Rate
|
||||
-----------
|
||||
|
||||
If `DEBUGY0' is defined,
|
||||
the number of samples taken since the last HID report
|
||||
is sent as Y axis on hat 0.
|
||||
You can use the
|
||||
The number of samples taken since the last HID report
|
||||
is sent as the second Y axis.
|
||||
If you `#define DEBUG`,
|
||||
you can use the
|
||||
[included gamepad tester](gamepad.html),
|
||||
to see the approximate number of samples as an integer.
|
||||
to see the approximate number of samples as an integer,
|
||||
on the first X axis.
|
||||
|
||||
This is approximate,
|
||||
because the browser encodes the value as a real number between -1 and 1.
|
||||
|
@ -52,11 +24,6 @@ We convert it back, but may lose a little precision.
|
|||
It's close enough for me,
|
||||
hopefully it's close enough for you.
|
||||
|
||||
This number will not be very useful
|
||||
unless you are polling the wammy bar,
|
||||
since without that input,
|
||||
updates are only sent when a button state changes.
|
||||
|
||||
|
||||
Debouncing
|
||||
----------
|
||||
|
@ -99,33 +66,11 @@ Here's what each bit means:
|
|||
* 0o13: Cymbal modifier
|
||||
* 0o14: Select
|
||||
|
||||
Hats
|
||||
----
|
||||
|
||||
I guess "hats" are what I would have called "joysticks and dpads".
|
||||
hatAndConstant
|
||||
--------------
|
||||
|
||||
|
||||
### Hat 0: unknown
|
||||
|
||||
This doesn't appear to be sent or used.
|
||||
|
||||
### Hat 1: guitar analog controls
|
||||
|
||||
The X axis is the position of the wammy bar.
|
||||
|
||||
The Y axis is the pickup selector.
|
||||
I believe this was a 5-position switch on some guitars.
|
||||
Only Rock Band 1 seems to use this.
|
||||
|
||||
### Hat 2: navigation
|
||||
|
||||
Sent by the dpad on the controller,
|
||||
as `hatAndConstant`.
|
||||
|
||||
Guitars send up/down for the up/down strum buttons.
|
||||
Drums send up/down on the blue/yellow cymbal pads.
|
||||
|
||||
The position of this digital input is reported in only 3 bits:
|
||||
The HAT switch reports its position like a clock.
|
||||
|
||||
7 0 1
|
||||
|
||||
|
@ -153,19 +98,20 @@ Sending these values does not seem to cause problems with my Wii games.
|
|||
Product ID (PID)
|
||||
----------------
|
||||
|
||||
The following USB PIDs are recognized by various things:
|
||||
Nicholas,
|
||||
who did the initial work on the guitar,
|
||||
suggested that PID 0x0005 would get the sketch working as drums.
|
||||
And that was correct:
|
||||
this works great on
|
||||
Wii Rock Band 1 and
|
||||
Wii LEGO Rock Band.
|
||||
|
||||
* 0x0003: XBox Drum
|
||||
* 0x0004: Wii Guitar
|
||||
* 0x0005: Wii Drums - Rock Band 1
|
||||
* 0x3110: Wii Drums - Rock Band 2
|
||||
But it fails in frustrating ways on
|
||||
Wii Rock Band 3:
|
||||
the yellow and blue pads don't navigate menus,
|
||||
and cymbals aren't detected.
|
||||
|
||||
There are some quirks to note:
|
||||
|
||||
* Rock Band 3 won't recognize cymbal hits on PID=0x0005,
|
||||
but the same program with PID=0x3110 works fine.
|
||||
* Wii games don't appear to recognized PID=0x0003.
|
||||
Maybe Rock Band 3 does: I didn't test that one.
|
||||
The fix was setting the USB PID to 0x3110.
|
||||
|
||||
|
||||
Drum Velocity
|
||||
|
@ -173,8 +119,7 @@ Drum Velocity
|
|||
|
||||
I split the 12 "reserved" bytes from Nicholas's
|
||||
`struct InstrumentButtonState`
|
||||
into 4 bytes of I-Don't-Know,
|
||||
4 bytes of velocity,
|
||||
into 4 bytes of I-Don't-Know, 4 bytes of velocity,
|
||||
and 4 more bytes of I-Don't-Know.
|
||||
Whenever a pad is hit,
|
||||
I send 127 on the corresponding velocity.
|
||||
|
@ -185,21 +130,10 @@ and looks only at the velocity values.
|
|||
|
||||
None of the Wii games I have
|
||||
seem to care what these values are set to.
|
||||
Clone Hero also does not care.
|
||||
|
||||
|
||||
Clone Hero
|
||||
==========
|
||||
|
||||
Clone Hero wants Hat 2 up on yellow cymbal hit,
|
||||
and Hat 2 down on blue cymbal hit.
|
||||
|
||||
If it doesn't see these while mapping drum pads,
|
||||
then hitting any color bad will trigger both drum and cymbal for that color.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
=========
|
||||
|
||||
The most valuable sources of information I found were:
|
||||
|
||||
|
|
Loading…
Reference in New Issue