MyGo 云音乐
用 Go 与 MyGo 写成的第三方网易云音乐桌面客户端。原生界面,没有 WebView;边下边播,无损与 Hi-Res,逐字歌词与「律动」,刘海播放器。
Translation pending
Live coding studio in the browser: Strudel, Hydra and MIDI.
MediaTypeScript · HTMLGitHub @verax90OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Estudio de live coding en el navegador: Strudel + Hydra + MIDI
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A live coding studio in the browser for joedoe.dev: sound with Strudel, visuals with Hydra and MIDI control, all in one editor. English and Spanish.Read the full README ↗A live coding studio in the browser for joedoe.dev: sound with Strudel, visuals with Hydra and MIDI control, all in one editor. English and Spanish.
?sala=<id>); listeners hear your code made in their own browser, with your visual, every play and stop, and the kits of your own samples the code uses. Browsers connect directly over WebRTC (PeerJS, whose free public server only introduces them; loaded only when a room is used). Not sent: your voice or free play. Each browser keeps its own bar line. With "share the image" ticked (the default), what the webcam visuals show (camera, video or tab) goes as WebRTC video and the listeners' webcam visuals use it; their own camera is never opened.bass: …) with Another, Mute and Remove. The Arrangement grid sets when each layer plays, in 8 parts of 4 bars (32 bars that loop), written as a .mask("<1!8 0!4 …>") at the end of the line so hand edits stay; Typical song fills in an intro, entries, a break and an ending.$: track (and turns loose patterns into tracks), so everything you add plays together. _$: or _name: mutes one..md for the Sessions gallery on joedoe.dev (/music/sessions), with the code, the visual and room for a YouTube link..js files that open again from the menu or by dropping them on the page.? key; in the menu on phones): getting started, shortcuts, every MPK Mini control in each mode, the panels and troubleshooting.H("...")), others listen to the audio; Auto changes to another every 4 bars. Lines stacks lines with mountains in the middle, after the Unknown Pleasures cover and FLUUUID's Lines: each new line is what sounds now (read off the limiter, so any pattern moves it), entering at the front and moving back, drawn on a canvas Hydra reads as s1.src/clips.ts, src/clips-panel.ts): footage played like samples, after Abjext's idea of an Octatrack for video. Videos (your files, or downloads from Find) are kept in IndexedDB; the name under each pad is what s("…") calls it, and clips sharing a name are a pool dealt like cards (all once, reshuffled, never the same twice in a row; name:2 picks one). .video(layer) on a pattern (an onTrigger, so it makes no sound) fires each hit: begin (else a random spot), speed (negative steps backwards), gain (opacity), legato/clip (how long it shows; without it, it holds), coarse (pixels) and fx("bw_mirror") (inv, bw, mirror, flip, zoom, hue, blur, freeze, strobe; broken signal and light: rgb, slice, echo, glow, flash; add, screen, diff, mult, lighten, overlay, dodge, hard blend layers 1-3; bands, cols, tiles, half cut a layer into pieces woven with the one under it; glitches: mosh (a fake datamosh: only some blocks of the new picture get through each frame, the rest keeps the old picture sliding, starting from what was on screen), blocks, melt (pixel-sort drips), vhs (tracking wobble and a rolling band), stutter (back to the hit's start ~9 times a second), burn, scan; glitch picks one per hit, and the panel's Glitch slider adds one at random to any hit that often). key and dark are luma keys (SVG feColorMatrix filters the canvas borrows): the dark or the light parts let the layers under show. A clip can have a marked part (⤢ opens it big; I and O mark start and end, the start frame becomes the pad's picture): random starts fall inside it and playback loops in it. A layer mixer in the panel picks the layer pads, keys and MIDI fire on and sets each layer's opacity and, for layers 2-4, how it mixes (auto leaves it to the hit's fx; normal, screen, add, diff, mult, overlay, lighten, key, dark, tiles, bands, cols or half replace it), so clips can be laid over each other without code; × empties a layer. "✂ Another piece" in the viewer makes one more clip from the same stored video (same name, so the pieces are a pool; nothing is copied, and the video is deleted only with its last piece). With "Only while held", letting go of a pad, key or MIDI note takes its clip off again (MIDI note-offs are now an event of their own). 🎲 Fill downloads three odd, light clips (≤ 60 MB) from NASA, Commons, Prelinger and VHS Vault at random, all named raro. Pads of the clips on screen light up (with their layer above 0), and keys 1–9 fire the first nine clips when not typing (Shift: layer 1, screen). "Into the code" writes three layers at once (cuts, woven pieces, bursts of light), after Abjext's stacked look. Clips visuals add a kick-driven feedback tunnel, broken RGB signal, a dot screen, footage rising out of static, glow, a Hydra datamosh (the last frame pushed by the new one's shapes, refreshed once a bar) and a crash on the highs. Strudel hands hits over ~0.2 s early: a video not on screen jumps to its spot then, so the cut lands on the beat, and while a video is still seeking the layer keeps the previous picture. The layers are drawn on a 640 × 360 canvas that Hydra reads as (the Clips visuals; in your own code), so the VJ effects, ASCII and video recording include them. Pads fire on press; with "MIDI notes fire clips", note 36 (the MPK's first pad) is clip 1 and velocity sets opacity. shows hits per second and how many frames were drawn while seeking.src/clip-search.ts): searches Prelinger, VHS Vault and all of Internet Archive (advancedsearch), NASA's image and video library and Wikimedia Commons from the browser, with suggested odd searches and Surprise me; a picture plays a preview straight from the source, Take downloads the lightest playable file (≤ 150 MB) into the pool, named after the search. Internet Archive does not let pages read its video files (no CORS), and the pixels have to be readable for Hydra, so they go through /ia/<host>/<path>: a Vite middleware in development, the edge function api/ia.js online (a vercel.json rewrite; only *.archive.org and NASA's video host). worker/ia-proxy.js does the same as a Cloudflare Worker, if the site ever leaves Vercel (point VITE_IA_PROXY at it). Each clip keeps a link to its page for the credit.src/youtube.ts): paste a link and Add (or Enter) and it joins the end of the queue without touching the video on: YouTube's own IFrame API player (loaded on first use) plays one video and, when it ends, the next (the first again after the last); ▶ plays one now, ✕ takes one out, Next skips, Remove all stops. Titles come from YouTube's oEmbed; a video its author does not let embed is skipped with a word; captions are kept off; a playlist link plays that list. The queue is remembered.setcps line.tracks.ts stemCode: every playing $: or named track, or each part of a stack(...), gets .orbit(11…), without touching the editor, also after Ctrl+Enter); superdough gives every orbit its own output, and each is tapped as it appears. Files are named after the track's label or its main sound (2_bd, bass). Voices that set their own orbit keep it. The zip is written without a library (stored, CRC-32).s("name") or s("kit:3"). They stay in the browser (IndexedDB). The MPK's bank B pads can play one of your kits, eight sounds at a time (MIDI panel), like pad banks on an MPC. Kits open into their numbered sounds: ▶ to hear one, + to pick it, and Add as a track puts the picked ones in your code in that order, with a .cut(n) of its own, so a sample longer than its step (a phrase, a loop) stops when it plays again instead of piling up on itself, and one track never cuts another.src/tools.json, notes in es/en), and under each section the reading: languages to install, communities, tutorials. A few open inside the studio.$: tracks, quantised to eighths or sixteenths, one bracket per bar. Over a playing pattern it starts on its next bar and measures against what you hear (the scheduler's now() minus its latency); with nothing playing, a one-bar count-in and a click at the BPM field's tempo. Or hum it: with From: Voice the take is the mic before its effects, over the same bars (shifted by the looper's mic lag); src/hum.ts reads its pitch every 10 ms with YIN at 12 kHz, each grid step takes the median of its voiced moments, a note that goes on is held with _ unless the voice dipped (re-sung), and in a key (Play in a key) each note moves to the scale. You get note("c4 e4 g4 _ ~ a4 a4 g4").s(<keys sound>). Best with headphones, so the music stays out of the mic.cc each pad or knob sends, to use with midin() and midikeys()..color(...tint(0.4))..mask(part(n)), parts switch on and off with the number keys 1-8, a click on the strip at the bottom, or MPK pads in CC mode. The "Scenes" pattern is a ready example. MIDI program change can also switch patterns instead of visuals (MIDI panel).src/news.ts): the latest additions, one line each with an Open button that takes you there (its panel, tab, visual or recording mode); the ? button carries a dot until you have seen them, and the help opens on them then. Panels close from their own ✕ or with Escape.src/play-state.ts): ▶ Play when stopped; ▶ Playing, outlined, when what sounds is what you see; ↻ Apply, lit and breathing, when the code has changes not applied yet (whitespace does not count, and code that failed to evaluate is not applied, since Strudel keeps the version before). The button keeps its width, so the bar never shifts.setcps at the BPM field's tempo and a kick), in one edit, so Ctrl+Z brings back what you had..js, a session page for joedoe.dev) and opening a .js (src/project.ts).src/finder.ts). On phones the More menu lays its items two to a row.marcas_audacity.txt (Audacity: File → Import → Labels, and the markers sit on the waveform) and sesion.md, the chapters (they paste into a YouTube description) and every version of the code at its time, so a good moment can be cut out and its code taken back (src/session.ts).touch() is how strong the touch is (its CC 90, 0 to 1, eased, back to 0 when you let go; units that send no CC 90, a setting, get it from the note, over the range of notes seen so far) and touching() whether anyone touches, for patterns (.lpf(ref(() => 300 + touch() * 5000))) and Hydra. The device is found by its name or as whatever sends CC 90. A "TouchMe" pattern and a "Touch" visual use them.
touchPulse() is 1 the moment hands meet (each TouchMe note-on) and fades in about a quarter of a second, for flashes. Visuals for it, kept out of Auto: Touch (TouchMe), Touch (ripples) (rings that open and speed up with contact, flashing on each touch), Touch (sparks) (none at rest, more with more contact, with trails) and Touch (heartbeat) (a circle that beats on each touch and grows with contact)..sp() and its events go out as MIDI instead of sound, with Web MIDI timestamps so they leave on time. s("a1 ~ a5 a1").sp() plays pads by bank and number (the SP-404MKII's MIDI note map, mode A or B), n("1 5 9 13").sp("b") pads of one bank, note("c3 e3").sp() the chromatic sample (channel 16), ccn(16).ccv(…).midichan(1).sp() the effect controls. "Send the tempo" stacks 96 clock ticks per cycle onto the playing pattern, so the clock comes from the same scheduler as the notes, plus Start on cycle 0 and Stop. Notes echoed back by the device are ignored by free play.capa1, capa2…) and added as a line, $: s("capa1").loopAt(2) (with .late(n) when the take did not start on a multiple of its length), so it can be muted, turned up or edited like any track. The first repeat is played by an AudioBufferSourceNode at the audio time of the next loop, since Strudel has already planned that bar; the line takes over from the one after. With the mic, the output latency (capped at 150 ms) plus 10 ms and a Timing nudge are taken off the take. Undo the last layer, or remove all.src/cycle-clock.ts, a silent event on every beat stacked onto the playing pattern, whose trigger gets the exact audio time Strudel schedules it for (the riff recorder uses it too). Saved normalised, twice: s("muestra1") whole (.loopAt(bars) plays it in time) and s("muestra1_trozos"), its chops for the pads or n("0 3 1 …"): 4, 8 or 16 equal, or 8 or 16 by hits (the strongest rises of the onset curve, each cut refined to 2 ms before its attack), shown on the waveform before saving; stored with your other samples.suppressLocalAudioPlayback). It goes into the master before the limiter, so the volume knob, recordings, the audio waves and the Lines visual get it; not sent to a live room. A file can start from the top with the studio's Play (when the first bar is heard) and stop and rewind with Stop; volume and repeat. Detect the tempo reads a decoded song's first minute at once (or listens to a tab for 10 s): an onset curve from the rise in energy every 10 ms, autocorrelated between 60 and 180 BPM with a gentle preference for 80-120, so a beat is not read as its double; the result goes into the BPM field, with ÷2 and ×2 to fix a half or double reading.src/voice.ts reads the voice's pitch 40 times a second (YIN from hum.ts on the mic before its effects, the median of the last five readings) and shows the note and how many cents off. Your voice plays a synth: an oscillator (saw, square, triangle, sine) through a gentle low-pass follows it free, to the nearest semitone or to the key, −2 to +1 octaves, as loud as you sing and silent when you stop; a reading that is the synth's own note coming back through the mic is ignored (possible when it is an octave away), but headphones are best. voiceNote() (MIDI, 0 when silent), voiceHeld() (the last note sung), voiceLevel() (0–1, quick up and slow down) and voiceColor(0|1|2) (the note's colour, the twelve notes round the colour wheel from C red, gliding) are globals for patterns and Hydra; the visuals Voice (colour by note) and Voice (shapes by pitch), kept out of Auto, use them.src/drop.ts). A TR-909 snare roll in four stages (4, 8, 16, 32 hits a bar) growing louder, handed to Strudel a quarter of a second ahead so Stop leaves nothing behind; white noise through a band-pass rising from 300 Hz to 9 kHz; a high-pass between the master and the limiter taking everything that plays up to 900 Hz; optionally an eighth of a bar of silence; and on the one, all back with a boom (a sine falling 110 → 40 Hz) and a crash. Bar times come from cycle-clock.ts; measured, the drop lands within 10 ms of the bar.duckorbit does the ducking, but everything plays on orbit 1, the kick too; so src/pump.ts maps every value on its way to the scheduler (a withValue around setPattern, read on each sound so switching it needs no new Play): a kick (bd, kick) without an orbit moves to orbit 9 and ducks every other orbit that has played and exists, with the amount as duckdepth, a 4 ms duckonset and the swell back in beats as duckattack. Stems keep working: their voices have orbits, and a kick ducks the others.mando.html, a separate light page (no Strudel, no Hydra; 2.5 kB plus PeerJS) with an XY pad, 8 pads and 8 knobs, connected to the studio over WebRTC like the live room. Pads arrive as the MPK's bank B pads (notes 32-39) and knobs as its knobs (CC 1-8), so free play, the master volume, the VJ and the knob HUD all respond; the XY is phoneX() and phoneY(). The studio's id is kept, so the link stays the same and the phone reconnects on its own; the QR code comes from qrcode-generator, loaded only then.o1 and a chain reading globalThis.vj draws it into o0, so moving a control runs nothing again; neutral leaves the picture as it was. Sliders, or MPK knobs 1–6 when ticked (the knob HUD follows).._pianoroll() for notes, ._punchcard() for the rest, with a .color() from a palette of six when it has none, which also colours its highlighted notes. One edit on, one off (it takes out only what it added; your own colours stay), so Ctrl+Z undoes it; the views sit on a dark band so they read over any visual (withSequencer in src/tracks.ts, src/sequencer.ts).Ctrl+Shift+H hides the code and leaves only the visuals.English by default. The studio follows ?lang=en|es, then the jd-lang cookie shared with joedoe.dev, then the browser language. The EN / ES button switches and remembers it.
| Key | Action |
|---|---|
Ctrl+Enter | Play / update |
Ctrl+. | Stop |
Ctrl+Shift+H | Show or hide the code |
Ctrl+Z / Ctrl+Shift+Z | Undo / redo |
1–8 | Switch scene parts on and off |
? | Help |
← → / PageUp PageDown | Previous / next song in a live set |
Add .analyze(1) at the end of your pattern and use these in Hydra code (they return 0 to 1):
| Function | Measures |
|---|---|
bass() | lows (20–150 Hz), made for the kick |
mid() | mids (150–2000 Hz) |
high() | highs (2–10 kHz), hats and cymbals |
level() | overall volume |
bend() | MIDI pitch bend, from -1 to 1 (e.g. the MPK joystick sideways) |
osc(10, 0.1).scale(() => 1 + bass()).out()
Needs Node 22 and pnpm.
pnpm install
pnpm dev
pnpm build
Web MIDI only works in Chromium browsers (Chrome, Edge, Brave) and on localhost or HTTPS.
Strudel is pinned to an exact version (@strudel/repl 1.3.0) because the studio
reaches past its public editor into a few internals. After any upgrade, check:
| What | Used for | Where |
|---|---|---|
Globals set by Strudel's evalScope: samples, initHydra, getAnalyzerData, getAudioContext, getSuperdoughAudioController, superdough | samples, bundled Hydra, audio-reactive visuals, limiter, free play | samples.ts, visuals.ts, limiter.ts, freeplay.ts |
getSuperdoughAudioController().output.destinationGain | inserting the limiter and the recording tap | limiter.ts, record.ts |
<strudel-editor>'s editor (StrudelMirror: code, setCode, evaluate, stop, repl.scheduler.started) and its update event (error, pending) | the whole UI, error bar | main.ts, status.ts |
The strudel.log document event and its message texts (load-sample, [getTrigger] error: …) | loading notice, runtime errors | status.ts |
midin(name)(cc, channel) and midikeys(name)() | the MIDI presets | presets.ts |
$: and named labels (bass:) become .p(id); once any exists, the bare last expression is dropped; _ in front mutes | Insert, Compose | tracks.ts, compose.ts |
H(pattern) reads the pattern at getTime(), and only the transpiler turns "..." into mini-notation | built-in visuals, which run without it, pass strings through mini() | visuals.ts |
WebMidi.js (behind midin) assigns input.onmidimessage | the studio listens with addEventListener so neither silences the other | midi.ts |
@strudel/repl bundles its own CodeMirror, so imports are separate instances |
AGPL-3.0-or-later, the same as Strudel and Hydra, which it builds on.
Icons: Lucide (ISC), drawn as CSS masks in src/icons.css, generated by scripts/icons.py from lucide-static.
s2src(s2)?debug@codemirror/*| undo/redo press the editor's own Ctrl+Z |
undo.ts |