Skip to content

Commit d32b545

Browse files
committed
Add more samples, add examples, add ability to loop samples
1 parent ec79791 commit d32b545

File tree

23 files changed

+25187
-40
lines changed

23 files changed

+25187
-40
lines changed

examples/cycle.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This example shows how to load multiple samples and
2+
# cycle through them. Each time the button is pressed,
3+
# it plays a sample and then advances to the next
4+
# sample in the list.
5+
6+
import winterbloom_bhb
7+
8+
bhb = winterbloom_bhb.BigHonkingButton()
9+
10+
11+
samples = [
12+
bhb.load_sample("samples/kick.wav"),
13+
bhb.load_sample("samples/snare.wav"),
14+
bhb.load_sample("samples/clap.wav"),
15+
]
16+
17+
current_sample_no = 0
18+
19+
while True:
20+
if bhb.triggered:
21+
bhb.gate_out = True
22+
bhb.play(samples[current_sample_no], pitch_cv=bhb.pitch_in)
23+
current_sample_no += 1
24+
if current_sample_no >= len(samples):
25+
current_sample_no = 0
26+
27+
if bhb.released:
28+
bhb.gate_out = False

examples/default.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This is the default program that comes on the Big Honking Button.
2+
# It makes a honk sound when the button is pressed (or the gate
3+
# is triggered) and you can change the honk sound's pitch using
4+
# the CV input.
5+
6+
import winterbloom_bhb
7+
8+
bhb = winterbloom_bhb.BigHonkingButton()
9+
sample = bhb.load_sample("samples/honk.wav")
10+
11+
while True:
12+
if bhb.triggered:
13+
bhb.gate_out = True
14+
bhb.play(sample, pitch_cv=bhb.pitch_in)
15+
16+
if bhb.released:
17+
bhb.gate_out = False
18+
# Uncomment the call to stop to make the sample
19+
# stop playing as soon as you release the button.
20+
# bhb.stop()

examples/noise.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# This advanced sample is similar to the sine example, except
2+
# it generates white noise.
3+
4+
import array
5+
import random
6+
import audiocore
7+
import winterbloom_bhb
8+
9+
bhb = winterbloom_bhb.BigHonkingButton()
10+
11+
12+
# This generates a raw set of samples that represents one full
13+
# cycle of a sine wave. If you wanted different waveforms, you
14+
# could change the formula here to generate that instead.
15+
def generate_noise(volume=1.0):
16+
volume = volume * (2 ** 15 - 1) # Increase this to increase the volume of the tone.
17+
length = 800
18+
samples = array.array("H", [0] * length)
19+
20+
for i in range(length):
21+
samples[i] = int(random.random() * volume)
22+
23+
return samples
24+
25+
noise = generate_noise(0.8)
26+
sample = audiocore.RawSample(noise)
27+
28+
# Change this to set the noise sample's frequency.
29+
# This generate works well as pretty low numbers,
30+
# but you can get some interesting effects at higher
31+
# values.
32+
frequency = 2
33+
sample.sample_rate = frequency * len(noise)
34+
35+
36+
while True:
37+
if bhb.triggered:
38+
bhb.gate_out = True
39+
bhb.play(sample, loop=True)
40+
41+
if bhb.released:
42+
bhb.gate_out = False
43+
bhb.stop()

examples/random.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# This example shows how to load multiple samples and
2+
# select one at random to play when the button is pressed.
3+
4+
import random
5+
import winterbloom_bhb
6+
7+
bhb = winterbloom_bhb.BigHonkingButton()
8+
9+
samples = [
10+
bhb.load_sample("samples/kick.wav"),
11+
bhb.load_sample("samples/snare.wav"),
12+
bhb.load_sample("samples/clap.wav"),
13+
]
14+
15+
while True:
16+
if bhb.triggered:
17+
bhb.gate_out = True
18+
bhb.play(random.choice(samples), pitch_cv=bhb.pitch_in)
19+
20+
if bhb.released:
21+
bhb.gate_out = False

