Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions player/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ EMSCRIPTEN_BINDINGS(sid) {
emscripten::function("setBeatsCount", &setBeatsCount, allow_raw_pointers());
emscripten::function("setTracksCount", &setTracksCount, allow_raw_pointers());
emscripten::function("createEmptyTune", &createEmptyTune, allow_raw_pointers());
emscripten::function("symbolFromPitch", &symbolFromPitch);
emscripten::function("pitchFromSymbol", &pitchFromSymbol);

class_<Note>("Note")
.class_function("makeRest", &Note::makeRest)
Expand Down
37 changes: 33 additions & 4 deletions player/src/wrappers.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "wrappers.h"

#include <sstream>
#include "latebit/sid/synth/Tune.h"

using namespace sid;
Expand Down Expand Up @@ -76,21 +77,49 @@ auto setTracksCount(Tune &tune, int count) -> unique_ptr<Tune> {
const int currentCount = tune.getTracksCount();

vector<unique_ptr<Track>> tracks = {};
printf("min: %d\n", min(count, currentCount));
for (int i = 0; i < min(count, currentCount); i++) {
printf("pushing track %d\n", i);
tracks.push_back(make_unique<Track>(*tune.getTrack(i)));
}

printf("remainder: %d\n", count - currentCount);
for (int i = 0; i < count - currentCount; i++) {
printf("pushing new track\n");
tracks.push_back(make_unique<Track>());
}

return make_unique<Tune>(tune.getBpm(), tune.getTicksPerBeat(), tune.getBeatsCount(), std::move(tracks));
}

auto symbolFromPitch(int pitch, WaveType wave) -> Symbol {
int note = pitch % 12;
int octave = int(pitch / 12);

stringstream ss;
switch (note) {
case 0: ss << "C-"; break;
case 1: ss << "C#"; break;
case 2: ss << "D-"; break;
case 3: ss << "D#"; break;
case 4: ss << "E-"; break;
case 5: ss << "F-"; break;
case 6: ss << "F#"; break;
case 7: ss << "G-"; break;
case 8: ss << "G#"; break;
case 9: ss << "A-"; break;
case 10: ss << "A#"; break;
case 11: ss << "B-"; break;
default: ss << "--";
}

ss << octave;
ss << wave;
ss << "--";

return ss.str();
}

auto pitchFromSymbol(Symbol sym) -> int {
return Note::fromSymbol(sym).getPitch();
}

auto createEmptyTune(int bpm, int ticksPerBeat, int beatsCount, int tracksCount) -> unique_ptr<Tune> {
std::vector<std::unique_ptr<Track>> tracks;
for (int i = 0; i < tracksCount; i++) {
Expand Down
3 changes: 3 additions & 0 deletions player/src/wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ auto getTrackSize(Tune &tune, int trackIndex) -> int;
auto setTracksCount(Tune &tune, int count) -> unique_ptr<Tune>;

auto createEmptyTune(int bpm, int ticksPerBeat, int beatsCount, int tracksCount) -> unique_ptr<Tune>;

auto symbolFromPitch(int pitch, WaveType wave) -> string;
auto pitchFromSymbol(Symbol string) -> int;
} // namespace player
3 changes: 2 additions & 1 deletion views/audio/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { state } from './state.js'
import { $metadata } from './components/metadata.js'
import { $playback } from './components/playback.js'
import { $editor } from './components/editor.js'
import { $sound } from './components/sound.js'
import { TuneParser, createEmptyTune } from './sid.js'
import { executeHostCommand, listen, Command, Event } from '../ipc.js'
import { ParserOptions } from './constants.js'
Expand All @@ -17,7 +18,7 @@ const $app = {
onLoad() {
$metadata.init();
$playback.init();
$editor.init();
$sound.init();

try {
/**
Expand Down
1 change: 1 addition & 0 deletions views/audio/components/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const $editor = {
this.$root.replaceWith($root);
// @ts-expect-error This is a valid assignment: $root is a Node, but also an Element
this.$root = $root;
this.$root.classList.add('with-cells');
}
}

Expand Down
54 changes: 54 additions & 0 deletions views/audio/components/sound.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// @ts-check
import { state } from '../state.js';
import { executeHostCommand, Command } from '../../ipc.js';
import { Note, Tune, TuneParser, getNote, setNote, symbolFromPitch } from '../sid.js'

export const $sound = {
/** @type {!HTMLElement} */
// @ts-expect-error
$root: document.getElementById('editor'),
init() {
state.listen('tune', (tune) => {
this.update(tune);
executeHostCommand(Command.UpdateDocumentText, TuneParser.toString(tune));
});

},
update(/** @type {Tune} */ tune) {
const maxTrackLength = tune.getBeatsCount() * tune.getTicksPerBeat();
const $root = this.$root.cloneNode(false);

// TODO: support more tracks
const trackIndex = 0;
// TODO: support more waves
const wave = 0;

for (let i = 0; i < maxTrackLength; i++) {
const $container = document.createElement('div');
$container.classList.add('slider-container');
const $slider = document.createElement('input');
$slider.type = 'range';
$slider.min = '0';
$slider.max = '95';
$slider.classList.add('slider');
$slider.value = getNote(tune, trackIndex, i).getPitch().toString();
const $preview = document.createElement('input');
$preview.disabled = true;
$preview.value = $slider.value;
$slider.addEventListener('change', (e) => {
const field = /** @type {HTMLInputElement} */ (e.target);
$preview.value = field.value;
const note = Note.fromSymbol(symbolFromPitch(field.valueAsNumber, wave))
state.setTune(setNote(tune, trackIndex, i, note));
})
$container.appendChild($slider);
$container.appendChild($preview);
$root.appendChild($container);
}

this.$root.replaceWith($root);
// @ts-expect-error This is a valid assignment: $root is a Node, but also an Element
this.$root = $root;
this.$root.classList.add('with-sliders');
}
}
2 changes: 2 additions & 0 deletions views/audio/sid.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ declare namespace Module {
function setBeatsCount(tune: Tune, beats: number): readonly Tune;
function setTracksCount(tune: Tune, tracks: number): readonly Tune;
function createEmptyTune(bpm: number, ticksPerBeat: number, beatsCount: number): readonly Tune;
function symbolFromPitch(pitch: number, wave: number): string;
function pitchFromSymbol(symbol: string): number;

class Note {
static makeRest(): Note;
Expand Down
6 changes: 5 additions & 1 deletion views/audio/sid.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const {
setBpm,
setTicksPerBeat,
MUSIC_PARSER_OPTIONS,
SOUND_PARSER_OPTIONS
SOUND_PARSER_OPTIONS,
symbolFromPitch,
pitchFromSymbol
} = sid;

export {
Expand All @@ -39,4 +41,6 @@ export {
setTicksPerBeat,
MUSIC_PARSER_OPTIONS,
SOUND_PARSER_OPTIONS,
symbolFromPitch,
pitchFromSymbol
}
95 changes: 56 additions & 39 deletions views/audio/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,61 +10,78 @@ header {
}

#editor {
--input-height: calc(var(--vscode-editor-font-size) * 2);
--input-width: 7ch;
--delete-width: var(--input-height);
--column-size: calc(var(--input-width) + var(--delete-width));
--index-size: 4ch;
--beat-gap: calc(var(--container-paddding) / 2);

flex: 3;

display: grid;
gap: var(--container-paddding);
grid-template-columns: var(--index-size) repeat(auto-fill, var(--column-size));

.beat {
margin-top: var(--beat-gap);
font-weight: bolder;
}
&.with-cells {
--input-height: calc(var(--vscode-editor-font-size) * 2);
--input-width: 7ch;
--delete-width: var(--input-height);
--column-size: calc(var(--input-width) + var(--delete-width));
--index-size: 4ch;
--beat-gap: calc(var(--container-paddding) / 2);

.cell {
display: flex;
gap: var(--container-paddding);
grid-template-columns: var(--index-size) repeat(auto-fill, var(--column-size));

&.beat {
.beat {
margin-top: var(--beat-gap);

.input {
font-weight: bolder;
}
font-weight: bolder;
}

.delete {
width: var(--input-height);
background-color: var(--vscode-input-background);
color: var(--vscode-input-background);
.index {
font-size: var(--vscode-editor-font-size);
font-family: var(--vscode-editor-font-family);
height: var(--input-height);
text-align: right;
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
}

&:hover {
.cell {
display: flex;

&.beat {
margin-top: var(--beat-gap);

.input {
font-weight: bolder;
}
}

.delete {
color: var(--vscode-input-foreground);
width: var(--input-height);
background-color: var(--vscode-input-background);
color: var(--vscode-input-background);
}
}

.input {
font-size: var(--vscode-editor-font-size);
font-family: var(--vscode-editor-font-family);
height: var(--input-height);
width: var(--input-width);
&:hover {
.delete {
color: var(--vscode-input-foreground);
}
}

.input {
font-size: var(--vscode-editor-font-size);
font-family: var(--vscode-editor-font-family);
height: var(--input-height);
width: var(--input-width);
}
}
}

.index {
font-size: var(--vscode-editor-font-size);
font-family: var(--vscode-editor-font-family);
height: var(--input-height);
text-align: right;
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
&.with-sliders {
gap: 8px;
grid-template-columns: repeat(auto-fill, 4ch);

.slider-container {
display: flex;
flex-direction: column;

.slider {
flex: 1;
writing-mode: vertical-lr;
}
}
}
}

Expand Down