examples/sine.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This advanced sample shows how to create a custom waveform
2+
# for the button to output. In this case, it generates a
3+
# sine wave. You can use this example as a basis for very
4+
# basic oscillators.
5+
# See also noise.py
6+
7+
import array
8+
import math
9+
import audiocore
10+
import winterbloom_bhb
11+
12+
bhb = winterbloom_bhb.BigHonkingButton()
13+
14+
15+
# This generates a raw set of samples that represents one full
16+
# cycle of a sine wave. If you wanted different waveforms, you
17+
# could change the formula here to generate that instead.
18+
def generate_sine_wave(volume=1.0):
19+
volume = volume * (2 ** 15 - 1) # Increase this to increase the volume of the tone.
20+
length = 100
21+
samples = array.array("H", [0] * length)
22+
23+
for i in range(length):
24+
samples[i] = int((1 + math.sin(math.pi * 2 * i / length)) * volume)
25+
26+
return samples
27+
28+
sine_wave = generate_sine_wave(0.8)
29+
sample = audiocore.RawSample(sine_wave)
30+
31+
# Change this to play different notes. You can also
32+
# check the CV input using `bhb.pitch_in` and re-adjust
33+
# the sample rate.
34+
frequency = 440
35+
sample.sample_rate = frequency * len(sine_wave)
36+
37+
38+
while True:
39+
if bhb.triggered:
40+
bhb.gate_out = True
41+
bhb.play(sample, loop=True)
42+
43+
if bhb.released:
44+
bhb.gate_out = False
45+
bhb.stop()

examples/tap_tempo.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This example shows how to use the button as a means of
2+
# setting a tempo and continuously triggering a sample
3+
# a sample based on the tapped out tempo on the button
4+
# (or via the gate input).
5+
6+
import time
7+
import winterbloom_bhb
8+
9+
bhb = winterbloom_bhb.BigHonkingButton()
10+
sample = bhb.load_sample("samples/kick.wav")
11+
12+
# The interval determines how many seconds
13+
# between samples. At startup, it'll play
14+
# the sample once every half second.
15+
interval = 0.5 # seconds
16+
17+
# This keeps track of the last time the
18+
# sample was played.
19+
last_sample_played = time.monotonic()
20+
21+
# This keeps track of the last time the
22+
# button was pressed. On startup, it's
23+
# just set to the current time, but
24+
# it doesn't really matter.
25+
last_button_press = time.monotonic()
26+
27+
28+
while True:
29+
# Get the current time and see if enough
30+
# time has passed to play the sample.
31+
now = time.monotonic()
32+
if now > last_sample_played + interval:
33+
last_sample_played = now
34+
bhb.play(sample)
35+
bhb.gate_out = True
36+
else:
37+
bhb.gate_out = False
38+
39+
# If the button is pressed (or gate in
40+
# is triggered), the figure out the time
41+
# between this press and the last press
42+
# and use that as the tempo.
43+
if bhb.triggered:
44+
interval = now - last_button_press
45+
last_button_press = now

factory/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
calibrations/
2+
bootloader.bin
3+
firmware.uf2

factory/factory_setup.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import io
2+
import time
3+
import os
4+
import subprocess
5+
import utils
6+
import shutil
7+
import zipfile
8+
9+
import requests
10+
11+
12+
JLINK_PATH = "C:\Program Files (x86)\SEGGER\JLink\JLink.exe"
13+
assert os.path.exists("bootloader.bin")
14+
assert os.path.exists("firmware.uf2")
15+
16+
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
17+
FIRMWARE_DIR = os.path.join(ROOT_DIR, "firmware")
18+
LIB_DIR = os.path.join(FIRMWARE_DIR, "lib")
19+
EXAMPLES_DIR = os.path.join(ROOT_DIR, "examples")
20+
21+
FILES_TO_DEPLOY = {
22+
"https://raw.githubusercontent.com/theacodes/Winterbloom_VoltageIO/master/winterbloom_voltageio.py": "lib",
23+
os.path.join(FIRMWARE_DIR, "winterbloom_bhb"): "lib",
24+
os.path.join(ROOT_DIR, "samples"): ".",
25+
os.path.join(ROOT_DIR, "examples"): ".",
26+
os.path.join(FIRMWARE_DIR, "LICENSE"): ".",
27+
os.path.join(FIRMWARE_DIR, "README.HTM"): ".",
28+
os.path.join(ROOT_DIR, "examples/default.py"): "code.py",
29+
}
30+
31+
32+
def program_bootloader():
33+
print("========== PROGRAMMING BOOTLOADER ==========")
34+
subprocess.check_call(
35+
[JLINK_PATH, "-device", "ATSAMD21G18", "-autoconnect", "1", "-if", "SWD", "-speed", "4000", "-CommanderScript", "flash-bootloader.jlink"]
36+
)
37+
38+
39+
def program_circuitpython():
40+
print("========== PROGRAMMING CIRCUITPYTHON ==========")
41+
input("Connect usb cable, press enter.")
42+
bootloader_drive = utils.find_drive_by_name("HONKBOOT")
43+
utils.copyfile("firmware.uf2", os.path.join(bootloader_drive, "NEW.uf2"))
44+
45+
46+
def deploy_circuitpython_code():
47+
print("========== DEPLOYING CODE ==========")
48+
# Wait for the circuitpython drive to show up.
49+
time.sleep(5)
50+
cpy_drive = utils.find_drive_by_name("CIRCUITPY")
51+
52+
utils.clean_pycache(FIRMWARE_DIR)
53+
utils.clean_pycache(EXAMPLES_DIR)
54+
55+
os.makedirs(os.path.join(cpy_drive, "lib"), exist_ok=True)
56+
57+
for src, dst in FILES_TO_DEPLOY.items():
58+
if src.startswith("https://"):
59+
if '.zip' in src:
60+
http_src, zip_path = src.rsplit(':', 1)
61+
62+
zip_data = io.BytesIO(requests.get(http_src).content)
63+
64+
with zipfile.ZipFile(zip_data, "r") as zipfh:
65+
file_data = zipfh.read(zip_path)
66+
67+
dst = os.path.join(dst, os.path.basename(zip_path))
68+
with open(os.path.join(cpy_drive, dst), "wb") as fh:
69+
fh.write(file_data)
70+
71+
else:
72+
file_data = requests.get(src).content
73+
_, file_name = src.rsplit('/', 1)
74+
dst = os.path.join(dst, file_name)
75+
with open(os.path.join(cpy_drive, dst), "wb") as fh:
76+
fh.write(file_data)
77+
78+
else:
79+
if os.path.isdir(src):
80+
dst = os.path.join(cpy_drive, dst, os.path.basename(src))
81+
if os.path.exists(dst):
82+
shutil.rmtree(dst)
83+
shutil.copytree(src, dst)
84+
else:
85+
shutil.copy(src, os.path.join(cpy_drive, dst))
86+
87+
print(f"Copied {src} to {dst}")
88+
89+
utils.flush(cpy_drive)
90+
91+
92+
def main():
93+
try:
94+
bootloader_drive = utils.find_drive_by_name("HONKBOOT")
95+
except:
96+
bootloader_drive = None
97+
98+
try:
99+
circuitpython_drive = utils.find_drive_by_name("CIRCUITPY")
100+
except:
101+
circuitpython_drive = None
102+
103+
if not circuitpython_drive and not bootloader_drive:
104+
program_bootloader()
105+
106+
if not circuitpython_drive:
107+
program_circuitpython()
108+
109+
if circuitpython_drive and os.path.exists(os.path.join(circuitpython_drive, "code.py")):
110+
if input("redeploy code? y/n: ").strip() == "y":
111+
deploy_circuitpython_code()
112+
else:
113+
deploy_circuitpython_code()
114+
115+
116+
if __name__ == "__main__":
117+
main()

factory/flash-bootloader.jlink

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
r
2+
loadfile bootloader.bin
3+
r
4+
go
5+
exit

factory/sync.exe

140 KB
Binary file not shown.

0 commit comments

Comments
 (0)